Skip to content

Side-channel prompt rendering + params-fold UX and accessibility (#174 follow-up)#217

Merged
cboos merged 12 commits into
mainfrom
dev/wf-sidechannel-json
Jun 11, 2026
Merged

Side-channel prompt rendering + params-fold UX and accessibility (#174 follow-up)#217
cboos merged 12 commits into
mainfrom
dev/wf-sidechannel-json

Conversation

@cboos

@cboos cboos commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #215/#216, refining how workflow sub-agent prompts and the
hybrid params tables read — driven by reviewing real rendered sessions.

Side-channel user prompts (workflow sub-agents)

These prompts are large prose+JSON hybrids that rendered as walls of text.
They now render as escaping collapsible Markdown with embedded JSON
blocks extracted into params tables
: a lone {/[ on its own line,
through a lone matching closer followed by a blank line, accepted only when
json.loads parses it. Each block is substituted with a z-prefixed UUID
placeholder (so the SHA→commit-URL linkifier can never match inside it),
the prose renders as Markdown, and the placeholders are swapped for the
generic params-table rendering — which the hybrid renderer (#216) upgrades
automatically. Blocks inside fenced code (backtick or tilde) stay verbatim;
blocks wider than 200 items fall back to an escaped <pre> fold
(generation-side breadth discipline). Scoped strictly to messages grafted
from a workflow agent's side-channel — trunk rendering is untouched.

Params-fold UX + accessibility

  • No interactive elements inside <summary> — Chrome flagged every
    rows-toggle as an accessibility violation (hundreds of identical DevTools
    errors on a large page; such elements aren't consistently reachable by
    keyboard/AT). The buttons move to a controls strip after the summary;
    summaries now hold only the preview text. Verified 415 → 0 offenders on a
    real session; a page-wide test pins the contract.
  • Key-column toggles: fold-valued rows hoist their ▸/▾ into the key
    cell — the whole key is one keyboard-reachable <button>
    (aria-expanded), every key cell reserves the same glyph slot so key
    texts align, and the per-row "▼ collapse" chrome that drowned the content
    is gone. The glyphs are the browser's native disclosure markers
    (display: list-item + list-style-type: disclosure-closed/open) — the
    same symmetric icons <summary> shows, immune to italic contexts.
  • State stays derived-from-DOM throughout: the capturing toggle listener
    syncs key glyphs, rows-toggles, and the expand-all button on any
    open/close path.

Visual tuning + runtime coverage

  • Standard sub-agent sidechains get their missing group line (tool-green,
    continuing the spawning card's border); the workflow phase color softens
    to #3a7d3c.
  • New Playwright coverage: computed-style contract for the :has()-driven
    group borders (snapshots embed the CSS text, so they can't catch DOM
    changes breaking the selectors), phase-pill click → hash navigation +
    auto-unfold, key-toggle cycle with externally-driven state sync, and the
    no-interactive-elements-in-summaries page contract.

Snapshot deltas are CSS/JS-only (regenerated serially); full unit, TUI,
browser suites, pyright, and the strict docs build are green.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Workflow side-channel user prompts render embedded JSON as formatted parameter tables.
    • Key-column toggle controls added for expanding/collapsing individual parameter rows.
  • UI/Visual Changes

    • Workflow phase card color updated.
    • Tool result sidechain borders use tool-green styling.
    • Parameter table fold controls redesigned for clearer layout and better accessibility.
  • Tests

    • Added browser and unit tests covering side-channel rendering, fold controls, accessibility, and visual CSS assertions.
  • Documentation

    • Updated workflow and CSS-class docs to reflect side-channel rendering and visual rules.

cboos and others added 11 commits June 11, 2026 20:06
…ction (#174 follow-up)

Workflow sub-agent prompts are large prose+JSON hybrids that rendered as a
wall of text. Two complementary changes, applied only to user messages
grafted from a workflow agent's side-channel (tagged by the graft pass):

- The prompt renders through the escaping Markdown collapsible (these
  prompts run long), via format_workflow_sidechannel_user_content.
- Embedded pretty-printed JSON blocks are extracted first: a lone { or [
  on its own line, through a lone matching closer followed by a blank
  line (or EOF), accepted only when json.loads parses it. Each block is
  substituted with a z-prefixed UUID placeholder (every uuid group gets a
  z so the SHA->commit-URL linkifier can never match inside it), the
  remaining text renders as Markdown, and each placeholder is then swapped
  for the generic params-table rendering of the parsed value (arrays as
  index->value rows) - which the hybrid JSON/Markdown renderer upgrades
  automatically once it lands. A placeholder landing in a fold's preview
  becomes a compact {  } hint there; the table renders once, in the body.

Fixture: the first workflow_basic agent prompt now embeds a JSON block to
exercise the path end-to-end. On a real 42-agent run, 34 embedded blocks
across 40 side-channel prompts extract into tables. Trunk/non-workflow
user rendering is untouched (gated on the graft tag); snapshot diff is
CSS-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirrors the params-table breadth cap from the hybrid-renderer review
(CodeRabbit, PR #216): a folded table still GENERATES one row per element,
so an extracted block wider than 200 top-level items renders as an escaped
JSON <pre> in a fold instead of tabulating — proportional to the source
text, and deliberately not Pygments-highlighted (itself generation-heavy
at that size). Boundary pinned on both sides (200 tabulates, 201 falls
back). The top-level render_params_table call is otherwise uncapped for
arbitrary payloads — tool params are naturally bounded, extracted JSON
is not.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vigation

Lands the test refinement suggested in review of the polish PR (deferred
here so it pins the FINAL rendered behavior — group borders plus the
hybrid params tables together):

- Computed-style contract for the :has()-driven group borders: snapshots
  embed the CSS text, so a DOM-structure change that silently breaks the
  :has() selectors would not fail them. Asserts the suppressed
  Workflow-level line (0px), the dark-green agents-group and grey
  side-channel lines (2px, exact colors), and the border alignment with
  each parent card's left edge.
- Phase-pill navigation: clicking a pill updates the hash and the
  hashchange handler unfolds the folded target phase card. The pill is
  first revealed via the page's own anchor-unfold (as a user arriving
  from session nav would); assertions poll via wait_for_function since
  hashchange/toggle run asynchronously.

Also widens a fixture assertion window: the hybrid renderer now wraps
params tables in a tool-params-root with an expand-all control, pushing
the table tag past the old 200-char probe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iple

The group line continues its PARENT card's border color — and a standard
sub-agent's sidechain hangs under the spawning tool_result, whose border
is tool-green. The grey it shipped with paired it to the workflow agent
concept instead of to its own parent card; rendering review caught the
mismatch. Workflow agents keep their grey (grey workflow_agent card →
grey side-channel line — the same principle, different parent). Docs
updated; snapshots CSS-only (regenerated serially).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The near-black green (#1b5e20) read too heavy against the pastel theme;
#3a7d3c keeps the phase level clearly darker than tool-green while
sitting better in the palette. One variable drives both the phase card
border and its agents group line; the runtime-CSS contract test pins the
new value. Snapshots CSS-only (regenerated serially).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two intertwined refinements to the hybrid params tables, from rendering
review:

ACCESSIBILITY: Chrome flagged every rows-toggle button as an interactive
element inside <summary> (not consistently reachable by keyboard/AT —
hundreds of identical DevTools errors on a large page). The button moves
to a controls strip AFTER the summary, inside the details (natively
hidden while closed); summaries now hold only the preview text. The
remaining handful of link-in-summary notices come from markdown-rendered
preview content elsewhere — out of scope here.

VISUAL WEIGHT: every open fold showing an in-summary '▼ collapse' drowned
the content. Fold-valued rows now hoist their toggle into the KEY column:
- The whole key (glyph + text) is one <button> — clicking the arrow or
  the key text toggles, and it's keyboard-reachable (aria-expanded).
- Every key cell reserves the same 1.3em glyph slot (empty spacer on
  scalar rows), so key texts align regardless of foldability and long
  keys no longer wrap from the width shift.
- One constant ▸ glyph, CSS-rotated 90° when open: symmetric open/closed
  states by construction (the ▶/▼ pair has mismatched metrics in Chrome).
- A keyed fold's open summary is hidden entirely (the key glyph + the
  controls strip carry the affordances); the top-level root fold has no
  key column and keeps its ::after 'collapse' hint (pure CSS, no
  interactive child).
State stays derived-from-DOM: the capturing toggle listener syncs
aria-expanded on any open/close path (click, rows-toggle, expand-all,
global toggle-all).

Verified on a real 42-agent session: summary-interactive offenders 415→0,
key alignment and whole-key click measured via Playwright. Tests updated
+ extended (controls-strip pin, no-button-in-summary pin, key-toggle
cycle/glyph sync, page-wide a11y contract).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rols

Rendering review follow-ups on the key-column toggles:

- The rotation pivoted around the 1.3em slot's box center while the glyph
  sat at its left edge, so opening visibly swung the arrow sideways.
  Centering the glyph in its slot makes the box-center rotation pivot on
  the glyph itself — it now rotates in place (measured zero center shift).
- The expand-all and rows-toggle buttons still used the mismatched ▶/▼
  text pair. All three control types now share the same structure — a
  constant ▸ in a .tool-param-fold-glyph slot (CSS-rotated on
  data-state/aria-expanded) plus a .tool-param-fold-label — and the sync
  functions update only the label text. One glyph class, one rotation
  rule, symmetric everywhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rotated ▸ font glyph inherited the italic style of the control labels
(a skewed triangle, then rotated — visibly off), and the ▶/▼ pair it had
replaced has mismatched metrics in Chrome. The clean source of symmetric
glyphs was on the page all along: the UA-drawn disclosure marker that
<summary> elements show. Render it on the (now empty) glyph spans via
display:list-item + list-style-type: disclosure-closed/open — same icon
as the summaries, geometrically symmetric, immune to italics. String
list-style-type values provide the fallback where the disclosure-*
keywords are unsupported, and the buttons lay out as inline-flex since
list-item is block-level. The scalar-row spacer span stays markerless.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…w-up)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewer-probed false-accept: a fenced example whose commentary follows a
blank line INSIDE the fence let the scan terminate at the lone closer,
json.loads accepted, and the params-table markup was substituted into the
rendered <pre><code>. The line scan now tracks ``` fence parity and skips
extraction while inside a fence. (A fence with no internal blank line was
already rejected by the closer check — pinned alongside the repro and a
parity-reset case.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
~~~ fences are CommonMark too and mistune renders them as code blocks —
same false-accept shape as the backtick case. One shared parity toggle for
both fence kinds is an approximation (CommonMark closes a fence only with
the same character), but for this skip-heuristic mixing kinds at worst
skips an extraction, never corrupts one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 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: 1774c4e1-3a3c-4aa7-9f5f-01d07992b785

📥 Commits

Reviewing files that changed from the base of the PR and between ecf9007 and 89cc46a.

📒 Files selected for processing (1)
  • dev-docs/workflows.md
✅ Files skipped from review due to trivial changes (1)
  • dev-docs/workflows.md

📝 Walkthrough

Walkthrough

This PR adds complete support for rendering workflow side-channel user content with embedded JSON extraction and substitution into parameter tables; it introduces TemplateMessage.in_workflow_sidechannel, wires HtmlRenderer to a specialized formatter, relocates fold controls outside

with key-column toggles, updates CSS/JS, fixtures, snapshots, docs, and tests.

Changes

Workflow Side-Channel User Rendering

Layer / File(s) Summary
TemplateMessage sidechannel flag and HTML renderer integration
claude_code_log/renderer.py, claude_code_log/html/renderer.py
TemplateMessage gains in_workflow_sidechannel boolean flag, set to True when workflow agent side-channel nodes are grafted into the main rendering context. HtmlRenderer.format_UserTextMessage detects the flag and routes to specialized format_workflow_sidechannel_user_content instead of standard user text rendering.
Workflow sidechannel user content formatter with JSON extraction
claude_code_log/html/user_formatters.py
New functions detect and extract standalone {...} / [...] JSON blocks from prose while respecting fence/code-block boundaries. Extracted blocks are replaced with UUID placeholders, remaining text is rendered via collapsible Markdown, and placeholders are swapped with HTML-rendered parameter tables or escaped <pre> fallback for oversized blocks.
Tool params fold control UI restructuring
claude_code_log/html/tool_formatters.py, claude_code_log/html/templates/transcript.html
Row-toggle controls moved to dedicated .tool-param-fold-controls strip after <summary> (removing interactive elements from summary). Key-column toggles (.tool-param-key-toggle) added as keyboard-accessible buttons to directly toggle fold-valued rows. Client-side JavaScript updated to sync fold glyph state via aria-expanded on key toggles and update label text in .tool-param-fold-label instead of replacing button content.
CSS styling for fold controls and sidechannel elements
claude_code_log/html/templates/components/message_styles.css, claude_code_log/html/templates/components/global_styles.css
New CSS for .tool-param-fold-controls, .tool-param-key-toggle, .tool-param-fold-glyph with alignment and disclosure-marker glyphs. Sidechain group border color changed from --workflow-agent-color to --tool-use-color. Added .embedded-json and .embedded-json-hint styling. Updated --workflow-phase-color from #1b5e20 to #3a7d3c.
Documentation: workflow sidechannel rendering and CSS styling
dev-docs/workflows.md, dev-docs/css-classes.md
Detailed walkthrough of JSON extraction, placeholder substitution, and params-table rendering steps. Documents border color propagation by nesting depth and clarifies sidechain modifier styling with tool-green group lines.
Test fixture and data updates
scripts/gen_workflow_fixture.py, test/test_data/workflow_basic/.../agent-ag000001.jsonl
workflow_basic fixture now includes prose+embedded-JSON prompt for review:loader agent; fixture generation prefers agent.prompt over agent.label. Test transcript updated with detailed "map the loader area" instructions and proposed changes.
Snapshot regression testing for fold controls and styling
test/__snapshots__/test_snapshot_html.ambr
Updated HTML/CSS/JS snapshots reflecting fold control relocation, key-column toggles, aria-expanded synchronization, embedded-json styling, sidechain border color, and workflow-phase-color changes across repeated workflow rendering blocks.
Fold control browser and unit tests
test/test_params_rows_toggle_browser.py, test/test_params_table_hybrid.py
Updated selectors for .tool-param-fold-controls and .tool-param-key-toggle; added key-column toggle cycle test with aria-expanded validation and a11y constraint that no interactive elements appear in <summary>. Updated hybrid-table assertions for fold-glyph alignment and keyed-toggle rendering.
Workflow sidechannel and browser integration tests
test/test_workflow_sidechannel_user.py, test/test_workflow_browser.py
New test module covers JSON extraction (successful and edge cases), collapsible Markdown + params-table rendering, long-prompt details/summary wrapping, and fixture integration. Added CSS border/alignment and phase-pill navigation browser tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • daaain/claude-code-log#216: Both PRs modify the tool-params table rendering and fold UI structure (tool_formatters.py, transcript.html, message_styles.css), with this PR extending the reusable params-table infrastructure for workflow sidechannel JSON embedding.
  • daaain/claude-code-log#210: This PR's addition of TemplateMessage.in_workflow_sidechannel and sidechannel-specific rendering is directly tied to PR #210's splicing and grafting of workflow agent side-channel transcript nodes into the main rendering context.
  • daaain/claude-code-log#215: Both PRs modify workflow-level CSS variables and sidechain-group border styling (global_styles.css, message_styles.css sidechain selectors, and .sidechain nesting rules) affecting phase and agent group display.

Suggested reviewers

  • daaain

Poem

🐰 I nibble bytes and tidy rows,

extracting JSON where it grows.
Keys get buttons, summaries stay clear,
glyphs in sync — hooray, my dear!
Side-channel prose turns tables bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.55% 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 summarizes the main changes: side-channel prompt rendering, params-fold UX improvements, and accessibility enhancements, accurately reflecting the primary objectives of this follow-up PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/wf-sidechannel-json

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: 1

🤖 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 `@dev-docs/workflows.md`:
- Around line 160-166: The docs for extract_embedded_json in
html/user_formatters.py (used by format_workflow_sidechannel_user_content) are
missing the contract detail that only non-empty JSON arrays/objects are
extracted; empty payloads like {} and [] are intentionally left inline even
though json.loads would accept them — update the sentence describing extraction
to explicitly state that extraction only occurs for non-empty dict/list payloads
and that empty {} / [] remain inline.
🪄 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: 5ab1ecf5-631d-4c8a-9720-cf877df061e2

📥 Commits

Reviewing files that changed from the base of the PR and between a049975 and ecf9007.

📒 Files selected for processing (16)
  • claude_code_log/html/renderer.py
  • claude_code_log/html/templates/components/global_styles.css
  • claude_code_log/html/templates/components/message_styles.css
  • claude_code_log/html/templates/transcript.html
  • claude_code_log/html/tool_formatters.py
  • claude_code_log/html/user_formatters.py
  • claude_code_log/renderer.py
  • dev-docs/css-classes.md
  • dev-docs/workflows.md
  • scripts/gen_workflow_fixture.py
  • test/__snapshots__/test_snapshot_html.ambr
  • test/test_data/workflow_basic/11110000-0000-4000-8000-000000000001/subagents/workflows/wf_demo01/agent-ag000001.jsonl
  • test/test_params_rows_toggle_browser.py
  • test/test_params_table_hybrid.py
  • test/test_workflow_browser.py
  • test/test_workflow_sidechannel_user.py

Comment thread dev-docs/workflows.md Outdated
…n (CR #217)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cboos cboos merged commit f4f743d into main Jun 11, 2026
17 checks passed
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.

1 participant