canvas/PRINCIPLES.md
2026-03-11 18:42:08 -07:00

2.9 KiB

@blinksgg/canvas — Core Principles

These principles guide every design decision in the canvas library. When in doubt, refer here. When principles conflict, earlier ones win.

1. Touch-first, mouse-compatible

Design for fingers and stylus first, then ensure mouse works well. Every interaction has device-aware thresholds — 44px hit targets for fingers, 3px drag thresholds for pencil, palm rejection when stylus is active. Mouse is not the default; it is one of three equal input sources.

"Pencil draws, fingers navigate, mouse does both." — input-classifier.ts

2. Headless core, optional UI

All state lives in Jotai atoms with zero React dependency. The core/ layer is a pure state machine: atoms in, atoms out. Components in components/ are one possible UI — consumers can build entirely custom UIs by importing only @blinksgg/canvas/core and @blinksgg/canvas/hooks.

3. Local-first, sync-optional

Every operation (drag, resize, copy, paste, undo, redo) works without a network connection. The clipboard is in-memory. Undo/redo uses delta-based snapshots in local state. The database layer (db/) is an optional add-on with an adapter interface — not a requirement.

4. Events are data, actions are configurable

Canvas interactions produce typed events (NodeDoubleClick, BackgroundLongPress, etc.). What happens in response is a mapping in a settings store, not hardcoded logic. Users remap actions via presets or the SettingsPanel. This separates "what happened" from "what to do about it."

5. Atoms over context

Prefer Jotai atoms over React context for shared state. Atoms are composable, testable without React, and don't cause provider waterfall re-renders. Use React context only for non-reactive configuration (style themes, callback refs).

6. Fully controllable headless API

Every canvas operation must be executable from the core/ layer without React. If a hook can do it, an atom or pure function in core/ can do it too. The React hooks are convenience wrappers, not the only path. External tools, tests, and non-React integrations should have full control via store.set(atom, value).

7. Performance by default

Virtualization is on by default (only render visible nodes). The React Compiler handles memoization automatically. Edge paths only recompute when their connected nodes move. Delta-based history is O(1) for moves instead of O(N) full-graph snapshots.

8. Composition over configuration

Provide small, focused hooks (useNodeDrag, useNodeResize, useTapGesture) that compose into complex behavior. The Canvas component is a convenient composition — not the only way to use the library. Advanced users compose hooks directly.

9. Backend-agnostic persistence

The CanvasStorageAdapter interface defines CRUD + subscriptions. Implement it for any backend. The simplest path is callback props (onNodePersist) with no adapter at all.