Skip to content

M8: PDF export, named snapshots, and per-user undo#13

Merged
pedrobritx merged 1 commit into
mainfrom
claude/notux-m8-pdf-snapshots-undo-pUJdY
May 31, 2026
Merged

M8: PDF export, named snapshots, and per-user undo#13
pedrobritx merged 1 commit into
mainfrom
claude/notux-m8-pdf-snapshots-undo-pUJdY

Conversation

@pedrobritx
Copy link
Copy Markdown
Owner

Summary

Implements NotUX M8 — the three features the Home screen already advertises ("Sign in for named snapshots & PDF export"):

  1. Per-user, board-wide undo
  2. Named snapshots (save / list / restore)
  3. PDF export (current page or all pages)

No SQL migration required — the existing boards/snapshots policies already support owner-claim + named inserts once a boards row exists.

Per-user undo

  • New LOCAL_ORIGIN symbol in @notux/sync, threaded through every user-initiated transaction: shapeStore (addShape/updateShape/deleteShape/deleteShapes/transact) and pageList (add/remove/rename/move). Seed-page creation and page-map auto-creation stay untracked so a fresh user's first Ctrl+Z can't delete the only page.
  • useUndoManager rewritten from a per-page manager to a single board-wide one tracking getMap("pages") + getArray("pageList") with trackedOrigins = {LOCAL_ORIGIN}. History now survives page switches, and remote peers' edits / IndexedDB hydration (different origins) are never captured — undo is genuinely per-user in multiplayer. captureTimeout: 300 coalesces drag bursts.

Named snapshots

  • ensureBoardOwnership claims an unowned boards row on load (signed in) so the owner-gated named-snapshot insert passes RLS.
  • snapshots.ts: encodeSnapshot (Y.encodeStateAsUpdate) and restoreSnapshot, which rebuilds pages + pageList in one LOCAL_ORIGIN transaction — so a restore broadcasts to peers and is undoable in a single step. pageStore's observer already handles the active page disappearing.
  • snapshotsApi.ts stores ydoc as a PostgREST \x hex bytea (no server-side decode RPC needed).
  • SnapshotsPanel UI (save label / list with relative time / restore-with-confirm), wired into the app-menu File section. Owner-gated; clear messaging in local-only / signed-out / non-owner states.

PDF export

  • Adds jspdf (dynamically imported, code-split into its own chunk).
  • renderPageToCanvas mounts a detached react-konva Stage reusing ShapesLayer (pixel-parity, no renderer reimplementation), cropped to the page's content bounding box + padding. Asset bitmaps are pre-warmed and readiness is polled (one Konva Image per asset shape) with a 3s timeout fallback.
  • exportBoardToPdf assembles one PDF page per non-empty board page (empty pages skipped; one blank page if all empty). Menu offers a current page / all pages chooser. Works in local-only mode.

Verification

  • pnpm -r typecheck ✅ and pnpm build ✅ (jspdf isolated in its own dynamic chunk).
  • Manual (suggested): two tabs → confirm undo only reverts the local tab's edits and survives page switches; sign in → save/restore a snapshot (reverts in both tabs, undoable in one step); export current/all pages to PDF cropped to content with assets rendered.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J9ed2Wi8rXZZz1bHFDwAVa


Generated by Claude Code

Per-user undo:
- Add LOCAL_ORIGIN symbol in @notux/sync; thread it through every
  user-initiated transaction (shapeStore mutations + transact, pageList
  add/remove/rename/move). Seed page and page-map creation stay untracked.
- Rewrite useUndoManager to a single board-wide manager tracking the
  "pages" map and "pageList" array with trackedOrigins={LOCAL_ORIGIN},
  so history survives page switches and never captures peers' edits or
  IndexedDB hydration. CanvasStage now calls useUndoManager().

Named snapshots:
- ensureBoardOwnership claims an unowned boards row on load so the
  owner-gated named-snapshot RLS passes.
- snapshots.ts encodes the doc (Y.encodeStateAsUpdate) and restores by
  rebuilding pages + pageList in one LOCAL_ORIGIN transaction (syncs to
  peers, undoable in one step).
- snapshotsApi stores ydoc as PostgREST \\x hex bytea; SnapshotsPanel UI
  for save/list/restore, wired into the app menu (owner-gated).

PDF export:
- Add jspdf (dynamically imported). renderPageToCanvas mounts a detached
  react-konva Stage reusing ShapesLayer, cropped to the content bounding
  box, pre-warming asset bitmaps and waiting for readiness with a timeout.
- exportBoardToPdf assembles one PDF page per non-empty board page; menu
  offers a current/all chooser.

Add download/history icons. No SQL migration required.
@pedrobritx pedrobritx marked this pull request as ready for review May 31, 2026 21:40
@pedrobritx pedrobritx merged commit e371f97 into main May 31, 2026
4 checks passed
@pedrobritx pedrobritx deleted the claude/notux-m8-pdf-snapshots-undo-pUJdY branch May 31, 2026 21:41
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