← All docs
Ideas

Next-session handoff

> Fresh agent? Start here. v0.6.0 (DMX) just landed on feat/dmx-track-b (Track A + B merged into one branch). This file is rewritten between sessions; nothing here is older than the last session.

30-second context

XOSC is an Electron + React + Zustand + ReactFlow app. Source nodes (USB inputs / Resolume parameter feedback / inbound OSC) wire on a graph canvas to sink nodes (OSC out / Resolume WS out / monitor) with optional transform nodes (clamp / scale / hold-timer / latch / etc.) in the middle. Loop protection is real: depth-cap at 8 hops + Resolume echo guard. No phone-home, loopback default, Windows 11 first.

Working dir: /var/www/xosc. Repo: github.com/saintpetejackboy/xosc-app. Site: https://xosc.gamingworld.uk (lives in web/dist, served by Apache on this same box).

Current HEAD: v0.6.0 in flight on feat/dmx-track-b. Two parallel agents (Track A: hardware + dmx-out/dmx-in nodes; Track B: sequences + pads + mixer) shipped onto the same branch. typecheck clean, npx electron-vite build clean, cd web && npm run build clean. Not yet tagged. v0.5.4 was the last released line and is feature-complete on main.

What's at HEAD that you might not expect

These shipped in v0.5.x so the older architecture-limits and dispatch-semantics docs are slightly stale:

  • Multi-hop dispatch. Router walks source → [transform …] → sink. MAX_DISPATCH_DEPTH = 8 bounds cycles.
  • transform nodes (mid-graph, both sink + source). Ops: clamp, scale, invert, curve, hold-timer (tap/hold split), double-tap, threshold (high/low hysteresis), latch, edge (rise/fall/both). State per (transformNodeId, sourceId) in src/features/mapping/transformOps.ts.
  • osc-in source nodes. electron/osc-listener.ts + src/features/inputs/useOscInputs.ts. Refcounted node-osc Server per (bindHost, port). Address patterns support and ?. Default loopback; LAN-visible is opt-in.
  • monitor sink node. Visual-only. Side-channel src/features/mapping/monitorBus.ts keeps 60 Hz traffic out of Zustand. Sparkline + last 6 raw samples. Refresh throttled to ~30 Hz.
  • Per-edge dispatch modes. MappingEdge.dispatch?: { mode: parallel|sequential|after-prev, order, delayMs?, requirePrevOk? }. Sequential / after-prev await each previous edge.
  • Per-edge throttle override. MappingEdge.throttleMs? overrides engine default (25 ms) on a single wire.
  • Toggle-state cleanup on rebind is automatic. useMappingRouter fingerprints each sink's incoming-edge set + triggerMode and drops the toggleState entry when the fingerprint shifts.
  • Resolume echo guard is on by default per resolume-in node. Disable in the node inspector if you want the round-trip back.
  • Save debug log to file lives in DebugPanel next to Copy. JSONL.
  • Cycle warning (v0.5.2). Static-time detection of feedback loops, including loops that close through Resolume parameter equality. Edges in a cycle render amber + ⟳ loop pill. Non-blocking — runtime depth cap and echo guard still rule.
  • Per-sink event-rate gauge (v0.5.2). OSC-out and Resolume-out nodes show events/s pill via sinkRateBus side-channel. Amber > 60/s, red > 200/s. Hidden at 0.
  • Resolume swagger autocomplete (v0.5.2). Composition refresh also pulls /api/v1/docs/swagger.json and unions paths into the picker so dynamic / effect params autocomplete even before Resolume materializes them in /composition.
  • Global hotkeys (v0.5.2). Per-keyboard-input Toggle in the inspector. When on, that key is registered via electron.globalShortcut and works while XOSC is unfocused. globalShortcut has no native key-up; XOSC synthesizes a release after 120 ms.
  • Custom theme editor (v0.5.3). Six-seed editor under Settings. Pick bg / surface / accent / accent-2 / text / border; the rest of the 18 vars are derived via chroma-js. Theme.seeds? carries the chosen seeds back so the editor re-hydrates. Live-applies on every change. See src/features/settings/CustomThemeEditor.tsx and src/theme/buildCustomTheme.ts.
  • mDNS host picker (v0.5.3). Main runs bonjour-service browsers for _resolume._tcp, _osc._udp, and _companion._tcp. Renderer-side usePeers() (out-of-Zustand, via useSyncExternalStore) feeds a HostPicker dropdown attached to ResolumeOut / ResolumeIn / OSC Out inspectors. Selecting a peer fills both host and port. Advertise is off — listening only.
  • Serial output node (v0.5.4). New serial-out sink kind. Payload template with {v} / {V} / {s} placeholders + line ending. Shares the open port with a serial-input node on the same path (one bridge handle, both directions). New IPC serial:write. See src/features/mapping/SerialOutNode.tsx and src/features/inspector/SerialOutInspector.tsx.
  • Wires breathe on traffic (v0.5.4). New edgeFireBus emits per-edge dispatch signals; ColorMixEdge re-mounts a sibling halo path keyed on a pulse counter so its CSS keyframe restarts each fire. Single press → single pulse; 60 Hz axis traffic → continuous halo.
  • Serial parser controls (v0.5.4). SerialBindUI now has Raw-bytes toggle + Delimiter field (escape syntax decoded on open).

