The document detail page picks a preview surface based on the MIME type.
**PDFs** render through pdfjs (the same engine Firefox uses internally) — consistent across Chrome, Safari, Firefox, and Edge, with Kodori-branded toolbar chrome instead of variable browser-native UI. Features in the toolbar:
- **Page navigation** — prev / next arrows or type a page number into the box. - **Zoom** — + / − buttons; values from 50% to 300% in 20% steps. - **Find in document** — Cmd-F (macOS) / Ctrl-F (Windows) or the magnifier button. Walks every page's extracted text, shows per-hit page snippets ("p.7 …signed by both parties on the…"), and jumps to the matching page on click. 200-hit cap; refine the query when you hit it. - **Download** — pulls the original bytes via the same /api/doc/<id>/download endpoint.
Selection and copy work exactly like a native viewer — pdfjs renders text on a transparent layer over the canvas. The text you copy is the text the find bar searched.
**Images, audio, video** play inline natively. Text-shaped files (.txt, .md, .json, .yaml, .csv, .xml, .svg, .eml) render their decoded contents.
**TIFF, BMP, HEIC, HEIF** — Chrome / Firefox / Edge can't render these formats inline in `<img>` tags (Safari is the only mainstream exception), so the preview endpoint converts them server-side. `/api/doc/[id]/preview` decodes the source bytes via `sharp` (libvips + libheif) and returns PNG inline at up to 2048px on the long edge. Multi-page TIFFs (legal fax-scan format is typically 5-50 pages) preview the first page only; the agent and search still see every page (D296 wraps multi-page TIFFs into a PDF for Claude vision), and operators who need to view all pages can click Download to get the source TIFF. The `X-Kodori-Preview-Converted-From` response header surfaces the original MIME so operators tracing "why is this PNG when I uploaded a TIFF?" find the answer in browser devtools without code spelunking. Source bytes stay untouched in storage; only the rendered preview is converted.
**AI summary** above the preview — every document with extracted text gets a Haiku-generated 3-sentence summary written during the auto-classify pass: first sentence = what it is + parties / matter / project; second = the substantive content (the deal, question, dispute, figure); third = the operative date / deadline / next action. Surfaces as an ochre-tinted callout above the preview, and below the document name on every /dashboard recent-docs card so operators see "what is this?" at a glance. Capped at 600 chars; null when the text is too sparse.
**Regenerate the summary.** When a doc gets re-uploaded, classification context shifts, or the original summary missed a key clause, click "Regenerate" beside the AI-summary callout on /doc/[id]. Kodori re-fires the auto-classify Inngest function which recomputes the summary alongside sensitivity / collection / keywords / docType in the same Haiku call. Async — the button shows "Refreshing…" and the page refreshes after 8 seconds to pick up the new summary. Open to anyone with read access (the gate is LLM cost, not data sensitivity). Audit-logged via `document.auto-classify-requested` with `reason: manual-regenerate` so the chain captures who refreshed which summary and when. Auto-classify's global concurrency cap of 10 means button-click floods queue rather than overrunning the LLM provider — no separate rate-limit needed.
**Word documents (.docx)** now render inline with formatting via mammoth — paragraphs, lists, tables, basic styling preserved. Loads in a sandboxed iframe; CSP blocks external resources + scripts so a malicious document can't exfiltrate or execute anything.
**Excel workbooks (.xlsx, .xls, .ods)** render the first 10 sheets as HTML tables, one per sheet. Cell values + layout preserved; formulas stripped (the displayed value is what shows).
**PowerPoint (.pptx)** still falls back to extracted text in v1 — no good HTML renderer for slides server-side yet. The original is one click away via Download. PowerPoint inline rendering (via LibreOffice headless render to PDF + pdfjs viewer) is on the roadmap.
**Render cap.** 25 MB per document — beyond that the render endpoint refuses with a 413 (Download stays available). Mammoth + SheetJS are lazy-loaded server-side, so deployments that never preview Office documents don't pay the parse-tree cost.
For anything we can't preview natively, the page falls back to a "Download to view" CTA.