Conversation
Adds a lightweight, dependency-free canvas confetti burst (confettiBurst.ts) fired once per session_id on a completed local Games win, gated by the pure isGamesWinForSelf helper and suppressed under app/OS reduced motion. Stamps and decodes the standard LXMF FIELD_REACTION (0x40) so Reticulum tapbacks interoperate with Ratspeak/Sideband: send_reaction adds the msgpack REACTION_TO/REACTION_CONTENT map before sign while keeping the emoji as content, and inbound decode is fail-open (accepts 32-byte binary or 64-hex target, wins over a reply, and leaves reply/plain paths untouched when the field is absent or malformed). Documents the v1.0.25 Games review (CSS-only, no protocol delta), marks Win celebration done, and tracks Four in a Row as a follow-up.
The debounced path-updated (129) rebuild called buildNodesFromContacts without contactsFromRadio, re-saving live radio contacts with on_radio=0 after a successful sync (only self stayed on-radio). Pass contactsFromRadio: true so on-radio state and diagnostics survive the rebuild, and cover it with unit tests.
Extract the Meshtastic/MeshCore reconnect attempt skeleton behind runLoraRfReconnectAttempt and delete the dead ConnectionPanel mount auto-connect path now that ProtocolAutoConnectCoordinator owns cold-start.
Point RATSPEAK_PATCH_ENTRIES and the patches README Upstream PR field at ratspeak/rsReticulum#21 (was empty / "no upstream PR yet") so `pnpm run update` reports the overlay as tracked.
Replace the MeshCore and Meshtastic MQTT network-preset pill rows with a shared MqttNetworkPresetSelect dropdown so both protocols stay uniform. Add Waev, Meshat.se, MeshCore.CA (Primary/Backup), and EastMesh as MeshCore device-signing JWT presets alongside the existing ones, and rename the Meshtastic "MQTT :1883" label to "Official".
…esets Enforce broker wsPath (/ws vs /mqtt) and TLS in the device-signing connect and deviation guards, driven by a single host->wsPath map in letsMeshJwt. Repair wsPath/tlsEnabled via the MeshCore.CA and LetsMesh region toggles. Align the manual Connect and startup auto-launch JWT gate on usesMeshcoreDeviceSigningMqtt (preset or device-signing host) so Custom settings pointed at a known broker mint a JWT consistently. Accept a 30s or 60s LetsMesh keepalive without a false deviation banner. Export one DEVICE_SIGNING_MESHCORE_PRESETS set for IATA scoping + reconcile, add a shared deviceSigningWssFields() helper, generalize the LetsMesh-branded error copy, key the US/EU region labels, and fill all locales. Add guard/aud/wsPath, Colorado region-ack, Colorado Stay, and IATA UI tests; refresh README, auth, parity, troubleshooting, and key-backup docs.
📝 WalkthroughWalkthroughThis PR adds four MeshCore MQTT presets and generalized device-signing authentication, centralizes RF reconnect handling, adds structured LXMF reactions, implements local-win confetti, preserves radio-origin contact metadata, and updates related tests and documentation. ChangesMeshCore MQTT presets and authentication
RF reconnect and auto-connect ownership
Structured LXMF reactions
Local-win game celebration
MeshCore contact rebuild metadata
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 (1)
src/renderer/lib/mqttAutoLaunch.ts (1)
62-76: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winInconsistent predicates for "uses MeshCore device-signing MQTT". This PR introduced
usesMeshcoreDeviceSigningMqtt(preset, settings)as the shared predicate, but only some call sites adopted it. The remaining sites still useisDeviceSigningMeshcorePresetalone orisLetsMeshSettings(server)alone, so the new Waev, Meshat.se, MeshCore.CA, and EastMesh presets are treated differently depending on which code path runs.
src/renderer/lib/mqttAutoLaunch.ts#L62-L76: replaceisLetsMeshSettings(settings.server)inshouldAutoLaunchMeshcoreMqttAtStartupwithusesMeshcoreDeviceSigningMqtt(readStoredMeshcoreMqttPreset(), settings)so the startup gate matches thetryAutoLaunchMqttgate at line 76.src/renderer/components/ConnectionPanel.tsx#L2440-L2447: useusesMeshcoreDeviceSigningMqtt(meshcorePreset, meshcoreMqttSettings)for the deviation banner so a Custom configuration pointed at a device-signing host shows the same banner it is validated against at line 2704, and confirm thatisLetsMeshSettingsrecognizes every host inDEVICE_SIGNING_HOST_WS_PATHS.Pair the change with tests for a Waev preset and for a Custom configuration pointed at
mqtt.waev.app.🤖 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/mqttAutoLaunch.ts` around lines 62 - 76, Align both MQTT call sites with the shared usesMeshcoreDeviceSigningMqtt predicate: in src/renderer/lib/mqttAutoLaunch.ts lines 62-76, update shouldAutoLaunchMeshcoreMqttAtStartup to use the stored preset and settings instead of isLetsMeshSettings alone; in src/renderer/components/ConnectionPanel.tsx lines 2440-2447, use usesMeshcoreDeviceSigningMqtt(meshcorePreset, meshcoreMqttSettings) for the deviation banner. Ensure isLetsMeshSettings covers every host in DEVICE_SIGNING_HOST_WS_PATHS, and add tests for a Waev preset and Custom settings targeting mqtt.waev.app.
🟡 Other comments (6)
src/renderer/components/GamesPanel.tsx-117-126 (1)
117-126: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRecord the session only after its confetti burst starts.
If a user selects a second completed local win during an active burst,
burstConfettireturns without rendering. Line 124 still records that secondsession_id. The effect then has no dependency change that retries the celebration.Make
burstConfettireport whether it started. Queue or retry the second celebration after the active burst ends. Add a test for two local-win sessions selected during one burst.🤖 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/GamesPanel.tsx` around lines 117 - 126, Update the celebration flow in the GamesPanel useEffect and burstConfetti so burstConfetti reports whether a burst actually started, recording selectedSession.session_id in celebratedWinsRef only after a successful start. When a second completed local win is selected during an active burst, queue or retry it after the current burst ends, and add coverage for two local-win sessions selected during one burst.Sources: Coding guidelines, Path instructions
README.md-509-509 (1)
509-509: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDocument host-based device-signing behavior for Custom settings.
usesMeshcoreDeviceSigningMqtt()enables device-signing validation when Custom settings target a known device-signing hostname. The statement that Custom is excluded can cause users to expect a non-JWT flow for a hand-tuned LetsMesh-compatible broker.State that named presets use device signing and that Custom settings also use it when their server matches a known device-signing broker host.
🤖 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 `@README.md` at line 509, Update the MQTT documentation around the device-signing presets to state that Custom settings also use device signing when the configured server matches a known device-signing broker hostname. Preserve the existing distinction for Ripple and Custom while clarifying that Custom is only non-device-signing for unmatched hosts.docs/meshcore-meshtastic-parity.md-180-180 (1)
180-180: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winFix the Port cell for the Meshtastic Custom preset.
The Port column contains
;. The MeshCore table uses—for the same case (line 208). Use the same placeholder here.📝 Proposed fix
-| Custom | (user) | ; | No automatic changes; use for private brokers | +| Custom | (user) | — | No automatic changes; use for private brokers |🤖 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 `@docs/meshcore-meshtastic-parity.md` at line 180, Update the Port cell for the Meshtastic Custom preset in the parity table from `;` to the em dash placeholder `—`, matching the MeshCore table’s corresponding Custom entry.src/renderer/components/ConnectionPanel.tsx-2218-2231 (1)
2218-2231: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winCancelling the Ripple or Colorado confirm leaves the picker showing the wrong preset.
MqttNetworkPresetSelectis a controlled<select>bound tomeshcorePreset. When the user picksrippleorcoloradomeshand then cancelswindow.confirm, the handler returns before anysetStatecall. No re-render is scheduled, so the DOM<select>keeps the cancelled option whilemeshcorePresetandmeshcoreMqttSettingsstill hold the previous preset. The user then sees a preset that is not applied, and Connect uses the old broker.Force a re-render on cancel so React restores the controlled value.
🐛 Proposed fix
+ const [presetSelectNonce, setPresetSelectNonce] = useState(0);onSelect={(value) => { const id = value as MeshcoreMqttPreset; if (id === 'custom') { setMeshcorePreset(id); return; } if (id === 'ripple') { - if (!window.confirm(t('connectionPanel.ripplePresetConfirm'))) return; + if (!window.confirm(t('connectionPanel.ripplePresetConfirm'))) { + setPresetSelectNonce((n) => n + 1); + return; + } } if (id === 'coloradomesh') { - if (!window.confirm(t('connectionPanel.coloradoPresetConfirm'))) return; + if (!window.confirm(t('connectionPanel.coloradoPresetConfirm'))) { + setPresetSelectNonce((n) => n + 1); + return; + } localStorage.setItem(COLORADO_MQTT_REGION_ACK_KEY, '1'); }Pair the fix with a test that cancels the Colorado confirm and asserts the combobox value stays
letsmesh.🤖 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/ConnectionPanel.tsx` around lines 2218 - 2231, Update the onSelect handler for MqttNetworkPresetSelect so cancelling either the Ripple or Colorado confirmation triggers a state update that preserves the current meshcorePreset, allowing React to restore the controlled select value. Add a test covering cancellation of the Colorado confirmation and assert the combobox remains set to letsmesh.reticulum-sidecar/src/stack/live.rs-4761-4764 (1)
4761-4764: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReject incomplete
FIELD_REACTIONpayloads.
read_valuedecodes one MessagePack value and does not require the whole field buffer to be consumed. A field with a valid reaction map plus trailing bytes is malformed, but it is still treated as a reaction. Use the decoded value in a way that only accepts a complete map—either a cursor/position check with an owned read, orread_value_refwith directValueRef::Maphandling.🤖 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 `@reticulum-sidecar/src/stack/live.rs` around lines 4761 - 4764, Update reaction_fields_from_message to require complete consumption of FIELD_REACTION after decoding: use an owned cursor and verify its position reaches the raw buffer length, or use read_value_ref with direct map handling. Return None for valid maps followed by trailing bytes, while preserving acceptance of complete reaction maps.src/renderer/runtime/useMeshcoreRuntime.ts-3479-3490 (1)
3479-3490: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winCleanup the MeshCore reconnect transport before returning the deferred restart.
When a setup abort occurs after
openMeshCoreTransportsucceeds,onAttemptErrorreturns'defer'and skipslateTransport.cleanup(openedDriverIdentityId). Add the same timeout-cleanup call to the setup-abort block, or handleopenedDriverIdentityIdthere, so an opened BLE/TCP transport cannot stay alive until the later follow-up attempt.🤖 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 3479 - 3490, The setup-abort branch in onAttemptError must clean up any transport opened by the current attempt before returning 'defer'. Reuse the existing lateTransport.cleanup path with openedDriverIdentityId (including its timeout behavior), then preserve the deferred reconnect flag and return value.
🧹 Nitpick comments (4)
src/renderer/lib/letsMeshConnectionGuards.test.ts (1)
94-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a case for a missing
wsPath.The upgrade path described in
docs/troubleshooting.mdis stale settings that carry nowsPath.validateLetsMeshPresetConnectmapsundefinedto''and rejects it. No test pins that behavior.🧪 Proposed test
+ it('rejects a missing wsPath on a device-signing broker', () => { + const { wsPath: _omitted, ...noPath } = base; + expect(validateLetsMeshPresetConnect(noPath)).toContain('/ws'); + });🤖 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/letsMeshConnectionGuards.test.ts` around lines 94 - 131, Add a test in the validateLetsMeshPresetConnect cases asserting that an omitted wsPath (undefined) is rejected and the validation message reflects the expected /ws path, covering stale settings without wsPath.src/renderer/components/ConnectionPanel.tsx (1)
713-714: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDelete the unused
autoConnectCancelRefdead store.
autoConnectCancelRefis only written inhandleConnect,handleCancelConnection, andhandleReconnect, and no reader remains after the mount auto-connect effect was removed. KeepcancelProtocolRfAutoConnect(protocol)as the single cancellation path instead.🤖 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/ConnectionPanel.tsx` around lines 713 - 714, Remove the unused autoConnectCancelRef declaration and all writes to it in handleConnect, handleCancelConnection, and handleReconnect. Retain cancelProtocolRfAutoConnect(protocol) as the sole deferred BLE reconnect cancellation path.src/renderer/lib/loraRfReconnectAttempt.test.ts (1)
293-309: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse
loadRendererLibSourcefor the source-contract read.This PR adds
loadRendererLibSourceinsrc/renderer/lib/sourceContractTestHelpers.ts, and the three other reconnect contract tests already use it. This block duplicates that logic with a dynamicnode:fsimport and a bare__dirname. Use the shared helper so the path resolution stays in one place.♻️ Proposed refactor
-describe('loraRfReconnectAttempt source contracts', () => { - it('owns raceWithDeadline budget and finally schedule flush', async () => { - const { readFileSync } = await import('node:fs'); - const { join } = await import('node:path'); - const source = readFileSync(join(__dirname, 'loraRfReconnectAttempt.ts'), 'utf8'); +describe('loraRfReconnectAttempt source contracts', () => { + it('owns raceWithDeadline budget and finally schedule flush', () => { + const source = loadRendererLibSource('loraRfReconnectAttempt.ts');Add the import at the top of the file:
import { loadRendererLibSource } from './sourceContractTestHelpers';🤖 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/loraRfReconnectAttempt.test.ts` around lines 293 - 309, Update the “loraRfReconnectAttempt source contracts” test to import and use the shared loadRendererLibSource helper when reading loraRfReconnectAttempt.ts. Remove the duplicated node:fs/node:path imports and __dirname-based path resolution, while preserving all existing source assertions.src/renderer/hooks/useProtocolRfAutoConnect.test.tsx (1)
464-491: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the ordering that this test names.
The test name states that the secondary waits for primary settle and then protocol settle before the BLE connect. The assertions only check that each mock was called. All three mocks resolve immediately, so the test still passes if the hook reorders the waits after
connectAutomatic. Assert the call order so the sequencing regression is caught.💚 Proposed assertion
expect(mocks.awaitNobleBlePrimaryAutoConnectSettled).toHaveBeenCalled(); expect(mocks.awaitNobleBleProtocolSettle).toHaveBeenCalledWith( 'meshtastic', expect.any(Number), ); + const primarySettleOrder = + mocks.awaitNobleBlePrimaryAutoConnectSettled.mock.invocationCallOrder[0]; + const protocolSettleOrder = mocks.awaitNobleBleProtocolSettle.mock.invocationCallOrder[0]; + const connectOrder = connectAutomatic.mock.invocationCallOrder[0]; + expect(primarySettleOrder).toBeLessThan(protocolSettleOrder); + expect(protocolSettleOrder).toBeLessThan(connectOrder);🤖 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/useProtocolRfAutoConnect.test.tsx` around lines 464 - 491, Update the test around useProtocolRfAutoConnect to assert execution order, not merely invocation. Track the order of awaitNobleBlePrimaryAutoConnectSettled, awaitNobleBleProtocolSettle, and connectAutomatic, then verify primary settle occurs first, protocol settle second, and BLE connect last for both tested platforms.
🤖 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 `@reticulum-sidecar/src/stack/live.rs`:
- Around line 3305-3322: Update the reaction-send flow around reaction_target so
the returned payload includes a structured reaction target only when
parse_optional_reply_to_hash successfully returns a valid target. Clear or omit
reaction_target in the content-only fallback, and add a regression test covering
an invalid target_hash to verify both the plain-emoji send and absence of
structured reaction metadata.
In `@src/renderer/components/MqttNetworkPresetSelect.test.tsx`:
- Around line 1-56: Add an accessibility test in the MqttNetworkPresetSelect
test suite that renders the select with its visible label, calls
hydrateAxeThemeColors() without mocking themeColors, runs axe against the
rendered container, and asserts toHaveNoViolations().
In `@src/renderer/lib/connectionPanelStorageMigrations.ts`:
- Line 38: Update preset reconciliation around PRESET_RECONCILE_PRESETS so it
compares and updates only transport fields, excluding authentication fields such
as the manually stored password. Preserve existing manual credentials when
applying meshcoreMqttPresetFields(), and add a test covering a saved waev manual
password that remains unchanged after migration.
In `@src/renderer/lib/meshcoreMqttPresets.ts`:
- Around line 90-100: Update deviceSigningWssFields to explicitly set
tlsInsecure to false, overriding any prior Ripple setting. Add a regression test
that applies a device-signing preset to MQTT settings with tlsInsecure true and
verifies certificate verification is restored.
---
Outside diff comments:
In `@src/renderer/lib/mqttAutoLaunch.ts`:
- Around line 62-76: Align both MQTT call sites with the shared
usesMeshcoreDeviceSigningMqtt predicate: in src/renderer/lib/mqttAutoLaunch.ts
lines 62-76, update shouldAutoLaunchMeshcoreMqttAtStartup to use the stored
preset and settings instead of isLetsMeshSettings alone; in
src/renderer/components/ConnectionPanel.tsx lines 2440-2447, use
usesMeshcoreDeviceSigningMqtt(meshcorePreset, meshcoreMqttSettings) for the
deviation banner. Ensure isLetsMeshSettings covers every host in
DEVICE_SIGNING_HOST_WS_PATHS, and add tests for a Waev preset and Custom
settings targeting mqtt.waev.app.
---
Other comments:
In `@docs/meshcore-meshtastic-parity.md`:
- Line 180: Update the Port cell for the Meshtastic Custom preset in the parity
table from `;` to the em dash placeholder `—`, matching the MeshCore table’s
corresponding Custom entry.
In `@README.md`:
- Line 509: Update the MQTT documentation around the device-signing presets to
state that Custom settings also use device signing when the configured server
matches a known device-signing broker hostname. Preserve the existing
distinction for Ripple and Custom while clarifying that Custom is only
non-device-signing for unmatched hosts.
In `@reticulum-sidecar/src/stack/live.rs`:
- Around line 4761-4764: Update reaction_fields_from_message to require complete
consumption of FIELD_REACTION after decoding: use an owned cursor and verify its
position reaches the raw buffer length, or use read_value_ref with direct map
handling. Return None for valid maps followed by trailing bytes, while
preserving acceptance of complete reaction maps.
In `@src/renderer/components/ConnectionPanel.tsx`:
- Around line 2218-2231: Update the onSelect handler for MqttNetworkPresetSelect
so cancelling either the Ripple or Colorado confirmation triggers a state update
that preserves the current meshcorePreset, allowing React to restore the
controlled select value. Add a test covering cancellation of the Colorado
confirmation and assert the combobox remains set to letsmesh.
In `@src/renderer/components/GamesPanel.tsx`:
- Around line 117-126: Update the celebration flow in the GamesPanel useEffect
and burstConfetti so burstConfetti reports whether a burst actually started,
recording selectedSession.session_id in celebratedWinsRef only after a
successful start. When a second completed local win is selected during an active
burst, queue or retry it after the current burst ends, and add coverage for two
local-win sessions selected during one burst.
In `@src/renderer/runtime/useMeshcoreRuntime.ts`:
- Around line 3479-3490: The setup-abort branch in onAttemptError must clean up
any transport opened by the current attempt before returning 'defer'. Reuse the
existing lateTransport.cleanup path with openedDriverIdentityId (including its
timeout behavior), then preserve the deferred reconnect flag and return value.
---
Nitpick comments:
In `@src/renderer/components/ConnectionPanel.tsx`:
- Around line 713-714: Remove the unused autoConnectCancelRef declaration and
all writes to it in handleConnect, handleCancelConnection, and handleReconnect.
Retain cancelProtocolRfAutoConnect(protocol) as the sole deferred BLE reconnect
cancellation path.
In `@src/renderer/hooks/useProtocolRfAutoConnect.test.tsx`:
- Around line 464-491: Update the test around useProtocolRfAutoConnect to assert
execution order, not merely invocation. Track the order of
awaitNobleBlePrimaryAutoConnectSettled, awaitNobleBleProtocolSettle, and
connectAutomatic, then verify primary settle occurs first, protocol settle
second, and BLE connect last for both tested platforms.
In `@src/renderer/lib/letsMeshConnectionGuards.test.ts`:
- Around line 94-131: Add a test in the validateLetsMeshPresetConnect cases
asserting that an omitted wsPath (undefined) is rejected and the validation
message reflects the expected /ws path, covering stale settings without wsPath.
In `@src/renderer/lib/loraRfReconnectAttempt.test.ts`:
- Around line 293-309: Update the “loraRfReconnectAttempt source contracts” test
to import and use the shared loadRendererLibSource helper when reading
loraRfReconnectAttempt.ts. Remove the duplicated node:fs/node:path imports and
__dirname-based path resolution, while preserving all existing source
assertions.
🪄 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: 6713544c-03f4-4353-a7f4-333626ee5b96
⛔ Files ignored due to path filters (18)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!**/pnpm-lock.yamlreticulum-sidecar/patches/README.mdis excluded by!reticulum-sidecar/patches/**src/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 (47)
README.mddocs/key-backup-and-crypto.mddocs/letsmesh-mqtt-auth.mddocs/meshcore-meshtastic-parity.mddocs/reticulum-games-parity.mddocs/reticulum-sidecar-ipc.mddocs/reticulum.mddocs/troubleshooting.mdpackage.jsonreticulum-sidecar/src/stack/live.rsscripts/update.shsrc/renderer/App.tsxsrc/renderer/components/ConnectionPanel.hostLinkMeter.test.tsxsrc/renderer/components/ConnectionPanel.test.tsxsrc/renderer/components/ConnectionPanel.tsxsrc/renderer/components/GamesPanel.test.tsxsrc/renderer/components/GamesPanel.tsxsrc/renderer/components/MqttNetworkPresetSelect.test.tsxsrc/renderer/components/MqttNetworkPresetSelect.tsxsrc/renderer/hooks/useProtocolRfAutoConnect.test.tsxsrc/renderer/lib/confettiBurst.test.tssrc/renderer/lib/confettiBurst.tssrc/renderer/lib/connectionPanelStorageMigrations.test.tssrc/renderer/lib/connectionPanelStorageMigrations.tssrc/renderer/lib/letsMeshConnectionGuards.test.tssrc/renderer/lib/letsMeshConnectionGuards.tssrc/renderer/lib/letsMeshJwt.test.tssrc/renderer/lib/letsMeshJwt.tssrc/renderer/lib/loraRfReconnectAttempt.test.tssrc/renderer/lib/loraRfReconnectAttempt.tssrc/renderer/lib/meshcore/meshcorePathUpdatedRuntime.test.tssrc/renderer/lib/meshcore/meshcorePathUpdatedRuntime.tssrc/renderer/lib/meshcoreMqttPresets.test.tssrc/renderer/lib/meshcoreMqttPresets.tssrc/renderer/lib/meshcoreMqttTopicPrefix.test.tssrc/renderer/lib/meshcoreMqttTopicPrefix.tssrc/renderer/lib/mqttAutoLaunch.tssrc/renderer/lib/protocolRfAutoConnectGate.tssrc/renderer/lib/reticulum/reticulumGamesMetadata.test.tssrc/renderer/lib/reticulum/reticulumGamesMetadata.tssrc/renderer/lib/sourceContractTestHelpers.tssrc/renderer/runtime/loraRfReconnectParity.contract.test.tssrc/renderer/runtime/useMeshcoreRuntime.reconnect.test.tssrc/renderer/runtime/useMeshcoreRuntime.tssrc/renderer/runtime/useMeshtasticRuntime.reconnect-hardening.test.tssrc/renderer/runtime/useMeshtasticRuntime.tsvitest.config.mts
💤 Files with no reviewable changes (2)
- src/renderer/App.tsx
- src/renderer/components/ConnectionPanel.hostLinkMeter.test.tsx
…nd reconnect Renderer / MeshCore MQTT: - Startup preset reconcile now rewrites only transport fields, preserving a manually stored password/username (connectionPanelStorageMigrations). - deviceSigningWssFields resets tlsInsecure to false so switching away from Ripple restores certificate verification. - shouldAutoLaunchMeshcoreMqttAtStartup and the deviation banner use the shared usesMeshcoreDeviceSigningMqtt predicate (named preset OR device-signing host), so Custom settings pointed at a known JWT broker behave consistently. - ConnectionPanel: cancelling the Ripple/Colorado preset confirm remounts the controlled select so it snaps back to the current preset; removed the unused autoConnectCancelRef. Reticulum sidecar (live.rs): - send_reaction only emits structured reaction_target when the target hash parses; the content-only fallback omits it. - reaction_fields_from_message rejects a FIELD_REACTION with trailing bytes. Games / reconnect: - burstConfetti returns whether a burst started; GamesPanel records a win as celebrated only on a real start and retries a win queued during an active burst. - useMeshcoreRuntime setup-abort branch cleans up any late-opened transport before deferring. Docs/tests: README/parity Custom clarification; new/updated coverage for each fix plus axe on MqttNetworkPresetSelect, execution-order assertion in useProtocolRfAutoConnect, omitted-wsPath guard, and the shared source helper.
An idle LRGP session passes its TTL (tic-tac-toe 24h; chess and four-in-a-row 7d active, 24h pending) and the protocol rejects any outbound action, so resign/move/draw failed with a raw "dispatch_error: session expired" toast while the stored row stayed "active" — leaving the game stuck with controls that always fail. Sidecar (games_session prepare_action): on LrgpError::SessionExpired, persist the session's stored status to "expired" so list_sessions and future refreshes stop showing a dead game as active, and return the stable "session_expired" code instead of the raw dispatch_error string. Renderer (reticulumGamesSession): recognize session_expired (and the legacy dispatch_error wrapper), flip the local session to expired so the resign/draw controls disappear and only Delete remains, and show a friendly, duration-agnostic toast. Also add an in-game notice under New challenge explaining the per-category expiry windows so players know unanswered challenges and idle tic-tac-toe expire after 24h while chess and four-in-a-row allow 7 days idle. Tests: new Rust regression (age a session past TTL, assert session_expired + persisted status) and renderer coverage for the expired flip/toast and the reason matcher. i18n keys added and translated to all locales.
Overview
The
depsbranch bundles a dependency refresh with feature and hardening work across MeshCore MQTT, LoRa (Meshtastic + MeshCore) reconnect, and Reticulum games. 69 files changed (+4,128 / −2,154). Commits, oldest → newest:2c8ffba0dfbc15FIELD_REACTIONinterope8efceaon_radioduring path-updated contact rebuild1d67711981ed75600561ea154e359480e42dae9d721. MeshCore MQTT broker presets (
600561e+a154e35)New brokers + shared picker
MqttNetworkPresetSelectdropdown (src/renderer/components/MqttNetworkPresetSelect.tsx, used by both Meshtastic and MeshCore panels).audlive inletsMeshJwt.ts; preset field blocks inmeshcoreMqttPresets.ts.Guard hardening (
a154e35)validateLetsMeshPresetConnect/letsMeshPresetConfigurationDeviation(letsMeshConnectionGuards.ts) now require the broker's expected WebSocket path (/wsfor LetsMesh/MeshMapper/Colorado,/mqttfor Waev/Meshat.se/MeshCore.CA/EastMesh) andtlsEnabled. Expected path comes from a single source of truth,DEVICE_SIGNING_HOST_WS_PATHSinletsMeshJwt.ts, from which the host allowlist (isLetsMeshSettings) is now derived — the two can no longer drift. New helper:deviceSigningWsPathForHost().tlsEnabled/wsPath(and keepalive) so switching a stale saved config repairs the structural fields while preserving the user's topic prefix.usesMeshcoreDeviceSigningMqtt(preset, settings)(named preset or device-signing host). Previously manual Connect gated on the preset while auto-launch gated on the host, so Custom-pointed-at-a-known-broker would JWT on auto-launch but not manual Connect. The identity banner also shows in the host-based case.De-duplication (
a154e35)DEVICE_SIGNING_MESHCORE_PRESETSset (replacing three copies: the presets module, IATA scoping inmeshcoreMqttTopicPrefix.ts, and the reconcile list inconnectionPanelStorageMigrations.ts).deviceSigningWssFields(server, wsPath)to collapse the identical WSS field blocks for Waev/Meshat.se/EastMesh/etc.i18n
letsMeshRequiresTlsandletsMeshRequiresWsPath; keyed the previously hardcodedUS/EUregion labels and addedaria-pressedto the region/broker toggles.itNBSP,jav1_literal,ukapostrophe).pnpm run check:i18n:branchpasses.Docs
README.md(picker + four brokers),docs/letsmesh-mqtt-auth.md(aud = connect host,/wsvs/mqtt, 30/60 keepalive),docs/meshcore-meshtastic-parity.md(device-signing Authentication subsection + MeshMapper.cc→.netnote),docs/troubleshooting.md(stale wsPath block + generalized token sections),docs/key-backup-and-crypto.md(device-signing wording).2. LoRa reconnect refactor (
1d67711)src/renderer/lib/loraRfReconnectAttempt.ts(+ tests) centralizes the single BLE reconnect attempt lifecycle (open + handshake budget, deferred-disconnect flush) used by bothuseMeshtasticRuntime.tsanduseMeshcoreRuntime.ts, which were each ~500 lines and largely duplicated. Both runtimes shrank substantially.protocolRfAutoConnectGate.ts,useProtocolRfAutoConnect); removed the corresponding wiring fromApp.tsxandConnectionPanel.tsx(large deletions inConnectionPanel.tsx/.test.tsxare this removal, not the MQTT work).loraRfReconnectParity.contract.test.ts,useMeshcoreRuntime.reconnect.test.ts, anduseMeshtasticRuntime.reconnect-hardening.test.ts(manual-disconnect-must-not-auto-reconnect contracts intact).3. Reticulum games (
0dfbc15)src/renderer/lib/confettiBurst.ts(+ tests) andGamesPanel.tsxwiring.FIELD_REACTIONinterop:reticulumGamesMetadata.ts(+ tests) and sidecar changes inreticulum-sidecar/src/stack/live.rs; parity docdocs/reticulum-games-parity.mdupdated.4. MeshCore
on_radiofix (e8efcea)meshcorePathUpdatedRuntime.tspreserves theon_radiopresence flag when rebuilding a contact from a path-updated (129) event, so a path refresh no longer drops radio presence. Covered bymeshcorePathUpdatedRuntime.test.ts.5. Dependencies & tracking (
2c8ffba,981ed75)package.json/pnpm-lock.yamldependency refresh.scripts/update.shwatch entry for rsReticulum bond-desync upstream PR Update ConnectionPanel.tsx text #21; minor Reticulum patch/README/doc touch-ups.6. PR review follow-ups (
9480e42)Addressed inline review findings from the earlier commits, keeping changes minimal and adding coverage:
deviceSigningWssFieldsresetstlsInsecure: falseso leaving Ripple restores cert verification;shouldAutoLaunchMeshcoreMqttAtStartup+ the deviation banner both use the sharedusesMeshcoreDeviceSigningMqttpredicate; cancelling the Ripple/Colorado preset confirm remounts the controlled select so it snaps back; removed the unusedautoConnectCancelRef.live.rs):send_reactionemits a structuredreaction_targetonly when the target hash parses (content-only fallback omits it);reaction_fields_from_messagerejects aFIELD_REACTIONwith trailing bytes.burstConfettireports whether a burst started;GamesPanelrecords a win as celebrated only on a real start and retries a win queued during an active burst;useMeshcoreRuntimesetup-abort branch cleans up any late-opened transport before deferring.MqttNetworkPresetSelect; execution-order assertion inuseProtocolRfAutoConnect; omitted-wsPathguard; shared source helper.7. Reticulum LRGP game session expiry (
dae9d72)An idle LRGP session passes its TTL (tic-tac-toe 24h; chess and four-in-a-row 7d active, 24h pending — all values upstream in
ratspeak/lrgp-rs) and the protocol rejects any outbound action, so resign/move/draw failed with a rawdispatch_error: session expiredtoast while the stored row stayedactive— leaving the game stuck with controls that always fail.games_session.rs): onLrgpError::SessionExpired, persist the session's stored status toexpired(solist_sessionsand future refreshes stop showing a dead game as active) and return the stablesession_expiredcode instead of the rawdispatch_errorstring. New Rust regression test ages a session past its TTL and asserts both the code and the persisted status.reticulumGamesSession.ts): recognizesession_expired(and the legacydispatch_errorwrapper), flip the local session toexpiredso the resign/draw controls disappear and only Delete remains, and show a friendly, duration-agnostic toast. Tests cover the flip, the toast, and the reason matcher.GamesPanel.tsx): a per-category expiry notice under New challenge — unanswered challenges 24h (all games); active tic-tac-toe 24h; chess and four-in-a-row 7 days. New i18n keys translated to all 15 locales.Risk / compatibility
wsPath/TLS now blocks Connect with a clear message (previously an opaque broker failure). Re-selecting the preset or using a broker/region toggle repairs it;connectionPanelStorageMigrations.tsalso reconciles on startup.wsPathfollows the actual broker host) rather than threaded through the selected preset — same guard accuracy with fewer call-site changes.Test plan
pnpm run typecheck(base,tsconfig.main.json,tsconfig.e2e.json)reticulumGamesSessionexpired flip + matchercargo test --features rns-stackgames_session (incl. new expiry regression) + reaction unit testspnpm run check:i18n(all 15 locales verified for the new games keys)pnpm run check:prin CI (full lint + typecheck +test:run+ sidecar)aud= connect host and correctwsPath; MeshCore.CA Primary/Backup + LetsMesh US/EU toggles; Colorado region gate; Ripple confirm; IATA topic normalize