M8: PDF export, named snapshots, and per-user undo#13
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements NotUX M8 — the three features the Home screen already advertises ("Sign in for named snapshots & PDF export"):
No SQL migration required — the existing
boards/snapshotspolicies already support owner-claim + named inserts once aboardsrow exists.Per-user undo
LOCAL_ORIGINsymbol in@notux/sync, threaded through every user-initiated transaction:shapeStore(addShape/updateShape/deleteShape/deleteShapes/transact) andpageList(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.useUndoManagerrewritten from a per-page manager to a single board-wide one trackinggetMap("pages")+getArray("pageList")withtrackedOrigins = {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: 300coalesces drag bursts.Named snapshots
ensureBoardOwnershipclaims an unownedboardsrow on load (signed in) so the owner-gated named-snapshot insert passes RLS.snapshots.ts:encodeSnapshot(Y.encodeStateAsUpdate) andrestoreSnapshot, which rebuildspages+pageListin oneLOCAL_ORIGINtransaction — so a restore broadcasts to peers and is undoable in a single step.pageStore's observer already handles the active page disappearing.snapshotsApi.tsstoresydocas a PostgREST\xhexbytea(no server-side decode RPC needed).SnapshotsPanelUI (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
jspdf(dynamically imported, code-split into its own chunk).renderPageToCanvasmounts a detached react-konva Stage reusingShapesLayer(pixel-parity, no renderer reimplementation), cropped to the page's content bounding box + padding. Asset bitmaps are pre-warmed and readiness is polled (one KonvaImageper asset shape) with a 3s timeout fallback.exportBoardToPdfassembles 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✅ andpnpm build✅ (jspdf isolated in its own dynamic chunk).🤖 Generated with Claude Code
https://claude.ai/code/session_01J9ed2Wi8rXZZz1bHFDwAVa
Generated by Claude Code