mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 13:56:54 -06:00
1231 lines
92 KiB
Markdown
1231 lines
92 KiB
Markdown
# 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.md` and `CLAUDE.md` are identical; there are no nested
|
||
`AGENTS.md`/`CLAUDE.md` files under `crates/`, `apps/`, `plugins/` or `xtask/`.
|
||
- Current lock: `egui`, `eframe`, `egui-wgpu`, `egui-winit`, `egui_extras`, `ecolor`, `emath`,
|
||
`epaint` all `0.36.2`; `winit` `0.30.13`; `wgpu` `30.0.1`; `accesskit` `0.24.1`;
|
||
`wasm-bindgen` `0.2.129`.
|
||
- Git dependencies: `craft-docpath` rev `82a24b006eefa036b43c09a67fb9a3fbd17ffe63`;
|
||
`craft-window` rev `1f805378d454dac48b5c214029d254055fdf961b` (that revision's workspace
|
||
declares egui 0.36 and cannot be used unchanged with 0.37).
|
||
- Build input: tracked `.cargo/config.toml` sets `CRAFT_FONTS_DIR=../craft-fonts` (relative,
|
||
optional). It is locally skip-worktree, so it did not appear in `git 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.md` is the
|
||
authoritative lifecycle evidence for this plan.
|
||
- `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/ime-surrounding-gap.md` is 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
|
||
|
||
1. 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.
|
||
2. Keep FigCraft's existing `App::logic` + `App::ui(&mut Ui)` architecture. Do not rewrite to
|
||
`App::update`; it does not exist. Do not call `Context::root_ui` from `App::ui`; the root `Ui`
|
||
is already borrowed (upstream #8535).
|
||
3. Treat the real eframe lifecycle: native and web both call `App::logic` **inside** the
|
||
`Context::run_ui` closure, once per layout pass. `run_logic` is not distinguishable through
|
||
`ViewportInfo::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.
|
||
4. 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 in `FigApp::run`/`set_props` (neither receives a `Context`) and would starve fresh
|
||
work.
|
||
5. 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.
|
||
6. Correct the actual a11y edge handling: FigCraft's panels are `resizable(false)`, so egui does not
|
||
create `left_panel.__resize`/`right_panel.__resize`. FigCraft's custom edges are
|
||
`Id::unique(("panel_edge", "left"/"right"))` with a focusable `Sense::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.
|
||
7. 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 `None` placeholders as the completed port.
|
||
8. 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`'s `DockArea`; if it later adopts it, use the explicit opt-in direction policy with
|
||
unchanged saved physical splits and logical tab order.
|
||
9. 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_fonts` is disabled.
|
||
10. 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.rs` `main()` → `eframe::run_native`; `App::logic`,
|
||
`App::raw_input_hook`, `App::ui`, `App::on_exit`.
|
||
- Child windows: `crates/ui-egui/src/windows.rs` `Windows`, `viewport`, `show`, `draw`,
|
||
`enter`/`leave`, `as_window`.
|
||
- Browser: `apps/figcraft-web/src/web.rs` `start`, `WebShell::ui`, `WebServices`.
|
||
- Headless/UI tests: `Context::default().run_ui(...)` in many `crates/ui-egui/src/*_tests.rs`
|
||
files; `crates/ui-egui/src/idle_tests.rs` has the frame/idle harness; `windows_tests.rs` has the
|
||
`run_logic` hidden-window harness.
|
||
- Render/control: `crates/ui-egui/src/canvas/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`: ignored `perf_ui_frames` and `report_components` benchmarks,
|
||
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.sh` enforces 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.rs` `App::logic` drains the control queue, hidden requests, Apple events,
|
||
close requests, title updates and recovery.
|
||
- `App::ui` wraps `FigApp::ui` in `windows::guard`.
|
||
- `crates/ui-egui/src/lib.rs` `FigApp::ui` calls `self.logic(&ctx)`, `autosave_tick`,
|
||
`auto_save_tick`, then `windows::begin`, `frame` (which also calls `control::drain`), `settle`,
|
||
`show`.
|
||
- `crates/ui-egui/src/windows.rs` `draw` swaps a child window's pane into the app, calls
|
||
`app.logic(&ctx)`, then frames it under `windows::guard`.
|
||
- `FigApp::logic` installs language/fonts/theme, computes UI zoom from `ctx.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::update` calls `app.logic` then `app.ui`
|
||
inside `Context::run_ui`.
|
||
- Web `crates/eframe/src/web/app_runner.rs` does the same for visible tabs and calls
|
||
`Context::run_logic` for hidden tabs; native `run.rs` can force a full `PassMode::Full` UI for
|
||
invisible windows and a `LogicOnly` pass for overdue redraws.
|
||
- `Context::run_logic` refreshes 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_pass` takes the output; `run_dyn` restores 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 be `None`; it does not identify
|
||
`run_logic` versus 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/canvas/frame_labels.rs:63` | non-exhaustive match missing `Shape::Band` | Add the 0.37 shape variant |
|
||
| `crates/ui-egui/src/panels/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.
|
||
|
||
```toml
|
||
[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/dialogs/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:
|
||
|
||
```sh
|
||
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_extras` and `winit` (and `egui_glow` if a glow build is enabled).
|
||
- `Cargo.lock` records 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`.
|
||
- `wgpu` resolves to one version. Current FigCraft lock is `30.0.1` while the candidate lock says
|
||
`30.0.0`; both satisfy `30.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 --locked` native 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
|
||
`tessellate` in 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/`select` implementation (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
|
||
`RequestPaintWhileHidden` path 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/dialogs/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/canvas/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/panels/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::unique` and `Id::new` use the same hash; no persisted state changes. `Ui::scope_id` and
|
||
`Ui::id` are 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.offset` and ensure only intentional comments remain.
|
||
- Run `cargo fmt` and `cargo clippy --workspace --all-targets --locked -- -D warnings`; a clean
|
||
`cargo check` is not enough because deprecations are warnings.
|
||
- F2 adds `egui_kittest` 0.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 into `MissingGlyphPolicy::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:
|
||
|
||
```rust
|
||
// 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 IDs
|
||
`Id::unique(("panel_edge", "left"))` / `Id::unique(("panel_edge", "right"))` from
|
||
`focusables` as 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 enables `Panel::resizable(true)`,
|
||
derive the real resize ID from the recorded root `Ui::scope_id()`:
|
||
`root_scope.with("left_panel").with("__resize")`, and test it.
|
||
- Record the root scope in `a11y::begin` from the passed root `Ui` (change the call from `&ctx` to
|
||
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`:
|
||
|
||
- `PendingShot` stores `id`, `path`, `reply`, `since`, `viewport`.
|
||
- `drain` scans `ctx.input` for `Event::Screenshot` and completes every pending shot for that
|
||
viewport in arrival order.
|
||
- `shot_reply` encodes PNG and writes via `figcraft_engine::file_access::write`.
|
||
- `idle_tests` manufactures `Event::Screenshot`; `windows_tests` only tests the hidden-window error.
|
||
- `apps/figcraft/src/control_server.rs` marks the request cancelled after the transport timeout, but
|
||
`PendingShot` does not retain that flag and `drain` only checks `now - 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:
|
||
|
||
1. 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 a `Weak` reference, never a strong
|
||
`Context`/app/queue, so a late capture cannot retain the app through shutdown.
|
||
2. 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 `CaptureSpec` records capture-time logical viewport rectangle, pixels-per-point, zoom and
|
||
expected surface size. Revalidate before issuance after resize/DPI changes.
|
||
3. `ui.screenshot` sends `ViewportCommand::Screenshot(ScreenshotCallback::new(...))` to the target
|
||
viewport (`send_viewport_cmd_to` for a child; `request_screenshot` for the current one). The
|
||
callback performs only `slot.try_complete(image)`; no PNG encoding, file I/O, command, lock held
|
||
across wakeup, or mutable app access.
|
||
4. The completion slot stores the image and a `CancelToken` copied from
|
||
`ControlRequest.cancelled`. It must release its lock before waking the owning viewport. Wake with
|
||
`Context::request_repaint_of(capture_spec.viewport)`, never a bare `request_repaint()` from a
|
||
detached callback; a stored `Context` clone 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.
|
||
5. On the UI thread, `control::drain_ui` takes 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 calls `shot_reply` **inside the existing
|
||
`windows::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.
|
||
6. 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.
|
||
7. 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 `RequestPaintWhileHidden` through 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 `Context` cycle; 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:
|
||
|
||
```rust
|
||
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_FRAME`
|
||
requests stay queued for the next forced UI pass, never errored just for logic-only
|
||
execution. `window.new` retains its explicit hidden error as a separate app policy;
|
||
- live `session.live_poll()` and `background_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_factor` and `ui.ui_scale` using the actual current geometry;
|
||
- `render_worker::Worker::new` and `FrameStats::record` once per visible root frame, not per pass;
|
||
- the first-font-frame `skip_draw` flag: 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::logic` calls
|
||
`with_app(|fig| windows::guard(fig, ctx, |fig| fig.background_tick(ctx, &mut clock)))` on every
|
||
callback. Wrapping in `windows::guard` preserves `file_access::confined` automation roots and
|
||
panic recovery for autosave/recovery/preference writes. Set `fig.logic_ticked = true` when the tick
|
||
ran.
|
||
- `App::ui` calls `fig.ui(ui)` through the existing `windows::guard` wrapper.
|
||
- `FigApp::ui` runs the background tick only when `logic_ticked` is false (direct harnesses), then
|
||
`ui_setup` on pass 0, then `frame`.
|
||
- `windows::draw` calls a new `child_logic(ctx, id)` for per-viewport state (focus, close request)
|
||
and **not** the app-global `background_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. Child `frame` keeps its existing `windows::guard` boundary.
|
||
- `FigApp::frame` calls `control::drain_ui` (UI-capable requests queued by the background tick and
|
||
any screenshot completions) instead of the old all-methods `control::drain`. A state-only request
|
||
arriving between passes is consumed by the next `App::logic` call; fresh work is never starved by
|
||
a nonzero pass index.
|
||
- `Context::run_logic` does not advance `cumulative_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_logic` leaves input events, time and layout stale. `background_tick` must not call
|
||
`cjk_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 hidden `run_ui` has current input and real geometry; a `run_logic` callback does
|
||
not.
|
||
- Use `FrameClock` based on `web_time::Instant` for 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 only `present::show` or canvas drawing. Split the existing timer/action code
|
||
in `crates/ui-egui/src/present.rs::show` (and the canvas GIF clock) into a non-drawing step called
|
||
by the tick; `show` only 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
|
||
hidden `run_logic` at 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.
|
||
Only `window.new` retains 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::RequestPaintWhileHidden`
|
||
from 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_ui` may call the closure more than once if `request_discard` is used. `App::logic`
|
||
runs once per pass too. Therefore every `background_tick` operation 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 `ActionToken` carrying the viewport,
|
||
frame number, source widget ID and command; `FigApp::frame` drains the queue once per token and
|
||
calls `run`/`run_user`/`set_props`. Reads and drawing remain per-pass. `FigApp::run` and
|
||
`set_props` do not receive a `Context`, so they cannot and must not read
|
||
`current_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: MouseWheelSource` to every construction. `control.rs` synthetic scroll uses
|
||
`Wheel`; unit tests use `Unknown` unless testing source-sensitive scroll behavior.
|
||
- `Event::PasteImage` and `Event::MiddleClickPaste` are 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's `paste` event.
|
||
- **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 regular
|
||
`wl_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_tests` and add
|
||
a delete-surrounding-bytes test asserting one undo step. The engine text edit state and egui
|
||
`TextEdit` state must not double-apply commits.
|
||
- **E21b/P7b outgoing surrounding text:** the candidate `egui-winit` never calls
|
||
`WindowExtWayland::set_ime_surrounding_text`, and `egui::IMEOutput` has no logical text. Add the
|
||
bounded transient payload (at most 4000 UTF-8 bytes) to `IMEOutput`, preserving Clone/Debug/Eq,
|
||
excluding it from serialized/debug persistence, with committed logical text, cursor/anchor byte
|
||
indices and owning widget/viewport identity. Built-in `TextEdit` produces 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-winit` sends 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_output` currently
|
||
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 from `bidi.rs`. This adapter is part of the atomic F2 compatibility stage, not a `None`
|
||
placeholder.
|
||
- 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.rs` currently pre-reorders names with `figcraft_text::display` and 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_tests`
|
||
and a mixed-logical/clipboard/IME test must pass. Any cache keyed by `FontId` must account for
|
||
`FontFamily::Name` and `LayoutJob::direction`. Do not delete `bidi.rs` until 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()))` in
|
||
`ui_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_name` are available in 0.37; use them only where
|
||
FigCraft currently has unnamed native widgets. Custom `a11y` node builders remain.
|
||
- Shared `DockArea` direction: FigCraft keeps its own panels and does not adopt `craft-ui`'s
|
||
`DockArea`. If a future PR does, it must pass an explicit opt-in `DockDirection`, 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_fonts` starts from `FontDefinitions::default()`. With egui
|
||
default features empty, that is `FontDefinitions::empty()` unless the app/UI crate enables
|
||
`default_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 enable `default_fonts` and `monochrome_emoji_fonts` on FigCraft's egui test
|
||
dependency for these fixtures. Audit UI-library/test/native/web graphs independently.
|
||
- Keep the optional `CRAFT_FONTS_DIR` build 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_fallbacks` and 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_fonts` is 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_setup` installs fonts for the first time, set a
|
||
`skip_draw` flag; `FigApp::ui` requests 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-fonts` at `2481a8a324b986a45954e90045ed1f8f2efaaf3c` as 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`, SHA256
|
||
`63111b5b2e074dd48cc67692e0a2726d86ee94c1c37fe8598257b7b4e87e869e`, OFL-1.1.
|
||
- Thai `fonts/noto-sans-thai/NotoSansThai.ttf`, SHA256
|
||
`5a1c559bb539583c8a1fd99d1c5b9491e5e14478c9cd2bd0970d5c3096cc9ef8`, OFL-1.1.
|
||
- CJK SC/Latin `fonts/noto-sans-cjk-sc/NotoSansCJKsc-Regular.otf`, SHA256
|
||
`2c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b`, OFL-1.1.
|
||
- Japanese `fonts/biz-ud-pgothic/BIZUDPGothic-Regular.ttf`, SHA256
|
||
`258d7156c165f2ff774b6efee637c22c3b950de0d8a10e501137061bc8085d01`, 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 the `epaint/color_fonts` feature 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.
|
||
- If a controlled Latin/emoji comparison is needed, use the exact `epaint_default_fonts` files 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 custom `panel_edge` overlays). 0.37 panel constructors accept `AsIdSalt` and
|
||
include separator width; test `fit_panels`, `panel_width_tests`, canvas minimum and hit testing.
|
||
Do not replace the custom edge drag with `Panel::resizable` without a UI3 geometry comparison.
|
||
- Clip/nesting: keep the root `Ui` and `scope_builder("toolbar")` nesting; custom overlays keep
|
||
their own `with_clip_rect` calls. 0.37 keeps `clip_rect_margin` as 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-ui` docking; do not add it as part of this migration. The
|
||
capture completion slot stays local. If a future PR adopts `DockArea`,
|
||
follow the explicit direction policy in 6.5.
|
||
- Child windows: keep `windows.rs` deferred viewports and pane swapping. 0.37 adds
|
||
`RequestPaintWhileHidden`; use only for reconciliation when a child must appear/update/disappear
|
||
while the root is hidden. Keep `embed_viewports` and the screen-reader tab fallback until
|
||
per-viewport AccessKit is proven.
|
||
- Title bars: `craft-window` decides `TitleBar::Native/Inset/Custom`; `window_chrome` maps to
|
||
`ViewportBuilder`. 0.37 keeps these builder methods. macOS traffic-light positioning is not
|
||
changed without a Mac; `Frame::set_traffic_lights_position` is available if later measured.
|
||
- GPU: `apps/figcraft/src/graphics.rs::backends/configure` still mutates
|
||
`WgpuConfiguration`/`WgpuSetup::CreateNew(create).instance_descriptor.backends`; verify compile
|
||
and preserve DX12-then-GL on Windows. E2/E3 stay. Measure `Renderer::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. Keep
|
||
`TextureOptions::LINEAR` and clamp/repeat/mirror behavior where used; test a translucent image and,
|
||
only in the `epaint/color_fonts` job, 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):
|
||
|
||
```sh
|
||
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_DISPLAY` and 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 if
|
||
`epaint/color_fonts` is 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.lock` hash, `rustc -Vv`, `cargo -V`, target triple, features, `CRAFT_FONTS_DIR`
|
||
presence/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
|
||
|
||
1. `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 in `apps/figcraft-cli/src/perf.rs::measure`.
|
||
2. `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 in `crates/ui-egui/src/perf_tests.rs`.
|
||
3. **#8731 text tessellation:** fixed synthetic document with 120–240 text labels; layout once, then
|
||
`ctx.tessellate` the 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.
|
||
4. **#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.
|
||
5. **#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.
|
||
6. Native startup: process spawn to first successful `ui.inspect`/first painted frame; 20 fresh
|
||
processes; separate shader/renderer creation logs.
|
||
7. Idle repaint/CPU: 60 s hidden and visible idle; `framesDrawn`, `repaintCauses`, OS CPU. Must be
|
||
zero frames and ~0% CPU when idle.
|
||
8. Memory: Linux `/usr/bin/time -v` or `/proc` peak RSS; Windows private bytes; record peak for
|
||
10k/50k documents, startup and capture admission.
|
||
9. Screenshot/control latency: `ui.screenshot` request to reply (including completion-slot drain,
|
||
cancellation and pending-bytes counters), `engine.execute`, save/reopen, window open/focus/close.
|
||
10. 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.rs` and
|
||
`apps/figcraft-cli/src/perf.rs` to 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.rs` and
|
||
`apps/figcraft/src/main.rs` plus 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-egui`
|
||
source 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.lock` and 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-libs` is 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
|
||
|
||
1. **Final storytold revisions:** `STORYTOLD_EGUI_FINAL_REV`, `STORYTOLD_WINIT_FINAL_REV` and
|
||
`STORYTOLD_CRAFT_LIBS_FINAL_REV` do 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.
|
||
2. **craft-libs #199:** PR #199 merged as `3832c47ef68a0e6214f2c1e85f3526b6adcb686e`. That merged
|
||
base is the anchor; the earlier `b68de8c` head and `ec8840a` audit merge are stale history and
|
||
neither tree is usable. The local worktree adds only the reviewed capture/text helpers on top of
|
||
`3832c47`; no published revision exists yet.
|
||
3. **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.
|
||
4. **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. `None`
|
||
placeholders are not a port.
|
||
5. **Fork publication reachability:** final storytold repositories must be public/readable; a
|
||
private/branch dependency breaks contributors. Fresh human approval is required before any push.
|
||
6. **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.
|
||
7. **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.
|
||
8. **TextEdit click-away (#8763/#8764):** #8764 is a proposed, unmerged fix. Verify FigCraft's
|
||
custom selection actions; if affected, fix/test before merge.
|
||
9. **Hidden deferred children (#8748):** closed and unmerged. Verify with
|
||
`RequestPaintWhileHidden`; otherwise adopt a reviewed fork fix or record the unresolved behavior.
|
||
10. **Font input:** baseline and candidate must use the same `CRAFT_FONTS_DIR` ref. A
|
||
no-craft-fonts run is valid for CI but not for a font-metrics comparison. Color-emoji claims
|
||
require the `epaint/color_fonts` feature job.
|
||
11. **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.
|
||
12. **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.
|
||
13. **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.
|
||
14. **Automation screenshot thread:** the callback must not write files outside `file_access`
|
||
confinement. 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.inspect` tree; 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.inspect` reports 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 `80f9163` and 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 and `run_logic` source show visibility does
|
||
not distinguish logic-only from forced hidden UI.
|
||
|
||
## 13. References
|
||
|
||
Primary sources consulted:
|
||
|
||
- `storytold/egui` final reviewed revision and upstream `0.37.0` `841a328b`:
|
||
`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/winit` final reviewed revision; candidate `8b35388b` W17-W27; published
|
||
`ffae5198` W1-W15; W15P PRIMARY design.
|
||
- `craft-libs` merged base `3832c47` (PR #199), historical API-port source `8614e4a`/`1764bca`,
|
||
and the reviewed `craft-ui` capture/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.
|