v0.30.0 — R24: the data-model round (triage finale)
Round 3 of 3 of the seventeen-item feedback triage — all seventeen items are now closed at the code level. Full ledger: docs/ROADMAP.md 当前状态 first entry.
What's new
- Version names & provenance (#8): name your snapshots, see which variant each came from and whether it was saved by you or auto-archived; '+ Save as version' lives on the variant card. A name dies with its number and is never inherited (contract-tested).
- Apply to Original (#7): copy a fitted variant's develop onto the Original card — pixels stay, source card stays, one Ctrl+Z undoes.
- One owner for the strip's active slot (#1): every develop writer (Ctrl+S, Analyze, reverse-fit, CLI match, web save, batch paste) now states what it knows through one primitive — a reverse-fit no longer reopens as '▣ Original'.
- Five-tier control registry + delivery root (#15b/M8): what renders vs what exports is now a compile-enforced fact with derived disclosure in all three directions; the ./out folder is a real setting (Settings → Delivery folder) with read-only-library warnings.
- Portrait RAWs render upright (#17, the real fix): rawler hard-codes orientation to Normal upstream ('// TODO fixme' in its source), so every portrait ARW/CR3/NEF was sideways in thumbnails, canvas and exports since v0.11 — DNG being the lone exception. Fixed via the EXIF tag directly; existing portrait edits migrate automatically (coordinates rotate losslessly on first load, disclosed by toast; raster masks are disclosed, not silently turned).
- The joint ladder is final: recalibrated on six real same-frame pairs from an actual library. The pair that produced a 'completely nonsense' fit now raises the joint warning and caps at floor confidence (it used to read 0.578 and stay silent); the cropped-reference warning now also caps confidence at 0.5 (a measured number: framing alone can manufacture ±23 saturation).
BREAKING (app-internal): a v0.29 exe cannot read recipe.json files saved by v0.30 (new coord_era field under the established deny_unknown_fields forward posture). XMP sidecars are unaffected.
Also: AI Denoise now disables itself with a localized explanation when the python sidecar isn't shipped (instead of failing in English after the click); error messages no longer leak internal Rust symbol paths; Analyze shares Ctrl+S's refusal semantics on an unresolved variants.json (deliberate consistency).
Gates: clippy 0 (both configs); 543 lib + 8 CLI + 114 GUI + 2+2 contract tests green (both configs); audit_i18n 0.