From e528589e9cf3ca5616e0b0cf4ee3f2df087a0273 Mon Sep 17 00:00:00 2001 From: DEls Date: Sun, 30 Aug 2026 13:09:06 +0200 Subject: [PATCH] feat(chat): save a single answer as PDF via the print dialog Hides app chrome via a 'printing-answer' class on body, leaves only the .print-target visible. The class is removed in a finally so a cancelled print dialog can't leave the app invisible. Co-Authored-By: Claude Opus 5 --- src/app/dashboard/chat/chat-view.tsx | 23 ++++++++++++++++++++++- src/app/globals.css | 28 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/app/dashboard/chat/chat-view.tsx b/src/app/dashboard/chat/chat-view.tsx index edde2ea..d56d01f 100644 --- a/src/app/dashboard/chat/chat-view.tsx +++ b/src/app/dashboard/chat/chat-view.tsx @@ -32,6 +32,21 @@ export function ChatView() { const bottomRef = React.useRef(null); const inputRef = React.useRef(null); + /** Print one answer. The class is removed in a finally so a cancelled + print dialog cannot leave the app invisible. */ + const printAnswer = React.useCallback((index: number) => { + const el = document.getElementById(`answer-${index}`); + if (!el) return; + el.classList.add("print-target"); + document.body.classList.add("printing-answer"); + try { + window.print(); + } finally { + document.body.classList.remove("printing-answer"); + el.classList.remove("print-target"); + } + }, []); + // Pending photos attached to the next user turn. Declared before any // handler that reads it; useCallback closes over this state at the // time the handler is created, so the state must be in scope by then. @@ -337,7 +352,7 @@ export function ChatView() { } return ( -
+
{t.text ? ( {t.text} ) : ( @@ -364,6 +379,12 @@ export function ChatView() { {toolLabel(name)} ))} +
)}
diff --git a/src/app/globals.css b/src/app/globals.css index 7a2380b..3d9de71 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -216,3 +216,31 @@ background-size: 32px 32px; } } + +/* Printing an answer: hide the app chrome and let the answer fill the page. Triggered by adding .printing-answer to . */ +@media print { + body.printing-answer * { + visibility: hidden; + } + body.printing-answer .print-target, + body.printing-answer .print-target * { + visibility: visible; + } + body.printing-answer .print-target { + position: absolute; + inset: 0; + padding: 0; + border: 0; + max-width: none; + } + body.printing-answer .print-hide { + display: none !important; + } + /* Keep table rows intact across page breaks. */ + body.printing-answer table { + page-break-inside: auto; + } + body.printing-answer tr { + page-break-inside: avoid; + } +}