MeshDash Docs
R2.0
/
Home Frontend Guide Frontend Architecture

Frontend Architecture

Frontend Guide frontend spa single page app javascript view loadView meshState sse lazy load app.js ui.js
How the MeshDash single-page application works — view loading, global state, SSE dispatch, and JavaScript modules.

The MeshDash frontend is a Single-Page Application (SPA) served from /static/. There is no JavaScript framework — it uses vanilla JS modules loaded via <script> tags, a shared global state object, and a custom view-loader system.

Directory Layout

static/
├── css/
│   └── style.css          ← all component styles, CSS variables, layout
├── js/
│   ├── app.js             ← core: state, SSE, loadView(), fetch patch, init
│   ├── ui.js              ← shared utilities: toast, modals, inspector, topbar
│   ├── webserial.js       ← Web Serial bridge (browser USB)
│   ├── overview.js        ← overview view module
│   ├── map.js             ← map view module
│   ├── dmes.js            ← direct messages module
│   ├── channels.js        ← channels module
│   ├── analytics.js       ← analytics module
│   ├── compare.js         ← node comparison module
│   ├── shark.js           ← MeshShark packet analyser
│   ├── traceroute.js      ← traceroute module
│   ├── iot.js             ← web telemetry module
│   ├── tasks.js           ← task scheduler module
│   ├── autoreply.js       ← auto-reply module
│   ├── plugins.js         ← plugin manager module
│   ├── node_config.js     ← node configuration module
│   └── terminal.js        ← C2 command terminal
└── views/
    ├── overview.html      ← HTML template for the overview view
    ├── map.html
    ├── dmes.html
    ├── channels.html
    ├── analytics.html
    ├── compare.html
    ├── shark.html
    ├── traceroute.html
    ├── iot.html
    ├── tasks.html
    ├── autoreply.html
    ├── plugins.html
    ├── node_config.html
    └── settings.html

How Views Load

The <main id="content"> element is the mount point. When a sidebar nav item is clicked it calls loadView('overview') (or whichever view name):

  1. Aborts any in-flight fetch from the previous view using AbortController
  2. GET /static/views/{viewName}.html — fetches the view HTML template
  3. Injects the HTML into #content as <div class="view-wrapper">...</div>
  4. Calls the view's init function via a dispatch table (e.g. window.C2CommsApp.init())
  5. On mobile, closes the sidebar

Views are lazy-loaded — the JS module for a view is only loaded from the server the first time that view is opened. Subsequent visits reuse the already-loaded module from window.

Global State: window.meshState

All live data lives in window.meshState, shared across every view:

window.meshState = {
    connectionStatus: 'Initializing',  // string from SSE
    nodes: {},          // dict: node_id → node object (live, updated by SSE)
    packets: [],        // recent packets from /api/packets (historical load)
    stats: {},          // session stats from SSE 'stats' events
    currentView: null,  // name of the currently active view
    sessionStart: Date.now(),
    local_node_id: null,
    dmUnread: {},       // node_id → unread count for Direct Messages
    channelUnread: {}   // channel_index → unread count for Channels
}

Any JS module can read window.meshState.nodes directly. It is always the current live state — no stale copies.

SSE — Real-Time Event Stream

On startup, app.js opens an EventSource to /sse (or /sse/{slot_id} for non-primary slots). The SSE manager (_sse) handles:

  • Exponential backoff reconnection (1 s → 16 s max)
  • A 90-second dead-stream watchdog — forces reconnect if no event received
  • Tab visibility change handler — reconnects if stream is dead when tab becomes visible

On each SSE event, app.js updates window.meshState and then calls the active view's update function (if it exists). The dispatch is:

SSE EventmeshState updateUI side-effect
nodesMerges node list into meshState.nodesView refreshes node grid
node_updateMerges single nodeUpdates node card in-place
packetPrepends to meshState.packetsMeshShark / overview live feed
statsUpdates meshState.statsOverview KPI strip updates
connection_statusUpdates meshState.connectionStatusTopbar CORE/RF dots, status text
message_status_updateDM/Channels message bubble status icon
local_node_infoUpdates meshState.local_node_idTopbar node ID display
system_updateToast notification
traceroute_resultTraceroute result panel renders
activityTX/RX LED indicators in topbar flash
plugin_updatePlugins grid updates card status
pingResets 90-second dead-stream watchdog

