* Build: line tables only for workspace crates, no debug info for dependencies
Full debug info made most of the dev and test target size and link time. Line tables keep file and line numbers in backtraces; dependency frames lose them.
* Tests: build each crate's integration tests as one binary
Each tests/*.rs file was its own test binary: 98 binaries, each linking the crate and its dependencies. The files are now modules of tests/it/main.rs, one binary per crate, which cuts link time and target size. Filter by module: `cargo test -p <crate> --test it <module>::`.
Binaries with their own main (harness = false) in clap-host and vst3-host stay separate, as do the ui-egui tests that switch the process-wide Light/Dark palette and recovery.rs, whose autosave locks leak into child processes other tests start.
* Xtask: run tests with nextest, doctests apart, and no examples in the test build
cargo xtask ci runs the tests with cargo nextest when it is installed, else cargo test with a notice, and doctests with cargo test --doc. The test build leaves out examples (--lib --bins --tests); clippy --all-targets still compiles them. Test binaries with harness = false run under cargo test: their own main cannot answer nextest's --list. clippy and the wasm check keep going past a failing crate. cargo xtask test runs the test step alone.
soundcraft-cli convert always passed dither: true, so a same-depth or
wider conversion still added ±1 LSB TPDF noise and digital silence came
out as noise. It now dithers only when writing integer samples that
cannot hold the source exactly: fewer bits than the source, a float or
lossy source, or samples changed by --rate/--normalize. 32-bit float
output never dithers. --dither and --no-dither override the choice,
like the Bounce option in #551.
`convert`, `run` and `script` read options with `arg_value`, which matches a
flag exactly and takes the argument after it. A misspelling, an `--option=value`
form or an option with no value therefore read as absent: the command ran
without what was asked for and still exited 0.
convert IN OUT --rate=44100 wrote IN's 48000 Hz unchanged
convert IN OUT --bitdepth 16 wrote the default 24-bit
convert IN OUT --bogus wrote the file, said nothing
run --cmd session.new --bounce=OUT bounced nothing
Check what each subcommand accepts and report an unknown option or a missing
value with a non-zero exit, and read `--option=value` as the value it looks
like. The documented space-separated form is unchanged.
convert ignored a --rate that did not parse and mapped any unknown
--bit-depth to 24-bit, then reported success. Both options are now
validated before the input is read; an invalid value prints which
option was wrong and exits non-zero without writing output. Omitted
options keep their defaults.
Keep the never-overwrite writer, and also give sources that share a path
their own files (#123). Save Copy In checks replace, then uses
save_session_copy (#124).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save As, Save Copy In, Save As Template and Export Selected Tracks as
New Session wrote over any existing file without asking. The Save As
prompt proposes ~/<name>/<name>.scraft, so every new session suggests
~/Untitled/Untitled.scraft, and saving a second new session with the
suggested path replaced the first one.
These commands now refuse to write over an existing file unless
`replace` is true; Save, Save As and Save Copy In may still write the
open session's own file. Scripts, MCP agents and the control channel
can't replace a session by accident. While the path in the prompt names
an existing file, the prompt asks whether to replace it and its OK
button becomes Replace; Enter alone doesn't replace. Saving also
creates the session's folder, so a new session without audio can be
saved to the suggested path. The CLI's --save names its output
explicitly and keeps replacing it, like --bounce.
* Progress docs to the craftrules standard; full re-measure against Pro Tools 2026.4.1
Stage: alpha. Menu breadth 482/512 (94.1%, measured), feature breadth ~70%,
ready for real work ~40%; ~350-570 agent-hours to beta, ~950-1,450 to full parity,
calibrated on this repo's PR history.
- ROADMAP.md rewritten: stage banner, two numbers, dimension/feature/language tables
- docs/target-app-parity.md: authoritative assessment (area table moved from ROADMAP)
- docs/gaps.md: 22 ranked gaps with evidence and estimates
- docs/roadmap.md: milestones, current focus, beta plan
- docs/ui-, file-format-, hardware-, plugin-, localization-parity.md: new checklists
- docs/parity.md -> docs/parity-checklist.md (generated by cargo xtask parity)
- architecture.md: video, vst3-host, au-host crates; device I/O limits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stage: pre-alpha under the core-workflow gate
Add the alpha gate to docs/roadmap.md: interface recording at a chosen buffer
size, MIDI recording, and session interchange with Pro Tools fail, so the stage
moves from alpha to pre-alpha (~5 points and ~110-180 agent-hours to alpha).
Mark the alpha blockers in docs/gaps.md; update ROADMAP.md, target-app-parity.md
and README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add mainstream-practitioner and essentials-user readiness numbers
Mainstream practitioner ~36% (weekly-area depth 50.6% x 0.90 interaction x 0.92
stability x 0.85 file exchange) and essentials user ~57% (core-feature depth
72.4% x 0.90 launch/stability x 0.90 discoverability x 0.97 files), with written
weights, discounts and GitHub user evidence. Full ready-for-real-work checked
against its written dimension weights (40.6%); unchanged at ~40%, stage unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Readiness by audience: hours to ~95% for full, mainstream and essentials
Full ~40% / 950-1,450 h, mainstream ~36% / 380-600 h, essentials ~57% / 110-200 h,
calibrated on this repo's PR history. The full number is already an additive
weighted sum over the written dimension table; method unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
app used to leave every ui., engine., session., and app. method untouched, so engine.history, ui.theme, and session.new were sent as unknown control methods. Only the real control-channel methods stay methods. Every other dotted id is engine.execute.
outln! treated a failed write as success and returned immediately, so run and script stopped before --bounce or --save whenever a pipe reader had gone away. A write error is now ignored and the exit code comes from the work itself.
Testing on macOS showed that the exit-handler fallback comes too late for a
plugin that has been used: with HALion Sonic inserted on a track, its bundleExit
segfaults, and both the app (on quit) and soundcraft-cli (after a bounce) exited
with 139. A plain scan was fine. Exit handlers run in reverse order of
registration, so state the plugin set up while in use is presumably torn down
before the host's handler runs.
soundcraft_vst3_host::shutdown() runs the module exits up front, and
soundcraft_engine::shutdown_plugin_hosts() calls it. The app calls that once the
event loop has returned (the player and its plugin instances are gone by then),
and the CLI once its subcommand has. The exit handler stays as the fallback for
other exits, such as test binaries.
The fixture can now set up state lazily the way such plugins do
(VST3_FIXTURE_LATE_STATE), and module_exit_shutdown checks both sides in child
processes: with shutdown() the exit is clean, with only the exit handler the
fixture reports the late teardown. Contract violations now end the process with
status 3 instead of an abort, so that expected failure leaves no crash report.
Refs #16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>