← All docs
Ideas

HANDOFF — Track A (hardware + graph integration)

> Fresh agent? You're Track A. You own DMX hardware backends, fixture profiles, the universe frame buffer, and the two new node kinds (dmx-out, dmx-in). Track B owns sequences and the mixer; 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. You ship Track A as v0.6.0-alpha and v0.6.0-beta; Track B ships v0.6.0 on top of your work.

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). Your work is mostly a port + generalize of TagTable's src/main/dmx.js (5 transmitters, 820 lines) and the built-in fixture concept (which TagTable doesn't have).

Read in this order

1. docs/ideas/dmx/README.md — the umbrella plan + the cross-track contract.
2. docs/ideas/dmx/01-hardware-backends.md — your main spec.
3. docs/ideas/dmx/02-fixtures.md — fixture profile schema + 8 built-ins.
4. docs/ideas/dmx/03-graph-integration.mddmx-out and dmx-in node kinds.
5. docs/ideas/dmx/08-logging-and-diag.md — logger source / diagnostics surface.
6. docs/ideas/dmx/09-themes-and-ui.md — theme variables + reusable components.
7. docs/ideas/dmx/10-rollout-plan.md — milestone phasing.

Your shippable milestones

  • v0.6.0-alpha — Art-Net only, dmx-out + dmx-in, full fixture library, settings panel. Tag v0.6.0-alpha. ~3 days.
  • v0.6.0-beta — sACN, Open DMX, Enttec Pro, Slow-break added. Tag v0.6.0-beta. ~3–4 days more.

What you'll write (exhaustive)

electron/dmx/
├── engine.ts                      # universe buffers + transmitter manager + setIntervals
├── transmitter.ts                 # Transmitter interface + TICK_INTERVALS
├── universeBus.ts                 # the contract Track B writes into; ship a placeholder mixer (last-write-wins)
├── diag.ts                        # per-transmitter counters
├── artnetIn.ts                    # inbound Art-Net listener
└── transmitters/
    ├── artNet.ts
    ├── sacn.ts
    ├── openDmx.ts
    ├── enttecPro.ts
    └── slowBreak.ts

src/features/dmx/
├── fixtures/
│ ├── types.ts
│ ├── builtins.ts
│ ├── apply.ts
│ └── index.ts
└── universeBusClient.ts # renderer-side wrapper around dmx:set-frame + dmx:write

src/features/mapping/
└── DmxOutNode.tsx

src/features/inputs/
└── useDmxInputs.ts

src/features/inspector/
├── DmxOutInspector.tsx
├── DmxInInspector.tsx
└── DmxSettingsPanel.tsx

Plus extensions to:

  • electron/main.ts (registerDmxHandlers + disposeDmx)
  • electron/preload.ts (xosc.dmx namespace)
  • electron/shared/types.ts (LogEntry source "dmx", IPC payload types)
  • src/types/api.ts (mirror)
  • src/store/store.ts (DmxOutNodeData, DmxInNodeData, addDmxOutNode, addDmxInNode, settings.dmx, knownKinds)
  • src/components/Topbar.tsx (two new buttons)
  • src/features/inspector/Inspector.tsx (dispatch new kinds)
  • src/features/mapping/GraphCanvas.tsx (nodeTypes + spawn handlers)
  • src/features/mapping/useMappingRouter.ts (fireSink branch for dmx-out)
  • src/store/cycleDetect.ts (synthetic edges for dmx-out / dmx-in pairs)
  • CHANGELOG.md (one block per release)
  • docs/ideas/next-session-handoff.md (update at the end)

The one contract you publish for Track B

src/features/dmx/universeBusClient.ts exports:

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;
};

source.kind is "graph" | "pad" | "art-net-in". Until Track B ships its mixer, your placeholder is last-write-wins with the dmx-out write applied last (so graph writes always show through). Track B will replace the internals; don't change the function signatures.

The renderer calls dmx:set-frame with the composed buffer (~60 Hz, rAF-driven). Until Track B's mixer is in place, your placeholder pushes the buffer immediately on every write / writeMany call (debounced to 16 ms).

Don't do

  • Don't write the sequence engine, pad launcher, mixer, or master clock. Those are Track B.
  • Don't pre-design pad-state IPC. Track B decides what main needs to know.
  • Don't ship dmx-in without testing it against an Art-Net source. Use dmxutil from Linux or a software Art-Net node on the dev box.
  • Don't merge to main without the user's explicit go on tagging. The user has pre-authorized git push and git tag, but each tag fires a Windows installer build — coordinate releases.

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. v0.5.4 is the current shipped version.

Sanity checks before claiming a milestone 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.
  • [ ] DMX source added to logger filter.
  • [ ] Cycle detector covers dmx-out ↔ dmx-in.
  • [ ] At least one transmitter type tested against real or simulated hardware (Art-Net is testable from Linux via oscutil / qlcplus).
  • [ ] Tag v0.6.0-alpha (or -beta) and push.

If you're alone

If Track B has no agent yet, ship v0.6.0-alpha and v0.6.0-beta first. Stop. Don't pre-emptively start sequence engine work — the user wants to QA hardware end-to-end first. Update docs/ideas/next-session-handoff.md with your status when you stop and let Track B pick up cleanly.