Skip to content

fix(chat): draw the single-chat row set in split view panes - #3302

Merged
bolichen97 merged 1 commit into
mainfrom
feat/chat-sdk-one-transcript
Aug 13, 2026
Merged

fix(chat): draw the single-chat row set in split view panes#3302
bolichen97 merged 1 commit into
mainfrom
feat/chat-sdk-one-transcript

Conversation

@chenmingwei23

@chenmingwei23 chenmingwei23 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

1. What is the problem?

A Split View pane renders its transcript through the shared list website/src/app-sdk/ChatMessageList.tsx, which resolves every row through the registry in website/src/app-sdk/messageRenderers.tsx. Those defaults are deliberately store-free — that module must stay importable by consumers running outside the dashboard's React root, which have no Redux store at all.

So the defaults cannot draw any row that needs live app state, and a pane showed a reduced transcript against single chat. Four row types drew nothing whatsoever:

Row Single chat A pane, before
thinking ThinkingBlock with the reasoning text nothing (undrawn)
file FileCard — player / download nothing (null)
nudge NudgeCard with the cycle chip nothing (no entry claimed the role)
workflow launch WorkflowRunCard + live run status a generic static pill
sub-agent launch SubagentRunCard + live agent badges a generic static pill
recovery inject RecoveryCard, one line the full cron-notification bubble
workflow completion WorkflowCompletionCard, compact the event dumped as markdown
stop_event StopEventCard a bare danger div

A tool row's expanded state was also held inside the row, so it was forgotten on every remount.

2. Why this issue matters to the user

Split View exists to watch several sessions at once, and the dropped rows are disproportionately the ones carrying what a background session is doing right now. A pane is exactly where you want to see a workflow launch, a wave of sub-agents, an auto-nudge cycle firing, or a recovered turn — and those rendered as nothing or as an unlabelled pill, so a stalled session looked the same as a working one.

Two gaps read as product bugs rather than missing renderers: a sent file simply never appeared, and an auto-nudge turn's prompt was invisible, making a live monitoring loop look idle.

3. How our fix solves it

The symptom is missing rows; the root cause is where the rows may live. The registry is store-free by contract, so the fix is not to teach it about Redux but to supply the store-connected set from the host, which is the registry's documented extension path (docs/app-kit/api-reference.md): "anything that genuinely needs live app state is supplied BY the host as a registry entry".

  • New website/src/pages/chat/transcriptRenderers.tsx carries the single-chat row set as 11 MessageRenderer entries. It sits under pages/chat/ precisely because it reaches for app state — app-sdk/ChatMessageList.tsx stays Redux-free for the embed SDK.
  • ChatPane switches from the single-purpose renderTool shorthand to the full renderers prop. The SDK's renderTool support is untouched and still exercised by its own test.
  • Tool disclosure moves above the rows (ChatPane holds the map, the entry reads it), so expansion survives the remount that a message update causes.
  • The two launch rows match on the shared isWorkflowRunTool / isSpawnRunTool predicates that the grouping logic already uses, so a launch card and TurnBlock can never disagree about whether a row is a launch.

One consequence worth naming: this module replaces both of the registry's shape-matched defaults (stop_event, subagent_completion). After mergeRenderers there are no shape-matched defaults left, so the "shape beats role" guarantee is carried by this module's own array order. That is now pinned by test rather than left to a comment.

4. What tests we did

New website/src/test/transcriptRenderers.test.tsx — 16 tests:

  • every previously-undrawn row resolves to an entry that actually renders something (asserting the rendered output, not just that an entry was found — resolving to a null-returning entry would look identical on screen);
  • the narrow rows win over the broad row they refine (launch cards over the generic tool line, recovery over plain inject, workflow completion over plain assistant);
  • the 🔧 guard survives, so the hidden 🚫 deny sibling is still never drawn — including when its output is launch-shaped;
  • shape still beats role after both shape-matched defaults are replaced: a stop event carried on nudge, error, file, assistant or notice still draws as a stop event;
  • the error row offers Continue only on the last error, only when the turn was interrupted, and never on a surface that cannot continue one;
  • rows the defaults already draw correctly still resolve to the defaults, and queued / system still resolve to an entry that draws nothing;
  • every launch / recovery / completion fixture is guarded by the real predicate, so a stale fixture fails loudly instead of passing for the wrong reason. Two fixtures did exactly that during development and were corrected.

