Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
4c8d918
feat: add Reticulum sidecar scaffold for Phase B (#593)
rinchen Jun 29, 2026
dd107a3
Merge branch 'main' into 593/reticulum-sidecar-scaffold
rinchen Jun 29, 2026
4fc686c
feat: complete Reticulum integration, sidecar IPC, and i18n audit
rinchen Jun 29, 2026
eab7a6a
chore: ignore Rust sidecar target and fix ws unused import
rinchen Jun 29, 2026
abd9a93
fix: resolve CodeQL findings on PR #596
rinchen Jun 29, 2026
36f8cad
fix: Reticulum connection panel start-before-identity flow (#593)
rinchen Jun 29, 2026
e6b8102
fix: align Reticulum connection panel exit and stop controls
rinchen Jun 29, 2026
ab0fca7
fix: Reticulum sidecar async reads, proxy guards, and connect icon
rinchen Jun 29, 2026
c98ad6b
fix: checkout Ratspeak siblings for Reticulum sidecar CI builds
rinchen Jun 29, 2026
86b0914
fix: Reticulum header parity, identity export, and MQTT connack errors
rinchen Jun 29, 2026
0b7c2bd
fix: Reticulum log filtering, identity UX, and Nodes tab label
rinchen Jun 29, 2026
c498751
fix: scope log panel App/Device filters to active protocol tab
rinchen Jun 30, 2026
f4e22d3
feat: Reticulum unified parity — Radio tab, config import, sidecar APIs
rinchen Jun 30, 2026
9821f3d
feat: add Reticulum interface delete on Radio tab
rinchen Jun 30, 2026
4cb17cd
feat: add Reticulum interface edit on Radio tab
rinchen Jun 30, 2026
c012969
docs: Reticulum guides, IPC reference, and locale quality fixes
rinchen Jun 30, 2026
5d9f955
fix: clone Ratspeak deps outside workspace in sidecar CI
rinchen Jun 30, 2026
ae52350
fix: require DM recipient for Reticulum chat send
rinchen Jun 30, 2026
7c2baf8
fix: annotate serial port vec for Windows sidecar build
rinchen Jun 30, 2026
5631650
fix(i18n): audit Reticulum and chat DM locale strings
rinchen Jun 30, 2026
8399dfa
feat: add RNode firmware flasher to Reticulum Radio tab
rinchen Jun 30, 2026
c792c42
feat: add Reticulum RF/TCP/network transport markers in chat
rinchen Jun 30, 2026
61eef0d
feat: add Reticulum Peers tab with dedicated list and detail UI
rinchen Jun 30, 2026
3dab208
feat: complete Reticulum MeshChat parity roadmap (phases 0–8)
rinchen Jun 30, 2026
937512e
refactor: align Reticulum with meshtastic/meshcore conventions
rinchen Jun 30, 2026
c32e1ef
fix: Reticulum sidecar noise, nomad tab, and channel key dedupe
rinchen Jun 30, 2026
8d7d999
fix: Reticulum topology tab, collapsed radio, and chat key dedupe
rinchen Jun 30, 2026
72637d0
fix: Reticulum sidecar sync, flasher keys, and identity UI
rinchen Jun 30, 2026
8f08606
fix: Reticulum announce save, peer refresh, and admin tab
rinchen Jun 30, 2026
09c19e9
fix: Reticulum audit — peers tab, settings merge, and hardening
rinchen Jun 30, 2026
ed9da5d
fix: Reticulum DMs, contacts, flasher, and sidecar egress badges
rinchen Jun 30, 2026
2857d1d
fix: retry Flatpak offline pnpm install on @jsr temp-dir races
rinchen Jun 30, 2026
14368bf
fix: unblock MeshCore room posts stuck behind zombie logins
rinchen Jun 30, 2026
1c8d1eb
fix: hash ESP32 firmware as Latin-1 bytes for esptool MD5 verify
rinchen Jun 30, 2026
ebe8979
fix: restore Reticulum DM history tabs and RF badge for USB RNode
rinchen Jun 30, 2026
8fdf729
chore: bump deps
rinchen Jun 30, 2026
f47f869
feat: Reticulum topology graph, wire sniffer, and Graph tab removal
rinchen Jun 30, 2026
a74f49a
fix: RNode flasher ESP32 flash, provision flow, and unified step UX
rinchen Jun 30, 2026
3e4afdd
fix: dedupe Packet Sniffer virtualizer React keys
rinchen Jul 1, 2026
4595fda
fix: dual-protocol BLE connect and MeshCore MQTT UI parity
rinchen Jul 1, 2026
5323523
feat: Reticulum multi-hop topology and async LXMF delivery
rinchen Jul 1, 2026
3bbd72d
fix: restore MeshCore and Meshtastic sidebar panels after Reticulum g…
rinchen Jul 1, 2026
af3aefb
fix: header status text contrast for axe color-contrast
rinchen Jul 1, 2026
7d30173
feat: Nomad Network browser with Micron rendering and file downloads
rinchen Jul 1, 2026
8da3aeb
fix: stabilize Sniffer scroll and Reticulum topology graph layout
rinchen Jul 1, 2026
6252028
fix(renderer): stabilize Sniffer packet log scroll pinning
rinchen Jul 1, 2026
fca671f
fix(reticulum): enrich peer names and draw hub-to-peer topology edges
rinchen Jul 1, 2026
2a162dd
fix(reticulum): footer peer count, chat DM visibility, radio panel cl…
rinchen Jul 1, 2026
4421bf6
fix(reticulum): prevent proxyGet timeouts during Nomad page fetches
rinchen Jul 1, 2026
464d44b
feat: live LXMF attachments, propagation sync, and voice clips
rinchen Jul 1, 2026
2deb0a4
feat(reticulum): meshchat-style topology, DM fixes, sniffer scroll, p…
rinchen Jul 1, 2026
258c973
fix: resolve CodeQL alert and rns-stack compile errors on PR #596
rinchen Jul 1, 2026
f0e20a0
fix(reticulum): restore sidecar wire packet tap for sniffer
rinchen Jul 1, 2026
bfc94bc
fix(reticulum): stop duplicate sniffer entries from dual event subscr…
rinchen Jul 1, 2026
5fae1b7
feat: apply Reticulum topology policy to Meshtastic/MeshCore peer graph
rinchen Jul 1, 2026
43495a8
fix: reduce clutter on Meshtastic/MeshCore peer communication graph
rinchen Jul 1, 2026
bf98d31
feat(renderer): add Sniffer pause-to-inspect and batch packet ingestion
rinchen Jul 1, 2026
eefe3e3
fix(renderer): stabilize SVG pan-zoom and sniffer row expansion
rinchen Jul 1, 2026
aa9807c
fix(renderer): restore graph clicks, align labels, and sniffer pause
rinchen Jul 1, 2026
d7a8cb9
feat: Reticulum mesh parity — outbox, search, UX, and diagnostics
rinchen Jul 1, 2026
383a0b2
fix: surface offline Reticulum USB interfaces and stale-port alerts
rinchen Jul 1, 2026
d653aa4
fix: resolve rns-stack compile errors on PR #596
rinchen Jul 1, 2026
c8a2191
chore(i18n): prune 118 unused keys and enforce orphaned-key check
rinchen Jul 1, 2026
973275b
fix: show Reticulum identity display name in main window header
rinchen Jul 1, 2026
4d7ba02
fix: dual-radio Noble BLE auto-connect and connection panel UX
rinchen Jul 1, 2026
566c30f
fix: serialize Reticulum sidecar start and stop reconnect storm
rinchen Jul 2, 2026
7a3dce8
feat: add Reticulum BLE transport, adapter lease, and device pickers
rinchen Jul 2, 2026
f86c285
fix: include rns-ble in dev sidecar auto-build
rinchen Jul 2, 2026
49a15e7
fix: resolve Reticulum BLE adapter conflicts and connection UI
rinchen Jul 2, 2026
e75b441
fix: block Reticulum BLE scan while mesh radios are configured
rinchen Jul 2, 2026
2cb8397
fix: resolve Reticulum sidecar CI and CodeQL alerts
rinchen Jul 2, 2026
d906b02
fix: enable triple-protocol BLE coexistence across mesh stacks
rinchen Jul 2, 2026
a93c8e5
chore: annotate ReticulumRadioPanel silent promise catches
rinchen Jul 2, 2026
02b3b11
fix: BLE RNode synthesis, interface health UI, and connect grace
rinchen Jul 2, 2026
71d485f
feat: move Reticulum interfaces to Connection and rename Radio tab
rinchen Jul 2, 2026
d4d0f05
feat: collapse Reticulum interfaces section on Connection tab
rinchen Jul 2, 2026
885198c
feat: add RNode Wi-Fi pairing and tcp:// sidecar transport
rinchen Jul 2, 2026
ea1aacc
fix: harden Reticulum stack from manual audit findings
rinchen Jul 2, 2026
18a5c62
fix: restore Reticulum wire packet tap and resolve PR #596 findings
rinchen Jul 2, 2026
66f0150
feat: add check:environment dev setup validator
rinchen Jul 2, 2026
fb84a29
chore: bump deps
rinchen Jul 2, 2026
8bbd001
fix: Nomad page proxy timeouts and CodeQL config-read alerts
rinchen Jul 2, 2026
70d3061
feat: Reticulum config audit/repair, RNode RF edit, instant tooltips
rinchen Jul 2, 2026
8db414e
fix: resolve CodeQL findings on config-read tests and MQTT schedule
rinchen Jul 2, 2026
f9e891a
feat: replace hardcoded English UI strings with i18n across protocols
rinchen Jul 2, 2026
c4eaac0
fix: finish AppPanel aria-labels and InactiveProtocolNotifier i18n
rinchen Jul 2, 2026
7edfa4b
fix: vendor rsReticulum packet-tap patch for rns-stack CI
rinchen Jul 2, 2026
49fb2c7
fix: remove duplicate Disable on unreachable Reticulum interfaces
rinchen Jul 2, 2026
811b669
feat: add propagation auto-sync setting and improve sync UX
rinchen Jul 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
157 changes: 157 additions & 0 deletions .github/workflows/reticulum-sidecar.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
name: Reticulum sidecar

permissions:
contents: read

on:
workflow_dispatch:
push:
paths:
- 'reticulum-sidecar/**'
- '.github/workflows/reticulum-sidecar.yaml'

jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact: mesh-client-reticulum-linux-x64
- os: macos-latest
target: aarch64-apple-darwin
artifact: mesh-client-reticulum-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact: mesh-client-reticulum-win-x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
# Optional rns-stack path deps must exist on disk even for the default stub build.
# checkout@v6 only allows paths under GITHUB_WORKSPACE; Cargo expects Ratspeak siblings.
- name: Clone Ratspeak stack (rsReticulum, rsLXMF)
shell: bash
run: |
git clone --depth 1 https://github.com/ratspeak/rsReticulum.git "${GITHUB_WORKSPACE}/../rsReticulum"
git clone --depth 1 https://github.com/ratspeak/rsLXMF.git "${GITHUB_WORKSPACE}/../rsLXMF"
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
targets: ${{ matrix.target }}
- name: Build sidecar (stub)
working-directory: reticulum-sidecar
run: |
cargo test
cargo build --release --target ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact }}
path: |
reticulum-sidecar/target/${{ matrix.target }}/release/mesh-client-reticulum*
retention-days: 14

