Repository navigation
Description
Overview
In browser mode (dev-proxy transport), editing a field on a root node that was opened by navigation never persists. The store tries to create the node instead of updating it, the daemon rejects the create with a 500, and the edit is gone after a reload.
Problem Statement
Repro (identical on main):
- Run browser mode against an isolated daemon (see the
run-appskill:NODESPACE_HOME+ shortNODESPACED_SOCKET, thendev:browser) - Create a root person node through the proxy:
POST /api/nodeswith{ id, nodeType: "person", properties: { first_name, last_name, email } } - Open it:
getNavigationService().navigateToNode(id, true) - Change a property field (e.g. Last name) and tab away
Observed:
- Console:
[SharedNodeStore] Database write failed for node <id>: BackendError: Failed to insert node: Failed to create node POST /api/nodes→ HTTP 500- The field shows the new value until reload.
GET /api/nodes/<id>still returns the old value.
Expected: an update against the existing node, which persists.
Root Cause (partially established)
Location: packages/desktop-app/src/lib/services/shared-node-store.svelte.ts (the updateNode persistence closure, the isPersistedToDatabase branch around line 1755 and the create fallback around line 1985)
The branch choice is this.persistedNodeIds.has(nodeId). For this node it is false, so the update goes down the "node doesn't exist yet" path and calls backendAdapter.createNode for a node that already exists.
Not yet established: which load path puts the page's root node into the store without marking it persisted. Database-sourced setNode/batchSetNodes do add to persistedNodeIds (around lines 2392, 2413, 2806, 2827). The first step is to trace how BaseNodeViewer / navigation inserts the root (base-node-viewer.svelte around lines 440-463, loadChildrenTree) and which source or options it uses. Also check whether Tauri mode is affected; this was only reproduced in browser mode.
Proposed Solution
Find the ingress that inserts the root without persisted tracking, and make it mark the node persisted: use a database source, or markAsPersistedOnly, consistent with the other load paths. Do not add a per-write existence query; persistedNodeIds exists to avoid one.
Architecture Impact
No ADR is affected. nodespace-docs/architecture/frontend-state-and-persistence.md documents persistedNodeIds as distinguishing backend-existing nodes from in-memory ones. This bug violates that contract; the fix restores it.
Acceptance Criteria
- The load path that leaves the root untracked is identified and fixed
- Editing a field on a navigated-to root node in browser mode issues an update (no
POST /api/nodes), and the value survives a reload - Tauri mode checked for the same defect
- Regression test: a root node loaded through the navigation/page-load path is in
persistedNodeIds, and a subsequentupdateNodetakes the update path - Code passes
bun run quality:fix - Tests pass (
bun run test:all)
Technical Specifications
Reference Files
- Store:
packages/desktop-app/src/lib/services/shared-node-store.svelte.ts:persistedNodeIds,updateNodepersistence closure,setNode/batchSetNodes/doLoadChildrenTree - Page loader:
packages/desktop-app/src/lib/design/components/base-node-viewer.svelte(root/children load) - Navigation:
packages/desktop-app/src/lib/services/navigation-service.ts
Related Issues
- Person and project nodes need strongly-typed structs like task (person fields render empty) #2807 (found while verifying its fix in browser mode)
Activity
malibio commentedon Sep 25, 2026
Frontend baseline: 264 files / 5286 tests passed