Where things live (verify if changed)

| Concern | File |
|---|---|
| Custom theme builder | src/theme/buildCustomTheme.ts |
| Custom theme editor UI | src/features/settings/CustomThemeEditor.tsx |
| mDNS browser (main) | electron/peer-discovery.ts |
| Renderer peer bus | src/features/discovery/peerBus.ts |
| Host picker dropdown | src/components/ui/HostPicker.tsx |
| Serial-out reconciler | src/features/inputs/useSerialOutputs.ts |
| Serial-out node + inspector | src/features/mapping/SerialOutNode.tsx, src/features/inspector/SerialOutInspector.tsx |
| Edge fire bus | src/features/mapping/edgeFireBus.ts |
| Event bus | src/features/inputs/inputBus.ts |
| Dispatch tracker (lineage + echo guard) | src/features/inputs/dispatchTracker.ts |
| Router | src/features/mapping/useMappingRouter.ts |
| Transform op evaluator | src/features/mapping/transformOps.ts |
| Monitor side-channel | src/features/mapping/monitorBus.ts |
| Sink-rate side-channel | src/features/mapping/sinkRateBus.ts |
| Cycle detector | src/store/cycleDetect.ts |
| Global hotkey policy (renderer) | src/features/inputs/useGlobalHotkeys.ts |
| Global hotkey reconciler (main) | electron/global-hotkeys.ts |
| OSC inbound bridge (main) | electron/osc-listener.ts |
| OSC inbound reconciler (renderer) | src/features/inputs/useOscInputs.ts |
| Resolume WS bridge | electron/resolume-bridge.ts |
| OSC engine | electron/osc-engine.ts |
| Resolume subscriptions | src/features/inputs/useResolumeSubscriptions.ts |
| Resolume composition + swagger | src/features/inputs/useResolumeComposition.ts |
| Store | src/store/store.ts |
| Canvas | src/features/mapping/GraphCanvas.tsx |
| Inspector dispatcher | src/features/inspector/Inspector.tsx |
| Edge inspector | src/features/inspector/EdgeInspector.tsx |

What v0.5 was about (shipped — backlog items 1–15 + v0.5.4 polish)

See docs/ideas/v0.5-backlog.md for the canonical list. Everything except #16 (DMX) is shipped. v0.5.4 also wrapped three loose ends from competitor-gap.md: serial-out node, wire-breathes-on-fire, and the serial parser UI controls.

Remaining open:

  • #16 DMX integration (XL) — planned in docs/ideas/dmx/. The original dmx-feasibility.md is superseded by the dmx/ folder, which contains the full v0.6 plan split into two parallel tracks (Track A: hardware + graph nodes; Track B: sequences + pad launcher + mixer). Each track has a sealed handoff (HANDOFF-A-hardware.md, HANDOFF-B-sequences.md) so two agents can work in parallel. User has explicitly green-lit DMX as the v0.6 milestone.

Suggested next session

The v0.5 line is feature-complete except DMX. If the user wants to keep moving without DMX:

  • Multi-window canvas — open a second window with the same graph (in the "structural calls" list of v0.5-backlog.md). Useful when monitoring on a control surface laptop. Needs user yes/no first.
  • Plugin system / community node types — premature; the backlog says revisit at v0.7.
  • Docs sweepdocs/inputs/ and docs/mapping/ are mostly current after v0.5.4 but a fresh pass would help.
If shipping a v0.6 milestone is the next ask, that's the conversation to have. Otherwise this is a good resting point.

