| 1 | import assert from "node:assert/strict"; |
| 2 | import { act } from "react"; |
| 3 | import { installDom, installBridgeApp, renderComposer } from "./composerInboxHarness"; |
| 4 | |
| 5 | const dom = installDom(); |
| 6 | const target = { tabId: "tab-a", sessionPath: "session-a", generation: 1, selection: 0, remote: false }; |
| 7 | let resolveReceipt!: (value: { itemId: string; disposition: string; position: number; paused: boolean }) => void; |
| 8 | const receipt = new Promise<{ itemId: string; disposition: string; position: number; paused: boolean }>(resolve => { resolveReceipt = resolve; }); |
| 9 | let submitted: unknown[] | undefined; |
| 10 | let saved = false; |
| 11 | installBridgeApp({ |
| 12 | CaptureInboxTarget: async () => target, |
| 13 | ListTabs: async () => [{ id: "tab-a" }], |
| 14 | InboxSnapshot: async () => ({ sessionPath: "session-a", revision: saved ? 2 : 1, mutationsSupported: true, |
| 15 | items: saved ? [{ id: "saved-guidance", preview: "preserve my guidance", state: "queued", intent: "followup", source: "desktop" }] : [] }), |
| 16 | EnqueueInboxFollowupForTarget: async (...args: unknown[]) => { submitted = args; return receipt; }, |
| 17 | InboxQueueForTarget: async () => { throw new Error("missing turn must queue as follow-up"); }, |
| 18 | }); |
| 19 | const { root, rerender } = await renderComposer({ running: true, localDurableGuidance: false, |
| 20 | onSteer: async () => { throw new Error("captured targets must use durable receipt tracking"); } }); |
| 21 | await rerender({ insertRequest: { id: 9101, text: "preserve my guidance", mode: "replace" } }); |
| 22 | const button = document.querySelector<HTMLButtonElement>(".composer__queue-steer"); |
| 23 | assert.ok(button); |
| 24 | await act(async () => { button.click(); }); |
| 25 | for (let i = 0; i < 50 && !submitted; i++) { |
| 26 | await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)); }); |
| 27 | } |
| 28 | assert.ok(submitted, "a missing turn must reach durable follow-up submission"); |
| 29 | assert.deepEqual(submitted.slice(0, 4), [target, "preserve my guidance", "preserve my guidance", []]); |
| 30 | assert.match(String(submitted[4]), /^followup-/); |
| 31 | assert.equal(document.querySelector<HTMLTextAreaElement>("textarea.composer__input")?.value, "preserve my guidance", "draft must survive until durable receipt"); |
| 32 | await act(async () => { |
| 33 | saved = true; |
| 34 | resolveReceipt({ itemId: "saved-guidance", disposition: "queued_followup", position: 1, paused: false }); |
| 35 | await new Promise(resolve => setTimeout(resolve, 0)); |
| 36 | }); |
| 37 | const renderDeadline = Date.now() + 5000; |
| 38 | while (Date.now() < renderDeadline && !document.querySelector(".inbox-queue__list")) { |
| 39 | await act(async () => { await new Promise(resolve => setTimeout(resolve, 10)); }); |
| 40 | } |
| 41 | assert.ok(document.querySelector(".inbox-queue__list")?.textContent?.includes("preserve my guidance"), `durable receipt should show queued guidance: ${document.body.textContent}`); |
| 42 | assert.equal(document.querySelector<HTMLTextAreaElement>("textarea.composer__input")?.value, "", "only the receipt clears the draft"); |
| 43 | assert.equal(document.querySelector(".toast--warn"), null); |
| 44 | await act(async () => root.unmount()); |
| 45 | dom.window.close(); |
| 46 | console.log("composer turn-discovery race preserves guidance through durable receipt"); |
| 47 |