diff --git a/.ideai/tickets/155/carnet.md b/.ideai/tickets/155/carnet.md index eec4da5..a096ea7 100644 --- a/.ideai/tickets/155/carnet.md +++ b/.ideai/tickets/155/carnet.md @@ -1,11 +1,17 @@ --- issueRef: "#155" -version: 6 +version: 7 updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} -updatedAt: 1786034876664 +updatedAt: 1786035611373 --- ## 2026-08-06 — Reopened from user validation on AppImage - User reports that clipboard paste in the custom CLI chat still does not work in practice. - Suspected historical scope was around #154/#155; this cycle treats #155 as the user-visible clipboard UX bug and keeps the #154 attachment foundation dependency in view. - Scope for this cycle: verify whether the regression is frontend paste interception, attachment import, backend routing, or AppImage/runtime mismatch; restore end-to-end paste of clipboard image/file into the custom chat composer. - Main reopened the ticket and assigned frontend/backend ownership before architecture arbitration. + +## 2026-08-06 — Implementation + QA +- DevBackend verified that no backend contract change was required: clipboard bytes already flow through the #154 attachment pipeline (`contentBase64`/`sourceKind=clipboard` -> Tauri command -> attachment store import). +- DevFrontend expanded paste handling to cover images exposed through `clipboardData.files`, while keeping the structured attachment send path (`attachments`, no prompt hack). +- Unit/integration tests passed across frontend, application, infrastructure, and app-tauri DTO layers. +- QA verdict: partial. The logic is green in targeted tests, but this environment cannot validate a native OS/Tauri/AppImage clipboard paste e2e. Manual AppImage verification remains the closure point for the user-visible bug. diff --git a/.ideai/tickets/155/issue.md b/.ideai/tickets/155/issue.md index 752cb5a..cde0154 100644 --- a/.ideai/tickets/155/issue.md +++ b/.ideai/tickets/155/issue.md @@ -11,7 +11,7 @@ attachments: [] createdBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} createdAt: 1785964740363 -updatedAt: 1786034876664 -version: 6 +updatedAt: 1786035611373 +version: 7 --- La custom CLI ne permet toujours pas de coller une image/fichier depuis le presse-papiers dans le chat. Après un test utilisateur du 2026-08-06, le collage attendu ne crée aucun attachment utilisable dans le composer. Il faut rétablir le support de collage clipboard dans le chat custom, sur la base du pipeline d'attachments, puis valider end-to-end sur l'AppImage réelle. \ No newline at end of file diff --git a/frontend/src/features/agents/CustomAgentChatView.test.tsx b/frontend/src/features/agents/CustomAgentChatView.test.tsx index fecd2df..10f6d90 100644 --- a/frontend/src/features/agents/CustomAgentChatView.test.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.test.tsx @@ -1080,6 +1080,80 @@ describe("CustomAgentChatView", () => { expect(screen.getByText("Fichier: paste-image.png")).toBeTruthy(); }); + it("accepts pasted images exposed only through clipboardData.files", async () => { + const agent = { + launchAgentChat: vi.fn(), + reattachAgentChat: vi.fn(async (sessionId: string) => ({ + sessionId, + scrollback: [], + })), + sendAgentChat: vi.fn(async () => {}), + cancelAgentChat: vi.fn(async () => {}), + closeAgentChat: vi.fn(async () => {}), + }; + + render( + null) }, + } as unknown as Gateways} + > + + , + ); + + await waitFor(() => + expect(agent.reattachAgentChat).toHaveBeenCalledWith( + "chat-session-1", + expect.any(Function), + ), + ); + + const file = new File(["ignored"], "files-only.png", { type: "image/png" }); + Object.defineProperty(file, "arrayBuffer", { + value: vi.fn(async () => new Uint8Array([4, 5, 6]).buffer), + }); + fireEvent.paste(screen.getByLabelText(/message CLI custom/), { + clipboardData: { + items: [], + files: [file], + getData: () => "", + }, + }); + + await screen.findByText("Fichier joint: files-only.png"); + fireEvent.click(screen.getByRole("button", { name: "Envoyer" })); + + await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledTimes(1)); + expect(agent.sendAgentChat).toHaveBeenCalledWith( + "chat-session-1", + "", + expect.any(Function), + { + attachments: [ + { + filename: "files-only.png", + contentBase64: "BAUG", + mime: "image/png", + sourceKind: "clipboard", + }, + ], + }, + ); + }); + it("keeps the chat shell bounded with a scrollable message area and fixed composer", async () => { const agent = { launchAgentChat: vi.fn(() => new Promise(() => {})), diff --git a/frontend/src/features/agents/CustomAgentChatView.tsx b/frontend/src/features/agents/CustomAgentChatView.tsx index e1de0aa..d641d9d 100644 --- a/frontend/src/features/agents/CustomAgentChatView.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.tsx @@ -268,10 +268,24 @@ async function clipboardImageToAttachment(file: File): Promise } function clipboardImageFiles(event: ClipboardEvent): File[] { - return Array.from(event.clipboardData.items) + const clipboardItems = event.clipboardData.items + ? Array.from(event.clipboardData.items) + : []; + const clipboardFiles = event.clipboardData.files + ? Array.from(event.clipboardData.files) + : []; + const fromItems = clipboardItems .filter((item) => item.kind === "file" && item.type.startsWith("image/")) .map((item) => item.getAsFile()) .filter((file): file is File => Boolean(file)); + const fromFiles = clipboardFiles.filter((file) => file.type.startsWith("image/")); + const seen = new Set(); + return [...fromItems, ...fromFiles].filter((file) => { + const key = `${file.name}\u0000${file.type}\u0000${file.size}\u0000${file.lastModified}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); } function slashCommandPrefix(draft: string): string | null {