diff options
| author | Adam Malczewski <[email protected]> | 2026-06-12 16:28:07 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-12 16:28:07 +0900 |
| commit | 4001274e3ba25a3946df1e9f2dc82ca6781cd2bf (patch) | |
| tree | 24af95e69bda5c38ab7eefd6b71d55b4c247040a /src/adapters | |
| parent | e6f6bd86eab07954d8f06e740659969c3dfecc7f (diff) | |
| download | dispatch-web-4001274e3ba25a3946df1e9f2dc82ca6781cd2bf.tar.gz dispatch-web-4001274e3ba25a3946df1e9f2dc82ca6781cd2bf.zip | |
feat(cache-warming): consume CR-4 lifecycle — tab-close cancel + scope-aware subscriptions
- closeTab now POSTs /conversations/:id/close (abort in-flight turn + stop/disable
warming server-side); disconnect still leaves both running ([email protected])
- syncSubscriptions honors catalog scope ([email protected]): global surfaces are
not re-subscribed on conversation switch
- fix(ws): the surface-message parser dropped the conversationId echo (CR-4d was
ours, not the backend's) — preserved + unit-tested
- secondsUntilNext: 3s stale guard — a past nextWarmAt renders as waiting, not 0s
- re-pinned + re-mirrored [email protected] / [email protected]
- scripts/probe-cache-warming.ts: live CR-4 probe (default-off, future nextWarmAt,
repeated warms, mid-turn close abort, idempotent re-close) — 17/17 against bin/up
Diffstat (limited to 'src/adapters')
| -rw-r--r-- | src/adapters/ws/logic.test.ts | 23 | ||||
| -rw-r--r-- | src/adapters/ws/logic.ts | 10 |
2 files changed, 32 insertions, 1 deletions
diff --git a/src/adapters/ws/logic.test.ts b/src/adapters/ws/logic.test.ts index 546afe1..2784295 100644 --- a/src/adapters/ws/logic.test.ts +++ b/src/adapters/ws/logic.test.ts @@ -64,6 +64,29 @@ describe("parseServerMessage", () => { }); }); + it("preserves the conversationId echo on a scoped surface message", () => { + const data = JSON.stringify({ + type: "surface", + spec: { id: "s1", region: "r", title: "S1", fields: [] }, + conversationId: "c1", + }); + const result = parseServerMessage(data); + expect(result).toEqual({ + type: "surface", + spec: { id: "s1", region: "r", title: "S1", fields: [] }, + conversationId: "c1", + }); + }); + + it("rejects a surface message with a non-string conversationId", () => { + const data = JSON.stringify({ + type: "surface", + spec: { id: "s1", region: "r", title: "S1", fields: [] }, + conversationId: 42, + }); + expect(parseServerMessage(data)).toBeNull(); + }); + it("parses an update message", () => { const data = JSON.stringify({ type: "update", diff --git a/src/adapters/ws/logic.ts b/src/adapters/ws/logic.ts index 6592f1b..17e3951 100644 --- a/src/adapters/ws/logic.ts +++ b/src/adapters/ws/logic.ts @@ -59,7 +59,15 @@ export function parseServerMessage(data: string): WsServerMessage | null { if (typeof spec.region !== "string") return null; if (typeof spec.title !== "string") return null; if (!Array.isArray(spec.fields)) return null; - return { type: "surface", spec: spec as unknown as SurfaceMessage["spec"] }; + // Preserve the conversationId echo (a conversation-scoped surface's initial + // reply carries it) — dropping it would defeat the protocol reducer's + // stale-scope filtering on a fast conversation switch. + const conversationId = parsed.conversationId; + if (conversationId !== undefined && typeof conversationId !== "string") return null; + const surfaceSpec = spec as unknown as SurfaceMessage["spec"]; + return conversationId !== undefined + ? { type: "surface", spec: surfaceSpec, conversationId } + : { type: "surface", spec: surfaceSpec }; } case "update": { const update = parsed.update; |
