# Changelog

All notable changes to `@blinksgg/canvas` will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.0.0] - 2026-03-11 — "React Compiler"

### BREAKING
- Peer dependencies now require `react` / `react-dom` **^19.2.0** (was ^19.0.0)
- Removed all manual `useCallback` / `useMemo` wrappers — React Compiler auto-optimizes

### Changed
- Integrated **React Compiler** (`babel-plugin-react-compiler`) into Vitest pipeline
- Added `@vitejs/plugin-react` for Babel transform support
- Removed `useCallback` from `useGestureSystem.ts` (4), `GestureProvider.tsx` (1), `useGestureResolver.ts` (1)
- Removed `useMemo` from `useGestureSystem.ts` (1), `GestureProvider.tsx` (1), `keyboard.ts` (1)
- Replaced `useMemo(() => nextOwnerId++, [])` with `useState(() => nextOwnerId++)` in `GestureProvider.tsx`

### Tests
- All 761 passing tests remain green — zero regressions

## [2.5.0] - 2026-03-11 — "Hook Integration"

### Changed
- Exported `easeInOutCubic` from `useAnimatedLayout.ts` for independent testing

### Added
- **`animated-layout.test.ts`** — 9 tests: easeInOutCubic boundary values, monotonicity, range
- **`useActionExecutor-hook.test.ts`** — 6 tests: renderHook integration, return shape
- **`usePlugin-hook.test.ts`** — 5 tests: register/unregister lifecycle, re-render stability
- **`useLayout-hook.test.ts`** — 4 tests: useLayout, useGetGraphBounds, useFitToBounds

### Tests
- 24 new tests across 4 new test files (first React hook integration tests)
- Total passing: 473 across 54 test files

## [2.4.0] - 2026-03-11 — "Deep Logic"

### Added
- **`drag-state-machine.test.ts`** — 11 tests: buildDragPositions, computeDragUpdates, isDragPrevented
- **`action-types-enums.test.ts`** — 9 tests: ActionCategory enum, BuiltInActionId uniqueness
- **`event-types-enums.test.ts`** — 10 tests: CanvasEventType, EVENT_TYPE_INFO completeness
- **`gesture-rules-types.test.ts`** — 6 tests: GestureDescriptor/Pattern/Rule shapes

### Tests
- 36 new tests across 4 new test files
- Total passing: 449 across 50 test files

## [2.3.0] - 2026-03-11 — "Edge Coverage"

### Added
- **`actions-node.test.ts`** — 8 tests: selection + node action registration verification
- **`actions-viewport.test.ts`** — 6 tests: viewport + history action registration
- **`modifier-helpers.test.ts`** — 9 tests: isRepeatBlocked, getSelectedNodeIds, resolveFocusableNodeId, getCurrentSubject
- **`pointer-bindings-v2.test.ts`** — 6 tests: array integrity, unique IDs, pattern types
- **`plugin-types.test.ts`** — 5 tests: PluginError construction, CanvasPlugin interface shape

### Tests
- 34 new tests across 5 new test files
- Total passing: 413 across 46 test files

## [2.2.0] - 2026-03-11 — "Total Coverage"

### Changed
- Split `built-in-actions.ts` (342L) into `actions-node.ts` and `actions-viewport.ts`; original slimmed to 44L barrel

### Added
- **`keyboard-bindings.test.ts`** — 6 tests: array integrity, unique IDs, standard shortcuts
- **`gesture-classification.test.ts`** — 3 tests: findNearestNode contract, empty graph
- **`viewport-commands.test.ts`** — 7 tests: command shapes, registration, descriptions
- **`serialization-commands.test.ts`** — 5 tests: export/import shapes, registration
- **`store-atoms.test.ts`** — 13 tests: default values, isCommandActive, currentInput, commandProgress
- **`command-executor.test.ts`** — 4 tests: collectInput rejection, cancelCommand

### Tests
- 38 new tests across 6 new test files
- Total passing: 379 across 41 test files

## [2.1.0] - 2026-03-11 — "Store & Adapter"

### Added
- **`command-line-store.test.ts`** — 11 tests covering open/close, search, select command, provide/skip/goBack input, error state
- **`storage-adapter.test.ts`** — 10 tests covering InMemoryStorageAdapter CRUD for nodes/edges, batch ops, error handling

### Tests
- 21 new tests across 2 new test files
- Total passing: 341 across 35 test files

## [2.0.0] - 2026-03-11 — "Clean Architecture"

### ⚠ BREAKING CHANGES
- Removed deprecated re-exports `addEdgeToLocalGraphAtom` and `removeEdgeFromLocalGraphAtom` from `graph-mutations.ts` — import from `graph-mutations-edges` instead
- Renamed `gesturesV2` → `gestures` namespace export in main barrel

### Changed
- `CANVAS_VERSION` bumped to `'2.0.0'`
- Updated 4 source files and 16 test files to import edge atoms from `graph-mutations-edges`

### Added
- **`action-executor.test.ts`** — 8 tests covering createActionContext, createActionContextFromTouchEvent
- **`settings-store.test.ts`** — 12 tests covering default state, setEventMapping, resetSettings, togglePanel, virtualization, hasUnsavedChanges

### Tests
- 20 new tests across 2 new test files
- Total passing: 320 across 33 test files

## [1.98.0] - 2026-03-11 — "Commands & Dispatch"

### Added
- **`dispatcher.test.ts`** — 10 tests covering registerAction/unregisterAction, dispatch simple and phase handlers, none action
- **`clipboard-commands.test.ts`** — 10 tests covering copy/cut/paste/duplicate/deleteSelected definitions and registration
- **`group-commands.test.ts`** — 10 tests covering group/ungroup/collapse/expand definitions and registration

### Tests
- 30 new tests across 3 new test files
- Total passing: 300 across 31 test files

## [1.95.0] - 2026-03-11 — "Final Polish"

### Added
- **`port-types.test.ts`** — 16 tests covering calculatePortPosition, getNodePorts, canPortAcceptConnection, arePortsCompatible
- **`command-registry.test.ts`** — 11 tests covering register/unregister, get/has, search, aliases, sorting
- **`keyboard-contexts.test.ts`** — 7 tests covering unique IDs, priorities, arrow key and nudge bindings
- **`canvas-styles.test.ts`** — 7 tests covering theme structure, mergeWithDefaults, light/dark differences

### Tests
- 44 new tests across 4 new test files
- Total passing: 270 across 28 test files

## [1.9.0] - 2026-03-11 — "Derived & Types"

### Added
- **`event-types.ts`** — CanvasEventType enum, EventTypeInfo, EVENT_TYPE_INFO lookup
- **`action-types.ts`** — ActionCategory enum, BuiltInActionId, ActionContext, ActionHelpers, ActionDefinition
- **`settings-state-types.ts`** — EventActionMapping, SettingsPreset, CanvasSettingsState, DEFAULT_MAPPINGS
- **`graph-derived-atoms.test.ts`** — 8 tests covering highestZIndex, nodeKeys, edgeKeys, reactivity
- **`settings-types.test.ts`** — 10 tests covering enum integrity, EVENT_TYPE_INFO coverage, action ID uniqueness
- **`canvas-serializer-validation.test.ts`** — 7 tests covering missing fields, wrong version, invalid nodes
- **`viewport-store-actions.test.ts`** — 8 tests covering zoom/pan defaults, reset, coordinate transforms