Gates, all green: tsc --noEmit 0 errors · eslint 0 errors (6 pre-existing warnings in ChatPane, untouched lines) · npm run i18n:check OK · npm run i18n:render OK · full vitest run: 1066 test files passed.

No new user-facing strings, so no locale catalogue changes.

Evidence

Captured on an isolated pod built from this branch, in a real Split View pane.

The pane, whole — its own header with the split/close controls, its own composer and status strip, so this is unmistakably a pane and not the single-chat surface. The sub-agent completion card (2 agents finished · Open Subagents panel) is one of the rows this change wires up:

Split view pane rendering the sub-agent completion card

The same pane with the tool group expanded. These are store-connected ToolCallLine rows — backend-stamped purpose text and per-row status glyphs ( for the two sandbox-denied reads, for the completed spawn_run load) — where the default registry would draw a static pill:

Split view pane rendering live ToolCallLine rows with purpose labels and status

Not demonstrated in these captures, and why: the thinking, file, auto-nudge, workflow-launch, workflow-completion, recovery and stop-event rows. The model in this pod emitted no separate thinking row (the "Thinking through the steps" text is ordinary assistant content), and the others need a real workflow run, a live monitor loop or a file_send to exist. Those rows are covered by the unit tests above rather than by a screenshot.

Visible in the captures and NOT introduced here: the empty 🔧 0 tool calls boxes. Those are groups containing only permission messages — CollapsibleToolGroup is given count={nonPerm.length}, which excludes permissions and so reads 0. Single chat never shows them at all, because groupDisplayItems.ts drops permission rows outright. This predates the change (a pane already grouped the same way) and is not fixable from the registry: the group is assembled before per-row resolution, which is the #2940 limitation. It belongs to the grouping convergence noted below.

5. Any other suggestions on the work

Deliberately out of scope, with reasons:

  • thinking still renders inside the collapsible group. It is in GROUPED_ROLES, so a host entry claiming it resolves within the group instead of standalone as single chat draws it. Opting a grouped role out of the group is not an extension point yet — chat SDK: let a host renderer opt a grouped role out of the collapsible group #2940. The entry is still a strict improvement (content shown vs nothing), and the limitation is documented at the entry.
  • File / folder open is not wired — filed as Files are not openable from a split view pane: the dock is activeSlot-keyed but pane focus is not #3300. The dock is usePanelTabs(activeSlot)-keyed while pane focus deliberately never routes through activeSlot, and two pieces of ChatPage's handler (search.close(), inline-preview de-duplication) are component-local and cannot be reproduced from a pane, so a copy would silently fail to show the file whenever the find pane is open.
  • The error row's Continue button needs slot-aware selectContinuable / selectTurnInterrupted; both are activeSlot-only today. The plumbing is in place behind optional options.
  • The user/assistant action surface (edit-and-resend, regenerate, variant switching, fork, plan-from-here, pins, quote, artifacts, diff chips) is a larger tier — AssistantMessage takes 31 props in single chat against the SDK default's 10.

Observed while working here, worth a separate look: the transcript has two independent grouping implementations — ChatMessageList's inline pass and pages/chat/groupDisplayItems.ts — and they disagree on whether thinking is groupable and on whether nudge opens a turn. Open PR #3207 is currently applying the same fix to both files, and the copy landing in the SDK omits the nudge boundary the other one has. Not touched here to keep this diff reviewable.

Collision note: ChatPane.tsx is also touched by open #3265, #3240 and #2243, so this change was kept deliberately small there (+26/−9, confined to the imports, one new memo, and the call site) to stay rebase-friendly.

Fixes #3299

@chenmingwei23
chenmingwei23 requested a review from a team August 13, 2026 13:50
@chenmingwei23
chenmingwei23 requested a review from a team as a code owner August 13, 2026 13:50
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 13, 2026
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of cba9e97780a0de97bda03375d71eef9199a7c5a2 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] cba9e97

False positive or not applicable? A repository writer can comment:
/ai-review override gpt cba9e97780a0de97bda03375d71eef9199a7c5a2: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — 🟡 CONCERNS

Advisory design-level review of cba9e97780a0de97bda03375d71eef9199a7c5a2 — updated in place on each push; does not block merge.

