From 1460e75967562bc9ee33b313f86c9d9eea26d36b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 21:49:09 +0000 Subject: [PATCH 1/4] Expand date column width in index.html Set an explicit width (22ch) on the Date column header and add white-space:nowrap to date cells so dates display fully without wrapping. https://claude.ai/code/session_01KtatZkskohpnjVjyphLdTD --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 86bf4fc..16fa014 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3160,7 +3160,7 @@ fn render_index(entries: &[(String, String, String)]) -> String { let mut rows = String::new(); for (title, filename, date) in entries { rows.push_str(&format!( - " \n {title}\n {date}\n \n", + " \n {title}\n {date}\n \n", filename = encode_text(filename), title = encode_text(title), date = encode_text(date), @@ -3197,7 +3197,7 @@ fn render_index(entries: &[(String, String, String)]) -> String { - + {rows} From 550200ff68935a77178e128ba286598c52f47e92 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 21:52:26 +0000 Subject: [PATCH 2/4] Decode file:/// URIs in tool result content Tool results (the green collapsible blocks) contained raw percent-encoded file URIs like "file:///c%3A/Users/...". Added decode_file_uris_in_text() that finds all file:/// URIs in free-form text and decodes them into normal paths, applied in render_tool_result_inline before rendering. https://claude.ai/code/session_01KtatZkskohpnjVjyphLdTD --- src/main.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 16fa014..70832b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -993,7 +993,8 @@ fn render_tool_result_inline( tool_name: &str, tool_args: &str, ) -> String { - let content_text = item.message.content.text(); + let raw_content = item.message.content.text(); + let content_text = decode_file_uris_in_text(&raw_content); let label = if tool_name.is_empty() { "Tool Result".to_string() } else { @@ -2798,6 +2799,28 @@ fn is_path_key(key: &str) -> bool { ) } +/// Find all `file:///...` URIs in free-form text and decode them into +/// normal paths. A URI ends at the first whitespace, `"`, `'`, `)`, `]`, +/// `>`, or end-of-string. +fn decode_file_uris_in_text(text: &str) -> String { + const PREFIX: &str = "file:///"; + let mut out = String::with_capacity(text.len()); + let mut rest = text; + while let Some(start) = rest.find(PREFIX) { + out.push_str(&rest[..start]); + let uri_start = &rest[start..]; + // Find the end of the URI + let end = uri_start + .find(|c: char| c.is_whitespace() || matches!(c, '"' | '\'' | ')' | ']' | '>')) + .unwrap_or(uri_start.len()); + let uri = &uri_start[..end]; + out.push_str(&decode_path(uri)); + rest = &uri_start[end..]; + } + out.push_str(rest); + out +} + /// Decode a value for display: strips `file:///` URI prefixes and /// decodes percent-encoded bytes (e.g. `%3A` → `:`). fn decode_path(input: &str) -> String { @@ -3473,6 +3496,37 @@ mod tests { assert_eq!(decode_path("/home/user/file.rs"), "/home/user/file.rs"); } + #[test] + fn test_decode_file_uris_in_text() { + // Windows-style file URI with percent-encoded colon + assert_eq!( + decode_file_uris_in_text("Successfully edited file:///c%3A/Users/test/file.rs"), + "Successfully edited c:/Users/test/file.rs" + ); + // Multiple URIs in one string + assert_eq!( + decode_file_uris_in_text( + "Moved file:///c%3A/old%20dir/a.rs to file:///c%3A/new%20dir/b.rs" + ), + "Moved c:/old dir/a.rs to c:/new dir/b.rs" + ); + // Unix file URI + assert_eq!( + decode_file_uris_in_text("Read file:///home/user/file.rs done"), + "Read /home/user/file.rs done" + ); + // No file URIs — unchanged + assert_eq!( + decode_file_uris_in_text("No URIs here"), + "No URIs here" + ); + // URI at end of string (no trailing space) + assert_eq!( + decode_file_uris_in_text("Edited file:///c%3A/Users/test.rs"), + "Edited c:/Users/test.rs" + ); + } + #[test] fn test_markdown_to_html() { let html = markdown_to_html("**bold** text"); From c0e50aad6240b7865e531b61205c514fa5a38e62 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 21:54:19 +0000 Subject: [PATCH 3/4] Bump version to 0.8.0 https://claude.ai/code/session_01KtatZkskohpnjVjyphLdTD --- CLAUDE.md | 2 +- Cargo.toml | 2 +- README.md | 8 ++++---- pyproject.toml | 2 +- python/continue_transcripts/__init__.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4292c5f..5d406c8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,7 @@ cargo build --release # Or install as a Python tool via uv (recommended for end users) uv tool install continue-transcripts \ --no-index \ - --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.7.0 + --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.8.0 ``` ### Running tests diff --git a/Cargo.toml b/Cargo.toml index 1afdac3..147a1f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "continue-transcripts" -version = "0.7.0" +version = "0.8.0" edition = "2021" description = "Convert continue.dev session files to readable HTML transcripts" license = "MIT" diff --git a/README.md b/README.md index 946407d..9e56369 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.7.0 + --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.8.0 ``` 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.7.0 + --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.8.0 ``` 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.7.0 \ + --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.8.0 \ 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.7.0 + --find-links https://github.com/curtisalexander/continue-transcripts/releases/expanded_assets/v0.8.0 ``` ### Building from source diff --git a/pyproject.toml b/pyproject.toml index c791bf9..42773ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "continue-transcripts" -version = "0.7.0" +version = "0.8.0" 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 8325296..4d7a385 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.7.0" +__version__ = "0.8.0" From 106584c115f1fe79a555c08c6c9297cd24040619 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 21:59:15 +0000 Subject: [PATCH 4/4] Syntax-highlight tool results for any tool with a file path arg Previously only Read/read_file tool results got syntax highlighting. Now any tool result whose arguments contain a file path key (file_path, filepath, notebook_path, path, file) will attempt highlighting based on the file extension. This covers Grep, Write, and other file-oriented tools. https://claude.ai/code/session_01KtatZkskohpnjVjyphLdTD --- src/main.rs | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 70832b2..661f8d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1017,13 +1017,9 @@ fn render_tool_result_inline( html.push_str(&ansi_to_html(&content_text)); html.push_str("\n"); } else { - // For Read/read_file results, try to syntax-highlight based on - // the file extension from the tool arguments. - let highlighted = if tool_name == "Read" || tool_name == "read_file" { - try_highlight_read_result(tool_args, &content_text) - } else { - None - }; + // Try to syntax-highlight based on the file extension from + // the tool arguments (works for any tool with a file path arg). + let highlighted = try_highlight_tool_result(tool_args, &content_text); if let Some(hl) = highlighted { html.push_str(" "); html.push_str(&hl); @@ -1041,15 +1037,15 @@ fn render_tool_result_inline( html } -/// Attempt to syntax-highlight the content of a Read tool result. -/// Extracts the file path from the JSON arguments, strips line-number prefixes -/// (e.g. " 1\t..."), and applies syntax highlighting based on file extension. -fn try_highlight_read_result(tool_args: &str, content: &str) -> Option { +/// Attempt to syntax-highlight the content of a tool result. +/// Extracts a file path from the JSON arguments (checking common key names), +/// strips line-number prefixes (e.g. " 1\t..."), and applies syntax +/// highlighting based on the file extension. +fn try_highlight_tool_result(tool_args: &str, content: &str) -> Option { let parsed: serde_json::Value = serde_json::from_str(tool_args).ok()?; - let file_path = parsed - .get("file_path") - .or_else(|| parsed.get("filepath")) - .and_then(|v| v.as_str())?; + let file_path = ["file_path", "filepath", "notebook_path", "path", "file"] + .iter() + .find_map(|key| parsed.get(*key).and_then(|v| v.as_str()))?; let lang = lang_from_filename(file_path); if lang.is_empty() { return None; @@ -3856,23 +3852,34 @@ mod tests { // ----------------------------------------------------------------------- #[test] - fn test_try_highlight_read_result() { + fn test_try_highlight_tool_result() { let args = r#"{"file_path": "/home/user/test.rs"}"#; let content = " 1\tfn main() {\n 2\t println!(\"hello\");\n 3\t}"; - let result = try_highlight_read_result(args, content); + let result = try_highlight_tool_result(args, content); assert!(result.is_some()); let hl = result.unwrap(); assert!(hl.contains("highlighted-code")); } #[test] - fn test_try_highlight_read_result_no_extension() { + fn test_try_highlight_tool_result_no_extension() { let args = r#"{"file_path": "/home/user/README"}"#; let content = "just some text"; - let result = try_highlight_read_result(args, content); + let result = try_highlight_tool_result(args, content); assert!(result.is_none()); } + #[test] + fn test_try_highlight_tool_result_path_key() { + // Grep-style tool with "path" key pointing to a Python file + let args = r#"{"pattern": "import", "path": "/home/user/app.py"}"#; + let content = "import os\nimport sys"; + let result = try_highlight_tool_result(args, content); + assert!(result.is_some()); + let hl = result.unwrap(); + assert!(hl.contains("highlighted-code")); + } + // ----------------------------------------------------------------------- // Model badge // -----------------------------------------------------------------------
SessionDate
SessionDate