Skip to content

feat(board): expandable sidebar hierarchy + deep-link routes for local boards - #190

Merged
winlp4ever merged 2 commits into
mainfrom
feat/local-board-hierarchy
Aug 5, 2026
Merged

feat(board): expandable sidebar hierarchy + deep-link routes for local boards#190
winlp4ever merged 2 commits into
mainfrom
feat/local-board-hierarchy

Conversation

@winlp4ever

Copy link
Copy Markdown
Contributor

What

Local boards now have full parity with synced boards in the sidebar: expand a local board to see its surface-node hierarchy (sheets / folders / code-sandbox / widgets), and click a node to open it — with real deep-link URLs.

Before, only synced boards had this (fed by the backend GET /boards/:id/contents + /boards/$id/{sheets,…}/$noteId routes); local boards were flat rows.

How

  • list-local-board-contents.ts (new): reconstructs the BoardContentItem[] hierarchy entirely client-side from the on-device store — getLocalStores().engineBoardPersistence(boardId).load() → project surface-kind nodes (data.styleType ∈ {sheet,folder,code-sandbox,widget}, echoing parentId/label/iconData). The client analog of the backend's pure projection. Local persistence is snapshot+oplog (no per-level query), so the whole board loads once and levels are filtered in memory.
  • LocalBoardItem: now expandable (Collapsible + toggle), rendering BoardTreeNode in local mode.
  • BoardTreeNode: gains a local mode — children come from the in-memory list, navigation targets /local/$boardId/*.
  • Routes: added /local/$boardId/{sheets,code-sandbox,widgets,mini-apps}/$noteId as children of the local board route (mirror of the synced children).
  • Navigator seam: the injected surface navigator now owns URL construction (store passes kind+ids), so the store stays route-agnostic. useHarnessSurfaceFromUrl(local) picks the route family + param name (boardId vs id). LocalBoardScreen renders <Outlet/> and calls the hook.

The canvas, surface panels (NodeSurfaceHost) and store were already board-agnostic and mounted for local — this adds the missing URL layer + sidebar tree.

Verify

  • tsc + eslint clean; 1103 tests pass; production build green.
  • Expand a local board in the sidebar → its notes/folders appear; click a sheet → panel opens at /local/$boardId/sheets/$noteId; folders scope the canvas via root_id.

🤖 Generated with Claude Code

… deep-link routes

Synced boards show an expandable surface-node tree (sheets/folders/code-sandbox/
widgets) in the sidebar and deep-link to /boards/$id/{sheets,...}/$noteId; local
boards had neither. Bring them to parity:

- list-local-board-contents: reconstruct the BoardContentItem hierarchy from the
  on-device store (BoardPersistence.load via getLocalStores engine), the client
  analog of the backend /boards/:id/contents projection. Whole board loaded once
  (snapshot+oplog has no per-level query); levels filtered in memory.
- LocalBoardItem: expandable, rendering BoardTreeNode in local mode.
- BoardTreeNode: local mode — children from the in-memory list, navigation to the
  /local/$boardId/* routes.
- Routes: add /local/$boardId/{sheets,code-sandbox,widgets,mini-apps}/$noteId as
  children of the local board route (mirror of the synced children).
- Navigator seam: the surface navigator now owns URL construction, so the store
  stays route-agnostic; useHarnessSurfaceFromUrl(local) picks the route family +
  param name. LocalBoardScreen renders <Outlet/> + calls the hook.

The canvas, panels (NodeSurfaceHost) and store were already board-agnostic and
mounted for local; this adds the URL layer + sidebar tree. tsc/eslint clean,
1103 tests pass, build green.
- Local board row kept its active highlight only on the bare /local/$boardId
  path; a surface sub-route (/local/$boardId/sheets/...) de-highlighted it.
  Match the synced rule: exact OR startsWith the board path.
- board-tree-node: drop the hardcoded route literals, reuse nodeSurfacePath(kind,
  local) so the kind→URL mapping lives in one place.
- list-local-board-contents: staleTime 0 + refetchOnWindowFocus off so each
  expand re-reads the store (the local source is mutable and has no invalidation
  hook, unlike synced) — created/renamed/deleted surfaces now show on re-expand.
  Local boards are small, so the replay is cheap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant