Explore a song through notebook cards that combine source, visual tools, inspection and listening.
Held from the first release. Bench (the declaration notebook and the Patch Bay board) has no menu, header or shortcut entry in this release; the piano roll it hosts still opens from a track's Edit notes.
Design history: Native Workbench, Workbench Convergence, and Bench Bay Composition Arrangement.
Bench is the shared Rust workbench's full-screen notebook over a song. The native app and browser run the same implementation in ui/src/bench.rs; the former TypeScript Living Source/Patch Bay and its CodeMirror card editor were deleted when the workbenches converged, and the Patch Bay was rebuilt native as the notebook's second layout (below).
The LEARN course can run through the bench: the guided-notebook presentation loads a lesson's guide.volta (prose cells + build-up steps) into the notebook with the goals panel beside it — see Learn with guided exercises §1b. Bench itself needed no changes for this: the course is the ordinary spliced-buffer projection plus the LEARN overlay.
Source notebook#
The song is projected as source-ordered cells:
- one embedded
EditorUiper declaration, spliced into the master buffer with guarded range edits; - top-level comment runs shown as prose cells, with source-preserving edit round trips;
- syntax colors, caret behavior, structural navigation, and compiler recovery shared with the main Source editor;
- edits deferred safely while a cell owns focus or an active pointer gesture;
- cell-local notices, live clock/context, and focused-source navigation.
Prose cells are markdown. A top-level -- comment run renders through ui/src/md.rs — the subset Write music with explanations documents: headings # through ###### (H1 1.7× — 2.05× in Story presentation — H2 1.35×, H3+ 1.15× of the body size), paragraphs (hard-wrapped source lines reflow into one), -/1. lists on a hanging bullet, > quotes on a left hairline, --- rules, fences as non-editable display code, and the inline runs `code` (monospace on a faint fill), **strong** (full-strength ink), *emph* (the italic Fragment face) and [text](url). Links with a volta: target act inside the Notebook; other URLs remain styled text with a hover address. One Volta-ism: Learn's -- ▸ <title> step marker (Learn with guided exercises) is a level-2 heading whose ▸ stays visible but dim. Rendering is the only thing markdown changes. Edit notes (shown on hover or focus) opens the raw ---stripped text and writes back with -- prefixes; clicking a heading or following a link keeps the page in its reading presentation. Rendering never rewrites the buffer.
Story authors can connect an explanation to its working surface:
| Link target | Action |
|---|---|
volta:cell/kickPat | Reveal and jump to the declaration. |
volta:viewer/tally/count | Open the first Count viewer in tally; Story makes it big. |
volta:viewer/tally/count/2 | Open the second matching operation in source-span order. |
volta:play/kickT | Start that track without changing other tracks. |
volta:scene/doorway | Apply that scene's track membership immediately. |
volta:stop | Stop transport. |
For example, -- [Watch the counter](volta:viewer/tally/count) stays ordinary source text. Declaration names are exact; viewer operation names match the compiled operation without regard to case (count, toggle, onCount, hold, etc., where the operation has a viewer). A missing target reports an unavailable-target notice. Viewer links currently address patch nodes; track insert viewers keep their existing inline entry. The patch header's viewers menu lists complete calls, including nested ones, so opening a counter no longer requires aiming at the end of an overlapping underline.
Fresh notebooks use Quiet. A run of small binding and macro cards packs onto a column grid — balanced rows of up to three (two in Grid, whose pattern cards carry step rows), each row one shared card height — and a lone card spans the column. A card's chrome (grip, hide-source and tools chips) sits on its head row and appears on hover or when keyboard focus reaches it, so a hidden control is never an invisible focus target. Existing saved Quiet preferences are preserved, including older sessions that omitted the setting when it was off. The outline includes prose chapters and declarations, appears beside the page at laptop widths, and opens the searchable jump palette when there is no room for a rail. ⌘P searches the same chapters and declarations. In the song-time strip, APPLY SCENE and SEEK label separate actions; the seek row shows bar numbers and has a larger hit target.
Cells are views of one document, not independent files. When one cell changes length, sibling ranges are rebased before later edits. Stale guarded targets reject instead of overwriting peer/local changes. Accepted collaboration edits remain a single undoable transaction after cell offsets are rebased to the master document.
Graphical LED and piano-roll edits keep the owning cell's undo history. ⌘Z and ⇧⌘Z work directly after these gestures, including after the live recompile. Text inputs continue to handle their own history. Enlarged viewer cards keep their presentation through recompilation while live telemetry histories reset for the new module.
Alternating piano rolls show one source cycle at a time. The cycle N / M selector displays the combined period (for example, periods 2 and 3 repeat over 6 cycles); only the selected alternative is drawn and edited. This is a source preview, so it does not display a misleading live playhead for a transformed or multi-cycle track. Ordinary single-cycle rolls retain their live playhead.
Music-native cells#
Depending on declaration kind and available runtime data, a cell can include:
- pattern LED rows; clicking a supported atom toggles it with a source edit;
- numeric literal scrub with a floating value HUD;
- track level/activity, mute, transport/audition, and active-scene context;
- editable code plus compile or diagnostics status;
inspect/scopeactions that dock live graph and analysis views;- prose, history/freeze, and fold-to-abbreviation affordances.
Unsupported or structurally ambiguous pattern forms remain text rather than pretending to be safely editable controls. Audio-dependent tools degrade to source navigation when the browser/native bridge does not expose the required sample or bus data.
Patch Bay board#
The header chip morphs the notebook into a whole-song island board. Its five task modes keep one graph and one selection while changing the working context:
- Graph — topology, folds, focus and source;
- Compose — the selected instrument rail and a hosted Bench tool;
- Arrange — occurrence cards, section deltas, activity, transitions and automation;
- Mix — the track/output/master console and route context;
- Diagnose — lenses, exact readings, probes and take comparison.
G/C/A/M/D switch only when an editor does not own text input. The mode and compatible Compose tool preference survive shell-state round trips; legacy Bay feature tokens restore into the corresponding mode. There is one mode row and at most one contextual row. At compact widths the secondary actions collapse into one accessible more… menu, while the persistent gesture-help line keeps jack pull, step paint, gain drag, source edit, cable selection/unplug, zoom and fit discoverable.
Double-click a source-backed patch card, or select it and press Enter, to expand its Rack wire view inside Bay. Swap a named input by pulling a signal cable or using its input menu; ‹ Bay restores the prior board mode and camera. Alt-click retains the inline source editor. The same Rack keeps audition, graph, chain and scope tools beside the source-backed wiring editor; see Patch Bay and Rack for gestures, validation and supported patch shapes.
Bench's global command palette exposes Graph, Compose, Arrange, Mix and Diagnose from either notebook or Bay. Declaration commands remain in the same palette while the board is open; choosing one returns to Source and focuses that cell. The palette consumes Escape before the Bay's own layered Escape handling.
The board itself is one card per declaration, wired by the module's own dependencies. It carries:
- zoom/pan with three semantic detail tiers plus a corner minimap. The pure viewport projection preserves semantic lane order, chooses lane stacks from the available aspect ratio and card heights, prioritizes selected/current/ focused cards, and honors manual positions as an overlay.
fitmeasures the projected content after screen-space docks are reserved. Console and focus use bounded layouts instead of leaving a tall fixed-column graph in unused space; - typed live cables (audio, output, sidechain, macro, pattern, reference) whose beads carry real signal, and per-island health badges, including "patterned but silent". Parallel edges share typed trunks between lane pairs and split near endpoints; a selected edge separates and keeps its own hit path, animation and probe identity. Every painted cable is also an accessible node on its midpoint —
cable · <kind> · <from> → <to> · select— so the selection the unplug/probe chips need is reachable without a pointer. Layout and routes are cached by graph + viewport state, so a stable frame does not rederive the graph; - an arrangement dock over the shared Song Map projection. Collapsed, it is the compact section strip, play-head and hover/selection veil. Expanded, it shows scene occurrences, markers, seek strip and per-track clips on the same bar geometry as Timeline. Selecting an occurrence never launches it;
apply ▶is the explicit live-scene action, and clip/card selection is synchronized; - conservative, source-backed arrangement edits: occurrence drag reorders, the trailing grip snaps bar weights to integers (Shift opens exact entry), and the action row duplicates, inserts existing scenes, deletes and changes the outer
loop/oncemarker. Each release re-resolves its guarded source target, analyzes the complete candidate and lands one undo transaction. Computed, transformed, imported and mismatched forms remain visible but open the exact source instead of flattening compiler output. Legacy scene-only songs can preview and materialize the exact inferred schedule; - a writable activity matrix whose rows are tracks and whose columns are scene definitions (with a separate occurrence-number header). Cells distinguish local, inherited, explicitly removed, absent and opaque membership. Click toggles, drag paints one verified/undoable batch, and Shift-click carries the last cell state. Column selection only previews the graph closure;
apply ▶is the separate live-scene command. Scene add/clone, rename and delete are guarded source plans with identifier/collision checks and dependency/retarget previews. Literal lists preserve comments and comma style; recognized scene algebra receives conservative trailing+ [track]/- [track]terms; opaque cells remain inert and open their exact source; - a truthful Arrange projection with one
sectioncard per schedule occurrence, including repeated and fully shared later scenes. Real shared declarations appear once on a labeled trunk annotated with every 1-based occurrence they feed; the older Graph fold is explicitly labelledfirst use(partition scenes by first use), not arrangement ownership. Every adjacent boundary derives entering, leaving, continuing, transition-only, automation-change and active-take-diff sets from compiled song time + raw graph. Hover or pin a delta to light the exact upstream and typed-forward declaration rail; - resolved transition markers on the shared song axis, including wildcard endpoints, lead-in, overlay/replace and conditional metadata. Local literal edges can be added, retargeted, resized, toggled or removed through guarded source plans;
songGraphuses a state-machine projection instead of a fake linear schedule. Beneath a selected section, literalSetandRampautomation lanes use the macro's declared range/unit and section-relative handles. Computed/conditional values remain readable and route to Source; - a temporary
loop selectionaudition which seeks and wraps over the selected occurrence without touching source;commit loopis a separate source edit; - guarded board edits — patch/pattern/macro jack drops, LED paint, fader drag, alt-click inline mini-editor, double-click template insert — each a pure plan, a full compile check, then a compare-and-swap splice; refusals are toasted and the source is untouched on failure. An LED paint DRAG is one gesture rather than one edit per step: it opens the check on its first step and then rides the notebook's
begin_splice_gesture/splice_silentseam, so the whole drag is a single undo entry (the piano roll's rule); - ⌫ unplug — cables are hit-testable (segment distance to the painted curve, screen-constant reach); a click selects the wire (halo + a
⟵ unpluglabel), ⌫/⌦ unwrites it, and with no cable selected the key falls back to a focused island with exactly ONE pattern/macro wire. A pattern cable rewrites the track'splayhead to the inline rest literal!"~"— legal, silent source that a later pattern-jack drop replugs straight over — and a macro cable strips its+ macro * depthterm off every parameter carrying it (whole-block splice). Audio ("a track always plays a patch"), sidechain (the follower is woven into an expression), output and reference cables refuse with the reason. Escape peels the cable selection like any gesture; - the cable probe — with a cable selected, a
probechip beside the lenses clips an alligator-clip panel onto that wire (screen-space, upper-right): audio/output cables get the engine's tapped waveform plus the analyser spectrum pane (the sameScopeFramebus-tap windows the scope docks draw, native and browser alike), a sidechain cable gets its follower's envelope trace, a macro cable gets its value ride, and pattern/reference cables say why there is nothing continuous to draw. Probing holds the bus taps hot; clicking a different cable re-clips the probe live; Escape peels the clip before it peels the selection. A⟵ unplugchip in the same row is ⌫'s clickable, accessible twin; - freeze / thaw — with a track island focused, a
freeze ❄chip beside the lenses bounces the track's whole chain to abounceOf 4cprint and plays the render back through a sample patch (three blocks written over the track's one, in a single verified splice — the corpus's own render-and-play idiom, so a freeze costs ~zero DSP and the native shell bakes the print in the background). The live chain is preserved verbatim inside the print's parentheses, sothaw ❄folds everything back byte-identically; a frozen card wears a small ❄ and its fader and jacks keep working on the player around the print; - the take diff — a
TAKEpicker beside the fold chips holds the board against one take: islands whose block changed since it wear a solid accent rim, islands the take didn't have wear a dashed one, and a banner counts the diff and names it. Changed names are chips that morph back to the notebook and open that declaration's ⟲ history popover — where the inline diff, the audible A/B and the verified restore already live; removed bindings (in the take, not on the board) are named in the banner, since they own no card to stand on. The diff is recomputed only when the buffer or the chosen take changes; Escape folds the overlay before it drops any selection; - bar-quantized re-patch — a patch/pattern jack dropped while the transport RUNS passes the same admission test at the drop (plan + compile check) but commits on the next bar line, so a live swap lands on a musical boundary instead of mid-phrase. The queued cable is painted as a marching dashed ghost stamped
→ bar N · esc cancels; Escape cancels it (before it peels anything else), a loop wrap or transport stop fires it early, and leaving the Bay does not cancel — the edit re-plans from live source at fire time and refuses cleanly if the buffer moved. Drops with the transport stopped land immediately, as before; - a focused Compose workspace that mounts the existing Roll, Chords, Groove, Variation, Counterpoint, Envelope, Wave, Dynamics, Analyzer and Rack paths through one
BenchUihost adapter. The selected rail stays fitted beside a remembered right/bottom dock on wide screens; compact Compose temporarily replaces the graph body and supplies a breadcrumb/Escape return. A clean tool retargets with card selection; a dirty tool pins and uses the notebook's reload/keep-mine conflict rules. Commits, hot-swap, drill-down and bus/node interest remain the notebook's, so leaving Compose releases registrations. Macro selection routes to value history, the native route matrix and Arrange automation destinations; song/section selection routes to the native Arrange dock and activity matrix. The cycle explorer is a Compose-modecycleschip (it forces Compose and is scoped to the selected track), and the mix strip is theMixer ↗drill-down — neither is a dock-hosted tool; the per-kind route table is Edit music in the notebook § Bay-hosted Compose workspace. Compact routes live in the same singlemore…menu; - source-backed composition actions on the selected instrument closure.
duplicate instrumentpreviews independent share/clone choices for patch, pattern and macro dependencies; Pattern/Patchmake variationclones only that dependency and retargets the track;extract to sharenames a direct inlineplaypattern and rewrites the chosen use sites. Preview rows show exact new bindings, retargets, generated names and unsafe members. Imported, derived, transformed or stale closures disable commit and open the precise source rather than partially cloning. Accepted work is one full candidate analysis, one atomic multi-splice and one Source undo. Each generated binding goes through the same staged-placement path the typed palette uses, so the new cards land under the source selection once their declarations actually appear (the Compose rail itself ignores hand placement, so the landing shows on leaving focus); - a typed
new…palette for Pattern, Patch, Macro and Track. Choosing a type only arms a frozen plan: source stays unchanged while compatible card targets highlight. Hover/accessibility text names the exact drop effect (including a Macro's concretepatch.parameterendpoint); dropping performs one guarded create-and-wire transaction, and the existing staged-placement path claims the new card only after rebuilt declarations arrive. Cancel/Escape is inert.save recipedelegates the played patch's source closure to Shelf capture; - an expanded pattern context with coordinated written, heard and compiler-period rows. Literal
fast,slow,everyand Euclid controls, plus lossless rotate, velocity, pitch and duration lane operations, use owning-block compare-and-swap guards and one undo per gesture. Heard events link back to written atoms only through current compiler source anchors. Pattern-argument transforms and track audio-chain chips are distinct. The IR does not expose post-juxchannel provenance, so the UI explicitly says stereo attribution is unavailable instead of inventing L/R rows; - an actionable Diagnose inspector shared with the board tint verdict. Its accessible legend defines Signal/Caution/Alarm; deterministic rows give exact Level, Cost, Harmony and first-Clip-origin readings and route to the appropriate Source, Rack, Timeline or Mixer destination. Missing/neutral measurements produce neither a tint nor a row. Scope can follow the selected section or whole song; section audition, audible take A/B and pinned probes preserve the lens. At narrow widths an expanded Arrange dock projects as a bounded scope strip so the focused card remains wholly visible without discarding Arrange state.
Every island, jack, arrangement occurrence, resize grip, clip, activity cell and track fader is its own accessible node. Arrangement labels report occurrence identity, bar span, selection and action; activity labels report track, definition, provenance and the proposed edit; selection and apply ▶ remain distinct in the accessibility tree. The fader is a DragValue that reports the level it is about to change and moves on ↑/↓ once focused. The board's model is derived once per source change rather than per frame (cells + analysis + module + key), and only the islands inside the viewport are painted.
Rack and scope docks#
The Rack dock derives a semantic graph from the compiled module and includes source links, rate/unit/type context, constant values, and shape-specific node glyphs. It can pan/zoom, refit, float above another view, and jump from a graph node to its originating source span.
Scope docks consume bridge-provided frames and can show waveform/envelope, spectrum and peak hold, spectrogram, RMS/peak/clip/phase/event statistics, and available response views. Live taps are interest-gated so closed cells do not keep unnecessary analysis streams active.
Structural editing#
Bench cells use the production structural layer, including selection motion, pipe/pattern verbs, and ownership-aware voice↔track moves. In STRUCT mode, declaration navigation crosses cells. Compile-gated commands verify the spliced master song rather than compiling an isolated declaration that might depend on surrounding definitions or imports.
See Edit source through musical structure for the safety contract. The structural key guide and Structure Lab are hidden from launch navigation during polish.
State and accessibility#
Fold state and relevant Bench preferences persist through shell state. On desktop, each embedded editor receives a distinct AccessKit identity and text runs. The browser uses the same canvas UI but does not yet expose an equivalent screen-reader tree; see Accessibility and keyboard controls.
Implementation map#
- Track cells also carry a
wheel ▸dock — the compact cycle wheel (Read a rhythm as a circle): the track's attacks on a circle, with a←/→stepper while the transport is stopped.
| Concern | Current implementation |
|---|---|
| Bench UI, cells, guarded master-document splices | ui/src/bench.rs |
| Pure source/module derivation and pattern tokenization | ui/src/benchmodel.rs |
| Prose-cell markdown (pure parser: blocks + inline runs) | ui/src/md.rs |
| Story links, chapter targets, and the viewer chooser | ui/src/notebook.rs |
| Embedded editor, scrub, STRUCT behavior | ui/src/editor.rs, ui/src/structedit.rs |
| Cell scopes and live analysis | ui/src/benchscope.rs |
| Patch Bay coordinator, painter and gestures | ui/src/benchbay.rs |
| Bay task modes | ui/src/benchbay/modes.rs |
| Viewport projection and cable routes | ui/src/benchbay/layout.rs |
| Compose adapter and focused tool state | ui/src/benchbay/compose.rs |
| Card hit geometry, health badges and body painting | ui/src/benchbay/cards.rs |
| Floating overlays (picker, probe, pins, matrix, cycles, minimap) | ui/src/benchbay/overlays.rs |
| Island template palette and its popup | ui/src/benchbay/palette.rs |
| Guarded source-edit planners for board gestures | ui/src/benchbay/plan.rs |
| Source-text scanning shared by cards and planners | ui/src/benchbay/text.rs |
| Bay arrangement/source planners and dock | ui/src/bayedit.rs, ui/src/benchbay/arrange.rs |
| Shared Timeline/Bay arrangement model and geometry | ui/src/songmap.rs |
| Card chrome and typed live card bodies | ui/src/benchcard.rs |
| Board lens verdicts/grouping and inspector | ui/src/baylens.rs, ui/src/benchbay/lens_ui.rs |
| Semantic graph/rack | ui/src/rack.rs, ui/src/moduleinfo.rs |
| Host data and source mutation seams | ui/src/bridge.rs; implementations in app-native/ and ui-wasm/ |
Limits#
- Browser screen-reader parity remains blocked on an explicit DOM/accessibility projection.
- Some sample-analysis and device-backed affordances depend on host capability.
- Structural rename/reference scope follows the shared editor/compiler limits; cross-file identity work is tracked with the analysis-service roadmap.