diff --git a/src/components/ai-toolbar.tsx b/src/components/ai-toolbar.tsx index 5b01bfd..711ac3d 100644 --- a/src/components/ai-toolbar.tsx +++ b/src/components/ai-toolbar.tsx @@ -349,6 +349,8 @@ export const AIToolbar = ({ e.key === "A" && e.shiftKey && (e.metaKey || e.ctrlKey) && + !(e.metaKey && e.ctrlKey) && + !e.altKey && !aiLoading ) { e.preventDefault(); diff --git a/tests/ai-toolbar.test.ts b/tests/ai-toolbar.test.ts index 85215a6..7d59572 100644 --- a/tests/ai-toolbar.test.ts +++ b/tests/ai-toolbar.test.ts @@ -600,6 +600,36 @@ describe("AI toolbar paste capture", () => { expect(imageTriggerOpen).toHaveBeenCalledTimes(1); }); + test("Shift+Mod+A ignores Alt-modified submissions", async () => { + const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary(); + const event = createTextareaKeydownEvent({ + key: "A", + ctrlKey: true, + shiftKey: true, + altKey: true, + }); + + textareaProps.onKeyDown?.(event); + + expect(event.preventDefault).not.toHaveBeenCalled(); + expect(imageTriggerOpen).not.toHaveBeenCalled(); + }); + + test("Shift+Mod+A ignores combined Ctrl+Meta modifiers", async () => { + const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary(); + const event = createTextareaKeydownEvent({ + key: "A", + ctrlKey: true, + metaKey: true, + shiftKey: true, + }); + + textareaProps.onKeyDown?.(event); + + expect(event.preventDefault).not.toHaveBeenCalled(); + expect(imageTriggerOpen).not.toHaveBeenCalled(); + }); + test("Shift+Mod+A stays disabled while generation is in progress", async () => { const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary({ aiLoading: true, @@ -693,6 +723,31 @@ describe("AI toolbar paste capture", () => { expect(onImagesSelect.mock.calls[0]?.[0][0]?.type).toBe("image/png"); }); + test("Cmd+V fallback forwards clipboard images for non-editable targets", async () => { + const onImagesSelect = mock(); + const clipboardRead = mock(async () => [ + { + types: ["image/png"], + getType: async () => new Blob(["image-bytes"], { type: "image/png" }), + }, + ]); + + (globalThis as { navigator?: Navigator }).navigator = { + clipboard: { read: clipboardRead }, + } as Navigator; + + await renderToolbar({ onImagesSelect }); + + const handleDocumentKeydown = getDocumentListener("keydown"); + + await handleDocumentKeydown( + createDocumentKeydownEvent({ metaKey: true }), + ); + + expect(clipboardRead).toHaveBeenCalledTimes(1); + expect(onImagesSelect).toHaveBeenCalledTimes(1); + }); + test("Ctrl+Meta+V does not trigger the async clipboard fallback", async () => { const onImagesSelect = mock(); const clipboardRead = mock(async () => []); @@ -857,6 +912,9 @@ describe("AI toolbar paste capture", () => { expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.name).toBe( asyncOnImagesSelect.mock.calls[0]?.[0][0]?.name, ); + expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.size).toBe( + asyncOnImagesSelect.mock.calls[0]?.[0][0]?.size, + ); expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified).toBe( asyncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified, );