> 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.md — dmx-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. Tagv0.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.dmxnamespace)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(fireSinkbranch fordmx-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-inwithout testing it against an Art-Net source. Usedmxutilfrom Linux or a software Art-Net node on the dev box. - Don't merge to
mainwithout the user's explicit go on tagging. The user has pre-authorizedgit pushandgit 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 typecheckclean. - [ ]
npx electron-vite buildclean. - [ ]
cd web && npm run buildregisters all new docs pages. - [ ] CHANGELOG block written.
- [ ]
bump-version.shrun. - [ ] 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.