build-rns-stack:
env:
RS_RETICULUM_REF: 6d2b28475321bc15c8f60796513d8878b47ed3ab
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact: mesh-client-reticulum-rns-linux-x64
- os: macos-latest
target: aarch64-apple-darwin
artifact: mesh-client-reticulum-rns-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact: mesh-client-reticulum-rns-win-x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Clone Ratspeak stack (rsReticulum, rsLXMF)
shell: bash
run: |
RNS_DIR="${GITHUB_WORKSPACE}/../rsReticulum"
PATCH="${GITHUB_WORKSPACE}/reticulum-sidecar/patches/rsReticulum-packet-tap.patch"
git clone https://github.com/ratspeak/rsReticulum.git "${RNS_DIR}"
git -C "${RNS_DIR}" checkout "${RS_RETICULUM_REF}"
git -C "${RNS_DIR}" apply --check "${PATCH}"
git -C "${RNS_DIR}" apply "${PATCH}"
git clone --depth 1 https://github.com/ratspeak/rsLXMF.git "${GITHUB_WORKSPACE}/../rsLXMF"
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
targets: ${{ matrix.target }}
- name: Install Linux BLE build deps
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- name: Build sidecar (rns-stack)
working-directory: reticulum-sidecar
run: |
cargo test --features rns-stack,rns-ble,rns-rnode-tcp
cargo build --release --target ${{ matrix.target }} --features rns-stack,rns-ble,rns-rnode-tcp
- name: Upload rns-stack artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact }}
path: |
reticulum-sidecar/target/${{ matrix.target }}/release/mesh-client-reticulum*
retention-days: 14

