summaryrefslogtreecommitdiffhomepage
path: root/src/app
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-10-21 03:23:35 +0900
committerAdam Malczewski <[email protected]>2026-10-21 03:23:35 +0900
commit550ade33285d1f39a909733e02a6b27211464df2 (patch)
tree365672bb62798b2b64d44a18b237f1f34394073f /src/app
parent0c0f366b3820136c9877f80005939011c30b2de4 (diff)
downloaddispatch-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.ts47
-rw-r--r--src/app/store.svelte.ts6
-rw-r--r--src/app/views/ComposerPane.svelte2
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}