Skip to content

Consolidate Frame into this repo, with its history from Studio - #26

Merged
fts-forge[bot] merged 288 commits into
mainfrom
chore/gh-3-consolidate
Nov 2, 2026
Merged

fts-forge[bot] merged 288 commits into
mainfrom
chore/gh-3-consolidate

Conversation

@fts-forge

@fts-forge fts-forge Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Closes #3. Part of #2.

What

Frame's history from Studio, joined to this repo's own history, so all of Frame lives here and history reads continuously from March 2026 to Studio's tip at the cut (studio@fc6b1954, 2026-11-01).

  • Cut. I ran git filter-repo on a fresh Studio clone, keeping every place Frame ever lived (frame/, legacy/frame/, crates/frame/, apps/frame/, docs/frame/, docs/frame-themes.md, xtask/frame/, plugin-faces under features/faces/ and features/ui/, scripts/face_gallery.py). Each path is renamed into this repo's layout:
  • Graft. The 16 Studio commits from the import (studio@5052b171) onward are recreated on 2dae10b with git commit-tree. Trees, authors and dates are unchanged. Studio's #N refs become FastTrackStudios/studio#N, and each commit ends with Studio-commit: FastTrackStudios/studio@<sha>. Because no existing hash changes, main (7975939) and 2dae10b are ancestors and this merges without rewriting main. The import commit's diff against 2dae10b is only what Studio's commit message lists:
    • three manifests;
    • library-fs taking an EventSink;
    • the 1176 scenario, moved out;
    • the 2650 generated face files.
  • On top, six commits:
    • The workspace root. It uses Studio's pins at the cut (architect v0.9.31) and the patches that reach Frame's graph, with Studio's lock as the start. Rust 1.99.1 is pinned in rust-toolchain.toml and in the flake (same nixpkgs and rust-overlay as Studio, with mold and the Vulkan loader). Studio's rustflags tables are copied in the same order so kache shares builds. The paths that named Studio's layout are fixed.
    • Clippy. Clippy is clean under -D warnings, and the feed::dsp doctest now compiles.
    • CI on the THEBATTLESHIP pool: fmt, clippy -D warnings, check, test, and one ci status.
    • The 1176 parity scenario is back with the suite (byte-identical blobs from 2dae10b, so no new objects).
    • scripts/frame-catch-up.sh (just catch-up <studio>).
    • Docs. README, CLAUDE.md, and LICENSE-MIT + LICENSE-APACHE: Frame is MIT OR Apache-2.0. figcraft's NOTICE comes with Depend on figcraft by tag (fts-v0.14.0-1), with a NOTICE and slim engine features #7, once figcraft is a dependency.

Branches: backed up and checked

  • theme-runtime and merge-fleet-rc7 (2dae10b) existed only on THEBATTLESHIP. They are now pushed as-is and as archive/theme-runtime and archive/merge-fleet-rc7, and the March main is tagged archive/main-2026-03.
  • I checked the 54 local worktree-agent-* branches, plus the named topic branches, against 2dae10b:
    • Ancestors of 2dae10b: 23 branches, already merged.
    • Every commit matches by patch-id: 37 branches.
    • The other 20 branches hold 20 distinct commits that don't match, broken down below.
    • Rebased under the same subject with conflict fixes: 17 commits (3b4d9ee 536b2c0 3c87b10 515c994 e459432 4d0e98a 4315bb2 1f99eb1 620a07a 879a3fa c033a6f a2a9738 d526513 76bdb8a 5e2fd09 88fd1fc 3692520). Most of their blobs are in 2dae10b's history.
    • No subject match: 3 commits (1a73736 Blackout Gain suite, 1d522db Modern Rhythm suite, e920a2b wip amp-suite kit parts). Every file they add exists in 2dae10b, developed further by 76db7e4 "three amp-suite recreations". So they are superseded.
    • Nothing is carried; nothing unmerged is lost.
  • Deleted (local, on THEBATTLESHIP): 53 worktree-agent-* branches. All local branches are also in a bundle at /run/media/Development/wt/frame-p0/frame-local-branches-2026-11-02.bundle.
  • Kept:
    • worktree-agent-a8950d35e9164cb1b, which is checked out in a live worktree. Its two commits are merged or superseded.
    • The named topic branches (final-*, suite-*, font-system, …). All are merged or superseded by the same check, but they are not worktree branches.

