Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 24 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,30 @@ The interactive timeline is implemented in JavaScript within `claude_code_log/te

## Architecture

For detailed architecture documentation, see:
Start with [dev-docs/application_model.md](dev-docs/application_model.md)
— the entry point covering subsystems, data lifecycle, and a glossary,
with pointers to the deep-dive docs:

- [dev-docs/rendering-architecture.md](dev-docs/rendering-architecture.md) - Data flow and rendering pipeline
- [dev-docs/messages.md](dev-docs/messages.md) - Message type reference
- [dev-docs/css-classes.md](dev-docs/css-classes.md) - CSS class combinations
- [dev-docs/dag.md](dev-docs/dag.md) - DAG-based session/fork architecture
- [dev-docs/agents.md](dev-docs/agents.md) - Sync/async/teammate agent integration
- [dev-docs/teammates.md](dev-docs/teammates.md) - Teammates feature deep-dive
- [dev-docs/message-hierarchy.md](dev-docs/message-hierarchy.md) - Fold/unfold state machine
- [dev-docs/implementing-a-tool-renderer.md](dev-docs/implementing-a-tool-renderer.md) - How-to: add a new tool

User-facing docs live in [docs/](docs/); plans and TODOs live in [work/](work/).

### Keeping dev-docs/ in sync

`dev-docs/` is **as-built reference** — the code is the authoritative
source. When a non-trivial change alters behavior, structure, or
invariants documented in a deep-dive, update the relevant page in
the same commit (or as a prompt follow-up). If `dev-docs/` and the
code disagree, the doc is wrong.

Typical lifecycle: a feature begins as a spec in `work/`, evolves
into a WIP scratchpad as the code adapts to reality, then graduates
into `dev-docs/` (new page or merged into an existing one) once the
implementation has stabilized.
9 changes: 7 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ claude_code_log/

scripts/ # Development utilities
test/test_data/ # Representative JSONL samples
dev-docs/ # Architecture documentation
dev-docs/ # Architecture / dev documentation (start in application_model.md)
docs/ # User-facing operations docs
work/ # Plans, TODOs, in-flight design docs
```

## Development Setup
Expand Down Expand Up @@ -199,7 +201,10 @@ The handler is installed in `cli.py` via `faulthandler.register(SIGUSR1)`. POSIX

## Architecture

For detailed architecture documentation, see [dev-docs/rendering-architecture.md](dev-docs/rendering-architecture.md).
Start with [dev-docs/application_model.md](dev-docs/application_model.md)
for the system overview (subsystems, data lifecycle, glossary). For
the rendering pipeline specifically, see
[dev-docs/rendering-architecture.md](dev-docs/rendering-architecture.md).

### Data Flow Overview

Expand Down
2 changes: 1 addition & 1 deletion claude_code_log/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2278,7 +2278,7 @@ def _print_archived_sessions_note(total_archived: int) -> None:
f"\nNote: {total_archived} archived session(s) found{cleanup_info}.\n"
" These sessions were cached before their JSONL files were deleted.\n"
" To restore them or adjust cleanup settings, see:\n"
" https://github.com/daaain/claude-code-log/blob/main/dev-docs/restoring-archived-sessions.md"
" https://github.com/daaain/claude-code-log/blob/main/docs/restoring-archived-sessions.md"
)


Expand Down
2 changes: 2 additions & 0 deletions dev-docs/agents.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Agents

> See [application_model.md](application_model.md) for the system overview.

`claude-code-log` renders three flavors of Task-spawned agents:

| Flavor | Trigger | Reference |
Expand Down
448 changes: 448 additions & 0 deletions dev-docs/application_model.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions dev-docs/css-classes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# CSS Classes for Message Types

> See [application_model.md](application_model.md) for the system overview.

This document provides a comprehensive reference for CSS class combinations used in Claude Code Log HTML output, their CSS rule support status, and pairing behavior.

**Generated from analysis of:** 29 session HTML files (3,244 message elements)
Expand Down
52 changes: 48 additions & 4 deletions dev-docs/dag.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# DAG-Based Message Architecture

> See [application_model.md](application_model.md) for the system overview.

