Skip to content

feat(canvas): spatial canvas (Excalidraw) M0–M3 — local-only, default-OFF - #785

Merged
h4yfans merged 7 commits into
mainfrom
spatial-canvas-m3-linking
Jul 20, 2026
Merged

feat(canvas): spatial canvas (Excalidraw) M0–M3 — local-only, default-OFF#785
h4yfans merged 7 commits into
mainfrom
spatial-canvas-m3-linking

Conversation

@h4yfans

@h4yfans h4yfans commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

What

  • M0spatialCanvas flag (default-OFF), migration 0035 (canvases + canvas_entity_refs), canvas contracts + IPC.
  • M1 — Excalidraw surface: canvas tab kind, sidebar section, self-hosted fonts, encrypted scene blob, debounced save.
  • M2 — item cards (Excalidraw rectangle + customData + a pointer-events:none DOM overlay preview), drag-drop / capture-first, ↗ redirect.
  • M3 — linking: connect two cards with the native Excalidraw bound arrow — it re-routes when either card moves and persists in the scene blob (no new tables/contracts). Card rects get a solid fill so their whole interior binds/selects (Excalidraw only hit-tests a transparent shape on its outline); the fill is hidden under the opaque overlay. Arrows stay canvas-local — not reflected into backlinks.

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.

h4yfans added 4 commits July 17, 2026 16:23
…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.
@github-actions github-actions Bot added dependencies documentation Improvements or additions to documentation enhancement New feature or request test labels Jul 17, 2026
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 0d4af06.

h4yfans added 2 commits July 20, 2026 01:34
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
h4yfans marked this pull request as ready for review July 20, 2026 12:41
@h4yfans
h4yfans force-pushed the spatial-canvas-m3-linking branch from 78fce5d to 859949f Compare July 20, 2026 12:47
…king

# Conflicts:
#	apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts
#	packages/rpc/src/index.ts
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
memry-docs Ignored Ignored Preview Jul 20, 2026 12:51pm
memrynote-landing Ignored Ignored Preview Jul 20, 2026 12:51pm

@h4yfans
h4yfans merged commit e87e835 into main Jul 20, 2026
16 of 19 checks passed
@h4yfans
h4yfans deleted the spatial-canvas-m3-linking branch July 20, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies documentation Improvements or additions to documentation enhancement New feature or request test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant