feat: MeshCom LoRa mesh integration#936
Open
ceotjoe wants to merge 34 commits intoaccius:Stagingfrom
Open
Conversation
Adds full MeshCom UDP JSON integration following the same pattern as
APRS-TNC and Meshtastic: rig-bridge plugin → plugin bus → cloud-relay
→ OHC server routes → React panel + map markers.
rig-bridge:
- meshcom-udp.js: new plugin binding UDP port 1799, deduplicates packets
by hw_id+msg_id (mesh rebroadcast), emits normalised 'meshcom' bus
events (subtype pos/msg/telem) — no direct HTTP, no serverUrl config
- cloud-relay.js: subscribes to 'meshcom' bus events, batches into
meshcomPackets[], includes in relay/state push alongside APRS
- server.js (rig-bridge): forwards meshcomPackets[] from relay/state
to /api/meshcom/local/{pos|msg|telem} endpoints
- Plugin UI: MeshCom UDP listed with configurable bind port and address
server:
- server/routes/meshcom.js: new route — in-memory node store with ETag
(304 on no change), ?since= incremental messages, /api/meshcom/nodes,
/api/meshcom/messages, /api/meshcom/send, /api/meshcom/status
- Altitude converted ft→m at ingest (MeshCom sends GPS feet)
- Firmware normalised from both encodings: local node sends "4.35"+"p",
LoRa-relayed sends integer 35+"p"; both → "4.35p" via normalizeFirmware()
frontend:
- MeshComPanel.jsx: three-tab panel (Nodes / Messages / Info) with
battery bar, weather/telemetry row, age indicator, send-to form
- WorldMap.jsx: hexagonal SVG marker, popup with weather + firmware,
lat/lon 0 guard uses Number.isFinite (not falsy check)
- useMeshCom.js: 30s poll (LoRa cadence), ETag on nodes, ?since= on
messages — minimises traffic on idle mesh
- primaryCall() helper in callsign.js: strips relay path from src field
("OE1XYZ,OE2ABC" → "OE1XYZ") for panel and map display
- Wired into App.jsx, DockableApp.jsx, useMapLayers (showMeshCom toggle)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Two root causes identified:
1. /api/meshcom/status made an outbound HTTP call to rig-bridge
(AbortSignal.timeout(2000)), holding a browser connection open for up
to 2s on every poll cycle when rig-bridge was not running. With
HTTP/1.1's 6-connection-per-host limit, three concurrent MeshCom
fetches (nodes + messages + status) could starve DX cluster, rig
control and all other panels.
Fix: status endpoint is now fully synchronous. Connectivity is derived
from lastIngestTime (stamped whenever a pos/msg/telem packet arrives)
— no outbound call ever made. Response time < 1ms.
2. useMeshCom fired all three fetches via Promise.all with no timeouts,
meaning a single slow response blocked the entire refresh cycle and
the "Loading…" spinner never cleared.
Fix:
- loading initialised to false — panel renders immediately with empty
state, never blocks perceived page readiness
- Each fetch now carries AbortSignal.timeout(5000) so a non-responsive
server cannot hold a connection open beyond 5s
- Fetches fire independently (not Promise.all) — a slow nodes response
cannot delay message or status updates
- refresh() is no longer async/awaited — fire-and-forget per fetch
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds 32 meshcomPanel.* translation keys covering every user-visible
string in the panel and map popup, translated into all supported
languages: de, fr, es, it, pt, nl, ru, ja, zh, ko, ca, sl, ms, th, ka.
MeshComPanel.jsx:
- Added useTranslation to all four sub-components (NodesTab, MessagesTab,
InfoTab, MeshComPanel)
- Replaced every hardcoded English string with t() calls
- TABS array refactored to TAB_IDS ('nodes'|'messages'|'info') with
labels resolved via t() at render time — active-tab comparisons
now use stable IDs rather than translated strings
- Interpolated keys: nodeCount {{count}}, infoStats {{nodes}}/{{messages}},
sendGroup {{n}}, mapToggleHide/Show
WorldMap.jsx:
- Map popup battery, altitude, age and firmware labels use
t('meshcomPanel.mapPopup*') with {{age}} interpolation
- t added to useEffect dependency array
Technical terms intentionally left untranslated: MeshCom, LoRa,
OE1KBC, ICSSW, UDP, RSSI, SNR, FW, code snippet content in <pre>.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Icon (panel header + map marker): - Replaced the placeholder teal hexagon with the official MeshCom logo shape: central filled circle + 6 outer open circles + hexagonal ring of spokes, in brand crimson #8B1A2A - Panel SVG uses var(--bg-panel) fill on outer nodes so they correctly mask spoke lines on both dark and light themes - Map marker wraps the same design in a semi-transparent white disc for visibility on any map tile; aged nodes (>30 min) render in grey - iconSize changed 20×22 → 24×24, iconAnchor centred (12,12) instead of bottom-pinned, matching the circular shape Map toggle button: - Replaced the bespoke teal ON/OFF button with the same style used by DXClusterPanel: rgba background, JetBrains Mono, IconMap icon prefix, coloured border — crimson when active, grey when inactive Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Clicking any received message selects it (crimson left border +
subtle background highlight). Two contextual reply buttons appear
inline beneath the message text:
↩ Group N / ↩ Broadcast — replies to the same dst as the original
↩ CALLSIGN — direct reply to the sender's callsign
(hidden when both targets would be identical)
The second button only appears when the group/broadcast target differs
from the sender, avoiding a duplicate option.
"Replying to CALLSIGN" context strip appears above the send form while
a reply is active, with an × to cancel. Cancelling resets the To:
field back to Broadcast (*).
Clicking a reply button pre-selects the To: dropdown and focuses
the message input immediately via inputRef.
If the sender is not a known node, their callsign is dynamically added
to the To: dropdown via dropdownCalls so the select value is always
valid regardless of node list state.
On successful send the reply context is automatically cleared.
i18n: 5 new meshcomPanel.reply* keys added to all 17 language files
(en, de, fr, es, it, pt, nl, ru, ja, zh, ko, ca, sl, ms, th, ka).
Colour scheme: message sender callsign and Send button updated from
teal to brand crimson #8B1A2A for consistency with the new icon.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
MeshCom and APRS callsigns include numeric SSID suffixes (-12, -99, etc.) that are not part of the licensed callsign and cause QRZ.com lookups to fail. extractBaseCall() now strips -\d+ before processing slash-portable notation, so OE1XYZ-12 resolves to OE1XYZ on QRZ. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces hardcoded #8B1A2A (crimson) on the Messages-tab source callsign with var(--accent-cyan) from the theme palette, which has adequate contrast on both dark and light themes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every hardcoded hex colour in MeshComPanel is now a theme-aware CSS custom property: #ef4444 → var(--accent-red) (errors, battery low) #f59e0b → var(--accent-amber) (battery mid) #22c55e → var(--accent-green) (battery full, connected dot) #6b7280 / accius#666 / accius#888 → var(--text-muted) (inactive dots, borders) #2dd4bf → var(--accent-cyan) (node age border, active tab) #8B1A2A / #c0394e → var(--accent-red) (selected border, reply btns, send btn) rgba(139,26,42,…) tints → var(--bg-tertiary) / var(--bg-secondary) rgba(100,100,100,…) → var(--bg-secondary) #fff on send button → var(--title-bar-text) SVG logo strokes/fills kept as brand-identity crimson (#8B1A2A). Active-tab label kept as #000 — no CSS variable covers "always dark on a cyan background" across all themes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Messages are now pruned from the in-memory buffer in the same 60-second
cleanup interval that already handles node expiry:
- MESHCOM_MESSAGE_MAX_AGE_HOURS env var (default 8)
- Messages are stored in arrival order; cleanup walks from the front
and splices any entries older than the cutoff in one call (O(n) scan,
O(k) splice where k = expired count — typically 0 during normal ops)
- Ring-buffer cap (200 entries) is retained as a safety net
Node expiry is unchanged (MESHCOM_NODE_MAX_AGE_MINUTES, default 60 min).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a full MeshCom section to the rig-bridge README covering: - How the plugin fits into the data pipeline (node → UDP → rig-bridge → cloud relay → OHC server → panel/map) - MeshCom firmware configuration (--extudp, broadcast IP) - Rig Bridge setup via the Plugins UI and manual JSON config - Full config reference table for all six fields - Packet types (pos / msg / telem) and processing details - Deduplication behaviour (hw_id + msg_id, 60 s TTL) - OHC data retention table with env var overrides - Troubleshooting table for common issues - Entry in Plugin Manager summary table - meshcom-udp.js added to project structure tree Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rig-bridge/plugins/meshcom-udp.js
- Merge normalizeLat/normalizeLon into single normalizeCoord helper
- Hoist isDuplicate + bus guard above type switch (was repeated 3×)
- Count packetsRx only after successful JSON parse; capture msg.toString() once; log non-JSON frames in verbose mode
- socket 'error' handler now cleans up dedupTimer and socket instead of
just setting running=false; logs if socket.close() itself throws
- disconnect() empty catch{} → error log with port-still-in-use hint
- Warn at connect() time when plugin bus is absent
- msgId: json.msg_id || null → ?? null (CLAUDE.md: || is for strings/booleans)
- Update JSDoc: document actual relay path and wire→bus telem field renames
server/routes/rig-bridge.js
- Replace silent .catch(()=>{}) on MeshCom ingest forward with logging
.then(r=>logWarn if !r.ok).catch(e=>logWarn) so failures are visible
server/routes/meshcom.js
- CONFIG.rigControl?.port || 5555 → ?? 5555 (port 0 is valid per CLAUDE.md)
- Store setInterval ref as cleanupTimer; call .unref() so it doesn't
prevent process exit
- Extract parseOrNull(v) helper; remove inline parseFloat repetition
- Array.from(weather.values()) instead of spread in weather endpoint
- /api/meshcom/send catch distinguishes TimeoutError from ECONNREFUSED
with actionable user-facing messages; logs via logWarn
- "ring buffer" → "bounded FIFO" in header comment
- Improve "Merge weather" comment for clarity
src/hooks/useMeshCom.js
- export const → export function
- fetchStatus catch now logs non-abort/timeout errors (was swallowed)
- sendMessage wraps apiFetch in try/catch to translate network/timeout
errors into user-friendly messages before they surface in the UI
- Correct header comment (loading is always false, not "cleared on mount")
src/components/MeshComPanel.jsx
- Compute primaryCall(node.call) once per row; use in three places
- Remove dead isDirect variable
- Map toggle background ternary simplified (both branches were identical)
- Extract replyButtonStyle constant (was duplicated inline)
- Extract replyLabel(target) helper to replace nested ternary
src/components/CallsignLink.jsx
- Remove unused useEffect import
rig-bridge/README.md
- Expand architecture diagram to show full ingest path
(relay/state → meshcom/local/{subtype} → store → GET endpoints)
- telem row documents wire-name → bus-name renames
(temp→tempC, pressure→pressureHpa, co2→co2ppm)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously all MeshCom data (nodes, messages, weather) was stored in
three module-level singletons, meaning every user on a shared server
saw every other user's mesh traffic merged into one pool.
server/routes/meshcom.js
- Replace the three global Maps/array with a per-session `sessions` Map
(sessionId → { nodes, messages, weather, lastIngestTime, lastAccessTime })
- getOrCreateSession() — used on write paths (ingest); creates session on
first packet from that rig-bridge relay connection
- getSessionIfExists() — used on read paths; never silently creates an
empty session (which would prevent TTL expiry)
- Cleanup timer now runs two levels: session-level expiry (evict entire
session after SESSION_TTL_MS = 90 min idle, configurable via
MESHCOM_SESSION_TTL_MINUTES) then data-level expiry within each live
session (same node/message TTLs as before)
- All ingest endpoints (pos/msg/telem) require sessionId in the body;
return 400 if absent
- All GET endpoints (nodes/messages/weather/status) require ?session=<id>;
return empty payload (not 400) when session is unknown so the browser
panel degrades gracefully before the first relay push arrives
server/routes/rig-bridge.js
- Inject sessionId into the MeshCom ingest body when forwarding packets
from the cloud relay push, using the sessionId already extracted from
the relay request headers/body
src/hooks/useMeshCom.js
- Add getSessionId() — generates an 8-char alphanumeric ID, persisted in
localStorage under 'ohc-meshcom-session'; rejects legacy UUIDs (same
pattern as useWSJTX to avoid Bitdefender false positives)
- Append ?session=<id> to all GET requests (nodes, messages, status)
- Pass session in POST /api/meshcom/send body
- Return sessionId from the hook (available to callers if needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The relay ingest uses the session ID from the x-relay-session header (= 'ohc-wsjtx-session' in localStorage, the relay session established when the user configured rig-bridge). useMeshCom was generating a separate 'ohc-meshcom-session' key, so the server stored data under the relay session but the browser polled under a different ID and received nothing. Fix: getSessionId() now reads from 'ohc-wsjtx-session', the shared relay session key. All relay-delivered data (WSJTX, APRS, MeshCom) flows through the same relay session, so all hooks must poll with the same session ID that rig-bridge sends in x-relay-session. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously useWSJTX had its own inline getSessionId() reading from 'ohc-wsjtx-session', and useMeshCom was patched to read from the same WSJTX key as a workaround. Both data types flow through the same cloud relay plugin under the same session ID, so the session utility should be shared and named after what it actually is. src/utils/relaySession.js (new) - getRelaySessionId() — single source of truth for the relay session ID - Canonical localStorage key: 'ohc-relay-session' - On first load, migrates any existing 'ohc-wsjtx-session' value so existing rig-bridge configurations keep working without reconfiguration - Validates ID format (8–12 char alphanumeric) to reject legacy UUIDs src/hooks/useWSJTX.js - Remove inline getSessionId() / localStorage logic - Import and use getRelaySessionId() from the shared util src/hooks/useMeshCom.js - Remove workaround comment and inline key reference - Import and use getRelaySessionId() from the shared util Any future hook consuming relay-delivered data should import getRelaySessionId() from src/utils/relaySession.js. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
relaySession.js — isValidId() previously rejected IDs longer than 12
characters. The /api/rig-bridge/relay/configure endpoint generates IDs
via crypto.randomBytes(8).toString('hex'), which produces a 16-character
lowercase hex string. Because 16 > 12 the validation silently failed,
causing getRelaySessionId() to generate a fresh random ID that never
matched the rig-bridge session — all ingest data was stored under the
16-char ID while the browser polled with the new 8-char ID, so the
MeshCom panel always showed empty data.
Upper bound widened to 32 (accommodates 8-char client-generated IDs,
16-char relay/configure IDs, and any future formats up to 32 chars).
Comment updated to document both generator variants.
meshcom.js — demote the three ingest log lines from logDebug to logInfo
so packet arrivals are visible without LOG_LEVEL=debug, making it easier
to confirm data is reaching the server when troubleshooting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a logInfo line in the relay/state handler that fires whenever the server receives MeshCom packets from the rig-bridge cloud relay push. Previously the entire ingest path was silent on success, making it impossible to tell from the server log whether packets were arriving at all, even with LOG_LEVEL=debug. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…licked
relay/configure generates a fresh 16-char session ID and the user pastes
it into the rig-bridge config. Previously that ID was only saved to the
OHC server config (config.rigControl.cloudRelaySession); the browser's
getRelaySessionId() reads from localStorage and was unaware of it, so
all relay-consuming hooks (WSJTX, MeshCom, APRS) kept polling with
whatever random ID they had before — never matching the rig-bridge.
Fix: add setRelaySessionId() to relaySession.js and call it from the
SettingsPanel button handler immediately after receiving the session from
relay/configure. The ID is now written to localStorage ('ohc-relay-session')
so every hook picks it up on their next render without requiring a page
reload or manual localStorage edit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The session ID belongs to the browser, not the server. Storing it in server config makes no sense in a multi-user environment — every user clicking "Connect Cloud Relay" would overwrite a shared field. - Read initial UI state from localStorage['ohc-relay-session'] instead of config.rigControl.cloudRelaySession - Remove cloudRelaySession from the onSave payload entirely - Disconnect button now removes the localStorage key so relay-consuming hooks generate a fresh ID on next load - The "Active — session..." display still works; it reads the same localStorage value that getRelaySessionId() uses Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the WSJTX data path:
meshcom-udp bus event → cloud-relay POST /relay/state
→ server fans { type:'plugin', event:'meshcom' } to relayStreamClients
→ RigContext EventSource → window 'rig-plugin-data' CustomEvent
→ useMeshCom SSE handler → immediate setNodes / setMessages
Polling stays active as a safety net for initial load and packet
recovery; lastMessageTsRef is kept in sync by both paths so the
?since= incremental parameter remains accurate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The cloud relay onmessage handler only dispatched 'state' type messages. 'plugin' and 'plugin-init' messages (WSJTX decodes, MeshCom packets, APRS) were silently dropped instead of being forwarded as rig-plugin-data window events. Individual hooks (useMeshCom, useWsjtx) listen for those window events and never received anything via the cloud relay SSE path. Adds the same plugin dispatch branch that the local/direct SSE path already had, making the two paths consistent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nfig
Root cause of "plugin offline":
RigContext read cloudRelaySession from rigConfig (server config), but
we removed it from the save payload in a previous commit. So rigConfig
always had an empty session → isCloudRelay = false → local-direct mode
→ relay/stream SSE never opened → relayStreamClients had no entries for
the session → SSE fan-out wrote to nobody → MeshCom status showed offline.
Fix:
- relaySession.js: add ohc-relay-configured flag + isRelayConfigured(),
setRelayConfigured(), clearRelaySession() helpers. The flag is set only
when the user explicitly clicks "Connect Cloud Relay", preventing an
auto-generated data-isolation session from enabling cloud relay mode.
- RigContext: read cloudRelaySession from localStorage via isRelayConfigured()
+ localStorage['ohc-relay-session'] instead of rigConfig.cloudRelaySession.
Includes _migrateCloudRelaySession() which silently copies old server-config
sessions to localStorage on first render so users don't need to re-connect.
- SettingsPanel: cloudRelaySession useState falls back to server config for
migration; Connect calls setRelayConfigured(true); Disconnect calls
clearRelaySession() (clears both session and configured flag).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three bugs in the no-cloud-relay path:
1. rig-bridge.js: meshcom bus events were never bridged to the /stream SSE.
decode, status, qso, aprs etc all had pluginBus.on() handlers that call
broadcast() — meshcom had none. Added the missing handler so browsers in
local/direct mode receive { type:'plugin', event:'meshcom' } over the
same SSE connection used for WSJTX decodes.
2. useMeshCom: polling fetches would overwrite SSE-populated state with empty
server responses (the OHC server has no ingest data in local mode).
- fetchNodes: skip setNodes if server returns empty AND SSE is live
- fetchStatus: skip entirely while SSE is fresh (last event < 25 min ago)
- SSE handler: set connected=true immediately on packet arrival instead
of waiting for the next 30 s poll cycle
3. server/routes/meshcom.js: ACTIVE_WINDOW_MS was 5 min. LoRa beacons can
be 15+ min apart, so sparse networks showed "offline" between beacons in
cloud relay mode. Raised to 30 min.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In local mode the OHC server proxies the send to rig-bridge via
CONFIG.rigControl.host:port — but when the server is in the cloud and
rig-bridge is on the local network this fails with a network error that
surfaces as "MeshCom UDP plugin not available".
Fix: mirror the pattern used by freq/mode/PTT commands in RigContext.
In local/direct mode the browser POSTs directly to rig-bridge's
/api/meshcom-udp/send endpoint instead of going through the server proxy.
- RigContext: expose rigUrl and isCloudRelay in context value
- useMeshCom: accept rigBridgeUrl option; when set, fetch() directly to
rig-bridge instead of apiFetch() to /api/meshcom/send
- MeshComPanel: read rigUrl + isCloudRelay from useRig(), pass
rigBridgeUrl={isCloudRelay ? null : rigUrl} to useMeshCom
Cloud relay mode is unchanged — still proxies via the OHC server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ggestions The <select> only allowed choosing from known nodes or fixed groups. Replaced with <input type="text" list="meshcom-to-suggestions"> so the user can type any callsign or group number freely while still getting autocomplete suggestions for: * — broadcast 0–5 — mesh groups known node callsigns (from live node list) reply-to sender (when a message is selected) Input auto-uppercases on every keystroke so callsigns arrive correctly formatted. The send handler already had `toField || '*'` as a guard for an empty field. Reply buttons still call setToField() directly — unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches the badge shown for APRS TNC in the rig-bridge config UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add MeshCom UDP _(Beta)_ to the table of contents (was missing entirely) - Renumber TOC entries 9-12 to accommodate the new entry - Add _(Beta)_ suffix to both MeshCom UDP Plugin section headings - Fix stale troubleshooting row: MeshCom now works in local/direct mode too, not only via Cloud Relay Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Group 1 — before merge: - relaySession.js: add console.warn to all 5 empty catch blocks so localStorage failures are visible (privacy browsers, quota exceeded) - meshcom-udp.js: guard json.src in handlePacket — prevents phantom 'UNDEFINED' nodes from malformed UDP datagrams - meshcom.js: validate ?since= query param with Number.isFinite to prevent NaN breaking incremental message fetch - meshcom.js: validate env-var parseInt with parseEnvMinutes helper — NaN would silently disable node/session expiry (memory leak) - MeshComPanel.jsx: fix group reply regex — /^[0-5]$/ instead of lexicographic string comparison that misclassified multi-char callsigns - meshcom.js: fix wrong function name in comment (getSessionId → getRelaySessionId); demote message body from logInfo to logDebug Group 2 — before merge: - cloud-relay.js: implement relayWsjtx/relayAprs/relayMeshCom config flags (documented but previously unread); all default to true - cloud-relay.js: restore pendingAprs and pendingMeshCom on push failure — only pendingDecodes was previously restored - useMeshCom.js: add msgId dedup to polling merge path to match the SSE handler and prevent duplicate messages Group 3 — before draft promotion: - meshcom-udp.js: always warn on non-JSON datagrams (was gated behind verbose flag, hiding firmware format changes in production) - meshcom-udp.js: add packetsTxErrors counter to getStatus() so silent async UDP TX failures are observable - rig-bridge.js: log SSE client eviction on both fan-out paths - MeshComPanel.jsx + WorldMap.jsx: compute node ageMin at render time from node.timestamp so SSE-delivered nodes age correctly in local/direct mode where polling returns no server-side ageMin - useMeshCom.js: move SSE_STALE_MS to module level alongside POLL_INTERVAL and FETCH_TIMEOUT_MS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- themes.css: add --bg-row-alt to all four themes (dark/light/legacy/retro) so alternating node/message row backgrounds are visible on light and correctly tinted on legacy (phosphor green) and retro - MeshComPanel.jsx: use var(--bg-row-alt) for node list zebra stripe instead of rgba(255,255,255,0.03) which was invisible on light theme - MeshComPanel.jsx: fix send button text color — var(--title-bar-text) is only defined in the retro theme, making the button text invisible in dark/light/legacy when active; replace with #fff (white on red) - WorldMap.jsx: replace hardcoded #2dd4bf/accius#888/#aaa in the MeshCom node map popup with var(--accent-cyan)/var(--text-muted) so the popup callsign and age text adapt correctly to every theme Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add className="panel" (padding:0) to the outer MeshCom container so it inherits var(--bg-panel), border, border-radius, and backdrop-filter from the shared .panel class — matching DX Cluster and other panels. Add className="meshcom-header" to the header bar and add a CSS rule in main.css that gives it the retro Windows title-bar gradient without the -10px margin compensation needed by 10px-padded panels (MeshCom uses padding:0 so the header is already flush to the edges). Result per theme: dark — semi-transparent dark panel, amber border, blur backdrop light — semi-transparent white panel, blue border, blur backdrop legacy — near-black panel, green border retro — Windows 95 grey 3D bevel panel, blue gradient title bar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The retro theme's button rule (color:#000 !important) and inline var(--text-primary) (#000) were overriding the title bar's white color. Adding a wildcard child selector with higher specificity [0,3,1] vs the button rule's [0,1,1] ensures spans, the node count, and the map toggle button all render white against the blue gradient title bar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous wildcard rule forced all children white. A more specific button selector [0,3,2] overrides it to keep button text black, matching the retro theme's raised 3D button appearance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The wildcard * rule was hitting SVG icon elements inside the button. Extend the button black-text rule to also cover button descendants so the IconMap SVG renders black alongside the button label text. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collaborator
Author
|
Please only merge after #943! Needs to be adjusted slightly then. |
Conflicts resolved in rig-bridge/package.json and rig-bridge/rig-bridge.js: upstream bumped to 2.1.3 (patch), feature branch is at 2.2.0 (minor for MeshCom integration) — keeping 2.2.0 as it supersedes the patch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
meshcom-udprig-bridge plugin and displays nodes on the map, messages in a chat-style tab, and telemetry/weather data in the node listrelay/streamSSE using the same{ type: 'plugin', event: 'meshcom' }envelope as WSJTX decodes;RigContextalready forwarded these asrig-plugin-datawindow events<input type="text" list="…">+<datalist>so any callsign or group can be entered freely while known nodes are still offered as suggestionslocalStorage;RigContextmigrates old server-config sessions automatically on first load;ohc-relay-configuredflag distinguishes explicit cloud relay setup from auto-generated data-isolation IDsmeshcomplugin bus events bridged to/streamSSE (was the only event type missing);meshcom-udpplugin documented in READMETest plan
meshcom-udpplugin in rig-bridge config; confirm nodes appear on map and messages in Messages tab within one beacon intervalrelay/streamSSE fan-out; status shows activeChecklist
server.js: caches have TTLs and size caps (we serve 2,000+ concurrent users)var(--accent-cyan), etc.).bak,.old,console.logdebug lines, or test scripts included