### Changed
- **`settings-types.ts`** — re-export barrel (419→~40L)

### Tests
- 33 new tests across 4 new test files
- Total passing: 226 across 24 test files

## [1.8.0] - 2026-03-11 — "Pipeline Polish"

### Added
- **`gesture-provider-utils.ts`** — isEditableTarget, setHeldKeyValue, applyHeldKeyDelta, getCurrentSubject, getSubjectPosition, buildGuardContext
- **`gesture-provider-utils.test.ts`** — 13 tests covering DOM detection, held-key immutability, key delta application
- **`timed-state-runner.test.ts`** — 8 tests covering state transitions, tap, double-tap, long-press, cancel, destroy
- **`contexts.test.ts`** — 5 tests covering PALM_REJECTION and ACTIVE_INTERACTION context priorities and integrity
- **`command-store.test.ts`** — 5 tests covering open/close command line, phase transitions

### Changed
- **`GestureProvider.tsx`** — delegates to `gesture-provider-utils` (347→~215L)

### Tests
- 31 new tests across 4 new test files
- Total passing: 193 across 20 test files

## [1.7.0] - 2026-03-11 — "Gesture Refine"

### Added
- **`keyboard-bindings.ts`** — 24 keyboard shortcut bindings extracted from DEFAULT_CONTEXT
- **`pointer-bindings.ts`** — 22 pointer/touch gesture bindings extracted from DEFAULT_CONTEXT
- **`minimap-utils.ts`** — computeGraphBounds, getMinimapTransform, minimapToWorld
- **`pointer-contexts.test.ts`** — 12 tests covering binding integrity, no duplicate IDs, pick mode contexts
- **`minimap-utils.test.ts`** — 8 tests covering bounds calculation, transform, world conversion
- **`canvas-serializer-roundtrip.test.ts`** — 9 tests covering export/import round-trip, viewport, offset, validation

### Changed
- **`pointer-contexts.ts`** — assembles DEFAULT_CONTEXT from imported arrays (346→~120L)
- **`Minimap.tsx`** — delegates to `minimap-utils` (310→~278L)

### Tests
- 30 new tests across 3 new test files
- Total passing: 162 across 16 test files

## [1.6.0] - 2026-03-11 — "Hook Coverage"

### Added
- **`useCanvasGraph.test.ts`** — 6 tests covering nodeKeys, edgeKeys, getNodeAttributes
- **`useCanvasSelection.test.ts`** — 8 tests covering select, deselect, toggle, add, remove, edge selection
- **`useCanvasViewport.test.ts`** — 7 tests covering zoom, pan, viewportRect, constants
- **`useCanvasHistory.test.ts`** — 6 tests covering recordSnapshot, undo, redo, clear, labels
- **`useVirtualization.test.ts`** — 5 tests covering metrics, enable/disable/toggle
- **`useCanvasSettings.test.ts`** — 8 tests covering mappings, presets, panel toggle, reset
- **`plugin-registry.test.ts`** — 12 tests covering register/unregister, dependencies, conflicts, lifecycle
- **`useDragStateMachine.ts`** — buildDragPositions, computeDragUpdates, isDragPrevented
- **`useDragConstraints.ts`** — snapToGrid, clampToBounds, applyDragConstraints
- **`modifier-helpers.ts`** — isRepeatBlocked, getSelectedNodeIds, getCurrentSubject, updateKeySubject
- **`gesture-classification.ts`** — findNearestNode, cycleFocus, navigateFocus, activateFocusedNode

### Changed
- **`useNodeDrag.ts`** — delegates to `useDragStateMachine` helpers (367→~310L)
- **`input-action-helpers.ts`** — delegates to `modifier-helpers` + `gesture-classification` (374→~210L)

### Tests
- 52 new tests across 7 new test files
- Total passing: 132 across 13 test files

## [1.5.0] - 2026-03-11 — "Test & Harden"

### Added
- **`graph-mutations.test.ts`** — 10 tests covering addNode, deleteNode, deleteEdge, loadGraphFromDb, drag lifecycle
- **`clipboard-store.test.ts`** — 10 tests covering copy, cut, paste, duplicate, edge remapping, guards
- **`group-store.test.ts`** — 12 tests covering collapse/expand, parent/child, circular guard, moveToGroup
- **`history-store.test.ts`** — 10 tests covering push, undo, redo, clear, labels, edge cases
- **`built-in-actions.test.ts`** — 14 tests covering registration, all 18 handler delegations, guards
- **`ContextMenuAction`** — standalone touch-friendly action row component
- **`ContextMenuDivider`** — section header/separator component
- **`ComboboxOption`** — option row with icon, label, description, highlight
- **`ComboboxSearch`** — search input with ARIA combobox role
- **`EdgePath`** — drag handle visual indicator (44px touch target)
- **`EdgeLabel`** — inline edge label positioning component

### Changed
- **`NodeContextMenu.tsx`** — delegates to `ContextMenuAction` + `ContextMenuDivider` (367→~310L)
- **`NodeTypeCombobox.tsx`** — delegates to `ComboboxOption` + `ComboboxSearch` (374→~310L)
- **`EdgeOverlay.tsx`** — delegates handle to `EdgePath` (349→~310L)

### Tests
- 57 new tests across 5 new test files
- Total passing: 97 across 8 test files

## [1.4.0] - 2026-03-11 — "Stable DB"

### Added
- **Adapter conformance test harness** — `runAdapterConformanceTests(name, factory)` validates any `CanvasStorageAdapter` implementation against the full CRUD + batch contract (15 tests)
- **Batch operations** — optional `createNodes`, `deleteNodes`, `createEdges`, `deleteEdges` on `CanvasStorageAdapter` with sequential fallback in `InMemoryStorageAdapter`
- **`PortHandle`** — standalone port handle component with drag, hover, and compatibility highlighting
- **`SettingsPresets`** — preset management component (apply, save-as, delete, reset)
- **`SettingsEventMap`** — event-to-action mapping table component
- **`CollectorInputPhase`** — progress/prompt display for command input collection
- **`CollectorSelectInput` / `CollectorBooleanInput`** — extracted select option and boolean input components with shared `ShortcutButton`

### Changed
- **`@blinksgg/canvas/db`** — promoted from **Beta → Stable**
- **`NodePorts.tsx`** — refactored from 406L → ~110L, delegates to `PortBar` + `PortHandle`
- **`CommandInputCollector.tsx`** — refactored from 397L → ~165L, delegates to `CollectorInputPhase` + `CollectorSelectInput`
- **`SettingsPanel.tsx`** — refactored from 392L → ~125L, delegates to `SettingsPresets` + `SettingsEventMap`
- **`storage-adapter.test.ts`** — refactored to use conformance harness; retains only atom-level tests

### Tests
- **`adapter-conformance.test.ts`** — 14 CRUD + 2 batch tests against `InMemoryStorageAdapter`
- **`storage-adapter.test.ts`** — 3 atom integration tests

