From bba6bd797edd9b22e74c359b7cd1b2aaf30e482a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Mar 2026 00:43:45 +0000 Subject: [PATCH 1/3] Fix copy button missing on tool results and context items The querySelectorAll for copy buttons only matched '.message-content pre' and 'pre.highlighted-code', missing plain-text and ANSI tool results (pre.tool-result-pre) and plain-text context items (pre.context-content). This was a regression from when terminal output copy support was added. https://claude.ai/code/session_01NFAcXUkWmPRkZPahwDWPCf --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 337e03c..c7d3d3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3123,8 +3123,8 @@ document.addEventListener('DOMContentLoaded', function() { } }); - // Copy buttons for all code blocks (fenced, highlighted, and tool-result) - document.querySelectorAll('.message-content pre, pre.highlighted-code').forEach(function(pre) { + // Copy buttons for all code blocks (fenced, highlighted, tool-result, and context items) + document.querySelectorAll('.message-content pre, pre.highlighted-code, pre.tool-result-pre, pre.context-content').forEach(function(pre) { var wrapper = document.createElement('div'); wrapper.className = 'code-block-wrapper'; pre.parentNode.insertBefore(wrapper, pre); From 62b38d29132e56472e106feaacafc1042c5f3a28 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Mar 2026 00:50:05 +0000 Subject: [PATCH 2/3] Add copy button tests covering all block types Tests for: plain-text tool results, ANSI terminal output, syntax-highlighted tool results, plain-text context items, syntax-highlighted context items, fenced code blocks (known and unknown languages), and the JS selector string. https://claude.ai/code/session_01NFAcXUkWmPRkZPahwDWPCf --- src/main.rs | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) diff --git a/src/main.rs b/src/main.rs index c7d3d3f..b02df4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5358,6 +5358,335 @@ mod tests { assert!(html.contains("data-copy-text=\"npm test\"")); } + // ----------------------------------------------------------------------- + // Copy button coverage for all block types + // ----------------------------------------------------------------------- + + #[test] + fn test_copy_button_plain_text_tool_result() { + // A tool result with no syntax highlighting (unknown extension) + // should get class "tool-result-pre" so the copy button JS matches it. + let session = Session { + session_id: "copy-plain-tool".to_string(), + title: "Copy Plain Tool".to_string(), + workspace_directory: "/tmp".to_string(), + history: vec![ + ChatHistoryItem { + message: ChatMessage { + role: "assistant".to_string(), + content: MessageContent::Text("Reading file.".to_string()), + tool_calls: Some(vec![ToolCallDelta { + function: Some(ToolCallFunction { + name: "Read".to_string(), + arguments: r#"{"file_path": "/tmp/data"}"#.to_string(), + }), + }]), + usage: None, + }, + context_items: vec![], + prompt_logs: None, + tool_call_states: None, + }, + ChatHistoryItem { + message: ChatMessage { + role: "tool".to_string(), + content: MessageContent::Text( + "some plain text content here".to_string(), + ), + tool_calls: None, + usage: None, + }, + context_items: vec![], + prompt_logs: None, + tool_call_states: None, + }, + ], + date_created: Some("2025-01-01".to_string()), + }; + + let html = render_session(&session, None); + // The tool result should be in a
 block
