···13113114. Milestone N: Status Bar (Editor HUD) *wb-N*
132132================================================================================
133133134134-Goal:
135135-Add a bottom status bar that surfaces the "always-useful" editor telemetry:
136136-cursor position, zoom, active tool/mode, selection summary, and persistence
137137-state - with a clean core → UI boundary.
138138-139139-Design inspirations:
140140-- Zoom controls commonly expose percentage + "zoom to fit/selection" shortcuts.
141141-Svelte integration detail:
142142-- Use $effect to subscribe/unsubscribe to external sources (runs client-side;
143143- cleanup function runs on re-run/unmount).
144144-145145-------------------------------------------------------------------------------
146146-N1. Define the StatusBar view model (core, pure TS)
147147-------------------------------------------------------------------------------
148148-149149-/packages/core/src/ui/statusbar.ts
150150-[x] Define StatusBarVM (single object the UI renders):
151151- - cursorWorld: { x, y } " world coords (always)
152152- - cursorScreen: { x, y }? " optional dev-only
153153- - zoomPct: number " e.g. 100, 67, 250
154154- - toolId: ToolId " select/rect/pen/...
155155- - mode: string " 'idle'|'dragging'|'panning'|'text-edit'
156156- - selection:
157157- - count: number
158158- - kind?: string " optional: 'rect', 'mixed', etc.
159159- - bounds?: { w, h } " optional v1
160160- - snap:
161161- - enabled: boolean
162162- - gridSize?: number " if grid enabled
163163- - angleStepDeg?: number " if angle snapping exists
164164- - persistence:
165165- - backend: 'indexeddb'
166166- - state: 'saved'|'saving'|'error'
167167- - lastSavedAt?: number " epoch ms
168168- - pendingWrites?: number " queue depth if you batch writes
169169- - errorMsg?: string
170170-171171-Notes:
172172-- This VM is intentionally READ-ONLY and derived from existing editor state,
173173- input state, and persistence sink state.
174174-175175-(DoD):
176176-- StatusBarVM compiles and is stable enough to render even before UI exists.
177177-178178-------------------------------------------------------------------------------
179179-N2. Provide selectors / derivations for StatusBarVM
180180-------------------------------------------------------------------------------
181181-182182-/packages/core/src/ui/statusbar.ts
183183-[x] Implement pure functions:
184184- - getZoomPct(state) -> number
185185- - getToolId(state) -> ToolId
186186- - getSelectionSummary(state) -> { count, kind?, bounds? }
187187- - getSnapSummary(state) -> snap summary (default safe values)
188188-189189-Cursor position source:
190190-[x] Define a minimal CursorState in core (NOT persisted):
191191- - cursorWorld: Vec2
192192- - cursorScreen?: Vec2
193193- - lastMoveAt: number
194194-195195-[x] Add updateCursor(world, screen?) action + reducer handler (or direct setter)
196196- that ONLY touches CursorState (no history command, no persistence).
197197-198198-(DoD):
199199-- You can compute StatusBarVM from (EditorState + CursorState + PersistState).
200200-201201-------------------------------------------------------------------------------
202202-N3. Wire cursor updates from pointer movement (apps/web)
203203-------------------------------------------------------------------------------
204204-205205-/apps/web/src/lib/input.ts
206206-[x] On pointermove (or mousemove when not captured):
207207- - compute world coords using camera.screenToWorld
208208- - dispatch updateCursor(world, screen)
209209-210210-Performance:
211211-[x] Throttle cursor updates:
212212- - v0: requestAnimationFrame coalescing (only publish latest per frame)
213213- - avoid flooding render/history/persistence
214214-215215-(DoD):
216216-- Cursor world coordinates update smoothly while moving the mouse.
217217-218218-------------------------------------------------------------------------------
219219-N4. Add persistence status signals (Dexie + persistence sink integration)
220220-------------------------------------------------------------------------------
221221-222222-Goal:
223223-Expose persistence state without touching the history system (Milestone L is
224224-done; persistence is already hooked to history in Milestone M).
225225-226226-/apps/web/src/lib/status.ts
227227-[x] Extend your persistence sink (from Milestone M) to expose a small status:
228228- - pendingWrites counter (increment on enqueue, decrement on commit)
229229- - lastSavedAt timestamp (set on successful commit)
230230- - lastError (set on failed commit)
231231-[x] Use Dexie liveQuery to observe the current board’s updatedAt from IndexedDB
232232- and reflect it in the UI (helps confirm persisted state across tabs).
233233-234234-(DoD):
235235-- Status bar can show: "Saving…" when pendingWrites > 0, and "Saved" with time
236236- when pendingWrites reaches 0.
237237-238238-------------------------------------------------------------------------------
239239-N5. Implement StatusBar.svelte using runes
240240-------------------------------------------------------------------------------
241241-242242-/apps/web/src/lib/components/StatusBar.svelte
243243-[ ] Render left → right (suggested):
244244- - Tool + mode
245245- - Cursor: X,Y (world)
246246- - Selection summary
247247- - Snap/grid summary
248248- - Zoom %
249249- - Save state ("Saved 3s ago" / "Saving…" / "Error")
250250-251251-[ ] Consume state via runes:
252252- - keep a local $state(snapshot) for EditorState
253253- - keep a local $state(cursor) for CursorState
254254- - keep a local $state(persist) for PersistStatus
255255-256256-Subscriptions:
257257-[ ] Use $effect to subscribe to any external streams and return cleanup
258258- unsubscribe.
259259-260260-Formatting:
261261-[ ] Cursor formatting:
262262- - v0: integers
263263- - v1: configurable precision (e.g. 0.1 units when zoomed in)
264264-265265-(DoD):
266266-- Status bar is visible, updates live, and never causes noticeable jank.
267267-268268-------------------------------------------------------------------------------
269269-N6. Interactions (small, high-value)
270270-------------------------------------------------------------------------------
271271-272272-Zoom control:
273273-[ ] Clicking zoomPct opens a tiny menu:
274274- - 50%, 100%, 200%
275275- - Zoom to fit
276276- - Zoom to selection
277277-(Inspiration: zoom/view options + shortcuts in Figma/FigJam.)
278278-279279-Snap toggles:
280280-[ ] Add quick toggles (optional v0, recommended v1):
281281- - snap enabled
282282- - grid enabled
283283-284284-(DoD):
285285-- Zoom is discoverable and controllable from the status bar.
286286-287287-------------------------------------------------------------------------------
288288-N7. Tests
289289-------------------------------------------------------------------------------
290290-291291-Core unit tests (/packages/core/test/statusbar.test.ts):
292292-[ ] getZoomPct returns expected values from camera zoom
293293-[ ] selection summary is correct (0, 1, many)
294294-[ ] snap summary defaults safe when features disabled
295295-296296-Web integration tests (optional v0):
297297-[ ] cursor update throttling: 100 pointermoves in a tick results in <= 1 state
298298- publication per frame (if you implement rAF coalescing)
299299-300300-Persistence tests (web):
301301-[ ] pendingWrites transitions: 0 -> N -> 0 yields state 'saving' then 'saved'
302302-[ ] error sets 'error' state and preserves lastSavedAt
303303-304304-------------------------------------------------------------------------------
305305-Definition of Done
306306-------------------------------------------------------------------------------
307307-308308-- Status bar shows:
309309- - cursor world position
310310- - zoom percentage
311311- - active tool/mode
312312- - selection count
313313- - persistence state (Saved/Saving/Error + lastSavedAt)
314314-- Cursor updates are throttled and do not spam history or persistence.
315315-- UI subscriptions use $effect with cleanup.
134134+The HUD is now powered end-to-end via a `StatusBarVM` + cursor store, a web
135135+persistence/snap manager, and `StatusBar.svelte` with zoom menu and snap/grid
136136+toggles backed by unit/integration tests for selectors, cursor throttling,
137137+persistence transitions, and Canvas wiring.
316138317139================================================================================
31814015. Milestone O: Export (PNG/SVG) *wb-O*