diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 841c385a..f12f6b9a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -43,6 +43,8 @@ jobs:
- name: Run benchmark tests with coverage append (primary only)
if: matrix.is-primary
+ env:
+ CLAUDE_CODE_LOG_DEBUG_TIMING: "1"
run: uv run pytest -m benchmark --cov=claude_code_log --cov-append --cov-report=xml --cov-report=html --cov-report=term -v
- name: Upload coverage HTML report as artifact
diff --git a/README.md b/README.md
index cca9bf10..9ce42066 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,7 @@ claude-code-log --all-projects
# Process all projects and open in browser
claude-code-log --open-browser
-# Process all projects with date filtering
+# Process all projects with date filtering
claude-code-log --from-date "yesterday" --to-date "today"
claude-code-log --from-date "last week"
@@ -154,6 +154,7 @@ The project uses:
- Python 3.10+ with uv package management
- Click for CLI interface and argument parsing
+- Textual for interactive Terminal User Interface
- Pydantic for robust data modeling and validation
- dateparser for natural language date parsing
- Standard library for JSON/HTML processing
@@ -377,17 +378,13 @@ uv run claude-code-log
- tutorial overlay
- integrate `claude-trace` request logs if present?
- convert images to WebP as screenshots are often huge PNGs – this might be time consuming to keep redoing (so would also need some caching) and need heavy dependencies with compilation (unless there are fast pure Python conversation libraries? Or WASM?)
-- add special formatting for built-in tools: Bash, Glob, Grep, LS, exit_plan_mode, Read, Edit, MultiEdit, Write, NotebookRead, NotebookEdit, WebFetch, TodoRead, TodoWrite, WebSearch
-- do we need to handle compacted conversation?
-- Thinking block should have Markdown rendering as sometimes they have formatting
-- system blocks like `init` also don't render perfectly, losing new lines
+- add special formatting for built-in tools: Glob, Grep, LS, MultiEdit, NotebookRead, NotebookEdit, WebFetch, TodoRead, WebSearch
- add `ccusage` like daily summary and maybe some textual summary too based on Claude generate session summaries?
– import logs from @claude Github Actions
- stream logs from @claude Github Actions, see [octotail](https://github.com/getbettr/octotail)
- wrap up CLI as Github Action to run after Cladue Github Action and process [output](https://github.com/anthropics/claude-code-base-action?tab=readme-ov-file#outputs)
- feed the filtered user messages to headless claude CLI to distill the user intent from the session
- filter message type on Python (CLI) side too, not just UI
-- Markdown renderer
- add minimalist theme and make it light + dark; animate gradient background in fancy theme
- do we need special handling for hooks?
- make processing parallel, currently we only use 1 CPU (core) and it's slow
diff --git a/claude_code_log/renderer.py b/claude_code_log/renderer.py
index 5b481279..6ea537a0 100644
--- a/claude_code_log/renderer.py
+++ b/claude_code_log/renderer.py
@@ -567,7 +567,7 @@ def format_exitplanmode_result(content: str) -> str:
def format_todowrite_content(tool_use: ToolUseContent) -> str:
- """Format TodoWrite tool use content as an actual todo list with checkboxes."""
+ """Format TodoWrite tool use content as a todo list."""
# Parse todos from input
todos_data = tool_use.input.get("todos", [])
if not todos_data:
@@ -586,31 +586,26 @@ def format_todowrite_content(tool_use: ToolUseContent) -> str:
try:
todo_id = escape_html(str(todo.get("id", "")))
content = escape_html(str(todo.get("content", "")))
- status = todo.get("status", "pending")
- priority = todo.get("priority", "medium")
+ status = str(todo.get("status", "pending")).lower()
+ priority = str(todo.get("priority", "medium")).lower()
status_emoji = status_emojis.get(status, "⏳")
- # Determine checkbox state
- checked = "checked" if status == "completed" else ""
- disabled = "disabled" if status == "completed" else ""
-
# CSS class for styling
item_class = f"todo-item {status} {priority}"
todo_items.append(f"""
-
{status_emoji}
{content}
#{todo_id}
""")
except AttributeError:
+ escaped_fallback = escape_html(str(todo))
todo_items.append(f"""
-
⏳
- {str(todo)}
+ {escaped_fallback}
""")
@@ -2532,18 +2527,32 @@ def _process_regular_message(
# Handle user-specific preprocessing
if message_type == "user":
# Note: sidechain user messages are skipped before reaching this function
- content_html, is_compacted, is_memory_input = render_user_message_content(
- text_only_content
- )
- if is_compacted:
- css_class = f"{message_type} compacted"
- message_title = "User (compacted conversation)"
- elif is_meta:
- # Slash command expanded prompts - LLM-generated content
+ if is_meta:
+ # Slash command expanded prompts - render as collapsible markdown
+ # These contain LLM-generated instruction text (markdown formatted)
css_class = f"{message_type} slash-command"
message_title = "User (slash command)"
- elif is_memory_input:
- message_title = "Memory"
+ # Combine all text content (items may be TextContent, dicts, or SDK objects)
+ all_text = "\n\n".join(
+ getattr(item, "text", "")
+ for item in text_only_content
+ if hasattr(item, "text")
+ )
+ content_html = render_markdown_collapsible(
+ all_text,
+ "slash-command-content",
+ line_threshold=20,
+ preview_line_count=5,
+ )
+ else:
+ content_html, is_compacted, is_memory_input = render_user_message_content(
+ text_only_content
+ )
+ if is_compacted:
+ css_class = f"{message_type} compacted"
+ message_title = "User (compacted conversation)"
+ elif is_memory_input:
+ message_title = "Memory"
else:
# Non-user messages: render directly
content_html = render_message_content(text_only_content, message_type)
@@ -2578,6 +2587,7 @@ def _identify_message_pairs(messages: List[TemplateMessage]) -> None:
and tool_result. Session ID is included to prevent cross-session pairing
when sessions are resumed (same tool_use_id can appear in multiple sessions).
Build index of uuid -> message index for parent-child system messages
+ Build index of parent_uuid -> message index for slash-command messages
2. Second pass: Sequential scan for adjacent pairs (system+output, bash, thinking+assistant)
and match tool_use/tool_result and uuid-based pairs using the index
"""
@@ -2590,6 +2600,8 @@ def _identify_message_pairs(messages: List[TemplateMessage]) -> None:
tuple[str, str], int
] = {} # (session_id, tool_use_id) -> index
uuid_index: Dict[str, int] = {} # uuid -> message index for parent-child pairing
+ # Index slash-command messages by their parent_uuid for pairing with system commands
+ slash_command_by_parent: Dict[str, int] = {} # parent_uuid -> message index
for i, msg in enumerate(messages):
if msg.tool_use_id and msg.session_id:
@@ -2601,6 +2613,9 @@ def _identify_message_pairs(messages: List[TemplateMessage]) -> None:
# Build UUID index for system messages (both parent and child)
if msg.uuid and "system" in msg.css_class:
uuid_index[msg.uuid] = i
+ # Index slash-command user messages by parent_uuid
+ if msg.parent_uuid and "slash-command" in msg.css_class:
+ slash_command_by_parent[msg.parent_uuid] = i
# Pass 2: Sequential scan to identify pairs
i = 0
@@ -2649,6 +2664,17 @@ def _identify_message_pairs(messages: List[TemplateMessage]) -> None:
current.is_paired = True
current.pair_role = "pair_last"
+ # Check for system command + user slash-command pair (via parent_uuid)
+ # The slash-command message's parent_uuid points to the system command's uuid
+ if "system" in current.css_class and current.uuid:
+ if current.uuid in slash_command_by_parent:
+ slash_idx = slash_command_by_parent[current.uuid]
+ slash_msg = messages[slash_idx]
+ current.is_paired = True
+ current.pair_role = "pair_first"
+ slash_msg.is_paired = True
+ slash_msg.pair_role = "pair_last"
+
# Check for bash-input + bash-output pair (adjacent only)
if current.css_class == "bash-input" and i + 1 < len(messages):
next_msg = messages[i + 1]
@@ -2683,7 +2709,8 @@ def _reorder_paired_messages(messages: List[TemplateMessage]) -> List[TemplateMe
Uses dictionary-based approach to find pairs efficiently:
1. Build index of all pair_last messages by tool_use_id
- 2. Single pass through messages, inserting pair_last immediately after pair_first
+ 2. Build index of slash-command pair_last messages by parent_uuid
+ 3. Single pass through messages, inserting pair_last immediately after pair_first
"""
from datetime import datetime
@@ -2692,6 +2719,8 @@ def _reorder_paired_messages(messages: List[TemplateMessage]) -> List[TemplateMe
pair_last_index: Dict[
tuple[str, str], int
] = {} # (session_id, tool_use_id) -> message index
+ # Index slash-command pair_last messages by parent_uuid
+ slash_command_pair_index: Dict[str, int] = {} # parent_uuid -> message index
for i, msg in enumerate(messages):
if (
@@ -2702,6 +2731,14 @@ def _reorder_paired_messages(messages: List[TemplateMessage]) -> List[TemplateMe
):
key = (msg.session_id, msg.tool_use_id)
pair_last_index[key] = i
+ # Index slash-command messages by parent_uuid
+ if (
+ msg.is_paired
+ and msg.pair_role == "pair_last"
+ and msg.parent_uuid
+ and "slash-command" in msg.css_class
+ ):
+ slash_command_pair_index[msg.parent_uuid] = i
# Create reordered list
reordered: List[TemplateMessage] = []
@@ -2715,16 +2752,23 @@ def _reorder_paired_messages(messages: List[TemplateMessage]) -> List[TemplateMe
# If this is the first message in a pair, immediately add its pair_last
# Key includes session_id to prevent cross-session pairing on resume
- if (
- msg.is_paired
- and msg.pair_role == "pair_first"
- and msg.tool_use_id
- and msg.session_id
- ):
- key = (msg.session_id, msg.tool_use_id)
- if key in pair_last_index:
- last_idx = pair_last_index[key]
+ if msg.is_paired and msg.pair_role == "pair_first":
+ pair_last = None
+ last_idx = None
+
+ # Check for tool_use_id based pairs
+ if msg.tool_use_id and msg.session_id:
+ key = (msg.session_id, msg.tool_use_id)
+ if key in pair_last_index:
+ last_idx = pair_last_index[key]
+ pair_last = messages[last_idx]
+
+ # Check for system + slash-command pairs (via uuid -> parent_uuid)
+ if pair_last is None and msg.uuid and msg.uuid in slash_command_pair_index:
+ last_idx = slash_command_pair_index[msg.uuid]
pair_last = messages[last_idx]
+
+ if pair_last is not None and last_idx is not None:
reordered.append(pair_last)
skip_indices.add(last_idx)
@@ -2799,8 +2843,8 @@ def _get_message_hierarchy_level(css_class: str, is_sidechain: bool) -> int:
Correct hierarchy based on logical nesting:
- Level 0: Session headers
- Level 1: User messages
- - Level 2: System messages, Assistant, Thinking
- - Level 3: Tool use/result (nested under assistant)
+ - Level 2: System commands/errors, Assistant, Thinking
+ - Level 3: Tool use/result, System info/warning (nested under assistant)
- Level 4: Sidechain assistant/thinking (nested under Task tool result)
- Level 5: Sidechain tools (nested under sidechain assistant)
@@ -2815,7 +2859,13 @@ def _get_message_hierarchy_level(css_class: str, is_sidechain: bool) -> int:
if "user" in css_class and not is_sidechain:
return 1
- # System messages at level 2 (siblings to assistant, under user)
+ # System info/warning at level 3 (tool-related, e.g., hook notifications)
+ if (
+ "system-info" in css_class or "system-warning" in css_class
+ ) and not is_sidechain:
+ return 3
+
+ # System commands/errors at level 2 (siblings to assistant)
if "system" in css_class and not is_sidechain:
return 2
@@ -2848,27 +2898,16 @@ def _build_message_hierarchy(messages: List[TemplateMessage]) -> None:
The hierarchy is determined by message type using _get_message_hierarchy_level(),
and a stack-based approach builds proper parent-child relationships.
- For system messages with parent_uuid, the hierarchy level is derived from the
- parent's level instead of the default, ensuring proper nesting.
-
Args:
messages: List of template messages in their final order (modified in place)
"""
hierarchy_stack: List[tuple[int, str]] = []
message_id_counter = 0
- # Build UUID -> (message_id, level) mapping for parent_uuid resolution
- # We do this in a single pass by updating the map as we assign IDs
- uuid_to_info: Dict[str, tuple[str, int]] = {}
-
for message in messages:
# Session headers are level 0
if message.is_session_header:
current_level = 0
- elif message.parent_uuid and message.parent_uuid in uuid_to_info:
- # System message with known parent - nest under parent
- _, parent_level = uuid_to_info[message.parent_uuid]
- current_level = parent_level + 1
else:
# Determine level from css_class
is_sidechain = "sidechain" in message.css_class
@@ -2894,10 +2933,6 @@ def _build_message_hierarchy(messages: List[TemplateMessage]) -> None:
# Push current message onto stack
hierarchy_stack.append((current_level, message_id))
- # Track UUID -> (message_id, level) for parent_uuid resolution
- if message.uuid:
- uuid_to_info[message.uuid] = (message_id, current_level)
-
# Update the message
message.message_id = message_id
message.ancestry = ancestry
@@ -3799,6 +3834,7 @@ def _process_messages_loop(
ancestry=[], # Will be assigned by _build_message_hierarchy
agent_id=getattr(message, "agentId", None),
uuid=getattr(message, "uuid", None),
+ parent_uuid=getattr(message, "parentUuid", None),
)
# Store raw text content for potential future use (e.g., deduplication,
diff --git a/claude_code_log/templates/components/global_styles.css b/claude_code_log/templates/components/global_styles.css
index 393b870c..4d4f1b3f 100644
--- a/claude_code_log/templates/components/global_styles.css
+++ b/claude_code_log/templates/components/global_styles.css
@@ -44,10 +44,15 @@
--answer-accent: #4caf50;
--answer-bg: #f0fff4;
+ /* Priority palette (purple intensity - darker = more urgent) */
+ --priority-600: #7c3aed;
+ --priority-400: #a78bfa;
+ --priority-300: #c4b5fd;
+
/* Priority colors for todos */
- --priority-high: #dc3545;
- --priority-medium: #ffc107;
- --priority-low: #28a745;
+ --priority-high: var(--priority-600);
+ --priority-medium: var(--priority-400);
+ --priority-low: var(--priority-300);
/* Status colors */
--status-in-progress: #fff3cd;
diff --git a/claude_code_log/templates/components/message_styles.css b/claude_code_log/templates/components/message_styles.css
index 7fd8472b..2bc67bf0 100644
--- a/claude_code_log/templates/components/message_styles.css
+++ b/claude_code_log/templates/components/message_styles.css
@@ -179,9 +179,9 @@
/* Right-aligned messages (user-initiated, right margin 0, left margin 33%) */
.user:not(.compacted),
-.system,
.bash-input,
-.bash-output {
+.bash-output,
+.system:not(.system-info):not(.system-warning):not(.system-error) {
margin-left: 33%;
margin-right: 0;
}
@@ -207,17 +207,11 @@
margin-right: 6em;
}
-/* System warnings/info (assistant-initiated) */
+/* System warnings/info at tool level (e.g., hook notifications) */
.system-warning,
.system-info {
- margin-left: 0;
- margin-right: 10em;
-}
-
-/* Exception: paired system-info messages align right (like user commands) */
-.system.system-info.paired-message {
- margin-left: 33%;
- margin-right: 0;
+ margin-left: 2em;
+ margin-right: 6em;
}
/* Sidechain messages (sub-assistant hierarchy) */
@@ -340,6 +334,10 @@
.system-info {
border-left-color: var(--info-dimmed);
background-color: var(--highlight-dimmed);
+}
+
+.system-info .header,
+.system-info .content {
font-size: 80%;
}
diff --git a/claude_code_log/templates/components/todo_styles.css b/claude_code_log/templates/components/todo_styles.css
index 944ee9ca..22a35140 100644
--- a/claude_code_log/templates/components/todo_styles.css
+++ b/claude_code_log/templates/components/todo_styles.css
@@ -40,18 +40,14 @@
background-color: var(--bg-hover);
}
-.todo-item.completed {
- opacity: 0.7;
-}
-
.todo-item.completed .todo-content {
text-decoration: line-through;
color: var(--text-muted);
+ opacity: 0.7;
}
-.todo-item input[type="checkbox"] {
- margin: 0;
- cursor: default;
+.todo-item.pending .todo-content {
+ font-weight: normal;
}
.todo-status {
diff --git a/justfile b/justfile
index f72fb3ea..c54251ac 100644
--- a/justfile
+++ b/justfile
@@ -10,8 +10,9 @@ test:
uv run pytest -n auto -m "not (tui or browser or benchmark)" -v
# Run benchmark tests (outputs to GITHUB_STEP_SUMMARY in CI)
+# DEBUG_TIMING enables coverage of renderer_timings.py
test-benchmark:
- uv run pytest -m benchmark -v
+ CLAUDE_CODE_LOG_DEBUG_TIMING=1 uv run pytest -m benchmark -v
# Update snapshot tests
update-snapshot:
@@ -43,7 +44,7 @@ test-all:
echo "🔄 Running integration tests..."
uv run pytest -n auto -m integration -v
echo "📊 Running benchmark tests..."
- uv run pytest -m benchmark -v
+ CLAUDE_CODE_LOG_DEBUG_TIMING=1 uv run pytest -m benchmark -v
echo "✅ All tests completed!"
# Run tests with coverage (all categories)
@@ -60,7 +61,7 @@ test-cov:
echo "🔄 Running integration tests with coverage append..."
uv run pytest -n auto -m integration --cov=claude_code_log --cov-append --cov-report=xml --cov-report=html --cov-report=term -v
echo "📊 Running benchmark tests with coverage append..."
- uv run pytest -m benchmark --cov=claude_code_log --cov-append --cov-report=xml --cov-report=html --cov-report=term -v
+ CLAUDE_CODE_LOG_DEBUG_TIMING=1 uv run pytest -m benchmark --cov=claude_code_log --cov-append --cov-report=xml --cov-report=html --cov-report=term -v
echo "✅ All tests with coverage completed!"
format:
diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr
index ed1796fb..b84f2ece 100644
--- a/test/__snapshots__/test_snapshot_html.ambr
+++ b/test/__snapshots__/test_snapshot_html.ambr
@@ -56,10 +56,15 @@
--answer-accent: #4caf50;
--answer-bg: #f0fff4;
+ /* Priority palette (purple intensity - darker = more urgent) */
+ --priority-600: #7c3aed;
+ --priority-400: #a78bfa;
+ --priority-300: #c4b5fd;
+
/* Priority colors for todos */
- --priority-high: #dc3545;
- --priority-medium: #ffc107;
- --priority-low: #28a745;
+ --priority-high: var(--priority-600);
+ --priority-medium: var(--priority-400);
+ --priority-low: var(--priority-300);
/* Status colors */
--status-in-progress: #fff3cd;
@@ -1901,10 +1906,15 @@
--answer-accent: #4caf50;
--answer-bg: #f0fff4;
+ /* Priority palette (purple intensity - darker = more urgent) */
+ --priority-600: #7c3aed;
+ --priority-400: #a78bfa;
+ --priority-300: #c4b5fd;
+
/* Priority colors for todos */
- --priority-high: #dc3545;
- --priority-medium: #ffc107;
- --priority-low: #28a745;
+ --priority-high: var(--priority-600);
+ --priority-medium: var(--priority-400);
+ --priority-low: var(--priority-300);
/* Status colors */
--status-in-progress: #fff3cd;
@@ -2261,9 +2271,9 @@
/* Right-aligned messages (user-initiated, right margin 0, left margin 33%) */
.user:not(.compacted),
- .system,
.bash-input,
- .bash-output {
+ .bash-output,
+ .system:not(.system-info):not(.system-warning):not(.system-error) {
margin-left: 33%;
margin-right: 0;
}
@@ -2289,17 +2299,11 @@
margin-right: 6em;
}
- /* System warnings/info (assistant-initiated) */
+ /* System warnings/info at tool level (e.g., hook notifications) */
.system-warning,
.system-info {
- margin-left: 0;
- margin-right: 10em;
- }
-
- /* Exception: paired system-info messages align right (like user commands) */
- .system.system-info.paired-message {
- margin-left: 33%;
- margin-right: 0;
+ margin-left: 2em;
+ margin-right: 6em;
}
/* Sidechain messages (sub-assistant hierarchy) */
@@ -2422,6 +2426,10 @@
.system-info {
border-left-color: var(--info-dimmed);
background-color: var(--highlight-dimmed);
+ }
+
+ .system-info .header,
+ .system-info .content {
font-size: 80%;
}
@@ -3308,18 +3316,14 @@
background-color: var(--bg-hover);
}
- .todo-item.completed {
- opacity: 0.7;
- }
-
.todo-item.completed .todo-content {
text-decoration: line-through;
color: var(--text-muted);
+ opacity: 0.7;
}
- .todo-item input[type="checkbox"] {
- margin: 0;
- cursor: default;
+ .todo-item.pending .todo-content {
+ font-weight: normal;
}
.todo-status {
@@ -6632,10 +6636,15 @@
--answer-accent: #4caf50;
--answer-bg: #f0fff4;
+ /* Priority palette (purple intensity - darker = more urgent) */
+ --priority-600: #7c3aed;
+ --priority-400: #a78bfa;
+ --priority-300: #c4b5fd;
+
/* Priority colors for todos */
- --priority-high: #dc3545;
- --priority-medium: #ffc107;
- --priority-low: #28a745;
+ --priority-high: var(--priority-600);
+ --priority-medium: var(--priority-400);
+ --priority-low: var(--priority-300);
/* Status colors */
--status-in-progress: #fff3cd;
@@ -6992,9 +7001,9 @@
/* Right-aligned messages (user-initiated, right margin 0, left margin 33%) */
.user:not(.compacted),
- .system,
.bash-input,
- .bash-output {
+ .bash-output,
+ .system:not(.system-info):not(.system-warning):not(.system-error) {
margin-left: 33%;
margin-right: 0;
}
@@ -7020,17 +7029,11 @@
margin-right: 6em;
}
- /* System warnings/info (assistant-initiated) */
+ /* System warnings/info at tool level (e.g., hook notifications) */
.system-warning,
.system-info {
- margin-left: 0;
- margin-right: 10em;
- }
-
- /* Exception: paired system-info messages align right (like user commands) */
- .system.system-info.paired-message {
- margin-left: 33%;
- margin-right: 0;
+ margin-left: 2em;
+ margin-right: 6em;
}
/* Sidechain messages (sub-assistant hierarchy) */
@@ -7153,6 +7156,10 @@
.system-info {
border-left-color: var(--info-dimmed);
background-color: var(--highlight-dimmed);
+ }
+
+ .system-info .header,
+ .system-info .content {
font-size: 80%;
}
@@ -8039,18 +8046,14 @@
background-color: var(--bg-hover);
}
- .todo-item.completed {
- opacity: 0.7;
- }
-
.todo-item.completed .todo-content {
text-decoration: line-through;
color: var(--text-muted);
+ opacity: 0.7;
}
- .todo-item input[type="checkbox"] {
- margin: 0;
- cursor: default;
+ .todo-item.pending .todo-content {
+ font-weight: normal;
}
.todo-status {
@@ -9905,34 +9908,29 @@
-
⏳
broken_todo
-
🔄
Implement core functionality
#2
-
⏳
Add comprehensive tests
#3
-
⏳
Write user documentation
#4
-
⏳
Perform code review
#5
@@ -11470,10 +11468,15 @@
--answer-accent: #4caf50;
--answer-bg: #f0fff4;
+ /* Priority palette (purple intensity - darker = more urgent) */
+ --priority-600: #7c3aed;
+ --priority-400: #a78bfa;
+ --priority-300: #c4b5fd;
+
/* Priority colors for todos */
- --priority-high: #dc3545;
- --priority-medium: #ffc107;
- --priority-low: #28a745;
+ --priority-high: var(--priority-600);
+ --priority-medium: var(--priority-400);
+ --priority-low: var(--priority-300);
/* Status colors */
--status-in-progress: #fff3cd;
@@ -11830,9 +11833,9 @@
/* Right-aligned messages (user-initiated, right margin 0, left margin 33%) */
.user:not(.compacted),
- .system,
.bash-input,
- .bash-output {
+ .bash-output,
+ .system:not(.system-info):not(.system-warning):not(.system-error) {
margin-left: 33%;
margin-right: 0;
}
@@ -11858,17 +11861,11 @@
margin-right: 6em;
}
- /* System warnings/info (assistant-initiated) */
+ /* System warnings/info at tool level (e.g., hook notifications) */
.system-warning,
.system-info {
- margin-left: 0;
- margin-right: 10em;
- }
-
- /* Exception: paired system-info messages align right (like user commands) */
- .system.system-info.paired-message {
- margin-left: 33%;
- margin-right: 0;
+ margin-left: 2em;
+ margin-right: 6em;
}
/* Sidechain messages (sub-assistant hierarchy) */
@@ -11991,6 +11988,10 @@
.system-info {
border-left-color: var(--info-dimmed);
background-color: var(--highlight-dimmed);
+ }
+
+ .system-info .header,
+ .system-info .content {
font-size: 80%;
}
@@ -12877,18 +12878,14 @@
background-color: var(--bg-hover);
}
- .todo-item.completed {
- opacity: 0.7;
- }
-
.todo-item.completed .todo-content {
text-decoration: line-through;
color: var(--text-muted);
+ opacity: 0.7;
}
- .todo-item input[type="checkbox"] {
- margin: 0;
- cursor: default;
+ .todo-item.pending .todo-content {
+ font-weight: normal;
}
.todo-status {
@@ -16339,10 +16336,15 @@
--answer-accent: #4caf50;
--answer-bg: #f0fff4;
+ /* Priority palette (purple intensity - darker = more urgent) */
+ --priority-600: #7c3aed;
+ --priority-400: #a78bfa;
+ --priority-300: #c4b5fd;
+
/* Priority colors for todos */
- --priority-high: #dc3545;
- --priority-medium: #ffc107;
- --priority-low: #28a745;
+ --priority-high: var(--priority-600);
+ --priority-medium: var(--priority-400);
+ --priority-low: var(--priority-300);
/* Status colors */
--status-in-progress: #fff3cd;
@@ -16699,9 +16701,9 @@
/* Right-aligned messages (user-initiated, right margin 0, left margin 33%) */
.user:not(.compacted),
- .system,
.bash-input,
- .bash-output {
+ .bash-output,
+ .system:not(.system-info):not(.system-warning):not(.system-error) {
margin-left: 33%;
margin-right: 0;
}
@@ -16727,17 +16729,11 @@
margin-right: 6em;
}
- /* System warnings/info (assistant-initiated) */
+ /* System warnings/info at tool level (e.g., hook notifications) */
.system-warning,
.system-info {
- margin-left: 0;
- margin-right: 10em;
- }
-
- /* Exception: paired system-info messages align right (like user commands) */
- .system.system-info.paired-message {
- margin-left: 33%;
- margin-right: 0;
+ margin-left: 2em;
+ margin-right: 6em;
}
/* Sidechain messages (sub-assistant hierarchy) */
@@ -16860,6 +16856,10 @@
.system-info {
border-left-color: var(--info-dimmed);
background-color: var(--highlight-dimmed);
+ }
+
+ .system-info .header,
+ .system-info .content {
font-size: 80%;
}
@@ -17746,18 +17746,14 @@
background-color: var(--bg-hover);
}
- .todo-item.completed {
- opacity: 0.7;
- }
-
.todo-item.completed .todo-content {
text-decoration: line-through;
color: var(--text-muted);
+ opacity: 0.7;
}
- .todo-item input[type="checkbox"] {
- margin: 0;
- cursor: default;
+ .todo-item.pending .todo-content {
+ font-weight: normal;
}
.todo-status {
diff --git a/test/test_todowrite_rendering.py b/test/test_todowrite_rendering.py
index 80942ab7..1a3b60a9 100644
--- a/test/test_todowrite_rendering.py
+++ b/test/test_todowrite_rendering.py
@@ -60,11 +60,6 @@ def test_format_todowrite_basic(self):
assert "🔄" in html # in_progress
assert "⏳" in html # pending
- # Check checkboxes
- assert 'type="checkbox"' in html
- assert "checked" in html # for completed item
- assert "disabled" in html # for completed item
-
# Check CSS classes
assert "todo-item completed high" in html
assert "todo-item in_progress medium" in html