NC Audio · Docs

Audio — egui 0.37 migration plan

Current qualification — 2026-10-25

The local implementation incorporates main through 541096d2cb5f96a13d5b58935e45711530471480. Managed job egui037-sound-main541-ci-20261025-0857 passed all seven stages: formatting, strict workspace lint, tests, doctests, assets, layering and WASM. Across 52 test summaries it recorded 1,420 passing executions, zero failures and one ignored.

The manifest and lock use canonical Git URLs and immutable revisions: egui 6d518fc799681571963842f95b55a6b68431b15a, winit a517b3846ee8457d84ee4975110bfda6797c44dc, craft-ui 650b1c0b058017c4eb3af06d5b39586d6732e07b, and craft-docpath/craft-media 16fb3d46e1eaa0d455465e4e66a3ac7b17b55bd3. The craft-ui commit is available on GitHub; it includes the newer docking Workspace/DockCommand APIs required by this app. Local validation used the retained task-scoped Git mirror. Remote CI must independently qualify fetching the dependency graph.

The docking port preserves Audio's default side beside the session canvas when a saved panel anchor has disappeared. It probes recorded locations and hidden-anchor chains before selecting that fallback. Regression coverage includes a deleted anchor and a hidden floating chain. Earlier drag/float/close/reopen/reload GUI evidence qualifies the prior candidate, not this final main reconciliation.

Final-source native/browser GUI, X11/Wayland, Windows/macOS, audio/plugin hardware, packaging/licence coverage and paired app performance remain acceptance gates. Human review and human merge are required. Historical pins, counts and qualification claims below describe earlier states and are superseded by this section.

Historical planning record

Status: implementation in progress (local worktree only). The planning sections below remain the contract; the local implementation amendments at the end record the current source and dependency state. Nothing here changes a published branch, tag, PR or release, and the current path-only lockfile/local integration configuration is not a publishable dependency state. Every push or new PR requires fresh, explicit human approval after the exact contents and validation results are shown; nothing here grants that approval. No agent/model/tool signature is to be added when this text or its derived PR text is published.

Scope: the assigned checkout /home/codex/work/CraftApps/egui037-migration-20261022/audio only. The shared storytold/egui, storytold/winit, storytold/craft-libs, craft-ai-integrations and sibling applications are dependency/hand-off points, not repositories changed by this plan.

The plan is written against source, not against a generic migration template. Every claim names the file and symbol it comes from. Historical compiler output and old audit logs are labelled as clues, never as current acceptance.

1. Decision in one page

Audio is not starting from a 0.36 update-only application. It already uses the root-Ui lifecycle: apps/audio/src/main.rs:37-94 implements eframe::App::logic/App::ui, and crates/ui-egui/src/lib.rs:1136 / :1232 already take &egui::Context for logic and &mut egui::Ui for layout. Upstream 0.36.2 has the same App::logic/App::ui shape, so this migration must not rewrite the app into update or introduce Context::root_ui bridges.

The committed dependency state is nevertheless inconsistent: Cargo.toml:49-52 asks for egui, eframe and egui_kittest 0.37 while craft-ui is pinned at 5c874d51…, whose egui dependency is 0.36.2. Cargo.lock therefore contains two egui families (egui/ecolor/emath/epaint 0.36.2 and 0.37.0). The first deliverable is one coherent graph and one coherent lifecycle, not another version bump.

Target release state:

  1. One immutable egui family from the complete reviewed/published storytold egui P8 hand-off and one immutable completed storytold winit, each at a storytold-owned full SHA. P8 is the E1–E21 base plus P2b (E4 regular-clipboard/PRIMARY routing over the async W15 transfer path), P7 held-key release pairing and E21, P7b/E21b outgoing surrounding text, and P7c's selected #8763 selection-action and #8748 hidden-parent/deferred-child fixes, plus only justified performance fixes. Winit must contain W15P, the W27 lifecycle revision and the bounded W15 async transfer API (§5.1). aiongg/craft/0.37 = a4f9a17f64a6bae61350fb85804c9df0d9865916 and aiongg/winit craft/0.30.13-lang = 8b35388bd719e8cc86a95971ba0bc026c9a8f058 are research inputs at inspection, not publishable pins. If those organization-owned revisions are not reachable when implementation starts, wait for approved publication; never fill the gap with a movable branch, an invented SHA or an incomplete stage. Stock 0.37 is only a measurement control, not an intermediate production stage.
  2. craft-ui and craft-docpath from one reviewed storytold/craft-libs revision on the reconciled merge base 3832c47ef68a0e6214f2c1e85f3526b6adcb686e, which carries the intended egui-API port. The historical PR #199 hunks (8614e4a, README 1764bca) and the earlier 4919088 main inspection describe how that base was assembled; they are no longer the base to apply. The stale PR head b68de8c5f4f1c060904d211a7b1320aa0f7bce78 and audit merge ec8840af8c5fda695cf195cdd05e5ae078415c07 must not be merged as trees: they omit current craft-platform and revert security/settings/docking/printer fixes. The exact reachable immutable publication SHA remains a central-publication prerequisite; it is not guessed here.
  3. Source changes limited to: the real compile-error classes (UserData/Event::Screenshot, MouseWheel.source, WidgetType→Role, private ScrollArea::State.offset), deprecations that clippy -D warnings will reject (Id::new, Ui::id), the native clipboard feature that 0.36 supplied implicitly, font/emoji feature and glyph-coverage fixes, accessibility names, and the lifecycle/input fixes in §6. No wholesale UI or command-layer rewrite.
  4. A staged implementation: atomic fork/shared-library/API compatibility including callback and lifecycle correctness, then deeper accessibility/input/fonts and platform/performance evidence. Each stage is independently buildable, testable and revertible. See §14.

The largest behavioural risk is not the Cargo update. It is the interaction between egui 0.37's multi-pass Context::run_ui, logic-only ticks (Context::run_logic / App::logic while hidden), and Audio's current per-frame side effects (control queue, autosave, transport, dropped files, screenshots). That work is specified in §6.

2. Baselines, exact revisions and evidence

2.1 Checkouts and baseline revisions

Role Revision / value Notes
Assigned Audio checkout (planning) 52471b37e957feaea56dfd48932ef16d314cc74a Historical planning HEAD; superseded by the local implementation update below
Historical coherent 0.36.2 Audio baseline e85a4801e9029c9a1f5ce536b11be65aea832ed8 (Release 0.12.1) Direct egui = "0.36", eframe = "0.36", egui_kittest = "0.36", craft-ui 0.36; one coherent family; the required performance baseline
Upstream egui 0.37.0 841a328ba2dd8f6996beb7189ebb7fc00e668a2a Peeled release commit; annotated tag object differs
Suite fork research input a4f9a17f64a6bae61350fb85804c9df0d9865916 aiongg/craft/0.37, movable branch; E1–E21; not a production pin
Suite winit research input 8b35388bd719e8cc86a95971ba0bc026c9a8f058 aiongg/winit craft/0.30.13-lang; W17–W27; movable branch; no W15P
storytold winit base ffae5198770d7730e718043f035a772b7dbc3b1b craft/0.30.13, W1–W15
Published storytold egui 0.36.2 767b1cad294120689a514bb4bd68c077e46679b9 Existing craft/0.36.2; not the 0.37 target
craft-libs reconciliation base 3832c47ef68a0e6214f2c1e85f3526b6adcb686e Current merged craft-ui/craft-docpath base for this migration; the exact publish pin is still a central-publication gate
craft-libs historical inspection 49190884278f16c9a9f5823d7698a8ffb5deb062 / PR #199 8614e4a + README 1764bca Earlier base and hunks; retained as history only. Branch head b68de8c…/audit ec8840a… are stale trees and must not be merged
craft-libs audit merge ec8840af8c5fda695cf195cdd05e5ae078415c07 Evidence only; not a target

