This is the canonical priority order for the next working session. Everything else in docs/ideas/ is supporting analysis. Do these in order. Skipping ahead is fine if a task is blocked, but please update this file when you do.
Effort key: XS (≤30 min) · S (≤2 h) · M (≤half day) · L (≥half day) · XL (multi-day).
1. Dispatch lineage + max-depth guard — S ✅ shipped 2026-04-29
Added causedBy: { nodeId, depth }? to InputFiredEvent. The mapping router drops depth > 8 events with a warn log. New src/features/inputs/dispatchTracker.ts records each resolume-out write so the matching parameter_update rebroadcast can attach causedBy and the depth cap terminates the Resolume echo loop. fireResolumeOutput threads depth through.
Files: src/features/inputs/inputBus.ts, src/features/inputs/dispatchTracker.ts (new), src/features/inputs/useResolumeSubscriptions.ts, src/features/mapping/useMappingRouter.ts.
2. Resolume echo-suppression on resolume-in — M ✅ shipped 2026-04-29
Reuses the dispatch tracker from #1. Per-node echoGuard?: boolean (default true) on ResolumeInNodeData. When a parameter_update matches a recent write within 200 ms, the useResolumeSubscriptions handler drops the emission entirely (instead of just attaching causedBy). Inspector exposes a toggle on the resolume-in node with explanatory copy.
Files: src/store/store.ts, src/features/inputs/useResolumeSubscriptions.ts, src/features/inspector/ResolumeInInspector.tsx.
3. Inbound OSC listener (osc-in node) — M ✅ shipped 2026-04-29
New electron/osc-listener.ts opens a refcounted UDP node-osc Server per (bindHost, port) and broadcasts decoded packets on a new osc:incoming IPC channel. New renderer hook useOscInputs.ts reconciles the listeners with the present osc-in nodes and emits matching packets onto the input bus. Supports and ? address patterns. New node + inspector + topbar button. Default bind is 127.0.0.1 with an explicit "LAN visible" warning when flipping to 0.0.0.0.
Files: electron/osc-listener.ts (new), electron/main.ts, electron/preload.ts, electron/shared/types.ts, electron/types/node-osc.d.ts, src/types/api.ts, src/store/store.ts, src/features/inputs/useOscInputs.ts (new), src/features/mapping/{OscInNode,GraphCanvas}.tsx, src/features/inspector/{Inspector,OscInInspector}.tsx, src/components/Topbar.tsx, src/App.tsx, docs/osc/inbound.md (new).
4. Math / logic nodes — L ✅ shipped 2026-04-29
New transform node kind shipped with the full op set: clamp, scale, invert, curve, hold-timer (tap / hold), double-tap, threshold (high / low), latch, edge (rise / fall / both). Multi-output ops expose multiple right-side handles; MappingEdge.sourceHandle / targetHandle carry the routing. Stateful ops keyed by (transformNodeId, sourceId) reset on hydration. Router rewritten for multi-hop traversal with MAX_DISPATCH_DEPTH = 8 cycle bound. Topbar button + node body + full inspector.
Files: src/store/store.ts, src/features/mapping/{TransformNode,GraphCanvas,useMappingRouter,transformOps,ColorMixEdge}.{ts,tsx}, src/features/inspector/TransformInspector.tsx, src/components/Topbar.tsx, docs/mapping/transform-nodes.md.
5. Dispatch-mode per edge — M ✅ shipped 2026-04-29
MappingEdge.dispatch?: { mode: parallel | sequential | after-prev, delayMs?, requirePrevOk?, order? }. Router groups outgoing edges by source, sorts by order, awaits sequential / after-prev steps, fires-and-forgets parallel ones. New EdgeInspector + edge selection state in the store. ColorMixEdge renders dashed strokes + an order/mode badge for non-default edges.
Files: src/store/store.ts, src/features/mapping/{useMappingRouter,ColorMixEdge,GraphCanvas}.tsx, src/features/inspector/{EdgeInspector,Inspector}.tsx, docs/mapping/dispatch-modes.md.
6. Per-edge throttle override — S ✅ shipped 2026-04-29 (v0.5.1)
MappingEdge.throttleMs? plumbed through fireSink → fireOscOutput / fireResolumeOutput. Edge inspector exposes Toggle + Slider (1..500 ms); default off (engine ~25 ms applies).
7. Cycle detection / warning UI — S ✅ shipped 2026-04-29 (v0.5.2)
detectCycleEdges walks combined static + virtual (resolume parameter equality on the same host:port) edges and marks any static edge whose source is reachable from its target. ColorMixEdge renders amber stroke + ⟳ loop pill when the flag is set. Non-blocking; runtime depth cap + echo guard remain authoritative.
Files: src/store/cycleDetect.ts (new), src/features/mapping/{GraphCanvas,ColorMixEdge}.tsx.
8. Per-sink event-rate gauge — S ✅ shipped 2026-04-29 (v0.5.2)
sinkRateBus side-channel publishes (sinkId, ts) from every fireSink. New useSinkEventRate(nodeId) hook keeps a 1-second ring buffer and refreshes at ~4 Hz. <SinkRateGauge /> pill turns amber > 60/s, red > 200/s. Hidden at 0.
Files: src/features/mapping/{sinkRateBus,SinkRateGauge,useMappingRouter,Output,ResolumeOut}Node.{ts,tsx}.
9. Toggle state cleanup on rebind — XS ✅ shipped 2026-04-29 (v0.5.1)
useMappingRouter now subscribes to graph changes, fingerprints each sink's incoming-edge set + triggerMode, and drops toggleState[sinkId] when the fingerprint shifts. Stale entries for deleted sinks are also dropped.
10. Save debug log to file — XS ✅ shipped 2026-04-29 (v0.5.1)
New log:save IPC handler in main: opens dialog.showSaveDialog, writes JSONL. DebugPanel exposes a Save button next to Copy.
11. Resolume swagger discovery on bridge open — S ✅ shipped 2026-04-29 (v0.5.2)
useResolumeComposition.refresh now fetches /api/v1/docs/swagger.json alongside the composition tree (best-effort; failure is silent). Swagger paths under /composition/... that aren't already represented are emitted as synthetic kind: "param" items so the picker autocompletes effects / dynamic params that the composition tree hasn't hydrated yet.
12. Global hotkeys — S ✅ shipped 2026-04-29 (v0.5.2)
New electron/global-hotkeys.ts reconciles electron.globalShortcut registrations against a renderer-supplied set. Renderer hook useGlobalHotkeys walks input nodes for globalHotkey: true && descriptor.kind === "keyboard", converts each descriptor's code + modifiers to an Electron accelerator, and IPCs the desired set on every change. globalShortcut fires once per press; main synthesizes a release ~120 ms later. InputInspector exposes a Toggle on keyboard-bound inputs.
Files: electron/global-hotkeys.ts (new), electron/main.ts, electron/preload.ts, src/types/api.ts, src/store/store.ts, src/features/inputs/useGlobalHotkeys.ts (new), src/features/inspector/InputInspector.tsx, src/App.tsx.
13. Custom theme editor (in-app) — M ✅ shipped 2026-04-29 (v0.5.3)
src/features/settings/CustomThemeEditor.tsx lives below the built-in
ThemePicker grid. Six seed inputs (bg, surface, accent, accent-2, text, border)
each as a TextInput + clickable swatch (OS color picker). buildCustomTheme
in src/theme/buildCustomTheme.ts derives the full 18-var theme via
chroma-js: surface-2/3 brightened/darkened off surface, border-strong nudged
off border, text-dim/mute mixed toward bg in oklab, glows as rgba off accent.
Mode (light/dark) auto-detects from bg luminance. Every change live-applies
through the existing ThemeProvider. Seeds persist on Theme.seeds? so the
editor re-hydrates on reopen.
14. mDNS / network auto-discovery — M ✅ shipped 2026-04-29 (v0.5.3)
Main runs bonjour-service browsers for _resolume._tcp, _osc._udp, and_companion._tcp and pushes diffs on peer:up / peer:down.src/features/discovery/peerBus.ts keeps the renderer-side snapshot viauseSyncExternalStore so 60Hz mDNS bursts don't trigger app-wide re-renders.src/components/ui/HostPicker.tsx is the drop-in for inspector host fields:
selecting a peer fills both host and port, filtered by service kind. Wired
into ResolumeOut, ResolumeIn, and OSC Out inspectors. Advertising is off.
See network-discovery.md for the full design.
15. OSC monitor node — S ✅ shipped 2026-04-29 (v0.5.1)
New monitor sink kind. Side-channel monitorBus keeps 60 Hz traffic out of Zustand; the node component subscribes to its own id and renders a 120-point auto-scaling sparkline + last 6 raw samples. Refresh throttled to ~30 Hz so it doesn't peg the UI.
v0.5.4 polish — shipped 2026-04-29
After v0.5.3, the competitor-gap.md audit identified three loose ends not in the original numbered backlog:
- Serial output node. Closes the Resolume → Arduino LED feedback round trip the competitor-gap doc had as "halfway." New
serial-outsink kind: port picker (shares with serial-in on the same path), baud rate, payload template with{v}/{V}/{s}placeholders, configurable line ending. Trigger mode (pulse / toggle / hold) honoured for discrete inputs; continuous inputs stream their normalized value through the placeholders. Files:electron/serial-bridge.ts(addedserial:write),src/store/store.ts(newSerialOutNodeData),src/features/mapping/SerialOutNode.tsx,src/features/inspector/SerialOutInspector.tsx,src/features/inputs/useSerialOutputs.ts(port reconciler). - Wire pulses on traffic. New
edgeFireBusemits per-edge dispatch signals;ColorMixEdgere-mounts a sibling halo<path>keyed on a pulse counter so its CSS animation restarts each fire. Single press → single pulse; 60 Hz axis traffic → wire is continuously lit. Idle wires pay nothing. - Serial input parser controls.
SerialBindUIexposes a Raw bytes toggle and a Delimiter field (decodes\n/\r\n/\r/\tescapes). Previouslyparserwas API-only.
16. DMX integration (multi-step, scoped behind a feature flag) — XL
Last on the priority list. See dmx-feasibility.md for the full study. Phased plan:
- 16a (M) — main-process DMX backend via
enttec-open-dmx-usborserialport+ custom Pro Mk2 frames. One transmitter perdevice-path:universe. - 16b (M) —
dmx-outnode kind: pick universe, pick fixture (or raw channel), set value. Continuous values map to channel 0..255. - 16c (L) — fixture profiles (RGB, RGBA, RGBAW, moving head). Borrowed JSON shape from TagTable. Modal for editing.
- 16d (L) — sequence/pattern node: priority queue, beat clock, simultaneous-vs-exclusive playback. The biggest unknown.
- 16e (M) —
dmx-in(Art-Net listener) so an external lighting console can drive XOSC.
Cuts (declined)
- Portable / zero-install build. The user explicitly doesn't want this.
- Logo / aesthetic redesign. Already done in v0.4.
Bigger structural calls (not in the backlog yet)
These need a user yes/no before they show up as numbered items:
- Multi-window canvas — open a second window with the same graph, useful when monitoring on a control surface laptop.
- Plugin system — allow community-contributed node types loaded from a folder. Probably premature; revisit at v0.7.
- Cloud/remote sync of configs — out of scope per air-gapped event readiness; skip.
Suggested order for the next session
1. Tackle items 1 → 3 in one go (foundation + the most-asked feature, inbound OSC). That's roughly half a day.
2. Then either 4 (logic nodes) for the biggest user-visible jump, or 5 + 6 for the cleanest dispatch story before logic-nodes lands.
3. Cut a v0.5.0 tag once 1–6 are in. 7–15 trickle in as v0.5.x.
4. DMX is its own milestone (v0.6 or v0.7). Don't blend.