-
Notifications
You must be signed in to change notification settings - Fork 3
Subsystem Web App
The browser-side and server-side both live in host/byh_app/backyardhero/. It's a standard Next.js 14 app using the Pages Router. SSR is unused — the app renders client-side after a quick HTML shell.
| Process | Where | Purpose |
|---|---|---|
npm run start (production) or npm run dev (dev) |
inside the container, supervisord program byh-app
|
Serves HTML + JS + the /api/* REST routes on port 1776. |
The Next.js server is the only HTTP-facing process. It does not hold any long-lived state — every request is independent. Persistent state lives in SQLite (/data/backyardhero.db) and the daemon's state file (/data/state).
The app has effectively one Next.js route: / (src/pages/index.js). Everything else is rendered inside MainNav.jsx as in-memory tabs. URLs do not encode tab; deep-linking goes to whatever tab was last selected.
The seven user-facing tabs:
| Key | Component | Page |
|---|---|---|
main |
ConsolePanel |
Console |
receivers |
ReceiverDisplay |
Receivers |
editor |
ShowBuilder |
Editor |
loadout |
ShowLoadout |
Loadout |
inventory |
InventoryManager |
Inventory |
manual |
ManualFiring |
Manual fire |
setting |
SettingsPanel |
Settings |
Two are gated:
- Loadout is hidden until a show is staged.
-
Receivers shows a red error badge if
useShowReceiverVerification()reports problems with the staged show.
Two Zustand stores:
The "app data" store. Mirrors most SQLite tables and handles all CRUD against the /api/* REST routes.
Holds:
-
Shows (
shows,stagedShow,stagedShowId— the last is persisted to localStorage so a page reload restores your stage). Methods:fetchShows,createShow,updateShow,deleteShow,setStagedShow,hydrateStagedShowFromId. -
Inventory (
inventory). Methods:fetchInventory, CRUD,setItemFiringProfile. -
System config (
systemConfig). Methods:fetchSystemConfig,saveSystemConfig. -
Receivers (mirror of SQL
Receiverstable). Methods:fetchReceivers,createReceiver,updateReceiver,reloadReceiversOnDaemon,retryReceiver,fetchReceiverConfig.
stagedShow rehydration is intentional: rather than persisting the full show object to localStorage (which goes stale fast — inventory edits, audio re-uploads, etc.), the store persists only stagedShowId and re-fetches the full row from /api/shows on page load. This avoids the entire class of "I edited the show but the Console still shows the old timeline" bugs.
The "live daemon state" store. Holds one big stateData object that's a parsed copy of the WS payload (which is a parsed copy of /data/state). All consumers — useAppMode, StatusBar, ReceiverDisplay, ShowControl — read from here.
The store has two setters:
-
setStateData(payload)— replace the whole state (used for a fresh WS message). -
patchStateData(partial)— merge a partial (used for the WS server's heartbeat that only carriesfw_last_update).
StatusBar.jsx opens the WebSocket on mount:
const socket = new WebSocket(`ws://${window.location.host.split(':')[0]}:8090`);(Port 8090 is hard-coded — that's the WS server. The hostname is derived from window.location so it works whether you're on localhost, a Pi at 192.168.1.50, etc.)
On each message:
- If the payload is
{ "_hb": true, "fw_last_update": 1715680000123 }, only thefw_last_updateis patched in (heartbeat — used to drive the freshness indicators in the status bar). - Otherwise, the entire
stateDatais replaced.
On disconnect, StatusBar does an exponential backoff reconnect.
All under src/pages/api/. Each one is a thin wrapper around either SQLite (via src/util/sqldb.js) or the daemon command drop-box (/tmp/d_cmd/). See API reference for the full list.
Highlights:
-
/api/system/cmd_daemon— the universal back-channel to the daemon. Any JSON object you POST is written verbatim to/tmp/d_cmd/<timestamp>.json. The daemon polls that directory at 2 Hz, reads the file, deletes it, and routes bytype. -
/api/system/config—GETreturnssystemcfg.jsonoverlaid with the liveReceiverstable from SQLite (so the UI always sees the canonical receivers).POSTwrites back the JSON, stripping anyreceiversblock (so the SQL table stays authoritative). -
/api/system/ota_flash—POSTaccepts a base64-encoded firmware blob, writes it to/tmp/ota_staging/<filename>, drops{ "type": "ota_flash_start", "ident": ..., "image_path": ..., "rate": ... }for the daemon.DELETEdrops{ "type": "ota_flash_abort" }. -
/api/inventory/[id]/reprocess-profile— spawnsprocess_firing_profiles.pyfor one inventory item. -
/api/catalog/refresh— spawnscrawl_catalog.py. -
/api/inventory/parse-shell-descriptions— calls OpenAI viaOPENAI_API_KEYto parse a paste of shell description text into structured shell rows.
A simplified file tree of the components directory:
src/components/
├── MainNav.jsx -- top-level tab router
├── shell/
│ ├── AppShell.jsx -- 3-row grid (header / main / footer), armed rail
│ ├── TopBar.jsx -- header with logo, mode badge, tab list
│ ├── StatusBar.jsx -- footer; WebSocket consumer; system health
│ ├── useAppMode.js -- single mode badge derivation
│ └── ModeBadge.jsx
├── console/
│ └── ShowControl.jsx -- the load/launch/abort flow
├── builder/
│ ├── ShowBuilder.jsx -- the editor itself (very large)
│ ├── ShowStateHeader.jsx
│ ├── Timeline.jsx -- main timeline (used in editor and console)
│ ├── AudioWaveform.jsx -- WaveSurfer-based waveform
│ ├── AddItemModal.jsx
│ ├── ChainTimingModal.jsx
│ ├── CopyItemModal.jsx
│ ├── RacksTab.jsx -- sub-tab in the editor
│ ├── RackGrid.jsx -- 2D rack editor
│ ├── RackResizeModal.jsx
│ ├── RackShellsSelector.jsx
│ ├── FuseModal.jsx
│ ├── FusedLineBuilderModal.jsx
│ ├── FusedItemLineBuilderModal.jsx
│ ├── CellShellSelector.jsx
│ ├── ShowTargetGrid.jsx -- per-show receiver target grid editor
│ ├── ShowReceiverModal.jsx
│ ├── SpatialLayoutMap.jsx -- Leaflet satellite map
│ ├── SatelliteMap.jsx
│ ├── AddressSearch.jsx -- Nominatim geocoding
│ └── ItemPreview.jsx
├── receivers/
│ ├── ReceiverDisplay.jsx -- fleet status grid
│ └── ShowLoadout.jsx -- packing list / spatial PDF
├── inventory/
│ ├── InventoryManager.jsx
│ ├── InventoryList.jsx
│ ├── ImportCatalogModal.jsx
│ ├── ShellPackEditor.jsx
│ └── ShotProfileModal.jsx -- firing profile editor (YouTube)
├── settings/
│ ├── SettingsPanel.jsx -- top-level tabs
│ ├── BrightnessSlider.jsx
│ ├── DebugModeToggle.jsx
│ ├── TxConfig.jsx
│ ├── ReceiverConfigSettings.jsx
│ ├── DaemonSettings.jsx
│ ├── RFScanPanel.jsx
│ ├── OtaFlashPanel.jsx
│ ├── ProtocolConfig.jsx
│ └── DefaultLocationSettings.jsx
├── manual/
│ ├── ManualFiring.jsx
│ └── ManualFirePanel.jsx
└── debug/ -- internal/dev-only diagnostics
-
src/util/sqldb.js— single source of truth for the SQLite schema. Initializes tables on every Next.js boot and runsALTER TABLE ADD COLUMNmigrations idempotently. See Subsystem: Database. -
src/util/showReceivers.js— helpers for the per-show receiver list (theshow_receiversJSON column).availableDevicesFromShowReceivers,verifyShowReceivers,summarizeVerificationErrors. -
src/util/useShowReceiverVerification.js— combines the staged show with the global receivers table to produce a list of verification errors (missing receiver, disabled receiver, not enough cues).
- Tailwind for layout and one-off styles. Configuration in
tailwind.config.js. - A custom dark-only design system in
src/styles/globals.cssexposing CSS variables (--bg-elev,--fg,--accent,--ok,--warn,--danger, plus--armed-channel/--live-channelfor the operational rails). - Mode hooks:
html.mode-armed,html.mode-live,html.mode-disconnectedshift the accent / foreground tokens. The "armed rail" — the angled barber-pole indicator on the side of the UI when armed/live — is a single SVG repeating-gradient driven by these classes.
The Dockerfile runs npm ci && npm run build so the production image ships a pre-built Next.js app. Dev mode replaces this with npm run dev for HMR; the supervisord config (supervisord.dev.conf) handles that.
Getting started
- Overview
- Desktop installers (macOS / Windows)
- macOS
- Linux
- Windows
- Production vs Development
- Connecting the dongle
- Flash a receiver
- Flash a dongle
- OTA flashing
Raspberry Pi
System overview
Subsystems
Hardware
- Receiver firmware
- Dongle firmware
- RF protocol
- Contributor Portal — BOMs, schematics, and board resources
UI walkthrough
Reference
Downloads
- Firmware
- Installers
Module Build & User Guides
- Cue
- Receiver
- Dongle