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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Adding a cross-boundary feature:

**Local Linux CI (optional):** Container mode — `act:ci`, `act:tests`, `act:pr`, … (needs a Docker-compatible engine + act; Podman preferred). Host mode — `act:ci:native`, `act:tests:native`, … (no container engine). See [docs/ci-cd.md](docs/ci-cd.md). macOS/Windows packaging uses native `dist:mac` / `dist:win`. **`dist:mac`** / **`dist:mac:publish`** always run **`scripts/verify-mac-packaging.mjs`** (ZIP + DMG symlink asserts, no raw `.app` CI uploads). macOS signing env (`CSC_LINK`, `CSC_KEY_PASSWORD`, `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID`, `CSC_IDENTITY_AUTO_DISCOVERY`) is scoped to **`macos-latest`** jobs in `release.yaml` / `build.yaml`; partial-secret validation fails the release job when `CSC_LINK` is set but notarization secrets are missing.

> **Update script sync:** When adding or removing packages from `patchedDependencies` in `pnpm-workspace.yaml`, keep `WATCH_ENTRIES` in `scripts/update.sh` in sync so the script warns on version changes to every patched dependency. When adding or removing Ratspeak overlays under `reticulum-sidecar/patches/`, keep `RATSPEAK_PATCH_ENTRIES` in `scripts/update.sh` (`check_ratspeak_patches`) in sync — `pnpm run update` queries upstream PRs (rsReticulum / rsLXMF) and warns when a local overlay can be removed. It also runs `check_ratspeak_upstream` (watched releases for rsLXST / lrgp-rs / Ratspeak / LXMFace, plus new `ratspeak` org repos) — keep `RATSPEAK_RELEASE_WATCH_ENTRIES` / `RATSPEAK_KNOWN_ORG_REPOS` in sync when adopting libs. `scripts/clone-ratspeak-stack.sh` floats **rsReticulum** / **rsLXMF** / **rsNomad** / **rsLXST** / **lrgp-rs** to `origin/main` (override with `RS_RETICULUM_REF` / `RS_LXMF_REF` / `RS_NOMAD_REF` / `RS_LXST_REF` / `RS_LRGP_REF`); overlays must apply or the clone fails. Ratspeak release watch uses stub-kind `games-parity` to nudge Games tab review (`docs/reticulum-games-parity.md`). Peer default avatars use vendored **LXMFace** (`src/renderer/lib/reticulum/lxmface.ts`). `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` (full-feature build includes `nomad-core` / rsNomad).
> **Update script sync:** When adding or removing packages from `patchedDependencies` in `pnpm-workspace.yaml`, keep `WATCH_ENTRIES` in `scripts/update.sh` in sync so the script warns on version changes to every patched dependency. When adding or removing Ratspeak overlays under `reticulum-sidecar/patches/`, keep `RATSPEAK_PATCH_ENTRIES` in `scripts/update.sh` (`check_ratspeak_patches`) in sync — `pnpm run update` queries upstream PRs (rsReticulum / rsLXMF) and warns when a local overlay can be removed. It also runs `check_ratspeak_upstream` (watched **published** releases for rsLXST / lrgp-rs / Ratspeak vs `reviewed-ref` pins, plus new `ratspeak` org repos) — keep `RATSPEAK_RELEASE_WATCH_ENTRIES` / `RATSPEAK_KNOWN_ORG_REPOS` in sync when adopting libs. LXMFace is not a published-release watch: its baseline is a vendored-file commit (`file:js/lxmface.js@<sha>`) compared with the latest GitHub commit that touched that file. `scripts/clone-ratspeak-stack.sh` floats **rsReticulum** / **rsLXMF** / **rsNomad** / **rsLXST** / **lrgp-rs** to `origin/main` (override with `RS_RETICULUM_REF` / `RS_LXMF_REF` / `RS_NOMAD_REF` / `RS_LXST_REF` / `RS_LRGP_REF`); overlays must apply or the clone fails. Ratspeak release watch uses stub-kind `games-parity` to nudge Games tab review when a published release is newer than the pin (`docs/reticulum-games-parity.md`). Peer default avatars use vendored **LXMFace** (`src/renderer/lib/reticulum/lxmface.ts`). `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` (full-feature build includes `nomad-core` / rsNomad).

