Turn continuity P3: unify the POST transport onto the turn subscription channel
Phase P3 of the Turn Continuity plan (#2532 shipped P0–P2). Deliberately deferred: it touches the most-travelled path in the app, and it can trail the rest without weakening the guarantees — the two transports share the turn log until then.
Goal
The POST that starts a turn returns as soon as the turn is registered; the tab that started it subscribes to the same turn-keyed channel as every other observer. Two streaming code paths become one, and "the tab that started the run" stops being a special case anywhere in the client.
Why
Today the initiating tab consumes updates from the POST response body while every other observer (reload, second tab, second device) consumes the turn-scoped SSE channel. Both read the same turn log since P0, but the duplicated consumer logic means every client behavior (stop, edit, retry, elicitation answers, turn-state handling) is wired twice — writeMessage's consume path and reattachToRun's — and divergence between the two has been a recurring bug source (e.g. the reattach-vs-POST double-apply guard).
Work
- POST
/conversation/[id]registers the turn, kicks off generation, and returns early (turn key + initial seq). - The initiating tab subscribes with
(messageId, fromSeq=0)— the identical path a returning tab uses. - Delete the POST-streaming consumer in
+page.svelteand the duplicated consume wiring. - Re-verify every chat behavior against the single channel: stop, edit, retry, ask_user_question answers, MCP elicitation answers, wait park/resume.
Done when
The POST-streaming consumer is deleted and all existing chat behaviors pass against the single channel — including the e2e suites (conversation-streaming, generation-reattach, generation-events).
Risk
The only phase of the plan flagged as risky. P0–P2 (merged) were additive; this rewires the primary send path. Ship behind careful e2e coverage; the guarantees (G1–G5) are unaffected either way.
Design doc: the "Turn Continuity" plan (P3 — One transport), summarized in #2532's description.
Source: huggingface/chat-ui