+        assert!(
+            html.contains("tool-result-pre"),
+            "plain text tool result should have tool-result-pre class"
+        );
+        assert!(
+            html.contains("
"),
+            "plain text tool result should render as 
"
+        );
+    }
+
+    #[test]
+    fn test_copy_button_ansi_tool_result() {
+        // Terminal output with ANSI escape codes should get "tool-result-pre"
+        // class so the copy button JS matches it.
+        let session = Session {
+            session_id: "copy-ansi".to_string(),
+            title: "Copy ANSI".to_string(),
+            workspace_directory: "/tmp".to_string(),
+            history: vec![
+                ChatHistoryItem {
+                    message: ChatMessage {
+                        role: "assistant".to_string(),
+                        content: MessageContent::Text("Running tests.".to_string()),
+                        tool_calls: Some(vec![ToolCallDelta {
+                            function: Some(ToolCallFunction {
+                                name: "Bash".to_string(),
+                                arguments: r#"{"command": "npm test"}"#.to_string(),
+                            }),
+                        }]),
+                        usage: None,
+                    },
+                    context_items: vec![],
+                    prompt_logs: None,
+                    tool_call_states: None,
+                },
+                ChatHistoryItem {
+                    message: ChatMessage {
+                        role: "tool".to_string(),
+                        content: MessageContent::Text(
+                            "\x1b[32m✓ pass\x1b[0m  \x1b[31m✕ fail\x1b[0m".to_string(),
+                        ),
+                        tool_calls: None,
+                        usage: None,
+                    },
+                    context_items: vec![],
+                    prompt_logs: None,
+                    tool_call_states: None,
+                },
+            ],
+            date_created: Some("2025-01-01".to_string()),
+        };
+
+        let html = render_session(&session, None);
+        assert!(
+            html.contains("
"),
+            "ANSI tool result should render as 
"
+        );
+        // Should contain converted ANSI spans, not raw escape codes
+        assert!(
+            html.contains(""),
+            "plain text context item should render as 
"
+        );
+    }
+
+    #[test]
+    fn test_copy_button_highlighted_context_item() {
+        // Context items with a known extension should get both
+        // "highlighted-code" and "context-content" classes.
+        let session = Session {
+            session_id: "copy-ctx-hl".to_string(),
+            title: "Copy Ctx HL".to_string(),
+            workspace_directory: "/tmp".to_string(),
+            history: vec![ChatHistoryItem {
+                message: ChatMessage {
+                    role: "user".to_string(),
+                    content: MessageContent::Text("Check this file".to_string()),
+                    tool_calls: None,
+                    usage: None,
+                },
+                context_items: vec![ContextItem {
+                    name: "src/app.py".to_string(),
+                    description: "Python source".to_string(),
+                    content: "import os\nprint('hello')".to_string(),
+                }],
+                prompt_logs: None,
+                tool_call_states: None,
+            }],
+            date_created: Some("2025-01-01".to_string()),
+        };
+
+        let html = render_session(&session, None);
+        assert!(
+            html.contains("highlighted-code context-content"),
+            "highlighted context item should have both highlighted-code and context-content classes"
+        );
+    }
+
+    #[test]
+    fn test_copy_button_fenced_code_block() {
+        // Markdown fenced code blocks inside message-content should have
+        // a 
 that the JS selector ".message-content pre" matches.
+        // Syntect-highlighted blocks get class="highlighted-code"; unknown
+        // languages fall back to .
+        let session = Session {
+            session_id: "copy-fenced".to_string(),
+            title: "Copy Fenced".to_string(),
+            workspace_directory: "/tmp".to_string(),
+            history: vec![ChatHistoryItem {
+                message: ChatMessage {
+                    role: "assistant".to_string(),
+                    content: MessageContent::Text(
+                        "Here is some code:\n```rust\nfn main() {}\n```".to_string(),
+                    ),
+                    tool_calls: None,
+                    usage: None,
+                },
+                context_items: vec![],
+                prompt_logs: None,
+                tool_call_states: None,
+            }],
+            date_created: Some("2025-01-01".to_string()),
+        };
+
+        let html = render_session(&session, None);
+        // The code block should be inside a message-content div
+        assert!(
+            html.contains("message-content"),
+            "assistant message should have message-content class"
+        );
+        // Syntect highlights known languages → 
+        // which lives inside the message-content div, matched by both
+        // ".message-content pre" and "pre.highlighted-code"
+        assert!(
+            html.contains("
 inside message-content,
+        // matched by ".message-content pre".
+        let session = Session {
+            session_id: "copy-fenced-unk".to_string(),
+            title: "Copy Fenced Unk".to_string(),
+            workspace_directory: "/tmp".to_string(),
+            history: vec![ChatHistoryItem {
+                message: ChatMessage {
+                    role: "assistant".to_string(),
+                    content: MessageContent::Text(
+                        "Example:\n```xyzlang\nfoo bar\n```".to_string(),
+                    ),
+                    tool_calls: None,
+                    usage: None,
+                },
+                context_items: vec![],
+                prompt_logs: None,
+                tool_call_states: None,
+            }],
+            date_created: Some("2025-01-01".to_string()),
+        };
+
+        let html = render_session(&session, None);
+        assert!(
+            html.contains("message-content"),
+            "assistant message should have message-content class"
+        );
+        // Unknown language → plain 

+        assert!(
+            html.contains("language-xyzlang"),
+            "unknown-language fenced code block should have language-* class"
+        );
+    }
+
+    #[test]
+    fn test_copy_button_js_selector_covers_all_classes() {
+        // The JS querySelectorAll for copy buttons must include selectors
+        // for all block types: message-content pre, highlighted-code,
+        // tool-result-pre, and context-content.
+        let session = Session {
+            session_id: "selector-test".to_string(),
+            title: "Selector Test".to_string(),
+            workspace_directory: "/tmp".to_string(),
+            history: vec![],
+            date_created: Some("2025-01-01".to_string()),
+        };
+
+        let html = render_session(&session, None);
+        // Verify the JS selector string covers all needed classes
+        assert!(
+            html.contains(
+                "querySelectorAll('.message-content pre, pre.highlighted-code, pre.tool-result-pre, pre.context-content')"
+            ),
+            "JS copy button selector must cover message-content pre, highlighted-code, tool-result-pre, and context-content"
+        );
+    }
+
     #[test]
     fn test_container_width_expanded() {
         let session = Session {

From ad45e88cde48a73977f0a2b96b600e8d58a69fb3 Mon Sep 17 00:00:00 2001
From: Claude 
Date: Tue, 3 Mar 2026 00:50:12 +0000
Subject: [PATCH 3/3] Bump version to 0.13.1

https://claude.ai/code/session_01NFAcXUkWmPRkZPahwDWPCf
---
 Cargo.toml                              | 2 +-
 README.md                               | 8 ++++----
 pyproject.toml                          | 2 +-
 python/continue_transcripts/__init__.py | 2 +-
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index c195097..a5c602a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "continue-transcripts"
-version = "0.13.0"
+version = "0.13.1"
 edition = "2021"
 description = "Convert continue.dev session files to readable HTML transcripts"
 license = "MIT"
diff --git a/README.md b/README.md
index b90377c..d430e36 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ Install globally so the `continue-transcripts` command is always available:
 ```sh
 uv tool install continue-transcripts \
   --no-index \
-  --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.13.0
+  --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.13.1
 ```
 
 The `continue-transcripts` command is then available on your `PATH`.
@@ -83,7 +83,7 @@ To upgrade later (update the version in the URL):
 ```sh
 uv tool install --upgrade continue-transcripts \
   --no-index \
-  --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.13.0
+  --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.13.1
 ```
 
 To uninstall:
@@ -100,7 +100,7 @@ Run without installing:
 uvx \
   --no-index \
   --from continue-transcripts \
-  --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.13.0 \
+  --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.13.1 \
   continue-transcripts ./sessions
 ```
 
@@ -115,7 +115,7 @@ uv venv .venv
 source .venv/bin/activate
 uv pip install continue-transcripts \
   --no-index \
-  --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.13.0
+  --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.13.1
 ```
 
 ### Building from source
diff --git a/pyproject.toml b/pyproject.toml
index 5f875e9..32d4d9f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "maturin"
 
 [project]
 name = "continue-transcripts"
-version = "0.13.0"
+version = "0.13.1"
 description = "Convert continue.dev session files to readable HTML transcripts"
 readme = "README.md"
 license = { text = "MIT" }
diff --git a/python/continue_transcripts/__init__.py b/python/continue_transcripts/__init__.py
index 3fcfefa..154bfb9 100644
--- a/python/continue_transcripts/__init__.py
+++ b/python/continue_transcripts/__init__.py
@@ -1,3 +1,3 @@
 """continue-transcripts - Convert continue.dev session files to readable HTML transcripts."""
 
-__version__ = "0.13.0"
+__version__ = "0.13.1"