Active Slot (Multi-Radio)

The frontend tracks which radio slot is being viewed via window._activeSlotId (default: 'node_0'). Switching slots (from the Settings view) calls:

window._sseSetSlot('node_1');

This updates CONFIG.ssePath to /sse/node_1 and reconnects the SSE stream. All API calls that need to be slot-scoped use the _slotAppend(url) helper which appends ?slot_id=node_1.

Global Utility Functions

escapeHtml(text)
XSS-safe HTML encoding. Use for all user-sourced strings injected into innerHTML.
fmtTime(ts)
Unix timestamp → HH:MM:SS string.
fmtTimeAgo(ts)
Unix timestamp → human relative time ("2m ago", "3h ago").
fmtUptime(sec)
Seconds → "2d 4h" / "1h 30m" / "45m".
getMeshVal(node, ...keys)
Safely reads a value from a node object, searching nested sub-objects (deviceMetrics, user, position, etc.).
triggerToast(msg, type)
Shows a transient notification. Types: 'ok' 'warn' 'err' 'acc'.
escapeHtml, openInspector, closeInspector
All defined on window in ui.js, accessible from any view module.

Web Serial Fetch Patch

When the Web Serial bridge is active, app.js wraps window.fetch. Any POST /api/messages call is silently redirected to WebSerialBridge.sendText() instead of the server. The caller receives a synthetic Response object so existing code never knows the difference. See Web Serial Connection for full details.

Startup Sequence

  1. DOMContentLoaded fires
  2. Fetch /api/status to detect public_mode and hide logout if needed
  3. Init C2Terminal (bottom terminal bar)
  4. Init Web Serial topbar button if feature is enabled
  5. Load plugin nav menu from /api/system/plugins/menu and inject into sidebar
  6. Fetch historical packets from /api/packets/history to pre-populate MeshShark
  7. Start SSE connection
  8. Call loadView('overview') — renders initial view
  9. Start periodic timers: diagnostics clock (1 s), system poll (30 s), SSE watchdog (15 s)
  10. Acquire Wake Lock to prevent CPU/network sleep during active monitoring

CSS Design System

All styling is in /static/css/style.css. It uses CSS custom properties (variables) for the entire colour palette:

--bg to --bg4
Dark navy background layers: #020912#12243f
--acc
Cyan accent #00c8f5 — primary interactive colour
--ok
Green #00f090 — success, online, connected
--warn
Amber #ffa800 — warnings, pending states
--err
Red #ff3050 — errors, disconnected, danger
--pur
Purple #b060ff — secondary accent, beta features
--txt, --txt2, --txt3
Text hierarchy: primary, secondary, muted
--mono
'Share Tech Mono', monospace — all IDs, codes, values
--sans
'Exo 2', sans-serif — labels, names, UI text

The background uses a CRT scanline overlay (body::before with a repeating linear gradient) for the characteristic terminal aesthetic. All interactive elements use var(--acc) for their active/hover state with a cyan box-shadow glow.

Lazy Loading

View JS modules are loaded on-demand the first time a view is opened. window._lazyLoadView(viewName, callback) checks if the module is already loaded (the global like window.C2CommsApp exists), and if not, dynamically creates a <script> tag to load /static/js/{viewName}.js. Once loaded the callback (which calls the init function) fires. Subsequent visits to the same view skip the script load entirely.

The overview module is always pre-loaded (not lazy) since it is the default landing view.

AbortController Per View

Each call to loadView() creates a new AbortController and immediately aborts any in-flight controller from the previous navigation. This prevents a slow view load from a previous navigation completing after you have already navigated away and overwriting the newly loaded view's content.

Wake Lock

On startup, MeshDash requests a screen wake lock via the navigator.wakeLock API. This prevents the device's CPU from entering low-power sleep states that could drop the network connection and disconnect the SSE stream. The wake lock is re-acquired whenever the tab becomes visible again (browsers release it automatically when the tab is hidden).