feat(nodes): per-node free-text notes field (#3921)#3932
Conversation
Adds an optional free-text `notes` annotation per node, scoped per source like other per-node local metadata (favorites/ignored/hide-from-map). This mirrors the official Meshtastic mobile clients' local "notes" field, but persists server-side so it survives across devices/browsers. It is purely MeshMonitor-local state — never synced to or from the mesh. Backend: - Migration 112 adds nullable `nodes.notes` (TEXT / VARCHAR(2000)) across SQLite/PostgreSQL/MySQL, idempotent; existing rows keep NULL. - Schema (3 backends), DbNode / NodeData / DeviceInfo types, dbNodeMapper. - NodesRepository.setNodeNotes (Drizzle, all backends, source-scoped) and DatabaseService.setNodeNotesAsync (repo-backed, no raw SQL). - upsertNode preserves notes across packet-driven updates and, like `mobile`, intentionally omits notes from the conflict DO-UPDATE so a packet never clobbers a user-authored note. - POST /api/nodes/:nodeId/notes route (requirePermission nodes/write, source from body, string + 2000-char + hex-nodeId validation). Frontend: - Editable notes textarea in NodeDetailsBlock (node detail view in Messages), gated on nodes/write permission; read-only display otherwise. Draft tracks a local baseline so Save settles immediately before the node poll catches up. - apiService.setNodeNotes client method + styling. Tests: - Repository per-source isolation + notes-preservation tests. - Route validation-contract tests (set/clear/type/length/sourceId/nodeId). - Migration registry count/last-name bump to 112. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
|
Claude finished @Yeraze's task —— View job PR Review: feat(nodes): per-node free-text notes field (#3921)Tasks:
Overall this is a well-structured implementation that follows existing project patterns closely. The notes field integrates cleanly with the multi-source architecture and multi-database support. Here's my analysis: Migration & SchemaMigration 112 (
Schema ( Minor observation: There's a subtle inconsistency between backends in the type system — SQLite/Postgres use unbounded Backend (Repository, Service, Routes)
The
Notes preservation in The PR uses two distinct code paths:
This triple-layered protection is thorough and correct.
The cache update at line 7621–7624 is correct for PG/MySQL users, but there's a subtle issue worth noting: the Route ( Validation order and input checking looks solid:
Potential issue: no node existence check. If someone calls
The Frontend
The
Tests
Gap: There's no test for calling SecurityNo concerns. Notes are stored server-side only, never returned to the mesh. Input is validated for type and length. Permission check ( The field is surfaced in API responses as part of the node object (via SummaryVerdict: Looks good to merge. The implementation is clean, thorough, and consistent with established project patterns. The two minor items worth tracking:
Neither is a blocker. |
Summary
Closes #3921. Adds an optional free-text
notesannotation per node, editable from the node detail view and stored server-side so it persists across devices/browsers.The official Meshtastic mobile clients (Android/Apple) expose a local per-node "notes" field that is purely client-side app state — no protobuf/admin message involved. This brings the same capability to MeshMonitor, scoped per source like other per-node local metadata (favorites / ignored / hide-from-map). It is never synced to or from the mesh.
Changes
Backend
112_add_notes_to_nodes) adds a nullablenodes.notescolumn (TEXTon SQLite/PostgreSQL,VARCHAR(2000)on MySQL), idempotent across all three backends. Existing rows keepNULLand behave exactly as before.DbNode/NodeData/DeviceInfotypes, anddbNodeMapper.NodesRepository.setNodeNotes(nodeNum, notes, sourceId)— Drizzle, source-scoped, works on all backends (no raw SQL).DatabaseService.setNodeNotesAsync— repository-backed, updates the in-memory cache.upsertNodepreserves notes across packet-driven updates and, likemobile, intentionally omits notes from the conflictDO UPDATE SETclause so an incoming packet can never clobber a user-authored note.POST /api/nodes/:nodeId/notesroute —requirePermission('nodes', 'write'), sourceId from body, with string / 2000-char / hex-nodeId validation.Frontend
<textarea>inNodeDetailsBlock(the node detail panel in Messages), gated onnodes:writepermission; read-only display when the user lacks write. The draft tracks a local baseline so the Save button settles immediately after a successful save, before the node poll catches up.apiService.setNodeNotesclient method + styling.Tests
nodes.test.ts).server.test.ts).Verification
tsconfig.server.json): 0 errors.vite build: success.🤖 Generated with Claude Code
https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n