| name | architecture |
| description | Project stack, module layout, and core data flow. Use when navigating the codebase, locating where functionality lives, or understanding how the client, Zustand store, and persistence layers connect.
|
| license | Proprietary. See LICENSE for complete terms. |
| metadata | {"author":"kNoAPP","version":"1.0.0"} |
Architecture
Browser-based companion client for
MeshCore LoRa mesh radios. Fully
client-side Next.js static site — no backend. Connects to companion radios over
USB Serial (Web Serial API), Bluetooth LE (Nordic UART service), or WiFi
WebSocket.
Stack
- Next.js (App Router,
output: 'export') + React + TypeScript
(strict)
- Tailwind CSS for layout; CSS variables in
app/globals.css for theming
- Zustand for global state (
store/meshStore.ts)
- react-i18next for in-app localization (
lib/i18n/, locales/*.json)
- Web Serial API, Web Bluetooth API, WebSocket for hardware
connectivity
- IndexedDB + AES-GCM for encrypted local message persistence
(
lib/storage.ts)
Module Layout
app/ Next.js App Router entry (page.tsx → AppShell)
components/ React UI (AppShell, Header, Sidebar, ChatArea, ConnectPanel, ...)
hooks/
useMeshCore.ts Wires MeshCoreClient ↔ Zustand store ↔ IndexedDB persistence
lib/
meshcore/
client.ts MeshCoreClient — command/response protocol, 5s polling loop
transports.ts USB / BLE / WiFi transport implementations (ITransport interface)
frames.ts Binary command encoding
frameParser.ts Inbound 0x3C-delimited frame parsing (USB + WiFi)
parsers.ts Binary response decoding → typed objects
constants.ts CMD/RESP codes, BLE UUIDs
storage.ts IndexedDB with AES-GCM encryption (key derived from channel secret + pubkey)
i18n/
config.ts Supported locales, storage key, browser-language detection
index.ts react-i18next instance bundling locales/*.json under one namespace
locales/ Per-language UI dictionaries (en is authoritative; es/de/fr drafts)
store/
meshStore.ts Zustand store — connection state, contacts, channels, messages, UI state
types/
meshcore.ts Shared TypeScript interfaces (Contact, Channel, Message, ...)
i18next.d.ts Module augmentation making t() keys type-safe against en.json
Core Data Flow
MeshCoreClient polls the radio every 5 seconds and fires callbacks →
useMeshCore bridges the client to the Zustand store → React components
re-render from store subscriptions.
State changes go through Zustand actions in store/meshStore.ts — never local
component state for data that belongs in the store, and never useEffect for
state that belongs in Zustand.
Persistence
localStorage is reserved for pre-connect preferences only (locale, theme)
— settings changeable before a radio is connected and needed synchronously at
first paint. Every other preference is per-radio and encrypted in IndexedDB,
carried in one ${pubkey}:preferences blob decrypted on connect and saved on
change. Message history, the advert cache, automation rules, and secrets follow
the same per-radio encrypted pattern (lib/storage.ts, key from
deriveStorageKey). See Persisting Preferences in AGENTS.md for the exact
steps to add a new preference.
Internationalization
All user-facing strings go through react-i18next's t(), keyed against
locales/en.json (the authoritative dictionary). components/I18nProvider.tsx
mounts the instance and syncs the active language from the store's locale
state; setLocale persists the choice to localStorage. In non-component code
(hooks, lib/utils.ts), use the i18n instance directly (i18n.t(...)). Any
new string must be added to locales/en.json and referenced via t() — never
hardcode display text in components.