← All docs
Ideas

HANDOFF — Track B (sequences + pad launcher + mixer)

> Fresh agent? You're Track B. You own the cue-list sequence engine, the pad launcher panel, the priority-aware mixer, and the master clock + tap tempo. Track A owns the hardware backends and node graph; you do not touch their files.

30-second context

XOSC is feature-complete for v0.5 except DMX. We're building a v0.6 milestone in two parallel tracks. Track A ships v0.6.0-alpha (graph-only DMX) and v0.6.0-beta (full hardware support). You ship v0.6.0 on top of that.

Working dir: /var/www/xosc. Repo: github.com/saintpetejackboy/xosc-app. The repo is public; you have push access. All workflows are GitHub Actions and run on tag pushes.

Reference implementation: https://github.com/saintpetejackboy/TagTable (private; clone with gh repo clone saintpetejackboy/TagTable /tmp/tagtable). Specifically your reference is:

  • src/renderer/app.js lines 3637–3879 — startLightingTemplate, lightingMasterTick, stopLightingTemplate, blackout, getTopLightingLayer, applyDimmer. This is the rAF-driven layer engine you're porting + generalizing from RGB-only to multi-role.
  • src/main/config.js lines 437+ — generatePresetTemplates, the 8 built-in cue lists you're seeding.
  • src/main/osc.js parseTemplateCommand — TagTable's /template/start <name|index> OSC interface, useful as a reference for how external triggers integrate.

Read in this order

1. docs/ideas/dmx/README.md — the umbrella plan + the cross-track contract.
2. docs/ideas/dmx/04-sequence-engine.md — your main spec.
3. docs/ideas/dmx/05-pad-launcher.md — the Pads view UI.
4. docs/ideas/dmx/06-mixer.md — the function that composes layers + graph writes per universe.
5. docs/ideas/dmx/07-clock-and-tempo.md — shared clocks + tap tempo.
6. docs/ideas/dmx/08-logging-and-diag.md — logger source / diagnostics surface.
7. docs/ideas/dmx/09-themes-and-ui.md — theme variables + reusable components.
8. docs/ideas/dmx/10-rollout-plan.md — milestone phasing.

