Skip to content

Render PDF file previews instead of "Preview not available" - #1979

Merged
ymichael merged 3 commits into
get-bb:mainfrom
jsilets:pdf-file-previews
Aug 20, 2026
Merged

Render PDF file previews instead of "Preview not available"#1979
ymichael merged 3 commits into
get-bb:mainfrom
jsilets:pdf-file-previews

Conversation

@jsilets

@jsilets jsilets commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What was wrong

buildFilePreview tests the mime type in this order: image types, known
text types, a UTF-8 fallback, then video types. application/pdf matches
none of them, so every PDF returns kind unsupported and the panel shows
Preview not available for application/pdf. The content routes already
serve PDFs as application/pdf with the raw bytes; the gap was frontend
only.

What changed

A pdf preview kind, rendered by framing the preview URL. Chromium hands an
application/pdf response to its built-in viewer, so this needs no PDF
library and no new dependency.

  • apps/app/src/lib/file-preview.ts: a pdf kind for application/pdf,
    tested before the UTF-8 fallback. A PDF with no compressed streams is all
    printable ASCII, and the fallback would render its markers as source text.
  • apps/app/src/components/secondary-panel/FilePreview.tsx: a pdf state
    and a FilePreviewPdf iframe. The frame sets no sandbox: a sandboxed
    frame blocks the viewer's own scripts and the load fails with
    ERR_BLOCKED_BY_CLIENT (verified in Electron 41 with this app's exact
    webPreferences). This is not the exposure the HTML preview sandbox
    guards against; Chromium gives the bytes to the viewer instead of parsing
    them as a document, so the file never executes in the app origin.
  • apps/app/src/components/secondary-panel/ThreadStorageFilePreview.tsx:
    routes the new kind, same shape as the image and video branches.
  • apps/app/src/hooks/queries/environment-queries.ts: PDFs join images and
    video in the data: URL rewrite, since the workspace surface reads
    previews from a JSON route rather than a byte stream.

All four preview surfaces render PDFs: host files, project files, thread
storage, and workspace files. No wire changes
(HOST_DAEMON_PROTOCOL_VERSION unchanged), no CLI or doc surfaces.

How you verified

Tests (fail before, pass after):

  • file-preview.test.ts: builds a pdf preview for application/pdf.
  • file-preview.test.ts: keeps an all-ASCII PDF a PDF instead of falling
    back to text.
  • FilePreview.test.tsx: frames the preview with no sandbox attribute.

turbo typecheck and turbo lint --filter=@bb/app clean (lint warnings
unchanged from base). Full --filter=@bb/app suite: 405 files, 3107 passed.

Manually rendered the new Ladle story in Electron 41: the viewer loads with
its toolbar, page rail, zoom, download, and print controls inside the normal
preview header.

image

Fixes #1985

AGENT GENERATED: by Claude Opus 5 and Claude Fable 5

@jsilets

jsilets commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main and addressed the review feedback in #1985 (comment): the classifier moved to packages/client-core, apps/mobile maps the new pdf kind to unsupported, and the data: URL branch of buildEnvironmentFilePreview now has a test. Full-repo typecheck passes. Details in the issue thread.

@ymichael
ymichael force-pushed the pdf-file-previews branch 2 times, most recently from 650c28c to 0bccce9 Compare August 20, 2026 21:14
jsilets and others added 3 commits August 20, 2026 14:22
buildFilePreview branched on image, known text mime types, a UTF-8
fallback, then video, so application/pdf fell through to the unsupported
return and the panel showed "Preview not available for application/pdf."
The content routes already serve PDFs as application/pdf, so only the
frontend was missing.

Add a pdf preview kind that frames the preview URL. Chromium hands an
application/pdf response to its own viewer, so this needs no PDF library.
The frame takes no sandbox attribute: a sandboxed frame blocks the
viewer's scripts and the load fails with ERR_BLOCKED_BY_CLIENT.

The pdf check sits ahead of the UTF-8 fallback because a PDF with no
compressed streams is all printable ASCII and would otherwise decode as
source text.

The mobile app maps the new kind to its unsupported state, so its
behavior is unchanged; a native PDF view there is follow-up work.
@ymichael
ymichael merged commit 5154a94 into get-bb:main Aug 20, 2026
12 checks passed
ymichael added a commit that referenced this pull request Aug 20, 2026
## What was wrong

PR #1979 correctly persisted the selected host for a project-backed file
in the file-opener params, and the PDF plugin read that source. Root
Compose did not: it reconstructed `experimental_Original`, copy-path
resolution, and Open in editor from the currently selected
environment/host plus `fileOpenerOwner`. Because Root Compose
intentionally preserves file tabs across context changes, a PDF opened
on host A could keep rendering from host A while those native surfaces
routed to host or environment B.

## What changed

`useThreadFileTabs` now parses the persisted opener params once and
treats them as the routed file identity. `fileOpenerOwner` remains
responsible only for native presentation state that is not part of the
plugin source, such as line ranges and workspace status.

Root Compose gives a persisted project-backed opener source precedence
over the live compose selection. The same resolved routing now drives
the host-rendered project preview, source root and absolute copy path,
and editor-open context. An explicit persisted host stays explicit; an
omitted host continues to mean primary-host routing. Native file tabs
still follow the existing live compose selection.

This is direct data flow from the existing persisted source, so it does
not add a second host field or change the tab/server/daemon contract.
`HOST_DAEMON_PROTOCOL_VERSION` is unchanged. There are no CLI, guide,
SDK, or documentation changes because the documented plugin contract
already requires this behavior.

## How you verified

Regression coverage demonstrates the old behavior and the fix:

- With the old live-context route restored, `pnpm exec turbo run test
--filter=@bb/app -- src/views/RootComposeView.test.ts` failed the two
new routing cases: explicit host A resolved as selected environment/host
B, and omitted-primary routing resolved as host B (67 passed, 2 failed).
- `pnpm exec turbo run test --filter=@bb/app --
src/components/secondary-panel/useThreadFileTabs.test.ts
src/views/RootComposeView.test.ts` — 88 passed. This includes
persisted-tab reload under a different project/environment,
explicit-host retention, primary-host omission, and unchanged native-tab
routing.
- `pnpm exec turbo run test --filter=@bb/app --force` — 404 files
passed; 3,126 tests passed and 3 skipped.
- `pnpm exec turbo run typecheck build --filter=@bb/app` — passed.
- `pnpm exec turbo run lint --filter=@bb/app` — passed with 0 errors and
156 existing warnings outside the changed lines.

After rebasing onto `origin/main` at
`a77a391a48003f364fd4d26bd8cde95e051eee3b`, the focused opener/Root
Compose/shared-panel set passed (3 files, 101 tests), and the Turbo app
typecheck and build passed.

Related to #1979.

> AGENT GENERATED: by GPT-5.6-Sol
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PDF file previews show "Preview not available for application/pdf"

2 participants