fix(nodes): position override writes to live source row, not 'default'#2913
Conversation
…ault' Issue #2902: clicking "Override Node Position" appeared to succeed but the node never moved on the map. PostgreSQL/MySQL deployments ended up with two rows per node — the live-source row (untouched) and a stray sourceId='default' row containing the override coordinates. Root cause ========== DatabaseService.setNodePositionOverride() (Postgres/MySQL path): - Looks up the cached node with the correct live sourceId — OK. - Mutates the cached node's override fields in place — OK. - Calls `nodesRepo.upsertNode(existingNode)` — BROKEN. The second arg (sourceId) was omitted, so NodesRepository.upsertNode fell back to `sourceId ?? 'default'`. It then ran getNode(nodeNum, 'default'), got null (no such row existed), and INSERTed a brand-new row with sourceId='default'. The live-source row never received the override, so the map (which renders from the live row) never moved. Fixes ===== 1. NodesRepository.upsertNode: when the caller omits the sourceId arg, fall back to `nodeData.sourceId` before defaulting to 'default'. Cached node objects always carry their sourceId; honoring it prevents the silent stray-row bug for any current or future caller. 2. DatabaseService.setNodePositionOverride (line ~7187): pass the sourceId argument explicitly to nodesRepo.upsertNode. Belt and suspenders with fix #1, but makes intent obvious at the call site. 3. DatabaseService.upsertNode (line ~2147): same explicit-sourceId pattern applied to the other bare nodesRepo.upsertNode call. The merged node already carries `sourceId: upsertSourceId`, but passing it explicitly removes the implicit dependency on fix #1. Regression test =============== nodes.test.ts: new test 'upsertNode - falls back to nodeData.sourceId when sourceId arg omitted' verifies that calling upsertNode with a node object whose sourceId field is set (mirroring the cached-node path) writes to that source's row and does NOT create a 'default' row. Runs against all three backend harnesses (SQLite/Postgres/MySQL). SQLite was unaffected — its raw-SQL path in setNodePositionOverride already used the correct sourceId. Fixes #2902 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job PR Review Complete ✅I've thoroughly analyzed this pull request fixing the position override bug in PostgreSQL/MySQL deployments. This is a high-quality fix with excellent attention to detail. ✅ Code Quality & Best PracticesExcellent defensive programming:
Good architectural decisions:
✅ Bug Analysis - Root Cause Correctly IdentifiedThe diagnosis is spot-on:
✅ Security AssessmentNo security concerns identified:
✅ Performance ConsiderationsImplementation is performance-neutral:
✅ Test Coverage - Excellent Regression ProtectionThe new test Comprehensive coverage:
Real-world simulation:
✅ Minor Suggestions
🎯 Overall AssessmentThis is an excellent fix that demonstrates:
The fix is minimal, targeted, and preserves all existing functionality while solving the critical issue. The regression test provides strong protection against future regressions. Recommendation: APPROVE - This fix is ready for merge. |
System Test ResultsMeshMonitor System Test ResultsTest Run: 2026-05-05 23:50:00 EDT Test Summary
✅ Overall Result: PASSEDAll deployment configurations are working correctly! Test DetailsConfiguration Import:
Quick Start Test:
Security Test:
V1 API Test:
Reverse Proxy Test:
Reverse Proxy + OIDC Test:
Virtual Node CLI Test:
Backup & Restore Test:
Database Migration Test:
DB Backing Consistency Test:
|
Summary
Fixes #2902 — clicking Override Node Position in PostgreSQL/MySQL deployments appeared to succeed, but the node never moved on the map. The
nodestable ended up with two rows per node: the live-source row (untouched) and a straysourceId='default'row containing the override coordinates.Root cause
DatabaseService.setNodePositionOverride()(Postgres/MySQL path atsrc/services/database.ts:7187):sourceId— OK.nodesRepo.upsertNode(existingNode)— broken. The second arg (sourceId) was omitted, soNodesRepository.upsertNodefell back tosourceId ?? 'default', rangetNode(nodeNum, 'default')(returned null), and INSERTed a brand-new row withsourceId='default'. The live-source row never received the override, so the map (which renders from the live row) never moved.The SQLite path used a raw
UPDATE ... WHERE sourceId = ?and was unaffected.Changes
src/db/repositories/nodes.ts—upsertNodenow falls back tonodeData.sourceIdbefore defaulting to'default'. Cached node objects always carry theirsourceId; honoring it prevents the silent stray-row bug for any current or future caller.src/services/database.ts:7187—setNodePositionOverridenow passessourceIdexplicitly tonodesRepo.upsertNode. Belt & suspenders with "Claude PR Assistant workflow" #1.src/services/database.ts:2147— same explicit-sourceIdpattern applied to the other barenodesRepo.upsertNodecall (DatabaseService.upsertNode).Regression test
src/db/repositories/nodes.test.ts— new testupsertNode - falls back to nodeData.sourceId when sourceId arg omitted (issue #2902)verifies that callingupsertNodewith a node object whosesourceIdfield is set writes to that source's row and does not create a stray'default'row. Runs against all three backend harnesses (SQLite / Postgres / MySQL).Test plan
sourceId != 'default', set a position override → live-source row gets the override fields and the marker moves on the mapsourceId='default'row is created in thenodestable🤖 Generated with Claude Code