Checks (local, in nix develop .#ci, kache)

  • cargo fmt --all -- --check: pass
  • cargo clippy --workspace --all-targets -- -D warnings: pass
  • just check: pass
  • just test: pass. Note how few tests there are: Studio's studio#203 cut Frame's integration tests, which leaves 4 unit tests and 1 doctest.
  • just catch-up: I replayed studio@0e8dcba4 onto its parent here, and the tree equals the imported commit's.
  • git log --follow on moved files reaches through 2dae10b to their creation in Frame.

Notes

  • Lints. Studio's workspace denied clippy pedantic and nursery, but Studio ran clippy as advisory (--cap-lints warn). Frame had 5,593 of those, so its table keeps the rust lints plus clippy's defaults, and enforces them. Stricter lints can be added one at a time, with their fixes.
  • The parity suite stays out of CI for now. It needs a GPU, and the pool runners haven't been checked for one; just parity runs locally.
  • Studio's copy. Until Studio depends on Frame by tag; no Frame code left in Studio studio#373, Studio PRs that touch crates/frame, docs/frame*, xtask/frame or features/ui/plugin-faces are replayed here with just catch-up.

- scene_collect: build_scene_primitives_and_effects returns an EffectsPlan
  (emissive draws recoloured in world space, strength = intensity × effective
  opacity; shader regions = primitive range + node-local→world box) next to
  unchanged primitives. build_scene_primitives draws emission as a new
  EffectKind::Glow (vello: blurred rounded box; SVG: feGaussianBlur filter).
- render::fx (anyrender): Mode {Gpu, Fallback, Off}, Stage: vello → texture,
  shader subtrees rendered with their enclosing groups/clips/layers/masks,
  run through a WGSL effect and composited in paint order; emission bucketed
  by radius, half-res, downsample + separable Gaussian (tight + wide), added
  mostly around the emitters. Built-ins glass, crt, lighting, flicker;
  asset:<id> custom WGSL (CONTRACT.md); failures logged once, drawn plainly;
  pipelines cached.
- Offscreen::render_with_effects; render_surface/kit_sheet --fx; desktop
  window + headless use the stage (--no-fx / --fx), redraw continuously for
  time-dependent effects.
- examples/surfaces/08-glow-demo.fm; plan + GPU pixel tests.

Parity corpus: 26/26 actual.png + actual.svg byte-identical to the baseline.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
A backlit VU needle's bloom leaked out below the meter window: the
emissive draw painted the node's own geometry without the clips that
cut the node itself. Each EmissiveDraw now carries its ancestors' open
ClipStarts with their world transforms, and bloom applies them.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
Reverb faces 01-ir … 08-special, one per reverb-ui SpaceDesign, each drawn
as its machine: a convolution processor with the impulse on a glass
display; a digital hall rack with a phosphor display (program, decay drawn
as reflections + tail, live RT60/pre-delay/size/mix) and a fader bank; a
steel plate seen through its window (frame, springs, driver, pickups,
damper) with the remote's DECAY dial printed in seconds and a VU; a room
box with the room in perspective and its early-reflection tap diagram; an
open spring tank on a green chassis; an ambient halo; a scope of drifting
delay lines; and a black effect box with a lamp ladder on the tail level.

Modulation faces 01-chorus … 05-wah: four stompboxes (enclosure, lid art,
LFO rate lamp on a <face>/lfo scalar stream, bypass footswitch with its
LED) and a side-on wah whose treadle rocks on its axle as a 16° knob.

Params namespaced per face with real units (decay s, pre-delay ms on a
printed 0–250 scale, rate Hz log 0.05–20, output dB, width to 200 %);
choice params for programs/engines/circuits; <face>/in,out level streams.
Shared helpers (copied from the VCA faces, plus displays, glow via
emission, ribbons, lamp rows, LFO lamp, footswitch, enclosure, any-ink
knobs) live in reverb/ui.rs. Test: every face lints 0 errors against its
merged catalog and every binding resolves.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…ive circuits

Delay (plugin "delay"): 01-digital (rack DDL with a VFD reading both times
in ms and as note values, TAP/FREE-SYNC/LINK keys, echo train on the glass),
02-tape (tape echo: reels, record + three switchable playback heads, VU,
time window), 03-analog (BBD stompbox with the chip in a window, bypass and
tap footswitches, red LED readout), 04-pitch (staircase display, interval
in semitones), 05-rhythmic (16 step keys are the taps), 06-special
(reverse/spectral/diffuse with the smear on a scope).

Saturation (plugin "saturate"): 01-tube (valves glowing behind a window,
stage selector, VU), 02-tape (meter bridge, speed and formulation
selectors), 03-transformer (core and winding in an open chassis), 04-
transistor (hammertone fuzz pedal), 05-digital (LCD staircase transfer
curve, bits/rate/ceiling readouts).

Shared parts (delay::parts, delay::art): placing by shaft, tighten,
printed scales at real positions, Label/Value slots for printed legends
and values, note-division Selector pickers, emitting readouts/lamps, LED
columns, reels, heads, enclosure, footswitch, valve, transformer, chip.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…c knob, LED meters, footswitch, lamps, GR ballistics, meter switch, readout formats

Kit (surface-kit):
- Place gains ink (Light/Dark/#hex), keep_case, sweep, touch (tight hit box
  padded to TOUCH_MIN), reverse, inner (concentric cap), select (meter
  source switch), format; helpers ink_color/case/opt/flag/control.
- Printed scales, numerals, detents, ticks and panel legends honour ink on
  knobs, switches, levers, push-in and LED banks; legends keep case on ask.
- Knobs with options are stepped (detent sweep, options as scale); every
  rotary takes Place::sweep. lever.pultec's paddle now turns about its
  pivot (square pivot frame), so no unfold_needle is needed.
- Opt-in tight boxes (Place::touch) for knobs, rotary switch, lever;
  place_in_cell/drawn_bounds for laying them out; kit_sheet uses them.
- New: knob.concentric (collar + nested cap, two bindings), meter.led_bar,
  meter.led_column, meter.led_ladder (thresholds, gr, reverse),
  meter.lamp (Glow on a scalar stream), switch.footswitch (LED, momentary).
- Banks reverse; cap legends shrink to fit the cap.

Runtime/proto:
- Gain reduction rests at 0 dB and attacks downward (Ballistics::reduction);
  meters rest correctly with no data; Meter bars honour the binding curve;
  reversed spans; gauge fill/thumb follow the lane's long axis.
- Gauge/Meter Led slots light as a threshold ladder; unlit Leds drop their
  emission; new SlotRole::Glow (opacity = value).
- Control::select (SourceSelect/MeterTap) switches a meter's stream from a
  choice param; Control::format (ValueFormat: decimals, unit, note, labels);
  stepped ranges print the decimals their step needs.
- design-lint: nested concentric controls are not an overlap; gauges and
  meters may show their value with Fill/Thumb/Led/Glow; readouts with Glow.
- render_surface: --stream id=v[,v2] (repeatable); --level feeds every
  level stream not named by --stream.
- design-session add_component takes ink, keep_case, sweep, touch, reverse.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…n strokes, text glow, exact aligned strokes

Nodes not imported from Figma (source.tool != "figma") are now drawn as
authored; Figma imports keep the exporter-compatible path, so the parity
corpus is unchanged (26/26 actual.png + actual.svg byte-identical to a
baseline rendered on this GPU before the change).

- PaintPrimitive::{Path, Rect} carry `authored`; Text carries `effects`.
- Shadows follow the outline: DropShadow / InnerShadow / Glow (and a solid
  LayerBlur) on authored vectors, and on authored rects with per-corner
  radii, are rasterised as an antialiased coverage mask of the real fill +
  stroke geometry, Gaussian-blurred on the CPU and drawn as a cached image
  (new anyrender_renderer/soft.rs). vello only blurs uniform rounded rects.
- Geometry as written: no compute_path_fit_transform for authored paths —
  path data is node-local and may reach outside the box. A clipping frame
  now clips authored geometry that reaches past its child's box, and an
  authored frame's clip opens after its own paint, so its own shadow,
  glow and outside/centre stroke aren't cut by it (children still are).
- Open strokes: a stroke without stroke geometry strokes the fill geometry
  (kurbo stroke, caps/joins/dashes; inside/outside via clip/DestOut on
  closed subpaths); open subpaths are stroked lines, never filled, and
  nothing falls back to stroking the box. SVG export matches (fills only
  the closed subpaths of such nodes).
- Top-edge band: an inside/outside stroke on a rounded rect was stroked
  along an inset outline; vello's GPU stroker put an edge a hair above
  the first tile row and filled that whole 16 px row with the stroke
  colour (the "dark band" on the 1176 faceplate at x=0,y=0, w=960, weight
  1.0667, radius 3.2 — not the angled gradient). Authored aligned strokes
  are now the exact even-odd ring between the two edges.
- Text: glyph outlines (skrifa) carry drop shadows, inner shadows and the
  fallback emission Glow; Figma text keeps only the glow.
- Golden primitive dumps regenerated: only the new fields changed.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…derer

- Drop underpaint (the band was the inside stroke); a unit's own colour is
  now the panel's own fill (repaint).
- Lines are open stroked paths: ribbons, header rules/grooves, scale ticks,
  graticules, VFD mesh, laminations, windings, leads, board traces, tape,
  reel dogs, screw slots, the transistor symbol; over()/path_in gone.
- Kit instead of workarounds: Place::touch for knob boxes, Place::ink for
  toggle captions, stepped chicken-head selectors via Place::options,
  meter.led_column for every wet/output column, switch.footswitch (tap is
  momentary) for the stompboxes; bank legends auto-fit (fit_legends gone).
- Readouts carry ValueFormat: note pickers print ValueFormat::labels(DIVISIONS),
  the crusher's rate one decimal.
- Lit things emit instead of wearing drop-shadow glows (lenses, readouts,
  annunciators, display bars, traces, valve cathodes); crt on the pitch
  display and the special scope, flicker on the valves' heater light,
  glass on every VU window.
- Removed dead helpers: tighten, unfold_needle, set_sweep, detent_sweep,
  detent_angle, reink, fit_legends, add_value, detents, print_scale,
  section, sheen, led_row, hand-built led_column, art::footswitch, jack,
  ribbon, over, path_in.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
The emissive recolour dropped a path's fill geometry when it had no
fills, but an authored stroke with no stroke geometry strokes the fill
geometry; with both gone the bloom fell back to the node's box.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…t/renderer

Faces (1176 ×3, LA-2A, CL 1B, 670, Vari-Mu, SSL bus, dbx 160, Distressor,
Pultec, SSL channel, API 550A, Neve 1073):
- Place::ink / keep_case instead of reink/repaint and legend restyling;
  Place::touch for tight boxes; Place::options stepped sweeps instead of
  blank scales + strip_scale + set_sweep; Place::reversed for the 1176
  ratio bank; unfold_needle removed (lever.pultec, switch.rotary).
- 1176 METER bank and LA-2A meter selector really switch the VU
  (Place::select + SourceSelect: GR / output ranges / off).
- Neve mid/low: knob.concentric instead of the hand-built collar + cap.
- Distressor GR ladder -> meter.led_ladder (gr, reversed); dbx 160
  BELOW/ABOVE -> a two-lamp GR ladder (dark at rest); SSL channel output
  -> meter.led_column ×2.
- Lamps and lit readout figures use emission, not drop-shadow glows;
  readout formats (ValueFormat) instead of whole-number guesses.
- Renderer workarounds removed: comp_fet's Center-stroke switch, filled
  ribbon glyphs (now open stroked paths).
- Dead helpers removed (tighten, unfold_needle, set_sweep, detents,
  print_scale, gr_ladder, threshold_lamps, led_column, reink, …).

Kit:
- knobs: contact() is one vector + zero-offset drop shadow along the
  outline (KIT.md "What turns" updated).
- meter.vu_amber: lamp bloom toned down (wash 0.05, hotspot tint only) so
  the card glows warmly but the scale stays crisp on the GPU.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…renderer

- ui: glowing() is emission only; stroked artwork is open stroked paths
  (strokes/line/bars) boxed to their points — ribbon, line_node quads,
  shape() padding, glyph and the haze-under-a-line glows are gone.
- Knobs via Place::ink/touch/scale/options/sweep: the kit prints each
  scale in the face's ink round a tight box; no strip_scale, tighten,
  set_sweep or face-printed scales. Stepped selectors are switch.rotary
  (chicken-heads) or a stepped knob, legends pushed clear of their dots.
- Kit meters and lamps in the face's colours (tint_lamps): meter.led_column
  for the IR/hall outputs, meter.led_bar for the ambient/random/special
  lamp rows, meter.lamp on <face>/lfo for every rate lamp,
  switch.footswitch for every stomp. lfo_lamp, lamp_row, led_column,
  footswitch, reink and the other dead helpers removed.
- ValueFormat per param unit on every readout (whole %, ms; 0.1 dB).
- IR file keys carry their names; glass shader on the IR and random
  displays and the plate window, crt stays on the hall; the wah treadle
  turns as one body.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…omentary/colour/glass options

Kit:
- Scale labels (knob numerals, stepped option words, concentric collar,
  switch.rotary) are placed so their box clears the tick/dot: pushed out
  along the ray by the box's half-extent, anchored left/right on the sides
  (draw::radial_label / radial_extent / text_width). Type floor
  draw::SCALE_TYPE_MIN = 9 px whatever the knob's size; the frame and the
  legend grow to hold them. Option words take the place's case.
- An empty scale (`scale(&[])`) prints no ring at all.
- Button banks fit one legend size for every cap (the smallest any needs),
  with a margin inside the cap; led_bank likewise.
- Single buttons (button.illuminated etc.) take the `momentary` flag.
- knob.concentric: `grey` variant (the 1073's all-grey pair) and stepped
  `options`.
- switch.rotary is the chicken-head (knob.chicken_head's moulding) over
  detent dots.
- Lamps, LED bars/columns/ladders and footswitch LEDs take `color=#hex` or
  per-lamp `colors=#a,#b,…` (Place::lamp_color).
- VU meters take a `glass` flag (glass shader on the card, GPU only).
- kit_sheet variations show each of these.

Faces:
- delay/parts.rs: clear_ticks and the hand-built momentary() removed; TAP
  keys use `momentary`.
- reverb/ui.rs: clear_dots and tint_lamps (+ HSL retint) removed; lamps,
  bars and stomps use color=/colors=.
- comp_tube selectors and API frequency/gain knobs use the kit's printed
  scale instead of re-engraving it; Pultec output likewise.
- Neve 1073 mid/low pairs: knob.concentric `grey`, stepped by options.
- Pultec levers back at their on-panel pivot (tight boxes kept).
- SSL channel trim/drive readouts: 0.1 dB / whole %; filter caps print
  every other mark.
- glass on every compressor VU; tight boxes on the tube-comp knobs and SSL
  caps (the larger print no longer overlaps boxes).

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
Every needle VU now carries Control::reference_dbfs =
VU_REFERENCE_DBFS (−18): a level stream reads against it, gain reduction
does not, and a meter switch's +4/+8 taps offset on top. The Fairchild's
hand calibration is gone. The LA-2A face prints no model name.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…neric names

FET limiter: three plates laid out from front-on photos — black (blackface
LN), silver (early blue-band), late (late silverface) — as fet/ with files
01-fet-black / 02-fet-silver / 03-fet-late. 2U proportions, plate-as-ears
with corner slots and Phillips screws, dial discs, key columns, dual-scale
meter card; no brand or model printed. Power jewel only on the early plate.

Tape echo: RE-201-style front (tolex case, name strip, black and green
fields, chrome-skirted knobs, jack strip); drops the EC-3 model line.

New kit parts: knob.knurled_cap, knob.chrome_skirt,
button.push_in_key_bank, meter.vu_limiter.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
Rebuild the four hardware EQ faces from reference photographs, with
generic names only:

- 01-program-eq (progeq/): 3U blue-grey panel, black scalloped phenolic
  knobs with bare 0-10 numerals, bar levers under CPS/KCS, IN bat toggle,
  pilot jewel, power switch (decoration); invented output trim and dB
  readout windows removed.
- 02-console-eq (consoleeq/): the channel strip's sections as columns
  (FILTERS, HF, HMF, LMF, LF) keeping the strip's stagger, collet caps
  with coloured tops in numbered dot scales, grey keys, warm dark grey
  paint; invented drive removed; LF gets its seventh position (400 Hz).
- 03-proportional-eq (propeq/): portrait black module, HF/MF/LF winged
  concentric pairs (cream gain wing, blue lobed frequency ring), seven
  frequencies a band and 2/4/6/9/12 dB gain clicks, FLTR toggle, LF/HF
  shelf-peak and IN push caps with the IN lamp; drive/trim removed.
- 04-classa-eq (classaeq/): blue-grey rack row, red and blue wing
  selectors and grey turrets in clear skirts, knurled aluminium collars
  for the stepped mid/low frequencies, cream phase/EQ keys; trim removed.

New kit components (new files, registered with one line each):
knob.scalloped, knob.console_cap, knob.winged_pair, knob.turret,
knob.turret_pair, switch.toggle_nut, button.round_cap, button.key.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
Rebuild the four tube faces from reference photos of the real units and
strip every maker/model name:

- 04-opto-leveler (opto/): grey 3U panel, red slanted name + swoosh,
  glossy black tab-pointer knobs with numerals turned round them, VU in a
  chamfered charcoal frame, meter selector over bare POWER toggle.
- 05-opto-comp (optocomp/): royal-blue panel, seven-sided knobs read
  against dots at the unevenly printed angles (param ranges follow them),
  bar selectors for meter / timing / sidechain bus, tan VU, red jewel,
  OFF/ON power knob. Adds meter select, sidechain bus and IN params and an
  input level stream; drops the invented readouts.
- 06-twin-valve (twinvalve/): both channels on a black plate in a grey
  cabinet front: bakelite meters, paddle metering/time-constant knobs,
  chunky scalloped input gain (20..0 dB) and threshold (0-10), shared AGC,
  ON toggle, pilot and fuse. Drops the invented output knob and plate.
- 07-vari-mu (varimu/): stereo layout on brushed gunmetal: right-to-left
  white reduction meters in black cases, black sub-panel of fin knobs in
  tick rings, toggle cluster, bypass/limit toggles, jacks and trims.

New kit components: knob.tab_pointer, knob.heptagon, knob.bar_pointer,
knob.fluted_stack, knob.paddle, knob.fin (knobs/valve.rs),
switch.toggle_bare (controls/bare_toggle.rs), meter.reduction
(meters/reduction.rs).

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…aracter compressors

Rebuild faces 08-10 from photographs of the real units and give them
generic names (08-bus-comp / buscomp, 09-vca-comp / vcacomp,
10-character-comp / charcomp). No brand or model text anywhere visible.

- Bus compressor: 1U pale silver-grey rack front; tall black housing with
  an amber-lit compression meter (needle rests left, swings right); blue
  capped-skirt knobs in dot rings; square backlit keys in grey bezels;
  adds the unit's external side-chain, auto-fade and fade-rate controls;
  drops the invented mix knob.
- VCA compressor: gloss-black desktop front between walnut cheeks with
  aluminium trim; knurled black knobs with aluminium inserts; white piano
  keys (power, interlocked meter select); big ivory card printed in blue
  in a deep bevelled frame.
- Character compressor: black 1U with white hairline frames; colour-coded
  16-lamp GR ladder numbered above; per-colour ratio/detector/audio lamp
  selects; four big cream numbered dials; power rocker; drops mix.

New kit components (new files, one-line registrations): knob.knurled_insert,
knob.capped_skirt, knob.numbered_cream (knobs/outboard.rs);
meter.vu_decibel_white, meter.gr_compression (meters/movements.rs);
button.piano, button.piano_bank (controls/piano.rs).

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…logo styles

Heavy tight lowercase mark with a dark dot on the VCA compressor, the
stacked maker's-corner name and boxed series badge on the bus compressor,
and a big heavy slanted name on the character compressor — generic words
only. comp_vca/logo.rs sets a mark's weight (overprinted past 600, as the
renderer carries one face), slant, tracking and colour.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
The unit's top half only: one meter, metering switch, input gain,
threshold and time constant driving both sides, with the AGC switch
(stereo or mid/side) under the time constant. The cabinet front keeps its
frame at 410 px tall instead of 590. Per-channel params and the second
GR stream are gone.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
- feed-proto: FeedKind (Level, GainReduction, Spectrum{bins,f_min,f_max,
  floor_db}, Tuner, Scalar, + History{of,len}, Waveform{len},
  Envelope{stages,voices}), FeedInfo, FeedOptions/FeedRequest, FeedFrame,
  VoiceState. Owns the shared value types (EntryId, Range, Unit, Curve…),
  moved from surface-proto which re-exports them; StreamKind/StreamInfo/
  StreamFrame are aliases, old catalog JSON reads unchanged.
- SurfaceEngine::request_feeds(Vec<FeedRequest>) -> Vec<FeedInfo>, with a
  default body so existing engines compile; SurfaceError::UnknownFeed.
- feed-dsp: realtime-safe taps (spectrum, history, level, scalar, scope,
  envelope) over a lock-free SPSC ring, own radix-2 FFT, Publisher.
  Allocation-counting test proves audio-thread pushes don't allocate.
- feed-memory: demo generators for every kind; Memory and the desktop
  mock use them and honour requests; render_surface --demo-feeds.
- docs: visualizer-framework section 1 describes what was built.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
- features/library: library-proto (SurfaceStore rpc: list/get/publish/
  catalogs, #[subscribe] updates(id) and changes; Signature "fits";
  whole-snapshot updates), library-fs (a watched folder of .frame/.fm,
  sidecar meta, previews, versions), library-link (ws serve/connect,
  Blocking client, Follow thread with backoff).
- apps/frame/library-server: vox over WebSocket + HTTP index/downloads/
  GPU-rendered previews.
- surface-runtime: Runtime::swap_surface (new runtime/swap.rs; one `mod`
  line in runtime.rs) keeps values, coalescing edits, meters and a
  gesture in flight across a design swap.
- desktop: --library/--surface/--browse/--install-dir; follows a surface
  live, gates updates by parse + new lint errors with a toast; a frame-
  drawn browser grid (fits filter, preview, install, fork); headless
  --updates N for before/after shots. Player::swap preserves state.
- frame-mcp: --library [--surface] publishes after every edit.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
A knob over a choice (3-way meter switch, time constant, ratio) sent the
raw drag position, so it glided between its positions and only snapped
when the engine rounded it. Values sent to a stepped param (choice, toggle
or Range::steps) are now quantized, and the wheel and arrow keys move one
whole detent per notch or press (their small nudges would round straight
back). Continuous params are unchanged.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
- surface-proto: ControlKind::Plot { style, x, y }, ::Curve { provider,
  inputs, series, style, x, y }, ::Handle { x, y, wheel, x_map, y_map };
  PlotStyle, Axis (Linear / Log / Time), PlotRange, CurveInput +
  InputSource, HandleAxis (after/shift offsets), BUILTIN_CURVES; slot roles
  Trace / Area / Marker; validation of curve inputs and handle axes.
- surface-runtime: plot geometry (decimated to pixel width, NaN gaps,
  steps/bars/dots, areas to a baseline); spectrum bins placed by their
  feed's band; client-side history rings for readings, History feeds as
  sent; CurveRegistry with builtins eq.response (RBJ bell/shelves/cuts
  with slopes/notch/band-pass, sum + per band), comp.transfer (soft knee),
  adsr.envelope (curvature, voice playheads), filter.response — memoised
  and shared across controls, overridable by engines; handles drag their
  params through their axes (log/linear, offsets), wheel, Shift fine,
  double-click reset, through transformed containers; hit_box,
  feed_requests + sync::Engine::request_feeds on connect.
- design-lint: curve-input, curve-provider, handle-area; plots bound to
  params / missing feeds are binding errors; handles skip overlap.
- surface-kit: Family::Viz — viz.spectrum, viz.gr_history, viz.scope,
  viz.comp, viz.eq_graph, viz.adsr; viz::catalog builds their params/feeds.
- examples: 09-comp-viz, 10-eq-graph, 11-adsr (+ catalogs), generated by
  make_viz_examples; kit_sheet --family viz; frame --script
  drag-first-handle.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
An EQ graph binds its bands through curve inputs and handles, so the
signature missed them and the surface claimed to fit any engine.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
…tors, LFOs, noise, spin

A `Motion` on `Node::motion` drives one target property (translate,
rotate, scale, stretch, skew, opacity, fill/stroke colour, emission,
dash offset, path morph, shape-provider geometry) from an input
expression (params, feeds, time, gestures, other motions) through
physical dynamics (spring, oscillator excited by a rising source, lfo,
noise, ballistic, keyframes, spin). Plain data: round-trips in markup,
live-reloads, deterministic offline.

- features/motion/motion-proto: data types + the expression language.
- features/motion/motion-runtime: Animator — fixed 1 ms step, stall cap,
  state kept across swaps, is_animating, shape providers (coil,
  reverb.room, wave). 200 motions: 0.016 ms/frame (release).
- surface-runtime: owns an Animator, steps it in frame(t), keeps it
  across swap_surface, hover/press sources, is_animating().
- document-proto/-text/-loro: Node.motion, Prop::Motion, motion= attr.
- surface-kit: motion presets; meter.lamp warms through a spring,
  `flicker` option on lamps and VU faces.
- design-lint rule `motion`; design-session `animate` tool.
- render_surface: --frames/--fps/--gif/--frames-dir, --set-at, --stream-at.
- plugin faces: spring tank rings with the input level and decays by
  dwell/damping; room geometry follows size/decay/diffusion; tape
  machine reels spin; tape echo loop runs; valve heaters warm and
  flicker with drive; pedal rate lamps pulse; wah treadle springs.
- docs: visualizer-framework §9 Motion, KIT.md, REFERENCE.md.

Claude-Session: https://claude.ai/code/session_01Nwusq16bN6CJteQKkaEwzo
Codys-Wright and others added 26 commits October 9, 2026 01:15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en from the side

The volume pedal, wah and dive bomb drew a toe half and a heel half that
squashed together. Now: a low casting with its nameplate let in and a
chrome trim, rubber feet, and one rubber-topped treadle tilting on its axle
(toe on the right down at the top of the range), rocked by a sideways drag;
the toe switch a nub under the toe. The wah gets an iPad FX-row face: the
pedal and its sweep across the row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
render listed tokio from the workspace — `full`, net and all — and used
none of it. As a path dependency of session's page build, that put
tokio's net (mio) onto wasm32, where it does not build: the browser
remote had not compiled since. Nothing in render's code, tests or
examples names tokio.

Claude-Session: https://claude.ai/code/session_01WYrYRZB6AGrkJoofKycdaZ
theme-runtime gave every Time machine its own phone face and back, tall
faces for a lane column, and the faces' heads with their own bypass;
fleet-rc7 gave them the iPad pane tier, the Digital delay, routing-grid
block faces, live streams and the touch EQ panel. The merge keeps both:

- a Time entry carries its tall pair and its pane faces; every delay and
  reverb machine's small face is its own (the placeholders are gone);
- the analog / tape / plate / pre-mod nameplates sit in the heads with
  the pane's sizes; tall faces and block faces both stay;
- phaser, flanger and rotary are theme-runtime's machines (desktop, back,
  phone) with an iPad pane too, laid out on fleet-rc7's knob row; its
  inline lane-only versions go;
- the EQ draws the touch panel on a touch surface, the fixed / tinted
  band panel otherwise;
- one Hall small face (theme-runtime's), the 480 with its pane.

faces.json, the catalog and every face are regenerated from the merged
code, not hand-merged: only rig-faces changed, every other plugin's faces
came out byte for byte. The 480 binding test compares against the
algorithms' faces, not the routing grid's block cells.
frame is only used here, so it lives here now: frame/, a workspace member
like the others (37 crates). It comes from frame's merge of fleet-rc7 into
theme-runtime (2dae10b, local branch merge-fleet-rc7): signal's iPad faces
and live streams and session's theme runtime and tall faces, one frame.

- The workspace's frame deps are path deps on frame/, not on a checkout
  beside the repo; signal-guitar-ui takes frame-live from the workspace
  again.
- frame's crates keep the versions they were built with where the
  workspace's differ (vello 0.10, anyrender from git, chrono with serde,
  image's defaults), written into their own manifests.
- The faces (frame/examples/plugins, 130 MB generated) are not in git:
  `just faces` writes them, byte for byte; session-daw and signal read
  them there.
- The 35 MB 1176 render-parity scenario is in studio-assets (just assets).
- library-fs takes an architect EventSink (the subscribe API it was
  already behind on).
- imported.txt follows ../frame merge-fleet-rc7; catch-up never carries
  the generated faces.

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@5052b17
Phase 1 of the reorganisation: crates move, none merge. The five folded
repos' directories are gone.

- apps/      session/{desktop,daw,daw-web,web,cli,player,vault-sync},
             signal/{desktop,web,mobile,cli,convert}, patchbay, frame,
             plugins/ (every plugin shell and the CLAP host)
- crates/    the shared core: daw (with the standalone engine), audio,
             sync, audiocore, midicore, input, dsp, ui, util, keyflow
             (until it goes home), frame
- features/  one directory per feature — dawfile, reaper, the FX (eq,
             comp, delay, reverb, saturate, ...), the rigs (guitar,
             drums, keys, synth, ...), signal, session, expression-editor,
             daw-ui, faces, patchbay, ...
- vendor/    every patched upstream, in one place
- xtask/     the REAPER test runners, the plugin bundler, frame's xtask
- docs/<repo>/ and legacy/<repo>/ hold what the old repos kept outside
  their crates, to fold into the root's (legacy/README.md).

Every manifest path, workspace member and patch, the code paths that
reach across crates, assets.txt (and studio-assets, re-laid to match),
.gitignore, the Justfile, nix and CI follow. features/faces/generated is
where `just faces` writes now.

So the whole workspace's tests build and run in one go (they never had):
- plugins export their CLAP/VST3 entry points only for `just
  plugins-bundle` (--features export); in one workspace build cargo
  unified the export on into builtin-fx and its tests, three
  `clap_entry`s in one binary;
- the vendored wgpu-hal names no examples it does not carry;
- signal's tests call their compat set_ext_state explicitly, fx-blocks'
  tests use their own crate's name, rig_dump fills Compositions::tones,
  daw-standalone's plugin_render example defines pump_run_loop once;
- the ableton round trip reads the fixtures it links to.
neural-amp-modeler comes from its v0.1.1 tag rather than a checkout
beside the repo: nothing outside studio is read any more.

Tests: 11,385 run, 11,324 passed. The failures traced are older than this
(processor's delay / saturate references, frame's viz count, a stale
.session-is-a-directory check) or need data this machine's checkout lacks
(keyflow's MusicXML corpus, sample libraries, the user's rig library).

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@5c27a4f
…ature

- crates/  daw (with its audio graph, allocator, utils and test macro),
           frame (its desktop / CLI / MCP / library server as tools/),
           keyflow (until it goes home)
- features/audio    audio IO, sample, audiocore core, the web worklets
           midi · input · sync · formats (was dawfile)
           adapters/reaper · control-surfaces (its own feature)
           dsp/{core, eq, comp, delay, reverb, ..., builtin}
           rigs/{core, guitar, drums, keys, synth, sampler, nam, ...}
           ui/{daw-ui, themer, widgets, theme, meters, ...}
- apps/    session · signal · patchbay · plugins

Crates move, none merge; manifests, cross-crate code paths, assets.txt
(studio-assets re-laid to match), the Justfile, nix and docs follow.

Tests: 11,385 run, 11,326 passed; the same failures as before the move,
less the three it fixed, plus one that is flaky under load and passes
alone.

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@df6b35e
A folded crate's tests and examples need the features their modules sit
behind. A crate-level self dev-dependency turns those features on for
its own test builds, so a plain workspace test run builds them all again
(1,471 tests had silently stopped running).

formats' fixtures now live at tests/fixtures/<format>, the same layout in
studio-assets. String literals that named a module path (tracing
targets, probe names, a checklist doc) name the crate again rather than
`crate::`; the webview seam check looks for the feature by its folded
name.

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@5612283
frame's libraries are one crate at crates/frame, by layer: the
document, surface, motion and feed protocols (and the theme expression
language) always; the runtime, render, theme, io, library, figma,
design, live and embed layers each behind a feature, as nested modules
(frame::document::proto, frame::surface::runtime, frame::render::canvas,
...). The tools (cli, desktop, mcp, library-server) stay binaries at
crates/frame/tools. REFERENCE.md sits beside document::text and KIT.md
beside surface::kit.

frame-live painted into a host's anyrender 0.11 scene while render uses
anyrender's git main: the two shared a dependency key, so the 0.11 pair
now has its own (anyrender_0_11, anyrender_svg_0_12), and the three
per-crate anyrender-0-11 features are one.

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@b15eb7d
…workspace

dx (krates) panics on a renamed dependency whose source a [patch]
replaces — `anyrender_0_11 = { package = "anyrender", version = "0.11" }`
resolved to the blitz fork the workspace patches crates.io anyrender
to — and with it every dx build in the workspace failed at the metadata
step. The dependency now names that git source itself.

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@dad7add
Guitar rig: builds into a Rig like the others — open / open_with keep
their signatures, open_in joins a given engine, open_offline runs on an
engine with no device; its output meter is read by track guid, its
output trim addresses the rig's project. Rigs on a shared engine remove
their tracks when dropped (GuitarRig, SamplerRig's last handle,
KeysRig).

One engine per process: signal_rig_host::engine::install_shared makes an
engine the process's, and every rig opened afterwards joins it — the
guitar rig (re)opens the device with its own audio settings
(restart_audio), the others open it only if nothing has (ensure_audio).
signal-desktop installs one at start-up, in --engine and in-process
alike, and serves that engine's daw over /vox beside the rig services, so
a daw client sees the rigs as the tracks they are. A rig on its own
engine still rolls its transport (Engine::play); a shared engine's
transport is the app's.

MIDI: the process's one hardware input (rig-host's midi_hub) and the
attach wide event move to features/midi as midi::hub and
midi::telemetry. daw-standalone's midi-input feature routes it into the
tracks armed with a MIDI record input (Standalone::route_midi_input), and
Session's engine keeps that route with whichever song it has attached —
Session plays instruments from a keyboard. Control surfaces keep their
own device pair: the hub merges devices and does not say which an event
came from, which a surface needs.

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@0ba6dc1
legacy/ is folded into the root Justfile, CI, nix and docs and deleted;
CLAUDE.md and README are rewritten for studio. Paths that pointed into it
or at the old sibling layout are fixed: the REAPER config, both REAPER
test runners, frame's parity scripts, the dev opt-level allowlist (11
names were stale, so those audio crates built unoptimised), FTS Mic in the
bundler.

