summaryrefslogtreecommitdiffhomepage
path: root/src/adapters
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-12 16:28:07 +0900
committerAdam Malczewski <[email protected]>2026-06-12 16:28:07 +0900
commit4001274e3ba25a3946df1e9f2dc82ca6781cd2bf (patch)
tree24af95e69bda5c38ab7eefd6b71d55b4c247040a /src/adapters
parente6f6bd86eab07954d8f06e740659969c3dfecc7f (diff)
downloaddispatch-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.ts23
-rw-r--r--src/adapters/ws/logic.ts10
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;