Skip to content

feat(media): preview PDFs in the library grid and viewer - #19

Merged
flyingwebie merged 4 commits into
mainfrom
feat/media-pdf-preview
Nov 3, 2026
Merged

flyingwebie merged 4 commits into
mainfrom
feat/media-pdf-preview

Conversation

@flyingwebie

Copy link
Copy Markdown
Owner

Summary

PDFs in the media library showed a folder icon in the grid and only a Download card in the viewer. This change previews them in both places. PDFs are still served as downloads (inline: false), so they never render on the admin origin. The library fetches the bytes and draws pages with pdf.js, which parses in a Web Worker, runs no document JavaScript, and has XFA disabled.

  • Viewer: every page is stacked in a scroll area and painted lazily as it nears the viewport. The page count and a Download button are shown. A file pdf.js cannot parse falls back to the existing download card.
  • Grid: PDF tiles show their first page. The thumbnail is rendered when the tile nears the viewport, at most two PDFs are parsed at once, and results are cached per content version, so Replace refreshes the thumbnail.
  • Other documents: Word files and similar now show a file icon instead of a folder icon.
  • Bundle: pdf.js and its worker load on first use only and are not in the admin's initial bundle.

This branch also carries two earlier commits that are stacked under it: collab delivery receipts (AI tool writes now report what the server actually received) and a shared runtime build cache in the publisher (each distinct script set is bundled once per publish). Both are described in docs/features/site-shell.md and docs/features/publisher.md.

Verification

  • bun run build
  • bun test (7,702 tests, 0 failing)
  • bun run lint
  • Docker/deployment check, if relevant (not run; no deployment change)

Manual check: opened a PDF in the grid and viewer in a browser against a local development database. The cover page showed as the thumbnail and all pages showed in the viewer. Download worked.

Checklist

  • Tests cover behavior changes. Unit tests cover the PDF helpers (MIME detection and content-key invalidation on replace or resize); the viewer and thumbnail are covered by the manual check above, not by automated component tests.
  • Docs were updated when behavior, config, deployment, or public surfaces changed (docs/features/media.md, docs/features/publisher.md, docs/features/site-shell.md, docs/features/mcp-connectors.md).
  • No compatibility shim was added for old pre-release behavior.
  • No secrets, local databases, uploads, or generated artifacts are included.

flyingwebie and others added 3 commits November 3, 2026 15:26
AI tools relayed through the open editor (MCP / instatic CLI and the
in-editor agent) reported success for writes the editor had refused or the
server never received. A refused write leaves the store unchanged, so
site_write_code_asset answered "updated" with the previous file's hash and
page replaces "succeeded" without sticking.

- CollabProvider.flush(): a ping sent after the last edit is a delivery
  receipt, because the relay answers each connection's frames in order.
- collabWriteWatch ledger: transport refusals and data-losing server resets
  during a tool call turn its result into an actionable error.
- runDeliveredSiteWrite wraps every site mutation tool: wait for the gate,
  run, then require no incident and a confirmed receipt.
- Heartbeat: a late ping tick (busy or throttled main thread) waits one
  second before ruling, so a big page render no longer flips a healthy
  editor to "Offline — reconnecting".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ublish

Publish ran esbuild per page, and each run first wrote every site script
(including large disabled ones reachable through dynamic imports) to a temp
workspace. Pages that load the same scripts produce byte-identical bundles,
since outputs import each other relatively, so the build is now shared via
createSiteRuntimeBuildCache() and only re-addressed under each page's
version path.

Measured on the 22-page dev site carrying the live site's scripts: runtime
builds 653 ms -> 27 ms, full publish ~2.0 s -> ~0.9 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PDFs showed a folder icon in the grid and only a Download card in the
viewer. They stay served as downloads (inline: false), so the library now
fetches the bytes and draws pages with pdf.js instead: a lazily painted,
page-by-page viewer with page count and Download, and a first-page
thumbnail on grid tiles (rendered near the viewport, two at a time, cached
per content version so Replace refreshes it). pdf.js runs in a worker with
no document scripting and XFA off, and loads only when a PDF is shown.
Non-PDF documents now show a file icon instead of a folder icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@flyingwebie
flyingwebie merged commit bb2be8c into main Nov 3, 2026
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.

1 participant