Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ This file is self-contained. ARCHITECTURE.md and CONTRIBUTING.md are human refer

Electron: `src/main/` (Node, SQLite, BLE, MQTT), `src/preload/` (bridge), `src/renderer/` (React 19, Vite, Zustand). **Multi-protocol:** Meshtastic, MeshCore, and Reticulum; gate UI with `ProtocolCapabilities` and `useRadioProvider(protocol)` (do not compare `protocol === 'meshcore'`). Routing/diagnostics changes must stay compatible with the Diagnostics panel (Hop Goblins, Hidden Terminals, etc. for LoRa; Reticulum uses `ReticulumDiagnosticEngine.ts`). **pnpm** only for package commands. **Never** add cryptocurrency tech or dependencies.

**Colors:** Use Tailwind CSS utility classes (e.g., `text-green-400`, `bg-slate-700`). Custom theme colors via CSS custom properties in `styles.css` (`--color-brand-green`, etc.). Avoid inline hex colors in JSX.
**Colors:** Use Tailwind CSS utility classes (e.g., `text-green-400`, `bg-slate-700`). Custom theme colors via CSS custom properties in `styles.css` (`--color-brand-green`, etc.). Avoid inline hex colors in JSX. **App → Appearance → Colors** lets users customize theme tokens including chat/RRC **message action** bar/button colors (`themeColors.ts`); **Show background** / **Always show message actions** control action-bar visibility.

