9.7 KiB
Frontend AGENTS.md
This document is the frontend working guide for agents and developers.
Keep it aligned with frontend/src source code.
Stack
- React 18 + TypeScript
- Vite
- Vitest + Testing Library
- shadcn/ui primitives
- Tailwind utility classes + local CSS (
index.css,styles.css) - Sonner (toasts)
- Leaflet / react-leaflet (map)
meshcore-hashtag-cracker+nosleep.js(channel cracker)
Frontend Map
frontend/src/
├── main.tsx # React entry point (StrictMode, root render)
├── App.tsx # App shell and orchestration
├── api.ts # Typed REST client
├── types.ts # Shared TS contracts
├── useWebSocket.ts # WS lifecycle + event dispatch
├── messageCache.ts # Conversation-scoped cache
├── prefetch.ts # Consumes prefetched API promises started in index.html
├── index.css # Global styles/utilities
├── styles.css # Additional global app styles
├── lib/
│ └── utils.ts # cn() — clsx + tailwind-merge helper
├── hooks/
│ ├── index.ts # Central re-export of all hooks
│ ├── useConversationMessages.ts # Fetch, pagination, dedup, ACK buffering
│ ├── useUnreadCounts.ts # Unread counters, mentions, recent-sort timestamps
│ ├── useRepeaterMode.ts # Repeater login/command workflow
│ ├── useAirtimeTracking.ts # Repeater airtime stats polling
│ ├── useRadioControl.ts # Radio health/config state, reconnection
│ ├── useAppSettings.ts # Settings, favorites, preferences migration
│ ├── useConversationRouter.ts # URL hash → active conversation routing
│ └── useContactsAndChannels.ts # Contact/channel loading, creation, deletion
├── utils/
│ ├── urlHash.ts # Hash parsing and encoding
│ ├── conversationState.ts # State keys, in-memory + localStorage helpers
│ ├── favorites.ts # LocalStorage migration for favorites
│ ├── messageParser.ts # Message text → rendered segments
│ ├── pathUtils.ts # Distance/validation helpers for paths + map
│ ├── pubkey.ts # getContactDisplayName (12-char prefix fallback)
│ ├── contactAvatar.ts # Avatar color derivation from public key
│ ├── rawPacketIdentity.ts # observation_id vs id dedup helpers
│ ├── visualizerUtils.ts # 3D visualizer node types, colors, particles
│ └── lastViewedConversation.ts # localStorage for last-viewed conversation
├── components/
│ ├── StatusBar.tsx
│ ├── Sidebar.tsx
│ ├── ChatHeader.tsx # Conversation header (trace, favorite, delete)
│ ├── MessageList.tsx
│ ├── MessageInput.tsx
│ ├── NewMessageModal.tsx
│ ├── SettingsModal.tsx
│ ├── settingsConstants.ts # Settings section ordering and labels
│ ├── RawPacketList.tsx
│ ├── MapView.tsx
│ ├── VisualizerView.tsx
│ ├── PacketVisualizer3D.tsx
│ ├── PathModal.tsx
│ ├── CrackerPanel.tsx
│ ├── BotCodeEditor.tsx
│ ├── ContactAvatar.tsx
│ └── ui/ # shadcn/ui primitives
├── types/
│ └── d3-force-3d.d.ts # Type declarations for d3-force-3d
└── test/
├── setup.ts
├── fixtures/websocket_events.json
├── api.test.ts
├── useAirtimeTracking.test.ts
├── appFavorites.test.tsx
├── appStartupHash.test.tsx
├── contactAvatar.test.ts
├── integration.test.ts
├── messageCache.test.ts
├── messageParser.test.ts
├── pathUtils.test.ts
├── radioPresets.test.ts
├── rawPacketIdentity.test.ts
├── repeaterMode.test.ts
├── settingsModal.test.tsx
├── sidebar.test.tsx
├── unreadCounts.test.ts
├── urlHash.test.ts
├── useConversationMessages.test.ts
├── useConversationMessages.race.test.ts
├── useRepeaterMode.test.ts
├── useWebSocket.dispatch.test.ts
└── useWebSocket.lifecycle.test.ts
Architecture Notes
State ownership
App.tsx orchestrates high-level state and delegates to hooks:
useRadioControl: radio health/config state, reconnect/reboot pollinguseAppSettings: settings CRUD, favorites, preferences migrationuseContactsAndChannels: contact/channel lists, creation, deletionuseConversationRouter: URL hash → active conversation routinguseConversationMessages: fetch, pagination, dedup/update helpersuseUnreadCounts: unread counters, mention tracking, recent-sort timestampsuseRepeaterMode: repeater login/command workflowuseAirtimeTracking: repeater airtime stats polling
Local message IDs
useRepeaterMode and useAirtimeTracking each have a createLocalMessage that generates ephemeral (non-persisted) message IDs via -Date.now(). Both hooks write to the same setMessages, so a same-millisecond call would produce duplicate IDs. In practice this requires a repeater command response and an airtime poll to land in the exact same ms — staggeringly unlikely and cosmetic-only (React duplicate key warning). Not worth fixing.
Initial load + realtime
- Initial data: REST fetches (
api.ts) for config/settings/channels/contacts/unreads. - WebSocket: realtime deltas/events.
- On WS connect, backend sends
healthonly; contacts/channels still come from REST.
New Message modal
NewMessageModal intentionally preserves form state (tab, inputs, checkboxes) when closed and reopened. The component instance persists across open/close cycles. This is by design so users don't lose in-progress input if they accidentally dismiss the dialog.
Message behavior
- Outgoing sends are added to UI after the send API returns (not pre-send optimistic insertion), then persisted server-side.
- Backend also emits WS
messagefor outgoing sends so other clients stay in sync. - ACK/repeat updates arrive as
message_ackedevents. - Outgoing channel messages show a 30-second resend control; resend calls
POST /api/messages/channel/{message_id}/resend.
Visualizer behavior
VisualizerView.tsxhostsPacketVisualizer3D.tsx(desktop split-pane and mobile tabs).PacketVisualizer3Duses persistent Three.js geometries for links/highlights/particles and updates typed-array buffers in-place per frame.- Packet repeat aggregation keys prefer decoder
messageHash(path-insensitive), with hash fallback for malformed packets. - Raw packet events carry both:
id: backend storage row identity (payload-level dedup)observation_id: realtime per-arrival identity (session fidelity)
- Packet feed/visualizer render keys and dedup logic should use
observation_id(fallback toidonly for older payloads).
WebSocket (useWebSocket.ts)
- Auto reconnect (3s) with cleanup guard on unmount.
- Heartbeat ping every 30s.
- Event handlers:
health,message,contact,raw_packet,message_acked,error,success,pong(ignored). - For
raw_packetevents, useobservation_idas event identity;idis a storage reference and may repeat.
URL Hash Navigation (utils/urlHash.ts)
Supported routes:
#raw#map#map/focus/{pubkey_or_prefix}#visualizer#channel/{channelKey}#channel/{channelKey}/{label}#contact/{publicKey}#contact/{publicKey}/{label}
Legacy name-based hashes are still accepted for compatibility.
Conversation State Keys (utils/conversationState.ts)
getStateKey(type, id) produces:
- channels:
channel-{channelKey} - contacts:
contact-{publicKey}
Use full contact public key here (not 12-char prefix).
conversationState.ts keeps an in-memory cache and localStorage helpers used for migration/compatibility.
Canonical persistence for unread and sort metadata is server-side (app_settings + read-state endpoints).
Utilities
utils/pubkey.ts
Current public export:
getContactDisplayName(name, pubkey)
It falls back to a 12-char prefix when name is missing.
utils/pathUtils.ts
Distance/validation helpers used by path + map UI.
utils/favorites.ts
LocalStorage migration helpers for favorites; canonical favorites are server-side.
Types and Contracts (types.ts)
AppSettings currently includes:
max_radio_contactsfavoritesauto_decrypt_dm_on_advertsidebar_sort_orderlast_message_timespreferences_migratedadvert_intervallast_advert_timebots
Repeater Mode
For repeater contacts (type=2):
- Telemetry/login phase (
POST /api/contacts/{key}/telemetry) - Command phase (
POST /api/contacts/{key}/command)
CLI responses are rendered as local-only messages (not persisted to DB).
Styling
UI styling is mostly utility-class driven (Tailwind-style classes in JSX) plus shared globals in index.css and styles.css.
Do not rely on old class-only layout assumptions.
Security Posture (intentional)
- No authentication UI.
- Frontend assumes trusted network usage.
- Bot editor intentionally allows arbitrary backend bot code configuration.
Testing
cd frontend
npm run test:run
npm run build
When touching cross-layer contracts, also run backend tests from repo root:
PYTHONPATH=. uv run pytest tests/ -v
Editing Checklist
- If API/WS payloads change, update
types.ts, handlers, and tests. - If URL/hash behavior changes, update
utils/urlHash.tstests. - If read/unread semantics change, update
useUnreadCountstests. - Keep this file concise; prefer source links over speculative detail.