Skip to content

feat(chat): add MessageParts to render a message's parts - #17

Closed
pratikbuilds wants to merge 1 commit into
conversation/16-remove-old-chatfrom
conversation/17-parts-renderer
Closed

pratikbuilds wants to merge 1 commit into
conversation/16-remove-old-chatfrom
conversation/17-parts-renderer

Conversation

@pratikbuilds

@pratikbuilds pratikbuilds commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #16. Part of corbitsdev#94.

Why

Every host maps ConversationPart to components by hand, and each one re-implements the approval focus handoff. When groupActivity folds a decided call into the run before it, the call's buttons unmount, and without that handoff focus drops to <body>. The old PartsRenderer only knew the part type that #16 removed.

What

  • MessageParts (src/ui/message-parts.tsx, in the root barrel) groups a message's parts with groupActivity and renders each kind as its component:
    • runs of reasoning and tool work → Activity
    • a call waiting on a person → ToolApproval
    • a batch with a waiting call → ToolGroup
    • text → MessageContent, using renderMarkdown as its render prop
    • consecutive file parts → one row of Attachments tiles
    • image parts → GeneratedImages, with onOpenImage
    • sources → Sources
    • events → ConversationNotice
  • Per-kind overrides: renderText, renderActivity, renderStep, renderApproval, renderBatch, renderBlock, renderFiles, renderEvent, renderImages, renderSources. An override can return undefined to keep the default or null to render nothing. A block's data belongs to the host, so blocks render nothing without renderBlock.
  • Focus handoff: after a decision folds the call away, if focus is left on the page it moves to the run's ActivityTrigger. In a batch that still has waiting calls, it moves to the next waiting call's buttons, wrapping to the first. The handoff waits until the host has recorded the decision in parts, and it never takes focus from an element the person has moved to. A custom renderApproval or renderBatch gets the same behaviour through the decide callback it receives.
  • streaming defaults to the enclosing Message's status.
  • Replaced hand-written mappings: stories/chat/full-chat.stories.tsx (its focus code is removed), stories/chat/activity.stories.tsx, and e2e/fixtures.tsx (the tool-approval and thread scenarios, plus a new tool-batch-approval scenario).

Tests

  • src/ui/message-parts.test.tsx (16 tests): every part kind, overrides, streaming, image open, and focus after a decision:

    • each of the three decisions
    • a call with no run before it
    • moving to the next waiting call in a batch, then wrapping
    • a decision the host records later
    • focus the person moved elsewhere stays there
    • a host's renderApproval

    The focus tests fail when the handoff is disabled.

  • Playwright: e2e/focus.ts checks document.activeElement on every frame. The tool-approval, batch-approval and full-chat specs assert that focus is never on <body> for even one frame after a keyboard decision.

  • bun run check, build, dep-guard and the full test:e2e pass: 126 passed, including the Storybook axe sweep. The known list is unchanged.

Every host mapped ConversationPart to components by hand and re-implemented
the focus handoff when a decided call folds into the run before it.
MessageParts groups parts with groupActivity and renders each kind as its
component (Activity, ToolApproval, ToolGroup, MessageContent, Attachments
tiles, GeneratedImages, Sources, ConversationNotice), with a render prop per
kind to replace any of them. After a decision folds a call away, focus moves
to the run's trigger, or to the next waiting call in a batch, instead of
falling to the page.

The full chat and activity stories and the e2e fixtures use it in place of
their own mappings; the e2e specs check that focus never sits on <body>
for a frame after a keyboard decision.
@pratikbuilds

Copy link
Copy Markdown
Owner Author

Moved upstream: corbitsdev#129

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