Skip to content

Render dynamic-workflow run tree on the nested DOM (#174 PR3)#210

Open
cboos wants to merge 15 commits into
mainfrom
dev/wf-tree-render
Open

Render dynamic-workflow run tree on the nested DOM (#174 PR3)#210
cboos wants to merge 15 commits into
mainfrom
dev/wf-tree-render

Conversation

@cboos

@cboos cboos commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

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
Workflow tool_use site, rendered on the existing nested DOM.

What this adds

  • Run-tree splice. _splice_workflow_runs (the last pass in
    generate_template_messages) attaches a self-contained sub-tree under each
    Workflow tool_use: a WorkflowPhaseMessage per phase, a WorkflowAgentMessage
    per 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.
  • Synthetic-node indices are allocated via 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.
  • Timeline parity. Dedicated workflow_phase / workflow_agent lanes plus
    detection branches placed before the generic tool_use match (the cards carry
    tool_use for transcript-filter visibility).
  • Single-file rendering. claude-code-log <session>.jsonl now discovers the
    session's sibling subagents/workflows/ runs and splices them, matching the
    directory-load behavior.
  • Pagination-robust linkage. Run↔tool_use linkage is resolved once at
    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

  • Validated end-to-end on a real ~76k-message project: the run renders its
    phases + 42 agent cards via the directory, single-file, and
    cache+pagination paths.
  • New tests cover the splice structure, side-channel grafting, index
    uniqueness, HTML + Markdown rendering, single-file rendering, the
    pagination-boundary case, and a Playwright fold test for the phase nodes.
  • Non-workflow output is byte-identical (snapshot diff is only the added
    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

    • Dynamic workflow phases and agents are rendered as interactive cards (metadata, model/tokens, results) and are spliced into transcripts.
    • Workflow header resolution prefers snapshot data; single-file transcripts now discover and render linked workflow runs.
  • Bug Fixes

    • Improved cross-page/linkage handling so workflow run links are resolved at full-session scope to avoid pagination breakage.
  • Tests

    • Added comprehensive rendering tests including browser folding behavior.
  • Documentation

    • Updated design docs describing the splice/render strategy and verification.

cboos and others added 14 commits June 7, 2026 17:19
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>
)

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>
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b80bbaac-170d-43ab-b205-d47bb21fcad8

📥 Commits

Reviewing files that changed from the base of the PR and between 4a670d7 and adf9cee.

📒 Files selected for processing (3)
  • claude_code_log/html/tool_formatters.py
  • test/test_workflow_rendering.py
  • work/dynamic-workflow-pr3-design.md
✅ Files skipped from review due to trivial changes (1)
  • work/dynamic-workflow-pr3-design.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • claude_code_log/html/tool_formatters.py
  • test/test_workflow_rendering.py

📝 Walkthrough

Walkthrough

This 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.

Changes

Dynamic Workflow Rendering

Layer / File(s) Summary
Message content types and SessionTree data model
claude_code_log/models.py, claude_code_log/dag.py
WorkflowPhaseMessage and WorkflowAgentMessage dataclasses added; SessionTree gains workflow_runs and workflow_links dicts to carry parsed runs and tool_use→run mappings.
Workflow loading, discovery, and tool_use linkage
claude_code_log/workflow.py, claude_code_log/converter.py
Adds resolve_workflow_header(run, script) (snapshot-first), _runs_in_session_dir, load_session_workflow_runs, _tool_result_text, and map_workflow_runs_by_tool_use(entries, runs) to discover runs and link them to Workflow tool_use ids; converter populates SessionTree.workflow_runs/workflow_links for directory and single-file modes.
HTML formatter implementation for workflow components
claude_code_log/html/tool_formatters.py
format_workflow_phase_content and format_workflow_agent_content render phase and agent cards (metadata, tokens/tool-call counts, result as pretty JSON/Markdown/preview); format_workflow_input now uses resolve_workflow_header.
Markdown formatter implementation for workflow components
claude_code_log/markdown/renderer.py
Adds format_WorkflowPhaseMessage and format_WorkflowAgentMessage and updates format_WorkflowToolInput to use snapshot-first header resolution.
HTML renderer dispatch
claude_code_log/html/renderer.py
HtmlRenderer imports workflow message types and adds format_WorkflowPhaseMessage / format_WorkflowAgentMessage delegating to HTML helpers.
HTML UI wiring: CSS, timeline classification, and utilities
claude_code_log/html/templates/components/message_styles.css, claude_code_log/html/templates/components/timeline.html, claude_code_log/html/utils.py
Adds CSS classes for .workflow-phase / .workflow-agent metadata and previews; extends timeline messageTypeGroups and buildTimelineData() to explicitly detect workflow classes; registers CSS and emoji mappings.
Renderer splice and linkage passes
claude_code_log/renderer.py
Adds _link_workflow_runs to attach WorkflowRun to WorkflowToolInput (via SessionTree mapping or task-id regex fallback), and _splice_workflow_runs which registers synthetic phase/agent TemplateMessages, grafts agent side-channel transcripts by re-running pipeline in sub-contexts with index/pair remapping, and updates child/descendant counts and titles for accurate fold controls.
Tests, snapshots, fixtures, and docs
test/test_workflow_rendering.py, test/test_workflow_browser.py, test/__snapshots__/test_snapshot_html.ambr, test/test_data/workflow_basic/*.jsonl, scripts/gen_workflow_fixture.py, work/dynamic-workflow-pr3-design.md
Updates fixture payloads; adds unit and Playwright browser tests for header resolution, run↔tool_use linkage, splicing behavior, single-file discovery, and pagination boundary cases; updates repeated HTML snapshots and adds design documentation for the splice strategy and watch-points.

Sequence Diagram

sequenceDiagram
  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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • daaain/claude-code-log#203: Introduces WorkflowRun parsing and discovery infrastructure that this PR consumes for session-run loading and linking.
  • daaain/claude-code-log#205: Shares changes to workflow header parsing/formatting (parse_workflow_meta → resolve_workflow_header) and early WorkflowToolInput rendering groundwork.
  • daaain/claude-code-log#97: Related SessionTree/DAG pipeline changes that this PR extends by adding workflow_runs/workflow_links to SessionTree and leveraging them during rendering.

Poem

🐰
I stitched the runs into the log with care,
phases and agents springing out to share,
task IDs linked, snapshots guide the name,
fold bars fold and then unfold again,
a tiny rabbit cheers the render game.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: implementing workflow run tree rendering on the nested DOM, with the issue reference (#174 PR3) providing precise context.
Linked Issues check ✅ Passed The PR fully addresses all coding requirements from issue #174: workflow run discovery/parsing, tool_use linkage, phase/agent/side-channel rendering in HTML/Markdown, single-file support, pagination robustness, and timeline parity.
Out of Scope Changes check ✅ Passed All changes are directly aligned with #174's objectives: workflow run data structures, linkage/splicing logic, renderer methods for workflow phases/agents, CSS/timeline updates, and comprehensive tests. CSS/timeline updates and snapshot changes are necessary for proper workflow rendering.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/wf-tree-render

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Add workflow lanes to timeline group ordering

groupOrder doesn’t include the new workflow_phase and workflow_agent groups, so they resolve to -1 in indexOf and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6788 and 4a670d7.

📒 Files selected for processing (17)
  • claude_code_log/converter.py
  • claude_code_log/dag.py
  • claude_code_log/html/renderer.py
  • claude_code_log/html/templates/components/message_styles.css
  • claude_code_log/html/templates/components/timeline.html
  • claude_code_log/html/tool_formatters.py
  • claude_code_log/html/utils.py
  • claude_code_log/markdown/renderer.py
  • claude_code_log/models.py
  • claude_code_log/renderer.py
  • claude_code_log/workflow.py
  • scripts/gen_workflow_fixture.py
  • test/__snapshots__/test_snapshot_html.ambr
  • test/test_data/workflow_basic/11110000-0000-4000-8000-000000000001.jsonl
  • test/test_workflow_browser.py
  • test/test_workflow_rendering.py
  • work/dynamic-workflow-pr3-design.md

Comment thread claude_code_log/html/tool_formatters.py
Comment thread work/dynamic-workflow-pr3-design.md Outdated
Comment thread work/dynamic-workflow-pr3-design.md Outdated
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for dynamic workflows

1 participant