From 87033fd014a93a97a8881d92da66e64160ba174c Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 27 Dec 2025 11:39:36 +0100 Subject: [PATCH 1/8] Add detailed titles for Glob and Read tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add title_GlobInput showing pattern (e.g., "Glob (*.md)") - Enhance title_ReadInput to show line range when limit is provided - "line N" for single line reads - "lines X-Y" for range reads 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- claude_code_log/html/renderer.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/claude_code_log/html/renderer.py b/claude_code_log/html/renderer.py index 893768e2..150be60b 100644 --- a/claude_code_log/html/renderer.py +++ b/claude_code_log/html/renderer.py @@ -27,6 +27,7 @@ BashInput, EditInput, ExitPlanModeInput, + GlobInput, MultiEditInput, ReadInput, TaskInput, @@ -306,7 +307,19 @@ def title_WriteInput(self, message: TemplateMessage) -> str: def title_ReadInput(self, message: TemplateMessage) -> str: input = cast(ReadInput, cast(ToolUseMessage, message.content).input) - return self._tool_title(message, "📄", input.file_path) + summary = input.file_path + # Add line range info if available + if input.limit is not None: + offset = input.offset or 0 + if input.limit == 1: + summary = f"{summary}, line {offset + 1}" + else: + summary = f"{summary}, lines {offset + 1}-{offset + input.limit}" + return self._tool_title(message, "📄", summary) + + def title_GlobInput(self, message: TemplateMessage) -> str: + input = cast(GlobInput, cast(ToolUseMessage, message.content).input) + return self._tool_title(message, "🔍", f"({input.pattern})") def title_BashInput(self, message: TemplateMessage) -> str: input = cast(BashInput, cast(ToolUseMessage, message.content).input) From d33fb142e2a5f3910a88e394460d96319ce53e4d Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 27 Dec 2025 12:15:52 +0100 Subject: [PATCH 2/8] Improve AskUserQuestion tool rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add title_AskUserQuestionInput with "❓ Asking questions..." title - Replace emoji icons (❓/✅) with Q:/A: labels in question/answer blocks - Swap border colors: assistant color for questions, user color for answers - Simplify backgrounds: --answer-bg now matches --question-bg - Add .qa-label CSS class for future styling flexibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- claude_code_log/html/renderer.py | 3 + .../templates/components/global_styles.css | 2 +- .../html/templates/components/todo_styles.css | 14 ++-- claude_code_log/html/tool_formatters.py | 22 +++++-- test/__snapshots__/test_snapshot_html.ambr | 66 ++++++++++++------- test/test_askuserquestion_rendering.py | 10 +-- 6 files changed, 75 insertions(+), 42 deletions(-) diff --git a/claude_code_log/html/renderer.py b/claude_code_log/html/renderer.py index 150be60b..94c40b02 100644 --- a/claude_code_log/html/renderer.py +++ b/claude_code_log/html/renderer.py @@ -281,6 +281,9 @@ def _tool_title( def title_TodoWriteInput(self, message: TemplateMessage) -> str: # noqa: ARG002 return "📝 Todo List" + def title_AskUserQuestionInput(self, message: TemplateMessage) -> str: # noqa: ARG002 + return "❓ Asking questions..." + def title_TaskInput(self, message: TemplateMessage) -> str: content = cast(ToolUseMessage, message.content) input = cast(TaskInput, content.input) diff --git a/claude_code_log/html/templates/components/global_styles.css b/claude_code_log/html/templates/components/global_styles.css index 4d4f1b3f..611bf573 100644 --- a/claude_code_log/html/templates/components/global_styles.css +++ b/claude_code_log/html/templates/components/global_styles.css @@ -42,7 +42,7 @@ --question-accent: #f5a623; --question-bg: #fffbf0; --answer-accent: #4caf50; - --answer-bg: #f0fff4; + --answer-bg: #fffbf0; /* Priority palette (purple intensity - darker = more urgent) */ --priority-600: #7c3aed; diff --git a/claude_code_log/html/templates/components/todo_styles.css b/claude_code_log/html/templates/components/todo_styles.css index 22a35140..c71eed4b 100644 --- a/claude_code_log/html/templates/components/todo_styles.css +++ b/claude_code_log/html/templates/components/todo_styles.css @@ -102,17 +102,17 @@ padding: 12px; background-color: var(--question-bg); border-radius: 6px; - border-left: 3px solid var(--question-accent); + border-left: 3px solid var(--assistant-color); } .question-block:last-child { margin-bottom: 0; } -/* Answered questions in result (lighter, success-tinted) */ +/* Answered questions in result */ .question-block.answered { background-color: var(--answer-bg); - border-left-color: var(--answer-accent); + border-left-color: var(--user-color); } .question-header { @@ -135,9 +135,13 @@ .answer-text { font-size: 1.05em; font-weight: 600; - color: var(--answer-accent); + color: var(--text-primary); line-height: 1.4; - padding-left: 4px; +} + +/* Q: and A: labels */ +.qa-label { + font-weight: 700; } .question-options-hint { diff --git a/claude_code_log/html/tool_formatters.py b/claude_code_log/html/tool_formatters.py index 9b0f6ac0..d367e811 100644 --- a/claude_code_log/html/tool_formatters.py +++ b/claude_code_log/html/tool_formatters.py @@ -62,9 +62,11 @@ def _render_question_item(q: AskUserQuestionItem) -> str: escaped_header = escape_html(q.header) html_parts.append(f'
{escaped_header}
') - # Question text with icon + # Question text with Q: label question_text = escape_html(q.question) - html_parts.append(f'
❓ {question_text}
') + html_parts.append( + f'
Q: {question_text}
' + ) # Options (if present) if q.options: @@ -155,8 +157,12 @@ def format_askuserquestion_result(content: str) -> str: escaped_q = escape_html(question) escaped_a = escape_html(answer) html_parts.append('
') - html_parts.append(f'
❓ {escaped_q}
') - html_parts.append(f'
✅ {escaped_a}
') + html_parts.append( + f'
Q: {escaped_q}
' + ) + html_parts.append( + f'
A: {escaped_a}
' + ) html_parts.append("
") html_parts.append("") @@ -391,8 +397,12 @@ def format_askuserquestion_output(output: AskUserQuestionOutput) -> str: escaped_q = escape_html(qa.question) escaped_a = escape_html(qa.answer) html_parts.append('
') - html_parts.append(f'
❓ {escaped_q}
') - html_parts.append(f'
✅ {escaped_a}
') + html_parts.append( + f'
Q: {escaped_q}
' + ) + html_parts.append( + f'
A: {escaped_a}
' + ) html_parts.append("
") html_parts.append("") diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr index 6ee0eec7..6c41afbd 100644 --- a/test/__snapshots__/test_snapshot_html.ambr +++ b/test/__snapshots__/test_snapshot_html.ambr @@ -54,7 +54,7 @@ --question-accent: #f5a623; --question-bg: #fffbf0; --answer-accent: #4caf50; - --answer-bg: #f0fff4; + --answer-bg: #fffbf0; /* Priority palette (purple intensity - darker = more urgent) */ --priority-600: #7c3aed; @@ -1904,7 +1904,7 @@ --question-accent: #f5a623; --question-bg: #fffbf0; --answer-accent: #4caf50; - --answer-bg: #f0fff4; + --answer-bg: #fffbf0; /* Priority palette (purple intensity - darker = more urgent) */ --priority-600: #7c3aed; @@ -3391,17 +3391,17 @@ padding: 12px; background-color: var(--question-bg); border-radius: 6px; - border-left: 3px solid var(--question-accent); + border-left: 3px solid var(--assistant-color); } .question-block:last-child { margin-bottom: 0; } - /* Answered questions in result (lighter, success-tinted) */ + /* Answered questions in result */ .question-block.answered { background-color: var(--answer-bg); - border-left-color: var(--answer-accent); + border-left-color: var(--user-color); } .question-header { @@ -3424,9 +3424,13 @@ .answer-text { font-size: 1.05em; font-weight: 600; - color: var(--answer-accent); + color: var(--text-primary); line-height: 1.4; - padding-left: 4px; + } + + /* Q: and A: labels */ + .qa-label { + font-weight: 700; } .question-options-hint { @@ -6704,7 +6708,7 @@ --question-accent: #f5a623; --question-bg: #fffbf0; --answer-accent: #4caf50; - --answer-bg: #f0fff4; + --answer-bg: #fffbf0; /* Priority palette (purple intensity - darker = more urgent) */ --priority-600: #7c3aed; @@ -8191,17 +8195,17 @@ padding: 12px; background-color: var(--question-bg); border-radius: 6px; - border-left: 3px solid var(--question-accent); + border-left: 3px solid var(--assistant-color); } .question-block:last-child { margin-bottom: 0; } - /* Answered questions in result (lighter, success-tinted) */ + /* Answered questions in result */ .question-block.answered { background-color: var(--answer-bg); - border-left-color: var(--answer-accent); + border-left-color: var(--user-color); } .question-header { @@ -8224,9 +8228,13 @@ .answer-text { font-size: 1.05em; font-weight: 600; - color: var(--answer-accent); + color: var(--text-primary); line-height: 1.4; - padding-left: 4px; + } + + /* Q: and A: labels */ + .qa-label { + font-weight: 700; } .question-options-hint { @@ -11593,7 +11601,7 @@ --question-accent: #f5a623; --question-bg: #fffbf0; --answer-accent: #4caf50; - --answer-bg: #f0fff4; + --answer-bg: #fffbf0; /* Priority palette (purple intensity - darker = more urgent) */ --priority-600: #7c3aed; @@ -13080,17 +13088,17 @@ padding: 12px; background-color: var(--question-bg); border-radius: 6px; - border-left: 3px solid var(--question-accent); + border-left: 3px solid var(--assistant-color); } .question-block:last-child { margin-bottom: 0; } - /* Answered questions in result (lighter, success-tinted) */ + /* Answered questions in result */ .question-block.answered { background-color: var(--answer-bg); - border-left-color: var(--answer-accent); + border-left-color: var(--user-color); } .question-header { @@ -13113,9 +13121,13 @@ .answer-text { font-size: 1.05em; font-weight: 600; - color: var(--answer-accent); + color: var(--text-primary); line-height: 1.4; - padding-left: 4px; + } + + /* Q: and A: labels */ + .qa-label { + font-weight: 700; } .question-options-hint { @@ -16530,7 +16542,7 @@ --question-accent: #f5a623; --question-bg: #fffbf0; --answer-accent: #4caf50; - --answer-bg: #f0fff4; + --answer-bg: #fffbf0; /* Priority palette (purple intensity - darker = more urgent) */ --priority-600: #7c3aed; @@ -18017,17 +18029,17 @@ padding: 12px; background-color: var(--question-bg); border-radius: 6px; - border-left: 3px solid var(--question-accent); + border-left: 3px solid var(--assistant-color); } .question-block:last-child { margin-bottom: 0; } - /* Answered questions in result (lighter, success-tinted) */ + /* Answered questions in result */ .question-block.answered { background-color: var(--answer-bg); - border-left-color: var(--answer-accent); + border-left-color: var(--user-color); } .question-header { @@ -18050,9 +18062,13 @@ .answer-text { font-size: 1.05em; font-weight: 600; - color: var(--answer-accent); + color: var(--text-primary); line-height: 1.4; - padding-left: 4px; + } + + /* Q: and A: labels */ + .qa-label { + font-weight: 700; } .question-options-hint { diff --git a/test/test_askuserquestion_rendering.py b/test/test_askuserquestion_rendering.py index a77f7f98..22949214 100644 --- a/test/test_askuserquestion_rendering.py +++ b/test/test_askuserquestion_rendering.py @@ -75,8 +75,8 @@ def test_format_askuserquestion_multiple_questions(self): assert "Treat .tar/.tar.gz like .zip" in html assert "Only process tar archives inside ZIP files" in html - # Check question icon - assert "❓" in html + # Check question label + assert "Q:" in html # Check select hint assert "(select one)" in html @@ -149,7 +149,7 @@ def test_format_askuserquestion_legacy_single_question(self): # Should still render the question assert 'class="askuserquestion-content"' in html assert "What is your preference?" in html - assert "❓" in html + assert "Q:" in html def test_format_askuserquestion_no_options(self): """Test AskUserQuestion formatting without options.""" @@ -224,8 +224,8 @@ def test_format_result_single_qa(self): assert 'class="question-block answered"' in html assert "What is your preference?" in html assert "Option A" in html - assert "❓" in html - assert "✅" in html + assert "Q:" in html + assert "A:" in html def test_format_result_multiple_qa(self): """Test formatting a result with multiple Q&A pairs.""" From c3eaff00bba87e8191a021a707ad66ba6ee97153 Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 27 Dec 2025 12:22:06 +0100 Subject: [PATCH 3/8] Make medium priority todo border transparent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduces visual clutter when all items have the same (default) priority. Only high and low priority items now display colored left borders. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- claude_code_log/html/templates/components/todo_styles.css | 2 +- test/__snapshots__/test_snapshot_html.ambr | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/claude_code_log/html/templates/components/todo_styles.css b/claude_code_log/html/templates/components/todo_styles.css index c71eed4b..ea055414 100644 --- a/claude_code_log/html/templates/components/todo_styles.css +++ b/claude_code_log/html/templates/components/todo_styles.css @@ -75,7 +75,7 @@ } .todo-item.medium { - border-left: 3px solid var(--priority-medium); + border-left: 3px solid transparent; } .todo-item.low { diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr index 6c41afbd..a262e713 100644 --- a/test/__snapshots__/test_snapshot_html.ambr +++ b/test/__snapshots__/test_snapshot_html.ambr @@ -3364,7 +3364,7 @@ } .todo-item.medium { - border-left: 3px solid var(--priority-medium); + border-left: 3px solid transparent; } .todo-item.low { @@ -8168,7 +8168,7 @@ } .todo-item.medium { - border-left: 3px solid var(--priority-medium); + border-left: 3px solid transparent; } .todo-item.low { @@ -13061,7 +13061,7 @@ } .todo-item.medium { - border-left: 3px solid var(--priority-medium); + border-left: 3px solid transparent; } .todo-item.low { @@ -18002,7 +18002,7 @@ } .todo-item.medium { - border-left: 3px solid var(--priority-medium); + border-left: 3px solid transparent; } .todo-item.low { From bccffc7a52da2fdae29e876375d2dcdc59c79d9f Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 27 Dec 2025 12:47:11 +0100 Subject: [PATCH 4/8] Polish fold bar and muted text styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lighten --text-muted from #666 to #888 for less visual weight - Add muted color to fold icons (matching fold labels) - Right-align fold bar content with extra right padding - Reduce fold bar bottom border from 2px to 1px 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../templates/components/global_styles.css | 2 +- .../templates/components/message_styles.css | 9 ++-- test/__snapshots__/test_snapshot_html.ambr | 46 ++++++++++--------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/claude_code_log/html/templates/components/global_styles.css b/claude_code_log/html/templates/components/global_styles.css index 611bf573..a61dac83 100644 --- a/claude_code_log/html/templates/components/global_styles.css +++ b/claude_code_log/html/templates/components/global_styles.css @@ -64,7 +64,7 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #666; + --text-muted: #888; --text-secondary: #495057; /* Border colors */ diff --git a/claude_code_log/html/templates/components/message_styles.css b/claude_code_log/html/templates/components/message_styles.css index aa739996..bca88d14 100644 --- a/claude_code_log/html/templates/components/message_styles.css +++ b/claude_code_log/html/templates/components/message_styles.css @@ -31,22 +31,22 @@ flex: 1; display: flex; align-items: center; - justify-content: center; + justify-content: flex-end; gap: 0.4em; cursor: pointer; user-select: none; font-size: 0.9em; font-weight: 500; - padding: 0.4em; + padding: 0.4em 0.8em 0.4em 0.4em; transition: all 0.2s ease; - border-bottom: 2px solid transparent; + border-bottom: 1px solid transparent; background: linear-gradient(to bottom, #f8f8f844, #f0f0f0); } /* Show border only when folded (content is hidden) */ .fold-bar-section.folded { border-bottom-style: solid; - border-bottom-width: 2px; + border-bottom-width: 1px; } .fold-bar-section:hover { @@ -72,6 +72,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; + color: var(--text-muted); } .fold-count { diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr index a262e713..daa8944d 100644 --- a/test/__snapshots__/test_snapshot_html.ambr +++ b/test/__snapshots__/test_snapshot_html.ambr @@ -76,7 +76,7 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #666; + --text-muted: #888; --text-secondary: #495057; /* Border colors */ @@ -1926,7 +1926,7 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #666; + --text-muted: #888; --text-secondary: #495057; /* Border colors */ @@ -2123,22 +2123,22 @@ flex: 1; display: flex; align-items: center; - justify-content: center; + justify-content: flex-end; gap: 0.4em; cursor: pointer; user-select: none; font-size: 0.9em; font-weight: 500; - padding: 0.4em; + padding: 0.4em 0.8em 0.4em 0.4em; transition: all 0.2s ease; - border-bottom: 2px solid transparent; + border-bottom: 1px solid transparent; background: linear-gradient(to bottom, #f8f8f844, #f0f0f0); } /* Show border only when folded (content is hidden) */ .fold-bar-section.folded { border-bottom-style: solid; - border-bottom-width: 2px; + border-bottom-width: 1px; } .fold-bar-section:hover { @@ -2164,6 +2164,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; + color: var(--text-muted); } .fold-count { @@ -6730,7 +6731,7 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #666; + --text-muted: #888; --text-secondary: #495057; /* Border colors */ @@ -6927,22 +6928,22 @@ flex: 1; display: flex; align-items: center; - justify-content: center; + justify-content: flex-end; gap: 0.4em; cursor: pointer; user-select: none; font-size: 0.9em; font-weight: 500; - padding: 0.4em; + padding: 0.4em 0.8em 0.4em 0.4em; transition: all 0.2s ease; - border-bottom: 2px solid transparent; + border-bottom: 1px solid transparent; background: linear-gradient(to bottom, #f8f8f844, #f0f0f0); } /* Show border only when folded (content is hidden) */ .fold-bar-section.folded { border-bottom-style: solid; - border-bottom-width: 2px; + border-bottom-width: 1px; } .fold-bar-section:hover { @@ -6968,6 +6969,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; + color: var(--text-muted); } .fold-count { @@ -11623,7 +11625,7 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #666; + --text-muted: #888; --text-secondary: #495057; /* Border colors */ @@ -11820,22 +11822,22 @@ flex: 1; display: flex; align-items: center; - justify-content: center; + justify-content: flex-end; gap: 0.4em; cursor: pointer; user-select: none; font-size: 0.9em; font-weight: 500; - padding: 0.4em; + padding: 0.4em 0.8em 0.4em 0.4em; transition: all 0.2s ease; - border-bottom: 2px solid transparent; + border-bottom: 1px solid transparent; background: linear-gradient(to bottom, #f8f8f844, #f0f0f0); } /* Show border only when folded (content is hidden) */ .fold-bar-section.folded { border-bottom-style: solid; - border-bottom-width: 2px; + border-bottom-width: 1px; } .fold-bar-section:hover { @@ -11861,6 +11863,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; + color: var(--text-muted); } .fold-count { @@ -16564,7 +16567,7 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #666; + --text-muted: #888; --text-secondary: #495057; /* Border colors */ @@ -16761,22 +16764,22 @@ flex: 1; display: flex; align-items: center; - justify-content: center; + justify-content: flex-end; gap: 0.4em; cursor: pointer; user-select: none; font-size: 0.9em; font-weight: 500; - padding: 0.4em; + padding: 0.4em 0.8em 0.4em 0.4em; transition: all 0.2s ease; - border-bottom: 2px solid transparent; + border-bottom: 1px solid transparent; background: linear-gradient(to bottom, #f8f8f844, #f0f0f0); } /* Show border only when folded (content is hidden) */ .fold-bar-section.folded { border-bottom-style: solid; - border-bottom-width: 2px; + border-bottom-width: 1px; } .fold-bar-section:hover { @@ -16802,6 +16805,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; + color: var(--text-muted); } .fold-count { From 6fef1225e6ea65b71ccc81e0df47e10edc7cb211 Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 27 Dec 2025 12:55:51 +0100 Subject: [PATCH 5/8] Add --fold-color variable for fold bar elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate fold-specific color (#888) from --text-muted (#666) to preserve readability of other muted text like detail labels. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../templates/components/global_styles.css | 3 +- .../templates/components/message_styles.css | 4 +-- test/__snapshots__/test_snapshot_html.ambr | 31 +++++++++++-------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/claude_code_log/html/templates/components/global_styles.css b/claude_code_log/html/templates/components/global_styles.css index a61dac83..ce6572aa 100644 --- a/claude_code_log/html/templates/components/global_styles.css +++ b/claude_code_log/html/templates/components/global_styles.css @@ -64,8 +64,9 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #888; + --text-muted: #666; --text-secondary: #495057; + --fold-color: #888; /* Border colors */ --border-light: #e0e0e0; diff --git a/claude_code_log/html/templates/components/message_styles.css b/claude_code_log/html/templates/components/message_styles.css index bca88d14..074a14cc 100644 --- a/claude_code_log/html/templates/components/message_styles.css +++ b/claude_code_log/html/templates/components/message_styles.css @@ -72,7 +72,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; - color: var(--text-muted); + color: var(--fold-color); } .fold-count { @@ -82,7 +82,7 @@ } .fold-label { - color: var(--text-muted); + color: var(--fold-color); font-size: 0.9em; } diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr index daa8944d..929936a6 100644 --- a/test/__snapshots__/test_snapshot_html.ambr +++ b/test/__snapshots__/test_snapshot_html.ambr @@ -76,8 +76,9 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #888; + --text-muted: #666; --text-secondary: #495057; + --fold-color: #888; /* Border colors */ --border-light: #e0e0e0; @@ -1926,8 +1927,9 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #888; + --text-muted: #666; --text-secondary: #495057; + --fold-color: #888; /* Border colors */ --border-light: #e0e0e0; @@ -2164,7 +2166,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; - color: var(--text-muted); + color: var(--fold-color); } .fold-count { @@ -2174,7 +2176,7 @@ } .fold-label { - color: var(--text-muted); + color: var(--fold-color); font-size: 0.9em; } @@ -6731,8 +6733,9 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #888; + --text-muted: #666; --text-secondary: #495057; + --fold-color: #888; /* Border colors */ --border-light: #e0e0e0; @@ -6969,7 +6972,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; - color: var(--text-muted); + color: var(--fold-color); } .fold-count { @@ -6979,7 +6982,7 @@ } .fold-label { - color: var(--text-muted); + color: var(--fold-color); font-size: 0.9em; } @@ -11625,8 +11628,9 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #888; + --text-muted: #666; --text-secondary: #495057; + --fold-color: #888; /* Border colors */ --border-light: #e0e0e0; @@ -11863,7 +11867,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; - color: var(--text-muted); + color: var(--fold-color); } .fold-count { @@ -11873,7 +11877,7 @@ } .fold-label { - color: var(--text-muted); + color: var(--fold-color); font-size: 0.9em; } @@ -16567,8 +16571,9 @@ /* Solid colors for text and accents */ --text-primary: #333; - --text-muted: #888; + --text-muted: #666; --text-secondary: #495057; + --fold-color: #888; /* Border colors */ --border-light: #e0e0e0; @@ -16805,7 +16810,7 @@ .fold-icon { font-size: 1.1em; line-height: 1; - color: var(--text-muted); + color: var(--fold-color); } .fold-count { @@ -16815,7 +16820,7 @@ } .fold-label { - color: var(--text-muted); + color: var(--fold-color); font-size: 0.9em; } From 617854a8908a340c78ccbf3b0bce54e6d1268399 Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 27 Dec 2025 12:59:35 +0100 Subject: [PATCH 6/8] Include path in Glob tool title to distinguish parallel calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When multiple Glob tools are invoked in parallel (same timestamp), showing only the pattern made them look like duplicates. Now shows "pattern in path" when path is specified. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- claude_code_log/html/renderer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/claude_code_log/html/renderer.py b/claude_code_log/html/renderer.py index 94c40b02..a9a07a76 100644 --- a/claude_code_log/html/renderer.py +++ b/claude_code_log/html/renderer.py @@ -322,7 +322,10 @@ def title_ReadInput(self, message: TemplateMessage) -> str: def title_GlobInput(self, message: TemplateMessage) -> str: input = cast(GlobInput, cast(ToolUseMessage, message.content).input) - return self._tool_title(message, "🔍", f"({input.pattern})") + summary = input.pattern + if input.path: + summary = f"{summary} in {input.path}" + return self._tool_title(message, "🔍", summary) def title_BashInput(self, message: TemplateMessage) -> str: input = cast(BashInput, cast(ToolUseMessage, message.content).input) From 65295200a41964de5a648bc2002b78610e32c17d Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 27 Dec 2025 13:03:53 +0100 Subject: [PATCH 7/8] Use full purple border for all thinking messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously unpaired thinking messages used --assistant-dimmed border. Now all thinking messages consistently use --assistant-color. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../templates/components/message_styles.css | 5 ----- test/__snapshots__/test_snapshot_html.ambr | 20 ------------------- 2 files changed, 25 deletions(-) diff --git a/claude_code_log/html/templates/components/message_styles.css b/claude_code_log/html/templates/components/message_styles.css index 074a14cc..ea3a9f65 100644 --- a/claude_code_log/html/templates/components/message_styles.css +++ b/claude_code_log/html/templates/components/message_styles.css @@ -539,11 +539,6 @@ } .thinking { - border-left-color: var(--assistant-dimmed); -} - -/* Full purple when thinking is paired (as pair_first) */ -.thinking.pair_first { border-left-color: var(--assistant-color); } diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr index 929936a6..f0d7e69c 100644 --- a/test/__snapshots__/test_snapshot_html.ambr +++ b/test/__snapshots__/test_snapshot_html.ambr @@ -2633,11 +2633,6 @@ } .thinking { - border-left-color: var(--assistant-dimmed); - } - - /* Full purple when thinking is paired (as pair_first) */ - .thinking.pair_first { border-left-color: var(--assistant-color); } @@ -7439,11 +7434,6 @@ } .thinking { - border-left-color: var(--assistant-dimmed); - } - - /* Full purple when thinking is paired (as pair_first) */ - .thinking.pair_first { border-left-color: var(--assistant-color); } @@ -12334,11 +12324,6 @@ } .thinking { - border-left-color: var(--assistant-dimmed); - } - - /* Full purple when thinking is paired (as pair_first) */ - .thinking.pair_first { border-left-color: var(--assistant-color); } @@ -17277,11 +17262,6 @@ } .thinking { - border-left-color: var(--assistant-dimmed); - } - - /* Full purple when thinking is paired (as pair_first) */ - .thinking.pair_first { border-left-color: var(--assistant-color); } From b35cffc9b61466d0b89885eb7cbe87b269001398 Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Mon, 29 Dec 2025 00:08:33 +0100 Subject: [PATCH 8/8] Add minimal docstrings to HtmlRenderer methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds concise docstrings showing typical output format to satisfy docstring coverage requirements (38 methods documented). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- claude_code_log/html/renderer.py | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/claude_code_log/html/renderer.py b/claude_code_log/html/renderer.py index a9a07a76..3b797a5e 100644 --- a/claude_code_log/html/renderer.py +++ b/claude_code_log/html/renderer.py @@ -148,15 +148,19 @@ class HtmlRenderer(Renderer): # ------------------------------------------------------------------------- def format_SystemMessage(self, message: SystemMessage) -> str: + """Format →
...
.""" return format_system_content(message) def format_HookSummaryMessage(self, message: HookSummaryMessage) -> str: + """Format →
...
.""" return format_hook_summary_content(message) def format_SessionHeaderMessage(self, message: SessionHeaderMessage) -> str: + """Format →
...
.""" return format_session_header_content(message) def format_DedupNoticeMessage(self, message: DedupNoticeMessage) -> str: + """Format → ....""" return format_dedup_notice_content(message) # ------------------------------------------------------------------------- @@ -164,27 +168,35 @@ def format_DedupNoticeMessage(self, message: DedupNoticeMessage) -> str: # ------------------------------------------------------------------------- def format_UserTextMessage(self, message: UserTextMessage) -> str: + """Format → rendered markdown HTML.""" return format_user_text_model_content(message) def format_UserSlashCommandMessage(self, message: UserSlashCommandMessage) -> str: + """Format → /cmd.""" return format_user_slash_command_content(message) def format_SlashCommandMessage(self, message: SlashCommandMessage) -> str: + """Format → /cmd arg.""" return format_slash_command_content(message) def format_CommandOutputMessage(self, message: CommandOutputMessage) -> str: + """Format →
...
.""" return format_command_output_content(message) def format_BashInputMessage(self, message: BashInputMessage) -> str: + """Format →
$ cmd
.""" return format_bash_input_content(message) def format_BashOutputMessage(self, message: BashOutputMessage) -> str: + """Format →
...
.""" return format_bash_output_content(message) def format_CompactedSummaryMessage(self, message: CompactedSummaryMessage) -> str: + """Format →
...
.""" return format_compacted_summary_content(message) def format_UserMemoryMessage(self, message: UserMemoryMessage) -> str: + """Format →
...
.""" return format_user_memory_content(message) # ------------------------------------------------------------------------- @@ -192,12 +204,15 @@ def format_UserMemoryMessage(self, message: UserMemoryMessage) -> str: # ------------------------------------------------------------------------- def format_AssistantTextMessage(self, message: AssistantTextMessage) -> str: + """Format → rendered markdown HTML.""" return format_assistant_text_content(message) def format_ThinkingMessage(self, message: ThinkingMessage) -> str: + """Format →
...
(foldable if >10 lines).""" return format_thinking_content(message, line_threshold=10) def format_UnknownMessage(self, message: UnknownMessage) -> str: + """Format →
JSON dump
.""" return format_unknown_content(message) # ------------------------------------------------------------------------- @@ -205,33 +220,43 @@ def format_UnknownMessage(self, message: UnknownMessage) -> str: # ------------------------------------------------------------------------- def format_BashInput(self, input: BashInput) -> str: + """Format →
$ command
.""" return format_bash_input(input) def format_ReadInput(self, input: ReadInput) -> str: + """Format → file_path | ...
.""" return format_read_input(input) def format_WriteInput(self, input: WriteInput) -> str: + """Format → file path + syntax-highlighted content preview.""" return format_write_input(input) def format_EditInput(self, input: EditInput) -> str: + """Format → file path + diff of old_string/new_string.""" return format_edit_input(input) def format_MultiEditInput(self, input: MultiEditInput) -> str: + """Format → file path + multiple diffs.""" return format_multiedit_input(input) def format_TaskInput(self, input: TaskInput) -> str: + """Format →
prompt text
.""" return format_task_input(input) def format_TodoWriteInput(self, input: TodoWriteInput) -> str: + """Format →
    ...
.""" return format_todowrite_input(input) def format_AskUserQuestionInput(self, input: AskUserQuestionInput) -> str: + """Format → questions as definition list.""" return format_askuserquestion_input(input) def format_ExitPlanModeInput(self, input: ExitPlanModeInput) -> str: + """Format → empty string (no content).""" return format_exitplanmode_input(input) def format_ToolUseContent(self, content: ToolUseContent) -> str: + """Format → key | value rows
.""" return render_params_table(content.input) # ------------------------------------------------------------------------- @@ -239,27 +264,35 @@ def format_ToolUseContent(self, content: ToolUseContent) -> str: # ------------------------------------------------------------------------- def format_ReadOutput(self, output: ReadOutput) -> str: + """Format → syntax-highlighted file content.""" return format_read_output(output) def format_WriteOutput(self, output: WriteOutput) -> str: + """Format → status message (e.g. 'Wrote 42 bytes').""" return format_write_output(output) def format_EditOutput(self, output: EditOutput) -> str: + """Format → status message (e.g. 'Applied edit').""" return format_edit_output(output) def format_BashOutput(self, output: BashOutput) -> str: + """Format →
stdout/stderr
.""" return format_bash_output(output) def format_TaskOutput(self, output: TaskOutput) -> str: + """Format → rendered markdown of task result.""" return format_task_output(output) def format_AskUserQuestionOutput(self, output: AskUserQuestionOutput) -> str: + """Format → user's answers as definition list.""" return format_askuserquestion_output(output) def format_ExitPlanModeOutput(self, output: ExitPlanModeOutput) -> str: + """Format → status message.""" return format_exitplanmode_output(output) def format_ToolResultContent(self, output: ToolResultContent) -> str: + """Format →
raw content
(fallback for unknown tools).""" return format_tool_result_content_raw(output) # ------------------------------------------------------------------------- @@ -279,12 +312,15 @@ def _tool_title( return f"{prefix}{escaped_name}" def title_TodoWriteInput(self, message: TemplateMessage) -> str: # noqa: ARG002 + """Title → '📝 Todo List'.""" return "📝 Todo List" def title_AskUserQuestionInput(self, message: TemplateMessage) -> str: # noqa: ARG002 + """Title → '❓ Asking questions...'.""" return "❓ Asking questions..." def title_TaskInput(self, message: TemplateMessage) -> str: + """Title → '🔧 Task (subagent_type)'.""" content = cast(ToolUseMessage, message.content) input = cast(TaskInput, content.input) escaped_name = escape_html(content.tool_name) @@ -301,14 +337,17 @@ def title_TaskInput(self, message: TemplateMessage) -> str: return f"🔧 {escaped_name}" def title_EditInput(self, message: TemplateMessage) -> str: + """Title → '📝 Edit '.""" input = cast(EditInput, cast(ToolUseMessage, message.content).input) return self._tool_title(message, "📝", input.file_path) def title_WriteInput(self, message: TemplateMessage) -> str: + """Title → '📝 Write '.""" input = cast(WriteInput, cast(ToolUseMessage, message.content).input) return self._tool_title(message, "📝", input.file_path) def title_ReadInput(self, message: TemplateMessage) -> str: + """Title → '📄 Read [, lines N-M]'.""" input = cast(ReadInput, cast(ToolUseMessage, message.content).input) summary = input.file_path # Add line range info if available @@ -321,6 +360,7 @@ def title_ReadInput(self, message: TemplateMessage) -> str: return self._tool_title(message, "📄", summary) def title_GlobInput(self, message: TemplateMessage) -> str: + """Title → '🔍 Glob [ in path]'.""" input = cast(GlobInput, cast(ToolUseMessage, message.content).input) summary = input.pattern if input.path: @@ -328,6 +368,7 @@ def title_GlobInput(self, message: TemplateMessage) -> str: return self._tool_title(message, "🔍", summary) def title_BashInput(self, message: TemplateMessage) -> str: + """Title → '💻 Bash '.""" input = cast(BashInput, cast(ToolUseMessage, message.content).input) return self._tool_title(message, "💻", input.description)