Side-channel prompt rendering + params-fold UX and accessibility (#174 follow-up)#217
Conversation
…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>
|
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 (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughThis 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.
|
| 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. |
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.🐰 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 | 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.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
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
📒 Files selected for processing (16)
claude_code_log/html/renderer.pyclaude_code_log/html/templates/components/global_styles.cssclaude_code_log/html/templates/components/message_styles.cssclaude_code_log/html/templates/transcript.htmlclaude_code_log/html/tool_formatters.pyclaude_code_log/html/user_formatters.pyclaude_code_log/renderer.pydev-docs/css-classes.mddev-docs/workflows.mdscripts/gen_workflow_fixture.pytest/__snapshots__/test_snapshot_html.ambrtest/test_data/workflow_basic/11110000-0000-4000-8000-000000000001/subagents/workflows/wf_demo01/agent-ag000001.jsonltest/test_params_rows_toggle_browser.pytest/test_params_table_hybrid.pytest/test_workflow_browser.pytest/test_workflow_sidechannel_user.py
…n (CR #217) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.loadsparses it. Each block is substituted with a z-prefixed UUIDplaceholder (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
<summary>— Chrome flagged everyrows-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.
cell — the whole key is one keyboard-reachable
<button>(
aria-expanded), every key cell reserves the same glyph slot so keytexts 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) — thesame symmetric icons
<summary>shows, immune to italic contexts.syncs key glyphs, rows-toggles, and the expand-all button on any
open/close path.
Visual tuning + runtime coverage
continuing the spawning card's border); the workflow phase color softens
to
#3a7d3c.:has()-drivengroup 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
UI/Visual Changes
Tests
Documentation