Duplication removed:
- app-shell: Session's and Signal's log ring, crash log, flight log and
  update check are one crate; prefs go straight to utils::prefs. The
  update check reads the studio repo's GitHub releases.
- Loudness: dsp-core's BS.1770 K-weighting and gated integrated LUFS are
  the one implementation; the sampler and the analyzer use them.
- The sampler's DrumMixer is gone: a kit's mixing is the daw's tracks.
- Tempo: the rigs' tempo is the engine's transport tempo (lock-free read
  on the audio thread).

Also: the realtime clippy rules apply under features/{dsp,rigs,audio}
only; inferno-control and nih-plug come from GitHub; every Tailwind input
points at the real crates (Session's sheet did not build) and scans
architect-ui through a resolved symlink; the golden media writer replaces
files atomically, so parallel tests no longer SIGBUS on a mapping.

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@6f0a34c
Step 1 of the agreed reorganisation (docs/design/session-daw-split,
docs/design/timeline.md for the next steps):

- features/signal is Signal's domain (was rigs/core/*, plus macromod);
  features/rigs holds the instruments, with rigs/host and rigs/proto for
  the shared rig plumbing. sampler, plugin-host and dsp/nam move out of
  rigs/; dsp/analyzer, dsp/separator, dsp/align are flattened; plugin
  faces live under ui/; app/{shell,launcher}; kit-sheet goes to xtask.
- adapters/<host>: formats is split into adapters/protools, ableton,
  logic and interchange (AAF, DAWproject), with their tests, examples and
  fixtures; the daw's own `.session` file and its store become
  crates/daw/session-format. dawfile-reaper and the dynamic-template
  extension join adapters/reaper; the web AudioWorklet is adapters/web.
- adapters/reaper: the forked overlay crates are one again — reaper-dioxus
  and reaper-embed are the daw-* forks (Retina sizing, dock host,
  snapshots) with the other fork's shared GPU device + prewarm and the
  overlay's start_hidden ported in.
- expression-editor is `editor` (task's unused `editor` entry dropped),
  grouped into model/, pitch/, drums/, harmony/, interact/; the chords
  crate folds into harmony/chords (it duplicated harmony's ChordGun).

Paths that pointed at moved crates are rewritten (manifests, include
paths, fixtures, asset links with their repo paths kept, .gitignore, CSS
sources, the generated faces, a tracked fixture symlink).

Claude-Session: https://claude.ai/code/session_0147CDF47Ra4UTu52kXSjuaB

Studio-commit: FastTrackStudios/studio@11e12c7
…een (FastTrackStudios/studio#174)

## Summary
FastTrackStudio pivots to one app, **Studio**. Signal's app crate moves
to `apps/studio/desktop` (`studio-desktop`, bundle
`app.fasttrackstudio.studio`, the existing "Studio - by FastTrackStudio"
App Store record) and links Session's app as a library
(`session-desktop` now has a lib; feature `session-app`). The window
opens on a front screen (`src/front.rs`) that enters Signal or Session;
a launch runs one side, so one engine (`FTS_STUDIO_OPEN=signal|session`
skips it). The iOS scene delegate moves into `app_shell::ios_scene`
(`StudioSceneDelegate`). New app icon (desktop + iOS), generated with
Codex.

Shipping: `studio-ios.yml` is the one TestFlight upload;
`signal-ios.yml` and `session-ios.yml` are gone, and `release.sh` (+ its
sandbox) ships the one app. CLAUDE.md,
`docs/agents/release-workflow.md`, the airlock skill and the dev-shell
banner follow. The fleet skill was updated to match
(FastTrackStudios/fleet-skills#25).

## Issue
Refs: none

## How tested
- `cargo check --workspace`: green. `just web-wasm-check`: green.
Lockfile, `cargo fmt --all --check`, `cargo rail change check`: green.
- `cargo check -p studio-desktop --target wasm32 --no-default-features
--features signal` (the advisory Signal browser-remote check): red on
`rig_view.rs`'s `libraries_view` reference, which is untouched by this
PR.
- iOS: `dx build --platform ios --device --release` with the studio-ios
feature set built; `deploy-testflight.sh` signed it with the Studio
profile and uploaded it (build 1792716084, marketing 0.1.2, manual test
upload). Info.plist carries `StudioSceneDelegate`, display name Studio,
the new icon and frame's faces.
- Not yet run on a device or simulator.

## Risk and rollback
Session in Studio's window on a desk: the window opens with the system
title bar (Session's style); Signal's side removes it at runtime and
draws its own. On iOS, Studio keeps Signal's launch settings (iPhone
landscape only, `BLITZ_SAFE_AREA=0`), which now apply to Session too.
Revert the PR to restore the two apps.

## Change files
- `.changes/added-studio-the-one-b46a.md` (studio-desktop,
session-desktop, app-shell: minor)
- `.changes/pointed-comments-and-the-9b71.md` (nam-plugin,
signal-mobile: none)
- `.changes/sta-266-…` renamed its `signal-desktop` key to
`studio-desktop`

## AI disclosure
Tool: Claude Code (+ Codex for the icon). Extent: wrote all. Human
reviewed: no.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Cody Wright <acodywright@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

Studio-commit: FastTrackStudios/studio@729ca0c
…ong them (FastTrackStudios/studio#184)

## Summary
The nextest run on main had 94 failures and 34 timeouts (run
39905030943), unnoticed because the tests job was advisory. They fell
into four kinds:
- **Missing data:** the private assets, the generated plugin faces and
the mic models.
- **Machine:** no GPU on the CI VM, and timeouts under load.
- **Stale tests.**
- **Real bugs:**
- **sampler:** document mode played no legato transitions, its off-grid
shorts spoke 14 ms late and it started documents in the wrong
articulation; a note-on also allocated on the audio thread.
- **web rig:** a stomped-on pedal played the dry guitar, and wasn't
seamless when its worker fell behind.
  - **eq:** a clippy rewrite broke the spectral bell's math.
- **stretch:** the run-up to a hit was smeared early, so quantized hits
landed 2–3 ms off.
- **Real-time allocations:** the Velvet reverb's tap list and PSOLA's
pitch detector allocated on the audio thread, as did a rebuilt plugin's
first render on macOS.
  - **session:** a setlist's later songs drew no waveforms.
- **dynamic-template:** its checks looked for Process in the wrong
place.
  - **frame:** the gate visualiser had a self-referencing slot.

Golden DSP vectors now compare within a float tolerance instead of
bit-exact hashes, which can't hold across x86_64 and aarch64. Three
deliberate DSP changes, made months ago without re-recording, now have
fresh references; the commits are cited in `8d1f5282`.

## Issue
Refs: STA-184. Follow-ups filed:
- FastTrackStudios/studio#178: blitz-fork click and hit-testing bugs; 2 tests ignored.
- FastTrackStudios/studio#180: on macOS, Cmd+N is bound to both comp-take and window-set; 1
test skipped on macOS only.
- FastTrackStudios/studio#182: the Velvet reverb costs a full core per instance.
- FastTrackStudios/studio#183: a load-only `HITS_PLACE` race; 1 test runs alone.

## How tested
- **Full workspace suite** on macOS aarch64, with the CI feature set
(`--workspace --exclude winit-uikit`; that crate is iOS-only and leaves
the workspace with the vendor move): **10,957 / 10,959** passing in the
last run. The two that failed then were load-sensitive and are handled:
kit-sheet's knob shot gets a 60 s timeout, and `edit_restore` runs alone
pending FastTrackStudios/studio#183.
- **Stress runs** for the timing-sensitive fixes:
  - worklet stomp: 30/30 alone and 45/45 run three at a time
  - `media_fetch`: 4/4
  - `midi`: 3/3
  - `edit_restore`: 5/5
- **Not run here:** Linux and lavapipe. The golden tolerances and the
scene-picture rule are built for exactly the differences seen in the CI
log, but this PR's CI run is their first test on Linux.
- `cargo fmt --all --check` is clean, and the lockfile is unchanged.

## Risk and rollback
- **Live behaviour changes:**
- Libraries with a legato engine but no vibrato controller, such as
Cinematic Brass, now play legato transitions.
  - Stretched hits now play their run-up as recorded.
  - EQ's spectral bell now reduces only inside its band.
- **Not run here:** the orchestra legato tests, which only run with the
CSS library installed.
- Everything else is test-only. Revert is the rollback.

## Change files
`.changes/`:
- **patch:** sampler, web-audio-worklet, stretch, daw-standalone, eq,
reverb, pitch, modulation, frame, dynamic-template.
- **none:** mic-plugin, dsp-core, signal-browser, signal-live.

`cargo rail change check` passes.

## AI disclosure
Tool: Claude Code. Extent: wrote all. Human reviewed: no.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Cody Wright <acodywright@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

Studio-commit: FastTrackStudios/studio@a18fad5
## Summary
The two flaky tests, carried into the stack from FastTrackStudios/studio#192 and FastTrackStudios/studio#191
(cherry-picked with `-x`), so the stack's own Local CI run is stable.
- `frame library::link::tests::server_in_process_client_subscribes`
accepts the update and the index change in either order. It failed 5
times in 40 runs before the fix and 0 in 40 after.
- The EQ editor's typing tests wait for every keystroke to land.

Supersedes FastTrackStudios/studio#192 and FastTrackStudios/studio#191.

## Issue
Refs: none

## How tested
`Local CI` on the stack top: 10,947 tests passed. The frame test was
already stress-tested in FastTrackStudios/studio#192.

## Risk and rollback
- Test-only.
- Revert to roll back.

## Change files
.changes/frame-link-test-order-3c1e.md (frame, none),
.changes/the-eq-editor-tests-8923.md

## AI disclosure
Tool: Claude Code. Extent: wrote all. Human reviewed: no.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Cody Wright <acodywright@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

Studio-commit: FastTrackStudios/studio@f0eb1b7
## Summary
This cuts the test suite to a core set: **11,317 tests in 815 binaries
become 648 in 39.** The suite cost more than it bought, because about
800 test binaries got built on every run, and that's why CI needed big
paid runners.

**Kept:**
- `dsp-core` (184): the shared DSP math.
- `session-format` (120): project files, where data safety lives.
- `daw-standalone` unit tests (118): the engine core.
- `timeline` (99), `daw-proto` (56), `studio`, `daw-transport-sync`,
`utils`, `daw-control`.
- Every effect's `param_round_trip`, which CLAUDE.md requires.

**Removed everywhere else** (not `vendor/`):
- Every `#[cfg(test)]` module: 1,295 of them in 1,152 files, plus the
file behind each `#[cfg(test)] mod x;`.
- Integration tests outside the set, their `[[test]]` tables and
orphaned `tests/` helpers: 681 files.

Other `#[cfg(test)]` items stay, because they're test-mode variants of
production code, not tests. The non-test build is unchanged, and fixture
data stays.

**Follow-ups in the same PR:**
- `just test`, `just ci`, local CI and the churn check build the
Justfile's `test_packages` (the crates that still have tests).
- `nextest.toml` loses the overrides for removed tests. Recipes that ran
removed tests are gone: the fx and editor screenshot shots,
`daw-scenes`, `daw-folder-items`, `ee-practice-test` and `drums-bench`.
- The flow gate (FastTrackStudios/studio#48) lost the verifying tests for 39 implemented flow
rules. They're listed in its exemptions file with that reason, so the
gate still holds for every new rule.

## Issue
Refs: none

## How tested
- `cargo check --workspace --all-targets` with the full feature list
passes.
- The core set runs 629 tests, all passing, in 17–19 s.
- `just daw-flows` passes.
- `Local CI` is green: 7m44s.

## Risk and rollback
- **Lost coverage:** about 95% of the regression tests are gone. FastTrackStudios/studio#184's
suite found 12 real bugs, and this coverage goes with it. The deleted
tests stay in git history.
- **Rollback:** revert.

## Change files
.changes/the-test-suite-was-e642.md (92 crates, none)

## AI disclosure
Tool: Claude Code. Extent: wrote all. Human reviewed: no.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Cody Wright <acodywright@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

Studio-commit: FastTrackStudios/studio@b79cf34
…ndency once (FastTrackStudios/studio#210)

## Summary
Faster clean builds, from the dependency graph:
- **No aws-lc.** Its build script, a cmake C build, took 186 s of a 375
s clean `cargo check --workspace`, all of it on the critical path. One
crate asked for it: `tone3000` 0.1.0, through reqwest 0.13's `rustls`,
whose only crypto backend is aws-lc. `vendor/tone3000` is the published
crate on reqwest 0.12's ring-backed `rustls-tls`, which is already in
the graph. There are no source changes and its 32 unit tests pass
(`FTS-PATCH.md`). Clean check: 6m15s → 5m29s.
- **12 fewer duplicated crates** (Cargo.lock: 203 → 191 crates with more
than one version):
- eq-standalone took manganis / dioxus-signals 0.7 from crates.io, which
dragged in the whole dioxus 0.7 core stack.
  - reaper-launcher moves to icns 0.4.
  - The workspace skrifa is now 0.42.
  - ableton moves to quick-xml 0.41.
- **Every external dependency declared once** in
`[workspace.dependencies]`: 281 declarations in 72 manifests. Left as
they were: declarations the workspace entry would give extra features
(tokio `full`, uuid, image, …) and the graphics stack's split versions.
- **A guard.** `scripts/check-duplicate-deps.py` fails when Cargo.lock
gains a crate version beyond `.config/duplicate-deps.toml`. It runs in
CI's Gates job, `just ci` and `just deps-dupes`.

## Issue
Refs: none (build-time work)

## How tested
- `cargo check --workspace`
- `--all-targets` with `just test`'s features
- all four wasm checks (`session-daw-web`, `session-web`, `signal-web`,
`studio-desktop --features signal`)
- the duplicates check
- vendored tone3000's tests (32 pass)
- `just ci-local` on the top of this stack

## Risk and rollback
- **tone3000 HTTP:** the client now runs on reqwest 0.12. Its
mocked-HTTP tests pass; a real TONE3000 sign-in and download hasn't been
exercised by hand.
- **Workspace inheritance:** no declaration gained features or changed
version. Ones that would have are skipped (the script's report is in the
commit message).
- **Rollback:** revert.

## Change files
`.changes/dedupe-deps-*.md` (`--bump none`: no user-visible change)

## AI disclosure
Tool: Claude Code. Extent: wrote all. Human reviewed: no.

Studio-commit: FastTrackStudios/studio@f6f3227
Stacked on FastTrackStudios/studio#354.

## Why
kache 2.0 reuses incremental state across worktrees only on rustc 1.98
or newer. The user also wants the newest stable compiler.

## What
- **The pin moves to `stable."1.99.1"`** (`nix/modules/toolchain.nix`).
`rust-overlay` is now its own flake input, `oxalica/rust-overlay` at
6ecf534a: `dioxus-flake`'s copy stops at 1.94.1. No other input moved.
- **The nightly is pinned by date** to the same one as before,
2026-04-05. It's only used for phon-jit's stencils and the wasm-threads
worklet.
- **`rust-overlay-rail` is removed.** cargo-rail now builds with the
workspace toolchain; it only needed its own because rail required rustc
1.98.1 or newer.
- **CI:** `xtask/workflows` `RUST = "1.99.1"`, and the workflows are
regenerated.
- **The two warnings 1.99 adds are fixed:**
- `AtomicU32::fetch_update` → `try_update` (session-engine `stream_set`,
frame `level`);
- `ambiguous_import_visibilities` in `patchbay_service`: explicit
imports replace `use super::*`.
- Docs and comments updated from 1.94 to 1.99.1.

## Testing (on 1.99.1)
- Dev build of both bins: pass, 7m 52s cold.
- `just check`: pass.
- Wasm checks: pass.
- `just test`: 766 passed.
- `just workflows-check`: pass.

## Note
Every cache key changes with the compiler, so the first build after
merging is cold. Some `#[must_use]` on constants in plugin-faces will
become hard errors in a future compiler, and are worth a follow-up.

Co-authored-by: fts-forge[bot] <345159799+fts-forge[bot]@users.noreply.github.com>

Studio-commit: FastTrackStudios/studio@0e8dcba
The root Studio gave Frame, now here: the workspace (frame, its tools,
plugin-faces, xtask), the dependencies at the versions Studio pinned at the
cut (studio@fc6b1954: architect v0.9.31, vox 0.10.0-rc.5, Blitz
fts-pointer-2026-10) and the patches of Studio's that reach Frame's graph,
with Studio's Cargo.lock as the starting lock.

- Rust 1.99.1 in rust-toolchain.toml and the flake's dev/ci shells (same
  nixpkgs and rust-overlay revisions as Studio), with mold and the Vulkan
  loader.
- .cargo/config.toml carries Studio's rustflags tables in Studio's order,
  so kache keys shared crates the same in both repos.
- The paths that named Studio's layout: plugin-faces' path to frame,
  xtask/frame/scripts -> xtask/scripts, the xtask's repo root, the faces'
  output (examples/plugin-faces/generated).
- Every crate MIT OR Apache-2.0, version 0.1.0.
- Lints: the rust lints as Studio had them and clippy's defaults, enforced.
  Studio's pedantic/nursery denies were never met by Frame (its clippy ran
  advisory) and are left out.
Mostly clippy --fix (collapsible ifs, auto-deref, manual impls). By hand:
- wgpu_preview's frame-time buckets no longer overlap;
- stray #[must_use]/doc pairs left on constants where functions moved;
- feed::dsp's doctest names frame:: (Studio ran nextest, which skips
  doctests, so it never compiled);
- Prop/Op keep their large variants (allowed): boxing them is a runtime
  performance call for frame#18;
- three test helpers whose tests Studio cut (studio#203) are allowed dead.
The repo is private, so every job runs on the self-hosted pool, which sets
RUSTC_WRAPPER=kache over the host's shared store. No RUSTFLAGS in the
workflow: it would replace .cargo/config.toml's tables. `ci` is the one
status to require.
Studio kept it in studio-assets; the parity scenarios move with Frame.
Its files are byte-identical to 2dae10b's, so this adds no new objects.
Until Studio depends on Frame by tag (FastTrackStudios/studio#373): each
Studio commit after the newest Studio-commit trailer that touches Frame's
paths is cut to them, renamed into this layout, applied with git am -3 and
given its own trailer. Checked by replaying studio@0e8dcba4 onto its parent
here: the result's tree equals the imported commit's.
Frame is open source under MIT OR Apache-2.0 (Cody, frame#2). The README
describes the one-crate layout, the recipes and where the history came
from; CLAUDE.md replaces the old beads AGENTS.md (removed in Studio).
@fts-forge
fts-forge Bot merged commit 7ce3945 into main Nov 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consolidate Frame into FastTrackStudios/frame, with its history

1 participant