The exact storytold/egui, storytold/winit and reviewed craft-libs revision after merge are blocking unknowns (§16). They must be supplied as full immutable SHAs before P1 manifests are changed.

2.2 Historical compiler clues (not acceptance)

/home/codex/work/ssd2/task-evidence/egui037-audit-20261022/{fork,stock}/audio.errors reports nine error classes from a trial that patched 11 crates.io packages and both craft-libs crates by local path:

The audit's path patches and source state are historical. They prove the API delta is small and show where to look; they do not prove the current tree compiles, and they do not cover tests or warnings that clippy -D warnings will reject.

2.3 Dependency facts in the committed lockfile

Cargo.lock currently contains:

A read-only offline cargo tree was attempted for planning and could not resolve the workspace because the local registry index lacks symphonia-common (required by symphonia-bundle-flac 0.6.1). The feature graph below therefore comes from manifests and the lockfile, and must be rechecked with network/cache in a managed job. This is not a build result.

2.4 Non-UI audio prerequisite follow-up

The symphonia 0.6.1 and cpal 0.18.2 prerequisite port was reviewed against the registries. Two audio-io defects and one playback semantic gap are repaired in source (no runtime claim here):

No build, test, formatter, clippy, device, VM or hardware run is implied by this subsection.

3. Current implementation inventory

3.1 Workspace, packages and feature targets

