Data Model
The same three types describe a show in Rust (crates/show/src/types.rs) and in
TypeScript (src/show.ts).
Show
type Show = {
title: string;
description: string;
sections: Section[];
};
Section
type Section = {
id: string;
title: string;
sections: Section[];
cues: Cue[];
};
A section is a leaf (has cues, no children) or a container (has children, no cues), never both. The editing actions maintain this invariant, and perform mode relies on it.
Cue
type Cue = {
id: string;
text: string;
};
Flat items (derived)
Perform mode flattens the tree into a chronological list:
type FlatCue = { type: "cue"; cue: Cue; sectionPath: string };
type FlatDivider = { type: "divider"; sectionPath: string };
type FlatItem = FlatCue | FlatDivider;
flattenCues walks the tree in order. A section with cues emits a divider
(labeled with the full ancestor path, e.g. Act I - Scene i) followed by its
cues; child sections are expanded in place. This is a pure derivation, so the
stored show stays hierarchical.
The file envelope
On disk, a show is wrapped for forward compatibility:
type ShowFile = {
format: "nque";
version: 0;
show: Show;
};
See Show file format.
IDs
Every section and cue carries a unique id (a v4 UUID in Rust, a random id in the frontend). The schema requires ids to be non-empty. They act as stable keys for edits and for locating nodes anywhere in the tree.
Traversal
Tree operations are implemented as recursive immutable helpers
(updateSections, insertInto, removeFrom, findSection, …). Each returns a
new show without mutating the original; isDirty is toggled by the caller.