docs(sidechat): record the followUpIsPlan exclusion with a pinning test (#6754) - #6759
Conversation
…st (#6754) SideChat destructures only followUpOptions from deriveFollowUpOptions, deliberately dropping followUpIsPlan: the side panel has no orchestrator mode source, so a plan-shaped chip stays on the composer-draft path. The drop was correct but unrecorded -- no comment stated the rationale and no test pinned the behaviour, so an auditor would either re-derive it or "fix" it by wiring dispatch into a host that cannot gate it. Two deliverables, no behaviour change (sibling of the ChatEmbed record on #6057, PR #6748): - A block comment at the destructure recording the load-bearing facts: the dispatch path gates on the host slot's mode which this panel cannot express, a side turn is never an orchestrator turn, and useComposerDraft is the correct owner of a plan-shaped chip here. - A pinning test (SideChat.planExclusion.test.tsx): premise pin that the fixture derives followUpIsPlan=true, a chip click lands on the composer-draft path, and a spy proves no planAction dispatch. Red-before proven: with a plan dispatch temporarily wired into SideChat's chip handler, the test fails; scaffolding reverted. Closes #6754
UX Review (Fable 5) — ✅ PASSUX-level review of UX-Verdict: PASS No user-facing change: the diff adds a code comment and a pinning test; no string, pixel, or interaction a user experiences is altered. [UX-REVIEWED] 7d7596f |
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: |
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS A real contributor trap, closed with the cheapest sound mechanism: a rationale comment plus a non-vacuous pinning test, zero runtime change. The test's design holds up: the premise pin ( [DESIGN-REVIEWED] 7d7596f |
First Principles Review (Fable 5) — ✅ PASSPremise-level review of All claims verified. The key fact grounding this change: First-Principles-Verdict: PASS Both items pin a real hazard with a named precedent — #5893 shows this exact drop was once a live bug in a sibling host — at zero runtime cost. What this change shipsIntent: make SideChat's deliberate drop of
Both items are declared; no config, flag, export, or behavior rides along (diff verified: [FIRST-PRINCIPLES-REVIEWED] 7d7596f |
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: |
bolichen97
left a comment
There was a problem hiding this comment.
Approved after a full-diff review (scope match, no out-of-scope files, security surface checked, tests verified non-vacuous). Review notes available on request.
Problem / Motivation
deriveFollowUpOptions(website/src/app-sdk/protocol/options.ts) returnsfollowUpOptions,followUpIsPlan, andfollowUpSourceKey.website/src/pages/chat/SideChat.tsxdestructures onlyfollowUpOptions, silently droppingfollowUpIsPlan. The drop is correct in effect — the side panel is not a plan-capable host — but nothing records that: no comment states the rationale, and no test pins the behaviour. A contributor auditing plan-chip coverage finds an undocumented drop and either re-derives the rationale from scratch or "fixes" it by wiring dispatch into a host that has no mode source to gate it.Why it matters
The undocumented exclusion is a trap for future contributors. Wiring plan dispatch into SideChat without a mode gate would let any plan-shaped side answer cancel or advance the parent slot's real orchestrator plan — a live cross-conversation mis-dispatch. The record (comment + pinning test) is what turns "silent drop" into "reviewed, deliberate exclusion" and makes any future wiring attempt fail a test instead of shipping.
What changed (motivation → approach → change)
Applies the same-size record pattern as PR #6748 does for ChatEmbed (sibling issue #6057 covers the same drop there). Two deliverables, zero runtime behaviour change:
deriveFollowUpOptionsdestructure recording the load-bearing facts: a side turn runs as an aside to the parent session and is never an orchestrator turn, souseComposerDraftowning a plan-shaped chip (pick → edits the draft, amendable before send) is correct behaviour, not a fallback; the dispatch path gates on the host slot's mode (ChatPagereadseffectiveMode === 'orchestrator'off the slot record), which this panel does not read today; and an unconditional dispatch would let a plan-shaped side answer cancel or advance the parent's real plan.website/src/test/SideChat.planExclusion.test.tsx), mirroring the ChatEmbed plan-exclusion test shape, with a sharedSIDE_MESSAGESfixture feeding both the rendered store and the premise derivation so they cannot drift apart.Deliberately out of scope (same as #6748): wiring
usePlanActionMutationinto SideChat. Path B parity is a product decision, not this change.Tests
New file
website/src/test/SideChat.planExclusion.test.tsx, three assertions in one scenario:deriveFollowUpOptions(asTranscript, false).followUpIsPlan === true, whereasTranscriptis mapped from the sameSIDE_MESSAGESarray the store renders, through the same role/cls expression SideChat's transcript memo applies (non-streaming, non-error case). Without this the test would pass vacuously the moment the fixture stops being plan-shaped.Gochip lands the label in the side composer's draft (input.value === 'Go').api.planActionis never called (andapi.sideTurnis not called either — picking is an edit, not a submit).Red-before proven: a plan dispatch was temporarily wired into SideChat's chip handler (
if (followUpIsPlan) { void api.planAction(slot, o); return }); the test then failed (expected '' to be 'Go'— the draft assertion reds because dispatch short-circuits the draft edit). The scaffolding was reverted; only the comment and the test remain in the diff.Full frontend suite on the rebased branch: 1638 test files, 25,938 passed, 0 failures (
npx vitest run).npx tsc -bclean.Manual verification
N/A — unit coverage sufficient: the change is a comment plus a test; the pinned interaction (chip click → draft edit, no dispatch) is exactly what the test drives through the real component.
Screenshots / video
Why no screenshot: the diff adds only a code comment and a test file; no rendered output changes anywhere.
N/A — this change adds a comment and a test and alters no rendered output. No pixel changes anywhere; explicitly not a skipped screenshot gate.
Related Issues
Fixes #6754
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)