feat(board): expandable sidebar hierarchy + deep-link routes for local boards - #190
Merged
Conversation
… 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.
This was referenced Aug 5, 2026
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
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,…}/$noteIdroutes); local boards were flat rows.How
list-local-board-contents.ts(new): reconstructs theBoardContentItem[]hierarchy entirely client-side from the on-device store —getLocalStores().engine→BoardPersistence(boardId).load()→ project surface-kind nodes (data.styleType ∈ {sheet,folder,code-sandbox,widget}, echoingparentId/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), renderingBoardTreeNodein local mode.BoardTreeNode: gains alocalmode — children come from the in-memory list, navigation targets/local/$boardId/*./local/$boardId/{sheets,code-sandbox,widgets,mini-apps}/$noteIdas children of the local board route (mirror of the synced children).kind+ids), so the store stays route-agnostic.useHarnessSurfaceFromUrl(local)picks the route family + param name (boardIdvsid).LocalBoardScreenrenders<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
/local/$boardId/sheets/$noteId; folders scope the canvas viaroot_id.🤖 Generated with Claude Code