You can skip 01–03 (Track A's hardware specs). The only Track A file you depend on is src/features/dmx/universeBusClient.ts — its write / writeMany / snapshot API is your contract.

Your shippable milestone

  • v0.6.0 — full sequence engine, pad launcher, mixer, clocks, tap tempo. Tag v0.6.0. ~5–7 days.

What you'll write (exhaustive)

src/features/dmx/
├── sequenceEngine.ts        # rAF-driven master tick + active layers
├── sequenceOps.ts           # start / stop / blackout API
├── sequenceFade.ts          # fade math (per role per layer)
├── sequencePresets.ts       # 8 built-in sequences (general fixtures)
├── sequenceTypes.ts         # Sequence, SequenceCue, SequenceLayer, FadeState
├── mixer.ts                 # composeFrame — per-frame composition
└── mixerInspector.ts        # debug-panel-friendly snapshot

src/features/sequences/
├── PadView.tsx # main grid view
├── Pad.tsx # one pad cell
├── PadEditorModal.tsx # editor modal
├── EffectGenerator.tsx # 4 preset cue fillers (chase, pulse, color cycle, fade rainbow)
├── ClockBar.tsx # topbar widget with BPM + tap + reset per clock
├── padTypes.ts # persisted pad list shape
├── usePads.ts # hook returning pad list + active layers
├── padHotkeys.ts # registers pad hotkeys with the keyboard input layer
├── clocks.ts # SharedClock map + tap tempo
├── useClockSync.ts # external clock source plumbing (MIDI clock — v0.6.1, optional)
├── pad-view.css
├── pad.css
├── pad-editor-modal.css
└── clock-bar.css

src/components/ui/
└── HexInput.tsx # extracted from CustomThemeEditor's swatch+native-color trick (shared with Track A's color usage)

Plus extensions to:

  • src/store/store.tssettings.dmx.sequences, settings.dmx.clocks, ui.activeView ("graph" | "pads"), pad list shape.
  • src/components/Topbar.tsx — Pads / Graph toggle, Clock bar slot.
  • src/App.tsx — render <PadView /> vs <GraphCanvas /> based on ui.activeView.
  • src/features/inputs/keyboard.ts — pad hotkey signatures (delegated through padHotkeys.ts).
  • CHANGELOG.md — one v0.6.0 block.
  • docs/ideas/next-session-handoff.md — update at the end.

The contract you depend on (Track A publishes)

// src/features/dmx/universeBusClient.ts
export const universeBus = {
  write(universeId: string, channel: number, value: number, source: WriteSource): void;
  writeMany(universeId: string, writes: Array<[ch: number, val: number]>, source: WriteSource): void;
  snapshot(universeId: string): Uint8Array;
};

interface WriteSource {
kind: "graph" | "pad" | "art-net-in";
nodeId?: string;
priority?: number;
}

Your sequence engine calls writeMany once per rAF tick per active layer. Your mixer composes intents before writing — the mixer replaces Track A's last-write-wins placeholder. After your mixer ships, the call shape doesn't change; the internal composition does.

Pad-to-graph integration (the headline feature)

Pads must appear as virtual sources/sinks on the input bus so the graph can wire to them:

  • A pad firing emits a synthetic input event with signature pad:<padId>:hotkey. Same shape as keyboard, MIDI, OSC events; the existing router handles it without modification.
  • A cue that has emit: { signature, value? } fires that exact event when the cue lands. Lets a "bass drop" cue trigger an OSC packet at the right beat.
  • A pad's hotkey can come from any source — keyboard, MIDI, OSC, gamepad — by binding an input node to the pad:<padId>:hotkey signature.
This is what makes pads useful beyond "click to start a chase." The user can fire pads from external MIDI controllers and chain them into Resolume.

Don't do

  • Don't write any of electron/dmx/*. That's Track A.
  • Don't ship beat-locked playback in v0.6.0. Tap-tempo gives you a BPM number you display. Layered against beats is a v0.6.1 polish.
  • Don't ship MIDI / OSC clock sync in v0.6.0 unless Track A is fully stalled and you have nothing else to do.
  • Don't replace universeBus's function signatures. You replace the composition rules; Track A's clients keep working.
  • Don't put the sequence engine in main. Renderer-side rAF is the right call (TagTable does this, and it lets the pad UI be reactive without IPC).

Operational

  • npm run typecheck — both projects.
  • npx electron-vite build — Electron + renderer.
  • cd web && npm run build — registers any new docs pages.
  • bash scripts/bump-version.sh patch|minor|major.
  • gh run list --limit 5 — check workflows after a tag push.
  • Live site: https://xosc.gamingworld.uk.

Sanity checks before claiming v0.6.0 done

  • [ ] npm run typecheck clean.
  • [ ] npx electron-vite build clean.
  • [ ] cd web && npm run build registers all new docs pages.
  • [ ] CHANGELOG block written.
  • [ ] bump-version.sh run.
  • [ ] At least 8 built-in sequences seed on first run.
  • [ ] Tap tempo + named clocks tested.
  • [ ] Pad hotkeys round-trip (keyboard fires pad; pad fires synthetic event back into bus).
  • [ ] Mixer respects slot exclusivity AND mix modes (verify with two layers writing the same channel).
  • [ ] Graph + pad writing the same universe — graph-write wins (this is intentional; 06-mixer.md covers it).
  • [ ] Tag v0.6.0 and push.

If Track A isn't done yet

If Track A's universeBus doesn't exist, don't stub it yourself. Coordinate with the user. The pad UI can be built against a stub, but the engine has nowhere to write until Track A ships. The right move is to update docs/ideas/next-session-handoff.md saying you're blocked on the alpha and pick a different task (docs sweep, multi-window canvas if user yes).