**Pre-commit hook order:**

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ Architecture and API: [docs/reticulum.md](docs/reticulum.md). Games wire parity:
- **Map tiles; OpenStreetMap Referer requirement**: Packaged desktop builds load the UI from the local filesystem. The main process now loads the renderer with an explicit HTTP referrer so OpenStreetMap tile requests include a valid `Referer` header and comply with the [tile usage policy](https://operations.osmfoundation.org/policies/tiles/). If you point the app at a different tile server, ensure its usage policy permits this client.
- **Reticulum — no LoRa companion parity**: Reticulum does not use Meshtastic/MeshCore `ConnectionDriver`, MQTT hybrid, channel pills, Rooms BBS, or Hop Goblins diagnostics. The **Chat** tab is **DM-only**; hub room chat lives on the **RRC** tab. Interface add/edit/delete updates config on disk — **restart the stack** after changes under `rns-stack`.
- **Reticulum — sidecar license**: The spawned `mesh-client-reticulum` binary is **AGPL-3.0** (separate process from the MIT Electron shell). See [docs/reticulum.md](docs/reticulum.md) and [docs/credits.md](docs/credits.md#bundled-binaries).
- **Graph / Topology visible-node cap**: Meshtastic and MeshCore **Graph** and Reticulum **Topology** render at most **400** nodes after hop filters (force-layout budget). Numeric **Max hops** is applied even when Show distant is off. Unknown hops are omitted unless Max hops is **All hops** and Show distant is on (they are not 1-hop neighbors). The nearby hop ceiling (Mesh hops > 1, Reticulum hops > 2) applies only when Max hops is **All hops**. Reticulum Topology can also filter **RF only** (RNode / KISS / BLE; hides TCP/I2P/Auto). Reticulum path-table ingest is a separate layer (renderer feed **800**, sidecar **2,000**).
- **Reticulum — propagation required for offline peers**: LXMF send fails with `no_propagation_node` when the destination is not in the path table and no cascade candidates exist (enabled remotes or local-prop). Local inbox Completes (`stored_locally`) ≠ peer delivery at a remote PN. When a path exists, Direct is tried first; on Direct fail the sidecar cascades preferred remote → other enabled remotes (hop-sorted) → local-prop last.

---
Expand Down
2 changes: 1 addition & 1 deletion docs/agents/meshcore-repeaters.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ MeshCore firmware **serializes traceroutes** — one active trace cycle per RF l
- **Login**: Optional for CLI/telemetry when password saved; Status/Neighbors do not require login RPC. **Room login** rejects immediately on prefix-matched LoginFail. **Repeater admin login** matches meshcore.js — LoginFail alone does not reject (congested links may emit LoginFail before LoginSuccess); timeout after LoginFail is reported as timeout, not wrong password.
- **Repeater CLI danger**: destructive commands (`meshcoreRepeaterCliDanger.ts`) require confirm modal in Repeaters panel; runtime rejects unconfirmed sends (`meshcore.errors.cliDangerNotConfirmed`). Commands longer than **512** characters (`REPEATER_CLI_MAX_COMMAND_LENGTH`) are rejected before send. Multi-hop CLI auto-pings once per session when no trace exists (`RepeatersPanel` → `onPing`); CLI aborts when ping does not produce a trace result. Safe quick pills include `clock`, `clock sync`, `clear stats`, `advert`, `board` (firmware CLI tokens as labels).
- **Per-repeater passwords:** shared factory `meshcorePerNodeCredentialStorage.ts` with `meshcoreRepeaterCredentialStorage.ts` / `meshcoreRoomCredentialStorage.ts` (`meshcoreRepeaterCredential:<nodeId>` and room keys in `app_settings` via IPC), `useMeshcoreRepeaterRemoteAuth.tsx`, `MeshcoreRepeaterPasswordControls.tsx`; Repeaters sidebar **Saved repeater passwords** + Forget (parallel to Rooms).
- **Waiting-message drain:** event 131 → `meshcoreWaitingMessagesDrain.ts` / `meshcoreProcessWaitingMessageItem.ts`; silent auto-drain vs manual **Sync now** (`MeshcoreWaitingMessagesHeaderIndicator.tsx` in the App header via `meshcoreWaitingMessagesStatusText.ts`; **queued backlog visible on any protocol tab**; **active sync spinner and paused/deferred** state only on the MeshCore tab); defers during TraceData/admin RPC.
- **Waiting-message drain:** event 131 → `meshcoreWaitingMessagesDrain.ts` / `meshcoreProcessWaitingMessageItem.ts`; silent auto-drain vs manual **Sync now** (`MeshcoreWaitingMessagesHeaderIndicator.tsx` in the App header via `meshcoreWaitingMessagesStatusText.ts`; **queued backlog visible on any protocol tab**; **active sync spinner and paused/deferred** state only on the MeshCore tab); defers during TraceData/admin RPC. Silent bulk `getWaitingMessages` circuit-opens after **2** consecutive timeouts (`MESHCORE_WAITING_MESSAGES_SILENT_BULK_TIMEOUT_TRIP`) and skips bulk until reconnect/success; disconnect bumps the attempt id so a late timeout cannot trip the next connection.
- **Cross-traffic**: Room sync/auto-login defer while `meshcoreCompanionRepeaterRfBusy()`; waiting-messages drain defers during TraceData.

Do not change behavior guarded by `meshcoreZeroHopRepeaterWorkingState.test.ts` without explicit user request. See [../meshcore-meshtastic-parity.md](../meshcore-meshtastic-parity.md#serialized-traceroutes-protocol-requirement).
3 changes: 2 additions & 1 deletion docs/agents/meshcore-rooms.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ Deep subsystem reference for AI assistants. Open this when a task touches MeshCo
- **Post length (single-packet):** room posts use the shared `ChatComposer` (`variant="room"`) and are **single-packet** like MeshCore chat — over the room payload limit (`getMeshcoreRoomPayloadLimit` in `chatComposerLimits.ts`) the send is blocked with the same `meshcoreSingleNotice` callout rather than split into `[i/N]` parts, and the ~5s fast-send advisory applies. See [`chat.md`](chat.md) (Composer limits / send cadence).
- **Session / RPC:** `meshcoreRoomSession.ts`, `meshcoreRoomLoginRpc.ts`, `meshcoreRoomPostRpc.ts`, `meshcoreRoomLogoutRpc.ts`, `meshcoreRoomLoginQueue.ts`, `meshcoreRoomLoginPathSync.ts`, `meshcoreRoomSentWait.ts`; credentials in `meshcoreRoomCredentialStorage.ts` / `meshcoreRoomSyncStorage.ts`.
- **Saved passwords:** `meshcoreRoomSavedSecrets.ts` — sidebar/overlay **Forget** / **Stop auto-login**; `forgetMeshcoreRoomSavedSecrets` clears credential + disables auto-login and auto-sync; `disableMeshcoreRoomLoginAfterAuthFailure` disables both without clearing password or in-memory failure UI.
- **Scheduler:** `meshcoreRoomSyncScheduler.ts` + `useMeshcoreRuntime.ts` — periodic re-login (Auto-sync, RF-only); single-flight ticks; background route resolve uses `skipTrace` / `MESHCORE_ROOM_SYNC_ROUTE_RESOLVE_FAST_MS`. Auth failure disables auto-sync and auto-login via `disableMeshcoreRoomLoginAfterAuthFailure`. Connect auto-login skips rooms with `getMeshcoreRoomAutoLoginFailure`. Timeouts in `timeConstants.ts` (shorter for TCP / 0-hop).
- **Scheduler:** `meshcoreRoomSyncScheduler.ts` + `useMeshcoreRuntime.ts` — periodic re-login (Auto-sync, RF-only); single-flight ticks; background route resolve uses `skipTrace` / `MESHCORE_ROOM_SYNC_ROUTE_RESOLVE_FAST_MS`. Auth failure disables auto-sync and auto-login via `disableMeshcoreRoomLoginAfterAuthFailure`. Timeouts in `timeConstants.ts` (shorter for TCP / 0-hop).
- **Connect auto-login:** `meshcoreRoomAutoLoginOnConnect.ts` + `useMeshcoreRuntime.ts` — 500ms debounce (`MESHCORE_ROOM_AUTO_LOGIN_DEBOUNCE_MS`) when Room contacts appear (`roomAutoLoginReadyKey`, including pubkey-ready). Overlapping triggers share one pass (`runMeshcoreRoomAutoLoginSingleFlight`) and dirty-re-run so a second room hydrating mid-pathSync still logs in. Disconnect bumps a generation so a dying pass must not `SendLogin` on the new conn. Skips logged-in, queued (`meshcoreIsRoomLoginQueued`), failed (`getMeshcoreRoomAutoLoginFailure`), and not-yet-hydrated contacts.
- **Wire text:** `meshcoreChannelText.ts` — channel/DM/room payloads, SignedPlain inbound strip, tapback/reply lines; `meshcoreGifWire.ts` — Open `g:GIFID`; `meshcoreOpenReaction.ts` — Open `r:HASH:INDEX`. Default companion keyless outbound; opt-in Open wire via App `meshcoreOpenWireCompatEnabled`.
3 changes: 2 additions & 1 deletion docs/agents/reticulum.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ Deep subsystem reference for AI assistants. Open this when a task touches the Re
- **LXMF replies:** sidecar stamps `FIELD_REPLY_TO` / capped `FIELD_REPLY_QUOTE` before sign; renderer ingest/Chat use `reticulum_reply_to_hash` + quote preview + jump-by-hash
- **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
- **RRC `/who`:** `RrcPanel` sends hub-global `/who <room>` with **no `K_ROOM`** so rrcd does not treat it as room chat; never `/who` synthetic `[hub]` / `@dm` rooms. Empty inbound `K_ROOM` → `[hub]` (`resolveRrcInboundChatRoom` / `RRC_HUB_STREAM_ROOM`), never the focused room. First `/who` NOTICE per named room may appear in the transcript (`consumeWhoTranscriptSlot` / `shouldShowRrcWhoTranscript`); later snapshots update the nicklist only. User-initiated Refresh / composer `/who` bypasses that slot.
- **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` / `pn_cascade.rs` (Direct-first; after Direct exhausts **multi-PN cascade**: preferred remote → other enabled remotes hop-sorted → in **Auto** only, up to 3 heard-but-not-added Discovered PNs hop-sorted → local-prop last; intermediate WS `sending` + `delivery_method: "propagated"` or `"stored_locally"`; terminal `delivered` at remote PN vs `stored_locally` for local hosted PN). **Local-prop** is a full PN (in-process cascade deposit via `accept_stamped_propagated_blob`; host peer `/offer` sync; auto Chat drain after peer ingress + post-peer silent `/get`; explicit local Sync via `drain_local_inbox`) — not an outbox; clients need not Prefer you. Propagated **link establishment timeout** advances the cascade when other PNs remain (avoids Prefer-hash timeout storms). Sync vs deposit: `PROPAGATION_SYNC_OUTBOUND_BUSY` / `PN_DEPOSIT_DEFER_ADVANCE_AFTER`. 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 cascade capacity remains — remote **or** enabled local-prop; also skips `propagated` / `stored_locally` rows so cascade 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. Remote PN Completes UI: **Stored at propagation node** (`ReticulumMessageStatusBadge` PN + green check); local-prop Completes: deposited on your hosted node (PN + amber house; peer sync may still propagate). Mode Off has no cascade capacity, so the link-timeout bridge fails the row. **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).
- **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, renderer ingest 800, drawn graph 400 after hop filters (same as LoRa Graph). Unknown hops only when All hops + Show distant. **RF only** checkbox keeps RNode/KISS/BLE spokes.
- **Retention:** App defaults Reticulum destination age/count pruning to 30 days / 10,000 destinations (favorites preserved; count max 50,000); Reticulum message retention independently enabled at 4,000. RRC room history retention independently enabled by default at **10,000** messages (30-day age prune) via `rrcMessageRetention*` settings and `db:pruneRrcMessagesByCount` / `db:pruneRrcMessagesByAge`.
- **Self label / header:** `reticulumSelfNodeLabel.ts` (`resolveReticulumSelfHeaderLabel` — Network display name in app header)
- **Nomad errors:** `lib/nomad/nomadPageErrorHumanize.ts` (sidecar error codes → i18n); LinkClient Nomad overlay in `reticulum-sidecar/patches/`
Expand Down
3 changes: 2 additions & 1 deletion docs/ci-cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ Automated dependency updates are configured in `.github/dependabot.yml`:
- **Open PRs:** `open-pull-requests-limit: 0` — Dependabot scans but does **not** open PRs.
Dependency bumps are applied manually via `pnpm run update` (`scripts/update.sh`), which
also runs dedupe, Ratspeak overlay PR checks, and an upstream release / new-org-repo watch
(rsLXST, lrgp-rs, Ratspeak with Games-parity nudge, LXMFace). Sibling **rsReticulum** /
(rsLXST, lrgp-rs, Ratspeak Games-parity when a newer published release exists, LXMFace
`js/lxmface.js` commit). Sibling **rsReticulum** /
**rsLXMF** / **rsNomad** / **rsLXST** / **lrgp-rs** float to `origin/main` via
`clone-ratspeak-stack.sh` (overlays must apply). See AGENTS.md §6.

Expand Down
Loading