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:
- 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
(
E4regular-clipboard/PRIMARY routing over the async W15 transfer path), P7 held-key release pairing andE21, P7b/E21b outgoing surrounding text, and P7c's selected#8763selection-action and#8748hidden-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=a4f9a17f64a6bae61350fb85804c9df0d9865916andaiongg/winit craft/0.30.13-lang=8b35388bd719e8cc86a95971ba0bc026c9a8f058are 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. craft-uiandcraft-docpathfrom one reviewedstorytold/craft-libsrevision on the reconciled merge base3832c47ef68a0e6214f2c1e85f3526b6adcb686e, which carries the intended egui-API port. The historical PR #199 hunks (8614e4a, README1764bca) and the earlier4919088main inspection describe how that base was assembled; they are no longer the base to apply. The stale PR headb68de8c5f4f1c060904d211a7b1320aa0f7bce78and audit mergeec8840af8c5fda695cf195cdd05e5ae078415c07must not be merged as trees: they omit currentcraft-platformand revert security/settings/docking/printer fixes. The exact reachable immutable publication SHA remains a central-publication prerequisite; it is not guessed here.- Source changes limited to: the real compile-error classes
(
UserData/Event::Screenshot,MouseWheel.source,WidgetType→Role, privateScrollArea::State.offset), deprecations thatclippy -D warningswill reject (Id::new,Ui::id), the nativeclipboardfeature 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. - 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:
control.rs:192:Event::MouseWheelinitializer lackssource.control.rs:253:Event::Screenshotno longer exists.control.rs:268:egui::UserDatano longer exists.panels.rs:69: noRole::SelectableLabel(the current source actually uses the deprecatedWidgetTypealias at this line).panels.rs:110: noRole::Checkbox(current sourceWidgetType::Checkbox).panels.rs:164,168,177,182:egui::scroll_area::State.offsetis private.
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:
egui0.36.2 and 0.37.0; likewiseecolor,emath,epaint(0.36.2 and 0.37.0).accesskit0.24.1 and 0.25.1;accesskit_winit0.34.1;kittest0.5.0.craft-uiatgit+…?rev=5c874d51…#5c874d51…, depending onegui 0.36.2.craft-docpathatgit+…?rev=82a24b00…#82a24b00…; no egui dependency.eframe,egui-winit,egui-wgpu,egui_kittest0.37.0 from crates.io;wgpu30.0.1;winit0.30.13 from crates.io.- No
[patch.crates-io], novendor/directory, and no machine-local source replacement..cargo/config.tomlcontains only thextaskalias.
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):
- Audio probe candidate selection.
crates/audio-io/src/symph.rsno longer treats any0xFFExpair as an MPEG/ADTS marker. It asks the registered readers for their exactprobe_data()markers and scores the candidate withScoreable::scoreover aScopedStream, mirroring the private probe's rules:Supportedselects, EOF/other scoring errors continue, and a non-EOF I/O error aborts. The scan remains the first actual candidate within the 1 MiB probe depth, so a false sync cannot suppress a later FLAC/CAF/MP4 check. - Regression coverage.
crates/audio-io/tests/malformed.rsnow prefixes empty FLAC and the existing malformed CAF/MP4 fixtures with a false0xFFE0sync and checks both publicprobeanddecode; a valid MP3 and a valid ADTS stream each carry incidentalcaff/kukimagic. The earlier-malformed-CAF-before-valid-CAF rejection stays covered. - ID3 footer.
id3v2_lenapplies the 10-byte footer only to ID3v2.4; v2.2/v2.3 bit0x10is reserved. An ID3-tag-only file remainsMalformed(the truncated/oversized class), noted rather than called a restoration of every prior ID3 classification. - RF64/BW64 alignment.
first_markerdocuments that its WAV marker includes RF64/BW64 forwav::check_info, while symphonia 0.6's probe registers onlyRIFF. Native PCM RF64/BW64 is parsed bywav::parsebefore this path; foreign RF64/BW64 stays unsupported rather than silently misdetected. - Playback stream invalidation.
crates/playback/src/devices.rsmapsStreamInvalidatedto the existing lost-stream/OutputTrouble::Gonepath (which offers Reconnect) instead of leaving a dead stream logging silently.DeviceChangedremains live andXruntransient;HostUnavailable,DeviceBusyandBackendErrorremain log-only. This is a conservative policy mapping, not a rebuilt stream and not a hardware result.
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
cfg(target_arch = "wasm32")/not:fonts.rs:46,82,106(system font loading is native-only),video_track.rs:93-105,217(thread vs inline decoder),bounce.rs:49-63(time slice vs 32 blocks),dialogs.rs:296-300(native picker),sampler_window.rsDroppedFileHandleweb bytes.cfg(target_os = "macos"):main.rs:19,43-50,199-203,286-301,native_menu.rs, icon include;muda,objc2,objc2-app-kit.cfg(target_os = "windows"):main.rs:264,graphics.rs(DX12/FXC),build.rsVERSIONINFO.cfg(unix):config.rs:299(settings directory/permissions).cfg(target_os = "linux")/ FreeBSD:fonts.rsfontconfig/fc-match, packaging; Linux CI is X11+Wayland.cfg(not(target_arch = "wasm32"))inapps/audio/Cargo.toml:26-27gives nativerfd.
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:
tests/docking_workflows.rs(drag float/close/reopen, reload, command ownership),tests/trackpad.rs(smooth_scroll_delta, pinch zoom, shift pan),tests/tracks_scroll_bar.rs(custom gutter paging),tests/side_lists_access.rs,tests/shared_controls.rs,tests/appearance.rs,tests/theme_docking.rs,tests/mix_instrument_contrast.rs(roles, colors, scale, theme),tests/save_changes.rs,tests/save_as_replace.rs,tests/recovery.rs,tests/bounce_dialog.rs,tests/edit_window_headers.rs,tests/midi_*.crates/ui-egui/src/lib.rs:1400-1545contains lifecycle/automation/autosave tests, includinga_hidden_window_still_autosavesusingContext::run_logic.
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
egui::Panel::top/bottomatedit_window.rs:109-113,menus.rs:224already use the unifiedPanelAPI; 0.37 only changes the salt constraint toimpl AsIdSaltandshow_insideis deprecated but not used. Keep panel order and ids.egui::Frame::NONE,CornerRadius,Modal,Popup::menu,Context::content_rect,Context::global_style,Context::set_theme(ThemePreference::System), andViewportBuilder/ViewportCommandcalls are already on the 0.37-compatible API surface.TextEdit::load_state/store,CCursorRange,has_glyphs, andTextureHandle::setremain available; their semantics need tests, not mechanical rewrites.Window::new's title-derived id is not a 0.37 change: 0.36.2 already usesIntoAtomsandId::new(title.text())(egui 0.36.2 crates/egui/src/containers/window.rs:102-105), and 0.37 usesId::unique. Keep the advice to pass a stable id for persistent windows, but do not list it as a new 0.37 incompatibility.- No
update→uirewrite and no genericContextcompatibility bridge is proposed.
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:
- The complete storytold egui P8 hand-off: E1–E21,
E4/P2b regular-clipboard and PRIMARY routing
(
get_primary_text/set_primary_text, per-seat owner/offer/serial handling, no second regularwl_data_device, password exclusion, no unchanged-selection reclaim) over the bounded async transfer path, P7 held-key release pairing plusE21, P7b/E21b outgoing surrounding text, and P7c's selected#8763/#8748fixes, plus justified performance fixes. Incoming E21 deletion/commit alone is not surrounding-text support. - The completed storytold winit: W15P PRIMARY on the existing wayland connection/per-seat lifecycle, the bounded W15 async transfer API (total deadline from admission, cumulative byte cap, bounded read/writer workers and queue depth, handle-owned result/cancel slot that cancels on drop, owner-specific completion waker; no unmanaged thread per request) and the W27 lifecycle revision with owner/focus epochs and pending preedit/commit/delete clearing. W15 is the regular clipboard path; it is not PRIMARY.
- The reviewed craft-libs CL-1 revision for
craft-ui/craft-docpathand the frozen shared capture helper (CL-2F). The eleven craft-ui consumers need CL-1 and CL-2F at this stage; the Litho/GridCraft pilot instead uses the provisional CL-2 API, and its adapters must precede CL-2F. Any incompatible existing craft-ui bump lands in this same atomic dependency stage. Reuse the API port hunks that now sit on the reconciled3832c47base (the earlier8614e4a/1764bcahunks on4919088are history); never merge PR #199's staleb68de8corec8840atrees.
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:
cargo metadata --locked --format-version 1and assert every egui family package has exactly one package id/source from the selected git SHA, andcraft-uiresolves to the reviewed craft-libs SHA.cargo tree -d --lockedshows no 0.36.2egui/ecolor/emath/epaint, no duplicatewinit, and no secondaccesskitfamily.cargo tree -e features -p audio --lockedshowseframe/clipboard,egui/default_fonts,egui/monochrome_emoji_fonts, and the intended winit/accesskit features. Repeat for-p audio-webunder the wasm target and checkeframeweb clipboard/file features. Also inspect the Audio UI test graph withcargo tree -e features -p audio-ui-egui --locked(dev-dependencies included) and the production-only graph withcargo tree -e features,no-dev -p audio-ui-egui --locked:egui/default_fonts,egui/monochrome_emoji_fontsandepaint_default_fontsmust 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] eguiisdefault-features = false; only its own[dev-dependencies]enable fonts, and a dependency's dev-dependencies do not propagate.cargo check --locked --target wasm32-unknown-unknown -p audio-ui-egui -p audio-web.cargo check --lockedfrom a clean cache; no source replacement, no local path, no branch dependency, and all git revisions reachable by a clean clone.- After the pin, add Dependabot
ignoreentries foregui,ecolor,emath,epaint,epaint_default_fonts,eframe,egui-winit,egui-wgpu,egui_kittestandwinit(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. - Record
git rev-parse HEAD, fullcargo treeoutput,Cargo.lockhash 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:
- E2+E3 in the suite fork (
egui-wgpusurface fit and error-scoped configure/renderer creation), relevant tographics.rsand resize/GPU failure paths. - E4 + winit W15 (regular Wayland clipboard) over the bounded async
transfer API, the separate W15P + E4/P2b PRIMARY path,
E19/E21 + W25/W26/W27 (non-Latin shortcuts, IME delete-surrounding), the
E21b/P7b outgoing surrounding-text state and
P7c's selected
#8763/#8748fixes, as one accepted P8 fork set. A pin without the bounded W15 transfer, W15P/P2b, E21b/P7b or the P7c fixes supports none of the complete clipboard, PRIMARY, surrounding-text or selection/deferred-child acceptance rows. - The app-local Windows DX12/FXC choice in
apps/audio/src/graphics.rs:11-30is a Audio fix and must be preserved. Do not delete it because the dependency changed. muda0.21.2,rfd0.17.2 andimage0.25.10 are non-egui dependencies; keep them unless a separate, reviewed reason exists.build.rsWindows resources andATTRIBUTION.mdassets are unaffected.
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:
logicpolls consumed control/plugin/result queues, revision-based autosave, transport/device state and capture deadlines. Multiple calls are safe: one queued command is consumed once, one pending open is taken once, revision changes are applied once and monotonic deadlines determine timed work. A first-pass guard may remain around genuinely per-layout-frame diagnostics, not the entire async inbox.uicalls an input-preparation method before layout for pointer/keyboard release and shortcuts. Raw dropped files are captured and cleared at the top ofui(before the!fonts_readyearly return) into a one-shot queue, then routed after the content has laid out the current sampler drop targets. Both are the known UI phase and run for every UI pass, including a forced hidden paint. Drain each input once; keep sampler hit routing and browser async read behavior. Native/web hostuimethods perform platform-specific open/import routing from the resulting one-shot actions. Synthetic input requests while hidden request a UI paint or return the documented error; they never report an unperformed action.App::raw_input_hookruns once per host frame inprepare_raw_input, beforerun_ui, and also on logic-only ticks (eframe/src/native/epi_integration.rs:315-340; webapp_runner.rshidden branch), so it is not a UI-pass hook. A synthetic step drained there on a logic-only tick has not been applied by a UI pass. Keep exactly one unapplied gesture step pending across repeated hidden logic ticks until a full UI pass consumes it; later hidden ticks must neither apply nor discard it, and a press/release pair must stay together rather than collapsing into one event. Test repeatedrun_logicticks with a queued click and key, then a full UI pass that applies each exactly once and in order.- Replace transport simulation's
ctx.input().timedelta with a monotonic clock whose last instant is updated after each advance. A second pass observes only additional elapsed time, never another full frame delta. Autosave and screenshot deadlines use the same injectable clock concept. Audio position remains owned by the audio player; do not move real-time work onto the UI callback. - Automation writes remain keyed by transport sample position and
last_write_at. Splitwrite_automation(ctx)so background work uses an explicit gesture state, not stale pointer input. Focus/visibility loss cancels the transient Touch drag; Latch/TouchLatch state continues according to its existing engine semantics. A current UI phase refreshes gesture state before processing new edits. - Fonts/theme installation remains idempotent. Frame statistics count rendered frames rather than hidden ticks or layout passes. Bounce processing uses an elapsed-time work budget and cannot get an extra full budget for every discard.
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
- Complete a two-pass frame, then run multiple hidden ticks through
the same public
logicmethod. Verify autosave, control replies, bounce and transport continue; assert the actual pass index behavior rather than inventing a stale nonzero index. - Drop one audio/session file through a real UI phase, then run hidden
ticks and a restore. It imports exactly once. Repeat with a forced
hidden UI paint (
RequestPaintWhileHidden): the drop must be handled on that paint and not replayed afterwards. Repeat again on the very first frame, while fonts are not ready anduireturns before layout; the drop must survive in the one-shot queue and import once on the first laid-out pass. Gating onvisible()must make the forced-hidden case fail; leaving the raw list uncleared must make the repeated-tick case fail; reading drops only after layout without pre-layout capture must make the first-frame case fail. - Queue a command and force a discard. Both native-shaped and web-shaped drivers produce one mutation, one reply and one undo transaction. Deliberately replay the same consumed command to prove detection; merely moving a drained queue to UI is not guaranteed to duplicate anything and is not a valid mutation by itself.
- Advance an injected monotonic clock while egui input time remains unchanged. Hidden simulation advances by that elapsed interval; an extra layout pass cannot apply the same interval twice. Restoring the stale input-time clock must fail.
- Exercise Touch, Latch and TouchLatch across pointer release, focus loss, minimize, hidden playback and restore. Check exact automation points and merge boundaries.
- Request a hidden UI paint with synthetic input and capture. Current
input executes once even though
visible()is false; ordinary logic-only ticks cannot replay it. - Complete a capture after timeout/cancel/shutdown: no file, no second reply and no retained image. Use a deliberately misrouted request slot to prove the pixel and reply assertions detect cross-request delivery.
7. Panels, docking, IDs, viewports and geometry
- Keep panel order:
menus::menu_bar(menus.rs:224) before the Edit/Mix content; Edit's toolbar/status/lower-tabs/universe panels atedit_window.rs:109-113; theDockAreachild atdocking.rs:410-434; all floating windows/areas after the main content (lib.rs:1254-1265).Panelconstructors acceptimpl AsIdSaltin 0.37; keep stable string salts and do not rename them during the mechanical migration. DockArea::new(egui::Id::new(("audio-docking", edit)))andcancel_drag::<Panel>(ctx, egui::Id::new(("audio-docking", edit)))atdocking.rs:332,410must change together toId::unique. A mismatch leaves a drag active after a workspace replacement;tests/docking_workflows.rsand craft-ui's cancellation tests are the regression.- The custom Tracks/Groups scrollbar (
panels.rs:155-185) must useout.state.clamped_offset().yfor drawing andScrollArea::vertical_scroll_offsetfor a pending target. Do not write the privateState.offset. Because the target is applied next frame,tests/tracks_scroll_bar.rsneeds a one-step settle after a page click; assert the offset is not overwritten by a wheel event in the intervening frame. audio-midi-bodytemporary data (docking.rs:429, testspro_tools_keys.rs:216,midi_panel.rs:16,new_midi_clip.rs:133) usesId::new; convert all producers and readers toId::uniquewith the same string. Do not leave a stale reader.- Viewports:
mix_window::show_separategives the child its own&mut Ui; do not callroot_uithere. Preservefocus_mix, close-request handling, andseparate_closed. In headless testsContext::embed_viewportsis true by default; native eframe sets it false. Headless tests can assert state transitions and no panic; native X11/Wayland/Windows evidence is required for real popout focus/close, mixed DPI, and parent-hidden behaviour. Upstream #8748 is closed and unmerged, not 0.37 behavior. The current Mix window uses immediate viewports (mix_window.rs:25-38), so it does not inherit the deferred-child fix. Keep the immediate-viewport limitation explicit. If the Mix window later moves toshow_viewport_deferred, require the fork's tested hidden-parent/deferred-child fix plus open/update/close under an overdue or occluded parent before claiming it. - Geometry/hit assertions: at 1600x1000 and 1400x900, with 1x/1.5x/2x,
assert the menu bar height, timeline rect, track row heights, clips
panel width, mix strip/narrow strip widths, dock tab rect, and the
ui.inspectedit_layoutvalues. Existing tests already derive many of these from painted shapes; retain that style. - Clip/nesting assertions: docking bodies must clip to their panel
rect intersected with the parent clip (
craft-uisurface.rsusesset_clip_rect), andminimum_bodymust not paint outside its scroll viewport. Add a shape-visibility assertion for a floating panel overlapping the canvas and for the MIDI body at its minimum size. - Persistence:
UiState/Workspaceremain the source of truth for layout, not egui ids. Test float/dock/close/reopen, reload, hidden panels and narrow Mix after the craft-ui update.
8. Input, IME, clipboard, PRIMARY and scrolling
- Enable
eframe/clipboardon native. This is a real behavioural restoration, not a cosmetic feature: 0.36's eframe target dependency forcedegui-winit/clipboard(eframe 0.36.2 Cargo.tomlnative dependency), while 0.37 exposes it as an optionalclipboardfeature. Without it, copy/paste of session names and text fields uses egui's in-app simulated clipboard. - Test copy/paste from every text surface: session name, track/bus/marker/clip rename, Spot fields, search fields and plugin/bus search. Native external paste and middle-click PRIMARY require a real X11/Wayland session; headless tests only establish event routing.
- egui 0.37 has
Event::MiddleClickPasteandMouseWheelSource. E4/W15 replaces smithay with winit's regular Wayland clipboard over the bounded async W15 transfer API: a total deadline from admission (2 s default, never reset per poll), a cumulative 16 MiB byte cap, at most 4 concurrent reads, 2 writer threads and queue depth 8, cancellation, stale-result discard and a live owner-specific completion waker. The transfer handle owns result/cancel/wake state and cancels on drop; no unmanaged thread is spawned per request. A bounded synchronous compatibility read may remain but must not run on the egui event path. PRIMARY is separate: it requires winit W15P plus egui E4/P2b, on the existing connection/per-seat lifecycle, with one regularwl_data_deviceper seat; a selection claim validates the triggering serial and seat synchronously while fresh, and only serving is asynchronous. A build with only W15/E4 has no native PRIMARY path (the candidate falls back to arboard/smithay). PRIMARY acceptance runs on wlroots/Hyprland, KWin and Mutter plus X11, and covers keyboard- and pointer-triggered selection serials and freshness per seat, owner/source/seat changes, no arbitrary first-seat fallback, empty/absent offers, self-paste, and slow/canceled/trickling/oversize transfers. Audio has no password field today, so password exclusion is a fork-level requirement rather than an app workflow; the unchanged-selection non-reclaim rule still applies to every app selection. Record each compositor's capability rather than assuming parity. ui.scroll(control.rs:187-197) must setsource(Wheelfor line units,Trackpadfor point units). PreserveTouchPhase::Start/Move/Endand test that 0.37 kinetic/rubber-band behaviour does not turn a synthetic paging action into an unbounded scroll. For exact offset tests, set the test style'sspacing.scroll.kinetic.decay_timeto 0 (or assert direction and bounds instead of exact pixels).- TextEdit click-away: upstream #8763 reports that a click outside a
focused field collapses its selection before the clicked button runs;
proposed PR #8764 is open/unmerged (head
6eb460a2a6d537d312a80579cb801895509deaed), not released behavior. The suite fork owns the selected P7c fix with pointer and keyboard regressions. Audio has inline rename fields (panels.rs:411,dialogs.rspath/name fields,spot.rs) and buttons beside them. Require the fork fix; permit an app-local workaround only with preserved selected range and undo semantics plus an explicit removal condition. Test select-then-click on a neighboring button through both pointer and keyboard paths. - IME/E21/W27: test preedit, commit, replacement, cancellation and
click-away in every rename path. Incoming deletion/commit is only half
the contract. The accepted pin must satisfy the complete shared
egui/winit E21b/P7b plus W27 lifecycle contract, not a
weaker subset: an owner/context generation identity that is present even
when surrounding text is Unavailable; a distinct
Available-empty state sent explicitly as
set_surrounding_text("", 0, 0)plus commit, while Unavailable omits the request after a committed clear boundary; a bounded <=4000-byte payload with UTF-8 byte cursor/anchor, preedit excluded and no full-document allocation for this new payload (the existing TextEdit change-detection/undo clone is recorded separately); clearing cached state and committing the disable/enable boundary on owner or available/unavailable transitions, since changing purpose or sending empty text is not a clear; cancelling pending preedit/commit/delete on Leave, disable, reset and seat removal; the final completed pass'sPlatformOutputas authoritative includingNone, notime.or(self.ime)accumulation; and a current-epochDonewith a mismatched serial applying incoming edits once while deferring outgoing state until a matching serial, with obsolete owner/epoch batches invalidated. Audio's fields are built-inTextEdits and inherit the producer; it has no custom document editor to adapt. Test outgoing text immediately after delete+commit and deletion-only batches, preedit exclusion, supplementary/combining/RTL text, field and two-window switches, focus loss/reset/late serial, current-epoch serial mismatch versus stale epoch, password/no-context clearing, and initially-empty/available-empty/unavailable transitions. Without this complete contract the "updated surrounding text" row is an unresolved gate, not a pass. The locked fork referencesunicode-bidi 0.3.18(Unicode 16) andunicode-segmentation 1.13.3(Unicode 17); conformance vectors must be pinned to those data versions. A similar Mozilla report (bug 2080478) is motivation, not proof of a Audio defect. - RTL/CJK names: the fork's E5/E12–E16 shape and reorder RTL text;
Audio's own layout remains LTR. Test Arabic/Hebrew session names and
Korean/Japanese names using the pinned fixtures from §9.2 (Noto Sans CJK
SC covers Hangul; BIZ UDPGothic covers Japanese), not a nondeterministic
system fallback. Include combining marks and emoji clusters. Do not add
an app-wide
right_to_leftstyle in this migration; the product UI is LTR and the fork's Style mirroring is not enabled. - Non-Latin keyboard layouts: E19/W25. Test a Russian/Arabic/Hebrew layout on Windows and Linux: letter shortcuts must not fire from a non-Latin character; Ctrl/Cmd shortcuts still work. If P1 only, record this as an unresolved gap, not a pass.
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:
- Enable
default_fontsandmonochrome_emoji_fontsfor theeguipackage used byui-eguiand the native app.eframe/default_fontscovers the binary, but not an isolatedcargo test -p audio-ui-eguigraph.egui0.37 declaresdefault = [];egui_kittest's normal[dependencies] eguiisdefault-features = false, and only kittest's OWN[dev-dependencies]enabledefault_fonts/monochrome_emoji_fonts. A dependency's dev-dependencies do not propagate, so kittest supplies no fonts to Audio's tests. Prove this per root withcargo tree -e featuresfor the production binary, the Audio UI test target and (separately, out of scope) kittest's own test root; treat the no-bundled-fonts prediction as a resolution claim until the command runs. - Keep
eframe/system_fontsoff for this migration. That avoids eframe's new provider, not all font-discovery cost: Audio's own loader still runs up to threefc-matchcalls (regular, bold, Hangul) and reads the returned files on Linux/FreeBSD (fonts.rs:53-79,:109-139). Measurefc-match, file reads and font install separately from egui work, cold and warm cache, and do not attribute the retained cost to egui. Re-evaluate a system provider only with startup measurements and coverage tests. - Keep the runtime
AUDIO_NO_SYSTEM_FONTSescape hatch. It is useful for reproducible UI tests and for minimal systems. - Keep the app's
FontIdcontract (theme.rs:439-451: proportional, monospace and the explicit"bold"family) and its sizes; do not inherit a new 0.37 default text style for track headers, mixer labels or counters. The bold family must retain the same fallback order after theFontDefinitionsfeature change. ATTRIBUTION.mdcurrently names Ubuntu-Light, Hack, Noto Emoji and emoji-icon-font as fonts shipped inside egui.monochrome_emoji_fontskeeps that statement true; if the feature is not enabled, the statement must be corrected. No new repository asset row is needed because these fonts remain inside theepaint_default_fontsdependency; its license texts travel with it.
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:
- craft-fonts at
2481a8a324b986a45954e90045ed1f8f2efaaf3c, each OFL-1.1 with its adjacentOFL.txt:- Noto Sans Arabic
63111b5b2e074dd48cc67692e0a2726d86ee94c1c37fe8598257b7b4e87e869e - Noto Sans Thai
5a1c559bb539583c8a1fd99d1c5b9491e5e14478c9cd2bd0970d5c3096cc9ef8 - Noto Sans CJK SC
2c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b - BIZ UDPGothic Japanese
258d7156c165f2ff774b6efee637c22c3b950de0d8a10e501137061bc8085d01
- Noto Sans Arabic
- Noto Sans Hebrew variable font from google/fonts at
8b0a1d0f5983c89bc2b93f1b5fb55f9e252744b5, SHA2567ef36a2c3593758cdb622e1bdef4f84523e92fbc3ccc667438dd80ff54c2de88, weight 400 / width 100, with itsOFL.txt. - Noto Color Emoji v2.047 from googlefonts/noto-emoji at
22e564626297b4df0a40570ad81d6c05cc7c38bd, SHA25639ee3c587e10e89669b9ff32703261d10d5f9c4dd5ad147b6b5a1c5200591817, OFL-1.1.
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:
text_toggle(widgets.rs:117-125) draws a key and only callson_hover_text; a tooltip is not an AccessKit name. Addresp.widget_info(|| WidgetInfo::selected(Role::Button, enabled, on, tip)).fader/pan_knob/surround_panner/multi_meter(widgets.rs) and custom track/timeline controls needWidgetInfowithRole::Slider/Button/CheckBox, label and value; output-only meters should not be advertised as inputs.TextEdits with only an adjacentui.labelneedResponse::labelled_byorUi::label_inputs_by; hint text is acceptable only when it is a real field name (search).DragValue/Sliderinstances without.text(...)inops_windows.rs,windows.rs,dialogs.rs,toolbar.rsneed a label.- Group rows use
Role::CheckBox; track rows useRole::Buttonwith toggled state, matchingtests/side_lists_access.rs. The role migration must keep those exact roles. - Hidden controls must disappear from the tree; 0.37's #8621 makes invisible widgets hidden. Do not add placeholder nodes.
- Top-level
Window/Area/Popupnodes need a stable accessible name where egui does not derive a useful one (0.37 addedArea::role/accessible_nameand improved popup/menu trees). Keep modal/dialog focus inside the modal and restore it to the invoking control on close.
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:
SoundApp::run(lib.rs:418-453) dispatches UI-layer commands viamenus::run_ui_command, thenEngine::execute;app.quitsetsquit_requested.Engine::executepushes one undo snapshot per changed document; continuous gestures useexecute_merged(mix.volume,mix.send_level, automation pass). Every additional logic run or UI pass must not create a second undo step.session.save*/session.open*remain engine/io operations; UI changes must not intercept them or require a paint.UiStateprefs (window_size,window_maximized,ui_scale, docking, recent sessions) are saved by the host; hidden logic must still update the recent flag.
Regression assertions:
- A two-pass frame triggered by a popup/menu executes one command and one undo step.
- Fader/send drag still produces one merged undo step; a pass with no pointer input does not close the merge prematurely.
- Save → close → reopen preserves tracks, clips, fades, mixer levels,
sends, automation, markers, groups, busses and selection;
save_changes.rs,save_as_replace.rsandrecovery.rsstay green and gain a richer fixture. - Close/reopen a floating dock panel and restore its workspace;
docking_workflows.rskeeps command ownership and transport commands. - Recovery after a crash/exit without Quit still offers the autosave; hidden logic must not stop the autosaver.
ui.screenshot/ui.inspectcontrol replies remain one request → one reply, including hidden ticks and timeouts.- A mutation probe that makes
runexecute twice must fail the undo assertion; a probe that moves autosave intouimust fail the hidden-autosave assertion.
11. GPU, video, textures and rendering
- Preserve
apps/audio/src/graphics.rs:11-30: Windows initializes only DX12 and defaults to FXC because GL initialization and missing DXC/DXIL break real machines. The 0.37 family keeps wgpu 30.0.1 and winit 0.30.13; do not change these defaults as part of the egui bump. - E2+E3 (surface fit and error-scoped configure/renderer creation) should be required in the fork/release pin if Audio keeps wgpu; verify resize, maximize, DPI changes, and device/ surface failure paths return errors instead of panicking.
VideoPool(video_track.rs:281-430) usesload_texture("video-frame", ...), per-thumbnail names, andTextureHandle::setfor the current frame. Tests setmax_texture_side = Some(8192); keep that and add a test for an oversized frame being rejected/ clamped without a debug assertion.- Screenshot callback images are CPU
Arc<ColorImage>; do not upload them as textures. Encode PNG and write files only incollect_screenshotson the UI thread. egui_kittestdefaultRendererOptions::PREDICTABLEgives deterministic test images; native eframe uses its own defaults. Record the renderer/backend/options for every screenshot and performance run. Software-rendered Windows and a real GPU are separate evidence rows.- 0.37 upstream reports that need reproduction, not automatic
acceptance:
- #8731: plain-text tessellation regression from the color-glyph check. Audio is text-heavy (menus, track headers, mixer labels). Measure text tessellation in the frame benchmark; if confirmed, adopt a row-level fast path in the fork with mixed/color-row tests. Do not use a clamp-only or output-changing shortcut.
- #8736: DX12/FXC shader compile cost in
egui-wgpu0.37. Audio explicitly selects FXC. Measure renderer construction and first frame separately from the rest of startup. A float/selectwrap implementation or a pipeline-overridable constant is acceptable only with Repeat/MirroredRepeat/clamp pixel tests; a clamp-only change is not equivalent. - #8512: system-font discovery/startup cost. Keeping
system_fontsoff avoids eframe's provider only; Audio's own loader still performs up to threefc-matchcalls and file reads. Measure those, and font install, separately from egui startup, cold and warm cache. - #8763/#8764: TextEdit click-away selection. #8764 is open/unmerged; the fork owns the fix. Test the inline rename and Spot paths through pointer and keyboard (§8).
- #8748: closed and unmerged, not 0.37 behavior. Current Audio uses immediate viewports; keep the limitation explicit and revisit only if the Mix window moves to deferred viewports.
- #8657 and #8577: Wayland clipboard/PRIMARY and bidi. PRIMARY means W15P + E4/P2b, not W15 alone; test native compositor workflows, not just compilation.
12. Native, web and platform workflows
12.1 Native (Linux, Windows, macOS, FreeBSD)
- Linux X11 and Wayland with a named compositor: launch with
--demo --control 0 --no-audio, inspect and screenshot via the control channel, exercise track rename, clip import, mix fader/send, docking drag/float/close/reopen, MIDI/Score, save/reopen, and copy/paste. The planning server has noDISPLAY/WAYLAND_DISPLAYsession; this can only be run in a managed native environment, and must not be reported as passed from headless renders. Read-only device inspection found an i915 render node (/dev/dri/renderD128) but no display session; that is availability evidence only, not a qualified Vulkan/OpenGL compositor result. - Wayland PRIMARY matrix: wlroots/Hyprland, KWin and Mutter, plus X11
regular clipboard. With W15P + E4/P2b installed, verify keyboard- and
pointer-triggered selection serials and freshness per seat,
owner/source/seat changes, no arbitrary first-seat fallback,
empty/absent offers, self-paste, unchanged-selection non-reclaim, and
slow/canceled/trickling/oversize transfers. Record each compositor's
capability/version and
WAYLAND_DEBUG=1protocol trace showing one regularwl_data_deviceper seat. A wlroots-only pass is not the matrix. - Two-viewport screenshot correlation: run native two-window evidence or a custom per-viewport renderer. kittest renders one image for all callbacks and cannot prove this.
- Windows: use a uniquely owned clone from
/home/codex/work/VMs/craft-windowsand itsACCESS.txt/READY.json; build with the reviewedBuild-Litho-style equivalent for Audio orcargo build --release --locked -p audio; verify FXC/DX12, mixed DPI, native file picker, save/reopen and screenshots. The baseline uses the software renderer, so physical-GPU/audio claims are separate and unresolved. Capacity gate before allocation: the default 128 GiB full clone does not fit the recorded allocator snapshot (about 23.6 GiB and 37.7 GiB allocatable on the two drives, not combinable). Inspecttask-storage.py statusfirst. Use a checksum-verified overlay clone with a reservation covering expected guest growth (at least the documented 10 GiB minimum) if it fits, or record the Windows row as blocked by capacity. Do not lower the reservation to pass the guard. - macOS: native menu (
muda), traffic lights, ⌘ shortcuts, Metal/wgpu, IME commit/deletion, file picker and termination need real macOS evidence. Cross-compilation is not acceptance. If unavailable, this is an explicit rollout blocker, not a pass. - FreeBSD: existing nightly workflow; compile/packaging can be checked, but native UI/IME/audio acceptance needs an actual FreeBSD environment (not provided here).
- Native control channel: use
--control 0andAUDIO_NO_PREFS/a scratchAUDIO_CONFIG_DIRfor isolation; never arm/monitor/record in an automated UI run (docs/control-protocol.md:22-25).
12.2 Web (wasm)
cargo check --target wasm32-unknown-unknown -p audio-ui-egui -p audio-web(part ofcargo xtask wasm) is a compile gate only.cd apps/audio-web && trunk build --releaseproducesdist/web; usepackaging/web/package.shfor the zip and relative-path checks. Record raw and compressed wasm size, first-interactive time, WebGPU and?webglfallback,?emptyand demo sessions.- Browser workflows: open demo, use the file picker and drag/drop,
import audio/MIDI, save a
.aprojzdownload, play after a user gesture, resize, and inspect the page. Browser screenshots and console logs are evidence; a native screenshot is not browser evidence. rfdbrowser picker andDroppedFileHandle::bytes_asyncare asynchronous. The raw drop list is consumed in the knownuiphase; completed byte reads are a separate consumed queue and may be handled inlogic. Test that a picker/drop is handled once and not repeated on subsequent frames, including a forced hidden UI paint. The browser has no control channel, so browser UI testing needs a page driver (Playwright/Chromium or equivalent), not the TCP protocol.
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
- Capture baseline images from
e85a4801e9029c9a1f5ce536b11be65aea832ed8and candidate images at the same fixture, window size (1600x1000 and 1400x900), DPI (1x/1.5x/2x), theme (dark/light/system), fonts (bundled-only test set) and renderer (egui_kittestwgpu withPREDICTABLE, or the named native backend). Store them under the task evidence directory, not as unreviewed committed goldens. - Prefer semantics/geometry over images. Exact RGBA equality is
required for flat palette fills, custom control faces, text colors and
shape rects, as existing
mix_instrument_contrast.rsandshared_controls.rsdo. - For full-window raster comparisons, use a documented mask: allow per-channel delta ≤3 in at most 0.5% of pixels, no delta >32 outside reviewed regions, and zero missing-glyph/tofu pixels. Any difference in panel/window geometry, focus ring, scroll thumb, or text position is a failure until reviewed. The only expected style changes are the reviewed 0.37 default button/corner differences; restore Audio's explicit values where the product should not change.
- Never bulk-refresh a golden after seeing a failure. A changed image is accepted only with a written reason and a corresponding geometry/semantic assertion.
- Mutation controls: replace a
RolewithUnknown, remove the wholewidget_infocall, remove an accessible name, shift a panel rect, drop a screenshot callback token, and replay a consumed command; each must fail its intended test. Remove the defect after proving detection. - kittest renders one image for every viewport screenshot callback
(
egui_kittest/src/lib.rs:914-942). Never use it to assert two-viewport pixel correlation or swapped-slot detection; those require a native two-window run or a custom per-viewport renderer.
13.4 Performance protocol
Record baselines before production edits. Four variants are distinct, and the report must not collapse them into "three":
- Historical coherent baseline:
e85a4801e9029c9a1f5ce536b11be65aea832ed8(0.36.2 egui family, its own non-UI dependency set). - Current-engine / 0.36-UI control: current non-UI
dependencies (the 11-package bump:
symphonia0.5→0.6,cpal0.16→0.18,zip9.0.1→9.0.5,libloading0.8.9→0.9.0,muda0.19.2→0.21.2 and the rest) with the egui family reverted to 0.36.2 andcraft-uiback at5c874d51eeb3de70e735c8ce87a59a7dedd47675. 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. - 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. - 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:
- Startup: 20 fresh processes, demo session,
--no-audio, time to first painted frame; on Windows also timeegui_wgpu::Renderer::new/pipeline creation separately (FXC, #8736) and font install/fc-matchseparately (#8512). - Idle: 60 s with no input; frames/repaints, CPU%,
median frame interval, idle
request_repaintcount. Any new busy loop or audio underrun is a failure. - 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. - Mix: 64 tracks, meters live (no-audio simulation and, separately, player), 60 Hz, 1,000 frames; strip layout and meter paint.
- Windows/dialogs: open/close MIDI, Score, Video, docking float/resize, About/Preferences; operation latency and frames to settle.
- Video: one 1080p frame decode + texture update cycle, repeated; report upload bytes and frame time.
- Screenshots: request-to-callback-and-file latency, pending queue depth, peak image bytes.
- 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
- 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. - 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
3832c47base (8614e4a/1764bcaare 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. - 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. - 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.
- 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
- Patch section is in the root
Cargo.toml, not a dependency's manifest. - Every selected egui family member and winit is at one immutable full SHA; no branch names, no path dependencies, no machine-local source replacement.
Cargo.lock.version = 4may remain; regenerate it through Cargo, do not hand-edit package identities. Record old and new lockfile hashes.- The shared libraries are consumed by reviewed git revision for this migration. A separate crates.io publication or stock compatibility promise is not implied. Audio must repeat required root patches and never depend on machine-local path overrides.
cargo package/cargo publishis not applicable toaudio(publish = false); still inspect the generatedCargo.lockand metadata for accidental local paths.- Dependabot must not silently re-split the family; add ignores or an all-family update rule.
- A clean clone with a cold cache must fetch every git revision and
pass
--lockedwithout local overrides. Recheck remote heads before merge; a moved branch is not a revision.
16. Blocking unknowns and explicit acceptance criteria
Blocking unknowns:
- Exact reviewed
craft-libscommit after applying PR #199's intended hunks to current main. The local reconciliation base is3832c47ef68a0e6214f2c1e85f3526b6adcb686e;b68de8c/ec8840aare stale trees and must not be merged, and the historical8614e4a/1764bcahunks on4919088are no longer the base. Until a reviewed reachable SHA exists, P1 cannot pin it. - Organization-owned egui/winit SHAs, including the required
new work.
aiongg/craft/0.37andaiongg/winit craft/0.30.13-langare movable research inputs. P1 requires approved, reachablestorytoldfull 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. - CL-2F capture-helper API. P1 sequences after the
shared helper's approved freeze. The inspected
4919088tree had nocrates/ui/src/capture.rs, and the current source importscraft_ui::capture::CaptureSlotfrom the local craft-libs worktree. The reconciled3832c47base must be confirmed to carry the agreed CL-2F API when the final pin is chosen; a provisional API must not be pinned. - 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.
- 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. - Browser workflow. Compilation to wasm is not a
browser test; a Chromium/Playwright-style driver, a running
trunk build --releasesite and download/picker/audio checks must be available or the browser row is unresolved. - 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:
- Exactly one immutable egui family and one winit revision in the accepted lock; no 0.36/0.37 duplicate; reviewed craft-ui revision; no local/branch/path dependency.
- All repository gates plus the selected feature, native Windows/Linux, wasm compile and browser workflows pass with recorded counts and evidence.
- The lifecycle tests prove no duplicate or lost control/autosave/transport/undo/screenshot work in multi-pass and logic-only frames.
- UI geometry, screenshots, accessibility, fonts/glyphs, input/IME/clipboard, docking/viewport and save/reopen assertions pass with mutation controls shown to detect their intended defects.
- Performance meets the predefined thresholds with paired, same-environment measurements across the four distinct variants (historical coherent baseline, current-engine/0.36-UI control only if it compiles, equivalent stock-0.37 control, fork), with non-UI dependency deltas and native-versus-synthetic timings reported separately; any accepted regression is an explicit, reviewed tradeoff.
- The complete egui P8 and completed winit (W15P, bounded W15 transfer, W27 lifecycle) are present in the accepted pin; PRIMARY (keyboard and pointer serials), bounded clipboard transfer, outgoing-IME lifecycle, two-viewport capture and rollback-rehearsal gates have real evidence or remain explicit blockers.
- No publication occurs until the user has seen the exact commit range and validation and given fresh approval.
17. Evidence and limitations of this planning document
- Source inspected: all root/nested manifests and the root lockfile;
crates/ui-eguisource and tests; desktop/web/CLI entry points; build.rs; graphics/native-menu/config; xtask and workflow files; selected upstream 0.37.0 and fork source (a4f9a17fegui and8b35388bwinit);egui_kittestaccessibility/screenshot code and its normalized manifest feature split; eframeprepare_raw_input/update_logic_onlyand the web hidden branch;egui-wgpusurface_fit/render_scale; workspace lints; craft-libs PR #199/audit evidence; suite rollout notes and upstream discussion snapshots (upstream-8764-fresh.json). - Static checks performed for this revision:
git diff e85a480..52471b3(manifests/lock only);#[test]counts (108 src, 156 tests, 1,159 workspace); cmap scan of theepaint_default_fontsfiles; source presence/absence checks forcapture.rs,set_ime_surrounding_text, W15P,AsId, 0.36.2Window::new, and the kittest single-image screenshot completion. These are inspection results, not runtime evidence. - No build, test, render, screenshot, benchmark, VM, push, PR or message was executed. The historical audit logs and the earlier fork/stock compiler errors are clues only.
- A read-only
cargo treewas blocked offline by a missingsymphonia-commonregistry index entry; feature/graph claims are from manifests/lockfile and must be rechecked in a managed job. - The plan intentionally uses placeholder names for revisions and a test helper; it does not invent a merge SHA, an upstream API, or a benchmark result. Those placeholders must be replaced by exact reviewed values before implementation.
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:
MouseWheel.source(Wheelfor line units,Trackpadfor point units), noEvent::Screenshot/UserData,Roleinstead ofWidgetType(SelectableLabel→Button,Checkbox→CheckBox),Id::unique,Ui::scope_id,TextEdit::align(CENTER_TOP)for the old horizontal center.- Screenshot lifecycle: app-owned strong
craft_ui::capture::CaptureSlotper request, weak renderer callback, 8 pending / 4 in flight, 64 MiB per image, 192 MiB aggregate encoding peak (image, raw copy, PNG buffer and base64 text), checked byte reservation from the capture-time viewport, 8 s monotonic deadline checked before any write, geometry and session binding with one retry path, cancellation on session/focus change, and exactly one reply.ui.screenshotsuccess now includes the request token; error strings are documented indocs/control-protocol.md. - Lifecycle:
logicno longer returns early oncurrent_pass_index; frame pacing and the transport simulation use a monotonicweb_time::Instant;raw_input_hookkeeps exactly one unapplied synthetic batch across hidden ticks; a settled UI pass acknowledges it and issues captures after layout; dropped files are captured before the font gate and routed after layout, once. - Control ordering: a scripted gesture's reply waits until a UI pass applied every event, later control requests behind it wait too, and an undeliverable gesture is canceled after 15 s. This makes a following inspect/engine command/screenshot see the post-gesture state. Scripted input does not publish OS clipboard or PRIMARY commands from its pass.
- Fonts/features:
egui/default_fonts+egui/monochrome_emoji_fontson the UI and app roots,eframe/clipboardon native, a bundled-face glyph test, and drawn/ASCII replacements for the uncovered▸,▾and♯glyphs. - Accessibility names for the custom toggles/selectors/faders and bare text fields used by dialogs, inline rename, Spot and comments; repeated per-track Edit/Mix controls now carry track-qualified accessible names and their current value. This is not the full §9.3 node inventory.
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.
- Synthetic input:
raw_input_hookrecords the exact events it appended (batch plus the hoverPointerMoveds) and removes that run before re-appending, so a host that retains its hook-mutatedRawInput(eframepending_raw_input, webinput.raw) holds one copy, not one more per hidden tick. A pending batch now sendsRequestPaintWhileHidden, the synthetic grace keeps being refreshed while a batch is pending, and a settled pass acknowledges the batch only if that pass actually received its events. - Lifecycle: frame statistics are sampled once per settled UI pass
from the monotonic clock (not per layout pass and never on hidden
ticks); a bounce slice runs on the first pass of a host frame only;
last_frame/record-hint freshness uses monotonic uptime; hidden logic ticks clear the cached pointer/keyboard state and a transientGesture::Fader, and automation writing uses that cached state instead of stale input. - Capture:
recoveranddialogs::open_pending_sessiongo throughSoundApp::document_replaced, so a pending capture is canceled for every document replacement path; the byte check accepts the same one-pixel tolerance as the dimension check; truncated images fail as malformed instead of succeeding; a missingmax_texture_sidefalls back to the context's last known limit; a window with no known geometry reportsscreenshot unavailablerather thantoo large. - Accessibility: I/O Setup bus names, the Score title/composer fields, per-strip faders and the inline pan/volume/send readouts now have accessible names; repeated controls are named per track.
ui.scrollpoint events are trackpad-like; the deterministic control tests disable kinetic coast and rubber-banding, anddocs/control-protocol.mddocuments the behavior.
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.