**Code style and testing:** [Code style & standards](CONTRIBUTING.md#code-style--standards) and [Testing protocols](CONTRIBUTING.md#testing-protocols) in [CONTRIBUTING.md](CONTRIBUTING.md).

Expand Down Expand Up @@ -158,7 +158,7 @@ Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`).
- **RNode flasher timeouts:** `RNODE_COMMAND_TIMEOUT_MS` (30 s serial), `RNODE_BT_PAIRING_TIMEOUT_MS` (90 s BLE pairing), `ESP32_FLASH_STALL_TIMEOUT_MS` / `NRF52_DFU_STALL_TIMEOUT_MS` (60 s no-progress → `ESP32_FLASH_STALLED` / `NRF52_DFU_STALLED`); humanized via `flasherErrorHumanize.ts`
- **Peer aliases / History vs Contacts:** LXMF/Nomad announce names overlay path-table peers; SQLite `reticulum_destinations.last_heard` = History, `is_contact` = Contacts (Save as contact only — inbound/outbound LXMF does **not** auto-add Contacts; sidecar `/contacts` wire rows are History hints unless SQLite `is_contact=1`); default avatars via vendored LXMFace (`lib/reticulum/lxmface.ts`); renderer refresh + `reticulumContactToNodeRecordPreservingLabel` refuse hash-prefix wipes of Chat/`nodeStore` labels; ingest stamps History via `persistReticulumHistoryFromPayload` + `stampHistoryPeer`; SQL upsert guard preserves real names over hash-prefix aliases; destination upsert requires exact 32-hex (lowercase) and omits `favorited` on icon-only patches so favorites/icons survive path/probe refresh
- **Stores/lib:** `reticulumIdentityStore.ts` (session-global sidecar identity status shared by `useReticulumSidecarApi` — distinct from identity-scoped `identityStore`), `reticulumPeerStore.ts` (path-table `peers` + `history` + saved `contacts`; soft-TTL reads, forced `?refresh=1`, incremental `peers_updated` route-field patches, 50ms batching, name/appearance preservation, 30s/60s large-mesh poll), `reticulumDiscoveryMapStore.ts`, `reticulumRmapDiscovery.ts`, `reticulumDiscoveryMapLayout.ts`, `nomadNetworkStore.ts`, `rrcHubStore.ts` / `rrcSessionStore.ts` (RRC hubs + multi-hub sessions; hydrate/clear room history via `rrcRoomHistory.ts`; persist → SQLite `rrc_messages` via `rrcMessagePersist.ts` + `ipc/rrc-db-handlers.ts`; prefs in `rrcHubPrefs` / `rrcRoomPrefs` / `rrcRecentRooms`; notifications in `rrcInactiveNotifications` / `rrcMention`); **Remote (rnsh/rncp):** `rncpTransferStore.ts`, `rnshSessionStore.ts`, `reticulumInboundPolicyStore.ts`, `reticulumRemoteAddressStore.ts`, `rncpEnableRequestStore.ts` + lib `remoteSettingsStorage.ts`, `pushRncpListenerPolicy.ts`, `rncpInboundPolicyLists.ts`, `sendRncpRequestEnable.ts`, `rncpRequestEnableRateLimit.ts`, `applyRncpReceiveDestShare.ts` / `rncpReceiveDestSharePending.ts` (mark pending on request-enable; consume on ingest within TTL), `hooks/useRemotePathCapability.ts`, `components/remote/*`; WS events `rmap.discovery`, `lxmf_outbound_status`, `nomadnetwork.node`, `rrc.*`, `rnsh.*` / `rncp.*` in `useReticulumRuntime` (sidecar also emits `nomad.serving_start` / `nomad.serving_stop`; renderer polls serving status via HTTP, not those WS events)
- **LXMF outbound delivery:** sidecar `lxmf_delivery.rs` / `lxmf_outbound.rs` (Direct-first; **one-shot fallback** to preferred **remote** PN on Direct fail; intermediate WS `sending` + `delivery_method: "propagated"`); renderer `applyReticulumOutboundDeliveryStatus.ts` (WS `lxmf_outbound_status` → Zustand + SQLite `delivery_status` + `delivery_method`; early-status buffer; hash/status allowlist), `reticulumOutboundFailureBridge.ts` (`shouldApplyLinkDeliveryTimeoutFailureBridge` skips the link-timeout Failed bridge when an effective remote PN target exists; also skips `propagated` rows so fallback is not killed), `markStaleReticulumOutbound.ts`. Propagated Completes UI: **Stored at propagation node**. **Paper exception:** `createReticulumPaperMessage` / paper create Completes immediately (`delivery_method: paper`, `ReticulumMessageStatusBadge` **Paper**) via `lxmf_message` — no `lxmf_outbound_status`; shared `reticulumMessageTransport` / `reticulumPaperErrors` keep IPC allowlists and i18n codes aligned.
- **LXMF outbound delivery:** sidecar `lxmf_delivery.rs` / `lxmf_outbound.rs` (Direct-first; **one-shot fallback** to preferred **remote** PN on Direct fail; intermediate WS `sending` + `delivery_method: "propagated"`); renderer `applyReticulumOutboundDeliveryStatus.ts` (WS `lxmf_outbound_status` → Zustand + SQLite `delivery_status` + `delivery_method`; early-status buffer; hash/status allowlist), `reticulumOutboundFailureBridge.ts` (`shouldApplyLinkDeliveryTimeoutFailureBridge` skips the link-timeout Failed bridge when an effective remote PN target exists; also skips `propagated` rows so fallback is not killed), `markStaleReticulumOutbound.ts`. Optimistic pending rows use `reticulum-pending-*`; send-path rekey passes `replaces_message_hash` on SQLite upsert to delete the prior pending hash. Propagated Completes UI: **Stored at propagation node**. **Paper exception:** `createReticulumPaperMessage` / paper create Completes immediately (`delivery_method: paper`, `ReticulumMessageStatusBadge` **Paper**) via `lxmf_message` — no `lxmf_outbound_status`; shared `reticulumMessageTransport` / `reticulumPaperErrors` keep IPC allowlists and i18n codes aligned.
- **DM path reachability:** `useReticulumDmPathProbe.ts`, `reticulumDmPathReachability.ts`, `ReticulumDmPathReachabilityBadge.tsx` — Chat **Probe** matches Peer List (sidecar running check → `/probe` → toast → refresh); `applyProbeResult(forHash, …)` applies the settle without a second `/probe` and ignores stale completions after DM switch; manual reprobe forces Checking… even when passive hops look reachable; Peers virtualizes above 100 rows via `reticulumPeerListRows.ts`; peer refresh policy in `reticulumSidecarPeerRefreshEvents.ts`
- **Inbound transport labels:** `received_via` resolves the path-table interface name against local interface config type, so a TCP hub display name still renders as TCP.
- **Topology:** `via_hash` is an immediate transport id; sidecar synthesizes missing relay nodes. `ReticulumTopologyPanel` uses force layout; sidecar caps graph input at 2,000 peers and renderer caps visible peers at 800 (grid repulsion above 400).
Expand Down Expand Up @@ -187,15 +187,15 @@ Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`).

See **Renderer: hooks vs runtime vs lib** (layout map above). Legacy `useDevice` / `useMeshCore` are removed ([#375](https://github.com/Colorado-Mesh/mesh-client/issues/375), [#377](https://github.com/Colorado-Mesh/mesh-client/issues/377)). Default rules for new UI:

| Concern | Use |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Orchestration (App tab) | `useProtocolFacade(protocol)` — connection, `useConnectionView`, panel bundle, nodes, messages |
| Active protocol identity | `useActiveMeshIdentity(protocol)` — focused `identityId` per tab; prefer `capabilities` over `protocol ===` |
| LoRa dual-protocol panel bundles (App) | `useAllProtocolPanelActions` (Meshtastic + MeshCore + Reticulum); prefer this over per-protocol panel-action hooks at the App shell |
| Reads (nodes, messages, connection fields) | Zustand stores + `useNodes` / `useMessages` / `useConnectionView` / `useConnectionStatus` |
| Writes (configure, send, admin, panel callbacks) | `usePanelActions(protocol, identityId, …)` / `useProtocolFacade(protocol).panel` or `useSendMessage(identityId)` |
| Connect / disconnect / auto-connect | `useProtocolConnectionActions(protocol)` (`useProtocolConnect` + `useProtocolDisconnect` + `lib/sessions/*Session.ts`); Meshtastic/MeshCore via `ConnectionDriver`; Reticulum via sidecar start/stop |
| Wire subscriptions, MQTT IPC, reconnect, DB hydration | `useMeshtasticRuntime` / `useMeshcoreRuntime` / `useReticulumRuntime` in `runtime/` — mount **once** from `App.tsx` via context providers |
| Concern | Use |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Orchestration (App tab) | `useProtocolFacade(protocol)` — connection, `useConnectionView`, panel bundle, nodes, messages |
| Active protocol identity | `useActiveMeshIdentity(protocol)` — focused `identityId` per tab; prefer `capabilities` over `protocol ===` |
| LoRa dual-protocol panel bundles (App) | `useAllProtocolPanelActions` (Meshtastic + MeshCore + Reticulum); prefer this over per-protocol panel-action hooks at the App shell |
| Reads (nodes, messages, connection fields) | Zustand stores + `useNodes` / `useMessages` / `useConnectionView` / `useConnectionStatus` |
| Writes (configure, send, admin, panel callbacks) | `usePanelActions(protocol, identityId, …)` / `useProtocolFacade(protocol).panel` or `useSendMessage(identityId)` |
| Connect / disconnect / auto-connect | `useProtocolConnectionActions(protocol)` (`useProtocolConnect` + `useProtocolDisconnect` + `lib/sessions/*Session.ts`); Meshtastic/MeshCore via `ConnectionDriver`; Reticulum via sidecar start/stop. Launch RF auto-connect (serial/BLE/TCP/HTTP) via `ProtocolAutoConnectCoordinator` + `useProtocolRfAutoConnect` + `protocolRfAutoConnectGate` (`cancelProtocolRfAutoConnect` before manual Connect). LoRa reconnect single-owner: `rfReconnectController` |
| Wire subscriptions, MQTT IPC, reconnect, DB hydration | `useMeshtasticRuntime` / `useMeshcoreRuntime` / `useReticulumRuntime` in `runtime/` — mount **once** from `App.tsx` via context providers |

Do **not** remount protocol runtimes in child components. Do **not** compare `protocol === 'meshcore'` for feature gates; use `ProtocolCapabilities` / `useRadioProvider(protocol)`.

Expand All @@ -218,6 +218,7 @@ Meshtastic and MeshCore share LoRa BLE reconnect contracts whenever possible (pl

**LoRa BLE reconnect parity (Meshtastic + MeshCore):**

- **`rfReconnectController`** (`lib/rfReconnectController.ts`): single-owner link-lost / schedule / endAttempt for both runtimes (MeshCore TCP uses the same owner; conn side effects must not call `handleConnectionLost` for TCP).
- **`gattSetupInflight`** in `noble-ble-manager` (both session ids): after `connectAsync`, mid-GATT disconnect rejects in-flight `connect()` promptly (Noble only).
- **Deferred Noble disconnect** while connect/reconnect open is in flight; **flush in reconnect `finally`** in `useMeshtasticRuntime` and `useMeshcoreRuntime` so edge-of-range drops keep retrying.
- **`NOBLE_BLE_RECONNECT_ATTEMPT_BUDGET_MS`** (`timeConstants.ts`) + `raceWithDeadline` (`bleReconnectHelper.ts`): hard ceiling per BLE reconnect open+handshake attempt on **all platforms** (unsticks configure/attach hangs; does not add a Linux cross-protocol mutex).
Expand Down
4 changes: 3 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ Sanitize user-controlled strings before logs and IPC per [AGENTS.md](AGENTS.md).
### App lifecycle (mount once from `App.tsx`)

- **`usePowerRecovery`** — sleep/wake IPC, MQTT power suspend/resume, staggered RF reconnect (Meshtastic ~4s, MeshCore ~8s, dual-Noble settle up to ~30s).
- **`useRendererHeartbeat`** / **`useLongSessionMaintenance`** — renderer liveness after resume; long-uptime restart nudge.
- **`useRendererHeartbeat`** / **`useLongSessionMaintenance`** — renderer pings main every 30s; main `rendererHeartbeatWatchdog` warns if no heartbeat within 30s after resume **while visible**, and polls for a **90s visible-window stall**; sticky `rendererUnresponsiveSeen` + `getRendererLiveness()` feed support snapshot `mainLiveness`; long-uptime restart nudge.
- **`ProtocolAutoConnectCoordinator`** / **`useProtocolRfAutoConnect`** — silent launch auto-connect for remembered serial/BLE/TCP/HTTP (cancel gate before manual Connect).
- **`rfReconnectController`** — LoRa single-owner reconnect scheduling shared by Meshtastic/MeshCore runtimes.
- **Dual-radio Noble BLE startup** (Meshtastic + MeshCore different peripherals): `lib/meshcoreDualNobleBleInit.ts` initialized from `App.tsx` `useLayoutEffect`; primary order from `mesh-client:protocol` localStorage — see [AGENTS.md](AGENTS.md) **Dual-radio Noble BLE startup**.

### Database
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Thank you for your interest in contributing.
- **Prettier:** Semi always, single quotes, trailing commas, print width 100, tab 2, LF.
- **TypeScript:** Strict; avoid `any`; prefer `unknown` + guards; export types; prefer interfaces over type aliases.
- **React:** Function components only; `exhaustive-deps` is errors; `?.` in JSX; **every interactive control needs `aria-label`**.
- **Colors:** Use Tailwind CSS utility classes (e.g., `text-green-400`, `bg-slate-700`). Custom theme colors via CSS custom properties from `styles.css` (`--color-brand-green`, `--color-deep-black`, etc.). Avoid inline hex colors in JSX.
- **Colors:** Use Tailwind CSS utility classes (e.g., `text-green-400`, `bg-slate-700`). Custom theme colors via CSS custom properties from `styles.css` (`--color-brand-green`, `--color-deep-black`, etc.). Avoid inline hex colors in JSX. End users can also customize theme tokens (including message action colors) under **App → Appearance → Colors**.
- **Zustand:** Module-level defaults for stable refs; prefer `useStore(s => s.field)` over broad subscriptions; avoid subscribing to whole Maps when one id suffices; `persist` for localStorage, IPC from an effect for SQLite; extract time constants to `src/renderer/lib/timeConstants.ts` (e.g. `MS_PER_SECOND`).
- **Performance:** No hot-path O(n); lazy cleanup when collections grow large.

Expand Down
Loading