Render dynamic-workflow run tree on the nested DOM (#174 PR3)#210
Render dynamic-workflow run tree on the nested DOM (#174 PR3)#210cboos wants to merge 15 commits into
Conversation
Wire PR1's load_workflow_runs into load_directory_transcripts: discover + parse any subagents/workflows/<runId>/ runs and stash them on SessionTree.workflow_runs (new field, keyed by runId; empty for single-file/non-workflow loads). This is the foundation for the renderer to link each run to its Workflow tool_use and splice the phase→agent→side-channel tree (steps 2-3). Also adds work/dynamic-workflow-pr3-design.md recording the locked splice approach (Strategy B: self-contained sub-tree spliced post-_build_message_tree). No rendering yet; behavior-preserving for non-workflow loads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…3, step 2)
Link each parsed WorkflowRun to its Workflow tool_use by taskId: the tool_result
content carries "Task ID: <id>" (the runId lives only in the dropped
toolUseResult), matched to WorkflowRun.task_id; the run is stashed on the
tool_use's WorkflowToolInput.workflow_run (new renderer-set field) by a new
_link_workflow_runs pass in generate_template_messages.
Make the meta header snapshot-first (cboos's refinement): resolve_workflow_header
prefers <runId>.json (run.workflow_name + phase titles) over the JS-meta regex,
WARNS when the JS parse misses a field the snapshot supplies (format-drift
signal), and falls back to the regex for a running/no-snapshot workflow.
Description has no snapshot source, so it stays JS-only. Both the HTML and
Markdown formatters use the resolver.
Fixture: the Workflow tool_result content now matches real data ("Workflow
launched ... Task ID: <id>") so the taskId linkage is exercised.
No tree splice yet (step 3). Tests: snapshot-first / fallback / drift-warning,
plus the directory-load run→tool_use linkage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record the located wiring points for the workflow tree splice so it can be resumed without re-exploration: node types (WorkflowPhaseMessage/ WorkflowAgentMessage) → CSS_CLASS_REGISTRY → format_/title_ dispatch → splice pass with message_index re-indexing → timeline parity, plus the three watch-points (timeline / fold-state / non-workflow byte-identical) and the exact next action. No code yet — steps 1-2 (load+link+snapshot-first header) are committed and green; step 3 (the splice) is the remaining work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onic (#174) cboos review: a session can contain several (even concurrent) Workflow tool_uses, so the synthetic-node message_index counter must be a SINGLE monotonic allocator that persists and advances across ALL workflow splices — not max(original messages)+1 recomputed per workflow, which would collide run #2's grafted nodes with run #1's. Spelled out in step D.1 + the open-risk note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-checked the step-3 wiring against current main (post #204/#205/#206/#208) before writing splice code. Key deltas: index allocation via ctx.register (inherently session-wide monotonic); has_children/is_paired are now read-only properties; should_render is recomputed by the render walk; subtree counts need a bottom-up helper (not a _mark/_build re-run); both renderers walk .children. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… 3 A-C)
Scaffolding for the workflow-run tree splice (no splice yet):
- models: WorkflowPhaseMessage / WorkflowAgentMessage MessageContent subclasses
(synthetic nodes, message_type workflow_phase / workflow_agent).
- html/utils: CSS_CLASS_REGISTRY entries ([tool_use, workflow_phase/agent]) +
get_message_emoji (phase 🧩, agent 🤖).
- renderer base: title_WorkflowPhaseMessage / title_WorkflowAgentMessage.
- html + markdown: format_Workflow{Phase,Agent}Message — phase card (detail +
agent count); agent card (model/state/tokens/tool-calls meta + result:
StructuredOutput dict JSON-highlighted, plain string as markdown).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…3, step 3 D-F) _splice_workflow_runs (last pass in generate_template_messages): for each Workflow tool_use with a linked WorkflowRun, build phase -> agent -> agent side-channel transcript as a self-contained sub-tree and attach via .children (Strategy B; render walks recurse children, so no ancestry rebuild needed). - Index allocation via ctx.register (inherently session-wide monotonic) keeps synthetic + grafted indices collision-free across multiple workflows. - Agent side-channel: re-render entries through the pipeline, re-register into the main ctx, remap pair_first/middle/last to the new index space (markdown pairing resolves via main ctx). - Counts incremented on host + propagated up ancestors (correct even if host had prior children). - timeline.html: dedicated workflow_phase / workflow_agent lanes + detection branches before the generic tool_use .find. - Tests: phase/agent nesting, side-channel grafting, index uniqueness, HTML + Markdown rendering, non-workflow no-splice guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Timeline JS gained dedicated workflow_phase / workflow_agent lanes + detection branches; that JS is embedded on every page, so all 7 affected snapshots pick up the same additions. Message-tree rendering is byte-identical (no content churn). Regenerated serially (-n0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- _format_type_counts: workflow_phase/agent -> 'phase(s)'/'agent(s)' so fold labels read '2 phases', '3 agents' (not 'workflow_phases'). - message_styles.css: phase/agent card chrome (meta line, detail, count, model/state/tokens, result). Embedded per-page, so the 7 HTML snapshots pick up the CSS additions only (no content churn); regenerated serially. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Watch-point 2: the synthetic workflow_phase node participates in the shared nested-DOM fold machine — clicking its fold control toggles its .children container (and restores on a second click). Asserts on the container's own hidden state (not offsetParent) so it's independent of ancestor fold defaults. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…174 PR3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two additions so workflow runs render beyond the directory-load path:
1. Single-file support: claude-code-log <SID>.jsonl now discovers the sibling
<SID>/subagents/workflows/ runs (load_session_workflow_runs) and splices the
tree like a directory load. convert_jsonl_to's single-file branch builds a
SessionTree with workflow_runs + links only when runs exist (no-workflow
single-file output stays byte-identical).
2. Pagination-boundary fix: resolve {tool_use_id: run} once at full-session
scope (map_workflow_runs_by_tool_use, over raw entries) BEFORE the renderer
paginates, stored on SessionTree.workflow_links. _link_workflow_runs prefers
this map, so a Workflow tool_use links to its run even when its tool_result
lands on a different page. Per-render tool_result scan remains the fallback
for direct generate_template_messages calls.
Verified on real data: single-file render of the trunk session shows 3 phases +
42 agent cards. Tests: single-file render; load_session_workflow_runs; cross-page
linkage via the precomputed map (and absence without it).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… PR3) Monk PR3 review N2: one-line clarifying comment at the _graft_agent_sidechannel recursion site — the agent transcript renders at FULL even at --detail high (the splice only fires at FULL/HIGH anyway). No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR implements dynamic workflow rendering for transcripts by detecting and loading WorkflowRun data from the filesystem, linking runs to Workflow tool_use inputs via task IDs (or session mapping), and splicing phase/agent subtrees with side-channel entries into the rendered message tree using snapshot-first metadata resolution. ChangesDynamic Workflow Rendering
Sequence DiagramsequenceDiagram
participant Converter
participant WorkflowModule
participant SessionTree
participant Renderer
participant TemplateMessage
Converter->>WorkflowModule: load_session_workflow_runs(transcript_path)
WorkflowModule->>WorkflowModule: discover and parse WorkflowRun files
WorkflowModule-->>Converter: list of WorkflowRun
Converter->>WorkflowModule: map_workflow_runs_by_tool_use(entries, runs)
WorkflowModule-->>Converter: {tool_use_id: WorkflowRun}
Converter->>SessionTree: attach workflow_runs and workflow_links
Renderer->>SessionTree: read workflow_runs and workflow_links
Renderer->>Renderer: _link_workflow_runs(assign runs to WorkflowToolInput)
Renderer->>Renderer: _splice_workflow_runs(register synthetic nodes)
Renderer->>TemplateMessage: register phase/agent TemplateMessages
Renderer->>TemplateMessage: graft agent side-channel entries + reindex
Renderer->>TemplateMessage: update child/descendant counts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
claude_code_log/html/templates/components/timeline.html (1)
322-323:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd workflow lanes to timeline group ordering
groupOrderdoesn’t include the newworkflow_phaseandworkflow_agentgroups, so they resolve to-1inindexOfand can appear in inconsistent positions.Suggested fix
- const order = ['user', 'teammate', 'task-notification', 'system', 'slash-command', 'command-output', 'bash-input', 'bash-output', 'thinking', 'assistant', 'sidechain', 'memory', 'tool_use', 'tool_result', 'image']; + const order = ['user', 'teammate', 'task-notification', 'system', 'slash-command', 'command-output', 'bash-input', 'bash-output', 'thinking', 'assistant', 'sidechain', 'memory', 'tool_use', 'workflow_phase', 'workflow_agent', 'tool_result', 'image'];As per coding guidelines, “When adding new message types … ensure the timeline's message type detection logic in templates/components/timeline.html is updated accordingly”; the ordering list should include the new workflow lanes too.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@claude_code_log/html/templates/components/timeline.html` around lines 322 - 323, The timeline group ordering array (the const order in templates/components/timeline.html used by the comparator that returns order.indexOf(a.id) - order.indexOf(b.id)) is missing the new workflow lanes, causing them to resolve to -1 and be ordered inconsistently; update the order array to include 'workflow_phase' and 'workflow_agent' in the desired positions (e.g., near other workflow/system items) so the comparator correctly ranks those group ids when sorting timeline groups.Sources: Coding guidelines, Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@claude_code_log/html/tool_formatters.py`:
- Around line 1280-1286: The current branch sends list-shaped results through
render_async_result_body(json.dumps(result,...)) but that helper only triggers
JSON-highlighting for object-shaped text, so lists fall back to markdown; fix by
detecting list results and wrapping them into an object before dumping (e.g.,
replace json.dumps(result, ...) with json.dumps({"list": result},
ensure_ascii=False) or similar) so render_async_result_body receives a
"{"-prefixed JSON string; update the code path using render_async_result_body
and json.dumps to wrap lists (keep dicts unchanged) and preserve any necessary
metadata if needed.
In `@work/dynamic-workflow-pr3-design.md`:
- Around line 212-214: The documentation contradicts the intended pipeline
order: update the description and placement of the splice pass in renderer.py so
that _splice_workflow_runs is described and invoked as the final pass (i.e.,
after _link_task_id_consumers), not before _link_async_notifications; change the
text that currently places it "AFTER _build_message_tree, BEFORE
_link_async_notifications" to state it runs after _link_task_id_consumers and
ensure the callsite ordering in the docs matches that final sequence.
- Around line 186-269: Replace the stale checklist-style "Step 3 implementation
map" with a past-tense summary of what was implemented and any follow-up notes:
state that WorkflowPhaseMessage and WorkflowAgentMessage dataclasses were added,
CSS_CLASS_REGISTRY entries were added, format_WorkflowPhaseMessage /
title_WorkflowPhaseMessage and format_WorkflowAgentMessage /
title_WorkflowAgentMessage were implemented in HtmlRenderer/MarkdownRenderer,
components/message_styles.css was updated, _splice_workflow_runs and
generate_template_messages were added for grafting/re-indexing, fold-state
fields and child-count helpers were set, messageTypeGroups and timeline
detection in components/timeline.html were extended, and tests
(test_workflow_rendering.py and the Playwright fold test) were added/updated;
remove the "EXACT NEXT ACTION" checklist or convert it into a short "Follow-ups
/ verification" list mentioning where to look (e.g., _splice_workflow_runs,
generate_template_messages, CSS_CLASS_REGISTRY, format_* methods,
messageTypeGroups) for any further tweaks.
---
Outside diff comments:
In `@claude_code_log/html/templates/components/timeline.html`:
- Around line 322-323: The timeline group ordering array (the const order in
templates/components/timeline.html used by the comparator that returns
order.indexOf(a.id) - order.indexOf(b.id)) is missing the new workflow lanes,
causing them to resolve to -1 and be ordered inconsistently; update the order
array to include 'workflow_phase' and 'workflow_agent' in the desired positions
(e.g., near other workflow/system items) so the comparator correctly ranks those
group ids when sorting timeline groups.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 550903ba-21a8-4522-a5cf-a4484c43d873
📒 Files selected for processing (17)
claude_code_log/converter.pyclaude_code_log/dag.pyclaude_code_log/html/renderer.pyclaude_code_log/html/templates/components/message_styles.cssclaude_code_log/html/templates/components/timeline.htmlclaude_code_log/html/tool_formatters.pyclaude_code_log/html/utils.pyclaude_code_log/markdown/renderer.pyclaude_code_log/models.pyclaude_code_log/renderer.pyclaude_code_log/workflow.pyscripts/gen_workflow_fixture.pytest/__snapshots__/test_snapshot_html.ambrtest/test_data/workflow_basic/11110000-0000-4000-8000-000000000001.jsonltest/test_workflow_browser.pytest/test_workflow_rendering.pywork/dynamic-workflow-pr3-design.md
CR #1 (code): format_workflow_agent_content routed dict AND list results through render_async_result_body, whose JSON heuristic only fires on '{"' — so a list-shaped (`[...]`) StructuredOutput result skipped the JSON-highlight path and fell to markdown, diverging from the Markdown renderer (which fences both dict and list as JSON). Now JSON-highlights dict/list directly via render_file_content_collapsible. Test added (HTML highlight + Markdown fence). CR #2/#3 (docs): design doc said the splice runs 'BEFORE _link_async_notifications' (contradicting the LAST-pass placement it also states + the implementation) — corrected to 'LAST, after _link_task_id_consumers'. Replaced the stale 'EXACT NEXT ACTION' forward-plan with a DONE/as-built status note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final PR for #174 (dynamic-workflow support). Builds on PR1 (parse) and PR2
(Workflow tool-input rendering): splices each parsed
WorkflowRun— phases →agents → each agent's side-channel transcript — into the message tree at its
Workflowtool_use site, rendered on the existing nested DOM.What this adds
_splice_workflow_runs(the last pass ingenerate_template_messages) attaches a self-contained sub-tree under eachWorkflow tool_use: a
WorkflowPhaseMessageper phase, aWorkflowAgentMessageper agent (model / state / tokens / tool-calls + result — StructuredOutput
dicts pretty-printed as JSON, plain strings as Markdown), and each agent's
side-channel transcript grafted beneath it. Renders in both HTML and Markdown.
ctx.register(len(messages)),an inherently session-wide monotonic allocator, so multiple/concurrent
workflows in one session never collide. Grafted side-channel nodes are
re-registered into the main context and their pairing references remapped.
workflow_phase/workflow_agentlanes plusdetection branches placed before the generic tool_use match (the cards carry
tool_usefor transcript-filter visibility).claude-code-log <session>.jsonlnow discovers thesession's sibling
subagents/workflows/runs and splices them, matching thedirectory-load behavior.
full-session scope (before pagination), so a Workflow tool_use links to its
run even when its tool_result lands on a different page.
Verification
phases + 42 agent cards via the directory, single-file, and
cache+pagination paths.
uniqueness, HTML + Markdown rendering, single-file rendering, the
pagination-boundary case, and a Playwright fold test for the phase nodes.
workflow CSS rules + timeline detection JS); full unit suite, browser tests,
and pyright all green.
Closes #174.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation