diff options
| author | Adam Malczewski <[email protected]> | 2026-10-21 03:23:35 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-10-21 03:23:35 +0900 |
| commit | 550ade33285d1f39a909733e02a6b27211464df2 (patch) | |
| tree | 365672bb62798b2b64d44a18b237f1f34394073f /src/app | |
| parent | 0c0f366b3820136c9877f80005939011c30b2de4 (diff) | |
| download | dispatch-web-550ade33285d1f39a909733e02a6b27211464df2.tar.gz dispatch-web-550ade33285d1f39a909733e02a6b27211464df2.zip | |
fix(review-4.7): preserve images through composer queue submissions
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/App.test.ts | 47 | ||||
| -rw-r--r-- | src/app/store.svelte.ts | 6 | ||||
| -rw-r--r-- | src/app/views/ComposerPane.svelte | 2 |
3 files changed, 51 insertions, 4 deletions
diff --git a/src/app/App.test.ts b/src/app/App.test.ts index 183b8b4..14dac3a 100644 --- a/src/app/App.test.ts +++ b/src/app/App.test.ts @@ -434,6 +434,53 @@ describe("App component interaction tests", () => { store.dispose(); }); + it.each([ + "look at this", + "", + ])("queues staged images through the full UI with text %j", async (text) => { + const ws = fakeSocket(); + const store = createAppStore({ + socketFactory: () => ws, + fetchImpl: fakeFetchImpl(), + localStorage: createFakeStorage(), + chatFlushScheduler: null, + }); + ws.resolveOpen(); + const { container } = render(App, { props: { store, onNavigate: vi.fn() } }); + const user = userEvent.setup(); + const textarea = screen.getByRole("textbox", { name: "Message input" }); + await user.type(textarea, "start"); + await user.click(screen.getByRole("button", { name: "Send" })); + const conversationId = activeConversationId(store); + ws.feedServerMessage({ + type: "chat.delta", + event: { type: "turn-start", conversationId, turnId: "t1" }, + }); + if (text) await user.type(textarea, text); + const input = container.querySelector('input[type="file"]') as HTMLInputElement; + Object.defineProperty(input, "files", { + value: [new File(["GIF"], "shot.gif", { type: "image/gif" })], + }); + input.dispatchEvent(new Event("change", { bubbles: true })); + await screen.findByRole("button", { name: "Remove image" }); + ws.sent.length = 0; + + await user.click(screen.getByRole("button", { name: "Queue" })); + + expect(sentMessages(ws).filter((m: { type: string }) => m.type.startsWith("chat."))).toEqual([ + { + type: "chat.queue", + conversationId, + workspaceId: "default", + text, + images: [{ url: "data:image/gif;base64,R0lG", mimeType: "image/gif" }], + }, + ]); + expect(textarea).toHaveValue(""); + expect(screen.queryByRole("button", { name: "Remove image" })).not.toBeInTheDocument(); + store.dispose(); + }); + it("incoming chat.delta renders text in the chat transcript", async () => { const ws = fakeSocket(); const store = createAppStore({ diff --git a/src/app/store.svelte.ts b/src/app/store.svelte.ts index 2eb13ef..f3d0c3b 100644 --- a/src/app/store.svelte.ts +++ b/src/app/store.svelte.ts @@ -257,7 +257,7 @@ export interface AppStore { * auto-starts a turn (equivalent to `send`). Safe to offer whenever the user * wants to add input — the server owns the idle-vs-generating decision. */ - queueMessage(text: string): void; + queueMessage(text: string, images?: readonly ImageInput[]): void; /** * Cancel (remove) a single queued steering message by id so it never runs * (`chat.queue.cancel` WS op). Fire-and-forget + idempotent: the @@ -1983,13 +1983,13 @@ export function createAppStore(opts?: CreateAppStoreOptions): AppStore { } }, - queueMessage(text: string): void { + queueMessage(text: string, images?: readonly ImageInput[]): void { // Only offered while generating (Composer switches to `chat.queue` // when `status === "running"`), so a draft (never generating) never // reaches here. `chat.queue` auto-starts a turn if idle, so even a race // (turn sealed between the status read and the send) is safe — the // server starts a fresh turn with the message as its opening prompt. - activeChat.queueMessage(text); + activeChat.queueMessage(text, images); }, cancelQueuedMessage(messageId: string): void { diff --git a/src/app/views/ComposerPane.svelte b/src/app/views/ComposerPane.svelte index dcd5b47..ac95598 100644 --- a/src/app/views/ComposerPane.svelte +++ b/src/app/views/ComposerPane.svelte @@ -27,7 +27,7 @@ <Composer onSend={(text, images) => store.send(text, images)} - onQueue={(text) => store.queueMessage(text)} + onQueue={(text, images) => store.queueMessage(text, images)} onStop={() => store.stopGeneration()} contextSize={store.activeChat.currentContextSize} contextWindow={store.modelInfo[store.activeModel]?.contextWindow} |
