fix(meshcore): filter empty channel slots + reconcile stale DB rows on sync#3040
Conversation
…n sync `syncChannelsFromDevice` was mirroring every slot meshcore.js reported, including the all-zero placeholders the Companion firmware returns for unconfigured slots. MeshCore Companion's `cmd_get_channel` does NOT error on an unused idx — it returns success with an empty name and a 16-byte zero secret. meshcore.js's `getChannels()` enumerates until the firmware errors, so the full slot table (MAX_CHANNELS, typically 40 on Companion builds) leaked into the channels view as 38 empty "Channel N" placeholders alongside the user's real channels. This patch: - Adds `isConfiguredMeshCoreChannel(ch)` — a slot is configured iff it has a non-empty name OR a non-zero secret. That keeps the legit edge case where a user generated a real key but left the display name blank. - `syncChannelsFromDevice` now filters with that predicate before upserting, so empty slots never reach the DB. - After upserting the configured slots, it reconciles by enumerating the existing DB rows for this source and deleting any whose idx isn't in the configured set. This covers both (a) cleaning up legacy installs that already have the 38 leaked rows in their DB and (b) reflecting an out-of-band delete via meshcore-cli on the next sync. Phase 3's "Add channel" UI is unaffected — `nextFreeIdx` walks 0..255 looking for the lowest unused slot in the *filtered* channel list, so slot 1 stays available even after the firmware reports it as "empty" in its 40-slot table. Tests - meshcoreManager.channels.test.ts: +3 cases — empty-slot filter, "name blank but real secret kept", and reconcile-deletes-stale-row. - All previous tests still pass (existing fixtures had no empty slots and no pre-existing rows, so the reconcile path is a no-op for them). Full suite: 4959 PASS / 0 FAIL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job PR Review Complete ✅This PR provides a solid fix for the MeshCore channel slots issue. Here's my comprehensive review: ✅ Code Quality & Best PracticesExcellent implementation:
Good practices followed:
✅ Bug Prevention & LogicKey fixes implemented correctly:
Potential edge cases considered:
✅ Performance ConsiderationsEfficient implementation:
No performance regressions:
✅ Security AssessmentNo security concerns identified:
✅ Test Coverage - ExcellentComprehensive new tests in
Test quality:
📝 Minor Suggestions
🎯 Overall AssessmentExcellent PR - This is a well-researched, thoroughly tested fix that:
Recommend approval - The fix is production-ready and the test coverage gives confidence in the implementation. |
System Test ResultsMeshMonitor System Test ResultsTest Run: 2026-05-15 22:01:42 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:
|
The phase-3 routes were calling `resolveSourceManager(sourceId)` for
MeshCore writes. That helper only knows about the Meshtastic
`sourceManagerRegistry` and silently falls back to the global
`meshtasticManager` singleton on a miss. The Meshtastic singleton has
no `setChannel` / `deleteChannel` methods, so the unsafe cast in the
route surfaced as a runtime TypeError ("mcManager.setChannel is not a
function") when the user clicked Save on the new Add-channel UI.
MeshCore managers live in their own `meshcoreManagerRegistry`
(src/server/meshcoreRegistry.ts), separate from the Meshtastic one.
Both PUT /api/channels/:id and DELETE /api/channels/:id now pull
their MeshCore manager from that registry. If the manager isn't
present (source not connected) the route returns 503 with a clear
message instead of silently invoking the Meshtastic fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Phase 2 of the MeshCore channels feature tags every locally-sent
channel message with `toPublicKey = 'channel-${idx}'` so the per-
channel filter in MeshCoreChannelsView can group sent messages back
into the right tab. Those synthetic keys are NOT real DM peers, but
`MeshCoreDirectMessagesView` was harvesting every distinct
`toPublicKey` and listing them in the sidebar — so the DMs view was
showing fake "channel-0", "channel-1" peers alongside real contacts.
Adds an `isChannelPseudoKey` predicate that rejects keys starting
with `channel-`, and applies it in both `dmPeers` (sidebar list) and
the per-conversation `filtered` projection. The contacts loop is
also guarded so a stray channel-shaped contact key wouldn't slip
through.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job PR Review: MeshCore Empty Channel Slots FixReviewing this PR that addresses the 40-channel issue where MeshCore sources were showing empty placeholder rows. Review Checklist
✅ Excellent PR - Comprehensive Fix for MeshCore Channel IssueThis PR provides a well-engineered solution to the 40-channel leak problem. Here's my detailed analysis: 🔧 Implementation Quality - OutstandingClean Architecture (
Excellent Reconciliation Logic (
🛡️ Edge Case Handling - ComprehensivePrimary Issue Resolved: Filters empty slots using
Channel Pseudo-Key Filtering (
⚡ Performance Considerations - EfficientOptimal Data Flow:
No Performance Regressions: Connect-time sync remains efficient, channel CRUD maintains same performance profile 🔒 Security Assessment - CleanNo Security Concerns:
🧪 Test Coverage - ExceptionalThree comprehensive test cases (
Test Quality Highlights:
📋 Minor Enhancement Opportunities
🎯 Overall AssessmentThis is production-ready code that:
Recommendation: Approve and merge. This fix is well-researched, thoroughly tested, and addresses both the immediate issue and long-term data consistency. |
System Test ResultsMeshMonitor System Test ResultsTest Run: 2026-05-15 23:09:11 EDT Test Summary
❌ Overall Result: FAILEDSome tests failed. Please review the failures above and fix before creating/updating PR. Failed Tests
|
The local node was showing up as a DM peer in the MeshCore DMs view sidebar — usually because MeshCore seeds the local node's own pubkey into the contacts list, and the "Always include all contacts so the user can start a new DM" loop in `dmPeers` was happily adding it. DMing yourself is meaningless. After computing the peer set, drop any entry whose key matches the locally-connected node's pubkey (using the existing keysMatch helper so a prefix-only match — the inbound `pubkey_prefix` case — is caught too). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job PR Review: MeshCore Empty Channel Slots FixReviewing this PR that addresses the 40-channel issue where MeshCore sources were showing empty placeholder rows. Review Checklist
✅ Excellent PR - Comprehensive Fix for MeshCore Channel IssueThis PR provides a well-engineered solution to the 40-channel leak problem. Here's my detailed analysis: 🔧 Implementation Quality - OutstandingClean Architecture (
Excellent Reconciliation Logic (
🛡️ Edge Case Handling - ComprehensivePrimary Issue Resolved: Filters empty slots using
Channel Pseudo-Key Filtering (
⚡ Performance Considerations - EfficientOptimal Data Flow:
No Performance Regressions: Connect-time sync remains efficient, channel CRUD maintains same performance profile 🔒 Security Assessment - CleanNo Security Concerns:
🧪 Test Coverage - ExceptionalThree comprehensive test cases (
Test Quality Highlights:
📋 Minor Enhancement Opportunities
🎯 Overall AssessmentThis is production-ready code that:
Recommendation: Approve and merge. This fix is well-researched, thoroughly tested, and addresses both the immediate issue and long-term data consistency. |
Bumps the version across all five canonical files (package.json, package-lock.json, helm/meshmonitor/Chart.yaml, desktop/src-tauri/tauri.conf.json, desktop/package.json) for the 4.5.2 release. Headline: MeshCore channel support is the major feature in this release. - Phase 1 (#3034) — MeshCoreManager exposes listChannels / setChannel / deleteChannel and mirrors the device's channel list into the shared `channels` table on connect. - Phase 2 (#3038) — MeshCoreChannelsView renders one tab per channel from the synced list; the send path is channel-idx aware. - Phase 3 (#3039) — Configuration UI adds, edits, regenerates the secret of, and deletes channels on the device; PUT/DELETE /api/channels/:id route writes through to MeshCore via the correct registry. - Polish (#3040) — empty-slot filter (the MAX_CHANNELS=40 leak), DB reconcile for stale empty rows, DM-view filters out channel pseudo-pubkeys and the local node from the peer sidebar. Also includes the per-node position-precision fix (#3033) from this release window. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
System Test ResultsMeshMonitor System Test ResultsTest Run: 2026-05-15 23:43:04 EDT Test Summary
❌ Overall Result: FAILEDSome tests failed. Please review the failures above and fix before creating/updating PR. Failed Tests
|
Fixes the 40-channel issue reported after deploying the MeshCore channels feature: every freshly-connected Companion source was showing 38 empty placeholder rows alongside the real channels.
Root cause
MeshCore Companion firmware's `cmd_get_channel` does not error on an unused slot — it returns success with an empty name and a 16-byte all-zero secret. meshcore.js's `getChannels()` enumerates until the firmware errors, so it walks the whole slot table — MAX_CHANNELS, typically 40 on Companion builds. `syncChannelsFromDevice` was mirroring every slot it received into the `channels` DB table, including the 38 empty placeholders.
Fix
"Add channel" still works
Phase 3's `nextFreeIdx` walks 0..255 looking for the lowest unused index in the filtered channel list. After this fix, slot 1 (and 2, 3, … 39) are correctly recognised as free even though the firmware still reports them as empty in its 40-slot table. The PUT /api/channels/ route already accepts any idx 0..255 for MeshCore sources (phase 3).
Test plan
New tests
`meshcoreManager.channels.test.ts` (+3):
🤖 Generated with Claude Code