Replaces timestamp-based ordering with `parentUuid` → `uuid` graph traversal.

Reference: [Messages as Commits: Claude Code's Git-Like DAG of Conversations](https://piebald.ai/blog/messages-as-commits-claude-codes-git-like-dag-of-conversations)
Expand Down Expand Up @@ -96,6 +98,14 @@ Where `s1`, `s2`, `s3` are synthesized session header messages.
- **Backlinks** on session headers: "Continues from message X in Session Y"
(shown on `s2` and `s3`)

> Where branch / session header *titles* (the `Branch • <uuid8> •
> <preview>` text) are assembled is a renderer concern, not a DAG
> concern. See the `SessionHeaderMessage` glossary entry in
> [application_model.md](application_model.md#4-cross-cutting-glossary)
> for the four functions involved (`_branch_label`,
> `_enrich_branch_titles`, `create_session_preview`,
> `simplify_command_tags`).

#### Current: `d-{index}` anchors (combined transcript only)

Backlinks use `#msg-d-{N}` anchors which are sequential indices assigned
Expand Down Expand Up @@ -178,8 +188,28 @@ is available.

1. Parse all entries, index by `uuid`
2. For duplicate `uuid`s, keep the one from the earliest `sessionId`
3. Build `children_by_uuid` from `parentUuid` links
4. Group messages by `sessionId`
3. Group messages by `sessionId`
4. `build_dag(nodes, sidechain_uuids)` populates `children_uuids` —
in three steps that **must run in this order** (PR #135):

```mermaid
flowchart TB
A["entries indexed by uuid<br/>(parent_uuid pointers may<br/>dangle or cycle)"] --> S1
S1["Step 1 — orphan promotion<br/>parent_uuid not in nodes →<br/>null it; warn unless the<br/>parent is a known sidechain<br/>uuid (silently promote)"] --> S2
S2["Step 2 — cycle break<br/>walk parent_uuid from each<br/>node; revisit ⇒ null the<br/>revisited node's parent;<br/>warn"] --> S3
S3["Step 3 — children build<br/>for each node with non-null<br/>parent_uuid, append to<br/>parent.children_uuids;<br/>skip self-loops, dedup"] --> O["acyclic parent→children DAG<br/>safe to walk"]
classDef step fill:#eef,stroke:#99c
class S1,S2,S3 step
```

Steps 1 and 2 mutate `parent_uuid` on the input nodes (they're
one-way: a promoted-to-root node can't recover its dangling
parent later). Step 3 is the only step that builds the
`children_uuids` lists. Doing children first would propagate
any cyclic edge into the children graph, and downstream walks
via `children_uuids` would loop forever — so cycles must be
broken at the parent-pointer layer before children are
materialised.

### Phase 3: Extract Session DAG-lines

Expand All @@ -198,6 +228,16 @@ For each session (`extract_session_dag_lines` in `dag.py`):
5. If DAG walk coverage is incomplete, fall back to a timestamp sort for
the whole session.

**Defence-in-depth in the walker** (PR #135): even though `build_dag`
breaks parent-pointer cycles before populating `children_uuids`, a
future bug or hand-edited fixture could reintroduce a cyclic edge
*after* DAG construction. `_walk_session_with_forks` keeps a
`walk_visited: set[str]` across the whole queue-driven walk; if a
uuid is visited twice, the chain is truncated at that point and a
warning is logged. The build-time cycle break and this walk-time
guard together rule out the unbounded-loop class of hangs that
motivated the PR.

### Phase 4: Build Session Tree

1. For each session, find where its DAG-line attaches to the DAG:
Expand Down Expand Up @@ -575,7 +615,11 @@ These should be checked at runtime (log warnings, don't crash):
produce multiple roots within one `sessionId`; all are walked and the
trunks are merged. Other multi-root causes warn (may indicate missing
parent data).
3. **DAG acyclicity**: No cycles in `parentUuid` chains
3. **DAG acyclicity**: `build_dag` walks each node's `parent_uuid`
chain and nulls the first revisited node's parent if a cycle is
detected (warns and promotes that node to root). The DAG seen by
downstream walks is always acyclic; `_walk_session_with_forks`
adds a `walk_visited` belt for defence-in-depth.
4. **Unique ownership**: After deduplication, each `uuid` belongs to
exactly one session
5. **Agent parenting**: Every top-level agent transcript has an identifiable
Expand Down Expand Up @@ -655,4 +699,4 @@ validate DAG construction against known transcripts.

- [rendering-architecture.md](rendering-architecture.md) — Current pipeline
- [messages.md](messages.md) — Message type reference
- [rendering-next.md](rendering-next.md) — Future rendering improvements
- [../work/rendering-next.md](../work/rendering-next.md) — Future rendering improvements
17 changes: 17 additions & 0 deletions dev-docs/implementing-a-tool-renderer.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Implementing a Tool Renderer

> See [application_model.md](application_model.md) for the system overview.

This guide walks through adding rendering support for a new Claude Code tool, using WebSearch as an example.

## Overview
Expand All @@ -11,6 +13,14 @@ Tool rendering involves several components working together:
3. **HTML Formatters** (`html/tool_formatters.py`) - HTML rendering functions
4. **Renderers** - Integration with HTML and Markdown renderers

JSON output (`json/renderer.py`, since PR #36) needs **no per-tool
integration**: it serialises whatever typed input/output models the
factory produced via `dataclasses.asdict` (with a `_json_default`
shim for Pydantic models embedded inside the dataclasses). Add the
models in Step 1 and the factory hooks in Steps 2–3, and your tool
shows up in JSON exports automatically. The HTML/Markdown formatter
work in Steps 4–5 stays format-specific.

## Step 1: Define Models

### Tool Input Model
Expand Down Expand Up @@ -253,6 +263,13 @@ Create test cases in the appropriate test files:
2. **Formatter tests** - Verify HTML/Markdown output is correct
3. **Integration tests** - Verify end-to-end rendering

JSON output is exercised by the broader `test/test_json_rendering.py`
/ `test/test_json_real_projects.py` suites; per-tool JSON output
typically needs no dedicated test because the `dataclasses.asdict`
serialisation is trivial. Add a JSON-specific case only if your tool
embeds a non-dataclass type the `_json_default` shim doesn't already
cover.

## Checklist

- [ ] Add input model to `models.py`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Fold Bar State Diagram
# Message Hierarchy and Fold State

> See [application_model.md](application_model.md) for the system overview.

## Message Hierarchy

Expand Down
4 changes: 3 additions & 1 deletion dev-docs/messages.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Message Types in Claude Code Transcripts

> See [application_model.md](application_model.md) for the system overview.

This document describes all message types found in Claude Code JSONL transcript files and their corresponding output representations. The goal is to define an **intermediate representation** that captures the logical message structure independent of HTML rendering.

## Overview
Expand Down Expand Up @@ -930,4 +932,4 @@ Sub-agent messages (from `Task` tool):
- [system_factory.py](../claude_code_log/factories/system_factory.py) - `create_system_message()`
- [meta_factory.py](../claude_code_log/factories/meta_factory.py) - `create_meta()`
- [rendering-architecture.md](rendering-architecture.md) - Rendering pipeline and Renderer class hierarchy
- [rendering-next.md](rendering-next.md) - Future rendering improvements
- [../work/rendering-next.md](../work/rendering-next.md) - Future rendering improvements
51 changes: 43 additions & 8 deletions dev-docs/rendering-architecture.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Rendering Architecture

This document describes how Claude Code transcript data flows from raw JSONL entries to final output (HTML, Markdown). The architecture separates concerns into distinct layers:
> See [application_model.md](application_model.md) for the system overview.

This document describes how Claude Code transcript data flows from raw JSONL entries to final output (HTML, Markdown, JSON). The architecture separates concerns into distinct layers:

1. **Parsing Layer** - Raw JSONL to typed transcript entries
2. **Factory Layer** - Transcript entries to `MessageContent` models
3. **Rendering Layer** - Format-neutral tree building and relationship processing
4. **Output Layer** - Format-specific rendering (HTML, Markdown)
4. **Output Layer** - Format-specific rendering (HTML, Markdown, JSON)

---

Expand All @@ -16,15 +18,27 @@ JSONL File
↓ (parser.py)
list[TranscriptEntry]
↓ (factories/)
list[TemplateMessage] with MessageContent
list[TemplateMessage] with MessageContent ← factory-layer
normalisation seam
(raw → display-polished)
↓ (renderer.py: generate_template_messages)
Tree of TemplateMessage (roots with children)
+ RenderingContext (message registry)
+ Session navigation data
↓ (html/renderer.py or markdown/renderer.py)
Final output (HTML or Markdown)
↓ (html/renderer.py | markdown/renderer.py | json/renderer.py)
Final output (HTML, Markdown, or JSON)
```

**The factory-layer seam matters**: any cleanup that should appear
in *every* output format (slash-command normalisation, command-args
hardening, teammate session-color enrichment, etc.) lives at factory
time, in the typed `MessageContent` models. The three renderers are
pure consumers of the polished tree — they never re-implement
display polish per format. As a corollary, when a new output format
is added (JSON shipped this way in PR #36), it inherits all polish
for free as long as it consumes `generate_template_messages`'
output.

**Key cardinality rules**:
- Each transcript entry has a `uuid`, but a single entry's `list[ContentItem]` may be chunked and produce multiple `MessageContent` objects (e.g., tool_use items are split into separate messages)
- Each `MessageContent` gets exactly one `TemplateMessage` wrapper
Expand Down Expand Up @@ -278,6 +292,20 @@ def title_ToolUseMessage(self, content: ToolUseMessage, message: TemplateMessage
- Writes directly to file/string without templates
- Simpler structure suited to plain text output

**JsonRenderer** ([json/renderer.py](../claude_code_log/json/renderer.py)):
- Doesn't implement `format_*` per content type — instead serialises
the entire `TemplateMessage` subtree via `dataclasses.asdict` plus
a small `_json_default` shim for the Pydantic models embedded in
tool inputs/outputs (and for `Enum`/`Path`).
- Calls `title_content(msg)` to attach a per-node title that mirrors
what HTML/Markdown surface — the only place dispatcher methods are
reused.
- Output is a single JSON document per session (or per combined
transcript / projects index) with the message tree nested directly
under each node's `children` array. See [application_model.md
§ 2.5](application_model.md#25-json-export) for the payload shape
and inheritance from the factory-layer normalisation seam.

---

## 8. HTML Formatter Organization
Expand Down Expand Up @@ -333,15 +361,22 @@ Note that `meta.uuid` is the original transcript entry's UUID. Since a single en
### Separation of Concerns

- **models.py**: Pure data structures, no rendering logic
- **factories/**: Data transformation, no I/O
- **factories/**: Data transformation, no I/O. **The
normalisation seam** — display polish for *all* output formats
lives here, not in renderers (e.g. `simplify_command_tags` lifting
bare `<command-name>X</command-name>` to `/X`, with the same fix
applied to both `simplify_command_tags` and
`create_slash_command_message` so HTML/Markdown/JSON observe a
single shape).
- **renderer.py**: Format-neutral processing (pairing, hierarchy, tree)
- **html/**, **markdown/**: Format-specific output generation
- **html/**, **markdown/**, **json/**: Format-specific output generation,
consuming the polished tree without re-implementing display rules.

---

## Related Documentation

- [messages.md](messages.md) - Complete message type reference
- [css-classes.md](css-classes.md) - CSS class combinations and rules
- [FOLD_STATE_DIAGRAM.md](FOLD_STATE_DIAGRAM.md) - Fold/unfold state machine
- [message-hierarchy.md](message-hierarchy.md) - Fold/unfold state machine
- [dag.md](dag.md) - DAG-based message architecture (replaces timestamp-based ordering)
2 changes: 2 additions & 0 deletions dev-docs/teammates.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Teammates Support

> See [application_model.md](application_model.md) for the system overview.

This document describes how `claude-code-log` supports the Claude Code
teammates feature (research preview, gated by
`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, available in CC 2.1.32+).
Expand Down
Loading
Loading