Workspace root: Cargo.toml:1-11 (resolver = "3", members crates/*, apps/*, xtask); workspace.package.rust-version = "1.90"; crates/ui-egui/Cargo.toml:7 already declares rust-version = "1.95" because the egui 0.37 family requires it. docs/architecture.md:13-14 records that the UI may require 1.95 without raising the core crates' 1.90 declaration.

Relevant package targets:

Target egui use Current features / notes
crates/ui-egui (library) egui; craft-ui docking/buttons Cargo.toml:24; dev egui_kittest with wgpu,snapshot at :31; build.rs bakes contributor credits (no asset change)
apps/audio (desktop binary) eframe, egui Cargo.toml:21 features default_fonts,wgpu,accesskit,wayland,x11,links; missing clipboard compared with 0.36's implicit native clipboard; Windows winresource; macOS muda/objc2
apps/audio-web (wasm binary) eframe, egui Cargo.toml:27 features default_fonts,wgpu,accesskit; rfd browser picker; WebRunner in src/web.rs
apps/audio-cli none Headless engine/control/MCP; no egui dependency
crates/{time,audio-io,midi,dsp,model,mix,engine,playback,automation} none Must stay UI-free; cargo xtask layers enforces this
xtask none ci, layers, assets, parity, wasm gates

rustc 1.99.0 / cargo 1.99.0 were present at inspection; the plan does not assume that this toolchain is used by every CI lane. The 1.95 UI MSRV must be checked against each lane.

Nested workspaces crates/clap-host/tests/fixture and crates/vst3-host/tests/fixture are independent roots with their own lockfiles and no egui/winit dependencies. They do not inherit the root patch and need no dependency change; they still need to build as part of the plugin tests.

3.2 Entry points and actual lifecycle paths

Path Current shape Migration relevance
Native shell apps/audio/src/main.rs:37-105 impl eframe::App: logic, raw_input_hook, ui, on_exit; run_native at :266 App::logic and App::ui both run inside Context::run_ui, once per layout pass; run_logic also calls logic while hidden. Do not model native logic as a once-per-frame hook outside run_ui
Native dropped files main.rs:52-58 calls SoundApp::take_dropped_files(ctx) from logic; sampler_window.rs:88-100 clones ctx.input().raw.dropped_files and does not clear it At the start of App::ui — before the !fonts_ready early return at lib.rs:1232-1239 — capture and clear raw.dropped_files through input_mut, together with the drop-time pointer position, into a one-shot pending queue. Process that queue after layout and sampler drop targets exist, for every UI pass including a forced hidden paint, and once fonts are ready. A first-frame or first forced-hidden-paint drop must not be lost; run_logic leaves the last pass's input in place, so later hidden ticks must not replay it
Native control server apps/audio/src/control_server.rs:35-140; crates/ui-egui/src/control.rs:49-65 drain Queue/reply must execute once per consumed request, including hidden ticks and multiple passes
Native screenshots control.rs:221-234, :244-279; docs/control-protocol.md:25 Old Event::Screenshot/UserData path must become callback based
Native separate Mix window mix_window.rs:25-38 show_viewport_immediate; separate_closed at :41 Child root Ui, focus/close, hidden-parent behaviour; never call root_ui from the child
Native macOS menu native_menu.rs; main.rs:43-50,286-301 muda events processed in logic; no egui pass required
Native Windows renderer apps/audio/src/graphics.rs:11-30; main.rs:263-265 DX12/FXC defaults are deliberate app fixes; preserve and measure the 0.37 shader cost
Web shell apps/audio-web/src/web.rs:194-218 WebShell logic/ui/raw_input_hook; start :18-50; take_drops at :136-160 already clears the raw list Same SoundApp lifecycle; route drop/picker completion from the known UI phase too, so both hosts share one rule; browser file picker/drop/download; no control channel
Headless render example crates/ui-egui/examples/ui_shot.rs uses Harness::builder().build_ui_state and calls app.logic inside the UI closure at :65-70 Keep logic then UI inside the harness closure, matching both native and web; assert effects across discarded passes
UI tests 54 integration files; 108 #[test] in crates/ui-egui/src plus 156 in crates/ui-egui/tests (264 total); 1,159 workspace #[test] occurrences at HEAD inspection Existing geometry/roles/command tests are the regression base; counts are the comparison point for later selected-test evidence; no committed kittest snapshot files exist

3.3 Platform cfgs

These cfgs are part of acceptance. A green Linux check is not a macOS native-menu, Windows IME, Wayland clipboard, or browser workflow result.

3.4 Existing tests and CI

cargo xtask ci (xtask/src/main.rs:174-200) runs cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace, cargo xtask assets, cargo xtask layers, and cargo xtask wasm. It does not add --locked to every step; a lockfile/graph check must be a separate accepted command.

Repository workflows at inspection: freebsd.yml (nightly/release, common action), windows-arm64.yml (nightly/release, common action), packaging-lint.yml, nix.yml (common action; there is no root flake in this checkout), sonarcloud.yml, release.yml (release branch only). The primary PR test matrix is supplied by storytold/craft-common-actions, not by a ci.yml in this repository.

Existing UI tests already exercise real frames and shapes rather than only state:

Optional evidence hooks already exist: CRAFT_UI_THEME_SHOTS, AUDIO_MIX_PROOF_DIR, ui_shot, and the control channel's ui.screenshot. They are not a substitute for native window evidence.

4. Actual egui 0.37 incompatibilities and required adaptations

These are released upstream changes, not fork features. The fork may contain more changes (E1–E21); those are handled in §5 and §12.

ID Source anchor Change Required Audio adaptation
A1 crates/ui-egui/src/control.rs:192 Event::MouseWheel now has source: MouseWheelSource (egui/src/data/input/event.rs 0.37.0) Set MouseWheelSource::Wheel for unit:"line", Trackpad for point/trackpad; assert source and phase in trackpad.rs
A2 control.rs:253, :268; PendingShot at :32-39 Event::Screenshot and egui::UserData removed; ViewportCommand::Screenshot(ScreenshotCallback) Replace event polling with ctx.request_screenshot, a bounded weak completion slot per request, and callback correlation (§6.4)
A3 panels.rs:69, :98, :110; all WidgetType::* occurrences WidgetType is a deprecated alias for accesskit::Role; SelectableLabel/Checkbox are not accesskit roles SelectableLabel→Role::Button, Checkbox→Role::CheckBox, ComboBox→Role::ComboBox, Slider→Role::Slider, RadioButton→Role::RadioButton; use egui::Role or egui::accesskit::Role consistently
A4 panels.rs:155-185 scroll_area::State.offset is private; public reads are clamped_offset()/unclamped_offset() Read out.state.clamped_offset().y; apply a pending target through ScrollArea::vertical_scroll_offset next frame (no private writes)
A5 Cargo.toml:50-52; all Id::new/ui.id() uses Id::new deprecated for Id::unique; Ui::id/UiBuilder::id renamed to scope_id; AsId is deprecated (egui/src/id.rs:8-12) but still present Mechanical Id::new→Id::unique, ui.id()→ui.scope_id(), UiBuilder::id→scope_id; same hash, no state change. clippy -D warnings makes this blocking
A6 crates/ui-egui/Cargo.toml:24,31; apps/audio/Cargo.toml:21; egui_kittest-0.37.0/Cargo.toml ([dependencies.egui], [dev-dependencies.egui]) 0.37 egui declares default = []; default_fonts/monochrome_emoji_fonts are opt-in. eframe's selected default_fonts enables ordinary bundled faces for the binary; the new egui/monochrome_emoji_fonts must be selected explicitly. kittest's normal egui dependency is default-features = false; only kittest's OWN [dev-dependencies] enable the font features, and dev-dependencies do not propagate. An isolated cargo test -p audio-ui-egui graph is therefore predicted to have no bundled fonts Enable default_fonts and monochrome_emoji_fonts on Audio's egui dependency for the UI/test root and prove unification with cargo tree -e features; treat the no-fonts prediction as a resolution claim until that command runs. See §9
A7 control.rs and Cargo.toml:21 0.36 eframe forced egui-winit/clipboard in its native dependency; 0.37 gates it behind eframe/clipboard Add "clipboard" to native eframe features. Without it, copy/paste silently uses egui's simulated clipboard instead of the OS clipboard
A8 crates/ui-egui/src/lib.rs:1136-1139 After a completed run_ui, current_pass_index() is zero (end_pass takes the output), so the >0 guard does not by itself skip an ordinary hidden tick. The real hazards are stale input/time in run_logic and duplicated non-idempotent work Keep public host ordering; separate fresh input from background queues and monotonic deadlines (§6.2); test hidden and repeated passes
A9 main.rs:52-58; sampler_window.rs:88-100; lib.rs:1232-1239; web web.rs:136-160 ctx.input() in run_logic is the last UI pass's input; native dropped_files is not consumed. Visibility does not identify a logic-only callback: RequestPaintWhileHidden can run App::ui while minimized/occluded. ui returns before layout while !fonts_ready, so a drop read only after layout is lost on the first frame Capture and clear raw.dropped_files through input_mut at the top of ui, before the !fonts_ready return, into a one-shot queue; process it after layout for every UI pass including a forced hidden paint. Never gate on visible()
A10 Cargo.toml:50-52; crates/ui-egui/src/theme.rs:431-475 0.37 style defaults are roomier/larger corner radius; panel/menu geometry can shift Keep explicit Audio spacing/font/size overrides; assert geometry and exact custom faces; do not refresh screenshots first
A11 bounce.rs:68; shortcuts.rs:64-101; edit_window.rs:341 0.37 adds MouseWheelSource, kinetic scrolling and rubber-banding; smooth scroll values can change Test wheel vs trackpad source, phase and momentum; make deterministic test styles (disable kinetic/rubber-band where exact offsets are asserted)
A12 egui_kittest 0.37 accessibility.rs Every visible input widget must have an accessible name; otherwise the harness panics after a settled step Add names to all custom-painted controls and text fields; audit every window/dialog; do not disable the check globally
A13 egui_kittest 0.37 builder.rs Missing glyphs panic by default Enable bundled fonts/emoji and fix the app's uncovered glyphs (§9); use allow_missing_glyphs only in a deliberately labelled tofu test, never for the suite
A14 apps/audio-web/src/web.rs:194-218 Same App lifecycle; browser has no control channel. eframe's web runner also calls logic then ui inside run_ui per pass Apply the same known-phase rules, not a visibility split; do not make browser pickers or downloads part of a multi-pass paint path

4.1 Not incompatibilities

5. Dependency target, fork provenance and publication rules

5.1 Coherent production target

P1 adopts the reviewed storytold egui family, companion winit and compatible craft-libs revision atomically with all required source changes. The aiongg SHAs are research inputs and patch provenance, not final production pins. Stock 0.37 may be built as an isolated measurement control; it is not an intermediate shipping stage. Wait for approved, reachable dependency publication rather than inventing a SHA before implementation or publication.

The final production pin is the whole prerequisite set, not the E1–E21 base or an intermediate stage:

P1 is blocked in full until the complete reviewed/published dependency set and CL-2F exist: no root pin, lockfile change, partial manifest change or per-row P1 claim lands earlier, and there is no stock/intermediate production stage. The fork SHAs cannot be filled in from a movable branch, an aiongg ref or this document.

5.2 Root patch shape (only after exact SHAs exist)

Cargo [patch] sections do not propagate from a dependency's workspace into a consumer root. A consumer that patches only egui will still get registry egui-winit, egui-wgpu or winit. The root Cargo.toml must name every used family member at the same full SHA (illustrative, not a manifest to land with placeholders):

[patch.crates-io]
egui                 = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
ecolor               = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
emath                = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
epaint               = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
epaint_default_fonts = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
eframe               = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
egui-winit           = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
egui-wgpu            = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
egui_kittest         = { git = "https://github.com/storytold/egui",  rev = "<REVIEWED_EGUI_SHA>" }
winit                = { git = "https://github.com/storytold/winit", rev = "<REVIEWED_WINIT_SHA>" }

The table is illustrative, not an exhaustive resolved-family list. Include egui_glow, egui_system_font_provider and any other egui workspace crate selected by native/web/dev features after metadata inspection. egui_extras is not directly used by Audio; inspect transitive test/optional graphs before deciding whether its patch is needed. egui_plot is not part of the 0.37 workspace and is not a Audio dependency. wgpu stays at 30.0.1 unless a separately reviewed change is needed; winit stays 0.30.13-compatible.

Workflow pins:

craft-ui     = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "<REVIEWED_CRAFT_LIBS_SHA>", features = ["serde"] }
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "<REVIEWED_CRAFT_LIBS_SHA>" }

Use one craft-libs revision for both if the reviewed diff only changes the UI crate plus unrelated main changes; otherwise retain the current craft-docpath rev and document why. Do not use two craft-libs revisions merely to avoid reviewing the merged commit. Review PR #199's actual edits line by line: its WidgetType→Role mappings, UiBuilder::id→scope_id, and the fixture ellipsis change are evidence of the API port; its lockfile and stale base are not to be copied. No machine-local path patch is allowed in the committed manifest or in .cargo/config.toml.

5.3 Lockfile and compatibility checks

Before accepting P1, in a managed build job with a complete registry index:

  1. cargo metadata --locked --format-version 1 and assert every egui family package has exactly one package id/source from the selected git SHA, and craft-ui resolves to the reviewed craft-libs SHA.
  2. cargo tree -d --locked shows no 0.36.2 egui/ecolor/emath/epaint, no duplicate winit, and no second accesskit family.
  3. cargo tree -e features -p audio --locked shows eframe/clipboard, egui/default_fonts, egui/monochrome_emoji_fonts, and the intended winit/accesskit features. Repeat for -p audio-web under the wasm target and check eframe web clipboard/file features. Also inspect the Audio UI test graph with cargo tree -e features -p audio-ui-egui --locked (dev-dependencies included) and the production-only graph with cargo tree -e features,no-dev -p audio-ui-egui --locked: egui/default_fonts, egui/monochrome_emoji_fonts and epaint_default_fonts must appear in the test graph. Keep the three roots distinct: the production binary, the Audio UI test target and kittest's own test root. kittest's normal [dependencies] egui is default-features = false; only its own [dev-dependencies] enable fonts, and a dependency's dev-dependencies do not propagate.
  4. cargo check --locked --target wasm32-unknown-unknown -p audio-ui-egui -p audio-web.
  5. cargo check --locked from a clean cache; no source replacement, no local path, no branch dependency, and all git revisions reachable by a clean clone.
  6. After the pin, add Dependabot ignore entries for egui, ecolor, emath, epaint, epaint_default_fonts, eframe, egui-winit, egui-wgpu, egui_kittest and winit (with a comment pointing here), or require the dependency PR to update all pins together. A daily single-package bump would otherwise recreate the split this plan removes.
  7. Record git rev-parse HEAD, full cargo tree output, Cargo.lock hash and selected feature list in the task evidence.

5.4 Vendors and existing fixes

Audio has no vendor/ directory and no [patch] section today. It must not add a vendored copy of egui to solve compile errors. The relevant external fixes are:

No blanket vendor deletion is authorized. If a future consumer of Audio carries a vendor, its disposition is tracked in that consumer's plan, not here.

5.5 Language work (#198) integration decision

Audio has no craft-i18n dependency and its menus/messages are English literals. The language work tracked by craft-libs issue #198 is not a merged UI-localization contract. This migration therefore consumes the fork's text/input mechanisms where they affect existing fields (E5/E12–E16 bidi shaping and grapheme editing, E19/W25 keyboard layouts, E21/W27 IME incoming deletion, E21b/P7b outgoing surrounding text), but does not add a locale switch, a craft-i18n catalog, an RTL application layout, or a second bidi pass. It also does not use E17 Context::set_language / E18 Frame::set_ui_language until a separate localization plan defines the UI language and its AccessKit root language. Existing logical strings remain the save/clipboard/accessibility source of truth; never feed already-reordered visual text back into a TextEdit or the document.

6. Root Ui, multi-pass and logic-only semantics

6.1 Actual host contract

Both native epi_integration.rs::update and web app_runner.rs::logic call App::logic followed by App::ui inside the Context::run_ui closure, once per layout pass. Both also call App::logic through run_logic while hidden. Native logic is not a once-per-frame hook outside run_ui. Keep the existing public SoundApp::logic/ui entry points and pass the existing root Ui; no recursive Context::root_ui bridge is needed.

run_logic refreshes viewport/focus information but leaves events, time and UI state from the last pass. After a completed run_ui, current_pass_index() is zero because end_pass takes the platform output. The existing first-pass guard does not, by itself, skip an ordinary hidden tick after a completed multipass frame. Do not create a test that expects that false failure. Visibility also does not identify the callback phase: RequestPaintWhileHidden can run UI while the window remains minimized/occluded.

6.2 Background work and current input

Extract small private methods only where they clarify the existing responsibilities:

Native/web hosts use the same logic/UI ordering. Test helpers must reproduce it: call logic then ui inside the UI closure; call only logic from run_logic. Do not simulate a fictional native host by calling background work once outside run_ui. Existing tests may keep their public entry points. Add a small helper for new phase-sensitive tests, without rewriting every test for style alone.

6.3 Side-effect placement

Work Current anchor Class Required change / regression
Font install and theme application lib.rs:1136-1154; fonts::install; theme::apply (theme.rs:453-480) logic host-once pre-pass, before any layout Keep both in logic (or a named host-once pre-pass called before layout) and idempotent by fonts_installed/theme_applied. ui currently returns before layout while fonts are not ready (lib.rs:1232-1239), and theme::apply mutates global style branches before the root ui.set_style(ctx.global_style()) call. Do not move this to ui: a hidden first tick must install fonts and a direct logic call must switch theme. Test hidden first frame, a multi-pass frame, and repeated direct logic calls.
Control queue drain lib.rs:1158, control.rs:49-65 logic Consume each queued request once; hidden ticks still answer requests. Test ui.inspect/engine.execute while minimized; two-pass frame executes command once.
Recent-session/prefs notification lib.rs:1159, host main.rs:71-74 logic Host save remains idempotent; hidden tick must not lose the pending recent change. Test after a hidden tick.
Plugin editor edits lib.rs:1160, apply_editor_edits logic Native plugin editors idle between paints; run once per tick. Test with a fake editor queue if possible; otherwise document native-only.
Bounce rendering lib.rs:1161, bounce.rs:22-38 logic 30 ms slice per tick; hidden window must continue. bounce::show's Escape check stays in ui (pass) or consumes current input. Test bounce progress across run_logic.
Pointer/gesture cleanup and automation merge lib.rs:1162-1170 logic for writing/merge; ui input preparation for pointer release Do not read stale ctx.input for pointer/keyboard in hidden ticks. Capture pointer_down/keyboard_wanted in ui input preparation, and clear a transient Gesture::Fader/pointer-down on entry to hidden logic so it cannot stick; write_automation uses the cached gesture/touched state, not stale input. Test a hidden tick does not write a spurious point.
Output/input device trouble lib.rs:1171-1172 logic Device errors arrive off-frame; one handling per tick.
Transport/player position lib.rs:1173-1180 logic Player position is read from the player; sim position only advances with real time. Hidden audio playback must continue. Test transport.play then run_logic with a fake/no-player path.
Open inserted plugin lib.rs:1181 logic Pending selection must open once, not per pass. Test a two-pass frame with pending_open.
Autosave revision/time lib.rs:1182-1184, 672-708 logic autosave_changes is revision-idempotent; autosave is wall-clock based. Hidden window must still autosave. Existing a_hidden_window_still_autosaves must be extended to hidden-after-multipass.
Input recorder ensure lib.rs:1185-1189 logic One open attempt per tick; test no duplicate recorder on two passes.
Repaint deadlines lib.rs:1191-1195 logic Keep request_repaint_after for autosave/plugin editors. Add a short deadline while screenshots are pending.
Screenshot collection/issue control.rs:244-279 logic Callback + weak slot; explicit count/byte admission and in-flight budget; one reply. See §6.4.
Shortcuts ui lib.rs:1244, shortcuts.rs ui input preparation Input-derived; hidden run_logic must not consume stale key events.
Dropped files native main.rs:52-58, sampler_window.rs:88-100; web web.rs:112-160 known UI phase (ui); never logic and never gated on visible() Capture and clear raw.dropped_files at the top of ui, before the !fonts_ready early return (lib.rs:1232-1239), together with the drop-time pointer position, into a one-shot pending queue; process it after layout once fonts are ready, on every UI pass including RequestPaintWhileHidden. Preserve sampler placement and web byte reads. Test a fonts-not-ready first frame (ordinary and forced-hidden), an ordinary paint and a forced hidden paint, each followed by hidden ticks, with exactly one import.
Native macOS menu main.rs:43-50 logic muda events are not egui input; sync is time-throttled. Hidden menu actions still apply.
Host title/window size/prefs main.rs:64-87 logic or a host-once helper Viewport info is current in run_logic; title/size updates are idempotent. Do not save prefs from every pass.
ui_shot test driver examples/ui_shot.rs:65-70 test helper (optional) The compatibility logic driver makes the current call safe; a helper that calls logic/ui input preparation explicitly is clearer and should be used for new scenarios.
Harness tests all build_ui_state closures test helper (recommended) Add a helper matching native/web order: raw input hook, then logic/UI inside each pass; hidden tests call logic only. Assert side-effect outcomes rather than one hook call; no broad test rewrite is required.

6.4 Screenshot callback correlation

Use the shared craft-ui design selected after review: a weak callback to a cancelable, request-owned completion slot. Each admitted request owns its token, reply/path, monotonic deadline, settle state and slot. The callback holds only a weak slot reference; timeout/cancel/close drops the live slot so a late callback releases its image without a file write or second reply. The live slot owns a viewport wakeup handle; release its lock before waking the context/viewport. Never strongly capture the Context in a callback queued inside that Context.

The eleven craft-ui consumers need CL-1 and the frozen CL-2F at this stage; the Litho/GridCraft pilot instead uses the provisional CL-2 API, and its adapters must precede CL-2F. other Crafting Apps use small contract-compatible local adapters, and craft-ai-integrations/ai-egui has no capture path, so the earlier "thirteen craft-ui consumers" shorthand is superseded. Audio is one of the eleven and keeps its own request IDs, paths, settle delays, deadlines and replies outside the helper.

Admission is explicit and checked before a capture is issued. The shared helper's default is four outstanding captures, 128 MiB per image and 256 MiB aggregate; an application may choose stricter caps. Audio keeps its own 8-request pending queue, 4 in-flight captures, 64 MiB per captured image and 192 MiB aggregate encoding peak (the image plus the raw/PNG/base64 copies built while encoding); a further request is rejected with "screenshot queue full" and writes no file. Before a request is issued, reserve a checked width_px * height_px * 4 from the capture-time surface/geometry estimate clamped by max_texture_side, with overflow and aggregate checks; it is not a post-completion guess. A request that cannot be reserved is rejected with "screenshot too large".

Bind the known issuance geometry and viewport/document generation (epoch) to the request: viewport points, pixels_per_point, zoom and the surface-size estimate. Revalidate that identity at issuance and completion rather than comparing current settings. Any geometry/DPI/zoom change before the image is saved cancels and retries within the deadline — even if the values change back before the save, because the pixels were rendered for the earlier geometry. A second mismatch fails with "screenshot stale" and never associates old pixels with new state. A request that misses its deadline returns "screenshot timed out". These errors travel through the existing control-protocol error field; success still produces exactly one reply and at most one file.

E2 surface_fit can render a smaller surface than the window (egui-wgpu/src/winit.rs:45,446) and its render_scale is private (:25), so returned image dimensions need not equal viewport points × pixels_per_point. Derive the effective scale from the returned image dimensions plus the capture-time logical viewport metadata, test a surface-reduced window, and accept that capture instead of rejecting it as stale.

The callback delivers only pixels. The application owns the strong completion slot and waker; the callback holds a Weak reference, releases the lock before waking, and does not wake itself. request_screenshot sends the viewport command that requests the paint; the callback itself requests no frame, and a post-completion wake is only for collection/reply. A screenshot that is queued but not yet issued — including a hidden request that is settling — needs an explicit RequestPaintWhileHidden from logic so the request can reach the UI phase. Encoding, file IO and replies happen after collection without holding the slot lock. Keep a monotonic deadline if no frame is presented. No unbounded mpsc queue is the default design; a bounded nonblocking channel is a measured fallback only if the shared slot design fails a demonstrated requirement.

kittest completes every screenshot callback from one rendered image (egui_kittest/src/lib.rs:914-942 collects all viewport callbacks and completes each with the same image), so it can test single-viewport request/cancel/lifetime behavior only. Cross-viewport pixel correlation needs native two-window evidence or a custom per-viewport renderer that renders each viewport separately.

Test 0, 1, N and N+1 requests; two large concurrent images; a resize race; forced discard; cancellation; timeout; out-of-order completion; shutdown; hidden wakeup; and retention returning to zero after drain. Each succeeds once or returns one of the defined errors. A late callback cannot create a file or send a second reply. Synthetic hidden input must visibly take effect on the requested UI paint or return an explicit failure.

6.5 Lifecycle regressions and deliberate failure probes

7. Panels, docking, IDs, viewports and geometry

8. Input, IME, clipboard, PRIMARY and scrolling

9. Fonts, glyphs, accessibility and theme

9.1 Font feature decision

0.36's default fonts included emoji faces; 0.37 makes default_fonts opt-in and moves emoji fonts behind monochrome_emoji_fonts (while eframe can add a system color provider). Audio's loader at fonts.rs:109-139 starts from FontDefinitions::default(), prepends a system regular/ bold and Hangul fallback, and assumes bundled defaults exist. Therefore:

9.2 Glyph coverage findings from the bundled font cmaps

The following is source/asset inspection of egui/crates/epaint_default_fonts/fonts/*.ttf (cmap scan), not a rendered test. It identifies specific risks for egui_kittest's new MissingGlyphPolicy::Panic:

Glyph / use Anchor Bundled coverage Required action
▸ U+25B8 in status text lib.rs:848,851,853,888; docs text Only Hack (monospace); proportional Ubuntu lacks it Replace with ›/> or draw it; add to a glyph test
▾ U+25BE in menu labels panels.rs:477 (Presets ▾) Only Hack (monospace); proportional Ubuntu lacks it Replace with ›/v/none, or draw the menu arrow; add to a glyph test
✕ U+2715 close/delete buttons windows.rs:258,404; panels.rs:422 emoji-icon-font, only with monochrome_emoji_fonts Enable the feature; otherwise use × U+00D7 (Ubuntu has it) or a code-drawn close icon
♯ U+266F sharp score_editor.rs:122 None of the bundled fonts Draw original sharp strokes in icons.rs/score_editor.rs, or bundle an OFL music font with an ATTRIBUTION row
𝄞 U+1D11E treble clef score_editor.rs:67 emoji-icon-font (present in its cmap) Coverage exists when monochrome_emoji_fonts is enabled. Replacing it with a drawn original clef is a visual-quality choice, not a missing-glyph fix; assert whichever face is chosen
⌘ U+2318 mix_window.rs:609 (mac tooltip) emoji-icon-font (present in its cmap); mac system font also supplies it Bundled coverage exists with monochrome_emoji_fonts. Keeping the mac-native glyph or drawing an original key is a visual-quality choice; in deterministic tests assert the bundled face. Do not globally allow missing glyphs
📁/🔊/🔍/⬆ windows.rs:327,355; panels.rs:206 Noto Emoji with monochrome_emoji_fonts Enable the feature and test in every family
♩ U+2669 panels.rs:352 emoji-icon-font Covered by monochrome_emoji_fonts; assert
−/∞ mix_window.rs:1079,1088 Ubuntu-Light Covered; assert

Add a fonts::tests (or a kittest-only test) that checks every literal glyph used by UI fixtures in FontFamily::Proportional, FontFamily::Monospace and FontFamily::Name("bold"), with MissingGlyphPolicy::Panic and a bundled-font-only font set. Use a dedicated test binary or a context_setup that installs FontDefinitions::default() so system fonts cannot make the test pass by accident. Do not call HarnessBuilder::allow_missing_glyphs() for the main suite; use it only in one explicitly named test that demonstrates the fallback is intentional.

Separate the bundled UI-glyph test (▸, ▾, ✕, ♯, ♩, 𝄞, ⌘, 📁/🔊/🔍/⬆) from document-text multilingual tests. The latter use the pinned fixture set from the egui plan §10.6; do not infer multilingual coverage from the bundled faces:

Fetch them into the task's explicit fixture directory, keep them out of app source unless the asset policy adds an ATTRIBUTION.md row, and record file hashes and licenses in the evidence manifest. A missing fixture or unsupported glyph must fail the row, never skip it silently.

9.3 Accessibility

egui_kittest now checks every visible input for a name after a settled step (accessibility.rs:35-70); the check treats a label or non-empty placeholder as a name. The app has many custom-painted controls and bare fields:

Harness::unnamed_widgets() is a useful supplementary assertion, but it is not sufficient: it walks only nodes that exist (egui_kittest/src/accessibility.rs:14-21), filters by is_input, and a control that never calls Response::widget_info has no node at all. Roles excluded from is_input, including Role::Unknown, also pass. A sweep can therefore be green while custom buttons and faders are unreachable.

For every top-level window (Edit, Mix, MIDI Editor, Score, Video, Workspace, Appearance, About, Session Info, Audio Health, Bounce modal, Save Changes, Spot and each popup), define an explicit expected node inventory: role, accessible name, selected/checked, disabled and value where applicable, plus absence where a control must not exist. Look nodes up with get_by_role/get_by_role_and_label and assert counts so an absent node fails. Cover every custom-painted control, not just the ones a workflow clicks. unnamed_widgets().is_empty() runs as an additional check.

Mutation controls: (a) remove a widget_info call entirely; (b) set a role to Role::Unknown; (c) remove an accessible name; (d) hide a control and assert it disappears. Each must fail its inventory test; remove the deliberate defect afterwards. Add a test that opens a popup and checks the popup tree separately from the window tree.

9.4 Theme and style

theme::apply (theme.rs:458-476) overwrites Visuals, item spacing, button padding and tooltip delay for both dark/light style branches and pins ThemePreference::System so later OS appearance changes still arrive. Preserve this contract. The 0.37 default style is roomier (buttons/corner radius); if theme::apply does not override every changed field, explicit geometry tests will catch it. Do not accept a global visual change because the upstream default changed. Re-run light/dark/system at 1x/1.5x/2x and both system_theme: Some / None; keep the existing theme_docking.rs and appearance.rs assertions.

10. Commands, undo, save/reopen and persistence

These contracts are below the UI and must not change:

Regression assertions:

  1. A two-pass frame triggered by a popup/menu executes one command and one undo step.
  2. Fader/send drag still produces one merged undo step; a pass with no pointer input does not close the merge prematurely.
  3. Save → close → reopen preserves tracks, clips, fades, mixer levels, sends, automation, markers, groups, busses and selection; save_changes.rs, save_as_replace.rs and recovery.rs stay green and gain a richer fixture.
  4. Close/reopen a floating dock panel and restore its workspace; docking_workflows.rs keeps command ownership and transport commands.
  5. Recovery after a crash/exit without Quit still offers the autosave; hidden logic must not stop the autosaver.
  6. ui.screenshot/ui.inspect control replies remain one request → one reply, including hidden ticks and timeouts.
  7. A mutation probe that makes run execute twice must fail the undo assertion; a probe that moves autosave into ui must fail the hidden-autosave assertion.

11. GPU, video, textures and rendering

12. Native, web and platform workflows

12.1 Native (Linux, Windows, macOS, FreeBSD)

12.2 Web (wasm)

13. Test and performance plan

13.1 Required gates

Run every new build/test through the managed server workflow (maintenance/task.py run / status / finish) with a unique task, source and evidence path. Do not run directly or reuse another worker's target. Proposed commands (implementation stage):

cargo fmt --all -- --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
cargo test -p audio-ui-egui --locked
cargo test -p audio-engine --locked
cargo test --workspace --all-targets --locked
cargo xtask assets
cargo xtask layers
cargo xtask wasm
cargo check --locked --target wasm32-unknown-unknown -p audio-ui-egui -p audio-web
cargo metadata --locked --format-version 1
cargo tree -d --locked
cargo tree -e features -p audio --locked

On Windows add cargo build --release --locked -p audio -p audio-cli, the VM GUI workflow, and the FXC startup capture. On Linux add X11 and Wayland native runs. Add trunk build --release and a browser workflow. Selected optional features to check explicitly: native eframe/clipboard, accesskit, wgpu, x11, wayland; web wgpu with WebGL2 fallback; egui_kittest/wgpu,snapshot; fork-only E17/E18 only if the app starts using a UI language.

cargo xtask ci is the repository gate but does not add --locked to each step; the implementation PR must add or separately run the lock/graph checks above and record actual selected test counts, not just exit codes.

13.2 Regression assertions

Risk Observable assertion
Root Ui/multi-pass One command, one undo step in a forced two-pass frame; no root_ui recursion panic
Hidden logic Autosave, control reply, player position and bounce progress continue; ordinary and forced-hidden paints each import a dropped file once and later hidden ticks do not replay it; paste does not repeat; screenshot timeout is clean
Docking Float/dock/close/reopen/reload keeps panel identity, permissions, floating rect and command ownership
Panels/geometry Timeline, track rows, strips, dock tabs and menu bar rects match the baseline within the stated tolerance
Accessibility Explicit expected node inventory per window/popup with role/name/selected/disabled/value and count assertions; unnamed_widgets().is_empty() only as an extra check; missing widget_info and Role::Unknown mutations fail; hidden controls absent
Fonts/glyphs Bundled UI glyphs have coverage in every family; pinned craft-fonts/Hebrew/Noto Color Emoji fixtures render document text; no tofu; no global allow_missing_glyphs
Text/IME Preedit/commit/cancel/replacement, click-away selection, grapheme/UTF-8 boundaries, outgoing surrounding text after delete+commit via E21b/P7b with owner present when unavailable, available-empty versus unavailable, transition clear/disable-enable, final-pass None, serial-mismatch apply-once/defer, stale-epoch invalidation; focus/password/reset clearing
Scroll/input Wheel vs trackpad source/phase, momentum/rubber-band policy, zoom anchor, shift-pan, gutter page
Clipboard/PRIMARY Bounded regular clipboard transfer (deadline/byte cap/workers/cancel/stale discard), keyboard- and pointer-triggered PRIMARY serials, owner/seat changes, no first-seat fallback, self-paste
Screenshot Two-viewport/request correlation in native/custom per-viewport evidence, exactly one reply/file, timeout/late callback inert, checked pre-issue reservation, geometry/epoch binding with cancel-on-any-change, surface-fit scale derivation, shared 4/128 MiB/256 MiB defaults with Audio's stricter 4/64 MiB per image and 192 MiB encoding-peak caps, drain-to-zero
Commands/undo Fader/send drag one merged step; command via menu/control/CLI same id and one mutation
Save/reopen Round-trip document meaning, selection/workspace and autosave recovery; no dirty flag loss
GPU/video Video texture update/resize, oversize rejection, surface errors return errors, no panic
Native/web Native focus/IME/picker/clipboard/PRIMARY and browser picker/download/playback are actually exercised

13.3 UI screenshot and pixel policy

13.4 Performance protocol

Record baselines before production edits. Four variants are distinct, and the report must not collapse them into "three":

  1. Historical coherent baseline: e85a4801e9029c9a1f5ce536b11be65aea832ed8 (0.36.2 egui family, its own non-UI dependency set).
  2. Current-engine / 0.36-UI control: current non-UI dependencies (the 11-package bump: symphonia 0.5→0.6, cpal 0.16→0.18, zip 9.0.1→9.0.5, libloading 0.8.9→0.9.0, muda 0.19.2→0.21.2 and the rest) with the egui family reverted to 0.36.2 and craft-ui back at 5c874d51eeb3de70e735c8ce87a59a7dedd47675. Build it only if it actually compiles; if it does not, record it as an unavailable attribution control, not a pass or a fabricated result.
  3. Equivalent stock-0.37 control: stock 841a328ba2dd8f6996beb7189ebb7fc00e668a2a, with the same operation expressed in a pinned minimal adapter/harness where fork-only APIs are involved. Record any semantic differences.
  4. Fork candidate at the exact reviewed P8/winit lock/SHA.

The stock-0.37 equivalent control is required, not optional: report it first in every table and evaluate any regression against both the paired baseline and this control. Report non-UI dependency deltas separately from egui-frame work. A cpal/symphonia startup or audio-underrun difference must not be attributed to egui or the fork. Record one lockfile, source SHA and feature list per variant, and note where an adapter is not bit-equivalent.

Use identical compiler, profile, features, font set, fixture, renderer/backend, driver, DPI and viewport. Separate software renderer and real-GPU results, and keep native eframe timings in a different row from synthetic kittest/Harness timings: they measure different event loops, paint scheduling and input paths and are not interchangeable. Do not mix any of these rows.

Workloads:

  1. Startup: 20 fresh processes, demo session, --no-audio, time to first painted frame; on Windows also time egui_wgpu::Renderer::new/pipeline creation separately (FXC, #8736) and font install/fc-match separately (#8512).
  2. Idle: 60 s with no input; frames/repaints, CPU%, median frame interval, idle request_repaint count. Any new busy loop or audio underrun is a failure.
  3. Text/timeline: 1,600x1,000, demo + 60 tracks, 5 clips each, 1,000 frames of horizontal pan and vertical scroll; smooth_scroll_delta, waveform shapes, text tessellation, frame p50/p95/ p99.
  4. Mix: 64 tracks, meters live (no-audio simulation and, separately, player), 60 Hz, 1,000 frames; strip layout and meter paint.
  5. Windows/dialogs: open/close MIDI, Score, Video, docking float/resize, About/Preferences; operation latency and frames to settle.
  6. Video: one 1080p frame decode + texture update cycle, repeated; report upload bytes and frame time.
  7. Screenshots: request-to-callback-and-file latency, pending queue depth, peak image bytes.
  8. Web: wasm raw/compressed size, first interactive, demo load and a fixed interaction script; record browser/backend separately.

Proposed measurement harness: crates/ui-egui/examples/frame_bench.rs (a dev example, not a shipped binary), driven by the same demo fixture and Harness renderer; ui_shot remains the screenshot tool. The harness must not be used as a production compatibility layer.

Measurements: 10 warmups and at least 30 iterations for steady operations; at least 1,000 frames for frame distributions (including p99); 20 fresh processes for startup. Report raw samples, median/p95/p99 only where sample count supports it, peak RSS (VmHWM on Linux), allocation count/bytes via a vetted dev-only profiler such as dhat or an external heap profiler, texture set/upload counts and bytes from FullOutput.textures_delta, and idle CPU/repaint counts. Do not add a local #[global_allocator]: a GlobalAlloc implementation needs unsafe, and the workspace forbids it (Cargo.toml:56-57, applied to ui-egui via [lints] workspace = true). Use paired baseline/candidate runs and alternate order; record host load and driver versions.

Predefined stop thresholds, frozen before seeing candidate results: investigate a repeatable

5% median, >10% p95 or >5% peak-RSS regression, >5% startup median or >25 ms, any new idle busy-loop/audio underrun, or any loss of an existing stricter app budget; every relative change must also exceed the measured run-to-run noise floor. For sub-millisecond operations, require the relative increase also to exceed 0.05 ms or be corroborated by instructions/allocations. Report p99 from ≥1,000 frames and investigate a repeatable p99 regression even though it has no separate fixed threshold. Reject noisy comparisons as inconclusive and rerun; do not widen thresholds after seeing a failure. A confirmed #8731/#8736 regression needs a reviewed fork fix or a documented exception, not a changed golden.

14. Reviewable implementation stages, DAG and rollback

Dependency DAG:

P0 reviewed plans + Audio baselines (e85a480..., current-engine/0.36-UI if it builds,
   stock-0.37 control, fork) and preserved baseline executable/fixture
   |
   +-- approved published storytold egui P8 (E1-E21 + P2b + P7 + P7b/E21b + P7c)
   +-- completed approved published storytold winit (W15P + bounded W15 transfer
       + W27 lifecycle revision)
   +-- reviewed craft-libs CL-1 (reconciled 3832c47 base)
   |        -> CL-2 capture prototype -> Design/Grid pilot adapters -> CL-2F API freeze
   |
   -> P1 atomic Audio compatibility (root pins, lock, API/clipboard/fonts,
      lifecycle, capture adapter against the frozen CL-2F API)
        -> P1 rollback rehearsal
        -> P2 deeper UI assurance (accessibility inventory, scroll, glyphs, IME)
        -> P3 native/browser/performance acceptance + repeat rollback rehearsal
  1. P0 — plans and baselines. Land all reviewed plans. Add regression/measurement support on the historical coherent baseline e85a4801e9029c9a1f5ce536b11be65aea832ed8; preserve the current mixed graph as a separately identified pre-existing failure. Record exact full SHAs, features and lockfiles for the four performance variants and raw baseline samples before production changes. Preserve the baseline executable and a saved fixture for rollback rehearsal.
  2. P1 — atomic compatibility. Prerequisites are the complete approved, reachable storytold egui P8 and completed storytold winit revisions (P2b over the bounded W15 transfer, P7, P7b/E21b, P7c; W15P, W15 transfer and W27 lifecycle/serial state), the reviewed craft-libs CL-1 revision built by on the reconciled 3832c47 base (8614e4a/1764bca are the historical port hunks), and the frozen CL-2F capture-helper API. P1 waits for CL-2F; it does not land a provisional helper API. Then land all root family pins, lock changes, API/clipboard/font adaptations, callback replacement, any incompatible craft-ui bump and required lifecycle fixes together. A dependency-only or stock-first intermediate release is not planned. P1 is blocked in full until every prerequisite exists: a missing SHA or unpublished stage blocks all manifest changes, not just one PRIMARY/IME/capture row, and no partial P1 is claimed.
  3. P1 rollback rehearsal. In a separate managed allocation, restore the previous coherent source/manifests/lockfiles/settings set at e85a4801e9029c9a1f5ce536b11be65aea832ed8, run the preserved baseline executable and reopen the saved fixture. Record the exact commands, identities and result. Do not restore only an old lockfile and call that a rehearsal.
  4. P2 — deeper UI assurance. Add the explicit accessibility node inventories, remaining roles/names, scroll, glyph and E21b outgoing-IME tests and any measured fixes. The P1 commit must already pass the required tests; this stage expands cases rather than deferring broken behavior.
  5. P3 — native/browser/performance acceptance. Qualify supported environments (including the W15P/E4/P2b PRIMARY matrix and two-viewport native capture), retain evidence, repeat the rollback rehearsal and perform a fresh adversarial implementation review. Regressions require app fixes or a reviewed shared-fork follow-up with downstream revalidation.

The first compatibility PR is a single coherent dependency unit. Subsequent commits are individually reviewable and buildable. Rollback restores the entire accepted source/manifests/lock/helper set in a new revision; do not restore only an old lock, drop the fork while keeping fork-only code, or reset published history. Preserve all evidence and source work. No stage authorizes publication without fresh approval.

15. Compatibility pin and lockfile acceptance details

16. Blocking unknowns and explicit acceptance criteria

Blocking unknowns:

  1. Exact reviewed craft-libs commit after applying PR #199's intended hunks to current main. The local reconciliation base is 3832c47ef68a0e6214f2c1e85f3526b6adcb686e; b68de8c/ec8840a are stale trees and must not be merged, and the historical 8614e4a/1764bca hunks on 4919088 are no longer the base. Until a reviewed reachable SHA exists, P1 cannot pin it.
  2. Organization-owned egui/winit SHAs, including the required new work. aiongg/craft/0.37 and aiongg/winit craft/0.30.13-lang are movable research inputs. P1 requires approved, reachable storytold full SHAs for the complete egui P8 set (E1–E21, P2b, P7, P7b/E21b, P7c) and the completed winit (W15P, bounded W15 transfer, W27 lifecycle). A missing SHA blocks all P1 manifest changes; these SHAs cannot be invented before implementation and publication.
  3. CL-2F capture-helper API. P1 sequences after the shared helper's approved freeze. The inspected 4919088 tree had no crates/ui/src/capture.rs, and the current source imports craft_ui::capture::CaptureSlot from the local craft-libs worktree. The reconciled 3832c47 base must be confirmed to carry the agreed CL-2F API when the final pin is chosen; a provisional API must not be pinned.
  4. Upstream #8731/#8736 disposition. The fork does not yet contain either fix. Audio's text-heavy UI and explicit FXC choice make both likely relevant; a measured result and a reviewed fork patch (or documented exception) are required before final performance acceptance.
  5. Native evidence environments. No DISPLAY/Wayland session, no Windows clone allocated, no macOS hardware and no physical-GPU/audio qualification exists in this planning run. The Windows full-clone capacity gate is also unresolved. These rows remain unresolved until managed evidence exists.
  6. Browser workflow. Compilation to wasm is not a browser test; a Chromium/Playwright-style driver, a running trunk build --release site and download/picker/audio checks must be available or the browser row is unresolved.
  7. Exact count of accessibility failures and scroll/multi-pass failures is unknown until the candidate is built. The plan specifies the audit and tests; it does not claim a pass.

Acceptance for the whole migration:

17. Evidence and limitations of this planning document

Local implementation update — 2026-10-23

The user clarified that reviewed plans remain local and implementation proceeds before publication. The earlier requirement to publish/land this plan first is superseded; pushes and PR publication still require explicit approval.

Canonical storytold/soundcraft main resolved to d47abe484d4f9ef0cc9f8e872e0b3880a61a4588 when the owned checkout was fast-forwarded on 2026-10-23. Earlier baseline SHAs and line anchors above are retained as historical review evidence.

Current local implementation update (2026-10-24): the owned worktree HEAD is 6b84a4f0461f2467402eafe41dcc708c369fb045 on codex/egui-037-audio-20261023, with the uncommitted migration/upstream-reconciliation edits on top. The craft-libs reconciliation base for those edits is 3832c47ef68a0e6214f2c1e85f3526b6adcb686e. The dependency graph is currently the local integration configuration/path-only lock, not a publishable manifest: one exact, reachable, immutable craft-libs revision plus the complete egui/winit family remains the final publication gate and must come from central local commits, not a guessed SHA.

No migration API redesign required by this delta; preserve the merged packaging, test and chrono changes where present.

The complete static delta review and exact old/new object identities are retained in the task evidence: canonical-diffs-refresh-20261023.json and overnight-consumer-review-20261023.md. This amendment claims no new build, native GUI or performance acceptance.

Local implementation stage 1 — compatibility and lifecycle (source only)

This stage is implemented in the owned worktree only. No build, test, formatter, clippy, lockfile, commit, push, PR or release was produced by the implementing worker; the parent runs the managed task.py jobs and regenerates Cargo.lock for the local-path dependency set.

Implemented against the local pre-E22 ../egui-implementation and the local ../craft-libs-implementation API:

Not included: E21b outgoing surrounding text (the sibling producer is under review), the full P2 accessibility inventory and mutation controls, native/browser/performance qualification, Windows and macOS evidence, and any change to the shared dependency revisions.

Known local test-helpers: new phase-sensitive tests call raw_input_hook(ctx, raw) and then logic/ui inside one run_ui closure, mirroring native and web eframe. The historical coherent baseline e85a480… keeps its 0.36 test helpers until the parent backports this helper shape for paired measurements.

Post-port review repair (source only, 2026-10-23)

This round addressed the substantive findings of egui037-audio-postport-review-20261023-root-v1.md in the owned worktree; no build, test, formatter, clippy, lockfile, commit, push or PR was produced.

Pending acceptance gates (not claimed by this repair): the E21b/P7b outgoing surrounding-text producer is implemented in ../egui-ime-implementation and is not integrated here — incoming IME works but outgoing surrounding text remains unqualified; the local egui pin does not contain the P7c selected #8763 click-away/selection-action fix, so rename/Spot/path rename fields inherit the unfixed behavior; native/browser GUI, IME and performance qualification still need the parent's managed runs.