Skip to content

fix(chat): bridge defers surfaceUpdate dispatch until components have ids#269

Merged
blove merged 1 commit into
mainfrom
claude/genui-streaming-bridge-fix
May 12, 2026
Merged

fix(chat): bridge defers surfaceUpdate dispatch until components have ids#269
blove merged 1 commit into
mainfrom
claude/genui-streaming-bridge-fix

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 12, 2026

Summary

The partial-args bridge dispatched surfaceUpdate envelopes the moment they were "structurally complete" (components is an array) — typically very early in streaming when the first component object hadn't yet streamed its id field. pickRoot then returned null and synthesis was skipped. The bridge's dispatchedCount advanced past the surfaceUpdate envelope, so subsequent pushes never retried — the surface never mounted via the live-stream path.

PR #268 fixed the backend so a2ui-partial events now reach the SSE wire (verified live: 758 events / 2.5 MB stream for a dashboard prompt), but the bridge consumed them without producing any live surface.

Fix

Option (c) from the design discussion: defer surfaceUpdate dispatch until pickRoot(components) returns a real string, then dispatch the surfaceUpdate + synthesized beginRendering as an atomic pair. After that, continue with index-based dispatch for subsequent envelopes (dataModelUpdates, etc.).

The store treats a "real" beginRendering arriving later with the same surfaceId as idempotent (it merges styles; component map unchanged), so we additionally skip-and-advance past it in the dispatch loop.

Tests

  • Existing 8 tests adjusted where they tested the old "dispatch surfaceUpdate immediately" behavior
  • 3 new tests: incremental component-id arrival, mount+dataModelUpdate flow, pickRoot fallback
  • nx test/build/lint chat green (11 tests passed)

Live smoke (post-merge)

  • liveSurfaceStore populates mid-stream
  • render-default-fallback visible during streaming window
  • Each dataModelUpdate flips one component from fallback to real

@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 12, 2026 11:28pm

Request Review

… ids

The partial-args bridge dispatched surfaceUpdate envelopes the moment they
were structurally complete (components is an array) — typically very early
in streaming when the first component object had not yet streamed its id
field. pickRoot then returned null and synthesis was skipped. The bridge's
dispatchedCount advanced past the surfaceUpdate envelope, so subsequent
pushes never retried and the surface never mounted via the live-stream path.

Now: defer dispatch until pickRoot(components) returns a real string, then
dispatch the surfaceUpdate + synthesised beginRendering as an atomic pair.
Subsequent envelopes flow through the existing index-based dispatch loop.
A real beginRendering arriving later for the same surfaceId is treated as
idempotent and skip-advanced past in the dispatch loop.
@blove blove force-pushed the claude/genui-streaming-bridge-fix branch from afbba74 to 3e75ad7 Compare May 12, 2026 23:21
@blove blove merged commit c5605ba into main May 12, 2026
13 of 14 checks passed
blove added a commit that referenced this pull request May 12, 2026
…rk (#271)

Brings the canonical smoke checklist current with 29 PRs that landed
between Phase 7 (#239) and today without checklist updates. Specifically:

Updated sections:
- chat-debug devtools — replaced bottom-drawer model with floating
  launcher + status pill + switch (PRs #249, #251)
- Control palette — palette v2 (status pill, shadcn-styled panel, PR #244)
- Generative UI / A2UI surfaces — single-bubble invariant (PR #255),
  parent-emits-envelopes architecture (PR #259), wrapped-content +
  tool_calls coexistence (PR #255), envelope reorder
- Server-side wire format — tool_calls preserved on the final AI
- Replaced 'Multi-thread' section with 'Sidenav (thread management)'
  reflecting the permanent semantic <nav> + Active/Archived sections
  (PR #253) and removing the old palette-toggled drawer model

Added sections:
- Cmd+K history search — palette open/search/select/close, archived
  result subtitle, keyboard navigation (PR #253)
- Per-row thread actions — kebab menu order per state (active, pinned,
  archived), rename + pin/unpin + archive/unarchive + delete flows
  (PRs #258, #260, #267)
- Thread titles — first-user-message derivation, idempotent writes,
  manual rename precedence (PR #242)
- Progressive A2UI streaming — per-component fallback transition
  observable during streaming window (PRs #252, #261, #262, #268, #269)
- Inline checkpoint markers — render between messages during multi-step
  runs (PR #243)
- Responsive sidenav — viewport breakpoints, auto-collapse behavior (PR #240)

Total: ~58 new check items across 6 new sections, plus rewrites to 5
existing sections. Original 333-line checklist → 391 lines / 237 check
items.
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