Skip to content

feat(desktop): Join a brain — receiver-only mode pointed at a remote wavegrid server - #132

Merged
pyramation merged 4 commits into
mainfrom
feat/desktop-join-brain
Sep 21, 2026
Merged

pyramation merged 4 commits into
mainfrom
feat/desktop-join-brain

Conversation

@pyramation

@pyramation pyramation commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A venue laptop can now run the desktop app as only a receiver dialing a remote brain (ws:// or wss://), keeping the Output/OSC editor, OSC debugger and Status/doctor screens. One new optional config key drives everything:

// @wavegrid/layout ReceiverConfig
receiver.server?: string   // ws:// or wss:// — unset = local brain (unchanged behaviour)

How an operator uses it

  1. Devices → Join a brain → paste wss://grace.hipzap.com (or press Use on a scanned LAN brain) → Save / Join. Clearing with Use local brain returns to whole-brain mode.
  2. Make the receiver key match the brain's project: import the brain's export with secrets (wavegrid projects export --include-secrets / Desktop Projects → Export) or wavegrid projects secrets set receiverKey (new command). Importing without secrets also means the embedded artist UI shows the brain's login screen (the desktop signs in with the project's jwtSecret).
  3. Set the OSC target on Output, then Start. Status/Show show receiver-only → <url>; doctor probes the remote brain.

What changed underneath

  • configEnvMap: SIMULATOR_URL = receiver.server || ws://localhost:<port>; envLayer reads SIMULATOR_URL back. New parseBrainUrl() (validates/normalises) and brainHttpOrigin() (wss://h → https://h).
  • brain.ts start(): when receiver.server is set → applyReceiverEnv + startReceiver only, server: null, url = brainHttpOrigin(remote) so the existing laser-view load/embeddedUrl path points at the remote UI. BrainStatus gains role: 'brain' | 'receiver' | null and remoteUrl; lanUrls empty, runningBind()/sendToBrain() no-op without a local server. startLocalReceiver/stopLocalReceiver (Restart) work unchanged.
  • CLI wavegrid receiver: precedence --server > receiver.server > mDNS > election > localhost, via a pure resolveUpstream(flag, configured, discover) seam (tested) — an operator who explicitly joined a remote brain is not hijacked by a stray LAN brain, and discovery is skipped entirely when a remote is configured. projects config set receiver.server <url|''> and projects secrets set <name> [value] (password prompt when omitted; new store.setSecret).
  • @wavegrid/doctor collectDiagnostics: defaults the probed URL to receiver.server; TCP probe port defaults to 443/80 for wss/ws without an explicit port.
  • @wavegrid/receiver: ?key= composition extracted to upstreamUrl() (tested with wss://); WebSocketInput untouched — ws handles TLS.
  • Wire protocol and cannon message format untouched. Tests added in layout, cli, receiver, desktop.

Headless Electron verification

Desktop app joined a real CLI brain (wavegrid server on :3555, same project) — one receiver connected, port 3000 left unbound, embedded laser UI loaded from the remote origin; Use local brain restarted the local server on :3000. http://foo cannot be saved. Not exercised: external wss:///TLS, scanned-brain Use, physical OSC output.

Devices — Join a brain Status — receiver-only
Devices: Join a brain Status: remote brain, one receiver
Show — embedded UI from remote brain Status — back to local brain
Show: receiver-only Status: local brain restored

Link to Devin session: https://app.devin.ai/sessions/7d5636b49920476f9207ea9f35045bd1
Open in Devin Desktop: https://app.devin.ai/desktop/session/7d5636b49920476f9207ea9f35045bd1?variant=devin
Requested by: @pyramation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@tenki-reviewer

tenki-reviewer Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Review complete. No blocking issues — approved ✅; 1 nitpick below.

🧹 Nitpicks (1) — 🟢 1 low
  • 🟢 Malformed brain URL fails silently — The JoinBrain guard only checks a ws:///wss:// prefix (join-brain.tsx:19), so a hostless value like ws:// enables Save, but on click parseBrainUrl throws and the rejection is swallowed by the fire-and-forget void saveJoin() in App.tsx:305, leaving a silent no-op.

PR #132 introduces a brain/receiver role split that lets an instance run as a receiver and join a remote brain, plus receiver-only server skip paths, editable receiver server config, and consolidated secrets/env config handling.

Files Change
packages/receiver/* Adds remote-brain upstream WebSocket dialing with key auth, receiver role selection, and SIMULATOR_URL handling.
packages/desktop/src/main/*, packages/desktop/src/types/ipc.ts Wires brain/receiver role state, receiver-only server skip, and editable receiverServer config across main/renderer IPC.
packages/desktop/src/renderer/routes/*, lib/use-wavegrid.ts Adds the Join-a-brain URL workflow, discovery wiring, and role-aware UI.
packages/cli/src/* Adds secrets/config-set commands, receiver flag/config precedence, and command registration.
packages/layout/src/*, packages/settings/src/* Reworks config/env loading and secret storage/masking.
packages/doctor/src/collect.ts Collects the new env/config/secret state for diagnostics.

The one kept review finding is a low-severity UI validation gap where a malformed hostless brain URL silently no-ops instead of surfacing an error.

Reviewed commit: ea220a0

@pyramation
pyramation merged commit 845dc3d into main Sep 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant