* E21: require release font coverage and stabilize capture deadline tests * E21: separate capture, IME and field-save regression phases * M0: correct the migration qualification reference * M-egui037: document input lifecycle and UI regression contracts * M0: correct egui 0.37 status docs and stabilize join-code regression --------- Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com> Co-authored-by: Codebuff <noreply@codebuff.com>
92 KiB
FigCraft egui 0.37 migration plan
Status update (2026-10-25): The migration integrates main 92682728 and pins egui 0.37, winit and craft-window. Required-font CI run egui037-fig-review-full-ci-20261025-0803 passed 3,306 test executions, zero failures and nine ignored across 71 summaries, including WASM and all three plugin bundles. Screenshot encoding now precedes a shared final-write/reply gate: timeout cancellation cannot overtake a committed file, and a fresh deadline check rejects a late write. A regression through the actual arrival path failed with the previous write behavior and passed with the gate. Native visible GUI/browser workflows, native IME, paired app performance and current-source platform/release installation remain open. Human review and human merge are required.
The original planning record below is historical.
Status: implemented locally, with host CI qualification recorded in the current qualification
section below and in docs/egui-037-local-followups.md. Earlier sections retain the original
planning baselines. Native/browser workflows and paired app performance remain open.
No publication approval exists for this document or any derived branch/PR.
Scope: the exact checkout
/home/codex/work/CraftApps/egui037-migration-20261022/figcraft-implementation. This plan covers
FigCraft only. Suite-wide fork work, shared craft-libs work and publication approval remain with
the parent workflow. This revision resolves the adversarial review of the previous draft in place;
the review file is
/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/plan-reviews/figcraft-v1.md.
Capture acceptance follows the suite contract: a queued-not-issued hidden request and
each settling frame must request RequestPaintWhileHidden from logic; only an issued
screenshot command forces paint itself. Callback arrival has no implicit wake, so the
live request owns the viewport waker. Record known issuance UI bounds and a geometry
epoch, reject/retry any intervening resize/DPI/zoom/surface change even if it changes
back, and derive pixel scale from returned dimensions only under an unchanged epoch.
Default limits are 4 requests, 128 MiB/image and 256 MiB aggregate. Count reserved
in-flight and completed images together.
1. Source identity and what was inspected
1.1 Checkout and baseline
- FigCraft integration checkout:
8167f13a6a416ca61fcb505a7eaf6be383f6c5c0. Earlier baselines (80f916326fb94f3a88cb24e64f396f564a0ebc06,8d790607ff1ad40e32343ca2116d8fdcf3244c8d) are historical review anchors only. - The worktree contains the migration implementation; this document is not a claim that those changes are compiled, tested or publishable.
- Repository instructions:
AGENTS.mdandCLAUDE.mdare identical; there are no nestedAGENTS.md/CLAUDE.mdfiles undercrates/,apps/,plugins/orxtask/. - Current lock:
egui,eframe,egui-wgpu,egui-winit,egui_extras,ecolor,emath,epaintall0.36.2;winit0.30.13;wgpu30.0.1;accesskit0.24.1;wasm-bindgen0.2.129. - Git dependencies:
craft-docpathrev82a24b006eefa036b43c09a67fb9a3fbd17ffe63;craft-windowrev1f805378d454dac48b5c214029d254055fdf961b(that revision's workspace declares egui 0.36 and cannot be used unchanged with 0.37). - Build input: tracked
.cargo/config.tomlsetsCRAFT_FONTS_DIR=../craft-fonts(relative, optional). It is locally skip-worktree, so it did not appear ingit status; it is not a path patch and does not change dependency resolution. CI intentionally builds without craft-fonts. - There are no
vendor/directories, source patches or[patch]sections in this repository.plugins/is a separate workspace and does not use egui.
1.2 Target revisions and publication boundary
The production pins are not the candidate fork SHAs. They are the final reviewed
storytold/egui, storytold/winit and storytold/craft-libs immutable revisions produced by the
suite's reviewed patch series, published with fresh human approval. Those SHAs do not exist yet at
this planning revision. Do not publish or implement a manifest that names a candidate or a branch.
| Item | Exact value / status |
|---|---|
| Upstream egui 0.37.0 release commit | 841a328ba2dd8f6996beb7189ebb7fc00e668a2a (annotated tag object differs) |
| Upstream egui 0.36.2 control | 49682f8baa058bf49e011035cfbd6e825f88a5ef |
| Published storytold egui 0.36.2 | 767b1cad294120689a514bb4bd68c077e46679b9 |
| Published storytold winit 0.30.13 | ffae5198770d7730e718043f035a772b7dbc3b1b |
| aiongg egui candidate | aiongg/egui craft/0.37 a4f9a17f64a6bae61350fb85804c9df0d9865916 — comparison input only |
| aiongg winit candidate | aiongg/winit craft/0.30.13-lang 8b35388bd719e8cc86a95971ba0bc026c9a8f058 — comparison input only |
| Final storytold egui | STORYTOLD_EGUI_FINAL_REV — unknown until the reviewed E1–E21 + E21b/P7b series is approved and published |
| Final storytold winit | STORYTOLD_WINIT_FINAL_REV — unknown until W1–W27 + W15P are approved and published |
| craft-libs merged baseline | 3832c47ef68a0e6214f2c1e85f3526b6adcb686e (PR #199 merged: “craft-ui: update egui to 0.37”) |
| craft-libs PR #199 head / audit merge | b68de8c5f4f1c060904d211a7b1320aa0f7bce78 / ec8840af8c5fda695cf195cdd05e5ae078415c07 — unsafe stale history; do not merge or reuse either tree |
| Local craft-libs integration | 3832c47 plus the reviewed capture-completion and text-alignment helpers in the local craft-libs-implementation worktree; no published commit yet |
| Final craft-libs | STORYTOLD_CRAFT_LIBS_FINAL_REV — unknown until the reviewed helper stage is approved and published atop 3832c47 |
Verified source facts for the craft-libs anchor: the historical port source 8614e4a contains no
crates/window/Cargo.toml and no crates/platform; b68de8c is a stale tree that omits current
craft-platform and reverts security, settings, docking and printer fixes. The merged
3832c47 tree contains crates/window, crates/platform and the 0.37 workspace. The local
implementation must stay on that merged base and must not reuse either PR head as its tree.
Language work: storytold/craft-libs#198 is an issue/proposal, not a PR or a merged dependency.
FigCraft consumes the final fork revisions; it does not copy language helpers from craft-ui.
1.3 Evidence consulted (historical, not fresh validation)
/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/lifecycle-source-audit.mdis the authoritative lifecycle evidence for this plan./home/codex/work/ssd2/task-evidence/egui037-migration-20261022/ime-surrounding-gap.mdis the authoritative E21b/P7b gap inventory./home/codex/work/ssd2/task-evidence/egui037-migration-20261022/upstream-discussions.json: issue/PR snapshots for #8535, #8489, #8555, #8583, #8512, #8657, #8577, #8731, #8736, #8763, #8764 and #8748./home/codex/work/ssd2/task-evidence/egui037-audit-20261022/{fork,stock}/figcraft.errors: historical compiler diagnostics, used only as clues./home/codex/work/ssd2/task-evidence/craft-fork-egui037-20261021/RESULTS.md,craft-fork-ai-lines-20261022/SURVEY.md,craft-fork-plan-20261020/PLAN.md.
No build, test, benchmark, browser run or GUI check was performed for this plan.
2. Decision summary
- Target the final reviewed storytold egui/winit revisions, not stock 0.37 alone and not the aiongg candidates. FigCraft needs the language/bidi/IME patches, W15P PRIMARY, E21b outgoing surrounding text and the egui-wgpu fixes carried by the reviewed series.
- Keep FigCraft's existing
App::logic+App::ui(&mut Ui)architecture. Do not rewrite toApp::update; it does not exist. Do not callContext::root_uifromApp::ui; the rootUiis already borrowed (upstream #8535). - Treat the real eframe lifecycle: native and web both call
App::logicinside theContext::run_uiclosure, once per layout pass.run_logicis not distinguishable throughViewportInfo::visible(). Design logic work to be safe and idempotent in both callback paths, and use a narrow first-pass guard only for genuinely once-per-layout-frame UI setup. - Keep command/undo/persistence contracts. UI input is consumed through an explicit one-shot
action queue/token; do not gate all commands on
current_pass_index() == 0, which cannot be implemented inFigApp::run/set_props(neither receives aContext) and would starve fresh work. - Use a small local completion-slot adapter in FigCraft, matching the shared capture contract, with a weak callback reference, per-request cancellation, bounded aggregate image bytes, capture-time viewport/geometry/DPI and viewport-aware wakeup. Keep request IDs, paths, replies and timeouts in FigCraft.
- Correct the actual a11y edge handling: FigCraft's panels are
resizable(false), so egui does not createleft_panel.__resize/right_panel.__resize. FigCraft's custom edges areId::unique(("panel_edge", "left"/"right"))with a focusableSense::click_and_drag(). Make them non-focusable and test the real IDs. Preserve prefs.panel_widths; fixed panels and disabled eframe persistence require no PanelState migration. - Add E21b/P7b outgoing IME surrounding text as an atomic compatibility prerequisite: bounded
transient payload, built-in TextEdit producer, Wayland adapter, and a real model/preedit/UTF-8
index adapter for FigCraft's custom editor. Never pass
Noneplaceholders as the completed port. - Keep logical text at shaping, clipboard, accessibility, save and IME boundaries. Do not feed
display-bidi (visually reordered) strings into egui shaping, clipboard or IME. FigCraft does not
use
craft-ui'sDockArea; if it later adopts it, use the explicit opt-in direction policy with unchanged saved physical splits and logical tab order. - Use pinned licensed font fixtures from the egui plan's section 10.6. Keep FigCraft's central
craft-fonts policy, do not copy font binaries into the repository, and do not claim color-emoji
coverage while
epaint/color_fontsis disabled. - Stage reviewable PRs with a dependency DAG and rollback. Every build/test uses
maintenance/task.py run/status/finish; no direct cargo test/build is acceptance.
3. Baseline audit
3.1 Manifests, entry points and feature targets
| Target | Manifest / source | Current features / role | 0.37 action |
|---|---|---|---|
| Desktop app | apps/figcraft/Cargo.toml, apps/figcraft/src/main.rs |
eframe default_fonts, wgpu, accesskit, wayland, x11; wgpu renderer; Windows windows 0.62; macOS fmv-macos-events; Linux winit wayland-csd-adwaita-notitle |
Keep feature shape; add final eframe/egui revisions; enable clipboard explicitly; keep system_fonts off initially; repoint craft-window to the final craft-libs revision |
| Browser app | apps/figcraft-web/Cargo.toml, src/main.rs, src/web.rs |
wasm32 eframe default_fonts, wgpu, accesskit; WebRunner; WebGPU/WebGL2 |
Update eframe/egui; keep web_screen_reader disabled as at baseline; web_file is an egui feature used by eframe for wasm, not a FigCraft feature to “verify” independently |
| UI crate | crates/ui-egui/Cargo.toml, src/lib.rs |
egui/egui_extras 0.36, no eframe; about 1,478 egui references; no kittest dev-dependency | Update egui/egui_extras; add default_fonts and egui_kittest 0.37 for strict a11y tests; add the local capture adapter; mechanical API migration |
| Text/render | crates/text, crates/render |
no egui dependency; own harfrust/skrifa/tiny-skia | No code change expected; verify duplicate harfrust/skrifa versions and UI/document metrics |
| CLI/server/MCP | apps/figcraft-cli, apps/figcraft-server, crates/mcp |
no egui/eframe (MCP headless) | No dependency change; keep commands and outputs |
| Plugins | plugins/ separate workspace |
no egui | No change |
Entry points:
- Native:
apps/figcraft/src/main.rsmain()→eframe::run_native;App::logic,App::raw_input_hook,App::ui,App::on_exit. - Child windows:
crates/ui-egui/src/windows.rsWindows,viewport,show,draw,enter/leave,as_window. - Browser:
apps/figcraft-web/src/web.rsstart,WebShell::ui,WebServices. - Headless/UI tests:
Context::default().run_ui(...)in manycrates/ui-egui/src/*_tests.rsfiles;crates/ui-egui/src/idle_tests.rshas the frame/idle harness;windows_tests.rshas therun_logichidden-window harness. - Render/control:
crates/ui-egui/src/render_worker.rs;crates/ui-egui/src/control.rs.
Build/test entry points:
.github/workflows/ci.yml: Linux fmt, clippy--workspace --all-targets --locked -D warnings, workspace tests,xtask layers,xtask assets,xtask wasm.xtask/src/main.rs:ci,layers,assets,wasm,bench,plugins.crates/ui-egui/src/perf_tests.rs: ignoredperf_ui_framesandreport_componentsbenchmarks, currently printing arithmetic means only.apps/figcraft-cli/src/perf.rs: release engine/render timings, currently a mean of several runs.apps/figcraft-web:trunk build --release;packaging/web/package.shenforces the 24 MiB wasm cap.- Windows ARM workflow only packages/installs; Windows x64 build/test and visible GUI checks must be run separately through the VM workflow. FreeBSD is a nightly VM build/package job; macOS is release-only and has no routine UI test job.
3.2 Current lifecycle: what is already true
On 0.36.2 the app already uses eframe::App::logic and eframe::App::ui:
apps/figcraft/src/main.rsApp::logicdrains the control queue, hidden requests, Apple events, close requests, title updates and recovery.App::uiwrapsFigApp::uiinwindows::guard.crates/ui-egui/src/lib.rsFigApp::uicallsself.logic(&ctx),autosave_tick,auto_save_tick, thenwindows::begin,frame(which also callscontrol::drain),settle,show.crates/ui-egui/src/windows.rsdrawswaps a child window's pane into the app, callsapp.logic(&ctx), then frames it underwindows::guard.FigApp::logicinstalls language/fonts/theme, computes UI zoom fromctx.content_rect(), creates the render worker and scans input for CJK fallback.- Commands go through
FigApp::run/run_user/run_background; the engine owns history snapshots.
The authoritative source sequence is not “logic once per native frame”:
- Native
crates/eframe/src/native/epi_integration.rs::updatecallsapp.logicthenapp.uiinsideContext::run_ui. - Web
crates/eframe/src/web/app_runner.rsdoes the same for visible tabs and callsContext::run_logicfor hidden tabs; nativerun.rscan force a fullPassMode::FullUI for invisible windows and aLogicOnlypass for overdue redraws. Context::run_logicrefreshes only viewport/window focus information; input events, time, layout and normal pass state remain from the previous UI pass.- After a normally completed
Context::run_ui,current_pass_index()is zero, including after multiple passes (ContextImpl::end_passtakes the output;run_dynrestores the count only before the next UI pass). Do not write tests that expect a stale nonzero index after completion. ViewportInfo::visible()derives from minimized/occluded and can beNone; it does not identifyrun_logicversus a forced hidden UI pass. Visibility is not the tick phase.
The migration must therefore make background work idempotent in both callback paths and move input/geometry/theme setup to the UI phase. A narrow first-pass guard is valid for once-per-layout UI setup; it must never suppress fresh control commands, widget actions, background completions or deadline work merely because a pass index is nonzero.
3.3 Historical compiler diagnostics (clues only)
| Location | Diagnostic | Meaning for this plan |
|---|---|---|
crates/ui-egui/src/control.rs:84, idle_tests.rs:275 |
egui::Event::Screenshot variant not found |
0.37 uses ScreenshotCallback; replace event polling |
crates/ui-egui/src/control.rs:119, idle_tests.rs:275 |
egui::UserData not found |
Removed with the callback API |
crates/ui-egui/src/control.rs:367 + 11 test files |
Event::MouseWheel missing field source |
Add MouseWheelSource |
crates/ui-egui/src/dialogs.rs:108 |
Role::SelectableLabel not found |
0.37 keeps deprecated pub type WidgetType = Role; only renamed variants such as SelectableLabel are gone. Map to Role::Button |
crates/ui-egui/src/frame_labels.rs:63 |
non-exhaustive match missing Shape::Band |
Add the 0.37 shape variant |
crates/ui-egui/src/typography.rs:1312 |
ScrollArea state offset private |
Use clamped_offset() |
| Many files | Id::new/Ui::id() deprecation warnings |
CI clippy -D warnings turns these into failures; replace mechanically |
clip_rect_margin is not removed in 0.37; it remains as a deprecated unused field. Remove its use
because it has no effect, not because it fails to compile. The audit stopped at these errors and did
not compile the app, tests, Windows, wasm or examples. A fresh managed build after the final pin is
required; this table is not acceptance.
4. Dependency and patch plan
4.1 What must be pinned
Add a root [patch.crates-io] block in Cargo.toml (consumer root; dependency patch sections do
not propagate). The production block uses the final reviewed storytold revisions. The aiongg SHAs
below are recorded only as candidate/comparison inputs and must never appear in the production
manifest.
[patch.crates-io]
egui = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
eframe = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
egui-wgpu = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
egui-winit = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
egui_extras = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
ecolor = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
epaint = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
emath = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
epaint_default_fonts = { git = "https://github.com/storytold/egui", rev = "STORYTOLD_EGUI_FINAL_REV" }
winit = { git = "https://github.com/storytold/winit", rev = "STORYTOLD_WINIT_FINAL_REV" }
Only patch crates FigCraft actually resolves. ecolor, emath and epaint_default_fonts are pulled
by egui/default fonts; leaving any of them on crates.io creates mixed egui-family versions. Add
egui_glow only if a glow build is introduced. If a crate is not resolved in the final graph,
remove its patch row and record the cargo tree evidence.
egui_extras must be patched because crates/ui-egui/src/credits.rs uses
TableBuilder/TableRow/TableBody; a 0.36 egui_extras would pull a second egui 0.36.
craft-window is a git dependency, not a path patch. Pin it to the final craft-libs revision whose
workspace requires egui 0.37. Do not keep 1f805378 (egui 0.36) with a 0.37 egui patch: Cargo
cannot apply a 0.37 patch to a ^0.36 requirement. craft-docpath is independent of egui; prefer
the same final craft-libs revision for both crates after confirming its source hash, or keep its
existing rev if the final commit changes unrelated behavior this task has not reviewed.
FigCraft currently has no craft-ui dependency. Keep its capture adapter local in
crates/ui-egui/src/control.rs, following the CL-2/CL-2F ownership, cancellation, byte-limit
and viewport-wakeup contract. Eleven existing creative consumers adopt the shared helper;
DeckCraft, FigCraft and ArtCraft Native use local adapters. Reuse the reviewed conformance cases
and compare behavior with the shared prototype; do not add the entire crate solely for capture.
4.2 Feature decisions
| Manifest | Decision | Reason / test |
|---|---|---|
Root [workspace.dependencies] |
egui = "0.37", egui_extras = "0.37", eframe = "0.37" |
Pin all three and verify in the lock |
crates/ui-egui/Cargo.toml |
egui = { workspace = true, features = ["default_fonts"] } (or the workspace dep carries it) |
0.37 egui default features are empty; theme::install_fonts calls FontDefinitions::default() and tests without eframe need real fallback fonts |
apps/figcraft/Cargo.toml |
Keep default_fonts; enable clipboard explicitly; keep wgpu, accesskit, wayland, x11 |
eframe 0.37 default_fonts forwards to egui; clipboard is optional in 0.37 |
apps/figcraft-web/Cargo.toml |
Keep default_fonts, wgpu, accesskit; keep web_screen_reader disabled |
Browser has no system-font provider; keep the 24 MiB wasm budget |
eframe system_fonts |
Off initially | FigCraft already scans system fonts and installs only needed faces; enabling fontique adds a second scan and nondeterminism. Evaluate later with a separate measured PR |
clipboard |
Explicitly enable native eframe/clipboard in F2 to preserve 0.36 OS text clipboard behavior; verify E4/W15P and image alpha |
0.36 forced the integration feature; 0.37 makes it optional. Keep existing arboard image handling and browser clipboard paths separately tested |
epaint/color_fonts |
Off by default | 0.37 color-glyph support is behind this feature. Do not claim color-emoji coverage in the normal build; if browser/native color emoji becomes an acceptance requirement, enable it in a separate feature job with the pinned Noto Color Emoji fixture and a color-glyph test |
egui_extras |
default-features = false; no svg feature unless used |
Current crate only uses tables |
4.3 Lockfile and duplicate checks
Every command below is a payload for the managed workflow in section 7.1; do not run it directly:
cargo tree --locked -p figcraft -i egui
cargo tree --locked -p figcraft -i eframe
cargo tree --locked -p figcraft -i winit
cargo tree --locked -p figcraft -d
cargo metadata --locked --format-version 1
Acceptance:
- Exactly one source/version for
egui,epaint,emath,ecolor,eframe,egui-wgpu,egui-winit,egui_extrasandwinit(andegui_glowif a glow build is enabled). Cargo.lockrecords the final storytold egui/winit/craft-libs revisions; no aiongg candidate SHA and no moving branch remains.- No registry 0.36 egui-family package remains; no duplicate
craft-window. wgpuresolves to one version. Current FigCraft lock is30.0.1while the candidate lock says30.0.0; both satisfy30.0, but the final lock must be inspected for wasm-bindgen and backend compatibility. Do not “fix” this by allowing two wgpu versions.cargo check --lockednative and wasm;cargo test --locked --workspace; clippy with-D warnings.- All patched git sources are public and reachable without credentials. No machine-local path, branch-only or private-repository patch is publishable.
5. Fork patch inventory and integration decisions
The final fork's PATCHES.md is the provenance record. Every row below is kept in the reviewed
series unless its decision says otherwise; none is deleted merely because FigCraft does not call the
API directly. The candidate revision is a comparison input, not the production source.
| Patch | Crates | Provenance / upstream | FigCraft impact | Decision / removal condition |
|---|---|---|---|---|
E1 8f3abf06 |
eframe | suite ViewportHooks; not upstream | Compiles NativeOptions; no direct use |
Keep for suite; remove when upstream has equivalent |
E2+E3 9dd799ab |
egui-wgpu | surface fit + error scope; #8361 related | Large/multi-monitor windows, GPU setup errors | Keep; test large surface/error path on Windows/Linux real GPU |
E4 66bacf23 |
egui-winit | Wayland clipboard via winit (#8657 draft) | Text copy/paste on Hyprland | Keep the regular clipboard path; PRIMARY is not accepted through the candidate's arboard fallback |
| W15P + E4 P2b | winit + egui-winit | winit-owned PRIMARY on the existing connection/event queue, per-seat PrimarySelectionManagerState; no second regular wl_data_device |
Pointer- and keyboard-selected PRIMARY on Wayland | Required prerequisite and integration stage; native acceptance on wlroots/KWin/Mutter. arboard only as a measured fallback if W15P fails a proven requirement |
E5 0e27d87e, 777977b7, f7e89e23, 5b2b8e19 |
epaint/egui | bidi #8577 + fixes | RTL UI/document text, marks, caret | Keep; RTL/caret tests; remove FigCraft double-processing where egui now shapes logical text |
E6 0abfabcf |
egui | IME focus | Avoids composition interruption | Keep; IME test |
E7 0670c58a |
egui-winit | Windows letter keys | Non-Latin layouts | Keep; Windows layout test |
E8 1b03c10f |
egui | TextEdit glyph overshoot | Thai/Vietnamese/Arabic marks | Keep; screenshot/geometry |
E9 b2f5cafc, e1341a3d, 5105330d, 75a5b3d3 |
egui | RTL chrome | Menus/scrollbars/headers/title bars | Keep; RTL screenshots |
E10 dc90c13e, c8e573f7, 34b2c0d4 |
eframe web | key/text-agent fixes | Browser shortcuts and IME | Keep; browser test |
E11 92e61afd |
epaint | single-thread vello flush | Avoids panic under multithreading |
Keep; compile/test; FigCraft does not enable vello multithreading |
E12 e955bb18, 485c0b89, 0185887c, 7015ac23, 4067b2c4, 620e070e |
epaint/egui | LayoutJob direction, RTL alignment, L1, linear reorder | RTL layout and text painting | Keep; RTL tests |
E13 5e4da7f4 |
epaint/egui | grapheme caret/hit/delete | Emoji/accented text editing | Keep; editor tests |
E14 cbd77549, b3722ac0 |
epaint/egui | fallback baseline align | CJK/Indic fallback rows | Keep; CJK/Indic screenshot |
E15 f1cae609 |
epaint/egui | LineBreaker trait/language | Thai/Japanese breaking if enabled | Keep; default SimpleLineBreaker preserves 0.36 |
E16 b3ef9771, bed6dfe3 |
epaint/egui | visual cursor/affinity | RTL cursor/Home/End | Keep; RTL caret tests |
E17 520435df |
egui | UI language, AccessKit language | Screen-reader language; bidi runs | Keep; use Context::set_language; AccessKit test |
E18 89de8f3c |
eframe | Frame::set_ui_language |
Optional language sync | Keep; can use Context directly |
E19 d64047bf, 642b74f7, baeb4515 |
egui-winit/egui | Latin layout chars | Shortcuts on non-Latin layouts | Keep; Windows/Linux layout test |
E20 23955535 |
egui | widget text language | RTL/localized widget text | Keep; RTL widget screenshot |
E21 a70470b0 |
egui-winit/egui | IME DeleteSurroundingBytes |
macOS/Wayland IME | Keep; IME test |
| E21b/P7b | egui, egui-winit, winit W27 | outgoing IME surrounding text; confirmed missing at candidate a4f9a17f |
Custom FigCraft text editor and Wayland predictive IME | Required new prerequisite; see 6.5. Not existing E21 capability |
Removal rule: a row may be removed only after an upstream release in use contains the same fix and the app tests pass against that release. Do not remove an unrelated fork fix because FigCraft does not exercise it directly; the fork is shared across the suite.
Optional new provenance patches, only if measurements require them:
- E22 text tessellation (#8731): upstream reports ~61% more instructions per plain-text
tessellatein 0.37. If FigCraft's three-way text-heavy benchmark exceeds the threshold, carry the issue's row-level fast path with author/provenance recorded, plus a pixel/vertex/index equality test for mixed and color rows. Do not change color-glyph semantics. - E23 DX12 shader compile (#8736): upstream reports ~40 ms extra pipeline creation on FXC. If
Windows startup exceeds the threshold, carry a float/
selectimplementation (not clamp-only) that preserves clamp/repeat/mirrored-repeat and color-glyph filtering, with startup and image tests. A pipeline-overridable constant is acceptable only if it compiles on all backends. - E24 TextEdit click-away (#8763 / PR #8764): #8763 is the issue; #8764 is an unmerged proposed fix, not released behavior. If FigCraft's fields or text-selection actions reproduce the 0.37 selection collapse, carry the reviewed fix or add the app-level regression test. A necessary app workaround must preserve the range and undo semantics and have its own removal condition.
- E25 hidden deferred windows (#8748): #8748 is closed and unmerged. Prefer the 0.37
RequestPaintWhileHiddenpath in FigCraft's window reconciliation before adding a fork patch. If a child cannot be created, updated or closed while the root is hidden, record the failing test and then adopt a reviewed fork fix with provenance; do not treat #8748 as available behavior.
6. Concrete API and behavior migration
6.1 Mechanical compile/deprecation changes
| File / symbol | Current | 0.37 change | Reason / test |
|---|---|---|---|
throughout crates/ui-egui, apps |
egui::Id::new(x) (443 uses) |
egui::Id::unique(x) |
0.37 deprecates Id::new; same fixed-seed hash. cargo clippy -D warnings fails otherwise |
63 uses, e.g. tabs.rs, layers.rs, right.rs, widgets.rs |
ui.id() |
ui.scope_id() |
Renamed; same scope ID |
any UiBuilder::new().id(...) (none found now) |
.id(id) |
.scope_id(id) |
Deprecated; verify by grep after codemod |
crates/ui-egui/src/dialogs.rs tab_strip |
WidgetType::SelectableLabel |
egui::Role::Button |
WidgetType survives as deprecated pub type WidgetType = Role; only SelectableLabel is gone |
crates/ui-egui/src/credits.rs sort_header |
WidgetType::Button/Label |
egui::Role::Button/Role::Label |
Same mapping; aliases may compile but should not produce warnings |
crates/ui-egui/src/control.rs ui.input scroll branch; 11 test files |
Event::MouseWheel { unit, delta, phase, modifiers } |
add source: egui::MouseWheelSource |
Use Wheel for synthetic wheel input, Unknown where unspecified |
crates/ui-egui/src/frame_labels.rs turn |
exhaustive Shape match |
add Shape::Band(_) |
0.37 BandShape; label drawing never adds one, leave it unchanged with a comment or rotate via its angle if a Band ever appears |
crates/ui-egui/src/typography.rs font_picker |
shown.state.offset.y |
shown.state.clamped_offset().y |
State::offset private/deprecated; clamped_offset is the drawn position |
crates/ui-egui/src/control.rs PendingShot/drain |
polls Event::Screenshot |
capture-slot callback | API removed; see 6.3 |
tests in idle_tests.rs |
synthetic Event::Screenshot |
complete a test capture slot | API removed |
Codemod safety:
Id::uniqueandId::newuse the same hash; no persisted state changes.Ui::scope_idandUi::idare the same value.- Do not replace tuple salts with formatted strings.
- After the codemod, grep for
Id::new,Ui::id(,UiBuilder::id(,SelectableLabel,Event::Screenshot,UserData,State.offsetand ensure only intentional comments remain. - Run
cargo fmtandcargo clippy --workspace --all-targets --locked -- -D warnings; a cleancargo checkis not enough because deprecations are warnings. - F2 adds
egui_kittest0.37 as a dev-dependency for the accessibility/focus smoke tests, using strict missing-name behavior and the default missing-glyph policy from its first commit. Only a deliberate arbitrary-script fixture may opt intoMissingGlyphPolicy::Tofu; it must not mask a production glyph gap and is not a substitute for the pinned fixtures in 6.6. If kittest adoption is deferred, equivalent custom assertions must still fail on unnamed controls and missing glyphs; a green suite that silently draws tofu is not accepted.
6.2 Panel IDs, saved state and accessibility focus order
0.36 Panel::left("left_panel") used Id::unique("left_panel"). 0.37 Panel::left takes
impl AsIdSalt and computes parent_ui.scope_id().with_salt(IdSalt::new("left_panel"))
(crates/egui/src/containers/panel.rs at the final source). FigCraft's
crates/ui-egui/src/a11y.rs::focusables currently excludes the old panel resize IDs, but its
panels are .resizable(false) (crates/ui-egui/src/lib.rs) so egui creates no
left_panel.__resize/right_panel.__resize widgets at all. The real custom edges are:
// crates/ui-egui/src/lib.rs::panel_edge
let id = egui::Id::new(("panel_edge", name)); // "left" / "right"
let resp = ui.interact(rect, id, egui::Sense::click_and_drag());
Sense::click_and_drag() includes FOCUSABLE, so these edges can enter the F6/Tab order today.
The fix is:
- Make the custom edge non-focusable with
egui::Sense::CLICK | egui::Sense::DRAG(keeping hover, click and drag behavior), and also exclude the exact IDsId::unique(("panel_edge", "left"))/Id::unique(("panel_edge", "right"))fromfocusablesas a defense-in-depth assertion. - Do not derive or test
Id::new("left_panel").with("__resize")as the live edge; that is dead code while FigCraft's panels are non-resizable. If a future PR enablesPanel::resizable(true), derive the real resize ID from the recorded rootUi::scope_id():root_scope.with("left_panel").with("__resize"), and test it. - Record the root scope in
a11y::beginfrom the passed rootUi(change the call from&ctxto the root&Ui/ui.scope_id()) so a future enablement is testable without guessing a global ID.
Panel widths come from prefs.panel_widths and fit_panels. Both panels are
resizable(false).exact_size(...); eframe persistence is off. There is no durable egui
PanelState to migrate, and copying it cannot affect fixed geometry. Preserve actual preference
save/reopen and test root-scoped IDs plus geometry/edge hit tests; remove the invented migration.
AccessKit assertions use roles/names rather than raw IDs.
6.3 Screenshot callbacks, correlation and automation confinement
Current crates/ui-egui/src/control.rs:
PendingShotstoresid,path,reply,since,viewport.drainscansctx.inputforEvent::Screenshotand completes every pending shot for that viewport in arrival order.shot_replyencodes PNG and writes viafigcraft_engine::file_access::write.idle_testsmanufacturesEvent::Screenshot;windows_testsonly tests the hidden-window error.apps/figcraft/src/control_server.rsmarks the request cancelled after the transport timeout, butPendingShotdoes not retain that flag anddrainonly checksnow - since < SHOT_TIMEOUT.
0.37 replaces the event with a one-shot ScreenshotCallback; UserData and Event::Screenshot
are gone. The callback receives Arc<ColorImage> and does not request a repaint. Design:
- Implement the suite capture contract locally in
crates/ui-egui/src/control.rs, using the reviewed CL-2/CL-2F semantics and conformance cases. FigCraft, DeckCraft and ArtCraft Native have local adapters; eleven existing craft-ui consumers share its helper. The local slot owns one result per admitted request and exposes a weak handle to the callback; the callback holds only aWeakreference, never a strongContext/app/queue, so a late capture cannot retain the app through shutdown. - On request, FigCraft records its own
ShotRequest { id, path: Option<String>, reply, deadline, serial, viewport, document_generation, capture_spec }and admits it only if:- the capture is for a live viewport and document generation;
- the request count and aggregate pending bytes are within the app budget;
- the
CaptureSpecrecords capture-time logical viewport rectangle, pixels-per-point, zoom and expected surface size. Revalidate before issuance after resize/DPI changes.
ui.screenshotsendsViewportCommand::Screenshot(ScreenshotCallback::new(...))to the target viewport (send_viewport_cmd_tofor a child;request_screenshotfor the current one). The callback performs onlyslot.try_complete(image); no PNG encoding, file I/O, command, lock held across wakeup, or mutable app access.- The completion slot stores the image and a
CancelTokencopied fromControlRequest.cancelled. It must release its lock before waking the owning viewport. Wake withContext::request_repaint_of(capture_spec.viewport), never a barerequest_repaint()from a detached callback; a storedContextclone is held only by the live slot and is dropped with the request. A live request requires an owning-viewport waker; fail admission if none exists, rather than waiting indefinitely for an unrelated frame. - On the UI thread,
control::drain_uitakes completed slots, rejects cancelled/timed-out/stale generations, validates image dimensions against the capture spec (or returns a documented scale mismatch error), matches by serial and callsshot_replyinside the existingwindows::guard/file_access::confinement. A late callback after timeout or close drops its image without writing or replying; a reused viewport ID cannot bind an old capture to a new document. - Admission bounds aggregate image bytes, not only the number of requests: reserve
surface_pixels × 4(plus a declared margin) before issuing, cap request count independently, and release both on completion/cancel/timeout. After drain/shutdown the retained pending image bytes must be zero. A post-callback size check alone does not bound peak memory. - Time out unfinished serials as today (transport timeout and
SHOT_TIMEOUT), cancel the slot and send the same error once. Handle channel-disconnect as cancellation.
Required tests:
- Two requests in one frame complete in reverse order; each reply carries its own request ID/path, correct dimensions and capture-time DPI/geometry metadata.
- Cancel before callback, callback after timeout, window close, document replacement, viewport reuse, and a child-viewport capture: no stale file write/reply, wakeup reaches the captured viewport, and retained bytes return to zero.
- Aggregate admission rejects an oversized/concurrent batch before issuing, then accepts a smaller request and releases memory after drain.
- A path outside the automation write root is rejected when completed from the slot; a callback outside the roots cannot write.
- Admitted hidden/minimized screenshot or injected-input requests remain queued; logic
sends
RequestPaintWhileHiddenthrough settling until UI consumes them. The prior screenshot error is returned only on deadline expiry if no paint arrives, exactly once. This intentionally replaces immediate hidden rejection for these two control methods. - A translucent pixel round-trips straight RGBA; premultiplied alpha is not darkened.
- The callback never holds a strong
Contextcycle; dropping the app/pending list releases slots and a late callback is a no-op.
Do not use Event::Screenshot polling anywhere. Do not put PNG encoding or path resolution in the
callback.
Preserve both shot_reply branches: Some(path) writes through the automation-root guard;
None returns {format:"png", width, height, data} with base64 PNG. Test path-less capture
under restricted roots and compare decoded pixels, without inventing a filesystem requirement.
Use the suite's 4-request, 128 MiB/image, 256 MiB in-flight/completed image budget, checked
arithmetic and reject-before-issue behavior. Encode one result at a time with a bounded PNG
writer (128 MiB output cap); reserve checked 4 * ceil(png_bytes / 3) base64 bytes before
encoding, and cap queued encoded replies at 256 MiB. Return a structured size/busy error instead
of an unbounded encoding allocation. Account scratch/readback RSS separately. Test boundary,
one-over, overflow, late/cancel and zero retained/queued bytes. Geometry follows the suite's
issuance epoch validation: pixels-only callback, target UI logical content rect, reject any
intervening resize/DPI/zoom/surface change and derive scale from returned dimensions.
6.4 Frame driver: background work, UI setup, first pass and hidden logic
Target structure
Keep the existing FigApp::ui(&mut Ui) root entry and split it into a phase-safe background tick and
a visible UI setup. Names are proposals, not existing APIs:
pub(crate) struct FrameClock { /* monotonic origin + injected test time */ }
impl FigApp {
/// Consumes queued work/deadlines; safe from App::logic (run_ui or run_logic) and direct tests.
pub(crate) fn background_tick(&mut self, ctx: &egui::Context, clock: &mut FrameClock);
/// Fonts/theme/zoom/render worker and per-frame stats; first pass of a visible frame only.
fn ui_setup(&mut self, ui: &mut egui::Ui);
/// Direct-harness entry: runs the background tick if App::logic did not, then setup + draw.
pub fn ui(&mut self, ui: &mut egui::Ui) {
if !std::mem::take(&mut self.logic_ticked) {
self.background_tick(ui.ctx(), &mut self.clock);
}
let stamp = (ui.ctx().viewport_id(), ui.ctx().cumulative_frame_nr());
if ui.ctx().current_pass_index() == 0 && self.ui_frame_stamp != Some(stamp) {
self.ui_frame_stamp = Some(stamp);
self.ui_setup(ui);
}
self.frame(ui);
}
}
background_tick owns only phase-safe app-global work:
- drain state-only control requests; admitted screenshot/injected-input
NEEDS_FRAMErequests stay queued for the next forced UI pass, never errored just for logic-only execution.window.newretains its explicit hidden error as a separate app policy; - live
session.live_poll()andbackground_done(consumed queue/revision identity); - recovery tick and autosave checkpoint/scheduling/save-thread joins, driven by monotonic deadlines;
- plugin/live preference persistence triggered by consumed dirty flags;
- title/close/recovery requests that must happen while hidden;
- prototype/after-delay/GIF state advance using monotonic deadlines (once per due deadline, not
once per callback), with drawing left to
present::show.
ui_setup owns only UI-phase work that needs a real current Ui:
ctx.options_mut(|o| o.zoom_with_keyboard = false)once at startup;- fonts/theme/language and
cjk_needed(event scan) on the first visible pass; ctx.content_rect()/ctx.set_zoom_factorandui.ui_scaleusing the actual current geometry;render_worker::Worker::newandFrameStats::recordonce per visible root frame, not per pass;- the first-font-frame
skip_drawflag: draw nothing, request repaint, and let the next frame draw.
FrameStats::record and ui.inspect {framesDrawn, repaintCauses} count frames, not passes. A
first-pass guard is used only for ui_setup; it is not a command or queue guard.
Callback wiring
apps/figcraft/src/main.rs::App::logiccallswith_app(|fig| windows::guard(fig, ctx, |fig| fig.background_tick(ctx, &mut clock)))on every callback. Wrapping inwindows::guardpreservesfile_access::confinedautomation roots and panic recovery for autosave/recovery/preference writes. Setfig.logic_ticked = truewhen the tick ran.App::uicallsfig.ui(ui)through the existingwindows::guardwrapper.FigApp::uiruns the background tick only whenlogic_tickedis false (direct harnesses), thenui_setupon pass 0, thenframe.windows::drawcalls a newchild_logic(ctx, id)for per-viewport state (focus, close request) and not the app-globalbackground_tick. The root frame is the single owner of global autosave/live/recovery/background work; a visible child in the same native frame must not repeat it. Childframekeeps its existingwindows::guardboundary.FigApp::framecallscontrol::drain_ui(UI-capable requests queued by the background tick and any screenshot completions) instead of the old all-methodscontrol::drain. A state-only request arriving between passes is consumed by the nextApp::logiccall; fresh work is never starved by a nonzero pass index.Context::run_logicdoes not advancecumulative_frame_nr; do not use it as a per-frame identity. Repeated hidden ticks are expected and must be safe because their queues/deadlines are consumption- or deadline-driven.
Hidden/logic-only rules
Context::run_logicleaves input events, time and layout stale.background_tickmust not callcjk_needed(event scan),shortcuts::handle,a11y::begin,swallowed_paste, or any code that consumes pointer/key events. It may drain state-only control requests and use commands that do not read UI input.- Do not infer phase from
ViewportInfo::visible(), minimized/occluded state, or a remembered pass index. A forced hiddenrun_uihas current input and real geometry; arun_logiccallback does not. - Use
FrameClockbased onweb_time::Instantfor autosave, recovery and playback deadlines in both paths. In UI phase,ctx.input(|i| i.time)may be recorded for diagnostics, but deadlines never come from stale input time. Tests inject a fake clock. - Prototype timers, after-delay actions, animated transitions and GIF frame clocks advance from
background_tick, not onlypresent::showor canvas drawing. Split the existing timer/action code incrates/ui-egui/src/present.rs::show(and the canvas GIF clock) into a non-drawing step called by the tick;showonly draws the state the tick produced. Hidden logic must not advance the same deadline twice; the visible pass must not re-run delayed actions. - Timers must schedule a wake-up even while hidden (
request_repaint_after/eframe's logic-only path), otherwise autosave, recovery and live polling stop when no frame paints. Test that a hiddenrun_logicat the deadline runs the tick once, not zero and not twice. - If a background completion would open a dialog or touch UI-only state while hidden, queue a
visible-only action instead of mutating a pane not swapped in. Commands that need a drawn frame
(
ui.screenshot,ui.input) remain queued with deadline and request hidden paint. Onlywindow.newretains its existing explicit error in logic-only mode. Keep one injected gesture step in flight across repeated logic ticks until UI applies it once; do not combine press/move/release before that pass or replay events after discard. - Hidden child windows: root logic may need to reconcile deferred viewports. If a child must be
created/updated/closed while the root is hidden, send
ViewportCommand::RequestPaintWhileHiddenfrom root logic and let the root UI run without presenting. Add a hidden-root/visible-child test. #8748 is closed/unmerged; do not assume the child behavior exists without this probe or a reviewed fork fix.
Multi-pass and side effects
Context::run_uimay call the closure more than once ifrequest_discardis used.App::logicruns once per pass too. Therefore everybackground_tickoperation must be idempotent by consumed queue, revision/generation or monotonic deadline. A global first-pass guard must not suppress a fresh control request or a background completion that arrives between passes.- User actions are dispatched through an explicit one-shot action queue/token, not by gating all
commands on a pass index. Each interactive widget queues an
ActionTokencarrying the viewport, frame number, source widget ID and command;FigApp::framedrains the queue once per token and callsrun/run_user/set_props. Reads and drawing remain per-pass.FigApp::runandset_propsdo not receive aContext, so they cannot and must not readcurrent_pass_index(). - Test a sizing pass (
ctx.request_discard()once) while clicking a command button: one action, one undo entry and one command result. Deliberately replay the same token in the second pass: the dedupe must prevent a second undo. Also test that a command/background completion produced during pass 1 is still applied (the guard is not a global starve). - Undo/redo remains engine-owned; no UI-side transaction may span passes.
6.5 MouseWheel, input, IME/E21b, RTL, clipboard and PRIMARY
- Add
source: MouseWheelSourceto every construction.control.rssynthetic scroll usesWheel; unit tests useUnknownunless testing source-sensitive scroll behavior. Event::PasteImageandEvent::MiddleClickPasteare new but optional for FigCraft; do not replace the existing arboard/WebServices paths without a regression test. The canvas paste path must still accept image bytes and the browser'spasteevent.- PRIMARY: E4's regular Wayland clipboard is not enough. The selected design is winit-owned
PRIMARY on the existing connection/event queue and per-seat lifecycle, using SCTK 0.19.2
PrimarySelectionManagerState/device/source/offer; do not bind a second regularwl_data_device. W15P lands and is reviewed before the E4 P2b integration stage. FigCraft must keep password exclusion, not reclaim an unchanged selection, and test pointer/keyboard triggering serials (including keyboard-only focus), per-seat origin/freshness, serial freshness, owner/seat changes, empty/absent offers, self-paste and canceled/slow transfers on wlroots/KWin/Mutter. arboard is only a measured fallback if W15P cannot meet a proven requirement; a documented unsupported limitation needs explicit acceptance, not a compile-only sign-off. - IME incoming: 0.37 changes composition visuals and deletion. Keep E6/E21; run
ime_testsand add a delete-surrounding-bytes test asserting one undo step. The engine text edit state and eguiTextEditstate must not double-apply commits. - E21b/P7b outgoing surrounding text: the candidate
egui-winitnever callsWindowExtWayland::set_ime_surrounding_text, andegui::IMEOutputhas no logical text. Add the bounded transient payload (at most 4000 UTF-8 bytes) toIMEOutput, preserving Clone/Debug/Eq, excluding it from serialized/debug persistence, with committed logical text, cursor/anchor byte indices and owning widget/viewport identity. Built-inTextEditproduces it after applying delete/commit/cursor updates, excluding preedit and remapping indices; a selection that cannot fit returns explicit unavailable/empty context, never a truncated “complete” context. Password fields emit no text; focus/reset/normal-to-password/no-IME transitions clear backend state.egui-winitsends it on text/selection/owner changes through W27 on Wayland only; per-window caching must not bleed across widgets/windows. PlatformOutput append keeps the final IME output and payload together across passes. - FigCraft's custom editor adapter:
crates/ui-egui/src/shortcuts.rs::ime_outputcurrently emits only purpose/rect/cursor_rect/interruption (around line 552). It must supply the real model adapter: committed document text for the edited node, preedit removed, caret/anchor converted from the engine's character/scalar coordinates to UTF-8 byte indices, bounded to 4000 bytes, with explicit unavailable when the selection is too large. Use logical text, never the display-bidi string frombidi.rs. This adapter is part of the atomic F2 compatibility stage, not aNoneplaceholder. - IME tests: outgoing text changes immediately after delete+commit and deletion-only batches; scalar/grapheme vs byte indices; supplementary emoji/combining/RTL logical order; preedit exclusion and caret movement; long selection/window limits; same-window field switch; two windows; focus loss/reset/late serial; password/no-context clears; multi-pass final-output selection. Native Wayland Vietnamese/predictive IME verifies the protocol trace and editor content/undo; macOS NSRange remains W26's separate path.
- RTL/bidi: E5/E12/E13/E16 change caret/hit/row order. The fork now shapes logical text. FigCraft's
bidi.rscurrently pre-reorders names withfigcraft_text::displayand draws cluster sections so egui's old shaper does not reverse them again. After E5/E12, remove double processing where egui manages the widget; keep only deliberately custom painting that needs visual-order metadata. Never pass a display-reordered string to egui shaping,TextEdit, clipboard, AccessKit names, save, or E21b surrounding text; those boundaries use the logical document string.rtl_names_testsand a mixed-logical/clipboard/IME test must pass. Any cache keyed byFontIdmust account forFontFamily::NameandLayoutJob::direction. Do not deletebidi.rsuntil E5/E12 tests prove the fork covers FigCraft's custom name painting. - Accessibility language: call
ctx.set_language(i18n::current().map(|l| l.code.to_string()))inui_setup(fork E17). Assert the AccessKit root node's language changes with the UI language. This is optional only if the final pinned fork lacks E17; do not use a nonexistent upstream API. Ui::label_inputs_by/Response::accessible_nameare available in 0.37; use them only where FigCraft currently has unnamed native widgets. Customa11ynode builders remain.- Shared
DockAreadirection: FigCraft keeps its own panels and does not adoptcraft-ui'sDockArea. If a future PR does, it must pass an explicit opt-inDockDirection, preserve the serialized physical splits/rectangles and logical tab order, and use the same rectangles for painting/hit/drag/keyboard with bottom-trailing resize grips. FigCraft must not reverse saved panel order or feed display-bidi text to the shared dock.
6.6 Fonts and glyph coverage
crates/ui-egui/src/theme.rs::install_fontsstarts fromFontDefinitions::default(). With egui default features empty, that isFontDefinitions::empty()unless the app/UI crate enablesdefault_fonts. Declare production fallback policy explicitly. The proposed 0.37 kittest dev dependency does not transitively enable fonts: only kittest's own dev-dependencies do. Explicitly enabledefault_fontsandmonochrome_emoji_fontson FigCraft's egui test dependency for these fixtures. Audit UI-library/test/native/web graphs independently.- Keep the optional
CRAFT_FONTS_DIRbuild input. Record whether each baseline/candidate run had craft-fonts and its exact ref; a no-craft-fonts run is valid for CI but not for a font-metrics comparison. - Keep
script_fonts,cjk_needed,bidi::script_fallbacks,key_symbol_fallbacksand the “English users load none” behavior. Tests: an English first frame installs no extra script face; a CJK name adds the expected face; shortcut symbols (⌘⌥⇧↵) have glyphs; a font-picker preview renders with the selected family. - If
system_fontsis evaluated later, test color emoji and arbitrary scripts, and record startup cost. Do not replace FigCraft's deterministic fallback with a blanket font provider in this migration. - Preserve the first-font-frame behavior: when
ui_setupinstalls fonts for the first time, set askip_drawflag;FigApp::uirequests a repaint and returns without drawing. Do not draw one frame with missing fonts. - 0.37 changed text layout/font versions. Pixel/width tests may shift by subpixel amounts. Inspect each difference; do not auto-update expected widths. The document renderer still uses FigCraft's own harfrust/skrifa stack, so UI and canvas text may legitimately differ.
- Pinned licensed fixtures (egui plan section 10.6): fetch
craft-fontsat2481a8a324b986a45954e90045ed1f8f2efaaf3cas a test resource and keep the repository's central-font policy (no font binaries committed here). Required fixture files, hashes and licences:- Arabic
fonts/noto-sans-arabic/NotoSansArabic.ttf, SHA25663111b5b2e074dd48cc67692e0a2726d86ee94c1c37fe8598257b7b4e87e869e, OFL-1.1. - Thai
fonts/noto-sans-thai/NotoSansThai.ttf, SHA2565a1c559bb539583c8a1fd99d1c5b9491e5e14478c9cd2bd0970d5c3096cc9ef8, OFL-1.1. - CJK SC/Latin
fonts/noto-sans-cjk-sc/NotoSansCJKsc-Regular.otf, SHA2562c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b, OFL-1.1. - Japanese
fonts/biz-ud-pgothic/BIZUDPGothic-Regular.ttf, SHA256258d7156c165f2ff774b6efee637c22c3b950de0d8a10e501137061bc8085d01, OFL-1.1. - Hebrew Noto Sans Hebrew variable font, SHA256
7ef36a2c3593758cdb622e1bdef4f84523e92fbc3ccc667438dd80ff54c2de88, OFL-1.1, fixed to weight 400/width 100. - Color glyphs: Noto Color Emoji v2.047, SHA256
39ee3c587e10e89669b9ff32703261d10d5f9c4dd5ad147b6b5a1c5200591817, OFL-1.1. This fixture is used only in theepaint/color_fontsfeature job; the normal build makes no color-emoji claim. Preserve the adjacent licence files in the evidence/fixture bundle. If a fixture cannot be fetched, record the font-dependent test as unresolved; do not silently rely on host fonts.
- Arabic
- If a controlled Latin/emoji comparison is needed, use the exact
epaint_default_fontsfiles and hashes from the pinned baseline/candidate, not whatever the host provides. The normal CI no-craft-fonts job still runs and must not become a font-metrics gate.
6.7 Panels, windows, viewports and GPU
- Panels: keep the root order in
crates/ui-egui/src/lib.rs::frame(left rail, left panel, right panel, central, then custompanel_edgeoverlays). 0.37 panel constructors acceptAsIdSaltand include separator width; testfit_panels,panel_width_tests, canvas minimum and hit testing. Do not replace the custom edge drag withPanel::resizablewithout a UI3 geometry comparison. - Clip/nesting: keep the root
Uiandscope_builder("toolbar")nesting; custom overlays keep their ownwith_clip_rectcalls. 0.37 keepsclip_rect_marginas a deprecated unused field; remove the use rather than expecting a compile error, and test that panel content does not paint over the canvas, popups/menus and drag ghosts stay in their intended layers, and hit rects match what is visible. - Docking: FigCraft does not use
craft-uidocking; do not add it as part of this migration. The capture completion slot stays local. If a future PR adoptsDockArea, follow the explicit direction policy in 6.5. - Child windows: keep
windows.rsdeferred viewports and pane swapping. 0.37 addsRequestPaintWhileHidden; use only for reconciliation when a child must appear/update/disappear while the root is hidden. Keepembed_viewportsand the screen-reader tab fallback until per-viewport AccessKit is proven. - Title bars:
craft-windowdecidesTitleBar::Native/Inset/Custom;window_chromemaps toViewportBuilder. 0.37 keeps these builder methods. macOS traffic-light positioning is not changed without a Mac;Frame::set_traffic_lights_positionis available if later measured. - GPU:
apps/figcraft/src/graphics.rs::backends/configurestill mutatesWgpuConfiguration/WgpuSetup::CreateNew(create).instance_descriptor.backends; verify compile and preserve DX12-then-GL on Windows. E2/E3 stay. MeasureRenderer::new/first frame on Windows DX12; do not report compile time as UI performance. - Texture deltas: eframe owns upload/free of
textures_delta; app code must not drop them. The 0.37 glyph atlas and color-glyph path change atlas contents and filtering. KeepTextureOptions::LINEARand clamp/repeat/mirror behavior where used; test a translucent image and, only in theepaint/color_fontsjob, a color-glyph fixture. Do not use clamp-only as a performance “fix” (#8736).
7. Test and regression plan
7.1 Repository gates through task.py
Read /home/codex/work/maintenance/TASK-JOBS.md before the first submission. Every future
fmt/clippy/test/benchmark is a new job on one FigCraft task; a fresh target is allocated once, and
finish is applied only after all checks, GUI workflows and evidence preservation are complete.
Example template (do not run during planning):
TASK=figcraft-egui037-migration
SRC=/home/codex/work/CraftApps/egui037-migration-20261022/figcraft
EV=/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/figcraft-validation
python3 /home/codex/work/maintenance/task.py run \
--job-id "$TASK-ci-01" --kind build --task "$TASK" \
--source "$SRC" --evidence "$EV" \
-- cargo xtask ci
python3 /home/codex/work/maintenance/task.py status "$TASK-ci-01" --tail-bytes 8192
# One new job ID per intentional command, same task/source/evidence:
python3 /home/codex/work/maintenance/task.py run \
--job-id "$TASK-ui-tests-01" --kind build --task "$TASK" \
--source "$SRC" --evidence "$EV" \
-- cargo test --locked -p figcraft-ui-egui
python3 /home/codex/work/maintenance/task.py status "$TASK-ui-tests-01" --tail-bytes 8192
After review and evidence preservation, inspect task.py finish JOB_ID, then apply with
--apply --owner-confirmed --checks-complete --evidence-preserved and poll status. The payload after
-- may be cargo fmt --all -- --check, clippy, workspace tests, xtask layers/assets/wasm, or a
targeted suite, but never a direct host command or a nested build driver. cargo xtask ci is
equivalent for the repository; run individual payloads when evidence must identify a failing gate.
cargo test --workspace builds without craft-fonts in CI; a with-craft-fonts configuration is a
separate job and records the exact font ref/hash.
Targeted suites, each its own job: figcraft-ui-egui, figcraft-engine, figcraft --bin figcraft,
figcraft-web, figcraft-cli. Then native Windows x64 and wasm workflows. macOS/FreeBSD remain
unresolved unless real evidence is available; a compile-only result is not a platform pass.
7.2 New/updated regression tests
| Test area | Assertion | Mutation / broken probe that must make it fail |
|---|---|---|
| Native/web lifecycle | Visible single and multipass: App::logic runs per pass, background work is idempotent; ui_setup runs once on pass 0; completed run_ui leaves pass index 0 |
Remove the idempotence/consume guard: duplicate autosave/live work must be detected. Add a test that expects a stale nonzero pass index: it must fail |
| Hidden logic | Repeated run_logic after a completed multipass calls background_tick each time (pass index 0), advances deadlines once per due instant, never consumes UI events, never runs ui_setup |
Suppress background work on pass index alone: hidden/fresh-queue test fails. Call cjk_needed/shortcuts in logic-only: stale-event test fails |
| Forced hidden UI paint | RequestPaintWhileHidden/full hidden pass runs App::logic + App::ui; ui_setup/input handling work because it is a real run_ui; visibility flag is not used to choose phase |
Infer LogicOnly from visible()==false: hidden-paint setup test fails |
| Restore/buffered input | Restore with buffered events dispatches one gesture once; a background completion between passes is not starved | Gate all commands on pass 0: completion/action test fails |
| Multi-pass commands | A request_discard sizing pass executes one queued action, creates one undo entry, returns one result; replaying the same action token does nothing |
Remove token dedupe: duplicate history entry must fail |
| Two windows/two viewports | Root plus visible child in one native frame: one app-global live poll/autosave/recovery/background completion, not one per child | Remove child/global split: two-window duplicate test fails |
| Panel IDs/a11y | Actual Id::unique(("panel_edge","left"/"right")) is absent from focusables; F6/Tab never focuses it; the next control gets focus; panel geometry unchanged |
Revert to Sense::click_and_drag() or the old left_panel.__resize filter: focus test fails on the real ID |
| Fixed panel geometry | Left/right fixed sizes and preferences remain identical with fresh or legacy egui memory; no persisted PanelState migration | Make a fixed panel resizable or alter its width: geometry assertion fails |
| Screenshot correlation | Two requests, reverse completion, each reply has its own ID/path/image/spec; callback outside automation roots rejected; hidden screenshot queues then paints or expires once | Match by viewport only or encode on render thread: cross-reply/root test fails |
| Screenshot cancellation/lifetime | Cancel-before-completion, timeout then late callback, window close, viewport reuse, document replacement: no file/reply; child wakeup reaches captured viewport; aggregate bytes return to zero | Strong callback context or unbounded queue: leak/retained-bytes test fails |
| Screenshot alpha | Translucent pixel round-trips straight RGBA; premultiplied alpha not darkened | Use premultiplied bytes directly: pixel assertion fails |
| Accessibility | Roles/names for toolbar, panes, tabs, tree items, spin buttons, combo boxes; root language; every input has a name; strict kittest missing-name/glyph failure on a new unnamed/tofu fixture | Remove a node label/role or opt into tofu silently: named-control/glyph assertion fails |
| Fonts/glyphs | English loads no extra face; CJK name adds one; shortcut symbols and font-picker preview have glyphs; no tofu in pinned fixture; no-craft-fonts CI still runs | Disable default_fonts or rely on host fonts: glyph/panic test fails |
| Input/IME/E21b | Mouse wheel source; IME commit/delete one undo; outgoing surrounding text after delete+commit and deletion-only; preedit excluded; UTF-8 byte indices; long selection unavailable; focus/password clear; two windows | Set payload None, omit preedit remap, or pass display-bidi text: protocol/editor test fails |
| RTL/logical boundaries | Bidi caret/hit/row order; clipboard, AccessKit name, save and IME use logical text; no double reversal with E5/E12 | Feed display text to shaping/clipboard/IME: logical-boundary test fails |
| Commands/undo/save | Rectangle/text edit → undo restores exactly; save/reopen compares document.inspect; no changed command ids/parity |
Skip history snapshot/change save order: hash/inspect mismatch |
| Geometry/screenshots | 1440×900 and 800×500: panel widths, canvas min, toolbar/tab hit rects, scale factors 100/125/150/200%; PNG dimensions and stable non-empty regions | Move a panel edge or change separator width: exact geometry assertion fails |
| Viewports/hidden child | Main + deferred child; close asks on unsaved; title follows document; screenshot targets right viewport; hidden root + visible child opens/updates/closes via RequestPaintWhileHidden (#8748 probe) |
Remove request-paint: child state remains stale; treat closed #8748 as merged: test cannot pass |
| #8763/#8764 selection | Select text, click a toolbar/context action; intended range is used, pointer and keyboard agree; one undo | Remove selection preservation: selected range becomes stale/collapsed |
| Idle/performance hooks | ui.inspect reports no idle repaint and stable frame counts; autosave/recovery run while hidden; prototype playback does not duplicate actions after a sizing pass |
Tick from present::show only: hidden/duplicate-action test fails |
Pixel comparisons:
- Prefer geometry, shape counts and semantic AccessKit assertions for control logic.
- Use screenshot/pixel comparisons for synthetic scenes: empty document, frame with text, selected frame with handles, RTL name, CJK name, font picker, dark/light, 100% and 150%.
- Fixtures are generated in code (
figcraft_engine::synthetic,figcraft_engine::synthetic_ds) or original assets already in the repository; no product screenshots or third-party assets. - Tolerance: exact geometry; for antialiasing/font raster changes allow at most 0.1% of pixels differing by more than 2/255 per channel and no pixel differing by more than 4/255; text layout bounding boxes may differ by ≤0.5 point after review. Any larger diff needs a written before/after reason and reviewer approval. Never auto-update a golden.
- Screenshot evidence is stored outside the repository in the durable task evidence directory;
reference screenshots of other products stay under the gitignored
plan/tree and are never committed.
Deliberate negative controls: wrong capture/viewport association, missing accessible name, missing glyph, dropped callback, forced duplicate action, stale hidden input and an unbounded capture queue must each fail the targeted test; remove the deliberate defect after confirming.
7.3 Native, browser and platform workflows
- Linux: headless tests plus a real X11 and Wayland session for window, clipboard, PRIMARY, IME and
panel geometry. The planning host has no inherited
DISPLAY/WAYLAND_DISPLAYand no compositor; the parent can access an i915 render node but this worker sandbox may hide/dev/dri. That is availability evidence only, not a qualified GPU/compositor result. Provision a display/window manager and record versions; if unavailable, record the gate as unresolved. - Windows x64: build/test in a uniquely owned clone, then launch the actual app on the interactive desktop, drive the control channel, capture window screenshots at 100/150/200%, test DX12 startup, IME/non-Latin shortcuts, save/reopen and multi-window. The sealed baseline uses the Microsoft Basic Render Driver; do not present it as a physical-GPU result. Do not count session-0 launches.
- wasm:
trunk build --release; load the page with WebGPU and?webgl; test open/import, edit, undo, export/download, live join, browser clipboard copy/paste, responsive panel geometry and, only ifepaint/color_fontsis enabled in that build, color emoji; record browser/OS/GPU. The browser has no native file paths and no control server; state whether automation or manual evidence was used. - macOS/FreeBSD: no routine UI test job. A release-workflow build is not a UI pass. Mark macOS window/traffic-light/IME/PRIMARY and FreeBSD runtime rows explicitly unresolved unless a real machine is available.
8. Performance and measurement plan
8.1 Baseline and three-way controls
Before modifying production code, from clean baseline 80f9163 with the old manifest/lock, record:
- exact
Cargo.lockhash,rustc -Vv,cargo -V, target triple, features,CRAFT_FONTS_DIRpresence/ref,FIGCRAFT_EMBED_CJK; - OS version, CPU, RAM, GPU/adapter/backend/driver, DPI/scale, window size, renderer (software vs
real GPU), release profile,
WGPU_BACKEND; - raw samples, command lines and timings in the durable evidence directory.
Measure three sources on the same machine, checkout fixtures, fonts, features, DPI and viewport:
unchanged 0.36.2 baseline (49682f8b/FigCraft 80f9163 lock), stock 0.37.0
(841a328b) via a pinned minimal adapter/harness where the app cannot compile unchanged, and the
final selected storytold fork. Record all three source/lock identities. An unavailable stock
equivalence is an attribution limitation, not evidence that an upstream regression is unavoidable.
Candidate runs use paired A/B/A/B ordering; do not compare compile duration with UI frame time.
8.2 Operations and issue-specific workloads
figcraft-cli perf --layers 1000,10000,50000: build/open, save/load, full render, retained pan, one-layer edit, hit test, hover, node edit, undo/redo, boolean drag, normalize, inspect, select-all+move. Existing rows inapps/figcraft-cli/src/perf.rs::measure.perf_ui_frames/report_components: idle, hover, wheel pan, one layer selected, Prototype tab, all screens expanded, scroll layers, filter, keyboard navigation, bottom-layer reveal, collapse all, Assets panel first/idle/scroll, edit a component, instance with many properties, 72-variant set. Existing rows incrates/ui-egui/src/perf_tests.rs.- #8731 text tessellation: fixed synthetic document with 120–240 text labels; layout once, then
ctx.tessellatethe same shapes for 30 steady repetitions (10 warmups); report per-call time, vertex/index counts and raw samples. Run 0.36, stock 0.37 and final fork. A row-level fast path is only accepted with mixed/color-row equality. - #8736 DX12: measure renderer/pipeline construction separately from first frame on Windows
x64, 20 fresh processes, with
WGPU_BACKEND=dx12; compare 0.36, stock 0.37 and final fork. Keep a software/renderer control row and identify the actual adapter/driver. - #8512 font startup: measure system-font discovery and first missing-glyph lookup separately,
20 fresh processes, with and without
CRAFT_FONTS_DIR/system_fonts; record coverage and provider settings. Do not enable fontique by default as a performance shortcut. - Native startup: process spawn to first successful
ui.inspect/first painted frame; 20 fresh processes; separate shader/renderer creation logs. - Idle repaint/CPU: 60 s hidden and visible idle;
framesDrawn,repaintCauses, OS CPU. Must be zero frames and ~0% CPU when idle. - Memory: Linux
/usr/bin/time -vor/procpeak RSS; Windows private bytes; record peak for 10k/50k documents, startup and capture admission. - Screenshot/control latency:
ui.screenshotrequest to reply (including completion-slot drain, cancellation and pending-bytes counters),engine.execute, save/reopen, window open/focus/close. - Browser: wasm size, first interactive, open/edit/undo/export, frame time in Chromium; WebGPU and WebGL2 separately, with software fallback vs real GPU identified.
8.3 Statistics and thresholds
- At least 10 warmups and 30 steady timed iterations for operations; at least 20 fresh processes
for startup; at least 1,000 frames for any frame distribution and p99. Slow full renders also use
30 samples, not 5. Report raw samples, median, p95, min/max, MAD and sample count; report p99 only
where the sample count supports it. A coefficient of variation >5% triggers a rerun on an idle
machine and a recorded explanation. Modify
crates/ui-egui/src/perf_tests.rsandapps/figcraft-cli/src/perf.rsto emit raw samples and these aggregates; the current means-only output is not acceptance evidence. - Three-way acceptance on paired baseline/candidate (shared suite floors are no looser than 5%
median / 10% p95; FigCraft keeps its stricter engine/render/UI gates):
- engine/render/UI median ≤ +3% and p95 ≤ +5% relative; absolute floor 0.05 ms for sub-ms operations. If evidence shows the stricter gate is noisy, record an explicit, pre-approved exception; never silently relax after seeing a failure.
- text tessellation ≤ +5% median (if upstream #8731 remains unpatched, the expected +61% is a blocking regression, not an accepted number); report stock 0.37 and fork separately.
- Windows DX12 startup ≤ +5% median / +10% p95 over 20 fresh processes; if #8736 remains unpatched, measure and require an owner-approved waiver or a provenance-recorded patch.
- font discovery/first missing glyph: report absolute timings and coverage separately; a repeatable >5% median or >10% p95 regression is investigated, not accepted as default cost.
- idle visible/hidden: zero unscheduled app repaint requests after settling; scheduled work keeps its documented deadline, and there is no new busy-loop.
- peak memory ≤ +3% or +64 MiB, whichever is reached first, for the same scenario; capture pending bytes return to zero after drain.
- screenshot/control latency ≤ +10% p95; save/reopen ≤ +5%.
- A regression triggers profiling and a written cause/fix; it is never accepted by changing a golden or deleting a test. Software-renderer and real-GPU results are separate rows. A stock-0.37-only failure is still an attribution result, not a FigCraft fork pass.
The required X0 dependency is the complete P8 handoff. E24/E25's selection-action and hidden-child regressions map to fork P7c (conditional fixes after reproduction), not optional app fallbacks. E21b requires owner/context generation even for Unavailable, Available-empty separate from unsupported, W27 committed clear transitions, latest-pass None authoritative, old-focus-epoch invalidation and current-epoch mismatched serials applying incoming edits once while deferring outgoing until a match. F2's adapter and native protocol tests use that exact contract, including empty→non-empty and password→normal transitions. P2b's bounded async transfer/cancel/routing and P7's emitted-key release pairing are also X0 prerequisites.
Clipboard routing in F2 is by action/focused editor: built-in TextEdit text Copy/Cut/Paste uses egui-winit; custom document/rich HTML/image operations retain Desktop Services' arboard handle and its wayland-data-control support. A single input action has one consuming path; never write both owners for the same action or mirror unchanged selection on a timer. PRIMARY belongs exclusively to W15P; Services never claims PRIMARY. Independent data-control and regular data-device handles are not themselves a duplicate regular wl_data_device on one connection. Test alternating TextEdit/document copy, HTML/plain/image fallback with alpha, focus/window changes and one paste/undo per action on supported compositors. Preserve the existing Services read-timeout behavior as a separately measured baseline; P2b's nonblocking guarantee is for the egui integration path, not an unimplemented rewrite of all Services clipboard calls.
The existing craft-window pin 1f805378 is branch-derived; verify and retain exact fetchability and source/executable evidence for baseline/rollback. Unmerged provenance alone does not prove the commit is unreachable. F2 still switches to the final reviewed craft-libs revision.
9. Reviewable PR stages and dependency DAG
External prerequisites (not FigCraft PRs)
X0: reviewed + approved storytold/egui final revision (complete P8: E1-E21 + P2b/P7/P7b/P7c)
X1: reviewed + approved storytold/winit final revision (W1-W27 + W15P)
X2: reviewed + approved storytold/craft-libs final revision
(merged PR #199 base 3832c47; reviewed capture/text helpers; craft-window/platform/quit;
reviewed CL-2/CL-2F capture contract for the local adapter; no b68de8c/ec8840a tree)
X3: optional E22-E25 only after measurements, with provenance and fresh review
FigCraft
F0 baseline evidence (no production change)
|
F1 lifecycle/action refactor on 0.36:
background_tick/ui_setup split, child/global split, action tokens, hidden/multipass tests
|
F2 atomic 0.37 compatibility PR (depends F1, X0-X2):
manifests, final patches, lock, Id/Ui/WidgetType/MouseWheel/Band/scroll,
local capture slot, panel IDs + preference preservation, a11y edge,
E21b adapter, RTL logical boundaries, kittest strictness
|
F3 optional performance/provenance patches (depends F2, F0/F2 measurements)
|
F4 workaround removal + docs/ROADMAP/parity notes (depends F2, F3; only with evidence)
Each stage is a separate reviewable PR/branch and must be green on its own:
- F1 includes the queued hidden screenshot/input behavior above and its basic
paint/timeout regression in the atomic callback migration. It touches
crates/ui-egui/src/lib.rs,windows.rs,control.rs,present.rsandapps/figcraft/src/main.rsplus tests; no manifest/dependency change. It proves the new background/UI split and action tokens on 0.36 first, including the native/web call sequence, hidden logic, forced hidden paint, restore-with-buffered-input and two-window global work. - F2 is atomic: root/app/UI manifests,
Cargo.lock, final patches,ui-eguisource and tests, the E21b model adapter and the panel preference/geometry checks. It must not leave a half-updated dependency set. The mechanical ID/WidgetType codemod and capture callback may be separate commits inside the PR, but the PR is mergeable and green as a whole. - F3 changes only the final patch revision(s) and carries the measurement/provenance table; rollback is a revision change plus relock.
- F4 removes a workaround only after the corresponding test/manual evidence; otherwise retain the workaround and record why.
Publication rule: every push/tag/new PR needs fresh explicit human approval after the parent shows
the exact contents and validation. No approval is granted by this plan. Public prose contains no
agent/model/tool signature; contributor credit remains username-only (@skylartaylor).
Rollback
- Preserve baseline
Cargo.toml,Cargo.lockand manifest hashes in evidence. - Roll back by reverting F2 (and F1 if the lifecycle change is implicated) in reverse order; do not keep a 0.36/0.37 feature flag for these APIs.
- Optional F3 patches roll back by restoring the previous final fork revision and re-locking; do not rewrite history or force-push.
- If only
craft-window/craft-libsis at fault, repoint to the last merged public revision and relock; do not copy the crate into the repository or use a machine-local path. - Keep the old coherent dependency set available in git history; do not delete retained evidence or unreviewed source work. Rehearse restoring the previous executable/lock and reopening a saved fixture before accepting the stage.
10. Blocking unknowns and acceptance gates
- Final storytold revisions:
STORYTOLD_EGUI_FINAL_REV,STORYTOLD_WINIT_FINAL_REVandSTORYTOLD_CRAFT_LIBS_FINAL_REVdo not exist until the reviewed series is approved and published. F2 is implemented locally against the review worktrees, but it cannot be published or presented as an immutable-pin change before those revisions exist. Candidate aiongg SHAs are comparison inputs. - craft-libs #199: PR #199 merged as
3832c47ef68a0e6214f2c1e85f3526b6adcb686e. That merged base is the anchor; the earlierb68de8chead andec8840aaudit merge are stale history and neither tree is usable. The local worktree adds only the reviewed capture/text helpers on top of3832c47; no published revision exists yet. - W15P PRIMARY: the candidate's arboard fallback is not accepted for GNOME/Mutter. W15P must be reviewed/published and E4 P2b integrated with native wlroots/KWin/Mutter evidence; otherwise the PRIMARY gate is unresolved/blocked.
- E21b/P7b: outgoing surrounding text is not present in the comparison candidate. The local
reviewed fork plus FigCraft's custom-editor producer implement the bounded payload and
receipt/change-cause contract described in
docs/egui-037-local-followups.md; the Wayland adapter and final immutable fork pin still require review, validation and publication.Noneplaceholders are not a port. - Fork publication reachability: final storytold repositories must be public/readable; a private/branch dependency breaks contributors. Fresh human approval is required before any push.
- Language #198 / E5-E21: the fork is the integration point. If the reviewed series changes or is reverted, bump the pin in a separate PR and rerun RTL/IME tests. Do not copy patches into FigCraft.
- Known 0.37 performance regressions (#8731, #8736, #8512): decide only after three-way paired measurements. If thresholds fail and no provenance-recorded fix is accepted, the migration is blocked.
- TextEdit click-away (#8763/#8764): #8764 is a proposed, unmerged fix. Verify FigCraft's custom selection actions; if affected, fix/test before merge.
- Hidden deferred children (#8748): closed and unmerged. Verify with
RequestPaintWhileHidden; otherwise adopt a reviewed fork fix or record the unresolved behavior. - Font input: baseline and candidate must use the same
CRAFT_FONTS_DIRref. A no-craft-fonts run is valid for CI but not for a font-metrics comparison. Color-emoji claims require theepaint/color_fontsfeature job. - macOS/FreeBSD/real GPU: no routine UI test evidence. The parent-visible i915 render node is
unqualified, and this worker sandbox may hide
/dev/dri; there is no inherited compositor or ambient display. Compile/packaging results are not runtime passes. Mark them unresolved until real evidence exists. - Lockfile resolution: one egui family, one winit, one wgpu, one craft-window. If Cargo chooses two versions, stop and fix the pins rather than accepting a duplicate.
- AccessKit per-viewport: the pinned 0.37 fork initializes the native AccessKit adapter only for the root viewport; creating a secondary viewport does not initialize an adapter. The “main window only” limitation remains, and no real screen-reader qualification is claimed. Keep the tab fallback until secondary-window accessibility is implemented and tested.
- Automation screenshot thread: the callback must not write files outside
file_accessconfinement. The shared completion-slot design in 6.3 is required, not optional.
11. App-specific acceptance assertions
- Opening a synthetic document, editing a rectangle and text, undoing, saving and reopening gives
the same
document.inspecttree; command ids, undo steps and file bytes are unchanged except for documented UI metadata. - The UI at 1440×900 and 800×500 keeps the panel/canvas geometry contract (
fit_panels,panel_width_tests), the central canvas hit area, and the toolbar/tab hit rects. - F6/Tab focus order covers panels and controls but never the actual custom panel edge; AccessKit
roles and names match
a11y_tests; the root language follows the UI language. - A screenshot of the main window and of a deferred child returns the correct viewport, request id, capture-time geometry/DPI and one reply, including two requests completed out of order; hidden screenshots queue then paint, or return the existing error once on deadline; cancellation/timeout/close never writes late.
- English, RTL, CJK and shortcut-symbol fixtures have glyphs using the pinned licensed fixtures; the font picker previews without changing the document; no extra script face is loaded for an English-only screen.
- RTL names and custom text edit logically: the displayed order is correct, while clipboard, AccessKit name, save and IME surrounding text carry the logical string.
ui.inspectreports no idle repaint requests and stable frame counts; autosave/recovery run while the window is hidden; prototype playback does not duplicate actions after a sizing pass.- Windows DX12 starts, draws, opens/saves/reopens, handles IME/non-Latin keys, and shows no Vulkan selection unless requested. Linux X11/Wayland copy/paste and PRIMARY are reported only from a real session. Browser WebGPU and WebGL2 builds load, edit, undo, export and handle clipboard events.
12. Verification limits of this plan
- No source change, dependency resolution, build, test, benchmark, screenshot, browser run or GUI interaction was performed. Historical compiler errors are not current acceptance.
- Line numbers and symbol locations come from baseline
80f9163and can move during implementation; anchors are given by file and symbol. - The final fork/craft-libs merge state is unknown until approved publication. Re-check the exact immutable revisions and public reachability immediately before implementation and before every publication.
- Upstream issue measurements (#8731, #8736, #8512) are reported by others, not reproduced here; this plan requires FigCraft's own three-way paired measurements.
- macOS, FreeBSD, real-GPU, screen-reader and Wayland/PRIMARY behavior remain unproven until evidence is recorded from real environments.
- This revision was prepared by static inspection only. Reviewer claims were checked against the
pinned source where possible; the review's suggestion to use
visible()as a phase signal was rejected because the authoritative lifecycle audit andrun_logicsource show visibility does not distinguish logic-only from forced hidden UI.
13. References
Primary sources consulted:
storytold/eguifinal reviewed revision and upstream0.37.0841a328b:crates/egui/src/context.rs,crates/egui/src/ui.rs,crates/egui/src/id.rs,crates/egui/src/id_salt.rs,crates/egui/src/sense.rs,crates/egui/src/containers/panel.rs,crates/egui/src/data/input/event.rs,crates/egui/src/data/screenshot_callback.rs,crates/egui/src/viewport.rs,crates/egui/src/containers/scroll_area.rs,crates/eframe/src/epi.rs,crates/eframe/src/native/*,crates/eframe/src/web/app_runner.rs,crates/egui-wgpu/src/winit.rs,crates/egui-winit/src/*,crates/egui_system_font_provider/src/lib.rs,crates/egui_extras/*.- aiongg candidate
a4f9a17f:PATCHES.md, the same source files, used only for comparison and gap identification. storytold/winitfinal reviewed revision; candidate8b35388bW17-W27; publishedffae5198W1-W15; W15P PRIMARY design.craft-libsmerged base3832c47(PR #199), historical API-port source8614e4a/1764bca, and the reviewedcraft-uicapture/CL-2F contract as the local adapter reference.craft-libs/docs/egui-037-suite-rollout.md,craft-libs/docs/egui-037-migration-plan.md,egui/docs/egui-037-migration-plan.md(including section 10.6 font fixtures)./home/codex/work/ssd2/task-evidence/egui037-migration-20261022/lifecycle-source-audit.md,ime-surrounding-gap.md,upstream-discussions.json.
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/figcraft main now resolves to 8d790607ff1ad40e32343ca2116d8fdcf3244c8d. 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.
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 (2026-10-23)
The owned worktree now contains the first coherent 0.37 compatibility/lifecycle stage described in
docs/egui-037-local-followups.md: the 0.37 dependency family and mechanical API ports, the
background_tick/ui_setup split with a monotonic frame clock and child/global separation, the
local capture-slot adapter (weak callback, identity/deadline/generation/geometry epoch, budget,
straight RGBA, hidden queue + RequestPaintWhileHidden), the real non-focusable panel-edge fix,
the root-scope a11y record, and incoming IME DeleteSurroundingBytes for the custom editor.
Outgoing E21b surrounding text is implemented as described in docs/egui-037-local-followups.md;
web hidden paint remains a recorded prerequisite, not a fake.
No build/test/GUI/browser/performance run was performed for this stage; the parent runs managed
validation and generates the lockfile from its local dependency config.
Historical local qualification — 2026-10-25
The following counts apply to the earlier 10702311 integration, not the later
2a3b7d4f qualification documented in docs/egui-037-local-followups.md. The
status update above covers the subsequent 92682728 integration. The intervening
9f788753 refresh passed 3,247 executions; the runs listed below passed 3,242.
The migration preserves captured main 10702311f345393ee1d8868c3257679befcfa14d
(53 commits and 114 paths beyond the first integration). The final review commit records
both the migration history and that upstream target as parents. The selected egui revision is
6d518fc799681571963842f95b55a6b68431b15a, winit is
a517b3846ee8457d84ee4975110bfda6797c44dc, and craft-window is
25da1314d443171b4adeaad2cd9454b273f35c6f. Cargo.lock SHA-256 is
7ba8af4de70ba657a652266ddee2920bfc501c851520a49ac60c8044a5c23cc1.
The normal UI enables default_fonts and monochrome_emoji_fonts.
Managed full CI egui037-figcraft-final-ci-20261024-2312 passed with bundled fonts.
Managed full CI egui037-figcraft-system-font-ci-20261025-0402 also passed without
bundled craft-fonts, using a licensed DejaVu Sans system-font fixture via XDG_DATA_HOME.
The final qualification run egui037-figcraft-final-qualification-ci-20261025-0453
repeated that configuration successfully after the documentation reconciliation.
Both runs completed formatting, strict lint, workspace tests, doctests, translations,
layering, assets, WASM checks, and plugin fmt/lint/tests/release-WASM builds. Each log
contains 3,242 passing executions, zero failures and nine ignored across 70 summaries;
this includes repeated translation checks and is not a count of distinct regressions.
The main doctest stage selects zero tests. Three plugin bundles were hash-verified and
preserved for each configuration.
The no-bundled-font run includes two test fixture corrections: text-fit cases explicitly scan installed fonts, and a concurrent collaboration test explicitly selects its foreground document after both joins complete instead of depending on network completion order. Independent review accepted these changes with the original layout, synchronization and isolation assertions intact. All 17 field-save regressions remain covered, including owner-bound deferred saves, same-pass document replacement and later valid edits/saves. Incoming IME interruption, screenshot geometry admission, and document/window ownership are implemented. Historical notes below describe the earlier planning stages.
The final review packet binds source, lock, logs, plugin outputs and commit ancestry. Native visible GUI/browser workflows, native/custom-editor IME, paired performance, current-source platform runtime and actual release installation/signing remain open. The system-font result is not a claim that missing document fonts are interchangeable. Human review and human merge are required; no publication is authorized by this document.