Skip to content

fix(meshcore): preserve hop/route/scope info in channel message history - #4475

Merged
Yeraze merged 1 commit into
mainfrom
claude/vibrant-volta-5ey0l7
Aug 1, 2026
Merged

fix(meshcore): preserve hop/route/scope info in channel message history#4475
Yeraze merged 1 commit into
mainfrom
claude/vibrant-volta-5ey0l7

Conversation

@Yeraze

@Yeraze Yeraze commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Fixes #4474

Summary

  • MeshCoreManager.getChannelMessages backs the /api/sources/:id/meshcore/messages/channel/:idx endpoint, used for both the initial per-channel message load and the infinite-scroll "load older" pagination ([BUG] Meshcore scrolling up on channel doesn't load older messages #4460/fix(meshcore): infinite-scroll pagination for channel message history #4461).
  • Its DB-row → API mapping dropped hopCount, routePath, scopeCode, and scopeName, even though these are persisted columns on meshcore_messages.
  • Live messages come from a separate in-memory pool (populated on receive, and correctly re-hydrated with these fields on connect()), which is why hop/path/scope info showed up on a message right when it arrived, then disappeared once that message was instead paginated in from history — matching the reported "disappears on older messages" behavior.
  • Fix: forward hopCount / routePath / scopeCode / scopeName from the DB row in getChannelMessages, mirroring the mapping already done in connect()'s DB rehydration.

Test plan

  • Added src/server/meshcoreManager.channelMessagesFields.test.ts — regression test asserting getChannelMessages forwards these fields (and defaults to null when the DB row has none).
  • npx vitest run src/server/meshcoreManager*.test.ts src/server/routes/meshcoreRoutes.test.ts src/db/repositories/meshcore.test.ts — 43 files / 607 tests passed.
  • npx tsc --noEmit — clean.
  • npm run lint:ci — clean (no new ratchet violations).

Generated by Claude Code

…ry (#4474)

MeshCoreManager.getChannelMessages backs the /messages/channel/:idx
endpoint used for both the initial channel load and infinite-scroll
load-older, but its DB-row-to-API mapping dropped hopCount, routePath,
scopeCode, and scopeName even though they're persisted columns. Live
messages (in-memory pool, hydrated on connect/receive) carried these
fields, so hop/path info was visible on newer messages but vanished for
anything paginated in from history.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wpq1d3ZYDGqgJ4SJ5WrKJG
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

Yeraze commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

The claude-review check failure isn't related to this PR's diff — the job log shows the automated review bot hit API Error: 401 ... "OAuth access token has been revoked." before it ever got to analyzing code. That's an auth/credential problem with the repo's Claude GitHub App integration itself, not something fixable from this branch. Flagging it here in case the token needs to be reissued; I'll keep watching the real test-suite checks (Test Suite (22.x/24.x/25.x), Security Scan, ClamAV Scan, etc.) and report back once those land.

-- Authored by Roger 🤓


Generated by Claude Code

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: fix(meshcore): preserve hop/route/scope info in channel message history

Tasks:

  • Gather context and read changed files
  • Review code changes in meshcoreManager.ts
  • Review test coverage in meshcoreManager.channelMessagesFields.test.ts
  • Post final review

Summary

This is a clean, focused fix for a clear bug. The root cause analysis in the PR description is accurate: the getChannelMessages DB→API mapping was simply missing four field passthroughs that the connect() rehydration path already had at meshcoreManager.ts:1112-1115.

Code Changes (meshcoreManager.ts:5929-5932)

Correctness: The fix is correct. The four added lines in getChannelMessages now mirror exactly what connect() does at line 1112-1115. The ?? null coercion is appropriate — it normalizes undefined (absent column) to null, consistent with the MeshCoreMessage interface which types all four as T | null.

pathLen — intentionally absent: pathLen is in the MeshCoreMessage interface but is not a column in meshcore_messages (confirmed in src/db/schema/meshcoreMessages.ts). It's only available on live device events (data.path_len). So the omission here is correct — no gap in this fix.

messageType / expectedAckCrc / estTimeout are also not persisted columns, so their absence from the mapping is similarly intentional.

Code style: Consistent with the surrounding mapping pattern. No issues.

Test Coverage (meshcoreManager.channelMessagesFields.test.ts)

The tests are well-structured:

  • Happy path (line 24-48): passes all four fields from a mock DB row and asserts each is forwarded. Good direct regression coverage.
  • Null/absent path (line 50-70): a mock row with no route fields confirms the ?? null fallback produces null (not undefined), which matches the interface contract and prevents surprises in UI consumers.

One minor observation: the mock DB row in the second test omits fromName, rssi, snr, and sourceId too — these are optional in the DB type, and the mock resolves fine. No issue.

Minor nit: The test file uses vi.restoreAllMocks() in beforeEach, which is correct. However since these are vi.spyOn calls on the databaseService singleton, afterEach with vi.restoreAllMocks() is more conventional (clean up after, not before). This is purely stylistic — beforeEach still works here because there are no shared spy states leaking between tests.

No concerns on:

  • Security: No user input flows through this path; only persisted DB values are forwarded.
  • Performance: No new queries; same two DB calls, same map pass.
  • Multi-source scoping: this.sourceId is already passed to getChannelMessages and getHeardRepeatersForMessages — unchanged.

Verdict

Approve. The fix is minimal, correct, and directly mirrors the already-working connect() path. Test coverage is adequate for a regression fix of this scope. The nit on beforeEach vs afterEach is not worth blocking.

@Yeraze
Yeraze merged commit 78890f6 into main Aug 1, 2026
15 of 16 checks passed
@Yeraze
Yeraze deleted the claude/vibrant-volta-5ey0l7 branch August 1, 2026 03:40
Yeraze added a commit that referenced this pull request Aug 1, 2026
Twelve changes since rc4, across all five version files (package.json,
package-lock.json, helm Chart.yaml, desktop/package.json, tauri.conf.json).

Highlights:
- Remote admin no longer holds an HTTP request across mesh round-trips
  (#4482#4485, #4486): commands, session-passkey acquisition, and remote
  config import all return 202 + an operation id and complete in the
  background, fixing upstream 502s behind a reverse proxy. Remote
  ignore/unignore gained routing-ACK confirmation.
- Admin messages now use the node's configured LoRa hop limit instead of a
  hardcoded 3 (#4479), so nodes further than 3 hops away are administrable.
- GET /stats no longer 500s when sourceId is omitted (#4470).
- MeshCore fixes: anon auth banner, channel-sync data loss, reply/trigger
  ordering (#4491), channel view scroll/delete (#4488), hop/route/scope
  preserved in channel history (#4475).
- Navigation unified across Meshtastic/MeshCore per-source views (#4481,
  #4484).

Carries the system-test label: this is the first release build to exercise
the async remote-admin paths against real hardware — the Configuration
Import leg covers /import-config, which has not run on a device until now.


Claude-Session: https://claude.ai/code/session_015cgD5kSurLjdeVkZ6PcQD9

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Meshcore path and hops information disappears on older messages

2 participants