I have what I need. The PR is a single frontend commit: a new transcriptRenderers.tsx module supplying the single-chat row set as host registry entries for split-view panes, plus the ChatPane wiring and tests. Key facts established: ChatPage.tsx still renders through its own inline role chain (29 role === branches) and does not consume the new module, so the dashboard now has two hand-maintained row sets that must agree; the approach itself follows the registry's documented extension path and keeps the SDK store-free.

Design-Verdict: CONCERNS

Right extension seam, but it forks the single-chat row set into a second hand-maintained copy that ChatPage itself never consumes — drift is now silent.

Watch

  • transcriptRenderers.tsx duplicates ChatPage's row logic ("carries ChatPage's row set as registry entries") while ChatPage.tsx keeps its own inline 29-branch role chain untouched. A future row added or a predicate changed in ChatPage lands only there; panes regress to a reduced transcript again with no failing test, since the new tests pin today's set. The PR itself documents this exact failure mode already live in the two grouping implementations (fix: collapse per-completion assistant response inside subagent turn #3207) — this adds a third parallel transcript artifact to keep in sync.
  • ~Half of TranscriptRendererOptions (onFileOpen, onContinue/continuable/interrupted, onOpenNudgeLoop, appInPanel) is dead surface from ChatPane today — API designed for a convergence that isn't scheduled anywhere; state the target consumer or trim until it exists.

Suggestions

  • Name the endgame in a tracked issue: ChatPage consumes createTranscriptRenderers (or its entries) so the dashboard has exactly one row set and this module stops being a copy — otherwise this PR's bug class recurs by construction.

[DESIGN-REVIEWED] cba9e97

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

Advisory UX-level review of cba9e97780a0de97bda03375d71eef9199a7c5a2 — updated in place on each push; does not block merge.

Evidence gathered: I've read the diff, both screenshots, and traced the click handlers of the cards this PR newly draws in panes (SubagentRunCard, WorkflowRunCard) against how the side panel is mounted and slot-scoped. Emitting the review.

UX-Verdict: CONCERNS

Panes now show the right rows, but the launch cards' click-through opens the anchor session's panel — the label's promise breaks in the exact surface this PR targets.

Watch

  • "Open Subagents panel" misfires from a non-anchor pane. The whole SubagentRunCard is a button whose open() dispatches selectSubagent + openActivityToTab('subagents') (SubagentRunCard.tsx:209-210), but SidePanel is mounted with slot={activeSlot} and pane focus deliberately never moves activeSlot — so clicking the card in a background pane opens the Subagents tab of a different session, typically "No subagents running". Split View exists to watch background sessions, so most clicks hit this; task fails silently every time. Smallest fix: mirror the completion card's pattern — route the click through a host prop the pane omits, rendering the card non-interactive (status-only) until a slot-aware panel exists. WorkflowRunCard's openActivityToTab('workflows') has the same shape; verify it before shipping.

Suggestions

[UX-REVIEWED] cba9e97

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed cba9e97780a0de97bda03375d71eef9199a7c5a2 — this comment is updated in place on each push.

Review details

The evidence is conclusive. The backend persists tool calls as role "tool" (chat_runner.py:4333 slot.append("tool", ...)), and tool_call/tool_result are WS event-type names that route to sseToolActivity/sseToolResult (updating toolLog), never to sseChatMessage. No code path persists or dispatches a chat_message with role tool_call/tool_result into any slot's messages array — grep for append("tool_call"/"role": "tool_call" yields nothing, and history replay stores role "tool" too.

The candidate's part (a) — a concrete input that occurs in practice — cannot be established. Its own path narration ("If such rows ever reach the pane's message array") is a "might," and establishing it requires assuming messages the backend never produces. The tool_lifecycle fallback to the bare pill is unreachable in this KiroACP-only surface. The candidate dies under falsification.

No findings.

[OPUS-REVIEWED] cba9e97

Verdict parsed from the review's SHA-scoped output markers for commit cba9e97780a0de97bda03375d71eef9199a7c5a2.

False positive or not applicable? A repository writer can comment:
/ai-review override fable cba9e97780a0de97bda03375d71eef9199a7c5a2: <one-sentence reason>

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Aug 13, 2026
The shared transcript's registry defaults are deliberately store-free, so a
split view pane could not draw any row needing live app state: thinking
traces, sent files and auto-nudge turns drew nothing at all, workflow and
sub-agent launches collapsed to a generic pill, and recovery injects, workflow
completions and stop events fell back to weaker rows.

