feat(app-sdk): MessageRenderContext carries an optional session-open seam (plumbing for #8254) - #9122
feat(app-sdk): MessageRenderContext carries an optional session-open seam (plumbing for #8254)#9122chenmingwei23 wants to merge 1 commit into
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of Verified the load-bearing claims: Design-Verdict: PASS Additive optional seam mirroring a proven shape ( [DESIGN-REVIEWED] e968b5e |
UX Review (Fable 5) — ✅ PASSUX-level review of No production call site passes the new props — UX-Verdict: PASS Plumbing-only seam with zero wired hosts — nothing a user sees, says, or clicks changes in this PR. [UX-REVIEWED] e968b5e |
First Principles Review (Fable 5) — 🔴 BLOCKPremise-level review of All the evidence is in. The new trio has zero suppliers anywhere in-tree (all three First-Principles-Verdict: BLOCK The seam ships with zero consumers by the author's own count, onto a published SDK contract the repo itself says must not be frozen unexercised. What this change shipsIntent: let a
Nothing is undeclared; the framing is honest. Honesty does not supply a consumer. BlockersZero-consumer surface on a published contract. The description states it: "There are zero host consumers in this PR by design" and "Nothing changes for the user in this PR." Counted: grep [FIRST-PRINCIPLES-REVIEWED] e968b5e |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
f3fed62 to
b21ea28
Compare
|
Addressed in GPT F1 (blocking) - pane link mutates global session state. The concern is real and I fixed it by construction, but I read the mechanism more narrowly than the prescription. A session chip is a "go to that session" affordance, and switching the active session is its intended effect - Fix, mirroring
I did not adopt the "host-owned rebind / per-pane navigation" prescription: rebinding a fixed-slot pane to a different slot on a link click is a larger behavioural change than the issue asks for, and it would diverge from the established Opus (advisory) - same finding. Resolved by the same change: First Principles (advisory) - two accuracy corrections, both fixed in the PR body:
New test |
b21ea28 to
3eb09f5
Compare
|
Addressed in The finding is correct on the merits. In a fixed-slot pane, I judged the two remedies and took neither:
The correct in-place wiring is genuinely host-shaped (rebind the fixed pane to the target slot, or drive that host's own navigation), and it differs per host. That is a separate decision, so I removed the What this PR now is: the interface addition #8254 actually asks about - Final diff: 3 files - |
…lback (#8254) Answers the issue's design question with the interface addition it proposed: mirror the existing onFileOpen? precedent so a /chat?sid= link CAN resolve to an in-place session switch, while the registry stays store-free. MessageRenderContext + ChatMessageListProps gain optional onSessionOpen / sessions / activeSession (host-supplied, exactly like onFileOpen?). The three default rows that render markdown -- user, assistant, note/inject -- forward them to MarkdownRenderer, whose resolveSessionChip already switches in place when they are present and falls through to an external link when absent. A host that wires nothing is byte-for-byte unchanged. No dashboard host is wired in this PR. Wiring a specific host is a separate, host-shaped decision: for a fixed-slot split-view or member-DM pane, dispatching the global switchSlot changes chat.activeSlot but the pane keeps rendering its own slotKey, so an inline session-link click would mutate global state with no visible effect in that pane -- worse than an honest external link. The correct in-place wiring for those hosts (rebind the pane, or route the embedded host's own navigation) is left to a follow-up so the callback is never fired where it does nothing. Refs #8254
3eb09f5 to
e968b5e
Compare
|
First Principles is right, and I am conceding on the merits rather than pushing back. The decisive evidence is the repo's own rule, which I verified at That rule binds here because I have also confirmed no host can exercise the seam correctly yet. In split mode So the subtraction you prescribe is the correct one: drop the exported trio and land it inside the first per-host wiring PR that #8254 anticipates - additive at that point, and shaped by a consumer that actually works. I am taking this back to the pipeline to either close this PR or reduce it to nothing shippable-alone; I will not force the seam past a documented published-surface rule. |
|
Standing down and closing this PR. First Principles' BLOCK is upheld on the merits, and the wiring turns out to be genuinely host-shaped, so the seam should not be published unexercised. Evidence that no single
The dashboard's own rows already have this capability via Per #8254 stays open for that first per-host wiring PR. Closing this one; nothing here is shippable alone. |
What is the problem?
MessageRenderContext(the exported app-sdk render context,messageRenderers.tsx:46) had no way to resolve a/chat?sid=<slot-key>session link. ItsMarkdownRenderercall sites passed content alone, soresolveSessionChiprefused for want ofonSessionOpen/sessionsand the link fell through to an external new-tab link. #8254 asks - as a CONTRIBUTING-routed RFC question, since the interface is exported - whether the context should carry an optional session-open callback, the way the dashboard's own rows already do.This PR ships the answer as plumbing only: the seam is added, no dashboard host is wired to it, and the user-visible new-tab behaviour is unchanged. #8254 stays open for the host-wiring half (see the last section for why that half is not mechanical).
Why this issue matters to the user
Nothing changes for the user in this PR. The value is to the next contributor: the exported context now can carry the session-open capability with
onFileOpen's exact shape, so a host that has a correct in-place switch to offer can supply it without touching the store-free registry. Absent a host, every row behaves exactly as today.How our fix solves it
The context already carries a host-supplied optional callback -
onFileOpen?atmessageRenderers.tsx:55- and that is the shape the session case needs. #3299's constraint is "no store import", not "no host callbacks".MessageRenderContextandChatMessageListPropsgain optionalonSessionOpen/sessions/activeSession, mirroringonFileOpen?verbatim. The registry stays store-free.MarkdownRenderer, whoseresolveSessionChipswitches in place when they are present, drops a link naming the active slot (the A /chat?sid= link in a user message opens a new tab instead of switching session #8253 negative control), and falls through to an external link when they are absent (every host today).MarkdownRendererandAssistantMessagealready accept these props end-to-end, so the forwarding is the whole wiring on the registry side.Scope is honest: this is the seam, not a behaviour change. There are zero host consumers in this PR by design.
What tests we did
ChatMessageList.test.tsxgains asession-navigation callback threading (#8254)block: the user, assistant, and note/inject rows each receive the trio when a host supplies it, plus a negative control proving the rows fall back to no session props when the host wires nothing.vitest runonChatMessageList.test.tsx,messageRenderers.test.ts,transcriptRenderers.test.tsx, andChatPane.switchLabelOptimistic.test.tsx(provingChatPaneis untouched) - all passing.tsc -bzero errors in the changed files;eslintclean. No Python touched. Diff is 3 files:messageRenderers.tsx,ChatMessageList.tsx,ChatMessageList.test.tsx.Any other suggestions on the work
Why no host is wired here, and why that half is not mechanical. All three
ChatMessageListhosts render a fixed slot, none renderschat.activeSlot:SessionGridViewbinds each pane toleaf.slot,MembersPagebinds its pane to a page-local slot, andChatEmbedis store-free.switchSlot(key)mutates the globalchat.activeSlotand has no router side-effect, so dispatching it from any of these panes would change global state with no visible effect in the pane the user clicked in - a callback that looks wired but silently does nothing, which is worse than the honest external link. A correct in-place wiring is host-shaped (rebind the fixed pane to the target slot, or drive that host's own navigation) and also needs the offline guard the main page documents (an offlineswitchSlotrejects and clears the target slot's messages). That belongs in a focused follow-up per host; shipping the seam first keeps this PR small and never fires the callback into a no-op.Refs #8254