build-windows-arm64:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
permissions:
contents: read
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
# Optional rns-stack path deps must exist on disk even for the default stub build.
- name: Clone Ratspeak stack (rsReticulum, rsLXMF)
shell: bash
run: |
git clone --depth 1 https://github.com/ratspeak/rsReticulum.git "${GITHUB_WORKSPACE}/../rsReticulum"
git clone --depth 1 https://github.com/ratspeak/rsLXMF.git "${GITHUB_WORKSPACE}/../rsLXMF"
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
targets: aarch64-pc-windows-msvc
- name: Build WoA sidecar (stub)
working-directory: reticulum-sidecar
run: |
cargo test
cargo build --release --target aarch64-pc-windows-msvc
- uses: actions/upload-artifact@v7
with:
name: mesh-client-reticulum-win-arm64
path: reticulum-sidecar/target/aarch64-pc-windows-msvc/release/mesh-client-reticulum.exe
retention-days: 14
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

build-windows-arm64-rns-stack:
env:
RS_RETICULUM_REF: 6d2b28475321bc15c8f60796513d8878b47ed3ab
permissions:
contents: read
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- name: Clone Ratspeak stack (rsReticulum, rsLXMF)
shell: bash
run: |
RNS_DIR="${GITHUB_WORKSPACE}/../rsReticulum"
PATCH="${GITHUB_WORKSPACE}/reticulum-sidecar/patches/rsReticulum-packet-tap.patch"
git clone https://github.com/ratspeak/rsReticulum.git "${RNS_DIR}"
git -C "${RNS_DIR}" checkout "${RS_RETICULUM_REF}"
git -C "${RNS_DIR}" apply --check "${PATCH}"
git -C "${RNS_DIR}" apply "${PATCH}"
git clone --depth 1 https://github.com/ratspeak/rsLXMF.git "${GITHUB_WORKSPACE}/../rsLXMF"
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
targets: aarch64-pc-windows-msvc
- name: Build WoA sidecar (rns-stack)
working-directory: reticulum-sidecar
run: |
cargo test --features rns-stack,rns-ble,rns-rnode-tcp
cargo build --release --target aarch64-pc-windows-msvc --features rns-stack,rns-ble,rns-rnode-tcp
- uses: actions/upload-artifact@v7
with:
name: mesh-client-reticulum-rns-win-arm64
path: reticulum-sidecar/target/aarch64-pc-windows-msvc/release/mesh-client-reticulum.exe
retention-days: 14
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ coverage/
.context-os
.rtk
flatpak/generated-sources.json