Supply those rows as host registry entries instead, which is the registry's
documented extension path and keeps app-sdk/ChatMessageList Redux-free for the
embed SDK. The two launch rows reuse the shared isWorkflowRunTool /
isSpawnRunTool predicates the grouping logic uses, so a launch card and
TurnBlock can never disagree about whether a row is a launch. Tool disclosure
moves above the rows so it survives a remount.

Fixes #3299
@chenmingwei23
chenmingwei23 force-pushed the feat/chat-sdk-one-transcript branch from 926c092 to cba9e97 Compare August 13, 2026 14:32
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Aug 13, 2026
@chenmingwei23

Copy link
Copy Markdown
Contributor Author

Frontend Tests (4) is red on cba9e9778 for one test that this change cannot reach, so it is being re-run rather than fixed here. Filed as #3314.

Test Files  1 failed | 1065 passed (1066)
FAIL src/test/MochiChatPanel.coverage.test.tsx > ChatPanel streaming footer >
     drops a half-arrived widget tag rather than showing its markup

The same file passes locally on this commit, 82/82. Mochi's panel is the self-owned vendored fork with its own role dispatch — neither that test nor apps/mochi/src/renderer/ChatPanel.tsx imports app-sdk/ChatMessageList, ChatPane, or the new transcriptRenderers module, so there is no path from this diff to that assertion. The assertion depends on where a chunk boundary lands mid-stream, which is the usual shape of a load-sensitive flake.

Frontend Coverage Merge is red as a consequence of the same shard, not independently.

Re-run is pending: the workflow still has jobs in flight, and gh run rerun --failed refuses while it is running.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Aug 13, 2026

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tier 1 auto-approve: small-fix (5 files). Criteria: no conflict, no requested changes, no security surface, AI reviewers green. Category: small-fix — corrects rendering of single-chat row set in split view panes.

@bolichen97
bolichen97 merged commit 9328817 into main Aug 13, 2026
92 of 97 checks passed
@bolichen97
bolichen97 deleted the feat/chat-sdk-one-transcript branch August 13, 2026 15:01
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 13, 2026
@chenmingwei23

Copy link
Copy Markdown
Contributor Author

Both advisory reviews on this PR are dispositioned in the follow-up: #3334

UX Review — FIXED. The finding was correct and it was a regression this PR introduced: SubagentRunCard / WorkflowRunCard are whole-card buttons that deep-link into an activeSlot-keyed panel, so drawing them in panes gave a background pane a click that opens another session's panel. #3334 decides the affordance from the card's own slot — button where the deep link lands where it promises (single chat, and a pane that is the active session), status only elsewhere with the chevron and the "Open … panel" label dropped rather than left lying. The information is kept: a background pane still reports the wave state and the run id. WorkflowRunCard was verified to have the same shape, as the review asked.

Design Review — partly fixed, partly filed. The drift concern now has a mechanical guard instead of a promise: a test extracts every role the single-chat chain dispatches on out of ChatPage.tsx and asserts the registry claims each one, so a new row type added to ChatPage fails a test rather than silently reducing the pane transcript. The endgame you asked to have named — ChatPage consuming these entries so there is exactly one row set — is #3332, and every option with no pane wiring yet now names its tracked consumer in the interface.

Also fixed there: both launch cards lay out their own full-width row, so wrapping them in ctx.row here doubled the padding.

Frontend Tests (4) was red at merge for #3314, a Mochi flake this diff cannot reach.

chenmingwei23 added a commit that referenced this pull request Aug 13, 2026
The workflow and sub-agent launch cards deep-link into the Workflows /
Subagents side panel. That panel is mounted for activeSlot, and split view
deliberately never moves activeSlot with pane focus, so #3302 — which started
drawing these cards in panes — gave a background pane a click that opens a
DIFFERENT session's panel, usually reading "No subagents running". Split view
exists to watch background sessions, so most clicks hit it.

Make the click correct instead of removing it: opening from a pane whose
session is not active dispatches switchSlot first, so the panel that opens is
the one the card's label promises. Safe inside split view — the auto-enter
effect is gated on splitMode being off, so switching neither reseeds nor leaves
the grid. Single chat passes no slot and is unchanged.

