Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
fb9350a
Add message type documentation and sample extraction
cboos Dec 2, 2025
af7d548
Add flatten() methods to TemplateMessage for tree traversal
cboos Dec 2, 2025
e0331a0
Add _build_message_tree() to populate children fields
cboos Dec 2, 2025
4299b9a
Add tests for TemplateMessage tree and flatten functionality
cboos Dec 2, 2025
c05cd6a
Update architecture doc: mark Phase 1 and Phase 2 complete
cboos Dec 2, 2025
0a0ae08
Fix type annotation issues in scripts and tests
cboos Dec 6, 2025
7f1d9c8
Extract ANSI colors and code rendering to dedicated modules
cboos Dec 6, 2025
25abf51
Decompose _process_messages_loop() into focused helpers (Phase 5)
cboos Dec 6, 2025
0c37d02
Simplify _identify_message_pairs() with focused helpers (Phase 6)
cboos Dec 6, 2025
540732a
Reorganize message samples into subdirectories with consistent naming
cboos Dec 7, 2025
660a7b2
Document hierarchy system architecture in FOLD_STATE_DIAGRAM.md (Phas…
cboos Dec 7, 2025
5b9aa86
Comprehensive message type and CSS documentation (Phase 7 extended)
cboos Dec 7, 2025
9d2a00c
Add Phase 8 tests: message variants, queue ops, CSS modifiers
cboos Dec 7, 2025
13300b6
Add TypedDict for OUTPUT_CATEGORIES type safety
cboos Dec 7, 2025
7e45f11
Add coverage gap tests and enable DEBUG_TIMING in CI
cboos Dec 7, 2025
e39e13f
Remove redundant paired-message CSS class
cboos Dec 7, 2025
9f60776
Mark Phase 8 (Testing Infrastructure) complete
cboos Dec 7, 2025
070ac15
Add MessageType enum and type guards for type safety (Phase 9)
cboos Dec 7, 2025
7aede59
Update MESSAGE_REFACTORING.md: mark Phase 9 complete
cboos Dec 7, 2025
9292229
Fix slash command and command output rendering to use correct user type
cboos Dec 7, 2025
6ce62b9
Remove redundant labels from slash command and command output content
cboos Dec 7, 2025
82a9f1f
Add error tool result sample from real_projects
cboos Dec 7, 2025
045671a
Show fold bar border only when collapsed
cboos Dec 7, 2025
8a6cc3a
Add Read tool error sample to emphasize generic error handling
cboos Dec 7, 2025
f22162f
Simplify extract_text_content() with isinstance checks (Phase 10)
cboos Dec 7, 2025
9206ae2
Add typed input models for 9 common tools (Phase 11)
cboos Dec 7, 2025
e318be5
Document Phase 11/12 independence and add typed input models to tool …
cboos Dec 7, 2025
9fc8260
Fix pairing documentation for slash command/command output
cboos Dec 7, 2025
90c14a7
Phase 12a: Add MessageModifiers dataclass and modifiers field
cboos Dec 8, 2025
5a31556
Phase 12e: Remove css_class field from TemplateMessage
cboos Dec 8, 2025
f3e5979
Remove css_class from message processing functions
cboos Dec 8, 2025
56ed1bb
Fix lint and type errors after css_class removal
cboos Dec 8, 2025
0339f2e
Use dataclasses.replace() for cleaner modifier updates
cboos Dec 8, 2025
6a791e6
Move HTML utilities to html_renderer.py (Phase 13, Step 2)
cboos Dec 8, 2025
743a1b7
Move collapsible rendering functions to html_renderer.py (Phase 13, S…
cboos Dec 8, 2025
fca9c61
Move template environment to html_renderer.py (Phase 13, Step 4)
cboos Dec 8, 2025
bfa1d4c
Extract tool formatters to html_tool_renderers.py (Phase 13, Step 5)
cboos Dec 8, 2025
569f29f
Update MESSAGE_REFACTORING.md with Phase 12 implementation plan
cboos Dec 8, 2025
fb1617a
Complete Phase 13: add thematic sections and update docs (Steps 10-11)
cboos Dec 8, 2025
dc68c70
Move lenient parsing to model layer for early typed parsing
cboos Dec 8, 2025
d38c211
Refactor: eliminate duplication between parse_tool_input and parsed_i…
cboos Dec 8, 2025
1f44b03
Move tool title generation to html_tool_renderers.py
cboos Dec 8, 2025
fb5011b
Add typed inputs for AskUserQuestion, ExitPlanMode, and Read tools
cboos Dec 8, 2025
b7f2e6c
Fix ty type checker warnings with explicit casts
cboos Dec 8, 2025
070dc44
Add explicit sessionId=None to SummaryTranscriptEntry and simplify ge…
cboos Dec 8, 2025
23b435a
Add Renderer abstraction with get_renderer factory pattern
cboos Dec 9, 2025
8d8b17b
Extract title_for_projects_index utility and inline template rendering
cboos Dec 9, 2025
1c49c28
Move format_timestamp_range to utils and remove unnecessary casts
cboos Dec 9, 2025
ab25b06
Reorganize HTML rendering into html/ subpackage
cboos Dec 9, 2025
244f3b0
Add format parameter to support multiple output formats
cboos Dec 9, 2025
4a7b5f6
Reuse format_timestamp_range instead of reimplementing it
cboos Dec 9, 2025
4b72793
Move HtmlRenderer class and convenience functions to html/renderer.py
cboos Dec 9, 2025
c224608
Extract session utility functions and reorganize section headers
cboos Dec 9, 2025
80d1373
Split generate_html into format-neutral and HTML-specific parts
cboos Dec 9, 2025
b164612
Reorganize renderer.py: add Template Generation section
cboos Dec 10, 2025
bd368b7
Move check_html_version to html/renderer.py
cboos Dec 10, 2025
25ea667
Fix ty type checker issues
cboos Dec 10, 2025
d7a911f
Fix test_edge_cases_render assertions to match refactored renderer
cboos Dec 10, 2025
6c2f51e
Reorganize formatters into thematic modules with content models
cboos Dec 11, 2025
78ce5b8
Update messages.md with improved documentation and bash samples
cboos Dec 11, 2025
60d8672
Add exit_plan_mode tool samples (legacy name variant)
cboos Dec 11, 2025
827b76e
Move User Message Content Models from user_formatters.py to models.py
cboos Dec 11, 2025
d6ca0ee
Refactor: move parsing helpers to parser.py, loading functions to con…
cboos Dec 12, 2025
b4bd343
Consolidate parsing functions into parser.py
cboos Dec 12, 2025
fa5a3c9
Move AssistantTextContent and ThinkingContentModel to models.py
cboos Dec 12, 2025
84feab5
Remove duplicate format_thinking_content, use html/assistant_formatters
cboos Dec 12, 2025
38063fe
Move format_tool_result_content to html/tool_formatters.py
cboos Dec 12, 2025
ebf26ea
Move format_image_content to html/assistant_formatters.py
cboos Dec 12, 2025
9c00594
Update documentation for formatter refactoring
cboos Dec 12, 2025
790f4bc
Refactor _process_bash_output with parser + formatter separation
cboos Dec 12, 2025
6d130bc
Add format_user_text_content and use in render_message_content
cboos Dec 12, 2025
4eef02d
Refactor IDE notification handling with parser + formatter separation
cboos Dec 12, 2025
b85f678
Use content models for session headers, dedup notices; inline _proces…
cboos Dec 13, 2025
9eb9035
Fix 80x performance regression in Content formatting
cboos Dec 13, 2025
c9ade5b
Fix message tree duplication in reorder functions
cboos Dec 13, 2025
18da172
Fix type errors in models.py and user_formatters.py
cboos Dec 13, 2025
5b35397
Move ansi_colors.py to html/ directory
cboos Dec 13, 2025
06c5baa
Update messages.md documentation and clean up models.py
cboos Dec 13, 2025
01bb986
Fix test import after ansi_colors.py move
cboos Dec 13, 2025
d46b66b
Revert "Fix 80x performance regression in Content formatting"
cboos Dec 13, 2025
848524d
Fix _format_all_content to not recurse into children
cboos Dec 13, 2025
c5048b9
Return tree roots from generate_template_messages, flatten via pre-order
cboos Dec 13, 2025
3db5396
Update TEMPLATE_MESSAGE_CHILDREN.md with tree-first architecture
cboos Dec 13, 2025
3d2207e
Clarify tree roots are messages without ancestry, not just session he…
cboos Dec 13, 2025
4659a27
Add markdown table cell styling
cboos Dec 13, 2025
cf909de
Fix double 80% font-size on sidechain tool results
cboos Dec 13, 2025
57a497f
Update MESSAGE_REFACTORING.md to reflect current architecture
cboos Dec 13, 2025
9986596
Move templates/ to html/templates/
cboos Dec 13, 2025
31120ff
Move renderer_code.py to html/ directory
cboos Dec 13, 2025
02d505b
Regenerate style guide HTML
cboos Dec 13, 2025
12de201
Improve tool sample extraction with proper pairing
cboos Dec 8, 2025
884ff19
Add second session to style guide from message samples
cboos Dec 8, 2025
44d3b2c
Extract _filter_messages from _collect_session_info
cboos Dec 13, 2025
c1862e0
Refactor user message content to use parser/formatter pattern
cboos Dec 13, 2025
7980e23
Update IDE tag tests to use parse/format API, remove wrappers
cboos Dec 13, 2025
9684fdf
Move parse_user_message_content to parser.py
cboos Dec 13, 2025
181f718
Add user message parsing tests, fix SDK TextBlock compatibility
cboos Dec 13, 2025
1c20943
Simplify parse_user_message_content return type
cboos Dec 13, 2025
0281830
Remove render_message_content, use type-specific parsers
cboos Dec 13, 2025
3b13098
Add UnknownContent model and formatter for unknown content types
cboos Dec 13, 2025
96bf23f
Remove content_html field from TemplateMessage
cboos Dec 13, 2025
5c7b01d
Add type annotations to fix pyright and ty errors
cboos Dec 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
345 changes: 345 additions & 0 deletions PLAN_PHASE12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
# Phase 12: Format-Neutral Decomposition Plan

## Overview

This plan separates format-neutral logic from HTML-specific generation in renderer.py. The goal is to:
1. Create a `TemplateMessage` that stores logical attributes instead of CSS classes
2. Move HTML-specific rendering to a new `html_renderer.py` module
3. Keep format-neutral processing in `renderer.py` (to be renamed later)

## Key Design Decisions

### 1. Replace `css_class` with Typed Attributes

Instead of encoding traits as space-separated CSS classes (e.g., `"user sidechain slash-command"`), we'll use explicit fields:

```python
# In models.py - add MessageModifiers dataclass
@dataclass
class MessageModifiers:
"""Semantic modifiers for message rendering."""
is_sidechain: bool = False
is_slash_command: bool = False
is_command_output: bool = False
is_compacted: bool = False
is_error: bool = False
is_steering: bool = False
system_level: Optional[str] = None # "info", "warning", "error", "hook"
```

The `TemplateMessage` will have:
- `type: MessageType` (already have the enum)
- `modifiers: MessageModifiers` (new)
- Remove `css_class` field

### 2. HTML Renderer Module (`html_renderer.py`)

New module containing HTML-specific functions:

```python
# html_renderer.py

def css_class_from_message(msg: TemplateMessage) -> str:
"""Generate CSS class string from message type and modifiers."""
parts = [msg.type.value]
if msg.modifiers.is_sidechain:
parts.append("sidechain")
if msg.modifiers.is_slash_command:
parts.append("slash-command")
if msg.modifiers.is_command_output:
parts.append("command-output")
if msg.modifiers.is_compacted:
parts.append("compacted")
if msg.modifiers.is_error:
parts.append("error")
if msg.modifiers.is_steering:
parts.append("steering")
if msg.modifiers.system_level:
parts.append(f"system-{msg.modifiers.system_level}")
return " ".join(parts)

def get_message_emoji(msg: TemplateMessage) -> str:
"""Return emoji for message type."""
# Move emoji logic from template to here

def render_content_html(msg: TemplateMessage) -> str:
"""Render message content to HTML."""
# Delegates to format_* functions
```

### 3. Keep Format-Neutral Processing in renderer.py

Functions that stay in renderer.py (format-neutral):
- `_process_messages_loop()` - but sets `modifiers` instead of `css_class`
- `_identify_message_pairs()` - pairing logic
- `_build_message_hierarchy()` - but uses `type` and `modifiers` instead of `css_class`
- `_reorder_paired_messages()` - reordering logic
- Deduplication logic
- Token aggregation

### 4. Migration Strategy

The migration will be done in phases to minimize disruption:

**Phase 12a: Add MessageModifiers**
- Add `MessageModifiers` dataclass to `models.py`
- Add `modifiers` field to `TemplateMessage`
- Keep `css_class` field for backward compatibility

**Phase 12b: Populate Modifiers**
- Update all TemplateMessage creation sites to set `modifiers`
- Replace `"x" in css_class` checks with `modifiers.is_x`

**Phase 12c: Create html_renderer.py**
- Move `escape_html()`, `render_markdown()` to html_renderer.py
- Create `css_class_from_message()` function
- Move tool formatters to html_renderer.py

**Phase 12d: Update Templates**
- Modify template to call `css_class_from_message(message)`
- Update emoji logic to use modifiers

**Phase 12e: Remove css_class**
- Remove `css_class` parameter from TemplateMessage
- Clean up any remaining references

## Detailed Implementation

### Phase 12a: Add MessageModifiers (models.py)

```python
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class MessageModifiers:
"""Semantic modifiers that affect message display.

These are format-neutral flags that renderers can use to determine
how to display a message. HTML renderer converts these to CSS classes,
text renderer might use them for indentation or formatting.
"""
is_sidechain: bool = False
is_slash_command: bool = False
is_command_output: bool = False
is_compacted: bool = False
is_error: bool = False
is_steering: bool = False
# System message level (mutually exclusive)
system_level: Optional[str] = None # "info", "warning", "error", "hook"
```

Add to TemplateMessage.__init__:
```python
def __init__(
self,
message_type: str, # Will become MessageType
content_html: str,
formatted_timestamp: str,
css_class: str, # Keep for now, will remove in 12e
modifiers: Optional[MessageModifiers] = None, # New
# ... other params
):
self.type = message_type
self.modifiers = modifiers or MessageModifiers()
# ... rest
```

### Phase 12b: Populate Modifiers

Update each TemplateMessage creation site. Example from `_process_system_message`:

```python
# Before
css_class = f"{message_type}"
if is_sidechain:
css_class = f"{css_class} sidechain"

# After
modifiers = MessageModifiers(is_sidechain=is_sidechain)
css_class = f"{message_type}" # Keep for backward compat
if is_sidechain:
css_class = f"{css_class} sidechain"
```

Update `_get_message_hierarchy_level()`:
```python
# Before
if "sidechain" in css_class:
...

# After
def _get_message_hierarchy_level(msg: TemplateMessage) -> int:
is_sidechain = msg.modifiers.is_sidechain
msg_type = msg.type

if msg_type == MessageType.USER and not is_sidechain:
return 1
# ...
```

### Phase 12c: Create html_renderer.py

```python
"""HTML-specific rendering utilities.

This module contains all HTML generation code:
- CSS class computation
- HTML escaping
- Markdown rendering
- Tool-specific formatters
"""

from html import escape
from typing import Optional, List
import mistune

from .models import MessageType, MessageModifiers, TemplateMessage


def escape_html(text: str) -> str:
"""Escape HTML special characters."""
return escape(text, quote=True)


def render_markdown(text: str) -> str:
"""Convert markdown to HTML."""
return mistune.html(text)


def css_class_from_message(msg: TemplateMessage) -> str:
"""Generate CSS class string from message type and modifiers.

This reconstructs the original css_class format for backward
compatibility with existing CSS and JavaScript.
"""
parts: List[str] = [msg.type.value if isinstance(msg.type, MessageType) else msg.type]

mods = msg.modifiers
if mods.is_slash_command:
parts.append("slash-command")
if mods.is_command_output:
parts.append("command-output")
if mods.is_compacted:
parts.append("compacted")
if mods.is_error:
parts.append("error")
if mods.is_steering:
parts.append("steering")
if mods.is_sidechain:
parts.append("sidechain")
if mods.system_level:
parts.append(f"system-{mods.system_level}")

return " ".join(parts)


def get_message_emoji(msg: TemplateMessage) -> str:
"""Return appropriate emoji for message type."""
msg_type = msg.type if isinstance(msg.type, MessageType) else msg.type

if msg_type == MessageType.SESSION_HEADER:
return "📋"
elif msg_type == MessageType.USER:
return "🤷"
elif msg_type == MessageType.ASSISTANT:
return "🤖"
elif msg_type == MessageType.SYSTEM:
return "⚙️"
elif msg_type == MessageType.TOOL_USE:
return "🛠️"
elif msg_type == MessageType.TOOL_RESULT:
if msg.modifiers.is_error:
return "🚨"
return "🧰"
elif msg_type == MessageType.THINKING:
return "💭"
elif msg_type == MessageType.IMAGE:
return "🖼️"
return ""


# Move format_* tool functions here:
# - format_ask_user_question_tool_content
# - format_todo_write_tool_content
# - format_bash_tool_content
# etc.
```

### Phase 12d: Update Templates

Update transcript.html to use the new functions. Register them as Jinja filters or pass as context:

```python
# In renderer.py when rendering template
from .html_renderer import css_class_from_message, get_message_emoji

template = env.get_template("transcript.html")
html = template.render(
messages=messages,
css_class_from_message=css_class_from_message,
get_message_emoji=get_message_emoji,
# ...
)
```

Template changes:
```jinja
{# Before #}
<div class='message {{ message.css_class }}{% if message.is_paired %} {{ message.pair_role }}{% endif %}'>

{# After #}
<div class='message {{ css_class_from_message(message) }}{% if message.is_paired %} {{ message.pair_role }}{% endif %}'>
```

### Phase 12e: Remove css_class

Once all references use modifiers:
1. Remove `css_class` parameter from `TemplateMessage.__init__`
2. Remove `self.css_class = css_class`
3. Clean up all `css_class=...` at creation sites
4. Update tests to use modifiers

## Files Changed

| File | Changes |
|------|---------|
| `models.py` | Add `MessageModifiers` dataclass |
| `renderer.py` | Update TemplateMessage, populate modifiers, update hierarchy logic |
| `html_renderer.py` | New file with HTML utilities and css_class_from_message |
| `templates/transcript.html` | Use css_class_from_message filter |
| `test_*.py` | Update tests to use modifiers |

## Testing Strategy

1. **Snapshot tests**: Run after each phase to verify HTML output unchanged
2. **Unit tests for css_class_from_message**: Verify it produces same strings
3. **Unit tests for modifiers**: Test each modifier flag
4. **Integration tests**: Full render with real transcripts

## Commit Plan

1. `Add MessageModifiers dataclass to models.py` (12a)
2. `Add modifiers field to TemplateMessage` (12a)
3. `Populate modifiers in message processing` (12b part 1)
4. `Update hierarchy logic to use modifiers` (12b part 2)
5. `Create html_renderer.py with css_class_from_message` (12c)
6. `Move escape_html and render_markdown to html_renderer` (12c)
7. `Update template to use css_class_from_message` (12d)
8. `Remove css_class field from TemplateMessage` (12e)

## Risk Assessment

- **Low risk**: MessageModifiers is additive, doesn't break existing code
- **Medium risk**: Moving functions to html_renderer.py requires import updates
- **High risk**: Template changes and css_class removal need careful testing

## Estimated Scope

- Phase 12a: ~30 lines added to models.py, ~10 lines to renderer.py
- Phase 12b: ~50 modifications across renderer.py
- Phase 12c: ~200 lines new file, ~200 lines moved from renderer.py
- Phase 12d: ~10 lines template changes
- Phase 12e: ~20 lines removed

Total: Moderate refactoring, ~5-8 commits
4 changes: 2 additions & 2 deletions claude_code_log/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def load_cached_entries(self, jsonl_path: Path) -> Optional[List[TranscriptEntry
entries_data.extend(cast(List[Dict[str, Any]], timestamp_entries))

# Deserialize back to TranscriptEntry objects
from .models import parse_transcript_entry
from .parser import parse_transcript_entry

entries = [
parse_transcript_entry(entry_dict) for entry_dict in entries_data
Expand Down Expand Up @@ -257,7 +257,7 @@ def load_cached_entries_filtered(
)

# Deserialize filtered entries
from .models import parse_transcript_entry
from .parser import parse_transcript_entry

entries = [
parse_transcript_entry(entry_dict)
Expand Down
Loading
Loading