# Rust reticulum sidecar (cargo build output)
reticulum-sidecar/target/
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ resources/win/*.xml
# CodeQL MaD — Prettier line-wrap breaks yamllint indentation for barrier tuples
.github/codeql/extensions/**/*.yml

# Rust sidecar (no Prettier TOML parser)
reticulum-sidecar/

# Images
*.png
*.jpg
Expand Down
35 changes: 32 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This file is self-contained. ARCHITECTURE.md and CONTRIBUTING.md are human refer
- **Testing:** Ship a passing test for behavioral changes; do not call the task done without it.
- **Stateful/I/O code:** Preserve integrity on failure; document failure point, fallback, and logging where it matters.
- **Pre-commit patience:** This repo has a very long pre-commit hook chain (lint, typecheck, thousands of tests, audit, actionlint, yamllint, many check:\* scripts). Commits can take 2+ minutes. Be patient and let them finish — do not interrupt or force-skip hooks.
- **Fresh clone:** Before other setup work, run `node scripts/check-environment.mjs` (works before pnpm is installed). After `pnpm install`, re-run `pnpm run check:environment`. Fix required failures using the printed hints and `setup:*` scripts; optional warnings can wait.

### Platform parity

Expand Down Expand Up @@ -109,7 +110,7 @@ Adding a cross-boundary feature:

**Key commands:** `pnpm run dev`, `pnpm run lint`, `pnpm run typecheck`, `pnpm run test:run`, `pnpm run update`.

> **Update script sync:** When adding or removing packages from `patchedDependencies` in `package.json:205-213`, keep `WATCH_ENTRIES` in `scripts/update.sh:59-69` in sync so the script warns on version changes to every patched dependency.
> **Update script sync:** When adding or removing packages from `patchedDependencies` in `package.json:205-213`, keep `WATCH_ENTRIES` in `scripts/update.sh:59-69` in sync so the script warns on version changes to every patched dependency. `pnpm run update` also runs `rustup update` (or Homebrew `rust` on macOS without rustup) and `cargo build` in `reticulum-sidecar/` when `cargo` is on `PATH`.

**Pre-commit hook order:**

Expand All @@ -132,6 +133,17 @@ Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`).

## 8. Subsystem Quick Reference

### Reticulum

- **Sidecar:** `reticulum-sidecar/` (AGPL Rust binary `mesh-client-reticulum`); dev: `pnpm run reticulum:sidecar:dev`
- **IPC:** `reticulum:*` main handlers (`proxyGet` / `proxyPost` / `proxyPut` / `proxyDelete`, config file read/import dialog); renderer uses `electronAPI.reticulum` proxy (no direct localhost from sandbox)
- **Panels:** `ReticulumStackPanel` (Connection — stack lifecycle, interfaces), `ReticulumNetworkPanel` (Network — identity, stack/announce settings, propagation, config import), `ReticulumAdminPanel` (Admin — RNode flasher, factory reset), `ReticulumPeerListPanel` (Peers), `NomadNetworkPanel` (Nomad Network)
- **Runtime:** `useReticulumRuntime`, `reticulumSession.ts`, `reticulumIngest.ts`; connect starts sidecar, not `ConnectionDriver` RF
- **Diagnostics:** `ReticulumDiagnosticEngine.ts` (Reticulum-native rows; no LoRa hop-goblin semantics)
- **No Noble/MQTT** for Reticulum tab; gate UI with `hasReticulumInterfaceConfig` / `hasReticulumNetworkPanel` / `ProtocolCapabilities`
- **Multi-protocol BLE:** Meshtastic, MeshCore, and Reticulum (BLE Peer + `ble://` RNode) may connect to **different** BLE devices at once on all platforms. Coexistence: `ble-coexistence-coordinator.ts` (peripheral MAC registry + scan-only mutex); Linux mesh uses Web Bluetooth + sidecar `btleplug`. Same MAC rejected; scans serialized—never disconnect unrelated GATT for scans.
- **Docs:** [docs/reticulum.md](docs/reticulum.md), [docs/reticulum-sidecar-ipc.md](docs/reticulum-sidecar-ipc.md)

### Diagnostics