Important user decisions (stable)

  • No portable / zero-install build. User explicitly declined.
  • No glossy / neon visual redesign. v0.4's look stays.
  • Footer attribution is "Meiux Meiux LLC". Peak Technologies appears only in NSIS installer metadata.
  • DMX is parked until user re-prioritises.
  • No default browser inputs — every UI control lives in src/components/ui/.
  • *Tagging v..* triggers GitHub Actions Windows installer build + site version.json upload. Don't tag without user consent.

Operational

  • npm run typecheck — both sides (node + web).
  • npx electron-vite build — builds Electron renderer + main.
  • cd web && npm run build — rebuilds the site (web/dist/, served by Apache).
  • bash scripts/bump-version.sh minor|patch|major — bumps VERSION + both package.jsons.
  • bash scripts/build-icons.sh — regenerates build/icon.{png,ico,svg} from src/assets/xosc-mark.webp.
  • gh run list --limit 5 — check GitHub Actions after a tag push.
  • The home-page download pill version comes from /var/www/xosc/downloads/version.json, written by the deploy job after a successful Windows installer build. The /changelog/ page reads CHANGELOG.md directly so it updates the moment the site rebuilds.

Sanity checks before claiming a v0.5.x patch done

  • [ ] npm run typecheck clean (both projects).
  • [ ] npx electron-vite build clean.
  • [ ] cd web && npm run build registers any new docs pages.
  • [ ] CHANGELOG ## v0.5.x block written; bump-version.sh patch run.
  • [ ] Commit message describes the why; tag v0.5.x; push commit + tag.
  • [ ] https://xosc.gamingworld.uk/changelog/ shows the new section after the deploy workflow finishes.

If the user is asleep

1. Don't take destructive actions (force-push, drop branches, delete uncommitted work).
2. Don't push to main without confirmation except when wrapping a clean release the user previously asked for.
3. Don't tag v
.. without explicit go — that triggers a Windows installer build.
4. Default to "draft a plan + the smallest reversible commit" if blocked.
5. Update this file at end of session so the next agent has fresh context.

Session log

2026-04-29 — v0.6.0 DMX milestone landed (parallel-track session)

Two agents worked the same repo concurrently against sealed handoffs in docs/ideas/dmx/:

Track A — hardware + graph nodes (other agent).

  • electron/dmx/{engine,transmitter,universeBus,diag,artnetIn}.ts plus electron/dmx/transmitters/{artNet,sacn,openDmx,enttecPro,slowBreak}.ts — five hardware backends behind a single transmitter interface; main owns the universe buffer; renderer pushes composed frames via dmx:set-frame.
  • src/features/dmx/fixtures/{builtins,types,apply,index}.ts — eight fixture profiles + applyFixture projector.
  • src/features/dmx/{nodeOutputBus,useDmxAutoCompose,useDmxTransmitters}.ts — per-node side-channel + auto-compose hook + transmitter reconciler.
  • DmxOutNode / DmxInNode + inspectors, store discriminants dmx-out / dmx-in, topbar buttons, useDmxInputs for inbound Art-Net.

Track B — sequences + pads + mixer (this agent).
  • src/features/dmx/sequence{Types,Engine,Fade,Ops,Presets,LayerBus}.ts — rAF-driven engine + start/stop/blackout API + 8 starter sequences.
  • src/features/dmx/{mixer,mixerInspector}.ts — replaces Track A's last-write-wins placeholder with slot exclusivity + replace/max/add mix modes; graph writes overlay on top of pad composition.
  • src/features/dmx/universeBusClient.ts — bridge between Track B's mixer and Track A's IPC. setAutoCompose lets graph-only writes flush even with no active layers.
  • src/features/sequences/{PadView,Pad,PadEditorModal,EffectGenerator,ClockBar,padHotkeys,padTypes,usePads,clocks}.tsx/.ts — top-level Pads view, full editor modal, tap tempo + named clocks, pad↔input-bus integration.
  • src/store/dmxTypes.ts + extensions to src/store/store.ts (settings.dmx.{pads, clocks, padGridColumns, presetsLoaded}, ui.activeView, addPad/updatePad/removePad/seedDmxPresets/setClockBpm/setActiveView).
  • Topbar: Graph/Pads view toggle + ClockBar slot. App routes <PadView /> vs <GraphCanvas /> based on ui.activeView.

