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