An earlier revision instead dropped the affordance in background panes. UX
review was right that this trades a lying link for a dead end: a user seeing
"1 agent failed" in the surface split view exists for would have no route to
the detail and no cue that one exists, and the card keeps enough of its accent
shell that a habituated click reads as broken. Retargeting removes the class
rather than hiding it, and needs no new copy — "Open in the Subagents panel"
stays true.

Also drops a double row wrapper: both cards lay out their own full-width row,
so wrapping them in the registry's ctx.row doubled the padding.

Adds a drift guard pinning that every role the single-chat chain dispatches on
is claimed by the registry a pane renders through, and names the tracked
consumer of each option that has no pane wiring yet.

Refs #3302, #3332
bolichen97 pushed a commit that referenced this pull request Aug 13, 2026
#3334)

The workflow and sub-agent launch cards deep-link into the Workflows /
Subagents side panel. That panel is mounted for activeSlot, and split view
deliberately never moves activeSlot with pane focus, so #3302 — which started
drawing these cards in panes — gave a background pane a click that opens a
DIFFERENT session's panel, usually reading "No subagents running". Split view
exists to watch background sessions, so most clicks hit it.

Make the click correct instead of removing it: opening from a pane whose
session is not active dispatches switchSlot first, so the panel that opens is
the one the card's label promises. Safe inside split view — the auto-enter
effect is gated on splitMode being off, so switching neither reseeds nor leaves
the grid. Single chat passes no slot and is unchanged.

An earlier revision instead dropped the affordance in background panes. UX
review was right that this trades a lying link for a dead end: a user seeing
"1 agent failed" in the surface split view exists for would have no route to
the detail and no cue that one exists, and the card keeps enough of its accent
shell that a habituated click reads as broken. Retargeting removes the class
rather than hiding it, and needs no new copy — "Open in the Subagents panel"
stays true.

Also drops a double row wrapper: both cards lay out their own full-width row,
so wrapping them in the registry's ctx.row doubled the padding.

Adds a drift guard pinning that every role the single-chat chain dispatches on
is claimed by the registry a pane renders through, and names the tracked
consumer of each option that has no pane wiring yet.

Refs #3302, #3332
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
…ev#3302)

The shared transcript's registry defaults are deliberately store-free, so a
split view pane could not draw any row needing live app state: thinking
traces, sent files and auto-nudge turns drew nothing at all, workflow and
sub-agent launches collapsed to a generic pill, and recovery injects, workflow
completions and stop events fell back to weaker rows.

Supply those rows as host registry entries instead, which is the registry's
documented extension path and keeps app-sdk/ChatMessageList Redux-free for the
embed SDK. The two launch rows reuse the shared isWorkflowRunTool /
isSpawnRunTool predicates the grouping logic uses, so a launch card and
TurnBlock can never disagree about whether a row is a launch. Tool disclosure
moves above the rows so it survives a remount.

Fixes kirodotdev#3299
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
kirodotdev#3334)

The workflow and sub-agent launch cards deep-link into the Workflows /
Subagents side panel. That panel is mounted for activeSlot, and split view
deliberately never moves activeSlot with pane focus, so kirodotdev#3302 — which started
drawing these cards in panes — gave a background pane a click that opens a
DIFFERENT session's panel, usually reading "No subagents running". Split view
exists to watch background sessions, so most clicks hit it.

Make the click correct instead of removing it: opening from a pane whose
session is not active dispatches switchSlot first, so the panel that opens is
the one the card's label promises. Safe inside split view — the auto-enter
effect is gated on splitMode being off, so switching neither reseeds nor leaves
the grid. Single chat passes no slot and is unchanged.

An earlier revision instead dropped the affordance in background panes. UX
review was right that this trades a lying link for a dead end: a user seeing
"1 agent failed" in the surface split view exists for would have no route to
the detail and no cue that one exists, and the card keeps enough of its accent
shell that a habituated click reads as broken. Retargeting removes the class
rather than hiding it, and needs no new copy — "Open in the Subagents panel"
stays true.

Also drops a double row wrapper: both cards lay out their own full-width row,
so wrapping them in the registry's ctx.row doubled the padding.

Adds a drift guard pinning that every role the single-chat chain dispatches on
is claimed by the registry a pane renders through, and names the tracked
consumer of each option that has no pane wiring yet.

Refs kirodotdev#3302, kirodotdev#3332
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.

Split view panes render a reduced transcript: eight row types the store-free registry cannot draw

2 participants