Cross-track contract: universeBus.write / writeMany / snapshot. Track A's main-process bus + Track B's renderer client share the same shape; Track B's mixer composes intents before they cross the IPC boundary.

Verified clean: npm run typecheck, npx electron-vite build, cd web && npm run build. Branch feat/dmx-track-b is the merge target. CHANGELOG ## v0.6.0 block written. VERSION not bumped, branch not tagged — user runs bash scripts/bump-version.sh minor then tags v0.6.0 after smoke-testing on Windows.

2026-04-29 — v0.5.4 shipped (prior session)

  • After v0.5.3 went green, the user asked to push to feature-complete except DMX. Audit identified three loose ends from competitor-gap.md: serial-out node, wire-breathes-on-fire, serial parser UI controls. All three landed in v0.5.4.
  • New files: electron/serial-bridge.ts (added serial:write IPC), src/features/inputs/useSerialOutputs.ts, src/features/mapping/SerialOutNode.tsx, src/features/mapping/edgeFireBus.ts, src/features/mapping/color-mix-edge.css, src/features/inspector/SerialOutInspector.tsx, docs/inputs/serial-output.md.
  • Touched: src/store/store.ts (new SerialOutNodeData), electron/preload.ts + src/types/api.ts (serial.write), src/features/mapping/{useMappingRouter,ColorMixEdge,GraphCanvas}, src/components/Topbar.tsx, src/features/inspector/{Inspector,SerialBindUI}.tsx, src/App.tsx, docs/inputs/serial.md, docs/ideas/{competitor-gap,v0.5-backlog}.md, CHANGELOG, VERSION → 0.5.4.
  • typecheck + electron-vite build clean. Tagged v0.5.4, pushed; both prior tags' workflows already green.

2026-04-29 — v0.5.3 shipped (earlier this session)

  • Backlog #13 (custom theme editor) and #14 (mDNS auto-discovery for hosts).
  • New files: src/theme/buildCustomTheme.ts, src/features/settings/CustomThemeEditor.tsx (+ css), electron/peer-discovery.ts, src/features/discovery/peerBus.ts, src/components/ui/HostPicker.tsx (+ css).
  • Touched: src/theme/themes.ts (added seeds? to Theme), src/features/settings/SettingsPanel.tsx, electron/main.ts + electron/preload.ts + electron/shared/types.ts, src/types/api.ts, all three host-bearing inspectors (OutputInspector, ResolumeOutInspector, ResolumeInInspector), docs/themes/customization.md, docs/ideas/{network-discovery,v0.5-backlog}.md, CHANGELOG, VERSION → 0.5.3.
  • New dep: bonjour-service ^1.3.0.
  • typecheck + electron-vite build clean. Not yet tagged or pushed — waiting on user. To ship: git push && git tag v0.5.3 && git push --tags (this triggers the Windows installer build + site deploy + version.json upload).

2026-04-29 — v0.5.2 shipped (prior session)

  • Backlog #7 (cycle warning UI), #8 (per-sink event-rate gauge), #11 (Resolume swagger discovery), #12 (global hotkeys).
  • New files: src/store/cycleDetect.ts, src/features/mapping/sinkRateBus.ts, src/features/mapping/SinkRateGauge.tsx, src/features/inputs/useGlobalHotkeys.ts, electron/global-hotkeys.ts.
  • Touched: ColorMixEdge, GraphCanvas, OutputNode, ResolumeOutNode, useMappingRouter, useResolumeComposition, store, App, InputInspector, preload, types/api, main, CHANGELOG, VERSION → 0.5.2.
  • typecheck + electron-vite build + Astro build all clean. Tagged v0.5.2, pushed; all three GitHub Actions workflows succeeded; installer + site + version.json published.
  • Storage cleanup also done this session: all 9 stale Actions caches deleted (~820 MB freed) and all old release installer .exe assets deleted (~427 MB freed) — only latest.yml files retained on old releases. Account was at 100% of the 2 GB GitHub Actions storage cap; now well under. Caches will regenerate on next workflow run.

2026-04-29 — v0.5.0 + v0.5.1 (prior sessions)

  • v0.5.0 (commit 5f6057d): backlog #1–5. Loop protection, Resolume echo guard, OSC inbound, transform nodes, dispatch modes.
  • v0.5.1 (commit f37632d): backlog #6 + #9 + #10 + #15. Per-edge throttle override, toggle-state cleanup on rebind, save debug log to file, OSC monitor node.