91 KiB
egui 0.37 migration plan for craft-libs
Status update (2026-10-25): local candidate includes main 865c9af7fdf455cd3892402d18e526a9f019c6bc, preserving current native docking, blocked-window focus and print-copy handling. Native input-callback cancellation no longer re-enters the plugin mutex. Both cancellation regressions reproduced the deadlock before the fix. Current gates passed: 1,774 test executions, zero failures, 21 ignored, plus strict workspace/UI/print lint, UI all-feature tests, native example build and UI WASM check. Independent merge/implementation and compiler-correction reviews passed. App-pinned ancestors 25da1314 and 650b1c0 remain reachable. Current-source native platforms/GUI/printer hardware and paired performance remain open; the attempted paired run stopped before measurement due to a collector metadata-projection bug. Publication needs separate explicit approval; human review and human merge are required.
The original planning record below is historical.
Status: final landing integration, 2026-10-25. The reviewed shared UI includes screenshot completion, shaped-text alignment, keymap support and regression coverage. The manifest and lock select egui 6d518fc799681571963842f95b55a6b68431b15a and winit a517b3846ee8457d84ee4975110bfda6797c44dc through canonical GitHub URLs.
The integration of migration ancestor 25da1314d443171b4adeaad2cd9454b273f35c6f with main 2ab6049d9031bc8fae7f7df669b5fccaf5b9966f passed formatting, strict workspace all-target clippy, workspace tests and the PDF all-feature suite in managed job egui037-craft-libs-current-main-gates-20261024-2021. Source and lock hashes were checked against that job before the local integration checkpoint.
New main 6e7f28518dd1cb57e1624bdc269c3c14316b13ae adds docking, native floating windows and other shared-library changes. Its integration passed fresh review. That review identified an IME reset acknowledgement lost when a native floating window was blocked; the fix preserves ordered control messages in both raw and processed panel input, with a native-driver regression. A second review accepted the fix.
Managed job egui037-craft-libs-native-fix-gates-20261025-0328 passed formatting, strict workspace lint, workspace tests, UI all-feature lint/tests, the native docking example build, UI WASM check, and prepress/plate-test all-feature lint/tests with hash-verified licensed PDF test fonts. Its 121 test summaries total 1,723 passed, 0 failed and 25 ignored; overlapping feature suites are included in those totals. PDF all-feature tests also passed in job egui037-craft-libs-new-main-ci-20261025-0242. Further shared-crate feature/platform matrices, visible native-window workflows and final performance acceptance remain open. App-pinned migration ancestors remain reachable. The prepared benchmark protocol has no accepted timing result; the completed egui text benchmark remains separately inconclusive. Earlier test counts below are historical snapshots.
Every push or new PR requires approval of its exact commit range and validation. Human review and merge are required. The baseline tables below preserve the original planning record.
This plan covers only storytold/craft-libs. It records the shared-library work needed before the
Craft applications move to the egui 0.37 family. Application-specific UI migrations, their document
models, command routing, persistence policy and native window ownership remain in those applications.
The suite rollout plan supplies the shared publication order, measurement protocol and cross-application acceptance contract. Stricter repository gates apply.
1. Baseline and provenance
1.1 Exact source baseline
| Item | Revision / value |
|---|---|
| craft-libs checkout | 49190884278f16c9a9f5823d7698a8ffb5deb062 (Merge pull request #238 ..., 2026-10-22) |
| older local comparison point | b1401e3 (stale local origin/main; not the current remote baseline) |
| lockfile | Cargo.lock, format version 4 |
| current egui family | egui, epaint, ecolor, emath, eframe, egui-wgpu, egui-winit, egui_extras, egui_kittest all 0.36.2 |
| current winit | 0.30.13, crates.io checksum a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d |
| current accesskit | 0.24.1; accesskit_consumer 0.35.0; kittest 0.4.0 |
| target upstream release | egui annotated tag 0.37.0, peeled commit 841a328ba2dd8f6996beb7189ebb7fc00e668a2a, released 2026-10-15; not main |
| egui source candidate | aiongg/egui craft/0.37 at a4f9a17f64a6bae61350fb85804c9df0d9865916; integrate/review into storytold before production pinning |
| winit source candidate | aiongg/winit craft/0.30.13-lang at 8b35388bd719e8cc86a95971ba0bc026c9a8f058; integrate/review into storytold before production pinning |
| storytold winit base | craft/0.30.13 at ffae5198770d7730e718043f035a772b7dbc3b1b, itself based on winit tag v0.30.13 (e9809ef5) |
| craft-libs PR under review | storytold/craft-libs PR 199, OPEN / CONFLICTING at head b68de8c5f4f1c060904d211a7b1320aa0f7bce78; its merge into local audit branch is ec8840af8c5fda695cf195cdd05e5ae078415c07 |
The audit merge ec8840a is not a usable target: its conflicted Cargo.lock still resolves egui
0.36.2 even though the manifest and source use the 0.37 API. The PR branch itself has a 0.37.0
registry lock. The historical audit log at
/home/codex/work/ssd2/task-evidence/egui037-audit-20261022/fork/craft-libs.log shows a successful
cargo check after the audit harness patched 11 crates to the fork path; it is a useful lead, not
fresh evidence and not a test run. Fresh validation uses maintenance/task.py run/status/finish: retain the owning resource, source
and evidence paths across sequential commands and use a fresh job ID for each intentional command.
1.2 Dependents and feature targets in this workspace
The root workspace has no application binaries or examples. Excluded vendored PDF crates retain their own examples/workspaces; keep them unchanged. Relevant root targets are libraries and their tests:
| Crate | egui use | Features / targets |
|---|---|---|
craft-ui (crates/ui) |
public components and tests | production egui with default-features = false; optional serde; dev egui with default_fonts, egui_extras/svg, egui_kittest/wgpu, image/png |
craft-window (crates/window) |
optional decorate(egui::ViewportBuilder) |
egui feature; Wayland probe only on Linux/BSD; pure logic without egui |
craft-quit (crates/quit) |
optional close handling and app menu | egui; menu implies egui; menu/terminate code only on macOS |
craft-platform (crates/platform) |
optional deferred close and native theme hook | egui with dialogs and/or appearance; appearance only on unix non-macOS non-wasm; browser only on wasm |
| other crates | none, except references in documentation | no egui dependency |
Cargo.toml:34 is the workspace pin; crates/ui/Cargo.toml:20-24 is the only egui test toolchain.
docs/ui-components-audit.md:5 and crates/ui/README.md:237 still say 0.36; both need updating after
validation, but historical source citations in the audit must not be silently rewritten.
1.3 Current tests and CI
Source-declared #[test] occurrences at the baseline: 728 workspace-wide, 91 in crates/ui
(85 in src, 6 integration). These counts are not proof of execution. The UI CI matrix is Ubuntu,
Windows and macOS for cargo fmt, clippy, cargo test -p craft-ui (default and all features), plus a
wasm32 cargo check (.github/workflows/ui.yml:20-58). The visual suite declares 372 control pairs,
36 deliberate negative pairs, 18 toolbar pairs, 12 palette pairs and 12 SVG pairs; every positive
pair requires exact RGBA and mesh equality (crates/ui/tests/visual_regression.rs:185-394).
The repository AGENTS.md additionally requires before merge:
cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, and
cargo test --workspace. Those are the minimum gates; the package-specific CI matrix and the fork
contract below add coverage rather than replacing them.
2. What actually changes in egui 0.37
The following are released 0.37 behavior, not fork behavior. They are the only reasons craft-ui
needs production edits for the upstream release.
| Change | Effect in craft-libs | Source anchor |
|---|---|---|
WidgetType is replaced by accesskit::Role (egui::Role); compatibility alias is deprecated |
Every WidgetInfo constructor must use Role; SelectableLabel becomes Button, Other becomes Unknown, Checkbox becomes CheckBox |
egui/src/lib.rs 0.37.0 lines ~640-655; PR #8555 |
Id::new and AsId are deprecated; Id::unique, IdSalt, IdSaltSet/Map are the explicit APIs |
Test IDs and any caller-provided IDs should use Id::unique; use IdSalt for scope-local salts |
egui/src/id.rs; PRs #8534, #8586, #8590 |
Ui::id is renamed Ui::scope_id; UiBuilder::id is renamed scope_id |
Child body/header UIs must pass a globally unique scope id; tests and README examples must use scope_id |
egui/src/ui.rs 0.37.0 lines ~359-383; ui_builder.rs lines ~63-82; PR #8542 |
Panel constructors accept impl AsIdSalt; CentralPanel stays last; root Ui is the normal app surface |
Application panel code must keep panel order and use a stable salt; craft-ui's DockArea is a child of that root/panel UI, not a Context replacement |
egui/src/containers/panel.rs 0.37.0; PR #8592; Context::run_ui / root_ui / run_pass |
default_fonts is no longer a default feature; eframe still enables it by default, but integrations must choose |
craft-ui production already disables defaults; test dev-dependency must keep default_fonts; applications with custom fonts must verify coverage |
egui/Cargo.toml 0.37.0; PR #8583 |
| egui 0.37 adds missing-glyph policy and system font providers; kittest panics on missing glyph by default | Tests that deliberately draw arbitrary script text must opt into MissingGlyphPolicy::Tofu; production should keep tofu rather than panic; custom app fonts need explicit coverage checks |
epaint/src/text/fonts.rs; egui_kittest/src/builder.rs (missing_glyph_policy); PRs #8601, #8512 |
ViewportCommand::Screenshot takes a one-shot ScreenshotCallback; Context::request_screenshot is the convenience API |
Any app screenshot path, not craft-ui itself, must move from UserData/Event::Screenshot correlation to callbacks |
egui/src/data/screenshot_callback.rs; viewport.rs:1202-1205; PR #8489 |
Response::accessible_name, Ui::label_inputs_by / name_inputs, Area::role, better popup/menu trees |
Accessibility labels/roles must be explicit for input widgets and icon-only controls; kittest now fails unnamed inputs | PRs #8604, #8608, #8612, #8581, #8589 |
Style defaults changed (roomier buttons, larger corner radius); Style theme plugin is experimental |
DockStyle::from_ui inherits changed defaults; visual fixtures must keep explicit geometry/colors instead of accepting a new golden |
PRs #8617, #8445, #8153 |
Context::run_ui, run_pass, root_ui and Context::run_logic define root-UI and hidden-window semantics |
Tests must use the correct entry point; logic-only work must not require a painted Ui; request_discard may call a UI closure more than once |
egui/src/context.rs 0.37.0; PR #8535; eframe App::logic/App::ui |
| PRIMARY selection support was added upstream | App clipboard behavior changes; E4 changes the backend on Wayland; PRIMARY must be tested separately from the regular clipboard | PR #8428; egui-winit/src/clipboard.rs |
egui_extras and egui_kittest have their own 0.37 releases and features; egui_plot is not part of this repo |
Do not assume every egui-family crate shares one version number; pin what is used and verify its manifest | crate manifests at tag 0.37.0 |
The upstream 0.37.0 tag itself has no PATCHES.md; fork-only material is in the suite branch. The
0.37.0 tag and the fork revision must never be confused: a registry-only build gets upstream
behavior, while a suite build gets E1-E21 and the winit patches.
3. Fork and winit provenance
craft-libs #198 is a broader language-support issue/proposal, not a PR or merged dependency. It does not authorize a wholesale locale/font roadmap in this port. PR 199 is open/conflicting at the recorded head; its stale merge tree must not replace current main. Current upstream report status is dated research, not proof of fixed behavior or fresh task validation.
3.1 The suite fork
aiongg/egui craft/0.37 is based on tag 0.37.0 and merges the craft/* topic branches in the
order listed in its PATCHES.md. The final inspected revision is
a4f9a17f64a6bae61350fb85804c9df0d9865916. The branch and its origin/ai-0.37 alias are movable;
all manifests and CI must use the full SHA. The older origin/craft/0.37.0-trial
(59163e291c56ebe0199e191c23bf58ed157c9862) is not the target because it lacks later language
work and is not the published suite reference.
The fork's workspace manifest pins winit by branch:
[patch.crates-io]
winit = { git = "https://github.com/aiongg/winit", branch = "craft/0.30.13-lang" }
That patch belongs to the egui workspace. Cargo does not propagate a dependency's patch section
into a consumer root. A consumer that only depends on aiongg/egui will resolve registry winit, and
E4/E19/E21 will fail to compile or silently use the wrong API. craft-libs and every app must add its
own winit patch with the immutable commit.
3.2 Winit patch ledger
Base: winit v0.30.13 (e9809ef5), then storytold/winit craft/0.30.13 (ffae5198) with W1-W15,
then aiongg/winit craft/0.30.13-lang (8b35388b) with W17-W27. W16 (Wayland pinch/rotation) is
not in this cut.
| Patch | Commit(s) | Behavior relevant to this plan | Removal / test condition |
|---|---|---|---|
| W1 | b357a332 |
Windows mixed-DPI WM_DPICHANGED rect |
Remove when a winit release in use has #4341; verify DPI geometry |
| W2 | 13ca693b |
Wayland text input dies only with its seat | Remove with upstream #4747; test seat removal |
| W3 | fd104a4c |
Windows IME caret from GCS_CURSORPOS only when reported |
Test Korean caret; remove on release |
| W4 | 3fffb7ce |
Hidden maximized windows stay hidden | Test restore; remove on release |
| W5 | 283370ce |
X11 focus replacement IME context | Superseded by W20; remove together |
| W6 | a82b90fb |
macOS NSTextInputClient commit flow | Test composition; remove on release |
| W7 | 9b90d5bd |
macOS discard marked text when IME disabled | Test disable; remove on release |
| W8 | 6a87addd |
macOS emoji picker text as commit | Test picker; remove on release |
| W9 | 9500f25d |
macOS no duplicate Korean space | Test Korean space; remove on upstream fix |
| W10 | 4a7dd791 |
macOS first Apple Korean key retry | Test first key; remove on release |
| W11 | 4df8554b |
X11 scale factor from server DPI before physical DPI | Test mixed-DPI X11; remove when upstream equivalent lands |
| W12 | 7facf001 |
macOS title-bar drag reaches app | Test drag; remove when upstream equivalent lands |
| W13 | 5506b1a9 |
Wayland pixel wheel/tilt/continuous reports Moved |
Test Ctrl zoom not latched; remove on upstream fix |
| W14 | c488d2e9 |
macOS opt-in cancelable termination -> close request | Test close/quit; keep until upstream release |
| W15 | cf73a29c, b05ce129 |
Wayland file drops and clipboard on one per-seat data device | Required by E4; remove only with a winit release carrying #4729/#4571 and equivalent regular-clipboard/data-device tests |
| W15P | New; no commit yet | Per-seat SCTK PRIMARY on the existing Wayland connection/event queue; platform/wayland.rs, wayland/clipboard.rs, wayland/state.rs and seat handlers |
Require bounded asynchronous read/write transfers, total deadlines, byte/request/worker caps, cancellation and focused-owner completion; pointer/keyboard triggering serials, seats/offers, empty/absent/self-paste tests on wlroots/KWin/Mutter. Remove only with an equivalent released native path |
| W17 | 2be27382 |
Regression guards for W2/W3/W5-W10/W12/W13 | Keep as tests while their patches remain |
| W18 | 894bf2d7 |
Windows hidden system caret at IME area | Test Sogou/Japanese IME; remove on upstream fix |
| W19 | fa2e1b3d |
Windows VK_PROCESSKEY not sent as a key press |
Test IME key ownership; remove on upstream fix |
| W20 | 019c95f1 |
X11 one input context per window; disable resets | Test Vietnamese Telex; replaces W5, remove together |
| W21 | 23bb19a7 |
X11 IME spot after 100 ms quiet | Test forwarded keys not refiltered; remove on upstream fix |
| W22 | 0816871e |
macOS caret, replacement ranges, un-composed commits | Test press-and-hold/dictation; remove on release |
| W23 | c8cd8aa1 |
macOS layout's Command table for shortcut keys | Test Dvorak-Command/V; remove on upstream fix |
| W24 | 4b5bc5a0 |
Window::reset_ime() for all four desktop backends |
Required by IME reset tests; remove on release with equivalent |
| W25 | eb4007da |
X11/Wayland KeyEventExtLatinLayout |
Required by E19; remove on release with equivalent |
| W26 | 2b9cd1cf |
macOS IME delete-surrounding by UTF-8 bytes | Required by E21; remove after the egui -> winit 0.31 move |
| W27 | f40c76a2 |
Wayland text-input-v3 surrounding text and delete-surrounding | Required by E21; remove after the egui -> winit 0.31 move |
Provenance concerns:
- Integrate and review W17-W27 plus W15P and the W27 lifecycle revision in
storytold/winitbefore pinning production consumers. Retain aiongg provenance; use the resulting full storytold commit, never a moving branch. - W15's Wayland clipboard API and E4 are a pair. Do not take one without the other.
- W26/W27 use a fork-only delete-surrounding API shape. When winit gains core
Ime::DeleteSurrounding, E21 and the winit patches must be removed together; otherwise the compiler sees two incompatible IME APIs. - W11 changes
pixels_per_point/DPR input. It affects tab fit, visual fixtures, screenshot pixel sizes and any performance comparison. Record the exact winit commit in those runs.
4. Audit of craft-libs PR 199
PR 199 is the useful starting point, not a branch to merge blindly.
4.1 What it gets right
Cargo.toml:34changes the workspace egui pin to0.37andcrates/ui/Cargo.toml:23-24changesegui_extrasandegui_kittestto0.37.crates/ui/src/buttons/mod.rs,crates/ui/src/docking/surface.rs,crates/ui/src/layout/resize.rs, andcrates/ui/src/tabs/mod.rsmap the deprecatedWidgetTypevalues to the 0.37 roles:SelectableLabel->Button,Checkbox->CheckBox,Other->Unknown. This matches egui's own mapping table.- Test IDs move from
Id::newtoId::unique; child-body IDs move fromUi::id/UiBuilder::idtoscope_id. These are semantic renames, not a new ID scheme. crates/ui/src/docking/tests.rsreplaces the⋮fixture label with…. A read-only cmap check of the bundled 0.36.2 and 0.37.0 Hack, Ubuntu, NotoEmoji and emoji-icon fonts finds no U+22EE in either release; the 0.36 test drew tofu and the 0.37 kittest harness instead panics on a missing glyph.- The branch's own lock resolves
egui/eframe/egui-*0.37.0 from crates.io and raises accesskit to 0.25.1 and kittest to 0.5.0. The lock churn is large but coherent for a registry-only build.
4.2 Findings and required corrections before reuse
- Both the PR merge tree and audit merge are unsafe bases.
b68de8chas a coherent registry lock but a stale tree relative to4919088: it lacks the entirecrates/platformcrate, drops PDF/media bounds and secure font-fetch changes, reverts settings merge behavior, docking permission guards and the linear printer-name collision algorithm.ec8840aadditionally has a stale 0.36 lock. Never merge/cherry-pick either tree as the migration result. - Reuse only the intended port hunks. Apply the API changes from
8614e4aand README change from1764bcaonto recorded current main. Preserve provenance, resolve against present source, and regenerate the lock with the final reviewed storytold fork/winit set. Include those pins atomically with source adaptation; no stock-only intermediate consumer release is planned. - README code still uses
ui.id().crates/ui/README.md:115is documentation, not compiled, but it is a public example and will fail under-D warningsif copied. Change it toui.scope_id()in the same documentation pass. The version statement at line 237 must also be updated. - The ellipsis change is not a production fix. The production source still paints
…and−; both are present in the bundled Hack/Ubuntu fonts. The old⋮was only a test fixture and was never covered by the bundled fonts; 0.36 silently painted tofu. Add an explicit glyph-coverage test so the next font/default change is caught deliberately, instead of relying on one changed string. - AccessKit role mapping needs contract tests, not just compilation. The PR changes enum names;
it does not prove that
Role::Tab(manually set afterWidgetInfo::selected(Role::Button)),Role::CheckBoxfor stack collapse,Role::Splitter, labels, enabled state and selection still reach the tree. The 0.37 tree changes from #8581/#8589 make this a real regression surface. - The PR does not test a hidden/minimized
App::logic.craft-uihas one UI-driven poll path (printing::State::showcallspoll) and no logic-only test. Eframe 0.37 callsApp::logicbut noApp::uiwhile hidden. The API is already public, but the contract is untested. - The PR does not add a publication-compatible check. Root
[patch.crates-io]entries will not travel to crates.io consumers. The packaged crate must be tested against registry egui 0.37, not only against the suite fork.
Decision: reuse only reviewed port hunks from 8614e4a and 1764bca. CL-1 uses
an explicit changed-file allow-list: Cargo.toml, Cargo.lock, crates/ui/Cargo.toml,
crates/ui/README.md, crates/ui/src/buttons/mod.rs, crates/ui/src/docking/surface.rs,
crates/ui/src/docking/tests.rs, crates/ui/src/layout/resize.rs,
crates/ui/src/layout/tests.rs, crates/ui/src/printing/tests.rs,
crates/ui/src/tabs/mod.rs, crates/ui/src/tabs/tests.rs,
crates/ui/tests/docking_workflows.rs and crates/ui/tests/visual/fixtures.rs.
Compare git diff --name-only against this list and review the actual allowed-file
hunks: being on the list never permits unrelated reversions. Extra files required
by a proven compatibility fix need a named reason and review before extending it.
In particular, .github/workflows/**, NOTICE, root README.md,
crates/ui/src/docking/model.rs, crates/platform/**, vendors and non-UI libraries
stay unchanged. Preserve linear shown_names, floating-group permission guards,
secure font fetches and all intervening hardening. Run
moving_a_floating_group_needs_every_member_movable,
protected_group_member_blocks_floating_group_resize,
collisions_resolve_like_the_pass_by_pass_rule and a_long_collision_cascade_stays_fast.
Root workspace/lock membership must still contain craft-platform.
5. Dependency and publication topology
5.1 Intended suite graph
For suite validation, craft-libs should resolve one and only one copy of each egui-family crate and
one winit:
craft-ui / craft-quit / craft-platform / craft-window
-> egui 0.37.0 (reviewed published storytold SHA, derived from a4f9a17f input)
-> egui-winit 0.37.0, eframe 0.37.0, egui-wgpu 0.37.0,
egui_extras 0.37.0, egui_kittest 0.37.0 (test/consumer)
-> epaint, epaint_default_fonts, ecolor, emath (same git revision)
-> winit 0.30.13 (reviewed published storytold SHA including W15P)
The root [patch.crates-io] must name every used package at the same full SHA. At minimum:
egui, eframe, egui-winit, egui-wgpu, egui_extras, egui_kittest, epaint,
epaint_default_fonts, ecolor, emath, and winit. Include egui_glow
when selected by tests/optional features; metadata determines the complete set. If egui_system_font_provider is enabled,
patch it too. Do not patch egui_plot or egui_dock unless a consumer actually uses them; do not
assume their 0.37 version numbers.
Required checks:
cargo tree -d
cargo tree -i egui@0.37.0
cargo tree -i winit@0.30.13
Acceptance is exactly one egui-family source and exactly one winit source, with no registry
egui/epaint/egui-winit copy beside the git copy. A duplicate is a failure, not a warning.
5.2 Publication reachability
craft-ui's production manifest must remain compatible with stock egui 0.37.0. The fork-only
behaviors are in dependencies, not in craft-libs source. This is deliberate:
- it keeps
cargo packageusable; - it lets a consumer choose stock egui 0.37 or the suite fork;
- it prevents a fork-only API (for example
Style::right_to_leftorRawInput::latin_layout_chars) from leaking into a published shared crate.
Add an isolated ci/registry-egui workspace (its own [workspace], not a member of the root
workspace) that depends on crates/ui by path without any patch. Run its clippy/tests against
registry egui 0.37.0. Then run cargo package -p craft-ui and inspect the generated manifest and
.crate contents. A machine-local path patch, an aiongg branch name, or a hidden winit dependency in
the packaged manifest blocks release.
Do not put path overrides into the published manifest. A consumer's [patch.crates-io] is the
supported place for the suite fork and winit; document that in the README compatibility section.
The suite's Cargo.lock is a lock, not a publication promise.
5.3 Versioning
craft-libs currently versions every crate 0.1.0 from the workspace. This migration ships as a
new immutable git revision with explicit compatibility notes: egui-facing public types change
identity, so every consumer must move coherently. Do not bump every unrelated workspace crate.
Publishing a crates.io release is a separate release operation requiring a semver/version plan;
it is not authorized or required by the git-based suite migration.
5.4 Vendors and nested workspaces
The only vendored packages are PDF readers/writer, not egui:
vendor/krilla(0.8.2),vendor/hayro(0.7.1),vendor/hayro-interpret(0.7.0),vendor/hayro-syntax(0.7.2),vendor/hayro-jbig2(0.3.0),vendor/hayro-cmap(0.1.0) andvendor/hayro-postscript(0.1.0).- Reader provenance and patch ledger:
vendor/README.craft-reader.md. The first reader transfer was from PdfCraft7437bde378f43856eada40af25b983812e615e8a; the ledger records subsequent transfers and fixes. Writer provenance and patches are recorded separately invendor/krilla/README.craft.md. Each source manifest records the relevant source hashes. - Equivalence decision: keep the 0.7 backend and its documented compatibility patches. They are not replaced by the egui migration. When reconciling current main, preserve its reader/writer fixes and provenance byte-for-byte; do not independently re-vendor or remove them.
- Removal/test condition: only a separate PDF backend migration may change them; their existing
cargo test -p craft-pdf --all-featuresandscripts/test-pdf-backends.shevidence remain the gate. Run these checks again after a main reconciliation changes the vendors; default-feature workspace tests alone do not qualify the PDF backend. The root[patch.crates-io]must keep these entries whilecraft-pdfuses them.
crates/pdf/read-only-contract is a nested workspace with its own Cargo.lock and a single
hayro-syntax patch. It does not depend on egui and is unaffected by the workspace egui lock; do not
edit its lock as part of this migration. There are no vendored egui, eframe, egui-winit or winit
sources in this repository.
6. Root-Ui and lifecycle design
6.1 Boundaries
craft-libs must not grow a blanket Context compatibility layer. The correct boundaries are:
- Application lifecycle: eframe owns
App::logicandApp::ui.logicruns before each UI pass, possibly repeatedly in one frame, and in hidden mode when a repaint is requested;uimay not run at all while hidden. Code that must drain queues, service print jobs, autosave, or decide a close must live inlogic. - Root UI:
App::uireceives the rootUi.craft_ui::docking::DockAreaand every other component take&mut Ui. They must never callContext::root_uifrom inside their own rendering; that would panic when the root UI is already borrowed (Context::root_uidocs at 0.37.0). - Painted output:
DockArea::showreturnsDockOutputactions and does not mutate the layout. The app applies them after rendering. This remains the command/undo/persistence boundary. - Hidden/minimized: no component may rely on a paint to make progress.
printing::State::pollalready takes&Contextand can run inlogic;showis a fallback. Background receivers, autosave timers and playback must be app-owned and continue inlogic. Pending work must arrange a wakeup withrequest_repaint/request_repaint_after; hidden polling is not a continuous scheduler. Test repaint-driven delivery explicitly. - Multiple passes:
Context::run_uican call its closure more than once afterrequest_discard. Both hosts call logic/UI inside that closure. Queues/actions must be consumed once and deadlines use monotonic time. Count observable effects across all passes; do not hide duplicates by counting only the final pass or altering the test host. After a completed run_ui the pass index is zero, but events/time remain stale in run_logic. Fresh input belongs to the known UI phase, including forced hidden paint.
6.2 Preserve lazy printing and hidden result delivery
Current behavior:
printing::State::showcallsrefreshon first display andpollevery displayed frame (crates/ui/src/printing.rs:205-209).polldrains discovery/submission receivers and callsrequest_repaint_after(100 ms)while work is pending (printing.rs:162-201).- WordCraft and GridCraft already call
poll/poll_printingfromlogic, and GridCraft also calls it fromui; repeated polling is currently harmless becausetry_recvconsumes once. A window that has never opened print should not start printer discovery merely becauselogicruns.
Keep refresh as the explicit discovery trigger and poll as the idempotent receiver drain.
Retain show's lazy first-display behavior. Apps may trigger discovery from the print-open action
before the page is drawn, then poll pending work in logic while hidden. Do not add an eager
tick API or new startup process/IPC work as part of this migration. Change production code only
if a regression demonstrates a missing transition. Settings, Feedback, begin, ready,
selection and job ownership stay unchanged.
Contract tests:
print_poll_is_lazy_without_open: run repeatedpollcalls throughContext::run_logicbefore any print action; assert no discovery worker starts and no periodic wakeup is requested.print_result_arrives_without_ui: triggerrefreshonce, send a fake printer list and runpollthroughrun_logic; assert printers/feedback update without aUi. Subsequentshowand discarded UI passes must not start another worker or consume a result twice.completed_work_requests_no_more_repaints: after a submission result is delivered, run 60run_logicpasses and assert norequest_repaint_after/immediate repaint is requested byState.queue_default_and_selection_survive_logic_only_poll: assert a still-present queue remains selected and a removed queue cannot be submitted.
6.3 Autosave, undo and side effects
No autosave, playback or undo engine lives in craft-libs. The migration contract for consumers is therefore explicit:
- queue drains, autosave, audio/playback polling and save-file completion callbacks go in
App::logic, notApp::ui; - painting only presents state; it must not be the sole trigger for a save/undo transaction;
DockAreaaction output is applied once, after rendering, through the app command path;- any
Context::request_discardpath must be idempotent; tests use a call counter to prove exactly one committed action or one undo step.
These are app acceptance criteria, not claims that this repository's tests can prove application behavior.
7. Component-by-component migration
7.0 Shared-helper policy
The 0.37 API renames are direct calls, not a reason to build a compatibility layer. Apply this rule:
- No wrappers for
Id::unique,Role,Ui::scope_id,ScreenshotCallback,Context::set_language,Response::accessible_nameorContext::run_logic. Applications call the egui API; craft-ui exposes only component-specific semantics. - Justified mechanism: a bounded-lifetime capture completion slot, reviewed with two concrete existing craft-ui consumers; section 7.8 keeps request IDs and application policy outside it.
- Existing print API: retain lazy
refreshand idempotentpoll. No eager discovery helper. - Docking direction: retain the persisted first/second split and tab model. Use an explicit opt-in DockArea direction policy from section 7.7, with matching hit coordinates; no hidden global mirroring mode.
- Not shared: fonts, themes, localization catalogs, autosave, undo history and native pickers. They remain application-owned, as the existing component audit requires.
7.1 IDs and scopes
Anchors: crates/ui/src/tabs/mod.rs:117 (Tab::new), :141 (ui.interact),
crates/ui/src/docking/surface.rs:539 (body child), :600 (header child), :761 (floating Area),
crates/ui/src/layout/resize.rs:82 (handle), crates/ui/src/printing.rs:210 (push_id).
Rules:
- Caller-supplied widget IDs stay caller-owned and global. Use
Id::unique(("app", panel))or an app-ownedIdSalt; never derive an ID from translated text. Tab::newandResizeHandle::newkeep takingId. The public signatures do not need a new wrapper.Ui::new_child(UiBuilder::new().scope_id(...))must use a scope id that is globally unique. The body scope isself.id.with(("body", panel)); the header scope isself.id.with(("header", selected)); they are distinct. Do not replace them withid_saltunless the child is intended to move with its parent.printing::State::showcontinues to useui.push_id(id, ...); the caller'sidremains the stable identity of the print block.Id::uniqueis the same hash as the oldId::new, so test behavior is intended to be identical. Add a test that the same source yields the sameIdacross frames and that two calls with different panel payloads differ.
7.2 Accessibility roles and names
Anchors: buttons/mod.rs:84, tabs/mod.rs:143-148, docking/surface.rs:419-421,569,628,769,
layout/resize.rs:157-173, printing.rs:213-254.
Required mappings and assertions:
| Control | Role / metadata | Assertion |
|---|---|---|
| Icon button | Role::Button, selected, label, enabled |
AccessKit role Button; Toggled::True/False; exact label; disabled flag |
| Tab | widget info starts Button, then node role Tab; clear_toggled; selected |
Role Tab, selected, no toggled flag, label exact; duplicate labels have distinct IDs |
| Stack collapse | Role::CheckBox, selected = open |
Role CheckBox, toggled/selected matches open, label exact |
| Resize handle | Role::Splitter, orientation, numeric value/range/step, Increment/Decrement/SetValue |
orientation perpendicular to movement; actions; SetValue delta; NaN ignored |
| Floating group grip | Role::Button, move label |
Role Button, label; not focusable as a splitter |
| Overflow | Role::Button, panels label |
Role Button, label, menu opens and entries keep logical labels |
| Tab menu actions | Role::Button, logical labels |
custom visual text does not replace AccessKit label |
| Print printer ComboBox | labelled by visible printer label | labelled_by present; get_by_label("Printer") finds it |
| Print copies DragValue | labelled by visible copies label | label present; check_accessibility passes |
| Print grayscale checkbox | own label | Role CheckBox, label present |
Do not disable Harness::check_accessibility globally. If a fixture deliberately uses an unnamed
input, scope the exception to that fixture and document why. egui_kittest 0.37 fails unnamed input
widgets by default (PR #8589); that is a feature, not a migration nuisance.
7.3 Panels, docking, order, clip and floating layers
DockArea does not use egui::Panel; it renders a snapshot into the caller's Ui and uses
Area::new(id).order(Order::Middle) for floating groups
(crates/ui/src/docking/surface.rs:761). The app's panel structure remains outside.
Required integration tests:
- Panel order and clip: in a root
Ui, addPanel::left("left").exact_size(200).show(...), thenCentralPanel::default().show(ui, |ui| DockArea::show(...)); assert the dock gets the remaining width, the left panel is not overdrawn, and bodyUiclip is a subset of the parent clip. - Nested panels: a top panel containing a horizontal
Paneland a central panel; assert order and no double allocation. This checks 0.37'sPanel/CentralPanellast rule. - Floating layer order: two floating groups,
raise_panelon the second, assert the top layer and tab focus; assert the serializedLayoutis unchanged byraise_panel. - IDs after move: move a panel into a floating group and back; assert the body
scope_idis the same before/after and that a focused text field retains its edit buffer. - No document allocation in floating-only mode: existing test
floating_only_surface_does_not_allocate_the_callers_document_regionremains mandatory. - Clip and popup: open the overflow
Popupand tab context menu near the viewport edge; assert popup rect stays inside the screen, menu labels are logical, and the parent clip does not clip the popup away. 0.37's popup tree changes (#8581) make this a real check.
Do not turn DockArea into a native multi-window manager. Native viewport creation, reparenting and
cross-monitor coordinates remain host-owned. Action::Float means an in-viewport floating group.
7.4 Tabs, split geometry, resize handles, toolbar and theme
tabs::fitis direction-agnostic and pure. Keep its exact width/overflow assertions. Add a 64-tab and a 4096-tab bounded-work test; the latter must still be linear and must not allocate unbounded storage.layout::split_rectandSplitLayout::resizedstay pure and f32/f64 bounded. Add property tests with extreme finite values and non-finite rejection; do not accept aNoneas a silent panel disappearance in app adapters.ResizeHandlefocus lock, arrow keys, AccessKit actions and pointer drag deltas stay. Add a test that a focused handle consumes only its axis arrows and leaves Tab traversal intact.fit_toolbarstays ordered-prefix only; add a test that every input index is either shown or in overflow, and that the overflow control is reachable when the width is exactly the overflow width.WidgetPalettekeeps preserving borders/expansion. Add a test that a 0.37 default-style change does not overwrite an explicit app border or expansion.DockStyle::from_uishould document that 0.37's default radius/spacing values are inherited; apps with exact designs must pass explicit values.
7.5 Fonts, glyphs and custom catalogs
Anchors: docking/surface.rs:425 (−/+), :629 (…), printing.rs labels,
crates/ui/tests/visual_regression.rs (font-free fixtures and SVG raster checks).
craft-uiproduction still disables egui default features. This is correct: the app chooses fonts.- The
crates/uidev-dependency keepsdefault_fontsso component tests can shape labels; keep it. - 0.37's
default_fontsno longer includes monochrome emoji fonts, and the 0.37 kittest harness panics when nothing can draw a glyph. That is why the⋮fixture had to change even though 0.36 had silently drawn tofu.…and−are present in Hack/Ubuntu; verify with:ctx.fonts_mut(|f| f.has_glyphs(&FontId::proportional(12.0), "…−+"))after onerun_uipass. - Add a test using
MissingGlyphPolicy::Panic(orHarnessdefault) with⋮as a deliberate negative fixture: it must fail. Then use…in the actual component and asserthas_glyphs. - Do not call
allow_missing_glyphs()on a whole suite just to make a test green. If arbitrary script fixtures are intentional, scope the policy to that fixture. - SVG catalogs remain trusted compiled-in bytes and are not a font fallback.
SvgIconSetstill requires a unique URI prefix per catalog/style; egui caches by URI. Test SVG at 12/24/48 logical pixels and exact RGBA.
7.6 Quit, platform and window helpers
craft-quit::on_close_requestalready expectsegui::Context,ViewportCommand::CancelCloseand the root viewport. It compiles against 0.37. Add a test withApp::logic-style ordering: app cancels close first, thenon_close_requestsees the cancellation and does not quit.craft-platform::DeferredClose::updatescansViewportCommand::Close/CancelClose; keep its test that a pending picker holds close and releases exactly once. The 0.37ViewportCommand::Screenshotcallback does not change this, but apps must not treat a pending screenshot as a pending close.craft-platform::sync_native_window_themeis anegui::Plugin::output_hook. In 0.37 the plugin API still callsoutput_hookafter the pass; keep the test that a manualSetThemeis not overwritten and that child viewport outputs receive the hint.craft-window::decorateonly touchesViewportBuilder; no 0.37 API change. Keep the Linux/BSD Wayland probe and theWAYLAND_SOCKETprotection. Do not call it more than once at startup.craft-quitmenu code usesmuda; 0.37 does not change muda. Do not add a second native menu abstraction.
7.7 Input, IME, RTL, clipboard and PRIMARY
craft-libs itself does not implement text input, IME, clipboards or RTL shaping; it consumes egui widgets and labels. The fork changes that matter:
- E5/E12/E13/E15/E16/E20: bidi shaping, visual order, grapheme editing and language-aware layout
affect every
Label/TextEditinside aDockAreabody. Test Arabic/Hebrew mixed labels through the fork fixture, not stock 0.37. Keep ASCII geometry tests unchanged as the no-regression baseline. - E9 and docking direction: built-in egui chrome follows the fork's direction,
but custom docking uses explicit geometry. Add an opt-in
DockArea::directionbuilder with a stock-compatibleDockDirection::{LeftToRight, RightToLeft}; default remains LTR. Applications pass their UI direction; craft-ui does not read fork-only style fields or infer language from labels.- Preserve serialized split first/second, axes, ratios, floating rectangles and logical tab order. Changing UI direction must not rearrange a saved workspace.
- Mirror horizontal tab-strip visual order and leading/trailing controls. Painting, clipping, hit tests, accessibility order and drag insertion use the same computed rectangles. Convert visual insertion index back to logical model index once.
- Split-pane geometry and physical splitter drag directions stay unchanged.
Floating resize grips use bottom-trailing placement (right in LTR, left in RTL);
RTL resize keeps the right/top edges fixed: for pointer delta
(dx, dy), clamp(width - dx, height + dy)to the same limits, then emitAction::MoveFloatingwith[old_right - new_width, old_top, new_width, new_height]. LTR keeps left/top fixed and clamps(width + dx, height + dy). Apply the existing screen clamp afterwards. Horizontal splitter motion to the right increases the physical left pane and decreases the right pane in both directions; vertical motion downward increases the top pane. Assert these rectangles/ratios and keyboard-generated deltas under both policies and at min/max/screen limits. For a focused floating resize grip, ArrowLeft supplies negative physical dx and ArrowRight positive dx: RTL ArrowLeft increases width and moves the left edge left while keeping right fixed; RTL ArrowRight decreases width and moves left right. LTR uses the opposite width effect with left fixed. ArrowDown grows height; ArrowUp shrinks it in both directions. - Physical left/right keyboard traversal follows visual tab order; panel IDs and focus stay stable across direction changes. Vertical behavior is unchanged.
- CL-3 tests LTR/RTL click/drag/reorder/resize, keyboard focus, protected floating groups and unchanged serialized workspace. Deliberately mirror paint without hit coordinates to prove failure detection. This is a visual policy, not a schema migration or wholesale locale redesign.
- E17/E18:
Context::set_language(Some("ar".to_owned()))sets the AccessKit root language;UiBuilder::languagemarks content language; eframe'sFrame::set_ui_languageis the convenient host hook. Craft-ui should not cache a language; pass localized text and let the app set language once per change. - E19: Latin layout characters affect app shortcuts, not craft-ui geometry. App tests must cover
Ctrl+Q on Russian/Arabic/Hebrew and Dvorak Command layouts; do not infer shortcut behavior from
Keyalone. - E21: TextEdit deletes the exact UTF-8 bytes an IME replaces and groups the deletion with the commit. App undo tests must assert one undo step for a press-and-hold/dictation replacement.
- E21b/P7b (new prerequisite): the candidate does not send outgoing surrounding text to W27.
The reviewed fork must add the bounded transient IME payload, built-in TextEdit producer and
Wayland adapter before CL-1's fork contract can pass. Check deletion-only and delete/commit
batches, selection/focus changes, password clearing, two windows, UTF-8 boundaries, preedit
exclusion and the 4,000-byte window. Stock-compatible craft-ui production code uses ordinary
TextEdit and needs no fork-only payload API; the fork contract fixture exercises it explicitly.
The eight custom-editor consumers need their own adapters as specified in the suite rollout.
CL-1 requires the final reviewed E21b/W27 revisions with an explicit input-owner/context
generation present for both
Available(including an empty document) andUnavailable, and clear/invalidate operations. Clear both caches and protocol state through a committed disable/enable boundary when the owner changes or text becomes unavailable; ordinary updates preserve composition. Cancel old pending preedit/commit/delete state on leave/reset/disable. Within the current focus epoch, text-input-v3donewith a mismatched serial still applies incoming edits once but defers outgoing state until a matching serial; old-epoch events cannot edit the new owner. The latest completed UI pass is authoritative, includingIMEOutput = None; do not retain the earlier payload throughime.or(self.ime). Test password/unavailable/over-limit transitions, queued deletion then refocus, reset, two windows and text-to-non-text discarded-pass output. Final SHAs are execution prerequisites.8b35388bremains a comparison input, not an interim production pin: CL-1 waits for the completed reviewed/published dependency series. - E4 + upstream #8428: the existing candidate's arboard PRIMARY fallback is
replaced by the selected new winit W15P path on the same connection/event queue,
using SCTK's per-seat PRIMARY objects. This must land before egui P2b and CL-1.
Test pointer- and keyboard-triggered ownership, including keyboard-only focus, with
fresh triggering serials scoped to their seat/origin (no arbitrary first-seat fallback),
owner/seat changes, empty/absent offers,
self-paste and canceled/slow transfers on wlroots/KWin/Mutter. Keep password and
unchanged-selection protections; no second regular data device. An isolated
PRIMARY-only fallback needs measured necessity and review; ordinary paste is
not PRIMARY acceptance. W15 and W15P must use bounded asynchronous transfer handles,
a total monotonic deadline, byte/count limits and bounded workers. The existing two-second
per-
WouldBlocktimeout is not a total bound. Egui consumes completion only for the still-live paste request/owner/input generation; cancellation and late completion cannot paste elsewhere. Verify responsiveness during slow trickles and oversized/concurrent offers.
7.8 Screenshot callback correlation (downstream contract)
craft-libs has no screenshot code; multiple suite applications do. The migration must replace
ViewportCommand::Screenshot(UserData) plus Event::Screenshot matching with one-shot callbacks:
// Proposed ownership, not an existing API:
// app request record -> one result slot -> optional image + owning-viewport wakeup
// screenshot callback -> weak reference to that slot
// app logic -> take the image once, validate request, encode/write/reply
Requirements:
ScreenshotCallback::new/Context::request_screenshotis one-shot. Clones share one callback; complete it at most once.- The request record owns one completion slot and the callback holds a weak reference to that exact slot. Correlation comes from ownership, not callback order or a second global token map. Drop/cancel releases the slot and rejects late results.
- If the window is hidden, send
ViewportCommand::RequestPaintWhileHiddenor the screenshot command itself (a pending screenshot already requests a paint). A hiddenApp::uiis not run otherwise. - In a custom/headless renderer (for example LightCraft's headless path), retain the
ScreenshotCallbackand callcomplete(image)with the next rendered image; do not reconstruct a fakeEvent. - Test two outstanding requests with distinct tags, callback-once behavior, no callback without a request, and a hidden-window request. Test E2 surface fitting: the returned image may be smaller than the window when surface fitting reduced it. The integration render_scale is private: derive x/y image scale from returned image dimensions and the recorded logical viewport rectangle for that capture, including pixels-per-point metadata. Verify expected aspect/rounding. A resize/DPI generation mismatch retries within the deadline or returns an error; never combine old image pixels with current geometry.
Add a narrow completion-slot helper for existing craft-ui consumers after reviewing two concrete adapters together (DesignCraft and GridCraft are the selected first consumers). Their tokens, paths, settle delays, deadlines and replies remain app-owned. The callback holds only a weak slot reference; avoid a strong Context/callback cycle, release locks before wakeup, and drop late results after cancellation. The app enforces aggregate request/image limits. A bounded nonblocking channel is the measured fallback if slot stress tests expose a problem. Apps without craft-ui need not add it solely for capture. See the suite rollout plan for the selected ownership contract.
Admission accounts for in-flight captures as well as completed slots: reserve a checked byte budget before issuing a capture, using the allowed surface dimensions, and cap request count independently. Defaults: 4 outstanding requests, 128 MiB/image, 256 MiB aggregate reserved in-flight/completed image bytes; stricter app budgets are allowed. Record known issuance UI logical bounds and a geometry epoch. Reject/retry any resize/DPI/zoom/surface epoch change before save, even a change back; the callback contains pixels only, not frame metadata. Revalidate dimensions before issuance after resize/DPI changes. On completion, reject an oversized or stale image and release both its memory and reservation; account for the renderer/readback's transient allocation separately in peak RSS. A post-callback size check alone does not prove that peak capture memory was bounded. Stress concurrent 4K captures, resize races, timeouts and canceled records; retained image bytes must return to zero after drain/shutdown.
8. Fork-specific patch plan: E1-E21
The fork's PATCHES.md is the source for the table below. The upstream column reflects the inspected
tag 0.37.0 and open PRs, not a claim about main after 2026-10-15. Removal is always conditional on
a released egui version in use containing equivalent behavior and on the full suite tests passing
without the patch.
| Patch | Commit(s) | Behavior | Upstream status at 0.37.0 | craft-libs relevance and required test |
|---|---|---|---|---|
| E1 | 8f3abf06 |
eframe NativeOptions::viewport_hooks: every viewport's native window on create/destroy and every viewport's RawInput before egui, including immediate viewports |
not proposed | No direct craft-ui use. Compile-only fork contract; native window identity is app/craft-tablet. Test hook installed/replaced and one call per viewport in egui repo. |
| E2+E3 | 9dd799ab |
egui-wgpu fits a surface to max_texture_dimension_2d and returns ConfigureSurface/CreateRenderer errors instead of panicking |
not proposed; addresses open #8361 | Required for large/multi-monitor windows. Test surface_fit edge cases and an oversized surface; report errors rather than abort. Screenshot pixel geometry must account for reduced surface. |
| E4 | 66bacf23 |
egui-winit uses winit's Wayland data device for regular clipboard, falling back to arboard; smithay not started | emilk/egui#8657 draft; winit#4729 open | Required on Hyprland/KWin; needs W15 plus new W15P for PRIMARY. Test regular/image/drop and PRIMARY separately, serial/seat/ownership/cancel cases, and no second regular data device. |
| E5 | 0e27d87e, 777977b7, f7e89e23, 5b2b8e19 |
Bidi shaping, visual row order, marks/end caret/selection, no-default-fonts tests | emilk/egui#8577 open, plus #8698 review fixes | Affects all app text and DockArea labels. Test Arabic/Hebrew mixed rows, selection across boundaries, marks, end caret, ASCII unchanged; run with default_fonts off and on. |
| E6 | 0abfabcf |
request_focus on the already-focused widget does not interrupt IME composition |
not proposed | App TextEdit behavior. Test focus to same widget preserves composition; focus to another widget commits/cancels as intended. |
| E7 | 0670c58a |
Windows letter keys from virtual-key code on layouts without A-Z | not proposed | App shortcuts. Test Russian/Arabic/Hebrew layout key names and no duplicate text. |
| E8 | 1b03c10f |
TextEdit does not clip glyphs that overshoot the row | not proposed | App text clipping. Test Thai/Vietnamese/Arabic marks in single-line and wrapped fields. |
| E9 | b2f5cafc, e1341a3d, 5105330d, 75a5b3d3 |
Style::right_to_left mirrors menus, scroll bars, headers, title bars, combo boxes, checkboxes, popups, resize grips |
not proposed | Required for RTL UI. Test built-in chrome in both directions; DockArea manual tabs/splits remain explicit. Do not let this alter persisted layout semantics. |
| E10 | dc90c13e, c8e573f7, 34b2c0d4 |
eframe web: named keys not typed as text; letter keys from KeyboardEvent.code; lang on hidden input and speech |
not proposed | Web apps. Test browser key layout, IME, screen-reader language; wasm check is compile-only. |
| E11 | 92e61afd |
epaint rasterizes each glyph on a single-threaded vello context, avoiding vello multithreading panic | emilk/egui#8641 open draft | Required when any app enables vello_cpu/multithreading (VectorCraft). Test rasterization with the feature on and no thread-per-glyph panic. |
| E12 | e955bb18, 485c0b89, 0185887c, 7015ac23, 4067b2c4, 620e070e |
LayoutJob::direction, RTL paragraph alignment, visual-end ellipsis, UAX #9 L1, linear reorder, bidi controls keep levels |
not proposed; follows #8577 | Test forced LTR/RTL, neutral paragraphs, wrapped RTL, elision, long mixed paragraph linearity. Affects tab label widths. |
| E13 | 5e4da7f4 |
Cursor movement/hit-test/Delete/Backspace by grapheme cluster | not proposed | App text editing. Test combining marks, emoji ZWJ/flags/keycaps, Devanagari, Thai, Hangul, CRLF; hit test every x in LTR/RTL. |
| E14 | cbd77549, b3722ac0 |
Fallback fonts share baseline and grow the row; FontTweak::fallback_align default unchanged |
not proposed | CJK/Indic fallback. Test row height, DockArea tab_height clipping, and default center behavior unchanged. |
| E15 | f1cae609 |
Pluggable LineBreaker, grapheme-safe emergency breaks, LayoutJob::language |
not proposed | App line breaking. Test SimpleLineBreaker old rules, custom breaker receives language, hard breaks and emergency cluster breaks. |
| E16 | b3ef9771, bed6dfe3 |
Visual cursor movement with affinity, Home/End reading order, per-cluster password mask, neutral paragraph inherits direction | not proposed | App text editing. Test bidi arrow behavior, Home/End, selection, password masking; no craft-ui API change. |
| E17 | 520435df |
Context::set_language, UiBuilder::language, Response::accessible_language; AccessKit root language; one text run per bidi level |
not proposed | Craft-ui labels/accessibility. Test root language, per-widget language, mixed bidi runs, screen-reader names. |
| E18 | 89de8f3c |
eframe Frame::set_ui_language; web speech/input language fallback |
fork-only | App host hook. Test native and web language propagation; no craft-ui cache. |
| E19 | d64047bf, 642b74f7, baeb4515 |
One Latin-letter shortcut rule on Windows/macOS/Linux; RawInput::keyboard_layout_is_latin; per-key Latin layout chars |
fork-only; needs W25 | App shortcuts. Test non-Latin layouts and Dvorak-Command; Key alone is insufficient. |
| E20 | 23955535 |
Widget text laid out in the language of its Ui |
fork-only (with E15/E17) | Tab/menu/label shaping language. Test Arabic/Hebrew labels inside DockArea and menu; ASCII unchanged. |
| E21 | a70470b0 |
ImeEvent::DeleteSurroundingBytes; TextEdit deletes exact bytes in the commit's undo step |
not proposed; needs W26/W27 | App IME. Test exact-byte deletion, one undo step, combining accent/flag/ZWJ replacement, and no over-deletion. |
| E21b/P7b | New, no commit yet | Bounded outgoing surrounding-text payload, TextEdit producer and egui-winit W27 adapter | Confirmed missing from candidate | Fork-only contract tests cover logical text, UTF-8/preedit mapping, 4,000-byte limits and focus/password clearing; custom app editors supply their own adapters. |
Decision: keep E1-E21 and implement E21b before the target is frozen; remove none during this migration. The removal
condition for every row is a released egui version in use with equivalent behavior, the fork test
suite passing without the patch, and the app workflows passing. E5 is the dependency root for E12,
E13, E16, E17 (language runs), E18 and E20; E19 and E21 are independent but require specific winit
patches. A removal decision for one patch must consider its
dependents, its tests and the winit side. The fork's own tests are numerous: epaint text layout tests
include bidi order, mixed Hebrew/Latin, cursor hit-testing, selection boundaries, grapheme clusters,
fallback baseline, line breaking and RTL wrap; egui integration tests include ime_focus,
text_edit_overshoot, layout_language and ime_delete_surrounding. Those tests are the first
acceptance layer, not the only one.
9. Test plan and regression assertions
9.1 Commands and evidence
All commands run through maintenance/task.py run/status/finish, never directly, with a fresh task,
exact source, durable evidence and a unique job ID. Use the allocated CARGO_TARGET_DIR; do not use a
shared target.
Minimum gates, in order:
cargo fmt --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
cargo clippy -p craft-ui --all-targets --all-features --locked -- -D warnings
cargo test -p craft-ui --all-features --locked
cargo check -p craft-ui --all-features --locked --target wasm32-unknown-unknown
Then the fork/registry contract and package checks described in section 5. Record actual executed
test counts, exit codes, rustc -Vv, cargo -V, lock hashes, feature list and the full command line
for each run. Do not call a historical audit log a fresh run.
9.2 Stable fixtures and exact assertions
Use fixed RawInput.screen_rect, fixed time, pixels_per_point 1.0/1.5/2.0, both themes, and
animation_time = 0.0. No test may update a golden automatically.
Required assertions:
- Pixel/mesh: 372 positive visual pairs exact RGBA and exact vertices/indices/texture IDs/clips; 36 deliberate color/spacing/stroke changes must differ in both. If a 0.37 default changes a fixture, diagnose the component or deliberately change the independent reference; never accept a new golden to hide a visual regression.
- Geometry: exact
TabFitwidths/overflow for the existing fixtures; split first/divider/second rectangles exact; 64-tab and 4096-tab work bounded;fit_toolbarevery index reachable. - IDs/focus: tab/body IDs equal across frames and moves; duplicate labels distinct; focus survives
a panel move;
scope_idchildren do not collide. - Actions:
DockAreareturns the expected singleMove/Float/Close/Resizesequence; the layout snapshot is unchanged until the caller applies it; 800 deterministic moves never lose or duplicate a panel. - Persistence:
LayoutandActionserialize to a fixed JSON fixture and back without changing panel identity, active index, fixed sizes, accordion state or floating rect. Unknown fields are a deliberate schema decision, not an accident. - Accessibility: exact roles/names/selected/enabled as in section 7.2;
Harness::check_accessibilitypasses; unnamed input fixture (deliberate) fails when the name is removed. - Lifecycle: no print worker starts before print is opened; once requested, results are
delivered via
run_logicwithoutrun_ui; completed work requests no further repaint and repeated logic/UI calls do not start a second worker. - Glyphs: component-painted glyph set (
…,−,+) passeshas_glyphswith bundled test fonts; missing⋮under panic policy demonstrates the test detects a font regression. - RTL/fork: in the fork contract workspace only, Arabic/Hebrew mixed labels keep logical order, tab widths and hit rects remain finite, built-in menus mirror, and an ASCII fixture is byte-identical to the stock 0.37 result (where the API is shared).
- Screenshot: two tokens, callback once, hidden-window request, surface-fit scaling; app fixtures live in app repositories, not craft-libs.
- Panic policy: no new
unwrap,expect,panic,todo,unimplementedorunreachablein production code; cratelib.rsdeny headers stay.
9.3 Deliberate failure probes
The existing 36 visual mutation comparisons are the primary pixel/mesh probes. Add these targeted negative checks:
- Render an icon button with the label omitted or role changed to
Label; the accessibility test must fail. - Remove
labelled_byfrom the printDragValue;Harness::check_accessibilityor the label query must fail. - Render
⋮underMissingGlyphPolicy::Panic; the fixture must fail. - Feed the same body
scope_idto two simultaneously rendered panels; a duplicate-ID test must fail. - Change a tab-strip width by one point or reverse the overflow order; exact geometry must fail.
- Call the print discover closure twice; the one-worker counter test must fail.
- Deliberately misroute a capture slot to another request; correlation must fail. Completing clones twice is a positive one-shot test and must still yield exactly one result.
Document the expected failure for each probe and keep it out of release acceptance.
10. Performance protocol and thresholds
10.1 Baseline capture and the implemented CPU harness
The harness is test-only work. Prepare separate baseline/control and candidate qualification
worktrees with the same measured work, cases and instrumentation; record their exact commits,
complete patches, harness/lock hashes and any reviewed API compatibility adapter. The original
plan proposed 49190884278f16c9a9f5823d7698a8ffb5deb062 as a baseline; that ref alone does not
qualify a comparison. Preserve the published source and existing control worktrees. Run correctness
gates before measuring, then alternate paired baseline/candidate runs under the same conditions.
Environment to record exactly:
- host CPU: Intel Xeon E-2176G, 12 threads (6 cores), 3.70 GHz base, 125 GiB RAM (the observed host);
- current host inspection finds i915 PCI
8086:3E96and an accessible render node; no display session is inherited. Qualify the actual selected adapter/driver/backend with a managed job. Historical llvmpipe results are software-renderer evidence only, not current hardware discovery; - Windows row (if used): the Windows VM's Microsoft Basic Render Driver (CPU), not a physical GPU;
- no real-GPU measurement has been performed for this task. Hardware availability alone does not establish renderer, compositor or presentation acceptance.
The implemented target is crates/ui/examples/ui_hotpaths.rs, a headless JSON-emitting example,
not a Criterion bench target. It measures:
- pure
tabs_fit,split_rect/resizedandfit_toolbarcases across the encoded sizes/scales; - dock layouts with four split/floating panels or a 32-panel tree at 800×600 and 1440×900, at 1× and 2×;
- left-panel plus central-dock integration, with actual rendered bodies, geometry and interaction
checks. Frame timing includes
Context::run_uiandContext::tessellatetogether.
Printing receiver/poll lifecycle, separately timed text tessellation, fresh-process font discovery, GPU pipeline construction and native child/input behavior need their own evidence. They are not implemented measurements of this example. Texture deltas are counted, never uploaded; no window, GPU renderer, compositor, physical input or allocation-counting allocator runs here.
Named cross-repository acceptance owners: egui fork section 10 owns fresh-process DX12 renderer/pipeline construction (#8736), and SoundCraft/PdfCraft Windows rows exercise their real options. Its selection-then-toolbar test owns #8763/#8764; craft-ui adds select-text-then-dock/button-action integration coverage. Its hidden parent/deferred-child fixture owns #8748; DesignCraft's native child-window scenario provides the consumer evidence. Link actual results to CL-4, rather than claiming craft-libs' library harness independently validates native DX12 or child windows.
Build and run the implemented example through the owning task.py resource, keeping its allocated
target/source/evidence paths. Build separately from measurement so Cargo startup/compilation is not
mistaken for executable RSS or wall time. For example, inside the parent's managed jobs:
cargo build --release -p craft-ui --example ui_hotpaths --locked
# Use a distinct durable output for each paired attempt; repeat in the candidate's own resource.
# CARGO_TARGET_DIR is supplied by task.py, never overridden or shared across sources.
CRAFT_UI_PERF_LABEL=baseline-attempt01 \
CRAFT_UI_PERF_OUT=/absolute/task-evidence/baseline-attempt01-ui-hotpaths.json \
"$CARGO_TARGET_DIR/release/examples/ui_hotpaths"
CRAFT_UI_PERF_OUT writes the pretty JSON report; unset it to emit JSON to stdout.
CRAFT_UI_PERF_LABEL is copied into the report. Default fonts are egui's bundled definitions;
optional CRAFT_UI_PERF_FONT supplies a pinned fixture file instead. Record its exact path and
SHA256 externally. The report's fonts.font_file is a readable string (null when unset); non-UTF-8
OS paths are displayed with replacement characters, so that display is not a lossless file identity.
Actual file loading retains the OS path. Keep raw reports, stderr, source/lock/executable hashes,
full argv/environment, Rust versions, feature set, CPU affinity and profile settings for both sides.
The report's build_profile uses debug_assertions as a label: it does not independently prove
optimization/LTO/codegen settings or account for environment profile overrides.
Schema 3 records the case matrix and raw arrays. Each case warms for at least 50 operations/frames and at least 3 seconds. Pure functions collect 50 mean-of-batch samples (16 iterations per sample, 2,000 for split cases), plus 50 separate individual latencies; those individual micro latencies are diagnostic because timer overhead dominates sub-microsecond work. Mean-of-batch samples are not a frame-tail distribution. Dock/integration cases collect 1,000 individual frame durations with median/p95/p99 statistics. Bodies, geometry, interaction checks, shapes/vertices/indices and the 64-bit mesh fingerprint come from the final measured frame; the fingerprint is not exact mesh or pixel identity. Texture/repaint counters aggregate measured frames, and the hover probe runs in a separate unmeasured frame. Galley counts are recorded after warmup and after measurement.
Before comparing paired reports, require nonempty consistent case keys/counts, matching viewport, DPI, theme, font fixtures, features and instrumentation, and the intended reviewed dependency delta. Alternate repeated runs and retain every raw pair; this example neither produces Criterion confidence intervals nor automatically accepts regressions. Apply section 10.2's thresholds only to metrics the actual output supports, with noise and correctness checks. Other criteria (exact visual/mesh equality, allocation counts, a 10k-frame cache-growth run, printing/native behavior and GPU/upload timing) need separate fixtures or profiling. Measure peak RSS/wall time around the built executable with the parent's managed runner; they are not fields measured by this harness. A successful example run alone does not establish performance, native rendering or all-platform acceptance.
10.2 Thresholds
These are acceptance thresholds for the migration, not automatically generated pass/fail golden values. Correctness gates are hard. Performance thresholds apply after excluding correctness failures.
| Metric | Threshold | Action on failure |
|---|---|---|
| Exact visual pairs | 0 changed pixels, 0 changed mesh elements | Fix component or make a reviewed, justified reference change |
| Exact geometry fixtures | exact values | Fix component |
| Accessibility names/roles | exact | Fix component or scoped fixture policy |
| Idle repaint | 0 paints in 2 s after settle, no request_repaint from completed print state |
Fix lifecycle |
| Dock frame median | <= +5% candidate vs paired baseline | Profile; rerun; no acceptance on one noisy run |
| Dock frame p95 | <= +10% | Profile; rerun; report |
| Pooled dock frame p99 (>=1,000 individual frames) | <= +15% | Profile; rerun; part of acceptance; max is diagnostic only |
| Layout pure functions median | <= +3% | Profile; rerun |
| Layout pure functions p95 | <= +5%, from individual latency samples | Profile; rerun |
| 800-action dock sequence median | <= +5%; inspect growth across larger trees | Inspect algorithmic regression |
| Texture upload bytes / 100 frames | <= +5%, no repeated upload of the same image | Inspect cache keys/URI |
| Peak RSS | investigate > +8 MiB or +5%, whichever is reached first | Report both absolute and percentage |
| Galley cache size | bounded, no monotonic growth over 10k frames | Inspect cache key/language/viewport |
Noise policy:
- Run baseline and candidate on the same host, same OS image, same Rust toolchain, same feature set, recorded lock identities and same viewport/DPI. Review the intended dependency delta rather than requiring identical old/new lock contents. Alternate baseline/candidate sessions; do not compare a laptop to the server or llvmpipe to a GPU.
- Warm up for at least 3 s or 50 iterations, whichever is longer. Discard warmup.
- Use 5 independent sessions of at least 200 individual frame samples for frame percentiles, plus the Criterion runs for microbenchmarks. If the coefficient of variation of medians is > 3%, or any session has a build/test/profiling process competing for CPU, discard and rerun the pair.
- Accept only when the median-of-medians, pooled p95 and pooled p99 meet the table. Report p99 only from the pooled >=1,000 individual samples, not each 200-frame session; if tail variability prevents a repeatable verdict, expand to 10,000 frames. Report the raw session values even when they pass.
- A changed visual golden is never auto-accepted because of a performance change. A compile-time measurement is not UI performance and must not be reported as such.
The Windows row is a separate performance table (Basic Render Driver, guest CPU, guest memory). The macOS row requires real CI or hardware evidence; without it the row is unresolved. The real-GPU row requires fresh adapter-qualified measurements. The suite protocol's startup counts, small operation floor and stricter applicable thresholds also apply.
11. Implementation PR stages
Dependency DAG:
all reviewed plans land
-> CL-0 baseline harness and measurements (before production change)
winit completed integration including W15P + W27 lifecycle revision -> approved publication
-> egui final P8 incl. P2b, P7, P7b/E21b and P7c -> approved publication
-> CL-1 atomic API/fork graph
-> CL-2 capture prototype + lazy-print tests
-> approved prototype revision for integration consumers
-> DesignCraft + GridCraft adapter PRs/reviews
-> CL-2F final helper API freeze and docs
-> CL-3 accessibility/glyph/persistence/direction tests
-> CL-4 stock/fork fixtures and linked native acceptance
-> CL-5 final compatibility documentation
CL-0 is a separate baseline harness stage. CL-1 cannot start until CL-0 baseline records are complete. CL-2 and CL-3 may proceed after CL-1 on disjoint code; their publication still requires approval. CL-2's initial API is explicitly provisional until the two named adapters are reviewed, preventing a dependency cycle between helper availability and API freeze.
CL-1: egui 0.37 API and coherent suite lock
Files:
Cargo.toml(workspace egui pin).crates/ui/Cargo.toml(dev dependency versions).crates/ui/src/buttons/mod.rs,crates/ui/src/docking/surface.rs,crates/ui/src/layout/resize.rs,crates/ui/src/tabs/mod.rs(role mapping).- The exact closed test-file allow-list in §4.2 (
Id::unique,scope_id, ellipsis fixture); additional files need a named reviewed exception, not a broad glob. crates/ui/README.md(version +scope_idexample).- Root patches and
Cargo.lockregenerated against the reviewed immutable storytold egui/winit commits; retained PDF backend patches remain intact.
Use a local stock-0.37 experiment to isolate API fallout, then land the coherent suite graph in
one compatibility PR after the fork dependencies are published with approval. Stock compatibility
is verified independently; no intermediate registry-only consumer graph is a suite handoff.
Rollback: revert the coherent commit. The old coherent set is preserved at
49190884278f16c9a9f5823d7698a8ffb5deb062: egui family 0.36.2, accesskit 0.24.1, kittest 0.4.0,
winit 0.30.13 and the baseline Cargo.lock. Do not partially revert only one crate and do not leave a
0.37 manifest with a 0.36 lock.
Prerequisite: the completed reviewed/published P8 fork includes P2b, P7, P7b/E21b and
P7c, with W15P and the W27 lifecycle revision. Intermediate pins are not accepted.
Acceptance: all section 9.1 gates against the suite fork and focused stock-0.37 compatibility
checks; cargo tree has one egui family; visual
suite still exact; no deprecation warnings under clippy -D warnings.
CL-2: Capture completion and existing lifecycle contracts
Files:
crates/ui/src/lib.rsdeclares publiccapture; the lightweight slot has no new feature gate or renderer dependency. Public types are available undercraft_ui::capture.crates/ui/src/capture.rscompletion-slot prototype and tests. Publish only after approval as a provisional git API; DesignCraft and GridCraft then implement their adapters against that exact revision. CL-2F freezes the public API after joint review.crates/ui/src/printing.rsonly if a demonstrated regression needs a fix; preserve lazy discovery.crates/ui/src/printing/tests.rs(logic-only and one-worker tests).crates/ui/README.mdlifecycle paragraph.
No change to Settings, Feedback, Discover, Submit, begin, ready, or job ownership.
Rollback: revert the helper together with its adapters, preserving pending-request cleanup and
the previously coherent app dependency set. Print behavior changes require their own regression.
Acceptance: tests from sections 6.2 and 7.8; cancellation, out-of-order result and shutdown stress; no worker before print-open, no duplicate workers or repaint after completion.
CL-3: Accessibility, glyph, persistence and docking direction contracts
Files:
crates/ui/src/docking/tests.rs,tabs/tests.rs,layout/tests.rs,buttons/tests.rs,printing/tests.rs.crates/ui/src/docking/surface.rsimplementsDockArea::direction/DockDirection;crates/ui/src/docking/mod.rsre-exports the public direction type.- New
crates/ui/tests/panel_integration.rsandserde_contracttest module; opt-in docking direction implementation and the exact geometry/hit/drag tests in §7.7. - Beyond the explicit direction policy, no production change unless an assertion exposes a real component bug; then fix the component in the same PR and document the exact role/name change.
Acceptance: exact roles/names as section 7.2; Harness::check_accessibility; glyph coverage; fixed
JSON round-trip; panel order/clip/float layer; targeted failure probes. Rollback: revert the
production direction API and its app adapters as a coherent set; other fixtures are test-only.
Any additional component fix is identified and reverted separately.
CL-4: Suite fork, winit pin and dual-compatibility checks
Files:
- Verify the root patches and lock introduced atomically in CL-1, with one git source per family.
ci/registry-eguiisolated workspace pluscargo package -p craft-uismoke check. This preserves library portability; it does not authorize a crates.io release.ci/egui-fork-contractisolated workspace that depends on the four UI-facing craft crates and compiles against the fork witheframewgpu/accesskit/x11/wayland and winit.- Documentation of the consumer patch requirement (README or docs).
This PR does not publish anything and does not change the published manifest's registry requirement. Rollback: retain CL-1's coherent dependency set and revert only this stage's fixture changes.
Acceptance:
cargo tree -dempty (or only pre-existing unrelated duplicates documented);- exactly one
egui/epaint/egui-winitand onewinitsource; - fork contract compiles on Linux (x11+wayland), Windows GNU check, and wasm check for
craft-ui; - packaged
craft-uistill compiles/tests against registry 0.37.0; - E1/E4/E19/E21 symbol smoke checks compile only where the API exists;
- no branch names in locks or manifests.
CL-5: Documentation and release checklist
Files:
crates/ui/README.mdcompatibility paragraph: registry 0.37 vs suite fork, consumer patch requirement,scope_idexample.docs/ui-components-audit.mdversion statement and a dated migration note; do not rewrite its historical pinned hashes.docs/egui-037-migration-plan.mdstatus update only after the plan is approved.- Release notes in the user's voice, no agent/model/tool attribution.
Acceptance: documentation links to immutable commits/tags; no machine-local path; no claim of a performance or platform result that was not measured. Rollback: documentation-only.
12. Risks and migration traps
- Lock conflict regression. The audit merge proves how easily a 0.37 manifest gets a 0.36 lock.
Always inspect the lock, not only the manifest, and run
cargo tree. - Fork patch not propagating. A consumer that only patches
eguiwill get registryegui-winitor registry winit. The root patch must name every family member and winit. - Movable branch pins.
craft/0.37,craft/0.30.13-langandorigin/ai-0.37can move. Use full SHAs in all locks, fixtures and docs. - Fork vs upstream API leak. The shared library deliberately preserves stock 0.37 compatibility,
independently of its git-only suite delivery. Keep fork-only APIs in app/fork
fixtures:
Context::set_language,UiBuilder::language,Response::accessible_language,Frame::set_ui_language,LayoutJob::{language,direction},LineBreaker,FontTweak::fallback_align,Style::right_to_left,RawInput::latin_layout_chars,RawInput::keyboard_layout_is_latin, E21b surrounding-text/owner payload and clear APIs,ImeEvent::DeleteSurroundingBytesandViewportHooks. The independent stock compile/test fixture is authoritative, not just a symbol-name grep. - Default fonts changed twice.
default_fontsbecame opt-in and monochrome emoji fonts became separate. The old⋮tofu case became a hard test failure under the new panic policy. Custom app fonts, not craft-ui production defaults, must prove glyph coverage. - Kittest panic policy.
MissingGlyphPolicy::Panicandcheck_accessibilityare deliberate bug detectors. Scoped opt-outs, never global. - Screenshot callback ownership. Callbacks are one-shot,
Send + 'static, and execute outside the UI pass. A callback that captures&mut Appwill not compile; a callback that never wakes the app will strand a hidden-window result. - Surface fitting changes screenshot pixels. E2 may render a window at a smaller surface and scale it. Use returned image dimensions and capture-time logical viewport/DPI metadata; the private integration render_scale is not a public app API.
- Manual DockArea layout is not mirrored by
Style::right_to_left. Tabs, split first/second and floating grip placement follow the explicit opt-in visual policy in section 7.7; E9 does not implement it automatically. - Bidi changes text widths. A tab label with Arabic/Hebrew can change
tabs::fitresults. Keep ASCII fixtures exact and add fork-specific mixed-script geometry tests; do not update ASCII references for an unrelated reason. - IME and undo. E21's exact-byte deletion is only correct if the app keeps the commit and deletion in one undo transaction. Do not reimplement deletion in app code.
- Clipboard PRIMARY. Regular copy/paste and middle-click PRIMARY are separate paths and separate compositor behaviors. Test both.
- GPU/resource tests.
WgpuTestRendereris offscreen and prefers CPU adapters but may fall back to a physical GPU. Record adapter identity; enforce a CPU adapter for deterministic goldens and keep physical-GPU performance rows separate. It is not native focus/IME/picker evidence and not a real-GPU claim. - Panel/root UI borrow. Calling
Context::root_uifrom insideDockAreaor a component panics. Components take&mut Ui. - Multiple passes.
request_discardcan repeat arun_uiclosure. One-shot side effects in tests or app callbacks must be guarded.
13. Resolved design and blocking acceptance
- Dependency policy. Integrate the reviewed patches into storytold egui/winit first; pin their final immutable commits at the suite root. The aiongg SHAs are reviewed inputs, not promised production tips. A separate registry fixture verifies stock API compatibility.
- Versioning. Ship a new git revision and compatibility note; no blanket workspace version bump or crates.io publication is part of this task.
- Printing. Keep user-triggered/lazy
refreshand idempotentpoll; hidden polling must deliver requested results without starting unrelated printer discovery. - Docking direction. Preserve serialized panel IDs, split axes, tab order and positions. Adopt the explicit opt-in DockArea direction policy in §7.7; test RTL painting, hit coordinates, visual-to-logical drag insertion and unchanged serialization. Do not silently mirror the persisted model or add a locale redesign.
- Capture. Use the narrow weak completion-slot design from section 7.8; review two concrete callers and stress cancellation/memory before freezing the API. Keep app policy outside it.
- Platform evidence. Qualify the accessible Linux i915 render node and software backends separately. Windows Basic Render Driver is software evidence. macOS needs native evidence, and wasm compilation does not replace a real browser interaction workflow.
Blocking acceptance criteria for craft-libs:
- CL-1 through CL-4 gates pass with fresh task/evidence records; no direct cargo run.
- One egui family and one winit source in the fork lock; packaged crate still resolves stock 0.37.
- Fresh reports reproduce or rule out the applicable upstream #8731 text-tessellation and #8512 font-startup regressions; renderer fixtures include #8736 DX12 setup, #8763 selection/action and hidden/deferred viewport behavior. #8763 is the open selection issue; #8764 its open, unmerged proposed fix. #8748 is a closed, unmerged PR. #8512 is a closed feature PR whose comments supply the startup-cost discussion. None is evidence of a released regression fix.
- Exact visual/geometry/accessibility/persistence tests and the deliberate failure probes pass.
cargo tree -dhas no new duplicates; no branch refs; no machine-local paths.- The performance table meets the thresholds, or a regression is profiled and explicitly reviewed with a documented exception. A compile-time number, a changed golden or a single noisy sample is not acceptance.
- Every push/new PR has explicit human approval after the exact diff and validation are shown.
Commits and PR text use the user's voice and username-only contributor credit (
@skylartaylor); no agent/model/tool attribution.
14. Source index used for this plan
Primary upstream:
- egui 0.37.0 changelog and release commit
841a328b:CHANGELOG.mdat tag0.37.0. crates/egui/src/context.rs(run_ui,run_pass,root_ui,run_logic,request_screenshot).crates/egui/src/id.rs(Id::unique,IdSalt),ui.rs(scope_id),ui_builder.rs.crates/egui/src/containers/panel.rs(Panel,CentralPanel).crates/egui/src/data/screenshot_callback.rs,viewport.rs(Screenshot).crates/egui_kittest/src/builder.rs,accessibility.rs;crates/epaint/src/text/fonts.rs(MissingGlyphPolicy,has_glyphs).- Upstream PRs: #8361, #8428, #8489, #8534, #8535, #8542, #8555, #8577, #8581, #8583, #8589, #8592, #8601, #8617, #8657, #8641, #4729 (winit).
Suite fork:
/home/codex/work/CraftApps/egui037-migration-20261022/egui:aiongg/craft/0.37=a4f9a17f64a6bae61350fb85804c9df0d9865916;PATCHES.mdE1-E21;0.37.0=841a328b./home/codex/work/CraftApps/craft-fork-20261020/winit-ai-lang:craft/0.30.13-lang=8b35388bd719e8cc86a95971ba0bc026c9a8f058;craft/0.30.13=ffae5198; PATCHES.md W1-W27.- craft-libs PR 199 ref
refs/review/pr199=b68de8c; audit mergeec8840ais not a target. - Historical evidence, not fresh validation:
/home/codex/work/ssd2/task-evidence/craft-fork-plan-20261020/PLAN.md,/home/codex/work/ssd2/task-evidence/craft-fork-ai-lines-20261022/SURVEY.md,/home/codex/work/ssd2/task-evidence/craft-fork-egui037-20261021/RESULTS.md,/home/codex/work/ssd2/task-evidence/egui037-audit-20261022/fork/craft-libs.log.
craft-libs source anchors:
Cargo.toml:34;crates/ui/Cargo.toml:13-24;.github/workflows/ui.yml:20-58.crates/ui/src/lib.rs;crates/ui/src/buttons/mod.rs:70-102.crates/ui/src/tabs/mod.rs:40-163.crates/ui/src/layout/geometry.rs:1-135;layout/resize.rs:1-198;layout/toolbar.rs:1-152.crates/ui/src/docking/model.rs:1-798;docking/surface.rs:1-919;docking/targets.rs:1-99.crates/ui/src/printing.rs:1-261;printing/tests.rs:1-257.crates/ui/src/theme/mod.rs:1-97;icons/mod.rs:1-78.crates/ui/tests/visual_regression.rs:1-394;tests/visual/fixtures.rs:1-327;tests/docking_workflows.rs:1-252.crates/platform/src/appearance.rs:439-525;platform/src/dialogs.rs:220-251;crates/quit/src/close.rs:1-190;crates/window/src/lib.rs:255.crates/ui/README.md:115,237;docs/ui-components-audit.md:5;docs/ui-visual-regressions.md;docs/ui-docking.md.
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/craft-libs main now resolves to f180fc0d35d521c5d18e118c881d3efde1ec1eee. The owned checkout was fast-forwarded to this revision. Earlier baseline SHAs and line anchors above are retained as historical review evidence; use this new revision and its exact lockfile for current-main integration and paired measurements. The original refs remain reachable.
Preserve new hayro/krilla hostile-input bounds, updated SOURCE manifests/nested lock, and chrono std-only workspace policy. PR199 advanced to1dcde7ed and now incorporates49190884 security fixes; its14-file delta can be reviewed against currentmain rather than treating newhead as the old stale tree. Regenerate lock against final dependencies.
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 row-alignment component, 2026-10-23
craft_ui::text::align_galley_rows positions already-shaped rows within an explicitly allocated width. It accepts a row alignment callback and uses stock egui 0.37 types, so it does not make the shared UI crate depend on the fork's bidi extensions. It is useful for comment cards, captions and message panels. It retains shared row glyphs/meshes and logical text, updates row positions and logical/mesh bounds, and never mutates the cached source galley. Oversized left-aligned rows overflow right, right-aligned rows overflow left, and centered rows overflow on both sides; nonfinite or negative widths are refused without changing the input.
WordCraft supplies alignment from the fork's rtl_paragraph field. This preserves native shaping, mixed-direction runs, mirrored bracket glyphs and neutral-paragraph inheritance. The fork's LayoutJob::halign_min_is_start aligns RTL rows to the widest text row, which is insufficient for a short comment in a wider card. No new Unicode reordering implementation is introduced. Unicode's logical-order and mirroring rules are described in UAX #9; the fork source documents its row placement in epaint/src/text/text_layout_types.rs.
Prepared helper tests check independent row alignment, unchanged cached row data, mesh bounds, invalid widths and oversized rows. WordCraft's real Arabic/mixed/neutral-paragraph test passes in egui037-wordcraft-candidate-20261023-targeted-07, including atlas-bitmap bracket orientation. Stock shared-library tests, performance and independent component review are pending; the prior 110-test result predates this helper.
The second independent component review found no blocking helper defect. Word now clips balloon ink to the card and disables the fork-only start-edge override before custom row alignment. Additional source tests cover fractional scale, zero width, idempotence and separate ink bounds. Execution remains pending.