## [1.3.0] - 2026-03-11 — "Serialize"

### Added
- **Canvas serialization** — `exportGraph(store)` / `importGraph(store, snapshot)` for full JSON snapshots of nodes, edges, positions, groups, and viewport state
- **`CanvasSnapshot`** type — portable JSON schema (version 1) with `SerializedNode`, `SerializedEdge`, `SerializedGroup`, viewport, and metadata fields
- **`validateSnapshot(data)`** — runtime schema validation with detailed error reporting
- **Import options** — `clearExisting` (default true), `offsetPosition` for paste-at-cursor, `remapIds` for UUID regeneration to avoid conflicts
- **Headless API integration** — `exportSnapshot()`, `importSnapshot()`, `validateSnapshot()` on `CanvasAPI`
- **Built-in commands** — `exportCanvas` (alias: `export`) copies graph JSON to clipboard; `importCanvas` (alias: `import`) reads, validates, and loads from clipboard
- **`createWheelHandler(config)`** — extracted wheel zoom logic into a standalone, testable pure function
- **`createPinchHandlers(config)`** — extracted pinch zoom logic (simultaneous pan, velocity sampling, inertia) into a standalone, testable pure function

### Changed
- **`useCanvasGestures.ts`** — refactored from 518 lines to ~370 lines by delegating wheel and pinch zoom to extracted modules
- **`gestures/index.ts`** — exports `createWheelHandler`, `WheelZoomConfig`, `createPinchHandlers`, `PinchZoomConfig`
- **`builtins/index.ts`** — registers `serialization-commands` alongside existing built-in command sets

### Tests
- **`canvas-serializer.test.ts`** — 18 tests: export (7), import (8), round-trip (1), validation (9)
- **`useWheelZoom.test.ts`** — 7 tests: zoom in/out, min/max clamp, guard conditions
- **`usePinchZoom.test.ts`** — 5 tests: start/update/end lifecycle, disabled state

## [1.2.0] - 2026-03-10 — "Plugin API"

### Added
- **Plugin system** — unified `CanvasPlugin` interface bundles node types, commands, actions, gesture contexts, and edge path calculators into a single declarative manifest
- **`plugin-types.ts`** — `CanvasPlugin`, `PluginContext`, `PluginRegistration`, `PluginError` class with typed error codes (`ALREADY_REGISTERED`, `NOT_FOUND`, `MISSING_DEPENDENCY`, `CONFLICT`, `LIFECYCLE_ERROR`)
- **`plugin-registry.ts`** — `registerPlugin()` / `unregisterPlugin()` with dependency resolution, cross-plugin conflict detection, atomic registration with rollback, and lifecycle hooks (`onRegister` → cleanup)
- **`edge-path-registry.ts`** — extensible custom edge path calculator registry; plugins can register new edge path types beyond the 8 built-ins
- **`usePlugin()` / `usePlugins()` hooks** — React integration for plugin lifecycle (register on mount, unregister on unmount, dependency-order cleanup)
- **`unregisterNodeType()`** — per-type removal for clean plugin teardown
- **2 new test suites** — `plugin-registry` (28 tests), `edge-path-registry` (8 tests)
  - Registration, duplicate detection, dependency resolution, conflict detection across all subsystems
  - Atomic rollback on lifecycle error, cleanup verification, query functions
  - Custom edge path precedence over built-ins, clear/unregister behavior

### Changed
- **`Canvas.renderNode` is now optional** — when omitted, the node type registry auto-resolves components by `node.dbData.node_type`, including those registered by plugins. Falls back to `FallbackNodeTypeComponent` for unknown types.
- **Barrel exports updated** — plugin types, registry, hooks, and edge path registry all exported from `core/`, `hooks/`, and `utils/` entry points

## [1.1.0] - 2026-03-10 — "Gesture Modular"

### Changed
- **Split `useRegisterInputActions.ts`** (528L → 259L): extracted `input-action-helpers.ts` (~370L) with all pure store helper functions (navigation, selection, mutation, escape logic)
- **Split `useCanvasGestures.ts`** (667L → 518L): extracted `useGuardContext.ts` (~70L) and `useInertia.ts` (~170L)
- **3 new modules** — all exported from `@blinksgg/canvas/gestures`:
  - `input-action-helpers.ts` — `findNearestNode`, `cycleFocus`, `nudgeSelection`, `deleteSelection`, `escapeInput`, etc.
  - `useGuardContext.ts` — guard context atom reads + stable ref for gesture callbacks
  - `useInertia.ts` — pan/zoom inertia engines, velocity samplers, pinch tracking

### Added
- **2 new test suites** — `input-action-helpers` (28 tests), `useInertia` (5 tests)
  - Navigation: `findNearestNode` in all 4 directions, axis preference, fallback behavior
  - Focus: `cycleFocus` forward/backward, `activateFocusedNode`
  - Mutations: `nudgeSelection`, `deleteSelection`, `selectAll`
  - Guards: `isRepeatBlocked`, `getCurrentSubject`, `clearSelectionState`
  - Zoom: `snapZoom` threshold behavior, constant validation

### Fixed
- **WeakMap for module-level caches** — `_prevUiNodes`, `_edgeCache`, `_positionCache` now use `WeakMap<object, ...>` keyed by Graph instance, preventing cross-store contamination in multi-Canvas setups
- **`clearAllPendingMutations()` on graph switch** — mutation queue is now cleared when switching graphs, preventing stale `inFlight` flags from blocking new mutations
- **`hasPendingMutations()` checks `queuedUiProperties`** — was only checking `queuedPosition`, missing queued UI property updates
- **`collectInput` rejects immediately** — was returning a never-resolving Promise, now throws with a descriptive error message
- **`startMutation()` count includes descendants** — was only counting selected nodes, not their group descendants, causing imbalanced `completeMutation()` calls
- **Timed-state config threading** — `onUp` now receives `config` parameter and uses `config.multiTapWindowMs` instead of hardcoded default
- **localStorage key namespaced** — `'canvas-settings'` → `'@blinksgg/canvas/settings'` to prevent cross-application collisions
- **Singleton keyboard routing documented** — `GestureProvider.tsx` now has a comment explaining the intentional global `activeOwnerId` pattern

---

## [1.0.0] - 2026-03-10 — "Stable Canvas"

First stable release. All APIs are now covered by semver guarantees.

