diff --git a/.gitignore b/.gitignore index 160f8f7b..fa249f7a 100644 --- a/.gitignore +++ b/.gitignore @@ -179,3 +179,8 @@ test_output test/test_data/*.html .claude-trace .specstory + +# Local configuration files +.claude/settings.local.json +.vscode/settings.json +local.ps1 diff --git a/.vscode/settings.json b/.vscode/settings.json index eb7cde4c..e7d44597 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,4 +5,5 @@ "docs/claude-code-log-transcript.html": true, "test/test_data/cache/**": true, }, + "workbench.colorTheme": "Solarized Dark", } \ No newline at end of file diff --git a/claude_code_log/renderer.py b/claude_code_log/renderer.py index 74de37fd..80d28d82 100644 --- a/claude_code_log/renderer.py +++ b/claude_code_log/renderer.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import List, Optional, Union, Dict, Any, cast, TYPE_CHECKING +from typing import List, Optional, Dict, Any, cast, TYPE_CHECKING if TYPE_CHECKING: from .cache import CacheManager @@ -131,8 +131,13 @@ def format_timestamp(timestamp_str: str | None) -> str: def escape_html(text: str) -> str: - """Escape HTML special characters in text.""" - return html.escape(text) + """Escape HTML special characters in text. + + Also normalizes line endings (CRLF -> LF) to prevent double spacing in
blocks.
+ """
+ # Normalize CRLF to LF to prevent double line breaks in HTML
+ normalized = text.replace("\r\n", "\n").replace("\r", "\n")
+ return html.escape(normalized)
def create_collapsible_details(
@@ -285,35 +290,258 @@ def format_todowrite_content(tool_use: ToolUseContent) -> str:
"""
+def format_bash_tool_content(tool_use: ToolUseContent) -> str:
+ """Format Bash tool use content in VS Code extension style."""
+ command = tool_use.input.get("command", "")
+ description = tool_use.input.get("description", "")
+
+ escaped_command = escape_html(command)
+
+ html_parts = [""]
+
+ # Add description if present
+ if description:
+ escaped_desc = escape_html(description)
+ html_parts.append(f"{escaped_desc}")
+
+ # Add command in preformatted block
+ html_parts.append(f"{escaped_command}")
+ html_parts.append("")
+
+ return "".join(html_parts)
+
+
+def render_params_table(params: Dict[str, Any]) -> str:
+ """Render a dictionary of parameters as an HTML table.
+
+ Reusable for tool parameters, diagnostic objects, etc.
+ """
+ if not params:
+ return "No parameters"
+
+ html_parts = ["| {escaped_key} | +{value_html} | +
{escaped_input}"
+ # Special handling for Edit
+ if tool_use.name == "Edit":
+ return format_edit_tool_content(tool_use)
- # For longer content, use collapsible details but no extra wrapper
- preview_text = escaped_input[:200] + "..."
- return f"""
- {preview_text}{escaped_input}
- {escaped_content}
+ {escaped_content}..." + escaped_text + "" else: # Assistant messages get markdown rendering - return render_markdown(content) + return render_markdown(content[0].text) # content is a list of ContentItem objects rendered_parts: List[str] = [] @@ -564,6 +948,8 @@ def __init__( session_id: Optional[str] = None, is_session_header: bool = False, token_usage: Optional[str] = None, + tool_use_id: Optional[str] = None, + title_hint: Optional[str] = None, ): self.type = message_type self.content_html = content_html @@ -576,6 +962,11 @@ def __init__( self.is_session_header = is_session_header self.session_subtitle: Optional[str] = None self.token_usage = token_usage + self.tool_use_id = tool_use_id + self.title_hint = title_hint + # Pairing metadata + self.is_paired = False + self.pair_role: Optional[str] = None # "pair_first", "pair_last", "pair_middle" class TemplateProject: @@ -1017,13 +1408,27 @@ def _process_local_command_output(text_content: str) -> tuple[str, str, str]: ) if stdout_match: stdout_content = stdout_match.group(1).strip() - # Convert ANSI codes to HTML for colored display - html_content = _convert_ansi_to_html(stdout_content) - # Use
to preserve formatting and line breaks - content_html = ( - f"Command Output:
" - f"{html_content}" - ) + + # Check if content looks like markdown (starts with markdown headers) + is_markdown = bool(re.match(r"^#+\s+", stdout_content, re.MULTILINE)) + + if is_markdown: + # Render as markdown + import mistune + + markdown_html = mistune.html(stdout_content) + content_html = ( + f"Command Output:
" + f"{markdown_html}" + ) + else: + # Convert ANSI codes to HTML for colored display + html_content = _convert_ansi_to_html(stdout_content) + # Useto preserve formatting and line breaks + content_html = ( + f"Command Output:
" + f"{html_content}" + ) else: content_html = escape_html(text_content) @@ -1099,20 +1504,38 @@ def _process_bash_output(text_content: str) -> tuple[str, str, str]: def _process_regular_message( - text_only_content: Union[str, List[ContentItem]], + text_only_content: List[ContentItem], message_type: str, is_sidechain: bool, ) -> tuple[str, str, str]: """Process regular message and return (css_class, content_html, message_type).""" css_class = f"{message_type}" - content_html = render_message_content(text_only_content, message_type) + + # Handle user-specific preprocessing + if message_type == "user": + # Sub-assistant prompts (sidechain user messages) should be rendered as markdown + if is_sidechain: + content_html = render_message_content(text_only_content, "assistant") + is_compacted = False + else: + content_html, is_compacted = render_user_message_content(text_only_content) + if is_compacted: + css_class = f"{message_type} compacted" + message_type = "đ¤ User (compacted conversation)" + else: + # Non-user messages: render directly + content_html = render_message_content(text_only_content, message_type) + is_compacted = False if is_sidechain: - css_class = f"{message_type} sidechain" + css_class = f"{css_class} sidechain" # Update message type for display - message_type = ( - "đ Sub-assistant prompt" if message_type == "user" else "đ Sub-assistant" - ) + if not is_compacted: # Don't override compacted message type + message_type = ( + "đ Sub-assistant prompt" + if message_type == "user" + else "đ Sub-assistant" + ) return css_class, content_html, message_type @@ -1128,6 +1551,73 @@ def _get_combined_transcript_link(cache_manager: "CacheManager") -> Optional[str return None +def _identify_message_pairs(messages: List[TemplateMessage]) -> None: + """Identify and mark paired messages (e.g., command + output, tool use + result). + + Modifies messages in-place by setting is_paired and pair_role fields. + """ + i = 0 + while i < len(messages): + current = messages[i] + + # Skip session headers + if current.is_session_header: + i += 1 + continue + + # Check for system command + command output pair + if current.css_class == "system" and i + 1 < len(messages): + next_msg = messages[i + 1] + if "command-output" in next_msg.css_class: + current.is_paired = True + current.pair_role = "pair_first" + next_msg.is_paired = True + next_msg.pair_role = "pair_last" + i += 2 + continue + + # Check for tool_use + tool_result pair (match by tool_use_id) + if "tool_use" in current.css_class and current.tool_use_id: + # Look ahead for matching tool_result + for j in range( + i + 1, min(i + 10, len(messages)) + ): # Look ahead up to 10 messages + next_msg = messages[j] + if ( + "tool_result" in next_msg.css_class + and next_msg.tool_use_id == current.tool_use_id + ): + current.is_paired = True + current.pair_role = "pair_first" + next_msg.is_paired = True + next_msg.pair_role = "pair_last" + break + + # Check for bash-input + bash-output pair + if current.css_class == "bash-input" and i + 1 < len(messages): + next_msg = messages[i + 1] + if next_msg.css_class == "bash-output": + current.is_paired = True + current.pair_role = "pair_first" + next_msg.is_paired = True + next_msg.pair_role = "pair_last" + i += 2 + continue + + # Check for thinking + assistant pair + if "thinking" in current.css_class and i + 1 < len(messages): + next_msg = messages[i + 1] + if "assistant" in next_msg.css_class: + current.is_paired = True + current.pair_role = "pair_first" + next_msg.is_paired = True + next_msg.pair_role = "pair_last" + i += 2 + continue + + i += 1 + + def generate_session_html( messages: List[TranscriptEntry], session_id: str, @@ -1239,8 +1729,9 @@ def generate_html( level_icon = {"warning": "â ī¸", "error": "â", "info": "âšī¸"}.get(level, "âšī¸") level_css = f"system system-{level}" - escaped_content = escape_html(message.content) - content_html = f"{level_icon} System {level.title()}: {escaped_content}" + # Process ANSI codes in system messages (they may contain command output) + html_content = _convert_ansi_to_html(message.content) + content_html = f"{level_icon} {html_content}" system_template_message = TemplateMessage( message_type=f"System {level.title()}", @@ -1260,7 +1751,7 @@ def generate_html( # Separate tool/thinking/image content from text content tool_items: List[ContentItem] = [] - text_only_content: Union[str, List[ContentItem]] = [] + text_only_content: List[ContentItem] = [] if isinstance(message_content, list): text_only_items: List[ContentItem] = [] @@ -1279,7 +1770,9 @@ def generate_html( text_only_content = text_only_items else: # Single string content - text_only_content = message_content + message_content = message_content.strip() + if message_content: + text_only_content = [TextContent(type="text", text=message_content)] # Skip if no meaningful content if not text_content.strip() and not tool_items: @@ -1447,12 +1940,7 @@ def generate_html( ) # Create main message (if it has text content) - if text_only_content and ( - isinstance(text_only_content, str) - and text_only_content.strip() - or isinstance(text_only_content, list) - and text_only_content - ): + if text_only_content: template_message = TemplateMessage( message_type=message_type, content_html=content_html, @@ -1474,6 +1962,8 @@ def generate_html( # Handle both custom types and Anthropic types item_type = getattr(tool_item, "type", None) + item_tool_use_id: Optional[str] = None + tool_title_hint: Optional[str] = None if isinstance(tool_item, ToolUseContent) or item_type == "tool_use": # Convert Anthropic type to our format if necessary @@ -1490,10 +1980,13 @@ def generate_html( tool_content_html = format_tool_use_content(tool_use_converted) escaped_name = escape_html(tool_use_converted.name) escaped_id = escape_html(tool_use_converted.id) + item_tool_use_id = tool_use_converted.id + tool_title_hint = f"ID: {escaped_id}" + # Use simplified display names without "Tool Use:" prefix if tool_use_converted.name == "TodoWrite": - tool_message_type = f"đ Todo List (ID: {escaped_id})" + tool_message_type = "đ Todo List" else: - tool_message_type = f"Tool Use: {escaped_name} (ID: {escaped_id})" + tool_message_type = escaped_name tool_css_class = "tool_use" elif isinstance(tool_item, ToolResultContent) or item_type == "tool_result": # Convert Anthropic type to our format if necessary @@ -1509,11 +2002,16 @@ def generate_html( tool_content_html = format_tool_result_content(tool_result_converted) escaped_id = escape_html(tool_result_converted.tool_use_id) - error_indicator = ( - " (đ¨ Error)" if tool_result_converted.is_error else "" + item_tool_use_id = tool_result_converted.tool_use_id + tool_title_hint = f"ID: {escaped_id}" + # Simplified: no "Tool Result" heading, just show error indicator if present + error_indicator = "đ¨ Error" if tool_result_converted.is_error else "" + tool_message_type = error_indicator if error_indicator else "" + tool_css_class = ( + "tool_result error" + if tool_result_converted.is_error + else "tool_result" ) - tool_message_type = f"Tool Result{error_indicator}: {escaped_id}" - tool_css_class = "tool_result" elif isinstance(tool_item, ThinkingContent) or item_type == "thinking": # Convert Anthropic type to our format if necessary if not isinstance(tool_item, ThinkingContent): @@ -1556,6 +2054,8 @@ def generate_html( raw_timestamp=tool_timestamp, session_summary=session_summary, session_id=session_id, + tool_use_id=item_tool_use_id, + title_hint=tool_title_hint, ) template_messages.append(tool_template_message) @@ -1612,6 +2112,9 @@ def generate_html( } ) + # Identify and mark paired messages (command+output, tool_use+tool_result, etc.) + _identify_message_pairs(template_messages) + # Render template env = _get_template_environment() template = env.get_template("transcript.html") diff --git a/claude_code_log/templates/components/edit_diff_styles.css b/claude_code_log/templates/components/edit_diff_styles.css new file mode 100644 index 00000000..af105adb --- /dev/null +++ b/claude_code_log/templates/components/edit_diff_styles.css @@ -0,0 +1,76 @@ +/* Edit tool diff styling */ +.edit-tool-content { + margin: 8px 0; +} + +.edit-file-path { + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 8px; + font-size: 0.95em; +} + +.edit-replace-all { + color: var(--text-muted); + font-size: 0.85em; + font-style: italic; + margin-bottom: 8px; +} + +.edit-diff { + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 4px; + overflow-x: auto; + font-family: var(--font-monospace); + font-size: 80%; + line-height: 2ex; +} + +/* Diff line styling */ +.diff-line { + padding: 2px 4px 2px 2px; + white-space: pre-wrap; + word-wrap: break-word; +} + +.diff-marker { + display: inline-block; + width: 1.5em; + text-align: center; + user-select: none; + color: var(--text-muted); +} + +/* Line backgrounds */ +.diff-removed { + background-color: #ffebe9; + border-left: 3px solid #f85149; +} + +.diff-added { + background-color: #dafbe1; + border-left: 3px solid #3fb950; +} + +.diff-context { + background-color: var(--code-bg-color); + border-left: 3px solid transparent; +} + +/* Character-level highlighting */ +.diff-char-removed { + background-color: #ffcecb; + border-radius: 2px; +} + +.diff-char-added { + background-color: #abf2bc; + border-radius: 2px; +} + +/* Remove default mark styling */ +mark.diff-char-removed, +mark.diff-char-added { + color: inherit; +} diff --git a/claude_code_log/templates/components/filter_styles.css b/claude_code_log/templates/components/filter_styles.css index d39e1439..e6bfaa36 100644 --- a/claude_code_log/templates/components/filter_styles.css +++ b/claude_code_log/templates/components/filter_styles.css @@ -107,6 +107,32 @@ background-color: #ffffff66; } +/* Color-coded filter buttons */ +.filter-toggle[data-type="user"] { + border-color: #ff9800; + border-width: 2px; +} + +.filter-toggle[data-type="system"] { + border-color: #d98100; + border-width: 2px; +} + +.filter-toggle[data-type="tool"] { + border-color: #4caf50; + border-width: 2px; +} + +.filter-toggle[data-type="assistant"] { + border-color: #9c27b0; + border-width: 2px; +} + +.filter-toggle[data-type="thinking"] { + border-color: #9c27b066; + border-width: 2px; +} + .filter-actions { display: flex; gap: 6px; diff --git a/claude_code_log/templates/components/global_styles.css b/claude_code_log/templates/components/global_styles.css index 4da9c3bb..77442f46 100644 --- a/claude_code_log/templates/components/global_styles.css +++ b/claude_code_log/templates/components/global_styles.css @@ -1,6 +1,45 @@ /* Global styles shared across all templates */ + +/* CSS Variables for shared colors and consistent theming */ +:root { + /* Base colors */ + --code-bg-color: #f5f1e8; + --tool-param-sep-color: #4b494822; + + /* Dimmed/transparent variants (66 = ~40% opacity) */ + --white-dimmed: #ffffff66; + --highlight-dimmed: #e3f2fd66; + --assistant-dimmed: #9c27b066; + --info-dimmed: #2196f366; + --warning-dimmed: #d9810066; + --success-dimmed: #4caf5066; + --error-dimmed: #f4433666; + --neutral-dimmed: #f8f9fa66; + --tool-input-dimmed: #fff3cd66; + --thinking-dimmed: #f0f0f066; + --tool-result-dimmed: #e8f5e866; + --session-bg-dimmed: #e8f4fd66; + --ide-notification-dimmed: #d2d6d966; + + /* Fully transparent variants (88 = ~53% opacity) */ + --highlight-semi: #e3f2fd88; + --error-semi: #ffebee88; + --neutral-semi: #f8f9fa88; + + /* Slightly transparent variants (55 = ~33% opacity) */ + --highlight-light: #e3f2fd55; + + /* Solid colors for text and accents */ + --text-muted: #666; + --text-secondary: #495057; + + /* Font families */ + --font-monospace: 'Fira Code', 'Monaco', 'Consolas', 'SF Mono', 'Inconsolata', 'Droid Sans Mono', 'Source Code Pro', 'Ubuntu Mono', 'Cascadia Code', 'Menlo', monospace; + --font-ui: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; +} + body { - font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', 'Ubuntu Mono', 'Cascadia Code', 'Menlo', 'Consolas', monospace; + font-family: var(--font-monospace); line-height: 1.5; max-width: 1200px; margin: 0 auto; @@ -17,14 +56,6 @@ h1 { } /* Common typography */ -code { - background-color: #f5f5f5; - padding: 2px 4px; - border-radius: 3px; - font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', 'Ubuntu Mono', 'Cascadia Code', 'Menlo', 'Consolas', monospace; - line-height: 1.5; -} - pre { background-color: #12121212; padding: 10px; @@ -32,7 +63,7 @@ pre { white-space: pre-wrap; word-wrap: break-word; word-break: break-word; - font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', 'Ubuntu Mono', 'Cascadia Code', 'Menlo', 'Consolas', monospace; + font-family: var(--font-monospace); line-height: 1.5; } diff --git a/claude_code_log/templates/components/message_styles.css b/claude_code_log/templates/components/message_styles.css index e6725e9e..8bd2502c 100644 --- a/claude_code_log/templates/components/message_styles.css +++ b/claude_code_log/templates/components/message_styles.css @@ -1,16 +1,43 @@ /* Message and content styles */ .message { margin-bottom: 1em; + margin-left: 1em; padding: 1em; border-radius: 8px; - border-left: #ffffff66 1px solid; - background-color: #e3f2fd55; + border-left: var(--white-dimmed) 2px solid; + background-color: var(--highlight-light); box-shadow: -7px -7px 10px #eeeeee44, 7px 7px 10px #00000011; - border-top: #ffffff66 1px solid; + border-top: var(--white-dimmed) 1px solid; border-bottom: #00000017 1px solid; border-right: #00000017 1px solid; } +/* Paired message styling */ +.message.paired-message { + margin-bottom: 0; +} + +.message.paired-message.pair_first { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: none; +} + +.message.paired-message.pair_last { + margin-top: 0; + margin-bottom: 1em; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top: 1px solid #00000011; +} + +.message.paired-message.pair_middle { + margin-top: 0; + border-radius: 0; + border-top: 1px solid #00000011; + border-bottom: none; +} + .session-divider { margin: 70px 0; border-top: 2px solid #fff; @@ -18,36 +45,47 @@ /* Message type styling */ .user { - border-left-color: #2196f3; + border-left-color: #ff9800; + margin-left: 0; } .assistant { border-left-color: #9c27b0; } +/* Dimmed assistant when paired with thinking */ +.assistant.paired-message { + border-left-color: var(--assistant-dimmed); +} + .system { - border-left-color: #ff9800; + border-left-color: #d98100; + margin-left: 0; } .system-warning { - border-left-color: #ff9800; - background-color: #fff3e088; + border-left-color: #2196f3; + background-color: var(--highlight-semi); + margin-left: 2em; /* Extra indent - assistant-initiated */ } .system-error { border-left-color: #f44336; - background-color: #ffebee88; + background-color: var(--error-semi); + margin-left: 0; } .system-info { - border-left-color: #2196f3; - background-color: #e3f2fd88; + border-left-color: var(--info-dimmed); + background-color: var(--highlight-dimmed); + margin-left: 2em; /* Extra indent - assistant-initiated */ + font-size: 80%; } /* Command output styling */ .command-output { background-color: #1e1e1e11; - border-left-color: #00bcd4; + border-left-color: var(--warning-dimmed); } .command-output-content { @@ -56,7 +94,7 @@ border-radius: 4px; border: 1px solid #00000011; margin-top: 8px; - font-family: 'Fira Code', 'Monaco', 'Consolas', monospace; + font-family: var(--font-monospace); font-size: 0.9em; line-height: 1.4; white-space: pre-wrap; @@ -79,7 +117,7 @@ } .bash-command { - font-family: 'Fira Code', 'Monaco', 'Consolas', monospace; + font-family: var(--font-monospace); font-size: 0.95em; color: #2c3e50; background-color: #f8f9fa; @@ -89,7 +127,7 @@ /* Bash output styling */ .bash-output { - background-color: #f8f9fa66; + background-color: var(--neutral-dimmed); border-left-color: #607d8b; } @@ -99,7 +137,7 @@ border-radius: 4px; border: 1px solid #00000011; margin: 8px 0; - font-family: 'Fira Code', 'Monaco', 'Consolas', monospace; + font-family: var(--font-monospace); font-size: 0.9em; line-height: 1.4; white-space: pre-wrap; @@ -114,7 +152,7 @@ border-radius: 4px; border: 1px solid #ffcdd2; margin: 8px 0; - font-family: 'Fira Code', 'Monaco', 'Consolas', monospace; + font-family: var(--font-monospace); font-size: 0.9em; line-height: 1.4; white-space: pre-wrap; @@ -128,24 +166,73 @@ font-style: italic; } +/* Bash tool content styling (Tool Use message) */ +.bash-tool-content { + margin: 8px 0; +} + +.bash-tool-description { + color: var(--text-muted); + font-size: 0.95em; + margin-bottom: 8px; + line-height: 1.4; +} + +.bash-tool-command { + background-color: #f8f9fa; + padding: 8px 12px; + border-radius: 4px; + border: 1px solid var(--tool-param-sep-color); + font-family: var(--font-monospace); + font-size: 0.9em; + color: #2c3e50; + margin: 0; + overflow-x: auto; +} + .tool_use { - border-left-color: #e91e63; + border-left-color: #4caf50; + margin-left: 2em; /* Extra indent - assistant-initiated */ } .tool_result { - border-left-color: #4caf50; + border-left-color: var(--success-dimmed); + margin-left: 2em; /* Extra indent - assistant-initiated */ +} + +.tool_result.error { + border-left-color: var(--error-dimmed); + background-color: var(--error-semi); +} + +.message.tool_result pre { + font-size: 80%; } /* Sidechain message styling */ .sidechain { opacity: 0.85; - background-color: #f8f9fa88; + background-color: var(--neutral-semi); border-left-width: 2px; border-left-style: dashed; } +/* Sidechain indentation hierarchy */ +.sidechain.user { + margin-left: 3em; /* Sub-assistant Prompt - nested below Task tool use (2em) */ +} + +.sidechain.assistant { + margin-left: 4em; /* Sub-assistant - nested below prompt (3em) */ +} + +.sidechain.tool_use, +.sidechain.tool_result { + margin-left: 5em; /* Sub-assistant tools - nested below assistant (4em) */ +} + .sidechain .sidechain-indicator { - color: #666; + color: var(--text-muted); font-size: 0.9em; margin-bottom: 5px; padding: 2px 6px; @@ -155,22 +242,28 @@ } .thinking { - border-left-color: #9e9e9e; + border-left-color: var(--assistant-dimmed); +} + +/* Full purple when thinking is paired (as pair_first) */ +.thinking.paired-message.pair_first { + border-left-color: #9c27b0; } .image { - border-left-color: #ff5722; + border-left-color: #d48a5e; + margin-left: 0; /* Align with user messages */ } /* Session header styling */ .session-header { - background-color: #e8f4fd66; + background-color: var(--session-bg-dimmed); border-radius: 8px; padding: 16px; margin: 30px 0 20px 0; box-shadow: -7px -7px 10px #eeeeee44, 7px 7px 10px #00000011; - border-left: #ffffff66 1px solid; - border-top: #ffffff66 1px solid; + border-left: var(--white-dimmed) 1px solid; + border-top: var(--white-dimmed) 1px solid; border-bottom: #00000017 1px solid; border-right: #00000017 1px solid; } @@ -180,6 +273,45 @@ font-size: 1.2em; } +/* IDE notification styling */ +.ide-notification { + background-color: var(--ide-notification-dimmed); + border-left: #9c27b0 2px solid; + padding: 8px 12px; + margin: 8px 0; + border-radius: 4px; + font-size: 0.9em; + font-style: italic; +} + +/* IDE selection styling */ +.ide-selection-collapsible { + margin-top: 4px; +} + +.ide-selection-collapsible summary { + cursor: pointer; + color: var(--text-muted); + user-select: none; +} + +.ide-selection-collapsible summary:hover { + color: #333; +} + +.ide-selection-content { + margin-top: 8px; + padding: 8px; + background-color: #f8f9fa; + border-radius: 3px; + border: 1px solid #dee2e6; + font-family: var(--font-monospace); + font-size: 0.85em; + line-height: 1.4; + white-space: pre-wrap; + overflow-x: auto; +} + /* Content styling */ .content { word-wrap: break-word; @@ -195,65 +327,136 @@ margin-left: 1em; } +/* Assistant and Thinking content styling */ +.assistant .content, +.thinking-text, +.user.compacted .content { + font-family: var(--font-ui); +} + +/* Code block styling */ +pre > code { + display: block; +} + +code { + background-color: var(--code-bg-color); +} + /* Tool content styling */ .tool-content { - background-color: #f8f9fa66; + background-color: var(--neutral-dimmed); border-radius: 4px; padding: 8px; margin: 8px 0; overflow-x: auto; box-shadow: -4px -4px 10px #eeeeee33, 4px 4px 10px #00000007; - border-left: #ffffff66 1px solid; - border-top: #ffffff66 1px solid; + border-left: var(--white-dimmed) 1px solid; + border-top: var(--white-dimmed) 1px solid; border-bottom: #00000017 1px solid; border-right: #00000017 1px solid; } +/* Tool parameter table styling */ +.tool-params-table { + width: 100%; + border-collapse: collapse; + font-size: 80%; +} + +.tool-params-table tr { + border-bottom: 1px solid var(--tool-param-sep-color); +} + +.tool-param-key { + padding: 4px; + font-weight: 600; + color: var(--text-secondary); + vertical-align: top; + width: 8em; +} + +.tool-param-value { + padding: 4px; + color: #212529; + vertical-align: top; +} + +.tool-param-structured { + margin: 0; + background-color: #f8f9fa; + padding: 4px; + border-radius: 3px; +} + +.tool-param-collapsible { + margin: 0; +} + +.tool-param-collapsible summary { + cursor: pointer; + color: var(--text-muted); +} + +.tool-param-collapsible summary:hover { + color: #333; +} + +.tool-param-full { + margin-top: 4px; + word-break: break-all; +} + +.tool-params-empty { + color: #999; + font-style: italic; +} + .tool-result { - background-color: #e8f5e866; + background-color: var(--tool-result-dimmed); border-left: #4caf5088 1px solid; } .tool-use { - background-color: #e3f2fd66; + background-color: var(--highlight-dimmed); border-left: #2196f388 1px solid; } .thinking-content { - background-color: #f0f0f066; + background-color: var(--thinking-dimmed); border-left: #66666688 1px solid; } .thinking-text { - font-style: italic; - white-space: pre-wrap; + font-family: var(--font-ui); + font-size: 90%; word-wrap: break-word; color: #555; } .tool-input { - background-color: #fff3cd66; + background-color: var(--tool-input-dimmed); border-radius: 4px; padding: 6px; margin: 4px 0; font-size: 0.9em; box-shadow: -7px -7px 10px #eeeeee44, 7px 7px 10px #00000011; - border-left: #ffffff66 1px solid; - border-top: #ffffff66 1px solid; + border-left: var(--white-dimmed) 1px solid; + border-top: var(--white-dimmed) 1px solid; border-bottom: #00000017 1px solid; border-right: #00000017 1px solid; } /* Session summary styling */ .session-summary { - background-color: #ffffff66; + background-color: var(--white-dimmed); border-left: #4caf5088 4px solid; padding: 12px; margin: 8px 0; border-radius: 0 4px 4px 0; font-style: italic; box-shadow: -7px -7px 10px #eeeeee44, 7px 7px 10px #00000011; - border-top: #ffffff66 1px solid; + border-top: var(--white-dimmed) 1px solid; border-bottom: #00000017 1px solid; border-right: #00000017 1px solid; } @@ -261,7 +464,7 @@ /* Collapsible details styling */ details summary { cursor: pointer; - color: #666; + color: var(--text-muted); } .collapsible-details { diff --git a/claude_code_log/templates/components/todo_styles.css b/claude_code_log/templates/components/todo_styles.css index 9f27d1bd..c1e6f71e 100644 --- a/claude_code_log/templates/components/todo_styles.css +++ b/claude_code_log/templates/components/todo_styles.css @@ -63,6 +63,8 @@ flex: 1; color: #333; font-weight: 500; + font-size: 90%; + font-family: var(--font-ui); } .todo-id { diff --git a/claude_code_log/templates/transcript.html b/claude_code_log/templates/transcript.html index db8e173e..2bd92d33 100644 --- a/claude_code_log/templates/transcript.html +++ b/claude_code_log/templates/transcript.html @@ -15,6 +15,7 @@ {% include 'components/todo_styles.css' %} {% include 'components/timeline_styles.css' %} {% include 'components/search_styles.css' %} +{% include 'components/edit_diff_styles.css' %} @@ -38,16 +39,14 @@đ Search & Filter
{% else %} -+ - - - - -@@ -81,12 +80,12 @@đ Search & Filter