diff --git a/dev-docs/docs/codebase/hierarchy-plan.md b/dev-docs/docs/codebase/hierarchy-plan.md new file mode 100644 index 0000000000..ca350d4364 --- /dev/null +++ b/dev-docs/docs/codebase/hierarchy-plan.md @@ -0,0 +1,101 @@ +## Excalidraw Hierarchical Model Plan + +### Background & Goals + +Introduce a fully in-memory hierarchical (tree) model on top of the existing flat `elements[]` storage for more efficient complex operations (queries, selection, collision), while keeping flat arrays as the persistence/collab projection. Gradually move to tree-first edits with flat projection. + +### Capabilities To Preserve + +- z-index via fractional indices +- add/remove to frame +- group/ungroup +- bound texts (containerId) +- arrow bindings (start/endBinding) +- history (undo/redo) and collab (delta broadcast) +- load/save the flat array + +### Existing Reusable Capabilities + +- Deltas & History: `element/src/delta.ts` (ElementsDelta/AppStateDelta/StoreDelta), `excalidraw/history.ts` (HistoryDelta), auto rebind, text bbox redraw, z-index normalization. +- Store & Snapshot: `element/src/store.ts` provides commit levels, batching, and delta emission. +- Scene & Relationships: `element/src/Scene.ts`, `element/src/frame.ts`, `element/src/groups.ts` for frames and groups logic. +- Rendering: `excalidraw/renderer/staticScene.ts` with order by fractional index. +- Restore/Import: `excalidraw/data/restore.ts`. + +### Data Model & Invariants + +- Node types: `ElementNode`, logical `GroupNode` (id=groupId), `FrameNode` (bound to frame element). `Table*Node` reserved. +- Parent priority: container > group (deep→shallow) > frame > root; single parent per node. +- Groups must not span multiple frames. +- Drawing order remains by fractional index; the tree offers structural and sibling-order views only. + +### Flat→Tree Build (`buildFromFlat`) + +- Input: `elements[]`/`elementsMap` (optionally including deleted). +- Output: `{ nodesById, roots, orderHints, diagnostics }`. +- Rules: + - Bound text attaches to its container; groups form deep→shallow parent chains from `groupIds`; frame parent from `frameId`; otherwise root. + - Sibling order: ascending by the minimum `index` across the node’s represented elements. + - Diagnostics: cross-frame groups, invalid container, cycles, missing refs (error/warn). + +### Tree → Flat Projection (`flattenToArray`) + +- Input: tree, optional "apply recommended reorder". +- Output: `{ nextFieldsByElementId, reorderIntent? }`. +- Rules: + - `frameId` from nearest frame ancestor; `groupIds` nearest→farthest; `containerId` from nearest container. + - Do not change draw order by default; any reordering is applied by the caller via `Scene.insert*` and `syncMovedIndices`. + +### Operations Mapping (Tree edits → Flat deltas) + +- z-index: sibling reordering → index deltas; normalized with `syncMovedIndices`. +- Frame membership: reparent to `FrameNode`/root → `frameId` updates; cross-frame groups disallowed. +- Group/ungroup: modify `GroupNode` structure → update `groupIds` chains. +- Bound text: reparent to container → update `containerId`/`boundElements`; text bbox redraw handled by `ElementsDelta`. +- Arrow binding: does not change parentage; only update start/endBinding; `ElementsDelta` handles rebind/unbind. + +### History & Collab + +- Transactional edits on the tree via `HierarchyManager.begin/commit/rollback`; commit projects to a minimal flat diff, wrapped as `StoreDelta`, and submitted via `Store.scheduleMicroAction` (IMMEDIATELY). +- Undo/redo uses `HistoryDelta`; replay re-emits flat deltas for sync. +- Collab remains flat-delta based; peers rebuild the tree deterministically from flats. + +### Rendering Strategy + +- Add a tree-backed rendering adapter beside `renderStaticScene` behind a feature flag, preserving draw-order semantics (fractional index). In the short term, use the tree for selection/collision pruning (frame → group → element). + +### Challenges & Risks + +- Cross-frame group handling (block or guided fix). +- Reorder consistency (tree sibling order vs fractional index). +- Collab conflicts (use `ElementsDelta.applyLatestChanges`). +- Performance (build O(n), queries O(1)/O(k)); cache/incremental via `sceneNonce`. +- Test coverage (round-trip, collab equivalence, history replay, deep groups/large frames/binding chains). + +### Phased Plan + +- Phase 0 Rules & Contracts + - Lock invariants and priorities; define diagnostics (error/warn). +- Phase 1 Pure functions & Validation + - Implement `buildFromFlat`, `flattenToArray`, `validateIntegrity`; cache by `sceneNonce`; add round-trip tests. +- Phase 2 Read-only integration + - Tree-backed selection and collision pruning; measure wins. +- Phase 3 Parallel render adapter + - Tree render adapter (flag) with preserved order semantics. +- Phase 4 Projection & Transactions + - `HierarchyManager.begin/commit/rollback`; commit→`StoreDelta`→Store. +- Phase 5 Migrate operations + - Frame membership and group/ungroup → tree+projection; then bound text; optional z-index reorder intent. +- Phase 6 Extensions & Tables + - Introduce `Table*Node` (in-memory first, then projection), with validation and UI. + +### Success Criteria + +- Correctness: same flat → same tree; unchanged structure round-trip no-ops; existing operations equivalent. +- History/Collab: still record and broadcast minimal flat deltas; deterministic tree on peers. +- Performance: selection/collision candidate reduction on large scenes; build/query latency targets met. +- Rollback: feature flag to fall back to legacy path at any time. + +### Next Steps + +- Finalize invariants and IO contracts; implement `buildFromFlat`/`flattenToArray` and `validateIntegrity`; add round‑trip and failure-case tests; prototype read-only integration and render adapter.