feat(meshcore): server-side node favoriting with pin-to-top (#3588)#3595
Conversation
Add the ability to favorite MeshCore nodes of any role (Companion, Repeater, Room Server, …), pinning them to the top of the node list — consistent with how Meshtastic node favorites are displayed. MeshCore firmware has no native favorite concept, so unlike Meshtastic (which round-trips a SetFavoriteNode admin message to the device), the flag is stored server-side only and never pushed to the device. - Schema: add `isFavorite` to meshcore_nodes (SQLite/PG/MySQL). - Migration 094: idempotent column add across all three backends. - Repository: `setNodeFavorite(sourceId, publicKey, isFavorite)` with the stub-insert pattern (favorite a node seen only in-memory). Source-scoped. - Manager: `setNodeFavorite` (local-only, no device IO) + map isFavorite in `getAllNodes`. - Route: POST /api/sources/:id/meshcore/nodes/:publicKey/favorite, gated by nodes:write, backfills identity from the in-memory contact, no device round-trip. - Frontend: star toggle per row in MeshCoreNodesView; favorites pinned to top mirroring the Meshtastic useProcessedNodes convention; optimistic update + recomputeNodes carry-forward so contact pushes don't un-pin. Tests: per-source isolation repo test + route tests (auth, validation, local-only behavior, backfill). Full vitest suite green (7024 passed). Closes #3588 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: feat(meshcore): server-side node favoriting with pin-to-top (#3588)Tasks:
Overall this is a well-implemented feature that correctly follows the codebase's patterns. The architecture decision to store the flag server-side only (with clear documentation of the reason) is correct and the implementation is consistent throughout the stack. Here are my findings: Strengths
Issues FoundMinor Bug:
|
Summary
Adds the ability to favorite MeshCore nodes of any role (Companion, Repeater, Room Server, …), pinning favorited nodes to the top of the node list — consistent with how Meshtastic node favorites are displayed.
MeshCore firmware has no native favorite concept, so unlike Meshtastic (which round-trips a
SetFavoriteNodeadmin message to the device), the favorite flag is stored server-side only and is never pushed to the device.Closes #3588
What changed
Storage (per-source, all 3 backends)
src/db/schema/meshcoreNodes.ts— newisFavoriteboolean column (SQLite/PostgreSQL/MySQL).src/server/migrations/094_add_meshcore_node_favorite.ts— idempotent column add across all three backends; registered insrc/db/migrations.ts;migrations.test.tscount bumped to 94.src/db/repositories/meshcore.ts—setNodeFavorite(sourceId, publicKey, isFavorite)using the existing stub-insert pattern (lets a user favorite a node seen only in-memory). Fully source-scoped via the composite PK.Backend (no device IO)
src/server/meshcoreManager.ts—setNodeFavorite()(local-only) + mapsisFavoriteingetAllNodes().src/server/routes/meshcoreRoutes.ts—POST /api/sources/:id/meshcore/nodes/:publicKey/favorite, gated bynodes:write(matches the Meshtastic favorite endpoint), backfills node identity from the in-memory contact, and performs no device round-trip.Frontend
MeshCoreNodesView.tsx— per-row star toggle; favorites pinned to top by sorting favorites/non-favorites independently then concatenating (mirrors the MeshtasticuseProcessedNodesconvention).hooks/useMeshCore.ts—setNodeFavoriteaction with optimistic update;recomputeNodesnow carries forward the favorite flag so a contact push can't transiently un-pin a node before the next snapshot reconciles from the DB.MeshCorePage.css— star button styling.Tests
src/db/repositories/meshcoreFavorite.perSource.test.ts— per-source isolation (favoriting under one source doesn't leak into another sharing the same public key), stub-seed, no-clobber, un-favorite.src/server/routes/meshcoreRoutes.test.ts— auth, malformed-key + non-boolean validation, local-only behavior (assertsmanager.setNodeFavoritecalled, no device path), contact backfill, un-favorite.meshcore.test.tsfor the new column.Verification
tsc -p tsconfig.server.json --noEmit: clean.NodeJS/anyfindings are pre-existing in untouched regions).🤖 Generated with Claude Code