- **Engines:** `src/renderer/lib/diagnostics/`; `RoutingDiagnosticEngine.ts`, `RFDiagnosticEngine.ts`, `RemediationEngine.ts`.
Expand Down Expand Up @@ -186,6 +198,20 @@ Meshtastic BLE: `connection.ts` / `TransportManager`. MeshCore BLE: `noble-ble-m

**Linux Web Bluetooth (Meshtastic):** `webbluetooth-ble-manager.ts` subscribes to **fromNum** GATT notify for unsolicited mesh traffic, runs a **3 s background fromRadio poll** between write cycles, and uses **multi-shot read probes** instead of a single post-write safety read (LoRa latency). MeshCore BLE echo filtering: `meshcoreCompanionTxEchoFilter.ts` (Noble + Web Bluetooth).

**Dual-radio Noble BLE startup (macOS/Windows):** When both Meshtastic and MeshCore have **different** saved BLE peripherals, the renderer must serialize auto-connect and manual Noble connects. Coordinator: `src/renderer/lib/meshcoreDualNobleBleInit.ts`; UI wiring: `ConnectionPanel.tsx` (both panels stay mounted from `App.tsx`).

| Rule | Detail |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Init timing | Call `initNobleBleDualRadioStartup()` from **`App.tsx` `useLayoutEffect`** (not `useEffect`). Child ConnectionPanel auto-connect `useEffect` runs after layout effects — initializing in parent `useEffect` races and leaves primary unset. |
| Primary order | `mesh-client:protocol` localStorage (`meshcore` / `meshtastic`; Reticulum or missing → Meshtastic). Single-radio installs skip peer deferral. |
| Primary notify | Primary calls `notifyNobleBlePrimaryRfLinkReady()` when GATT + handshake succeed (MeshCore transport + Meshtastic `createBleConnection`), or `notifyNobleBlePrimaryAutoConnectSettled()` on first attempt failure — **not** after full configure or scan fallback. |
| Secondary wait | Secondary waits only on `awaitNobleBlePrimaryAutoConnectSettled()` — do **not** add `awaitNobleBleProtocolSettle()` here (mutex + post-config defer handles configure overlap). |
| Mutex | All Noble IPC connects go through `withNobleBleConnectMutex()` (Meshtastic + MeshCore). |
| MeshCore reconnect | `useMeshcoreRuntime` must **not** start the reconnect loop on disconnect before the first successful configure — ConnectionPanel `reconnectBleWithScan` owns initial retries (`meshcoreEverConfiguredRef`). |
| Tests | Protocol-neutral unit tests: `meshcoreDualNobleBleInit.test.ts`. Meshtastic + MeshCore defer paths: `ConnectionPanel.test.tsx` (`active-protocol-first BLE auto-connect`). |

Do **not** reintroduce Meshtastic-only startup gates, child-before-parent init, or runtime-side secondary auto-connect subscriptions — ConnectionPanel owns auto-connect for both protocols.

### Meshtastic channel URLs & Store & Forward

- **Config apply (Radio / Modules / Security):** Firmware `setConfig` / `setModuleConfig` replace full protobuf structs. UI must merge cached device slices with form edits via `meshtasticConfigApply.ts` (`mergeMeshtasticConfigApplyValue`, `buildMeshtasticModuleApplyValue`); slices live in `deviceStore.meshtasticConfigSlices` and `moduleConfigs` (PacketRouter + `meshtasticLegacyWireSubscriptions`). Module-specific validation: `meshtasticMqttModuleApply.ts`, `meshtasticSerialModuleApply.ts`. Apply failures surface `clientNotification` text within 8s (`meshtasticClientNotification.ts` → `formatMeshtasticModuleApplyError`); inline status via `ConfigApplyNotice.tsx`. Forms re-sync after reboot via `useSyncFormFromConfig`.
Expand All @@ -212,9 +238,10 @@ Panels: `src/renderer/components/`. New tabs: `lazyTabPanels.ts` / `lazyAppPanel
- **Locale files:** `src/renderer/locales/{en,es,uk,de,zh,pt-BR,fr,it,pl,cs,ja,ru,nl,ko,tr,id}/translation.json` — English is source of truth (`pnpm run check:i18n` reports key count).
- **Locale persistence:** `locale` key in `app_settings` SQLite table (canonical) and `mesh-client:appSettings` localStorage (fast startup read); reconciled in `App.tsx` on mount.
- **Reduce motion:** `reduceMotion` boolean in the same `app_settings` / localStorage bundle; toggled in **App → Appearance** ([`AppPanel.tsx`](src/renderer/components/AppPanel.tsx)). When true, non-essential UI motion (animated icons, decorative CSS pulses) is suppressed; loading spinners and connection status pulses remain. Does not auto-sync to OS `prefers-reduced-motion` after first-run init — see [`docs/accessibility-checklist.md`](docs/accessibility-checklist.md).
- **Adding strings:** add to `src/renderer/locales/en/translation.json`, use `t('your.key')` in components; `check:i18n` enforces all call sites resolve to English keys.
- **Adding strings:** add to `src/renderer/locales/en/translation.json`, use `t('your.key')` in components; `check:i18n` enforces all call sites resolve to English keys and **fails on unused English keys** (no static `t()`, registered dynamic prefix, quoted literal in `src/`, or `tabs.*` from `TAB_SLOT_IDS`).
- **Removing strings:** delete the key from `en/translation.json` and run `pnpm run i18n:prune-unused -- --write` to drop it from every locale (or remove manually). `check:i18n` blocks orphaned English keys.
- **Auto-translate:** `pnpm run i18n:auto-translate` uses MyMemory (default) or LibreTranslate (`LIBRETRANSLATE_URL`). With git, the default run **only** fills keys that are **new in English vs `HEAD`** and still missing from each locale (pre-commit uses this). Use **`pnpm run i18n:auto-translate --all`** or **`I18N_TRANSLATE_ALL=1`** to backfill every key missing from a locale vs English. Use **`--audit`** (or `I18N_AUDIT=1`) to additionally retranslate any key whose locale value is still identical to English (i.e. never actually translated). Existing translated entries are never overwritten. MyMemory sends contact `info@coloradomesh.org` by default for the 50 k words/day quota; override with `MYMEMORY_EMAIL` if needed.
- **Key check:** `pnpm run check:i18n` — hard fails on missing English keys; warns (does not fail) on incomplete locale coverage so rate-limit gaps don't block commits. Also runs locale quality rules via `scripts/check-i18n-quality.mjs` (mojibake, `meshtastic://` spacing, false friends).
- **Key check:** `pnpm run check:i18n` — hard fails on missing English keys and unused English keys; warns (does not fail) on incomplete locale coverage so rate-limit gaps don't block commits. Also runs locale quality rules via `scripts/check-i18n-quality.mjs` (mojibake, `meshtastic://` spacing, false friends). Unused-key detection lives in `scripts/i18n-unused-keys.mjs`; `pnpm run check:i18n:branch` skips the unused pass and only runs quality rules on keys new/changed vs `HEAD`.
- **Language selector:** `src/renderer/components/LanguageSelector.tsx` — globe-icon dropdown in the header; calls `i18n.changeLanguage()` + `mergeAppSetting('locale', ...)` + `electronAPI.appSettings.set('locale', ...)`.

### Chat Panel
Expand Down Expand Up @@ -254,6 +281,8 @@ Panels: `src/renderer/components/`. New tabs: `lazyTabPanels.ts` / `lazyAppPanel
| Empty chat/nodes offline | `hydrateIdentityStoresFromDb`, connect-time cache in runtimes, `useDbRefresh`; identity split — [troubleshooting](docs/troubleshooting.md#chat-stuck-new-traffic-in-logsdb-but-messages-do-not-appear) |
| Chat stuck / badge moves, no new rows | `identityByProtocol`, `useActiveMeshIdentity`, `mergeOfflineIdentityStore`; **Copy Debug Snapshot** — [troubleshooting](docs/troubleshooting.md#reporting-bugs-copy-debug-snapshot-app-tab) |
| BLE timeout | `noble-ble-manager.ts`, `bleConnectErrors` |
| Reticulum sidecar won't start | `reticulum-sidecar-manager.ts`, `ipc/reticulum-handlers.ts`, [troubleshooting](docs/troubleshooting.md#reticulum-sidecar-wont-start-or-health-poll-times-out) |
| Reticulum interface CRUD fails | `ReticulumInterfacesPanel.tsx` / `ReticulumStackPanel.tsx`, `proxyPut`/`proxyDelete` — [troubleshooting](docs/troubleshooting.md#reticulum-interface-addeditdelete-fails) |
| Serial missing | `serialPortSignature.ts` |
| MQTT loop | `mqtt-manager.ts` |
| DB errors | `database.ts` migrations |
Expand Down
Loading
Loading