██████╗ ██╗ ██╗ ███████╗
██╔════╝ ██║ ██║ ██╔════╝
██║ ██║ ██║ █████╗
██║ ██║ ██║ ██╔══╝
╚██████╗ ╚██████╔╝ ███████╗
╚═════╝ ╚═════╝ ╚══════╝
██████╗ ██████╗ ███╗ ██╗ ████████╗ ██████╗ ██████╗ ██╗
██╔════╝ ██╔═══██╗ ████╗ ██║ ╚══██╔══╝ ██╔══██╗ ██╔═══██╗ ██║
██║ ██║ ██║ ██╔██╗ ██║ ██║ ██████╔╝ ██║ ██║ ██║
██║ ██║ ██║ ██║╚██╗██║ ██║ ██╔══██╗ ██║ ██║ ██║
╚██████╗ ╚██████╔╝ ██║ ╚████║ ██║ ██║ ██║ ╚██████╔╝ ███████╗
╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
A cue-calling prompt book for stage managers. Mark cues on the words they're called on, then call the show and let lighting and sound receive the GO over MIDI Show Control and OSC.
Note
The v2 design is agreed. None of it is built yet. The code on this branch is still the 2025 hackathon prototype, unchanged except for the commit that made it boot, so treat anything you run today as the old version. Design covers where it's going and the Roadmap is the honest picture.
A stage manager calls a show from a prompt book: a copy of the script marked with every cue, showing which word each one lands on. They read ahead, give each department a standby, then call the go on the beat.
Doing that on paper costs three jobs at once. You read the book, you speak the cue over headset, and you keep a stopwatch running so you can write act running times on the show report. Existing software splits the problem badly: playback software owns the cues but has no idea where they sit in the script, and script software owns the script but can't fire anything. The stage manager ends up as the integration layer, retyping cue numbers into a second system.
CueControl is both halves. Load a script in Fountain, click the word a cue is called on, label it the way you'd write it in a real book. The mark lives inside the script file, so the book and the script are one document. During the show, one key is GO.
| Piece | State |
|---|---|
| v2 design | Agreed, see Design |
| v2 code | Not started |
| Test seam | Decided: one pure show core, driven with node --test |
| Prototype | Runs, with known defects |
I built the prototype over a weekend at HackLondon 2025 and abandoned it. It
loaded its script over HTTP from a companion API server, which we did write that
weekend: the client expects GET and POST on /fountain at localhost:3000,
exchanging JSON of the form { content }. That server never made it into this
repo and the copy is gone, so from the moment this was the only surviving half,
every load ended in the error handler. I swapped the fetch for a file picker in
September 2026, and the version that runs today is archived on the
v1 branch.
I'm rebuilding rather than patching. Reading the prototype back turned up
nineteen defects, and the three that matter most are structural: the cue prompt
tile reads the wrong line, exporting a marked script drops every Fountain syntax
marker, and the clock runs 14.5% slow because it adds a fixed 10ms per
setInterval(10) tick. That last one loses about 17 minutes over a two-hour act,
which is the number a stage manager writes on the show report.
The decisions that shape everything else:
One pure show core. A single module owns the script tokens, the cue index, the calling position and the clock, constructed with injected clock and send ports. Every panel is a renderer over it. The prototype broke that three ways: panels owned domain state, panels read each other's DOM, and shared state sat on the global object.
The project is one Fountain file. Cues, layout, department config and recorded timings live inside it as boneyard comments, which the Fountain spec says every other tool ignores. Per-line data sits next to the line it annotates so it survives a cut made in any editor. Show-level config goes in one header block.
Cues attach to words, not characters. A word index survives reflow, resize
and indentation. The prototype stored a character offset measured with
measureText, which broke on every indented and every wrapped line.
Both transports, one download. A dependency-free Node file serves the app
from localhost and relays OSC over UDP. Serving from that origin also makes
localhost a secure context, so Web MIDI works, with no mixed content and no
private network preflight. Open it from anywhere else and you get the book with
OSC disabled and a reason.
Red is standby, green is go, amber is warning. Cue lights are physical fixtures and those meanings are established for anyone who has worked backstage. Record mode therefore can't use red and becomes a text label.
| Layer | Technology | Status |
|---|---|---|
| App | JavaScript, ES modules, no bundler | In use |
| Markup and styling | HTML5, CSS custom properties | In use |
| Script format | Fountain, with annotations in boneyard comments | In use |
| Camera | MediaDevices, for the stage view tile | In use |
| Bridge | Node 18 or later, node:http and node:dgram, no dependencies |
Specified, not written |
| Cue transport | Web MIDI sending MSC, and OSC over the bridge | Specified, not written |
| Tests | node --test against the show core |
Specified, not written |
These are the prototype, not the rewrite.
Right-clicking a grid cell opens the tile menu. Each type disappears once one is placed, so you get one of each. The cells have no visible borders because the border color is set to black on a black background, which v2 fixes along with the rest of the palette.
A Script Follow tile with a Timer Display beside it. The highlighted line is the current one, and space moves it down while the script scrolls to keep it centered. The clock reads 00:00:03:650, already behind the time that actually passed.
This runs the prototype. There's nothing of v2 to run yet.
- A browser with ES module support. Any current Chrome, Firefox, Safari or Edge.
- Python 3, which ships with macOS and most Linux distributions, to serve the files. Any static file server does the same job.
- A script in Fountain format. There's one at
sample.fountainif you don't have one to hand.
No package manager is involved. No dependencies, no lockfile, nothing to install.
git clone https://github.com/saturncity/cuecontrol.git
cd cuecontrol
python3 -m http.server 8080Open http://localhost:8080 and choose a Fountain file when the start screen asks for one.
Serve it over HTTP rather than opening index.html from disk. Browsers refuse to
load ES modules over file://, so a double-click gives you a blank page and a
CORS error in the console.
Ordered roughly the way I'd build it. The round-trip test comes first because it's the one that stops the tool eating someone's prompt book.
- Agree the v2 design and write it up
- Pick the test seam
- Show core, with injected clock and send ports
- Fountain parse and serialize that round-trips without dropping syntax
- Word-level cue placement
- Department table and cue label parsing, point cues included
- Warning, standby and go, with configurable lookahead
- Act running times from monotonic timestamps
- Auto-save, plus export and import
- Project launcher with recent shows
- Web MIDI sending MSC
- The OSC bridge, serving the app and relaying UDP
- Connection indicators and the cue log
- Dark console palette, self-hosted font
cuecontrol/
├── index.html # The prototype's single page
├── script.js # Entry point. Loads a script, hands off to grid.js
├── sample.fountain # A short scene to load
├── css/ # Grid, tile chrome and Fountain element styles
├── js/ # The prototype's modules, one per tile plus the parser
├── docs/assets/ # Screenshots
└── LICENSE
The design is settled but not sacred, and it's easier to change now than after it's built. If something in Design is wrong, open an issue and say so. I'd rather hear it from someone who has called a show than argue it from first principles.
Worth knowing before you read it: the terminology is deliberate. Standby and go
mean specific things, GO always comes last in a spoken cue, and point cues like
LX 12.5 are ordinary rather than an edge case. The spec follows real practice
where that conflicts with what would be easier to build.
MIT. See LICENSE.

