feat(canvas): spatial canvas (Excalidraw) M0–M3 — local-only, default-OFF - #785
Merged
Conversation
…ontracts, IPC Heptabase-style spatial canvas (Excalidraw), milestone M0 behind a hidden default-off flag. No user-facing surface yet. - spatialCanvas flag in FeaturesSettingsSchema + defaults (schema-only, intentionally NOT in FEATURE_KEYS until the opt-in phase); feature-flags test rewritten with a declared HIDDEN_FEATURE_KEYS set - migration 0035: canvases + canvas_entity_refs (additive, hand-written, IF NOT EXISTS); upgrade-path + raw-idempotency + FK/PK/index tests - canvas-api contracts + CanvasChannels (+ channel prefix/duplicate guards) - @memry/rpc canvas domain -> generated window.api.canvas bindings - main/canvas/store.ts (drizzle CRUD, soft-delete prunes entity refs in-tx) + encryption.ts (vault-key XChaCha20 at-rest envelope, canvas_snapshot AD) - canvas-handlers.ts wired into ipc index; invoke map + preload regenerated - spec copied onto branch + §18 re-validation addendum (12-agent workflow findings: migration verification design, M4 conflict-copy plumbing, Excalidraw API facts via context7) Verification: typecheck 16/16, ipc:check, check:contracts, check:architecture, full desktop suite 10980 passed, contracts 1449, rpc 81.
…tence (local-only) - @excalidraw/excalidraw@0.18.1 as devDependency (renderer-bundled; prod deps stay native-only per runtime-dependencies.test) - self-hosted fonts under renderer public/excalidraw/fonts + external excalidraw-asset-path.js head script (CSP: no inline scripts in prod; value must be a fully-qualified URL to survive packaged file://) - tab kind 'canvas' (entity-based, non-singleton): TAB_ICONS/TYPE_TO_ICON 'pen-tool', getDefaultPath, manual tab-content case with double-lazy CanvasPage so Excalidraw stays out of the main renderer chunk - CanvasPage: flag-off placeholder (tab restore cannot gate hidden-phase tabs), load via canvas.get, theme follow (white/light->light), langCode from Excalidraw's bundled translations - debounced+deduped scene persistence (serialize at save time, skip pan/zoom-only commits) registered with the save-registry flush-on-quit - canvas-handlers: vault key resolved once per process (agent bootstrap parity) — per-invoke keychain resolution breaks under the NODE_ENV=test keychain degradation and wastes a keychain roundtrip per IPC call - sidebar Canvases section behind spatialCanvas flag + sidebar-canvas-list - canvas.* i18n block (en); eslint ignore for renderer public assets - e2e: create->draw->reload persists ink; flag-off placeholder + hidden section; unit tests for persister, lang mapping, handlers memoization, page states, list component, service
… (local-only)
Hybrid card model: an Excalidraw rectangle carries customData {entityType,
entityId} for geometry/selection/arrow-binding; a DOM overlay renders a
read-only preview over each visible card. No entity content is copied into
the scene — cards reference notes/tasks/events by id only.
- canvas-cards.ts: pure geometry/data core (entity-ref extraction, overlay
transform, viewport membership with hysteresis, card skeleton, drag payload)
- use-canvas-entities.ts: fetch-on-visible entity cache with live title/status
via note/task/calendar events; get()===null (or archived) = dangling
- canvas-card.tsx: A4-style note preview (marked token-walker body), task/event
status chips, dangling + loading states, arrow redirect button
- canvas-card-overlay.tsx: imperative transform on every onChange (zero setState
during pan), viewport virtualization, capture-phase drop/dblclick, capture-
first New-note button; z-[3] lifts the overlay above Excalidraw's interactive
canvas (--zIndex-interactiveCanvas:2) so card buttons are clickable
- canvas-redirect.ts: proven memry-links viewState deep-links (note tab, Tasks
drawer, Calendar focus)
- canvas-editor.tsx: mounts the overlay; saves now persist advisory entityRefs
- notes-tree drag source tags notes with application/x-memry-canvas-item so a
sidebar note can be dropped onto a canvas
- e2e: drop to referencing card (no copy) + redirect opens note tab, live title
edit + dangling on delete, capture-first, pan sync + virtualization. Cold-
launch vault-open wait added (embedding init ~30s > waitForVaultReady 15s)
- unit tests for cards logic, redirect, entity hook, card + overlay components
Task/event drag-to-canvas is out of scope (dnd-kit, not dataTransfer); those
cards still render + redirect. In-place editing is M6.
Connect two item cards with the native Excalidraw arrow tool: the bound arrow re-routes when either card moves and persists inside the scene blob (no new tables, contracts, or migration). Give card rectangles a solid fill so the whole card interior is an arrow-binding and selection target — Excalidraw only hit-tests a transparent shape on its outline, so a transparent card bound (and could be grabbed) only at its border. The fill is invisible under the opaque card overlay. Arrows stay canvas-local and are not reflected into the wikilink/backlinks graph.
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Eliminated the section discussing the second-device setup adopting the existing vault, which was based on user feedback. This change streamlines the document by focusing on current ideas and proposals.
…lt off)
Promote spatialCanvas from a hidden flag to a FEATURE_KEYS entry so it
appears in Settings › Features as a user toggle (default off). Add its
en label/description. The canvas tab type ('canvas') stays out of
FEATURE_KEYS, so tab restore and the flag-off placeholder are unchanged.
Still local-only — cross-device sync (M4) is not wired yet.
h4yfans
marked this pull request as ready for review
July 20, 2026 12:41
h4yfans
force-pushed
the
spatial-canvas-m3-linking
branch
from
July 20, 2026 12:47
78fce5d to
859949f
Compare
…king # Conflicts: # apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts # packages/rpc/src/index.ts
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
spatialCanvasflag (default-OFF), migration 0035 (canvases+canvas_entity_refs), canvas contracts + IPC.canvastab kind, sidebar section, self-hosted fonts, encrypted scene blob, debounced save.rectangle+customData+ apointer-events:noneDOM overlay preview), drag-drop / capture-first, ↗ redirect.Why
Heptabase-style spatial canvas for brain-dump thinking. Entirely behind a default-OFF flag — zero behavior change when off. Sync (M4) is out of scope and gated separately; docs land at the M7 rollout.