### Added
- **API stability document** — `docs/api-stability.md` classifying all exports as Stable, Beta, or Experimental
- **Migration guide** — `docs/migration-v1.md` covering v0.x → v1.0 breaking changes
- **3 new hook test suites** — `useNodeDrag-atoms`, `useNodeResize-atoms`, `useForceLayout-atoms`
- **`peerDependenciesMeta`** — d3-force, @tanstack/react-query, @blocknote/* marked as optional

### Changed
- **Version bump** — 0.36.0 → 1.0.0
- **`onAction` callback type** — Now accepts `InputEvent` (pointer + keyboard union) instead of `GestureEvent` (pointer only). Use `event.kind === 'key'` guard to narrow.

### Highlights
- 72+ test suites, 219+ exported atoms, zero TODO/FIXME comments
- 8 entry points: root, core, hooks, components, commands, gestures, db, utils
- React 19 native with React Compiler auto-memoization
- Touch-first gesture pipeline with specificity-scored binding resolution
- Local-first with optional Supabase sync via storage adapter
- Full headless API for programmatic control without React

---

## [0.36.0] - 2026-03-09 — "Gesture Pipeline"

### Changed
- **Split `contexts.ts`** (614L → 100L): extracted `keyboard-contexts.ts` (~200L) + `pointer-contexts.ts` (~280L)

### Added
- **3 new test suites** — `gesture-specificity` (12), `gesture-mapper` (10), `gesture-dispatcher` (10)
  - Specificity scoring: type/subject/source/modifier/button/key matching
  - Mapper: context indexing, priority resolution, consumeInput, guards
  - Dispatcher: handler registry, phase routing, none/unknown handling

---

## [0.35.0] - 2026-03-09 — "Gesture Polish"

### Added
- **3 new test suites** — `gesture-inertia` (12), `timed-state` (10), `gesture-normalize` (4)
  - VelocitySampler, PanInertia, ZoomInertia fully tested
  - Timed state machine: tap/double-tap/triple-tap/long-press/cancel
  - Modifier extraction from pointer events

---

## [0.34.0] - 2026-03-09 — "Layout Hooks"

### Changed
- **Split `useNodeDrag.ts`** — extracted `DragMemo` + `UseNodeDragOptions` into `hooks/drag-types.ts` (~32L)

### Added
- **2 new test suites** — `gesture-rules-defaults` (8), `keyboard-shortcuts` (5)

---

## [0.33.0] - 2026-03-09 — "Settings & Config"

### Changed
- **Split `settings-store.ts`** — extracted presets + utility into `settings-presets.ts` (~85L)

### Added
- **1 new test suite** — `settings-presets` (9 tests: preset config, uniqueness, event coverage, utility)

---

## [0.32.0] - 2026-03-09 — "Core Stores"

### Changed
- **Split `history-store.ts`** — extracted pure delta functions into `history-actions.ts` (~160L):
  - `applyDelta`, `invertDelta`, `createSnapshot` now exported standalone

### Added
- **2 new test suites** — `history-actions` (14), `group-store-utils` (7)

---

## [0.31.0] - 2026-03-09 — "Command System"

### Changed
- **Split `commands/store.ts`** (343L) into 2 modules:
  - `store-atoms.ts` — core state + derived atoms (~95L)
  - `store.ts` — action atoms + helpers (~260L)

### Added
- **3 new test suites** — `commands-registry` (13), `commands-executor` (3), `commands-store` (15)
  - Total: 670 tests across 54 files

---

## [0.30.0] - 2026-03-09 — "Gesture Engine"

### Changed
- **Split `gesture-rules.ts`** (473L) into 3 focused modules:
  - `gesture-rules.ts` — scoring, resolver, index (~200L)
  - `gesture-rules-types.ts` — all type definitions (~117L)
  - `gesture-rules-defaults.ts` — default rules, labels, merge (~190L)

### Added
- **3 new test suites** — `sync-store` (14), `gesture-rule-store` (12), `gesture-configs` (6)
  - Total: 639 tests across 51 files

---

## [0.29.0] - 2026-03-09 — "Registry Split"

### Changed
- **Split `action-registry.ts`** (449L) into 2 modules:
  - `action-registry.ts` — registry CRUD + utilities (~120L)
  - `built-in-actions.ts` — all 20+ built-in action definitions (~340L)

### Added
- **4 new test suites** — `graph-mutations-edges` (10), `graph-mutations-advanced` (8), `debug` (8), `component-registry` (6)
  - Total: 607 tests across 48 files

---

## [0.28.0] - 2026-03-09 — "Modular Mutations"

### Changed
- **Split `graph-mutations.ts`** (524L) into 3 focused modules:
  - `graph-mutations.ts` — node CRUD, drag lifecycle, DB sync
  - `graph-mutations-edges.ts` — edge CRUD, animation, label editing
  - `graph-mutations-advanced.ts` — split/merge nodes, drop target

### Added
- **4 new test suites** — `layout` (11), `hit-test` (4), `action-executor` (8), `mutation-queue` (7)
  - Total: 575 tests across 44 files

---

## [0.27.0] - 2026-03-09 — "Deep Test"

### Added
- **4 new test suites** — `settings-store` (12), `edge-path-calculators` (12), `snap-store` (16), `locked-node-store` (10)
  - Total: 542 tests across 40 files
- **`history-types.ts`** — extracted `HistoryDelta`, `HistoryEntry`, `HistoryState`, `GraphSnapshot` from `history-store.ts`

---

## [0.26.0] - 2026-03-09 — "Improve Core"

### Added
- **Minimap edge drawing** — edges render as thin lines between node centers; new `edgeColor` prop
- **Fuzzy multi-token search** — split query by whitespace, all tokens must match (order-independent); exported `fuzzyMatch` helper
- **Edge search** — search now matches edge labels and types; `searchEdgeResultsAtom`, `searchEdgeResultCountAtom`, `searchTotalResultCountAtom`
- **Smart alignment guides** — node-to-node edge/center snapping; `findAlignmentGuides`, `alignmentGuidesAtom`, `AlignmentGuides` component
- **2 new test suites** — `alignment-guides.test.ts` (8 tests), 7 fuzzy tests added to `search-store.test.ts`
  - Total: 489 tests across 36 files

### Changed
- **Clipboard cut** — `cutToClipboardAtom` now actually deletes selected nodes (with undo support via `pushHistoryAtom`)
- **Edge search dimming** — `EdgeRenderer` keeps matched edges visible during search (checks `searchEdgeResultsAtom`)

---

## [0.25.0] - 2026-03-09 — "Test & Harden"

### Added
- **8 new test suites** (65 tests) — `graph-mutations`, `graph-derived`, `graph-position`, `action-registry`, `interaction-store`, `reduced-motion-store`, `external-keyboard-store`, `toast-store`
  - Total: 474 tests across 35 files

### Changed
- **Split `TouchActionButton`** — 559-line monolith → 3 files: `icons.tsx` (7 SVGs), `RadialMenu.tsx` (layout + items), `index.tsx` (orchestrator)

---

## [0.24.0] - 2026-03-09 — "iPad First"

### Added
- **`prefers-reduced-motion` support** — new `prefersReducedMotionAtom` + `watchReducedMotionAtom`; pan/zoom inertia, animated layouts, and edge fade animations respect the OS setting
- **CSS `prefers-reduced-motion`** — all canvas keyframe animations (search pulse, drop target, edge enter/exit, tap pulse) disabled when active
- **Undo/redo in `TouchActionButton`** — primary FAB actions on touch devices (with `UndoIcon` / `RedoIcon`)
- **External keyboard detection** — `hasExternalKeyboardAtom`; hides `TouchActionButton` when modifier keys are available
- **Cursor hiding on touch** — `@media (pointer: coarse)` resets all cursors to `default` via `data-canvas-root`
- **`data-canvas-root`** attribute on `Viewport` for CSS targeting

### Changed
- **Safe-area margins** — `ViewportControls` and `TouchActionButton` use `max(16px, env(safe-area-inset-*))` instead of hardcoded `16px`

---

## [0.23.0] - 2026-03-09 — "Clean Barrel"

### Changed
- **Slimmed `core/index.ts` barrel** — 458 → 85 lines (replaced 33 named export blocks with `export *`)
- **Renamed** `CanvasToast` interface → `CanvasToastData` to resolve name collision with `CanvasToast` component

### Removed
- **`findPortAtPosition`** (deprecated in v0.20) — use `hitTestPort` from `utils/hit-test`
- **`resolveGestureIntent` / `isBackgroundGesture`** (deprecated in v0.14) — use `resolveGestureIndexed` from `gesture-rules`
- **`gesture-resolver.test.ts`** — tests for removed functions
- All `@deprecated` markers cleared from production code

---

## [0.22.0] - 2026-03-09 — "Modular Core"

### Changed
- **Split `graph-store.ts`** (914 → 68 lines) into 4 focused modules:
  - `graph-store.ts` — core atoms (`graphAtom`, `graphUpdateVersionAtom`, drag/edge creation state)
  - `graph-position.ts` — position management (`nodePositionAtomFamily`, `updateNodePositionAtom`, cleanup)
  - `graph-derived.ts` — read-only UI state (`uiNodesAtom`, `nodeFamilyAtom`, `edgeFamilyAtom`)
  - `graph-mutations.ts` — write atoms (CRUD, split/merge, edge animations, DB sync)
- **Zero public API changes** — all exports still available from `core/index.ts` barrel
- Rewrote 51 import sites (25 production, 24 test, 2 dynamic)

---

## [0.21.0] - 2026-03-09 — "Debug"

### Changed
- **Unified logging** — migrated all 33 raw `console.*` calls to `createDebug` with `debug` package namespaces
- **`createDebug` extended** — now returns `debug()`, `.warn()`, `.error()` sub-loggers
  - `.warn()` and `.error()` are always enabled (bypass `DEBUG` filter)
  - `.warn()` routes to `console.warn`, `.error()` to `console.error`
  - Enable verbose logging: `localStorage.debug = 'canvas:*'`

### Files migrated
- `useForceLayout.ts` (9 calls), `db/queries/nodes.ts` (5), `db/queries/edges.ts` (4)
- `action-executor.ts` (6), `settings-store.ts` (3), `history-store.ts` (2)
- `NodeErrorBoundary.tsx` (1), `NodeTypeCombobox.tsx` (1), `useAnimatedLayout.ts` (1), `NoteNode.tsx` (2)

---

## [0.20.1] - 2026-03-09

### Added
- **`hitTestNode` / `hitTestPort`** — testable hit-test utilities replacing 5 direct `document.elementFromPoint` calls
- **`setHitTestProvider`** — swap DOM hit-testing for mocks in tests or SSR

### Changed
- **`findPortAtPosition`** deprecated — delegates to `hitTestPort`

---

## [0.20.0] - 2026-03-09 — "Maintain"

### Fixed
- **`atomFamily` deprecation** — migrated from deprecated `jotai/utils` to `jotai-family` package (prepares for jotai v3, eliminates deprecation warnings)
- **Corrupt `graphology` install** — force-reinstalled pnpm store to fix missing dist files

### Removed
- **Dead `CanvasConfig` type** — stale interface with `supabaseUrl`/`supabaseAnonKey` fields (unused, unexported)

---

## [0.17.0] - 2026-03-09 — "Connect"

### Added
- **`EdgePreviewLine`** — animated SVG bezier preview during edge creation (dashed line with color states for idle/valid/snapped)
- **Drag-from-port** — click a port in `NodePorts` to initiate edge creation (previously only available via `EdgeOverlay` handle)
- **Port snap visual** — port dots scale up (1.3x) and glow when a drag is hovering over a compatible port
- **`canConnect` validation** — new callback prop on `EdgeOverlay` to reject invalid connections before edge creation

### Changed
- **`EdgeOverlayProps`** — added optional `canConnect` callback
- **`NodePorts` / `PortBar`** — port elements now have `onPointerDown` for drag initiation

---

## [0.16.0] - 2026-03-09 — "Harden"

### Removed (breaking)
- **`resolveGestureIntent`** — removed from public API; use `resolveGestureIndexed` from gesture-rules instead
- **`isBackgroundGesture`** — removed from public API
- **`nodeGestureConfig` / `viewportGestureConfig`** — removed deprecated constants; use `getNodeGestureConfig(source)` / `getViewportGestureConfig(source)` functions
- **`useSupabaseClient` / `supabaseClientAtom`** — removed from public API; use `useStorageAdapter()` instead
- **Legacy Supabase props** — `CanvasDbProvider` and `CanvasProvider` no longer accept `supabaseUrl` / `supabaseAnonKey`; pass an `adapter` prop instead

### Changed
- **`CanvasProviderProps.adapter`** — now required (was optional)
- **`CanvasDbProviderProps`** — simplified to single interface (was discriminated union)
- **`utils/index.ts`** — exports `getNodeGestureConfig` / `getViewportGestureConfig` functions instead of deprecated constants
- **README.md** — refreshed with v0.14 Gesture System v2 and v0.15 Performance sections; updated architecture diagram and state management table

---

## [0.15.0] - 2026-03-07 — "Perform"

### Added
- **Spatial grid index** — `SpatialGrid` class with fixed-cell bucketing for O(visible) viewport culling; replaces O(N) linear scan in `visibleNodeKeysAtom`
- **`spatialIndexAtom`** — Jotai atom rebuilding the grid on graph structure and position changes
- **Performance instrumentation** — `canvasMark` / `canvasWrap` helpers with `performance.mark`/`performance.measure`; opt-in via `setPerfEnabled(true)` or `window.__canvasPerf?.(true)`
- **`perfEnabledAtom`** — Jotai atom for perf toggle; `setPerfEnabled` imperative API
- **Gestures subpath export** — `@blinksgg/canvas/gestures` for code-splitting the gesture pipeline
- 17 new tests covering `SpatialGrid` (12) and perf instrumentation (5)

### Changed
- **`edgeFamilyAtom`** — structural equality cache (`_edgeCache`) prevents re-renders when source/target positions and attributes haven't changed
- **`uiNodesAtom`** — structural equality check: returns previous array reference when all entries match by id, position, and isDragging
- **`updateNodePositionAtom`** — wrapped with `canvasMark('drag-frame')` for profiling the drag hot path
- **`visibleNodeKeysAtom`** — uses `SpatialGrid.query()` + `canvasMark('virtualization-cull')` instead of linear `Array.filter`
- **`virtualization-store`** — removed standalone `isNodeVisible` function in favor of spatial grid AABB intersection

---

## [0.14.0] - 2026-03-07 — "Unify"

### Added
- **Gesture System v2** — unified pointer + keyboard input pipeline with 4-layer architecture: Normalize → Recognize → Resolve → Dispatch
- **Specificity-scored binding resolution** — patterns scored by type (128), key (64), subjectKind (32), modifier (16/8), source (4), button (2); highest score wins across priority-sorted context stack
- **Keyboard as parallel input stream** — `KeyInputEvent` type shares resolver and dispatcher with `PointerGestureEvent` via `InputEvent` union; `InputPattern` matches both kinds
- **Built-in mapping contexts** — `PALM_REJECTION_CONTEXT` (pri 0), `ACTIVE_INTERACTION_CONTEXT` (pri 15), `SEARCH_CONTEXT` (pri 25), `KEYBOARD_MANIPULATE_CONTEXT` (pri 30), `KEYBOARD_NAVIGATE_CONTEXT` (pri 40), `DEFAULT_CONTEXT` (pri 100)
- **Held keys** — `HeldKeysState` tracks pressed keyboard keys; pointer events carry `heldKeys` for patterns like `Space+drag → pan`
- **Custom modifiers** — `Modifiers.custom?: Record<string, boolean>` for non-keyboard flags (iPad toolbar buttons, stylus buttons); scored identically to standard modifiers
- **Phase-aware action handlers** — `PhaseHandler` interface routes `onStart/onMove/onEnd/onInstant/onCancel` for pointer and `onDown/onUp` for keyboard; simple function handlers fire on `start`/`instant`/`down`
- **`consumeInput` flag** — bindings can block lower-priority contexts from matching the same event
- **`useGestureSystem` hook** — manages context stack, palm rejection toggle, held key state; produces pre-built `MappingIndex` for O(1) event bucketing
- **`useCanvasGestures` hook** — wires pointer events from a viewport ref through the full pipeline with inertia support
- **`useNodeGestures` hook** — per-node pointer event handlers feeding the pipeline with `{ kind: 'node', nodeId }` subject
- **`useRegisterInputActions` hook** — registers all built-in action handlers (selection, viewport, clipboard, history, layout, navigation, search)
- **`useInputModeGestureContext` hook** — auto-pushes/removes input mode contexts (pickNode, pickNodes, pickPoint)
- **`GestureProvider` / `InputProvider`** — React context provider wrapping the gesture system for tree-wide access
- **Keyboard shortcuts in gesture pipeline** — `/` (command line), `Escape`, `Delete`/`Backspace`, `Ctrl/Cmd+C/X/V/D/A/Z/Y/F/M` all routed through binding resolution instead of ad-hoc key handlers
- **Keyboard navigate mode** — Arrow keys move focus, Enter enters manipulate mode, Space activates, Tab cycles nodes
- **Keyboard manipulate mode** — Arrow keys nudge selected nodes (Shift = large nudge), Escape exits
- **Search keyboard bindings** — `Enter`/`Shift+Enter` and `Ctrl/Cmd+G` cycle search results within gesture pipeline
- **`SplitNode`, `GroupNodes`, `MergeNodes`** — added to `BuiltInActionId` enum; registered as proper pipeline actions with `ActionHelpers` callbacks
- **Pan inertia** — `PanInertia` class with friction-based deceleration after drag release
- **Timed state machine** — `TimedStateRunner` for tap counting, long-press detection, double/triple-tap recognition with configurable thresholds
- **Interactive demo** — `GestureV2Demo` with Binding Composer (pointer/keyboard), Event Composer (pointer/keyboard), held key simulation, real-time event log with input kind badges
- **Architecture docs** — `packages/canvas/docs/arch-gesture-system-v2.md`, `plan-gesture-system-v2.md`, `plan-keyboard-gesture-integration.md`
- **5 new test suites** — specificity (16 tests), mapper (12), timed-state (8), palm-rejection (5), inertia (4)

### Changed
- **`keyboard.ts`** — shortcut handling simplified; most key bindings moved to gesture pipeline contexts
- **`Viewport.tsx`** — gesture intent resolution delegated to v2 pipeline when available
- **`Node.tsx`** — pointer events can route through `useNodeGestures` pipeline
- **`settings-types.ts`** — `ActionContext` gains optional `selectedNodeIds`; `ActionHelpers` gains optional `splitNode`, `groupNodes`, `mergeNodes`
- **`interaction-store.ts`** — added `KeyboardInteractionMode` type (`'navigate' | 'manipulate'`) and `keyboardInteractionModeAtom`
- **`selection-path-store.ts`** — exported `isSelectingAtom` for guard context access

---

## [0.13.0] - 2026-03-05 — "Refine"

### Added
- **Undo/redo for structural operations** — `splitNodeAtom`, `mergeNodesAtom`, `nestNodesOnDropAtom`, `groupSelectedNodesAtom`, `ungroupNodesAtom` now push history snapshots with descriptive labels; all fully undoable
- **Undo/redo toast feedback** — `canvasToastAtom`, `showToastAtom` in `toast-store.ts`; `CanvasToast` overlay component shows "Undo: {label}" / "Redo: {label}" for 2 seconds after Ctrl+Z/Y
- **Tree layout** — `useTreeLayout` hook: BFS depth assignment, root detection, top-down/left-right direction; `treeLayout` and `horizontalLayout` commands
- **Grid layout** — `useGridLayout` hook: spatial-order-preserving grid with auto column count; `gridLayout` command
- **Animated layout transitions** — `useAnimatedLayout` shared hook: RAF loop with cubic easing, history snapshot before animation, persistence callback
- **Zoom inertia** — pinch-to-zoom continues with momentum after fingers lift (friction=0.88, velocity tracking)
- **Snap-to-100% zoom detent** — zoom snaps to 1.0 when within 0.03 threshold (both wheel and pinch)
- **Simultaneous pan during pinch** — tracks finger midpoint movement frame-to-frame for fluid two-finger gestures
- **Tap pulse animation** — `canvas-tap-pulse` CSS keyframe on ViewportControls buttons for tactile feedback
- 19 new tests (404 total) covering undo for split/merge/nest/group/ungroup, tree layout algorithm, grid layout algorithm

### Changed
- **`CommandProvider`** — wires `useTreeLayout` and `useGridLayout` into `CommandContext.layout`
- **`useCanvasHistory`** — shows toast with operation label on undo/redo
- **`Viewport`** — zoom inertia loop, snap-to-100% in onWheel/onPinch, simultaneous pan tracking in onPinch
- **`ViewportControls`** — ControlButton applies `canvas-tap-pulse` class on click
- **`CanvasAnimations`** — added `canvas-tap-pulse` keyframe

---

## [0.12.0] - 2026-03-05 — "Reshape"

### Added
- **Drag-to-nest** — drag a node over another to nest it as a child; visual drop-target highlight with pulsing blue outline; `dropTargetNodeIdAtom`, `nestNodesOnDropAtom`
- **Two-finger split** — two fingers diverging on a node (80px threshold) splits it into two copies with all edges duplicated; `splitNodeAtom`, `useSplitGesture` hook
- **Merge nodes** — `Ctrl+M` or command palette merges 2+ selected nodes; edges re-routed to survivor, internal edges discarded; `mergeNodesAtom`, `mergeNodesCommand`
- **`split-node` gesture intent** — added to `GestureIntent` type; pinch-on-node now maps to `split-node` instead of `zoom`
- **`.canvas-drop-target` CSS** — pulsing blue outline animation for drop target feedback
- 18 new tests (385 total) covering nest-on-drop, split-node, merge-nodes

### Changed
- **`useNodeDrag`** — detects drop target via `elementFromPoint` during drag; nests nodes on drop end
- **`Node`** — integrates `useSplitGesture` for two-finger split; shows drop-target highlight
- **`useGlobalKeyboard`** — added `Ctrl+M` shortcut for merge

---

## [0.11.0] - 2026-03-05 — "Interactive"

### Added
- **Nested group drag** — dragging a group node now moves all its descendants (children, nested groups) together via `getNodeDescendants` helper
- **Edge deletion animations** — `removeEdgeWithAnimationAtom` snapshots edge state before removal and renders a fade-out animation via `departingEdgesAtom`; `DepartingEdgeItem` renders the exit animation
- **Edge creation animations** — CSS fade-in animation class `.canvas-edge-enter` for newly created edges
- **Edge label editing** — double-click an edge label to open an inline `<input>` overlay; `editingEdgeLabelAtom` + `updateEdgeLabelAtom` manage state; `EdgeLabelEditor` component positioned at label world coords
- **`EdgeLabelEditor`** component — HTML input overlay for inline edge label editing, included automatically in `Canvas`
- 14 new tests (367 total) covering nested group drag, departing edges, edge label editing

### Changed
- **`useNodeDrag`** — on first drag frame, expands `initialPositions` to include all descendants of group nodes being dragged
- **`edgeFamilyAtom`** — now depends on `graphUpdateVersionAtom` for non-position attribute reactivity (label, color changes)
- **`EdgeRenderer`** — renders departing edges with exit animation; edge labels support double-click to edit
- **`CanvasAnimations`** — added `canvas-edge-fade-in`, `canvas-edge-fade-out` keyframes and `.canvas-edge-enter`, `.canvas-edge-exit` CSS classes

---

## [0.10.0] - 2026-03-05 — "Polish"

### Added
- **Edge re-routing** — `collapsedEdgeRemapAtom` maps collapsed children to their outermost collapsed ancestor; edges visually re-route to the group node instead of disappearing
- **Group auto-resize** — `autoResizeGroupAtom` recomputes group bounding box from children with padding; triggered automatically when drag ends on a child node
- **Search keyboard shortcuts** — `Ctrl+F` opens search, `Enter`/`Shift+Enter` cycle results, `Ctrl+G`/`Ctrl+Shift+G` alternative navigation, `Escape` clears search before clearing selection
- **Edge search dimming** — edges dim to `opacity: 0.2` when search is active and neither endpoint matches, with `150ms` CSS transition
- **Search highlight pulse** — currently highlighted search result gets an animated amber box-shadow pulse (`canvas-search-highlight` CSS class)
- **`CanvasAnimations`** component — injects CSS keyframes for search pulse and edge transitions
- 11 new tests (353 total) covering edge remap, internal edge hiding, cross-boundary edges, auto-resize, and drag-triggered resize

### Changed
- **`visibleEdgeKeysAtom`** — now remap-aware: resolves effective endpoints through `collapsedEdgeRemapAtom`, hides internal edges (both endpoints in same collapsed group)
- **`edgeFamilyAtom`** — uses remapped positions when source/target is inside a collapsed group
- **`endNodeDragAtom`** — triggers `autoResizeGroupAtom` for the parent group after drag ends
- **`useGlobalKeyboard`** — `Escape` now clears search first (before clearing selection)

---

## [0.9.0] - 2026-03-05 — "Full Stack"

### Added
- **Abstract persistence** — `CanvasStorageAdapter` interface with `SupabaseStorageAdapter` implementation and `InMemoryStorageAdapter`. `CanvasDbProvider` now accepts an `adapter` prop for any backend
- **`storageAdapterAtom`** — Jotai atom for global adapter access; `useStorageAdapter()` hook
- **Node grouping** — `parentId` attribute on graph nodes, collapsible container groups with `GroupNode` component
- **`group-store`** — `collapsedGroupsAtom`, `toggleGroupCollapseAtom`, `nodeChildrenAtom`, `nodeParentAtom`, `setNodeParentAtom`, `groupSelectedNodesAtom`, `ungroupNodesAtom`, `isNodeCollapsed()` utility
- **Group commands** — `groupNodes`, `ungroupNodes`, `collapseGroup`, `expandGroup` registered in command palette
- **Search & filter** — `searchQueryAtom`, `searchResultsAtom` (case-insensitive substring match on label/type/id), `highlightedSearchIndexAtom` with next/prev navigation
- **Search commands** — `searchNodes` (aliases: `find`, `search`) and `clearSearch` commands
- **Node dimming** — non-matching nodes rendered with `opacity: 0.2` and `pointerEvents: none` when search filter is active
- **Component tests** — 39 new React component tests (Canvas, Node, Viewport, Minimap, SelectionOverlay, GroupNode) using `@testing-library/react`
- 89 new tests (342 total)

### Changed
- **`uiNodesAtom`** — filters out nodes whose ancestor group is collapsed (walks parent chain)
- **`CanvasDbProvider`** — accepts union props: `{ adapter }` (primary) or `{ supabaseUrl, supabaseAnonKey }` (legacy, deprecated)
- **`CanvasProvider`** — accepts optional `adapter` prop alongside deprecated supabase credential props

---

## [0.8.0] - 2026-03-05 — "Full Picture"

### Added
- **Minimap** — `<canvas>`-based overview component showing all nodes with draggable viewport rectangle. Click/drag to pan. Configurable position, size, and colors
- **Lasso selection** — pencil drag on background draws freeform lasso path; nodes whose center falls inside the polygon are selected
- **Rect selection** — Shift+drag on background draws rectangular selection box; nodes overlapping the rect (AABB intersection) are selected
- **`SelectionOverlay`** — SVG component rendering the active lasso/rect path with configurable colors
- **`selection-path-store`** — new core store with `startSelectionAtom`, `updateSelectionAtom`, `endSelectionAtom`, `selectionPathAtom`, `selectionRectAtom`
- **`pointInPolygon()`** — ray-casting utility for lasso selection, exported from core
- **Zoom animations** — `animateZoomToNodeAtom` and `animateFitToBoundsAtom` write atoms for smooth animated viewport transitions with cubic ease-in-out
- **`useZoomTransition`** — hook driving requestAnimationFrame-based zoom/pan animation loop
- **`zoomAnimationTargetAtom`** — stores current animation target (start/end zoom/pan, duration)
- 39 new tests (253 total) covering selection paths, point-in-polygon, and zoom animation atoms

### Changed
- **Viewport.tsx** — now resolves gesture intent once at drag start (with modifier key state) and stores it in a ref, fixing Shift+drag rect-select and pencil lasso
- **TODO.md** — marked v0.7 completions, updated roadmap for v0.9+

---

## [0.7.0] - 2026-03-05 — "Solid Ground"

### Added
- **Headless Canvas API** — `createCanvasAPI(store)` factory provides all canvas operations (selection, viewport, graph, history, clipboard, snap, virtualization, actions) without React
- **`buildActionHelpers(store)`** — pure Jotai store-based ActionHelpers construction, extracted from `useActionExecutor` for headless use
- **`fitToBoundsAtom`** — headless write atom for fitting viewport to graph or selection bounds
- **`centerOnNodeAtom`** — headless write atom for centering viewport on a specific node
- **Arrow key navigation** — spatial keyboard navigation between nodes (Arrow keys = directional focus, Enter/Space = select, Tab = cycle by z-index)
- **`focusedNodeIdAtom`** — keyboard focus state, distinct from selection, with visual focus ring
- **`useArrowKeyNavigation` hook** — React hook for enabling keyboard navigation
- **`useCanvasGraph` hook** — convenience hook for graph structure access (nodeCount, edgeCount, getNode, getEdge)
- **214 unit tests** — comprehensive coverage for graph-store, selection-store, history-store, clipboard-store, viewport-store, virtualization-store, and canvas-api (up from 47)

### Changed
- **Edge re-render optimization** — `edgeFamilyAtom` no longer depends on `nodePositionUpdateCounterAtom` (global counter). Reactivity flows through `nodePositionAtomFamily` with structural equality caching. Performance: O(connected edges) per drag frame instead of O(all edges)
- **`useActionExecutor` simplified** — refactored to use `buildActionHelpers` internally (~30 lines from ~160)
- **`useFitToBounds` simplified** — delegates to `fitToBoundsAtom` instead of inline computation
- **Position atom caching** — `nodePositionAtomFamily` returns same object reference for unchanged positions via `_positionCache` Map

### Fixed
- Position atom cleanup now also clears the position cache to prevent stale references

---

## [0.6.0] - 2026-03-03

### Changed
- **React Compiler integration** — automatic memoization replaces 110 `useCallback`, 25 `useMemo`, 2 `React.memo`
- **Context as JSX** — 3 providers modernized (`CanvasStyleProvider`, `CommandProvider`, `CanvasDbProvider`)
- **Ref cleanup** — `CanvasStyleProvider` uses ref callback with cleanup for CSS variable lifecycle

### Removed
- **Dropped React 18** — peerDependencies require `react@^19.0.0`

---

## [0.5.0] - 2026-03-03

### Added
- `NodeClick` event type — single-click events on nodes now flow through the action executor
- `EdgeHover` / `EdgeLeave` observability callbacks on `Canvas`
- `React.memo` on `EdgeItem` component for render optimization

### Fixed
- `ConnectedNodeRenderer` virtualization — switched from `nodeKeysAtom` to `visibleNodeKeysAtom`
- `ConnectedNode` forwards `onClick`, `onHover`, `onLeave` props correctly
- `history-store` typed assertion for delta type checking

---

## [0.4.0] - 2026-03-03

### Added
- **Edge events** — `EdgeClick`, `EdgeDoubleClick`, `EdgeRightClick` with callback props on `Canvas`
- **Observability callbacks** — `onSelectionChange`, `onViewportChange`, `onDragStart`, `onDragEnd`, `onNodeHover`, `onNodeLeave`
- **Strong type contracts** — `UINodeProperties` replaces `Record<string, unknown>`, `UINodeState` replaces `any` in event callbacks
- `SelectEdge` built-in action in the action registry

---

## [0.3.0] - 2026-03-03

### Added
- All 8 event-action mappings fully wired (`NodeRightClick`, `NodeLongPress`, `BackgroundClick`)
- `BackgroundClick` routed through action executor when `onBackgroundClick` prop is provided

### Fixed
- `centerOnNode` viewport calculation — correctly reads node position from graph store

---

## [0.2.x] - 2025-01-14

### Added

#### Settings Panel
- `CanvasEventType` enum for all canvas events (double-click, triple-click, right-click, long-press)
- `ActionRegistry` with `registerAction()` for custom actions
- Built-in actions: selection, viewport, node, layout, history (17 total)
- `useCanvasSettings()` hook, `useActionExecutor()` hook
- `SettingsPanel` headless UI component
- Three built-in presets: Default, Minimal, Power User
- `localStorage` persistence via `atomWithStorage`

#### Layout System
- `useFitToBounds()`, `useGetGraphBounds()`, `useSelectionBounds()`, `useForceLayout()`
- Layout utilities: `calculateBounds()`, `checkNodesOverlap()`, `getNodeCenter()`

#### Command Palette
- `@blinksgg/canvas/commands` subpath export
- `CommandProvider`, `CommandLine`, `CommandSearch`, `CommandInputCollector`, `CommandFeedbackOverlay`
- `registerBuiltinCommands()`, `registerCommand()`, `useCommandLine()`, `useGlobalKeyboard()`
- Keyboard shortcuts: `/`, `Cmd+K`, `Escape`

#### Clipboard
- Local-first copy/cut/paste/duplicate (`Ctrl+C/X/V/D`)

#### Virtualization
- Viewport culling (+200px buffer)

#### Connection Ports
- `PortDefinition`, `NodePorts` component, `calculatePortPosition()`, `arePortsCompatible()`

#### Input System
- `classifyPointer()` for finger/pencil/mouse classification
- `resolveGestureIntent()` with palm rejection
- Touch/stylus support with device-aware thresholds

#### Other
- `d3-force` added as peer dependency

---

## [0.2.0] - 2025-01-07

### Added
- **Subscription hooks** for reactive state access:
  - `useCanvasSelection()`, `useCanvasViewport()`, `useCanvasDrag()`
- **New subpath export** `@blinksgg/canvas/hooks`
- **Configurable edge paths** with 8 path types:
  - `bezier` (default), `bezier-vertical`, `bezier-smart`
  - `straight`, `step`, `step-vertical`, `step-smart`, `smooth-step`
- Edge path calculator utilities from `@blinksgg/canvas/utils`

### Changed
- `EdgeRenderer` uses configurable path calculators instead of hardcoded bezier curves

---

## [0.1.0] - 2025-01-01

### Added
- Initial release
- Core state management with Jotai atoms
- Node drag, resize, and selection
- Edge creation and management
- Viewport pan/zoom with gesture support
- Supabase sync integration
- Style theming system with CSS variables
- Undo/redo history support

---

## Breaking Changes Guide

### Upgrading to 0.6.0

- **React 18 is no longer supported.** Update to React 19.
- No API changes — the React Compiler handles memoization automatically.

### Upgrading to 0.2.0

No breaking changes. Subscription hooks are recommended over direct atom imports:

```tsx
// Before
import { selectedNodeIdsAtom } from '@blinksgg/canvas/core';
const selectedNodeIds = useAtomValue(selectedNodeIdsAtom);

// After (recommended)
import { useCanvasSelection } from '@blinksgg/canvas/hooks';
const { selectedNodeIds, count, hasSelection } = useCanvasSelection();
```

---

## Version Policy

- **MAJOR** version: Breaking changes (removed/renamed exports, changed return types)
- **MINOR** version: New features (new hooks, components, options)
- **PATCH** version: Bug fixes (no API changes)
