Conversation
…oise Concurrent MeshCore connect auto-login was racing pathSync on every node-list change; silent bulk getWaitingMessages always waited 45s before fallback. Collapse auto-login to a single flight, skip bulk after consecutive timeouts, and stop logging expected TCP/ResizeObserver races as errors.
Auto /who was gated by a panel ref, so returning to RRC remounted and polled again, dumping member lists into chat. Keep the once-per-join gate on the hub session, show the first roster line only, and route unscoped hub notices to [hub].
Apply the 48-node layout budget only when distant peers are hidden; use 400 when they are shown. Document the cap, show it on both panels, and add a Reticulum Topology RF-only filter.
Pin reviewed-ref baselines so already-reviewed tags (Ratspeak v1.0.25) and repos without GitHub Releases (LXMFace) no longer look like errors. Scan Four in a Row only when a published release is newer than the pin.
Meshtastic TCP writes now fail in the renderer when the socket is gone so frames are not silently dropped. Room auto-login single-flight survives disconnect, RF-only topology matches configured spokes exactly, and RRC /who no longer skips a full roster after a partial nicklist.
The nearby hop ceiling was ANDed with Max hops, so hop 2/8 was a no-op on MeshCore/Meshtastic Graph. Apply that ceiling only when Max hops is All.
Unknown hops no longer pass numeric Max hops, and the 48-node cap no longer hides real 1-hop peers behind Show distant. Layout budget is 400 after hop filters.
|
Warning Review limit reached
Next review available in: 27 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: QUIET Plan: Pro Plus Run ID: 📒 Files selected for processing (31)
📝 WalkthroughWalkthroughThe PR updates release monitoring, topology limits and filters, RRC ChangesUpstream release monitoring
Topology filtering and rendering
RRC
MeshCore connection workflows
Transport and logging
Documentation and package updates
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/renderer/runtime/useMeshcoreRuntime.ts (1)
6034-6049: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftInvalidate and recheck the auto-login generation at the SendLogin boundary.
A connection-loss or connection-replacement path does not call
resetMeshcoreRoomAutoLoginSingleFlight(). Also, a reset can occur after the current stale check and beforerepeaterRemoteRpcRef.current()executes its callback. The stale pass can then callmeshcoreRoomLogin()withconnRef.currentfrom a new connection.
src/renderer/runtime/useMeshcoreRuntime.ts#L6034-L6049: RecheckabortIfStaleinside the serialized callback immediately beforemeshcoreRoomLogin().src/renderer/runtime/useMeshcoreRuntime.ts#L3274-L3274: Reset the generation from all connection teardown and supersession paths, including connection loss,prepareRfConnect, and unmount cleanup.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/runtime/useMeshcoreRuntime.ts` around lines 6034 - 6049, In src/renderer/runtime/useMeshcoreRuntime.ts lines 6034-6049, recheck opts?.abortIfStale?.() inside the serialized repeaterRemoteRpcRef.current callback immediately before meshcoreRoomLogin(), aborting with the existing stale-login error when invalidated. In src/renderer/runtime/useMeshcoreRuntime.ts line 3274, update resetMeshcoreRoomAutoLoginSingleFlight() usage so the generation resets across every connection teardown or supersession path, including connection loss, prepareRfConnect, and unmount cleanup.src/renderer/runtime/useReticulumRuntime.ts (1)
1163-1202: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDrop parsed
/whonotices that do not match a joined room.When
parseRrcWhoNotice()succeeds butrrcWhoNoticeJoinedRoom()returnsnull, this code continues toaddMessage(). A stale response after PART, or a hub notice for an unjoined room, is then persisted and shown in the wire room or[hub].Return before transcript handling when
whoexists andwhoRoomisnull. Keep roster replacement and transcript-slot consumption limited to joined rooms.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/runtime/useReticulumRuntime.ts` around lines 1163 - 1202, In the notice-handling flow around parseRrcWhoNotice and rrcWhoNoticeJoinedRoom, return before transcript handling when who is present but whoRoom is null. Ensure unmatched /who notices cannot reach addMessage or wire/[hub] transcript persistence, while keeping mergeRoomMembers and consumeWhoTranscriptSlot restricted to joined rooms.src/renderer/components/RrcPanel.tsx (1)
802-817: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReserve the transcript slot for the requested
/whoroom.When the active room is
generaland the user sends/who lobby, this code reservesgeneral. Thelobbyresponse can then be suppressed after its first snapshot, while a latergeneralresponse can appear unexpectedly.Parse the
/whoargument withrrcWhoCommandToken(). Reserve force only for the resolved joined room that the command targets.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/components/RrcPanel.tsx` around lines 802 - 817, Update the `/who` handling in the message-send flow to parse the command argument with `rrcWhoCommandToken()`, resolve the targeted joined room, and pass that room to `reserveWhoTranscriptForce` instead of always using `activeRoom`. Only reserve force when the resolved target is a valid non-DM joined room, while preserving existing behavior for non-`/who` messages.
🟡 Other comments (4)
AGENTS.md-121-121 (1)
121-121: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winDescribe the LXMFace baseline correctly.
Line 121 states that LXMFace uses a published-release watch.
scripts/update.shusesfile:js/lxmface.js@<sha>and checks the latest commit that changed that file. State that LXMFace uses a vendored-file commit baseline.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AGENTS.md` at line 121, Update the LXMFace description in the “Update script sync” section to state that its baseline is a vendored-file commit, using the commit for file:js/lxmface.js and the latest commit that changed that file, rather than describing it as a published-release watch.src/renderer/lib/topologyGraphLimits.ts-20-23 (1)
20-23: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winCorrect the stale 48-node-cap description.
topologyGraphVisibleNodeCap()always returns the 400-node cap. Hiding distant peers changes hop eligibility. It does not reduce the post-filter layout budget to 48 nodes.
src/renderer/lib/topologyGraphLimits.ts#L20-L23: Change the comment to state that the layout budget remains 400 after hop filtering.docs/reticulum.md#L138-L138: Remove the claim that hidden distant peers use a 48-node drawn-graph cap.docs/troubleshooting.md#L1762-L1768: Remove 48 as an active visible-node limit and update the fix guidance.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/lib/topologyGraphLimits.ts` around lines 20 - 23, Update the comment for topologyGraphVisibleNodeCap() to state that the post-hop-filter layout budget remains 400 nodes; in docs/reticulum.md lines 138-138, remove the claim that hidden distant peers impose a 48-node drawn-graph cap; in docs/troubleshooting.md lines 1762-1768, remove 48 as an active visible-node limit and revise the troubleshooting guidance accordingly.src/renderer/lib/reticulum/buildReticulumTopologyLayout.ts-270-278 (1)
270-278: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReserve the self-node slot during breadth-first expansion.
Line 255 reserves one cap slot for
SELF_ID. Lines 270-278 can still addcapnon-self IDs. The caller then renders self pluscappeers, which exceeds the configured visible-node cap by one.Proposed fix
- while (queue.length > 0 && visible.size < cap) { + const peerBudget = Math.max(0, cap - 1); + while (queue.length > 0 && visible.size < peerBudget) { const current = queue.shift()!; for (const neighbor of adj.get(current) ?? []) { if (neighbor === SELF_ID || visited.has(neighbor)) continue; if (!filteredIds.includes(neighbor)) continue; visited.add(neighbor); visible.add(neighbor); queue.push(neighbor); - if (visible.size >= cap) break; + if (visible.size >= peerBudget) break; } }Add a layout-overflow test that asserts the final graph, including self, does not exceed the cap.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/lib/reticulum/buildReticulumTopologyLayout.ts` around lines 270 - 278, Update the breadth-first expansion around the queue loop to reserve one slot for SELF_ID by limiting non-self additions to cap - 1, while preserving filtering and traversal behavior. Ensure the final rendered graph, including SELF_ID, never exceeds the configured cap, and add a layout-overflow test asserting that total graph nodes remain within the cap.src/main/index.ts-6400-6402 (1)
6400-6402: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winHandle stale socket writes as expected disconnects
A stale socket reference can survive after
destroy(). In this state,sock.write()reportsERR_STREAM_DESTROYEDorERR_STREAM_WRITE_AFTER_END, and the handler rejects. Resolve these expected disconnect errors as'no-socket'. Continue rejecting genuine write failures. Add regression coverage with a destroyed socket after the reference check.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/index.ts` around lines 6400 - 6402, Update the meshtastic TCP write handler around the active-socket check to catch write errors indicating a destroyed or ended socket and resolve them as 'no-socket'. Continue propagating all other write failures, and add regression coverage using a destroyed socket after the reference check.
🧹 Nitpick comments (2)
src/renderer/lib/transportTcpIpc.test.ts (1)
80-90: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert that the rejected payload is not replayed.
The test verifies the error, but it does not explicitly assert the no-replay contract. Add a call-count assertion so this failure path cannot issue duplicate writes.
Suggested regression assertion
await expect(writer.write(new Uint8Array([1, 2, 3]))).rejects.toThrow( 'meshtastic:tcp-write: no active socket', ); + expect(window.electronAPI.meshtastic.tcp.write).toHaveBeenCalledTimes(1); writer.releaseLock();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/lib/transportTcpIpc.test.ts` around lines 80 - 90, Add a call-count assertion to the `toDevice.write` rejection test, verifying `window.electronAPI.meshtastic.tcp.write` is invoked exactly once after the rejected `writer.write` call. Keep the existing error assertion and lock cleanup unchanged.src/main/log-service.test.ts (1)
417-426: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCover the optional prefix path.
The tests cover the canonical strings but not the normalization branch in
src/main/log-service.ts:412-418. Add an assertion with the optional[Violation]prefix and surrounding whitespace. Otherwise, that behavior can regress while the suite remains green.Proposed test
expect(isDroppableRendererConsoleNoise('ResizeObserver loop limit exceeded')).toBe(true); + expect( + isDroppableRendererConsoleNoise( + ' [Violation] ResizeObserver loop completed with undelivered notifications. ', + ), + ).toBe(true);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/log-service.test.ts` around lines 417 - 426, Add a test assertion in the isDroppableRendererConsoleNoise test for a ResizeObserver warning containing the optional “[Violation]” prefix and surrounding whitespace, and verify it returns true. Keep the existing canonical warning assertions unchanged.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/renderer/components/RrcPanel.tsx`:
- Around line 276-289: Update requestRoomWho() in
src/renderer/components/RrcPanel.tsx:276-289 to await the send result and
release automatic request state for rejected sends or resolved { ok: false }
results; clear forced transcript state whenever a forced send fails. Update
sendHubCommand() in src/renderer/components/RrcPanel.tsx:241-260 to clear forced
transcript state on unsuccessful completion. Add tests in
src/renderer/components/RrcPanel.test.tsx:651-669 covering resolved { ok: false
} sends and failed forced reservations.
In `@src/renderer/hooks/meshcore/meshcoreConnSideEffects.ts`:
- Around line 312-314: Update drainWaitingMessagesIncremental and the
skipped-drain branch in src/renderer/hooks/meshcore/meshcoreConnSideEffects.ts
(lines 312-314) to propagate successful retrieval and invoke
noteMeshcoreSilentBulkSuccess only after that success, allowing the breaker to
reset. Update src/renderer/hooks/meshcore/meshcoreConnSideEffects.test.ts (lines
425-452) to open the breaker, complete incremental retrieval successfully, and
verify the next silent drain retries getWaitingMessages(). Update
docs/agents/meshcore-repeaters.md (line 19) only as needed to retain the
reconnect-or-success statement now supported by production behavior.
In `@src/renderer/lib/reticulum/reticulumTopologyPeerRenderSelect.ts`:
- Around line 21-32: Update selectReticulumTopologyPeersForRender to apply the
active hop and nearby-hop eligibility filters before sorting and enforcing
TOPOLOGY_PEER_RENDER_CAP, reusing the same filtering behavior expected by
buildReticulumMeshTopologyGraph. Preserve rfOnly filtering, then add a
regression test covering more than 800 fresh ineligible peers followed by older
eligible peers and verify the eligible peers are retained.
In `@src/renderer/runtime/useReticulumRuntime.rrc.test.ts`:
- Around line 42-48: Replace the SOURCE string-matching test in the runtime
behavior suite with tests that dispatch rrc.message events through the runtime
or a dedicated testable event-handler helper. Assert joined-room roster
replacement, display of the first /who notice, suppression of subsequent notices
via consumeWhoTranscriptSlot, and rejection of unjoined-room messages, using
observable runtime state or outputs rather than source text.
---
Outside diff comments:
In `@src/renderer/components/RrcPanel.tsx`:
- Around line 802-817: Update the `/who` handling in the message-send flow to
parse the command argument with `rrcWhoCommandToken()`, resolve the targeted
joined room, and pass that room to `reserveWhoTranscriptForce` instead of always
using `activeRoom`. Only reserve force when the resolved target is a valid
non-DM joined room, while preserving existing behavior for non-`/who` messages.
In `@src/renderer/runtime/useMeshcoreRuntime.ts`:
- Around line 6034-6049: In src/renderer/runtime/useMeshcoreRuntime.ts lines
6034-6049, recheck opts?.abortIfStale?.() inside the serialized
repeaterRemoteRpcRef.current callback immediately before meshcoreRoomLogin(),
aborting with the existing stale-login error when invalidated. In
src/renderer/runtime/useMeshcoreRuntime.ts line 3274, update
resetMeshcoreRoomAutoLoginSingleFlight() usage so the generation resets across
every connection teardown or supersession path, including connection loss,
prepareRfConnect, and unmount cleanup.
In `@src/renderer/runtime/useReticulumRuntime.ts`:
- Around line 1163-1202: In the notice-handling flow around parseRrcWhoNotice
and rrcWhoNoticeJoinedRoom, return before transcript handling when who is
present but whoRoom is null. Ensure unmatched /who notices cannot reach
addMessage or wire/[hub] transcript persistence, while keeping mergeRoomMembers
and consumeWhoTranscriptSlot restricted to joined rooms.
---
Other comments:
In `@AGENTS.md`:
- Line 121: Update the LXMFace description in the “Update script sync” section
to state that its baseline is a vendored-file commit, using the commit for
file:js/lxmface.js and the latest commit that changed that file, rather than
describing it as a published-release watch.
In `@src/main/index.ts`:
- Around line 6400-6402: Update the meshtastic TCP write handler around the
active-socket check to catch write errors indicating a destroyed or ended socket
and resolve them as 'no-socket'. Continue propagating all other write failures,
and add regression coverage using a destroyed socket after the reference check.
In `@src/renderer/lib/reticulum/buildReticulumTopologyLayout.ts`:
- Around line 270-278: Update the breadth-first expansion around the queue loop
to reserve one slot for SELF_ID by limiting non-self additions to cap - 1, while
preserving filtering and traversal behavior. Ensure the final rendered graph,
including SELF_ID, never exceeds the configured cap, and add a layout-overflow
test asserting that total graph nodes remain within the cap.
In `@src/renderer/lib/topologyGraphLimits.ts`:
- Around line 20-23: Update the comment for topologyGraphVisibleNodeCap() to
state that the post-hop-filter layout budget remains 400 nodes; in
docs/reticulum.md lines 138-138, remove the claim that hidden distant peers
impose a 48-node drawn-graph cap; in docs/troubleshooting.md lines 1762-1768,
remove 48 as an active visible-node limit and revise the troubleshooting
guidance accordingly.
---
Nitpick comments:
In `@src/main/log-service.test.ts`:
- Around line 417-426: Add a test assertion in the
isDroppableRendererConsoleNoise test for a ResizeObserver warning containing the
optional “[Violation]” prefix and surrounding whitespace, and verify it returns
true. Keep the existing canonical warning assertions unchanged.
In `@src/renderer/lib/transportTcpIpc.test.ts`:
- Around line 80-90: Add a call-count assertion to the `toDevice.write`
rejection test, verifying `window.electronAPI.meshtastic.tcp.write` is invoked
exactly once after the rejected `writer.write` call. Keep the existing error
assertion and lock cleanup unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: QUIET
Plan: Pro Plus
Run ID: ea8c6bc4-891b-4764-ae50-9f047fd31ff5
⛔ Files ignored due to path filters (17)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!**/pnpm-lock.yamlsrc/renderer/locales/cs/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/de/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/en/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/es/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/fr/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/id/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/it/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/ja/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/ko/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/nl/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/pl/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/pt-BR/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/ru/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/tr/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/uk/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/zh/translation.jsonis excluded by!src/renderer/locales/**
📒 Files selected for processing (58)
AGENTS.mdREADME.mddocs/agents/meshcore-repeaters.mddocs/agents/meshcore-rooms.mddocs/agents/reticulum.mddocs/ci-cd.mddocs/diagnostics.mddocs/reticulum-games-parity.mddocs/reticulum.mddocs/troubleshooting.mdpackage.jsonscripts/update.shscripts/update.test.mjssrc/main/index.contract.test.tssrc/main/index.tssrc/main/log-service.test.tssrc/main/log-service.tssrc/preload/index.tssrc/renderer/components/PeerGraphPanel.test.tsxsrc/renderer/components/PeerGraphPanel.tsxsrc/renderer/components/ReticulumTopologyPanel.test.tsxsrc/renderer/components/ReticulumTopologyPanel.tsxsrc/renderer/components/RrcPanel.test.tsxsrc/renderer/components/RrcPanel.tsxsrc/renderer/components/TopologyHopFilterControls.tsxsrc/renderer/components/TopologyVisibleLimitNote.test.tsxsrc/renderer/components/TopologyVisibleLimitNote.tsxsrc/renderer/hooks/meshcore/meshcoreConnSideEffects.test.tssrc/renderer/hooks/meshcore/meshcoreConnSideEffects.tssrc/renderer/lib/buildMeshPeerTopologyGraph.test.tssrc/renderer/lib/buildMeshPeerTopologyGraph.tssrc/renderer/lib/meshcoreRoomAutoLoginOnConnect.test.tssrc/renderer/lib/meshcoreRoomAutoLoginOnConnect.tssrc/renderer/lib/meshcoreWaitingMessagesDrain.test.tssrc/renderer/lib/meshcoreWaitingMessagesDrain.tssrc/renderer/lib/meshtastic/meshtasticTransportLossDetection.test.tssrc/renderer/lib/meshtastic/meshtasticTransportLossDetection.tssrc/renderer/lib/reticulum/buildReticulumTopologyLayout.test.tssrc/renderer/lib/reticulum/buildReticulumTopologyLayout.tssrc/renderer/lib/reticulum/reticulumTopologyPeerRenderSelect.test.tssrc/renderer/lib/reticulum/reticulumTopologyPeerRenderSelect.tssrc/renderer/lib/reticulum/reticulumTopologyRfFilter.test.tssrc/renderer/lib/reticulum/reticulumTopologyRfFilter.tssrc/renderer/lib/rrcMessageDisplay.test.tssrc/renderer/lib/rrcMessageDisplay.tssrc/renderer/lib/rrcNoticeParsers.test.tssrc/renderer/lib/rrcRoomName.test.tssrc/renderer/lib/rrcRoomName.tssrc/renderer/lib/timeConstants.tssrc/renderer/lib/topologyGraphLimits.test.tssrc/renderer/lib/topologyGraphLimits.tssrc/renderer/lib/transportTcpIpc.test.tssrc/renderer/runtime/useMeshcoreRuntime.tssrc/renderer/runtime/useReticulumRuntime.rrc.test.tssrc/renderer/runtime/useReticulumRuntime.tssrc/renderer/stores/rrcSessionStore.test.tssrc/renderer/stores/rrcSessionStore.tssrc/shared/electron-api.types.ts
| if (shouldSkipMeshcoreSilentBulkGetWaitingMessages()) { | ||
| await drainWaitingMessagesIncremental(conn, state, deps); | ||
| return; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Reset the circuit after a successful skipped drain.
After Line 312 returns through drainWaitingMessagesIncremental(), Line 330 cannot call noteMeshcoreSilentBulkSuccess(). The breaker then remains open until a lifecycle reset. This conflicts with the documented “until reconnect/success” behavior and the PR objective to reset after success.
src/renderer/hooks/meshcore/meshcoreConnSideEffects.ts#L312-L314: propagate successful incremental retrieval fromdrainWaitingMessagesIncremental()and clear the breaker only after that success.docs/agents/meshcore-repeaters.md#L19-L19: retain the reconnect-or-success statement only after the production path supports it.src/renderer/hooks/meshcore/meshcoreConnSideEffects.test.ts#L425-L452: after opening the breaker, complete an incremental retrieval successfully and verify that the next silent drain retriesgetWaitingMessages().
📍 Affects 3 files
src/renderer/hooks/meshcore/meshcoreConnSideEffects.ts#L312-L314(this comment)docs/agents/meshcore-repeaters.md#L19-L19src/renderer/hooks/meshcore/meshcoreConnSideEffects.test.ts#L425-L452
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/hooks/meshcore/meshcoreConnSideEffects.ts` around lines 312 -
314, Update drainWaitingMessagesIncremental and the skipped-drain branch in
src/renderer/hooks/meshcore/meshcoreConnSideEffects.ts (lines 312-314) to
propagate successful retrieval and invoke noteMeshcoreSilentBulkSuccess only
after that success, allowing the breaker to reset. Update
src/renderer/hooks/meshcore/meshcoreConnSideEffects.test.ts (lines 425-452) to
open the breaker, complete incremental retrieval successfully, and verify the
next silent drain retries getWaitingMessages(). Update
docs/agents/meshcore-repeaters.md (line 19) only as needed to retain the
reconnect-or-success statement now supported by production behavior.
| it('suppresses later /who notices after mergeRoomMembers and consumeWhoTranscriptSlot', () => { | ||
| expect(SOURCE).toContain('resolveRrcInboundChatRoom'); | ||
| expect(SOURCE).toMatch(/room = resolveRrcInboundChatRoom\(/); | ||
| expect(SOURCE).toMatch(/mergeRoomMembers\(whoRoom, who\.members, 'replace', hubDestHash\)/); | ||
| expect(SOURCE).toMatch(/consumeWhoTranscriptSlot\(whoRoom, hubDestHash\)[\s\S]*?return;/); | ||
| expect(SOURCE).toMatch(/if \(who && whoRoom\) \{[\s\S]*?room = whoRoom/); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Replace source-text assertions with runtime behavior assertions.
This test passes when the expected strings remain in the source, even if event control flow no longer updates the roster or suppresses the transcript correctly.
Dispatch rrc.message events through the runtime, or extract the event handling into a testable helper. Assert joined-room replacement, first-notice display, later-notice suppression, and unjoined-room rejection.
As per coding guidelines, “Ship a passing test for behavioral changes.” As per path instructions, “Prefer behavioral assertions; skip style-only test nits.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/runtime/useReticulumRuntime.rrc.test.ts` around lines 42 - 48,
Replace the SOURCE string-matching test in the runtime behavior suite with tests
that dispatch rrc.message events through the runtime or a dedicated testable
event-handler helper. Assert joined-room roster replacement, display of the
first /who notice, suppression of subsequent notices via
consumeWhoTranscriptSlot, and rejection of unjoined-room messages, using
observable runtime state or outputs rather than source text.
Sources: Coding guidelines, Path instructions
Await RRC /who send results, hop-filter Topology ingest before the 800 cap, reset the MeshCore silent-bulk breaker after incremental success, and treat destroyed Meshtastic TCP sockets as no-socket.
Summary
This branch is a batch of user-facing and ops fixes across Graph/Topology, RRC, MeshCore Rooms, Meshtastic TCP, and
pnpm run update. Nothing here is a new product feature; each change closes a race, a misleading filter, or noisy false-positive logging.8 commits (
origin/main..HEAD):3ffef32b— MeshCore room auto-login stampede + waiting-message drain timeouts + expected reconnect log noise96f8287d— RRC hidden/whoflooding the room transcript11e6f38a— Graph/Topology 48-node layout budget hiding nodes despite hop filters; Topology RF only4cdc3587— dependency bumpsc426f8ff—pnpm run updateonly warns on newer published Ratspeak releasescb5299d8— review follow-ups: TCP write sentinel, auto-login across disconnect, exact RF matching,/whoroster merge7b542766— Graph Max hops was a no-op when Show distant was offd712adc9— unknown hops were treated as 1-hop neighbors; layout budget is 400 after hop filtersGraph / Topology hop filters and visible-node cap
Problem: Meshtastic/MeshCore Graph and Reticulum Topology could hide real nearby peers (or ignore the hop controls) for three independent reasons:
hops_away/hopsnull) were treated as 1-hop neighbors, so they passed numeric Max hops and the nearby ceiling.Fix (
topologyGraphLimits.ts, shared by both panels):FORCE_REPULSION_FULL_PAIR_CAP). The old 48 cap no longer hides real 1-hop peers behind Show distant.Reticulum Topology extras:
"RNode"does not keep"RNode_TCP_East".serial_portis passed through so BLE RNode classification is accurate.Docs: README known-issues,
docs/diagnostics.md,docs/troubleshooting.md,docs/reticulum.md. i18n keys:peerGraph.hiddenCountLimit,peerGraph.visibleNodeLimitNote,reticulumTopology.hiddenCountLimit,reticulumTopology.visibleNodeLimitNote,reticulumTopology.rfOnly.RRC: stop hidden
/whofrom flooding chatProblem: Auto
/who(needed because rrcd JOINED member lists are optional) was gated by a paneluseRef. Leaving RRC and coming back remounted the panel, cleared the gate, and polled/whoagain. Every snapshot dumped into the focused room as NOTICE text. Unscoped hub notices (emptyK_ROOM) also landed in whatever room was focused.Fix:
/whogate lives on the hub session (whoRequestedRooms), so it survives panel remount. Cleared on part / hub teardown; failed sends release the slot./whosnapshot may appear in the named room; later snapshots update the nicklist only (whoTranscriptShownRooms). Refresh / composer/whocan force one more transcript line (reserveWhoTranscriptForce)./whois a hub-global slash command: omitK_ROOM, send/who <token>only (token rejects whitespace / extra slash).K_ROOMgo to[hub], never the focused chat room./whoNOTICE room is resolved against joined rooms only (rrcWhoNoticeJoinedRoom) so a partial nicklist cannot skip a later full roster, and an unknown/evil room name is ignored.MeshCore: room auto-login stampede and waiting-message drain
Problem: Connect auto-login re-ran
pathSyncon every node-list change (unrelated advert churn). Overlapping passes raced the companion RF lane. Disconnect did not invalidate an in-flight pass, so a dying login couldSendLoginon the new connection. Silent bulkgetWaitingMessagesalways waited the full 45s timeout before falling back, even after consecutive failures.Fix:
runMeshcoreRoomAutoLoginSingleFlight): overlapping triggers join the in-flight pass and re-select targets when it finishes (a Room that appeared mid-pathSyncstill logs in).nodes.size, with a 500ms debounce.abortIfStaleafterpathSyncpreventsSendLoginon the new conn. The promise is left in place so reconnect joins it instead of overlapping.selectMeshcoreRoomAutoLoginTargets).getWaitingMessagescircuit-opens after 2 consecutive timeouts and skips bulk until reconnect/success. Disconnect bumps the attempt id so a late timeout cannot trip the next connection.Meshtastic TCP write + expected log noise
Problem:
meshtastic:tcp-writewith no socket rejected in main, so Electron logged handler[error]on every reconnect race. If that rejection were swallowed, frames could be silently dropped. Chromium ResizeObserver loop warnings were forwarded as renderer errors.Fix:
'no-socket'(debug log only) so Electron does not log handler[error].throw new Error('meshtastic:tcp-write: no active socket')soTransportTcpIpc/ the SDK see a failed write and do not replay bytes on a later socket.onDisconnected(the no-socket message still does not matchTRANSPORT_LOST_MESSAGE).isDroppableRendererConsoleNoisedrops ResizeObserver loop completed/limit-exceeded lines (including[violation]prefix).pnpm run update: Ratspeak published-release pinsProblem:
check_ratspeak_upstreamtreated every latest GitHub Release as news. Already-reviewed tags (Ratspeak v1.0.25) and repos without Releases (LXMFace) looked like errors. Tags /main/ RCs without a published Release were noisy.Fix:
reviewed-refpin (v0.1.2,v1.0.25, empty, orfile:<path>@<sha>for vendored files).games-paritystill points at Games tab review docs; additionally flags Four in a Row when the release body orcomparediff mentions it.file:js/lxmface.js@308a729d…againstsrc/renderer/lib/reticulum/lxmface.ts.Dependency bumps (
4cdc3587)@zip.js/zip.js^2.8.36→^2.8.37@typescript-eslint/eslint-plugin/parser/typescript-eslint^8.66.0→^8.67.0Commits (
origin/main..HEAD)75 files, +3378 / −346.
Test plan
Unit coverage landed with the behavior (Graph/Topology hop filters + RF-only + unknown hops, RRC
/whosession gate + transcript slot +[hub]routing, MeshCore auto-login single-flight + drain breaker, TCP write sentinel, log-service ResizeObserver drop,scripts/update.test.mjsreviewed-ref cases). Suggested manual checks:RNode_TCP_*is not kept just because another interface is namedRNode. Filter changes do not re-fetch the sidecar./whomember lists. Nicklist still updates. Unscoped hub notices land in[hub]. Manual/whoor Refresh can show one roster line.pathSync/login wave, not one per advert. Disconnect mid-login does notSendLoginon the new connection. After two silent bulkgetWaitingMessagestimeouts, drain falls back without waiting 45s each time.meshtastic:tcp-writehandler[error]spam; connection still detects loss (disconnect event). Frames are not replayed on the next socket.pnpm run update: Ratspeak v1.0.25 and LXMFace at the pinned file SHA print “reviewed; current”, not a warning box.Summary by CodeRabbit
New Features
/whoresults with hub-wide handling, room-aware rosters, and reduced duplicate transcript messages.Bug Fixes
Documentation