Skip to content

feat(M6): PDF + image import#10

Merged
pedrobritx merged 1 commit into
mainfrom
claude/zealous-mendel-LB4tu
May 29, 2026
Merged

feat(M6): PDF + image import#10
pedrobritx merged 1 commit into
mainfrom
claude/zealous-mendel-LB4tu

Conversation

@pedrobritx
Copy link
Copy Markdown
Owner

Milestone 6 — PDF + image import

Makes the scaffolded asset pipeline real: you can now import images and PDFs onto a board and they render as transformable objects that sync to collaborators. Replaces the placeholder AssetRefRenderer (dashed box) with a real Konva <Image> renderer.

What's included

  • Two entry points: an Import button in the tool palette (hidden <input accept="image/*,application/pdf" multiple>) and drag-and-drop files onto the canvas (dropped at the cursor's world position).
  • Images → one YAssetRef (scaled to fit), placed at the drop point / viewport centre.
  • PDFsone YAssetRef per page laid out in a grid (all pages share a single asset, discriminated by pageIndex). Pages rasterize lazily on render via pdf.js.
  • Storage: bytes live only in Supabase Storage (per the chosen design) in a public board-assets bucket at the deterministic path <boardId>/<assetId>; a best-effort row is also written to the existing assets table. Because the path is deterministic and the shape carries pageIndex (null = image), the renderer needs nothing beyond the YAssetRef + the Supabase client — no extra metadata channel. Import is disabled in local-only mode (no Supabase) with a tooltip.
  • Asset loader (assets/assetLoader.ts): resolves a paintable bitmap per (assetId, pageIndex) with in-memory LRU caches (bitmaps, blobs, parsed PDF docs) + in-flight dedupe; downloads bytes from Storage and rasterizes PDF pages with pdf.js.
  • pdf.js is added to @notux/canvas + @notux/web and loaded via dynamic import(), so the ~365 KB core + ~1.4 MB worker land in separate lazy chunks (verified in the build output), not the initial bundle.
  • A whole import is committed in one transaction, so a single undo reverts it. M5 transform/rotate/z-order/lock/opacity apply unchanged (asset is already a box kind in ShapesLayer).
  • New migration 0002_assets_storage.sql: board-assets bucket + free-for-all storage.objects RLS mirroring the public-board model in 0001_init.sql.

Verification

  • pnpm -r typecheck
  • pnpm -F @notux/web build ✅ (emits separate pdf-*.js + pdf.worker.min-*.mjs chunks; no dynamic/static import warning)
  • Manual testing requires a configured Supabase project (Storage + the new migration). Suggested checks: import an image (button + drag-drop at non-1 zoom/pan), import a multi-page PDF → grid of pages, reload (re-downloads from Storage), two-browser realtime (peer downloads from Storage), undo reverts a whole import, and oversized/unsupported/corrupt-file error tiles.

Notes / trade-offs

  • "All pages in a grid" rasterizes every page when shapes are on-screen; memory is bounded by LRU + a 2048px raster clamp. Fine for typical decks; heavy for very large PDFs by design.
  • Asset metadata is intentionally not mirrored into the Yjs doc — rendering derives everything from the shape + deterministic storage path, avoiding a metadata/realtime race.

🤖 Draft PR — opened automatically after pushing.

https://claude.ai/code/session_011TMH7eYBb5huhrHTzQf9Kb


Generated by Claude Code

Add the ability to import images and PDFs onto a board, replacing the
placeholder asset renderer with a real one. Entry points: an Import button
in the tool palette and drag-and-drop onto the canvas.

- Images become a single YAssetRef; PDFs become one YAssetRef per page laid
  out in a grid (all pages sharing one asset, discriminated by pageIndex).
- Bytes are stored in a public Supabase Storage bucket (board-assets) at the
  deterministic path <boardId>/<assetId>; a best-effort row is written to the
  existing assets table. Import is disabled when Supabase isn't configured.
- New asset loader resolves a paintable bitmap per (assetId, pageIndex) with
  in-memory LRU caches, downloading bytes from Storage and lazily rasterizing
  PDF pages with pdf.js (loaded as a separate chunk via dynamic import).
- AssetRefRenderer paints a Konva Image (loading/error placeholders), so M5
  transform/rotate/z-order/lock/opacity apply unchanged. A whole import is one
  transaction, so a single undo reverts it.
- Add 0002_assets_storage.sql: board-assets bucket + free-for-all storage RLS
  mirroring the public-board model.

https://claude.ai/code/session_011TMH7eYBb5huhrHTzQf9Kb
@pedrobritx pedrobritx marked this pull request as ready for review May 29, 2026 16:08
@pedrobritx pedrobritx merged commit 8612a7a into main May 29, 2026
4 checks passed
@pedrobritx pedrobritx deleted the claude/zealous-mendel-LB4tu branch May 29, 2026 16:08
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.

2 participants