## Context The Planner uses Yjs for collaborative CRDT state with four shared types: `waypoints` (Y.Array), `noGoAreas` (Y.Array), `routeData` (Y.Map), and `notes` (Y.Text). All mutations happen through Yjs transactions. The editor supports multiple simultaneous users via y-websocket. Yjs provides a built-in `UndoManager` class that tracks changes to specified shared types, scoped by transaction origin, with configurable capture timeouts for grouping related changes. ## Goals / Non-Goals **Goals:** - Undo/redo for waypoint operations (add, delete, reorder, drag-move) - Undo/redo for no-go area operations (add, delete) - Undo/redo for notes text edits - Keyboard shortcuts that work naturally (Ctrl+Z, Ctrl+Shift+Z, Ctrl+Y) - Topbar buttons with visual disabled state - Collaborative-safe: only undo your own changes, never other users' - Drag operations grouped as a single undo step **Non-Goals:** - Undo for routeData (derived data, not user input) - Undo for route options / color mode changes - Persistent undo history across page reloads - Customizable undo stack depth ## Decisions ### D1: Single UndoManager tracking waypoints, noGoAreas, and notes Create one `Y.UndoManager` instance in `use-yjs.ts`, tracking all three user-editable shared types: `waypoints`, `noGoAreas`, and `notes`. ```ts const undoManager = new Y.UndoManager( [waypoints, noGoAreas, notes], { captureTimeout: 500, trackedOrigins: new Set(["local"]), } ); ``` A single UndoManager means undo walks back through all user actions in order, regardless of which shared type was modified. This matches user expectations: "undo my last thing" not "undo my last waypoint thing." The UndoManager is created inside the `useYjs` hook and exposed on the `YjsState` interface so all components can access it. ### D2: Transaction origins for local vs. remote scoping All local mutations must use the origin `"local"` so UndoManager can distinguish them from remote changes arriving via y-websocket: ```ts doc.transact(() => { waypoints.push([yMap]); }, "local"); ``` Current code has some `doc.transact()` calls without an origin and some direct mutations (e.g., `yjs.waypoints.push([yMap])`) outside a transaction. All mutation sites need to be wrapped in `doc.transact(() => { ... }, "local")`. Mutation sites to update: - `PlannerMap.tsx`: `addWaypoint`, `insertWaypointAtSegment`, `moveWaypoint`, `deleteWaypoint` - `WaypointSidebar.tsx`: `deleteWaypoint`, `moveWaypoint` - `NoGoAreaLayer.tsx`: no-go area creation (`pm:create` handler), no-go area deletion (contextmenu handler) - `NotesPanel.tsx`: `handleInput` (already uses `doc.transact`, needs origin) - `use-yjs.ts`: initial waypoint seeding (use `"init"` origin, not `"local"`, so it's not undoable) ### D3: Keyboard shortcuts Register a global `keydown` listener (in a `useUndoShortcuts` hook or similar) that handles: - **Ctrl+Z** (or Cmd+Z on macOS): `undoManager.undo()` - **Ctrl+Shift+Z** or **Ctrl+Y** (Cmd+Shift+Z on macOS): `undoManager.redo()` The listener must suppress when the active element is an ``, `