AGENTS.md@packages/client/src/components/preview · git:20260824.881ffda · 2026-08-24 · sha256 d834609fd2ca5a2d

AGENTS.md@packages/client/src/components/preview git:20260824.881ffdaA

Immutable. This exact content is served forever at /api/v1/blob/d834609fd2ca5a2d.

# DOX — packages/client/src/components/preview

Files in this directory. One row per source file.

| File | Purpose |
|------|---------|
| `AsciiDocPreview.tsx` | Fetches `/api/file/render`, renders sanitized HTML via `dangerouslySetInnerHTML`. Wraps in `.asciidoc-body` for scoped CSS. Server enforces `safe:"secure"` mode. See change: render-file-previews. |
| `AudioPreview.tsx` | `<audio src={rawUrl} controls preload=metadata>`. Server Range support drives scrubber. Shared by editor-pane audio tab. See change: improve-content-editor. |
| `DocxPreview.tsx` | Fetches `/api/file/render`; branches on the discriminated `data.mode`. `mode:"pdf"` → lazy `PdfPreview` with `srcUrl=renderedPdfUrl(target)` (`/api/file/rendered-pdf`). `mode:"html"` → sanitized HTML via `dangerouslySetInnerHTML` (mirror of AsciiDocPreview) + `TruncationBanner` when `truncated`. `{success:false}` → `FallbackPreview` (design D5). Loading/error states. See change: render-office-previews. |
| `EmlPreview.tsx` | `.eml` (email) renderer for `RendererKind "email"`. Fetches `/api/file/eml`, shows collapsed-expandable header block (escaped text, never `dangerouslySetInnerHTML`), body in `<iframe sandbox="" srcDoc>` (OPAQUE origin — stricter than `HtmlPreview`, no `allow-same-origin`). Attachments dispatch by mimeType → inline `PdfPreview`/`ImagePreview` via `blob:` URLs (`srcUrl` prop, lazy-fetched on expand) or download-only row. `cid:` inline images resolved to `blob:` URLs before srcDoc build. Remote content blocked by default; "Load remote content" re-requests `?allowRemote=1`. See change: add-eml-preview. |
| `FallbackPreview.tsx` | File targets: "We can't preview this file. [Download]" → `/api/file/raw`. URL targets: "[Open in new tab]" with `rel="noopener noreferrer"`. See change: render-file-previews. |
| `FilePreviewContext.tsx` | Owns hoisted file-preview open-state above chat message list. → see `FilePreviewContext.tsx.AGENTS.md` |
| `FilePreviewOverlay.tsx` | Modal overlay. Reads file via `/api/file?cwd&path`. Routes by ext: `.md`/`.mdx` → `MarkdownContent`; image →… → see `FilePreviewOverlay.tsx.AGENTS.md` |
| `FrontmatterProperties.tsx` | Obsidian-style YAML frontmatter Properties panel. Exports `extractFrontmatter` (leading `---` block parser),… → see `FrontmatterProperties.tsx.AGENTS.md` |
| `HtmlPreview.tsx` | Local .html files only. Fetches `/api/file/raw` as text, renders in `<iframe sandbox="allow-same-origin" srcDoc={html}>`. No `allow-scripts`/`allow-forms`/`allow-top-navigation`/`allow-popups`. Browser-native isolation; no DOMPurify. See change: render-file-previews. |
| `ImageLightbox.tsx` | Portal full-screen image overlay with `useZoomPan` (wheel/pointer/touch zoom+pan, 0.25–10x). Escape via shared escape-stack (`useEscapeDismiss` — closes only the lightbox, not a dialog/overlay beneath it); backdrop click via document listener. Exports `ImageLightbox`. See change: fix-stacked-escape-closes-layers. |
| `ImagePreview.tsx` | Two variants via `variant` prop. `inline` (default): `<img>` capped `max-h-[40vh]` for cards. `full`: full-tab pan/zoom + zoom controls (`useZoomPan`, ex editor-pane `ImageViewer`) — editor image tab uses this. Optional `src` prop overrides the image URL (e.g. a `blob:` URL for an EML attachment). See change: render-file-previews. See change: improve-content-editor. See change: add-eml-preview. |
| `ImagePreviewStrip.tsx` | Pasted-image thumbnail grid + error banner shared by `CommandInput` and `ExploreDialog`. → see `ImagePreviewStrip.tsx.AGENTS.md` |
| `is-external-href.ts` | Import-free leaf: `isExternalHref(href)` — cross-origin true, fragment/relative/same-origin false, unparseable true (fail-safe). Extracted from `MarkdownContent.tsx`. See change: cleanup-import-cycles. |
| `MarkdownContent.tsx` | ReactMarkdown renderer (chat/thinking/flow agent detail/READMEs/previews); KaTeX math + `pi-asset:` image… → see `MarkdownContent.tsx.AGENTS.md` No longer imports `FileLink`; renders it via injected `context.fileLink`, the gate for linkification at all 3 sites (`p`, `li`, inline `code`). `isExternalHref` moved out. See change: cleanup-import-cycles. |
| `MarkdownPreview.tsx` | Fetches `/api/file` (text content) + renders via `<MarkdownContent>`; passes `imageBase={{cwd, dir: absOf(cwd, dirname(path))}}` so local images resolve. Loading/error states. See change: render-file-previews. See change: fix-markdown-preview-relative-images. |
| `MarkdownPreviewView.tsx` | Markdown preview pane with header (optional back `onBack?` + `backLabel?` tooltip override + title + `MarkdownSearch`) and optional tab bar (`PreviewTab[]`). Root `flex-1 flex flex-col min-h-0` — conforms to the flush-Dialog child contract, needs no host wrapper. `closeInset` REMOVED (flush hosts render no ✕). See change: fix-flush-dialog-scroll-and-close-collision. → see `MarkdownPreviewView.tsx.AGENTS.md` |
| `MarkdownSearch.tsx` | In-markdown fuzzy search overlay. `fuse.js` index over text blocks; exact-substring match first, fuzzy… → see `MarkdownSearch.tsx.AGENTS.md` |
| `MermaidBlock.tsx` | Renders fenced mermaid blocks via lazy `mermaid.render()`. → see `MermaidBlock.tsx.AGENTS.md` |
| `PdfPreview.tsx` | Dynamic `import("pdfjs-dist")` keeps pdfjs out of main bundle. Worker resolved via Vite `?url` import (`pdfjs-dist/build/pdf.worker.min.mjs?url`) — no manual copy to `public/`. Renders via pdfjs `PDFViewer` (`pdfjs-dist/web/pdf_viewer.mjs`): virtualized continuous scroll + text layer (`textLayerMode:2` → selection + ctrl-F find), no hand-rolled paging. `mountViewer` MUST `await loadPdfJs()` BEFORE `loadViewer()` — `pdf_viewer.mjs` destructures `globalThis.pdfjsLib` at module-eval time (no static import of pdf.mjs); loading both via `Promise.all` races the chunks and throws "Cannot destructure property 'AbortException' of 'globalThis.pdfjsLib'". Destroys doc on unmount. Optional `srcUrl` prop overrides `rawUrl(target)` (DocxPreview passes `/api/file/rendered-pdf`; EmlPreview passes a `blob:` URL for a PDF attachment). See change: render-file-previews. See change: render-office-previews. See change: add-eml-preview. See change: pdf-preview-continuous-scroll. See change: fix-pdf-viewer-load-order. |
| `PptxPreview.tsx` | `.pptx` (slide-deck) renderer for `RendererKind "pptx"`. On-demand (design P2): initial state shows a `data-testid="pptx-render-slides"` "Render slides" button, does NOT auto-fetch. Activate → `fetch(renderUrl)` (`/api/file/render`, converts pptx→PDF via document-converter, cached); `{mode:"pdf"}` → lazy `PdfPreview` with `srcUrl=renderedPdfUrl(target)`. `{success:false}` (incl. engine-absent — no in-process fallback for pptx) → `FallbackPreview`. Loading state "Rendering slides…". See change: render-pptx-preview. |
| `PreviewCard.tsx` | Inline chat-message card for `/view` rows. Header: icon (per renderer kind) + target label + `⤢ expand`… → see `PreviewCard.tsx.AGENTS.md` |
| `PreviewOverlayView.tsx` | Full-viewport shell for overlay routes `/folder/:cwd/view?path=` + `/pi-view?url=`. → see `PreviewOverlayView.tsx.AGENTS.md` |
| `raw-url.ts` | Helpers `rawUrl(target)` → `/api/file/raw?cwd=&path=`, `renderUrl(target)` → `/api/file/render?...`, `readTextUrl(target)` → `/api/file?...`, `renderedPdfUrl(target)` → `/api/file/rendered-pdf?...`, `sheetUrl(target,limit?)` → `/api/file/sheet?...&limit=`, `emlUrl(target, allowRemote?)` → `/api/file/eml?...`, `emlAttachmentUrl(target, index)` → `/api/file/eml-attachment?...`. All consume `getApiBase()`. See change: render-file-previews. See change: render-office-previews. See change: add-eml-preview. |
| `resolve-local-image-src.ts` | Browser-safe (no `node:path`) `resolveLocalImageSrc(src, {cwd,dir})` → `rawUrl` for LOCAL srcs (scheme-absent, non-`//`, non-`#`; relative joins `dir` + collapses `.`/`..`, POSIX-absolute verbatim), else `null`; + `dirname(path)`. Traversal defense is server `/api/file/raw`, not this helper. See change: fix-markdown-preview-relative-images. |
| `SpreadsheetPreview.tsx` | Fetches `/api/file/sheet`; frozen-header row/col grid, sheet tabs for multi-sheet workbooks (active-sheet switch is client-only within loaded data), `TruncationBanner` ("Showing first N of M rows · sheet i of k" + csv charset pill). `{success:false}` → `FallbackPreview`. Loading/error states. See change: render-office-previews. |
| `TruncationBanner.tsx` | Shared bounded-preview banner (design D3) for DocxPreview (image trim) + SpreadsheetPreview (row trim). Props `{message, downloadHref, charset?}`; renders message + optional charset pill + `/api/file/raw` download. `data-testid="truncation-banner"`. See change: render-office-previews. |
| `VideoPreview.tsx` | `<video src={rawUrl} controls preload="metadata">` 16:9 aspect. Server's Range support drives seek bar. See change: render-file-previews. |
| `YouTubePreview.tsx` | Exports `extractYouTubeId(url): string\|null` handling `youtu.be/<id>`, `youtube.com/watch?v=`, `/embed/`, `/v/`, `/shorts/`. Renders `<iframe src="https://www.youtube.com/embed/<id>">` 16:9 + `allowFullScreen`. See change: render-file-previews. |
| `ZoomControls.tsx` | Re-export shim. Re-exports `ZoomControls` from `@blackbelt-technology/pi-dashboard-client-utils/ZoomControls`. Symbol migrated in change `complete-flows-plugin-migration` (Layer 0). |