mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-31 13:57:18 -06:00
1427 lines
101 KiB
Markdown
1427 lines
101 KiB
Markdown
# WordCraft: egui 0.37 migration plan
|
||
|
||
**Status:** current-main reconciliation through `b4da59f0c38b506c36629b1d812ad7c0519fc238` passed all eight mandatory CI stages on 2026-10-25: 4,270 passing test executions, zero failures and 4 ignored (`egui037-wordcraft-main-refresh-ci-20261025-0603`). This includes main's encrypted-DOCX integrity requirement and selected-paragraph-mark highlight behavior. The dependency pins and lockfile are unchanged. A fresh review verified that the merge preserves the IME documentation and upstream fixes. Earlier counts and source snapshots below are historical.
|
||
|
||
Read-only comment balloons now consume the explicit `AlignedGalley` result from craft-ui at
|
||
`25da1314d443171b4adeaad2cd9454b273f35c6f`: height comes from the requested reading area,
|
||
and painting retains the existing reading origin and card clip while preserving overwide
|
||
right-to-left rows through an explicit offset. Added geometry/ink and actual card/body/reply
|
||
regressions use bundled Latin fonts and require exact embedded Arabic/Hebrew faces in the
|
||
canonical `CRAFT_FONTS_REQUIRED=1` lane. Optional-input builds still exercise Latin geometry.
|
||
The earlier 616-file migration/current-main PDF overlay and its CI run remain historical
|
||
baseline evidence. The current full CI qualifies the API/dependency followup, preview fix,
|
||
phonetic labels and preserved notice/packaging source repair.
|
||
Native GUI/IME/capture/save-reopen traces, package notice verification, paired performance,
|
||
platform/installer execution and app publication remain separate gates. This records local
|
||
work, not publication or complete platform acceptance. Historical notes below remain history.
|
||
**Scope:** the WordCraft checkout at
|
||
`/home/codex/work/CraftApps/egui037-migration-20261022/wordcraft-implementation`.
|
||
**Baseline source:** `3a4770ef8ce037d2650c93e2d5764536549357c7` ("Merge pull request #3286 from
|
||
matstrange/fix/protection-inspect-tracking"), workspace version 0.10.0, edition 2024,
|
||
`rust-version = "1.95"`, resolver 3. Baseline `Cargo.lock` SHA-256
|
||
`be3d0675270364a44b7011d1d80a6a255d5823c1a52088347faa18fa9cebfd88`; baseline `Cargo.toml`
|
||
SHA-256 `7838ccd3744441a54b9e8024121e9007e64b54294a6e0b442fe758edc45c184a`.
|
||
|
||
Baseline dependency identities in that lockfile:
|
||
|
||
| Source | Baseline identity |
|
||
|---|---|
|
||
| egui family | registry `egui`/`eframe`/`egui-winit`/`egui-wgpu`/`egui_glow`/`egui_kittest`/`epaint`/`emath`/`ecolor` = 0.36.2; `kittest` = 0.4.0 |
|
||
| winit | registry 0.30.13 |
|
||
| accesskit | registry `accesskit` 0.24.1, `accesskit_winit` 0.32.2, `accesskit_unix` 0.21.1 |
|
||
| craft-libs | `craft-ui` + `craft-print` at `d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d`; `craft-pdf` at `c3487320b2d1de657b860b3c8abb44e1f2b32bd8` |
|
||
| fonts | `CRAFT_FONTS_REF` `b076417b5880bd4154bd22f00695b1d28c6bea83` (CI), plus the bundled Inter/SourceSans/JetBrains Mono assets |
|
||
| toolchain | workspace MSRV 1.95; the planning host used rustc/cargo 1.99.0, which is not an acceptance toolchain by itself |
|
||
|
||
**Target and current local pins:** egui family 0.37.0 at
|
||
`6d518fc799681571963842f95b55a6b68431b15a`, winit at
|
||
`a517b3846ee8457d84ee4975110bfda6797c44dc`, and craft-ui at
|
||
`25da1314d443171b4adeaad2cd9454b273f35c6f`. The regenerated lock records these exact
|
||
revisions and passed the dependency-source audit. The current-main shared PDF, printing and
|
||
locale update uses craft-pdf, krilla, craft-print and craft-i18n at
|
||
`17744430be327d0257496a6a31c8d38fe20a6756`. These are immutable canonical Git URL pins.
|
||
The predecessor passed full CI; the latest main integration is undergoing its own full CI.
|
||
The translation catalog is pinned
|
||
separately to the reviewed `9205108cfdced5177706264038848b68b2422d2b`; it is not a Cargo dependency.
|
||
Publication observations and
|
||
authority remain separate from this local status. Stock 0.37 is a measurement control, not the production target.
|
||
Earlier baseline and research revisions below remain historical references.
|
||
|
||
This plan follows the suite contract in
|
||
`craft-libs/docs/egui-037-suite-rollout.md` and the planning contract. It records only
|
||
WordCraft-specific source, behavior and evidence. The shared rollout plan remains the authority
|
||
for suite-wide publication order and the shared performance protocol.
|
||
|
||
## 1. What is and is not changing
|
||
|
||
WordCraft already uses the 0.36 `App::logic` / `App::ui` split and an existing root `Ui`.
|
||
`App::logic` is already called before every UI pass, including repeated passes; `App::ui`
|
||
already receives the root `Ui`. The 0.37 migration must preserve that architecture rather than
|
||
rewriting it. The real work is:
|
||
|
||
1. making the dependency graph coherent and immutable;
|
||
2. repairing the concrete 0.37 API breaks;
|
||
3. removing application-side text processing that the fork now performs correctly;
|
||
4. preserving one-shot lifecycle work across repeated passes and logic-only frames;
|
||
5. validating that the page, chrome, accessibility, input, clipboard and save/reopen behavior
|
||
remain what users expect.
|
||
|
||
No engine, command id, document format, undo semantic or persistence format is intentionally
|
||
changed by this migration. The app remains `Session`-driven; egui remains a thin L6 front end.
|
||
|
||
## 2. Repository inventory
|
||
|
||
### 2.1 Workspace and package targets
|
||
|
||
`Cargo.toml` declares one workspace, `members = ["crates/*", "apps/*", "xtask"]`, with no nested
|
||
workspace and no vendor directory. `cargo metadata --no-deps` lists these packages:
|
||
|
||
- application: `wordcraft` (`apps/wordcraft`, native eframe app), `wordcraft-web`
|
||
(`apps/wordcraft-web`, wasm eframe app), `wordcraft-cli` (no egui dependency).
|
||
- UI: `wordcraft-ui-egui` (`crates/ui-egui`, the only direct egui/eframe consumer in the
|
||
workspace). It also depends on `craft-ui` for print-page controls and on `egui_kittest` as a
|
||
dev-dependency.
|
||
- lower layers and stand-alone crates: `wordcraft-{geom,doc,fonts,layout,docx,docbin,formats,
|
||
render,pdf,proof,engine,mcp,zotero,control-key,metafile}`. None may acquire an egui dependency;
|
||
`cargo xtask layers` enforces this (`xtask/src/layers.rs`, `UI_CRATES`).
|
||
- `xtask` itself has no egui dependency.
|
||
|
||
Direct egui-family declarations are only:
|
||
|
||
| Manifest | Declaration | Notes |
|
||
|---|---|---|
|
||
| root `Cargo.toml` | `egui = "0.36"`, `egui_extras = { version = "0.36", default-features = false }`, `eframe = { version = "0.36", default-features = false }`, `egui_kittest = "0.36"` | `egui_extras` is currently unused by source; remove it or pin it deliberately. |
|
||
| `crates/ui-egui/Cargo.toml` | `egui = { workspace = true }`; dev: `egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }` | `egui` is not given `default_fonts` here; production gets it from the app roots' eframe feature, while the UI test root must declare egui's font features itself because kittest's own dev-dependencies do not propagate. |
|
||
| `apps/wordcraft/Cargo.toml` | `eframe` with `default_fonts`, `wgpu`, `accesskit`, `wayland`, `x11`, `links`; `egui`; Linux/BSD `winit = "0.30"` with `x11` | `eframe` is the native renderer; S1 adds `clipboard` to preserve 0.36’s integration-forced OS text clipboard. |
|
||
| `apps/wordcraft-web/Cargo.toml` | `egui`; wasm `eframe` with `default_fonts`, `wgpu`, `accesskit` | web runner and browser download/inbox shell. |
|
||
|
||
`craft-libs` is pinned in two places: `craft-ui` and `craft-print` at
|
||
`d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d`, and `craft-pdf` at
|
||
`c3487320b2d1de657b860b3c8abb44e1f2b32bd8`. `craft-ui` and `craft-print` share public print
|
||
types; `craft-pdf` does not share those types. Do not churn `craft-pdf` just to make revision
|
||
strings look uniform.
|
||
|
||
### 2.2 Entry points and examples
|
||
|
||
- Native: `apps/wordcraft/src/main.rs`. `impl eframe::App for App` at `main.rs:44` implements
|
||
`logic`, `raw_input_hook`, `ui` and `on_exit` (`main.rs:45-120`). `NativeOptions` and the
|
||
winit/platform setup are at `main.rs:376-420`; Windows wgpu backend/probe code is
|
||
`apps/wordcraft/src/graphics.rs:26-180`.
|
||
- Web: `apps/wordcraft-web/src/web.rs`. `WebShell` implements `eframe::App` at `web.rs:117`;
|
||
`logic` drains browser drops/messages and calls `WordApp::logic`, `ui` calls `WordApp::ui`;
|
||
`WebOptions`/WebGPU-WebGL2 selection is at `web.rs:35-65`.
|
||
- UI library: `WordApp::logic` at `crates/ui-egui/src/lib.rs:2087`, `WordApp::ui` at
|
||
`lib.rs:2353`, `raw_input_hook` at `lib.rs:2325`.
|
||
- Headless screenshot example: `crates/ui-egui/examples/ui_shot.rs` (1440x900 at 2x by default,
|
||
`wgpu` harness, `--sample`, control JSON lines).
|
||
- Performance probe: `crates/ui-egui/examples/perf_probe.rs` (open, typing, wheel/trackpad
|
||
scrolling, zoom, ribbon tabs, resize, accept-all; returns medians, p95 and max, not p99).
|
||
- Large-table benchmark: `crates/ui-egui/src/tests_perf.rs` (`big_table_scroll_bench`, ignored,
|
||
2000x8 synthetic table by default, `WORDCRAFT_BENCH_DOCX` override).
|
||
- Control protocol: `crates/ui-egui/src/control.rs`, documented in `docs/control-protocol.md`.
|
||
`ui.screenshot`/`ui.render` reply shapes, request ids, timeouts and paths are public contracts.
|
||
|
||
### 2.3 Platform configuration
|
||
|
||
Relevant cfgs are all in the UI/app boundary, not in lower layers:
|
||
|
||
- `cfg(target_os = "linux")`: `zbus = "=5.19.0"` in `crates/ui-egui/Cargo.toml`;
|
||
`desktop_theme.rs` reads the desktop portal. `system_clipboard.rs` and
|
||
`apps/wordcraft/src/clipboard.rs` use `wl-clipboard-rs` for the **regular** Wayland
|
||
clipboard (formats/text/pictures); they do not read or write PRIMARY
|
||
(`ClipboardType::Regular` only, `clipboard.rs:135-160`). Middle-click on the canvas is
|
||
WordCraft's autoscroll, not a PRIMARY paste (`autoscroll.rs:1-10`).
|
||
- `cfg(all(unix, not(target_os = "macos")))`: app `winit` X11; XWayland selection is in
|
||
`main.rs:404-413`.
|
||
- `cfg(target_os = "macos")`: `fmv-macos-events`, `objc2-app-kit`, `dispatch2`; termination
|
||
workaround `schedule_macos_terminate` at `main.rs:121-160`; AppKit pasteboard additions in
|
||
`apps/wordcraft/src/clipboard.rs`.
|
||
- `cfg(target_os = "windows")`: `open`, `windows` 0.62, `clipboard-win`; wgpu DX12/FXC and
|
||
backend probe in `apps/wordcraft/src/graphics.rs`.
|
||
- `cfg(target_arch = "wasm32")`: `js-sys`, `web-sys`, browser inbox, `font_store.rs`, browser
|
||
downloads; `sys-locale` with `js`.
|
||
|
||
No code uses child viewports (`show_viewport_deferred`/`show_viewport_immediate`), `craft-ui`
|
||
docking, or `craft-window`. The app's "windows" are `egui::Window` areas inside the single root
|
||
viewport. A viewport-focused migration is therefore not triggered by this app; the one root
|
||
viewport still matters for screenshots, DPI and wgpu surface limits.
|
||
|
||
## 3. Dependency target and publication constraints
|
||
|
||
### 3.1 Exact coherent target
|
||
|
||
All egui-family crates must resolve from one immutable fork revision, not merely from
|
||
`version = "0.37.0"`. The target family is:
|
||
|
||
| Crate | Target | Source |
|
||
|---|---|---|
|
||
| `egui`, `eframe`, `egui-winit`, `egui-wgpu`, `egui_glow`, `egui_kittest`, `egui_extras`, `epaint`, `emath`, `ecolor`, `epaint_default_fonts`, `egui_system_font_provider` | 0.37.0 | the approved, published, reachable `storytold/egui` P8 release containing the complete series: P2b async W15P routing, P7 with held-key release pairing, P7b/E21b, P7c's selected #8763/#8748 fixes and justified performance fixes; the exact SHA is assigned only after review/publication. Research input: aiongg `a4f9a17f64a6bae61350fb85804c9df0d9865916` (`ai-0.37` in an `emilk/egui` clone). |
|
||
| `winit` | 0.30.13 | the completed, approved, published, reachable `storytold/winit` revision containing W1-W15 minus W5, plus W17-W27 including W20 as W5’s replacement including the W27 lifecycle revision, and W15P PRIMARY; exact SHA assigned after review/publication. Research input: aiongg `8b35388bd719e8cc86a95971ba0bc026c9a8f058` (`ai-lang`). |
|
||
| `accesskit` / `accesskit_winit` | 0.25.x / 0.34.x | registry, resolved by the lockfile |
|
||
| `kittest` | 0.5.0 | registry, via `egui_kittest` 0.37 |
|
||
| `craft-ui`, `craft-print` | the published craft-libs revision containing CL-1 and the CL-2F frozen helper containing the reviewed PR 199 port on current main | exact git `rev`, not a branch; it must move in the same atomic dependency stage as the egui/winit pins |
|
||
| `craft-pdf` | existing `c3487320...` unless a separate reviewed reason appears | exact git `rev` |
|
||
|
||
Required prerequisites, all in the approved revisions above:
|
||
|
||
- **E4/P2b + W15P:** the complete shared contract in `egui` plan §6 E4/P2b and winit plan
|
||
§5.3.1-5.3.2. Claim selection synchronously on a fresh **pointer or keyboard** triggering
|
||
serial for the focused seat, with origin and epoch; only serving/reading is async, using the
|
||
total-deadline (2 s), byte (16 MiB), worker (4 readers, 2 writers, queue 8) bounds. A live
|
||
per-owner completion waker routes only to the still-current request; canceled/stale results
|
||
are discarded. WordCraft's application clipboard stays regular-only; built-in `TextEdit`
|
||
PRIMARY is tested only where the feature graph enables it.
|
||
- **E21b/P7b + winit W27 lifecycle:** the complete shared contract in `egui` plan §6 E21b and
|
||
winit plan §5.2.3/§5.2.2, not a partial restatement. It includes the owner/context generation
|
||
on both Available and Unavailable, distinct explicitly-sent empty Available payloads, cache
|
||
clearing plus a committed disable/enable transition on owner/unavailable changes, the latest
|
||
completed pass (including `None`) as authoritative, and current-epoch serial-mismatch
|
||
handling that applies incoming edits once while deferring outgoing state. WordCraft's two
|
||
canvas producers need real model adapters.
|
||
- **P7c selected UI-regression fixes:** the fork owns the selected #8763 click-away selection
|
||
fix and #8748 hidden-parent/child fix, with pointer and keyboard regressions. #8764 remains
|
||
only a proposal input. WordCraft has no child viewports, so #8748 is a dependency-series
|
||
gate rather than app behavior, but no app workaround substitutes for P7c.
|
||
|
||
The root `[patch.crates-io]` must contain every crate whose crates.io identity would otherwise
|
||
pull a second egui/winit. A dependency's own `[patch]` table is ignored when it is a dependency,
|
||
so the wordcraft root must repeat the patches. At minimum: `egui`, `eframe`, `egui-winit`,
|
||
`egui-wgpu`, `egui_glow`, `egui_kittest`, `egui_extras`, `epaint`, `emath`, `ecolor`,
|
||
`epaint_default_fonts`, `egui_system_font_provider`, and `winit`. Keep the existing
|
||
`krilla` patch only because `craft-pdf` requires it; it is unrelated to this migration.
|
||
The published manifest must not contain any absolute path patch, branch, `path = "/home/..."`,
|
||
or machine-local checkout.
|
||
|
||
Use exact requirements in the workspace (`=0.37.0`, `=0.30.13`) so that removing a patch cannot
|
||
silently float to a different registry release. The resulting `Cargo.lock` must contain exactly
|
||
one `egui`, `epaint`, `emath`, `ecolor`, `eframe`, `egui-winit`, `egui-wgpu` and `winit` package,
|
||
all with the intended `git+...#<sha>` source and no `path` source.
|
||
|
||
### 3.2 Existing shared work
|
||
|
||
- **Current shared-library integration base:** `3832c47ef68a0e6214f2c1e85f3526b6adcb686e`,
|
||
the reconciled merge base that carries the reviewed 0.37 API port of `craft-ui`. It is the
|
||
base for this migration; the exact reachable immutable publication pin remains a
|
||
central-publication prerequisite and is not guessed here. The historical PR 199 head
|
||
`b68de8c5f4f1c060904d211a7b1320aa0f7bce78` and audit merge
|
||
`ec8840af8c5fda695cf195cdd05e5ae078415c07` are evidence only and must not be merged as
|
||
trees: they omit current `craft-platform` and revert security, settings, docking and printer
|
||
fixes.
|
||
- **craft-libs PR 199** (historical). Its API port — `WidgetType` to `accesskit::Role`,
|
||
`UiBuilder::id`/`Ui::id` to `scope_id`, `Id::new` to `Id::unique`,
|
||
`egui_extras`/`egui_kittest` 0.37, and the missing-glyph docking-fixture change — is already
|
||
contained in the `3832c47…` base. The historical API-port hunks from
|
||
`8614e4a04fae516fba16c22b31ff1008cb6be10b` and the relevant README changes from `1764bca`
|
||
on the earlier `49190884278f16c9a9f5823d7698a8ffb5deb062` main describe how that base was
|
||
assembled; they are no longer the base to apply. Preserve intervening fixes and provenance,
|
||
and re-run the actual gates; neither PR 199 nor historical checks qualify the resulting
|
||
tree. WordCraft pins the published CL-1 plus CL-2F revision in the same atomic dependency
|
||
stage as the egui/winit pins (section 9), never after a 0.37-only first step.
|
||
- **craft-libs #192 / print types** already landed on the path to PR 199: `craft-ui::printing::Printer`
|
||
gains `label`; `craft-print::Printer` gains `description`. WordCraft must adapt when it moves
|
||
its `craft-ui`/`craft-print` pin:
|
||
- `apps/wordcraft/src/print.rs:8` builds `printing::Printer` from
|
||
`craft_print::printers()`; add `label: printer.description`.
|
||
- `crates/ui-egui/src/lib.rs:6038` (test) constructs `Printer { name, default }`; add
|
||
`label: String::new()` or `..Default::default()`.
|
||
- `crates/ui-egui/src/printing.rs` continues to re-export the shared types and labels.
|
||
- **craft-libs #198** is a language-support proposal/issue, not a merged dependency. Its
|
||
requirements that matter to WordCraft are already represented in fork patches E5/E12/E13/E16/E17/
|
||
E19/E21: logical text at OS/accessibility boundaries, no double bidi, IME replacement by bytes,
|
||
and Latin shortcut keys on non-Latin layouts. Do not import the proposal's locale/formatting
|
||
roadmap or any prohibited font asset into this migration.
|
||
|
||
### 3.3 Fork patch provenance and decisions
|
||
|
||
The fork is a superset of 0.37.0 plus the suite patches documented in its `PATCHES.md`. The
|
||
completed P8 revision retains the whole series; do not delete unrelated patches to reduce the
|
||
diff. WordCraft consumes only that published P8 revision, never the aiongg candidate or an
|
||
intermediate stage. The WordCraft-relevant patches are:
|
||
|
||
| Patch | Relevance to WordCraft | Decision |
|
||
|---|---|---|
|
||
| E1 eframe viewport hooks | No child viewports today | Retain; do not adopt. |
|
||
| E2+E3 egui-wgpu surface fit/error scopes | Protects large windows and device setup | Retain; test large/high-DPI window and device-error paths. |
|
||
| E4 egui-winit Wayland clipboard | WordCraft reads the regular clipboard through arboard/wl-clipboard-rs; E4 changes egui-winit's own path | Required fork prerequisite with the complete P2b/W15P contract (synchronous pointer-or-keyboard claim, bounded async transfer); test the app's regular clipboard separately and do not assume E4 replaces WordCraft's HTML/RTF/MathML path. Do not add app-level PRIMARY paste. |
|
||
| E5/E12/E16 bidi shaping, direction, caret affinity | Replaces WordCraft's manual `bidi_display`/`visual_row` workarounds | Required. Remove double processing; use logical text and fork layout. |
|
||
| E6 IME focus request | Text fields and canvas IME | Retain; test preedit/focus. |
|
||
| E7/E19 Latin keys on non-Latin layouts | App shortcuts use `Event::Key.key`; fork supplies Latin layout characters | Required for shortcut parity; test Russian/Arabic/Hebrew layouts. |
|
||
| E8 TextEdit glyph overshoot | Dialogs and custom combo fields | Retain; visual test combining marks/Thai/Vietnamese. |
|
||
| E9 RTL UI mirroring | WordCraft has no RTL interface catalog yet | Retain as capability; do not enable a half-mirrored UI without a locale. |
|
||
| E10 web named keys/language | Browser shortcuts and hidden input | Required for web; test browser workflow. |
|
||
| E11 vello single-threaded glyph rasterization | WordCraft's page renderer is separate; epaint UI glyphs still use it | Retain; no app change unless `vello_cpu` feature use is introduced. |
|
||
| E13 grapheme editing | Fork TextEdit/caret behavior; app's document editing is engine-owned | Retain; ensure no duplicate grapheme handling; test emoji/combining. |
|
||
| E14 fallback baseline | CJK/Indic UI fallbacks | Retain; test CJK/Indic interface text. |
|
||
| E15 line breaker | egui UI text (ribbon, dialogs); document layout is app-owned | Retain; do not move document layout to epaint. |
|
||
| E17 language on Context/Ui | Accessibility language and widget text | Adopt `Context::set_language(Some(lang.code()))`; optionally tag document/canvas content with the editing language. |
|
||
| E18 eframe UI language | Could replace app-side `Frame` language plumbing | Evaluate; not required. |
|
||
| E20 widget text language | Dialog/ribbon text layout | Retain; test CJK/Thai line breaking. |
|
||
| E21 `ImeEvent::DeleteSurroundingBytes` | Desktop macOS/Wayland IME replacement | Required. Add a byte-range path alongside the existing char-count web path. |
|
||
| E21b/P7b outgoing IME surrounding text | Canvas/Wayland surrounding-text support; the candidate has incoming deletion only | Required fork prerequisite under the complete E21b/W27 contract (owner generation, empty-Available, serial/epoch rules). TextEdit producer and W27 adapter; both WordCraft canvas producers need real model/preedit/UTF-8 adapters. |
|
||
|
||
Winit provenance: the final pin must be the completed, approved, published `storytold/winit`
|
||
revision containing W1-W15 minus W5, plus W17-W27 including W20 as W5’s replacement including the W27 lifecycle revision, and the
|
||
W15P PRIMARY path. The inspected `8b35388` `ai-lang` commit is an aiongg research input, not a production
|
||
pin. WordCraft's existing issue references (#787, #721 and related IME/DPI fixes) depend on
|
||
W1-W15; do not compare only against upstream 0.30.13 and conclude the patches are new. The patch
|
||
table records the removal condition for each. No wordcraft vendor directory exists, so no vendor
|
||
deletion is part of this plan.
|
||
|
||
### 3.4 Pin and lock checks
|
||
|
||
Proposed checks, to run through the managed `task.py` workflow after the plans land:
|
||
|
||
1. `cargo metadata --locked --format-version 1` over the root and with
|
||
`--filter-platform wasm32-unknown-unknown` (`cargo metadata` has no `--target`); parse
|
||
package `source` and `version` and fail on any `path` source,
|
||
branch source, unknown git URL, duplicate egui/winit, or a `craft-ui`/`craft-print` pair from
|
||
different craft-libs revisions.
|
||
2. `cargo tree -d --workspace` and `cargo tree -i egui`, `cargo tree -i winit`; no duplicate
|
||
family member.
|
||
3. `cargo fetch --locked` from a clean clone, then a second `cargo metadata --locked`; no
|
||
network-only branch resolution.
|
||
4. A small `xtask` dependency gate (proposed name `cargo xtask deps`) with tests for: missing
|
||
root patch, two egui versions, old `craft-ui`, moving branch, absolute path, and a git SHA
|
||
that is not reachable from the approved fork ref. This turns the manual checks into a
|
||
regression.
|
||
5. Feature-identity checks, not just version metadata: `cargo tree -e features --target
|
||
wasm32-unknown-unknown -p wordcraft-web` and the native `-p wordcraft` tree must show the
|
||
expected `egui/default_fonts`, `egui-winit/clipboard` (required on native, with arboard resolved),
|
||
`accesskit`, `wgpu` and no `system_fonts` resolution for the app root. Record the exact
|
||
feature sets in the lock/evidence manifest.
|
||
6. Publication reachability: a clean clone must fetch the approved `storytold` revisions and
|
||
pass `--locked` without local overrides. S1 cannot start while the only available trees are
|
||
the aiongg research inputs; do not substitute a candidate SHA for a published pin.
|
||
7. Release/packaging check after the source migration: signed native builds and the web package
|
||
must use the same lock identity; no local overrides.
|
||
|
||
## 4. Concrete 0.37 API and behavior refactors
|
||
|
||
The historical compiler audit at
|
||
`/home/codex/work/ssd2/task-evidence/egui037-audit-20261022/{fork,stock}/wordcraft.errors`
|
||
lists 42 diagnostics, identical for stock and fork. They are clues, not current acceptance and
|
||
not an exhaustive list. The concrete fixes are:
|
||
|
||
### 4.1 `Event::MouseWheel` gains `source`
|
||
|
||
`egui::Event::MouseWheel` now has `source: MouseWheelSource` (0.37 `data/input/event.rs`).
|
||
Affected constructors and patterns:
|
||
|
||
- `crates/ui-egui/src/control.rs:247` (`ui.wheel` synthetic event): add
|
||
`source: egui::MouseWheelSource::Unknown`.
|
||
- `crates/ui-egui/src/canvas.rs:642` (pattern), `canvas.rs:3821` (test constructor).
|
||
- `crates/ui-egui/src/scroll.rs:180`, `:278`, `:329`, `:418`; `tests_perf.rs:39`;
|
||
`examples/perf_probe.rs:106`; `mini_toolbar.rs:779`; `dialogs_keyboard.rs:430`; and the
|
||
test constructors in `lib.rs`
|
||
(`:3772`, `:3817`, `:3924`, `:4131`, `:4277`, `:4332`, `:4397`, `:4522`, `:4821`, `:5018`,
|
||
`:6094`).
|
||
- Add `..` to patterns where the source is not used. For tests, use `Wheel` or `Trackpad` to
|
||
match the behavior under test; for the control channel use `Unknown`.
|
||
- Preserve `CanvasScroll` behavior: WordCraft disables egui's canvas wheel handling
|
||
(`ScrollArea::scroll_source { mouse_wheel: false }`) and computes its own delta. Do not start
|
||
feeding the same momentum twice. The app's `SYNTH_MOMENTUM` policy remains Linux/BSD-only;
|
||
macOS/Windows/Web already send their own momentum. Use `source` only for diagnostics/future
|
||
differentiation, not to change current deltas without a measured test.
|
||
|
||
### 4.2 `ScrollArea::State::offset` is private
|
||
|
||
Use the 0.37 accessors and builder:
|
||
|
||
- `crates/ui-egui/src/canvas.rs:1650`: `app.canvas.scroll_offset = out.state.clamped_offset();`
|
||
(the old public offset was the clamped value; `unclamped_offset()` is for painting/rubber-band
|
||
only).
|
||
- `crates/ui-egui/src/widgets.rs:1158`: `shown.state.clamped_offset().y`.
|
||
- `crates/ui-egui/src/scroll.rs:440` (test helper): `seen = (out.state.clamped_offset().y, zoom);`.
|
||
- `crates/ui-egui/src/ribbon.rs:543` and `:560`: read `scroll.state.clamped_offset().x`.
|
||
Setting `state.offset.x` is no longer possible. Store a one-frame pending horizontal offset
|
||
(in `ui.data_mut` keyed by the scroll area id, or a small `RibbonScroll` field) and build the
|
||
next frame with `egui::ScrollArea::horizontal().horizontal_scroll_offset(x)`. Clear the pending
|
||
value after it is consumed. This preserves the edge-chevron behavior without touching private
|
||
state.
|
||
- `crates/ui-egui/src/canvas.rs:1173-1205` already uses `.scroll_offset(after)`; keep that
|
||
builder path. The scroll maximum remains the app's own `content_size - inner_rect.size()`.
|
||
|
||
Verify the sweep is exhaustive: the only `state.offset` reads/writes in `crates/ui-egui/src`
|
||
are `canvas.rs:1650`, `widgets.rs:1158`, `scroll.rs:440`, `ribbon.rs:543/560`, plus the builder
|
||
path at `canvas.rs:1173-1205`; no other direct field access remains. Regression: wheel notch and
|
||
trackpad tests assert the same offsets and page indices after the change; ribbon left/right
|
||
chevrons move exactly one step and clamp at both ends.
|
||
|
||
### 4.3 `WidgetType` becomes `accesskit::Role`
|
||
|
||
The app uses `egui::WidgetInfo::labeled/selected(WidgetType::...)` in 34 places. Replace with
|
||
`egui::Role` using the mapping egui itself documents (and craft-libs PR 199 uses):
|
||
|
||
| Old | New | Files |
|
||
|---|---|---|
|
||
| `Button` | `Role::Button` | `backstage.rs:79,127,234,524`; `widgets.rs:294,333,336,402,510,523,934,1256,1289`; `print_page.rs:290`; `format_shape.rs:439`; `previews.rs:231`; `chrome.rs:333,392,416,492,551,698`; `window_close.rs:60`; `ribbon.rs:355,467,696,2271` (Full Screen) |
|
||
| `SelectableLabel` (selected) | `Role::Button` with `selected` | `options.rs:150`; `previews.rs:530`; `chrome.rs:671`; `ribbon.rs:404` |
|
||
| `TextEdit` | `Role::TextInput` | `widgets.rs:878` |
|
||
| `Image` | `Role::Image` | `print_page.rs:278` |
|
||
| `Other` | `Role::Splitter` for the ruler drag grip, not `Unknown` | `table_ruler.rs:60` |
|
||
|
||
The `table_ruler` grip is a draggable column/row boundary; `Role::Splitter` is an input role and
|
||
matches its behavior. If a test insists on the old generic role, review whether that test was
|
||
asserting implementation rather than semantics; do not hide a real control under `Unknown`.
|
||
`Role::Unknown` is not an input in `egui::accessibility::is_input`, so `unnamed_widgets()` cannot
|
||
detect a missing/Unknown node. Add an explicit `get_by_role(Role::Splitter, ...)` assertion for
|
||
the grip; a mutation that removes its name must fail `unnamed_widgets()`, and a mutation that
|
||
changes the role to `Unknown` must fail the explicit role query.
|
||
|
||
`egui::accesskit::Role` remains available for tests; `egui::Role` is the re-export. Existing
|
||
`get_by_role_and_label(Role::Button, ...)` tests remain valid.
|
||
|
||
### 4.4 Screenshot command becomes a callback
|
||
|
||
0.37 removes `Event::Screenshot` and `UserData`; `ViewportCommand::Screenshot` carries a
|
||
`ScreenshotCallback`, and `Context::request_screenshot` is the convenience API. Current code:
|
||
|
||
- `crates/ui-egui/src/lib.rs:2577` `issue_screenshots` sends
|
||
`ViewportCommand::Screenshot(UserData::new(token))`.
|
||
- `crates/ui-egui/src/lib.rs:2593` `collect_screenshots` scans `egui::Event::Screenshot` and
|
||
returns immediately when `pending_shots` is empty.
|
||
- `crates/ui-egui/src/control.rs:312` `save_screenshot(&ColorImage, path)` encodes/writes.
|
||
|
||
Required design. WordCraft is one of the eleven `craft-ui` capture consumers and adopts the
|
||
shared helper only after CL-2F freezes it; it does not take the provisional CL-2 revision used
|
||
by the DesignCraft/GridCraft pilots. The callback contract is:
|
||
|
||
- Keep request admission, paths, reply schema, settling delay and the 8 s deadline in
|
||
`WordApp`. Each request record owns a strong completion slot and waker; the
|
||
`ScreenshotCallback` holds only a `Weak` reference to the slot and receives pixels only. It
|
||
must not capture `WordApp`, a `Context`, a file path, or an unbounded `mpsc::Sender`. The
|
||
callback stores the image, validates it, releases the lock, and only then invokes the live
|
||
slot's app-owned waker. It never calls `Context::request_repaint` itself and never wakes
|
||
while holding the lock; never strongly capture a context from a callback queued in that same
|
||
context.
|
||
- `Context::request_screenshot` itself schedules a repaint: it calls `send_viewport_cmd` ->
|
||
`send_viewport_cmd_to` -> `request_repaint_of`. Only callback *arrival* is documented not to
|
||
request a repaint. Do not claim the request is repaint-free; keep the existing
|
||
`request_repaint_after(16 ms)` while a request is pending and wake explicitly after
|
||
completion.
|
||
- Admit by count **and** bytes. Before issuing, estimate `width * height * 4` from the current
|
||
viewport logical content and pixels-per-point with checked arithmetic, then reserve its
|
||
working set (raw RGBA + worst-case PNG + base64, a 10/3 factor) in an in-flight budget that
|
||
also counts completed-but-undrained slots. Use the shared defaults: at most 4 outstanding
|
||
requests, 128 MiB reserved per image and 256 MiB aggregate; reject excess admission without
|
||
evicting earlier requests. A 4K viewport at 2x is about 7680x4320x4 ~= 127 MiB raw and is
|
||
therefore rejected by the working-set limit rather than silently exceeding the encode peak.
|
||
Revalidate the checked dimensions and geometry epoch immediately before issuance.
|
||
- Bind the image to the issuance-time UI geometry: logical content/screen bounds, size,
|
||
pixels-per-point, zoom and surface epoch. On completion, validate dimensions/byte length
|
||
against the reservation and that epoch. Any geometry/DPI/zoom change before save, **even a
|
||
change back**, cancels and retries within the deadline or returns an explicit stale-capture
|
||
error; never combine old pixels with current geometry. With an unchanged epoch, derive the
|
||
pixel scale from the returned image dimensions and verify aspect/rounding.
|
||
- `collect_screenshots` drains the slots unconditionally in `logic`, matches the request
|
||
record, calls `control::save_screenshot(&image, path.as_deref())`, sends exactly one reply,
|
||
and releases the slot and reservation. A timeout/cancel/drop removes the record and releases
|
||
the slot; a late callback then finds a dead weak reference and retains nothing, writes no
|
||
file and sends no second reply. Retained image bytes must return to zero after drain,
|
||
timeout, cancel and shutdown.
|
||
- Hidden windows: the screenshot command itself forces a full hidden `App::logic` + `App::ui`
|
||
pass (`ActionRequested::wants_paint()`), while callback arrival does not repaint. A request
|
||
queued in `logic` but issued only in `ui`, including every remaining settling frame, must
|
||
therefore send `ViewportCommand::RequestPaintWhileHidden` from the logic side to reach
|
||
issuance; a wake/repaint alone may run hidden logic only. Keep the timeout contract and test
|
||
both the forced pass and the queued-not-issued settling path.
|
||
- The app has one root viewport. If child viewports are added later, route requests by
|
||
`ViewportId` and generation; do not reuse an old token for a new document or viewport.
|
||
- `ui_shot` uses `Harness::render`, not the viewport callback; keep it as a separate headless
|
||
path. Do not make `ui.render` depend on the native callback.
|
||
- The suite has fourteen capture consumers. The eleven existing `craft-ui` consumers require
|
||
CL-1 and CL-2F; only the DesignCraft/GridCraft pilots use the reviewed CL-2 provisional
|
||
revision, and their adapters are reviewed before CL-2F freezes the API. DeckCraft, FigCraft
|
||
and ArtCraft Native keep contract-compatible local adapters; `ai-egui` has no capture path.
|
||
Do not add `craft-ui` solely for capture; if DeckCraft/FigCraft later adopt it, WordCraft's
|
||
adapter stays unchanged.
|
||
|
||
Tests: four-request/128 MiB/256 MiB admission, two outstanding requests with distinct
|
||
tokens, out-of-order completion, callback once and no self-wake, late callback after
|
||
timeout/cancel (no file/second reply), forced hidden pass, queued-not-issued
|
||
`RequestPaintWhileHidden`, resize/DPI/zoom change and change-back, and retained-bytes-zero
|
||
after drain/shutdown.
|
||
|
||
### 4.5 `Id::new` and `Ui::id` deprecations
|
||
|
||
0.37 deprecates `Id::new` in favor of `Id::unique` and renames `Ui::id` to `Ui::scope_id`
|
||
(`Ui::id` remains deprecated). Exact symbol-filtered counts in `crates/ui-egui/src` at this
|
||
baseline are 108 egui `Id::new` calls (89 `egui::Id::new` and 19 bare `Id::new`) and 37
|
||
`ui.id()` calls; `FontId::new` (3) and `LayerId::new` (3) are unrelated and must not be swept.
|
||
The mechanical count is a review aid, not a replacement trigger. Replace with same-hash
|
||
equivalents:
|
||
|
||
- `egui::Id::new(x)` -> `egui::Id::unique(x)`; hashes are intentionally unchanged for existing
|
||
ids, so widget state, focus and temp data keys stay stable.
|
||
- `ui.id()` -> `ui.scope_id()`; the value is unchanged.
|
||
- `UiBuilder::new().id(x)` -> `.scope_id(x)` if any is introduced (none currently in app code).
|
||
- Do not replace `Id::unique` with `IdSalt` for globally stored state; `IdSalt` is for child
|
||
scopes only. Preserve the distinction between stable scope ids and `ui.unique_id()`.
|
||
|
||
The mechanical sweep is large but must be reviewed by symbol, not regex alone: `ui.id()` on a
|
||
`Ui` is not the same as `TextureHandle::id()`, `CommandSpec::id()`, `Document::id()` or other
|
||
domain ids. The replacement applies only to egui `Ui`.
|
||
|
||
### 4.6 Panels take `AsIdSalt`
|
||
|
||
`Panel::left/right/top/bottom` in 0.37 take `impl AsIdSalt`, not `impl Into<Id>`; the panel id
|
||
is now `parent_scope.with(IdSalt)`. Existing string literals in `chrome.rs`, `panes.rs`,
|
||
`backstage.rs` and `ribbon.rs` compile, but their effective ids change. WordCraft does not persist
|
||
panel sizes in `ui.json` or enable egui `persistence`, so the only consequence is fresh in-process
|
||
panel state after upgrade. Verify:
|
||
|
||
- panel order remains title bar -> ribbon -> status -> side panes -> central canvas in
|
||
`WordApp::ui` (`lib.rs:2353-2410`);
|
||
- `Panel::top("title_bar")`, `Panel::top("ribbon")`, `Panel::bottom("status")`, the side panes in
|
||
`panes.rs:20-105`, and the `CentralPanel` reserve and clip correctly at 760x480, 1440x900 and
|
||
2x DPI;
|
||
- no panel is shown on a non-root `Ui` where 0.37 would change order. `backstage.rs:40-160`
|
||
shows a left panel and then a central panel on the passed root `Ui`; keep that contract.
|
||
- Do not use `Context::root_ui` inside `WordApp::ui`: 0.37 explicitly panics when the root `Ui`
|
||
is already borrowed (`Context::root_ui` docs, upstream #8535). Pass the existing `Ui`.
|
||
|
||
### 4.7 `default_fonts` is no longer a default feature
|
||
|
||
0.37 removes `default_fonts` from `egui`/`epaint` default features; `eframe` still enables it.
|
||
`egui_kittest`'s normal `[dependencies] egui` uses `default-features = false`; only kittest's
|
||
own `[dev-dependencies]` enable `default_fonts`/`monochrome_emoji_fonts`, and a dependency's
|
||
dev-dependencies do not propagate. Neither kittest's manifest nor its dev-dependency graph
|
||
supplies fonts to WordCraft's test root. WordCraft's `theme::font_definitions_language` starts from
|
||
`FontDefinitions::default()`, inserts `Inter` at index 0, pushes `SourceSans` at the end, then
|
||
appends CJK/system faces (`theme.rs:536-620`). With `default_fonts` the default proportional
|
||
family is `["Ubuntu-Light", "egui-icons"]`; with `monochrome_emoji_fonts` it is
|
||
`["Ubuntu-Light", "egui-icons", "NotoEmoji-Regular", "emoji-icon-font"]`.
|
||
|
||
The actual production order is therefore:
|
||
`Inter, Ubuntu-Light, egui-icons, SourceSans, <CJK/system faces>`.
|
||
Explicitly enable `default_fonts` and `monochrome_emoji_fonts` on the UI test root's egui
|
||
dev dependency for the controlled emoji fixture; its expected order is:
|
||
`Inter, Ubuntu-Light, egui-icons, NotoEmoji-Regular, emoji-icon-font, SourceSans,
|
||
<CJK/system faces>`.
|
||
Do not assert the earlier proposed `Inter, SourceSans, Ubuntu-Light, egui-icons` order; it is
|
||
wrong and would force an unintended fallback reorder.
|
||
|
||
Concrete fix:
|
||
|
||
- Keep eframe's `default_fonts` feature in both app manifests.
|
||
- Make `wordcraft-ui-egui` test features explicit with `egui` dev-dependency features
|
||
`default_fonts` and `monochrome_emoji_fonts`; these are egui features, not kittest features.
|
||
Do not rely on kittest's private dev-dependencies: without this explicit declaration the
|
||
test root is font-empty apart from WordCraft's own assets. Do not enable
|
||
`monochrome_emoji_fonts` in production just to make the test order match.
|
||
- Add a font coverage test that asserts the exact prefix/position for the feature set under
|
||
test: `Inter` at 0, `Ubuntu-Light` and `egui-icons` in the default fallback block,
|
||
`SourceSans` immediately after that block, then CJK/system faces. Assert `has_glyphs` for the
|
||
interface catalog's punctuation/symbol strings, `icons.rs` labels, Arabic/Hebrew, CJK,
|
||
Thai/Vietnamese combining marks and only emoji the chosen UI font actually covers.
|
||
- Choose the emoji source explicitly: interface emoji coverage is the bundled monochrome
|
||
`egui-icons` set (plus the test-only `NotoEmoji-Regular`/`emoji-icon-font` fallbacks from
|
||
`monochrome_emoji_fonts`). The production UI family has no color emoji font; do not assert
|
||
color/ZWJ emoji in the interface test. For document/canvas emoji, use the pinned test-only
|
||
Noto Color Emoji v2.047 fixture from egui plan 10.6 (`39ee3c58...`, OFL-1.1) or record a
|
||
native platform color-font result separately. Do not bundle a proprietary emoji font.
|
||
- Keep `system_fonts` off; it would add a system provider and startup discovery cost (upstream
|
||
#8512), change coverage and complicate reproducibility.
|
||
|
||
The strict accessible-name sweep and this font/glyph work are part of S1, not deferred to the later screenshot stage:
|
||
`egui_kittest` 0.37's `check_accessibility` defaults to true and panics on unnamed visible
|
||
inputs after `Harness::run`, so S1 cannot leave `cargo xtask ci` green without them.
|
||
|
||
### 4.8 kittest 0.37 stricter defaults
|
||
|
||
`egui_kittest` 0.37 fails tests when an input widget has no accessible name and panics on a glyph
|
||
no installed font can draw. WordCraft has many custom-drawn controls and many `TextEdit`s whose
|
||
adjacent labels are not attached. The migration must not disable these checks globally.
|
||
|
||
- First run the UI suite with defaults; enumerate `Harness::unnamed_widgets()`.
|
||
- Attach names with `Response::labelled_by(label.id)` where a visible label exists, or
|
||
`Response::accessible_name(...)` for icon-only controls. Prefer visible-label association over
|
||
duplicating translated strings. `Ui::label_inputs_by`/`Ui::name_inputs` are available in 0.37
|
||
for groups.
|
||
- Audit the dialog modules: `dialogs_controls.rs`, `dialogs_review.rs`, `dialogs_layout.rs`,
|
||
`dialogs_insert.rs`, `dialogs_para.rs`, `dialogs_lists.rs`, `dialogs_table_style.rs`,
|
||
`options.rs`, `backstage.rs`, `colors.rs`, `print_page.rs`, and the combo field in
|
||
`widgets.rs:842-878`. A `TextEdit` with a `hint_text` passes the check, but a visible label is
|
||
better semantics; do not add hints merely to satisfy the test.
|
||
- Keep `Role::Button`/`Role::TextInput` accurate; a named widget with the wrong role is still a
|
||
screen-reader regression. The a11y tree in `a11y.rs` (Document/paragraph/table/text-run) must
|
||
keep its current logical text; the fork's bidi changes must not leak visual-order strings into
|
||
AccessKit.
|
||
- This is API compatibility, not behavior, so the name sweep and font coverage are part of S1.
|
||
The suite has 15 `h.run()` call sites and 153 harness constructions; most tests use
|
||
`step`/`run_steps`, where the default check does not fire. Add explicit
|
||
`h.check_accessibility()`/`unnamed_widgets()` assertions where the UI settles, and add explicit
|
||
role queries for controls such as the ruler grip. `Role::Unknown` is not an input and cannot
|
||
detect a missing node.
|
||
|
||
### 4.9 `craft-ui`/`craft-print` print type change
|
||
|
||
Already described in 3.2. Add a test that discovery maps CUPS `description` to `label`, and that
|
||
`shown_names` distinguishes two printers with the same label. Keep the app's command ids and
|
||
print settings schema unchanged.
|
||
|
||
### 4.10 Text: remove double bidi and handle byte IME replacement
|
||
|
||
This is the highest-risk app-specific change. WordCraft manually reorders right-to-left text for
|
||
egui 0.36:
|
||
|
||
- `crates/ui-egui/src/canvas.rs:2132-2185` `has_rtl`/`bidi_display`/`visual_row`, used by
|
||
`backstage.rs:529-530` for recent file names.
|
||
- `crates/ui-egui/src/canvas.rs:2190-2245` `balloon_text` wraps and visually reorders comment
|
||
text itself, then right-aligns RTL rows.
|
||
- `crates/ui-egui/src/keys.rs:352-390` handles `ImeEvent::Preedit/Commit/DeleteSurrounding`
|
||
(char counts) and `keys.rs:626-680` implements `delete_surrounding` and `type_text`.
|
||
- `apps/wordcraft/src/clipboard.rs` and `crates/ui-egui/src/a11y.rs` use logical text at the
|
||
OS/accessibility boundary; they must stay logical.
|
||
|
||
With the fork's E5/E12/E16, egui shapes RTL runs and places rows in visual order. Keeping
|
||
`bidi_display` would reverse the text a second time. Refactor:
|
||
|
||
- Replace `bidi_display(name/path)` calls in `backstage.rs:529-530` with the original logical
|
||
strings. Set `LayoutJob`/widget direction only where a specific element needs an explicit base
|
||
direction; for ordinary labels, let `TextDirection::Auto` choose from the first strong
|
||
character.
|
||
- Replace the manual wrapping/reordering in `balloon_text` with an egui `LayoutJob` over logical
|
||
text, `TextDirection::Auto`, the existing font/color and `TextWrapping::max_width = width`.
|
||
Do not use `.trim()` on each visual row; let epaint handle trailing whitespace and line breaks.
|
||
If a right-aligned RTL paragraph needs an explicit `halign`, use the fork's
|
||
`halign_min_is_start`/`TextDirection` controls, not pre-reversed strings.
|
||
- Keep `has_rtl` only if a non-layout decision needs it; otherwise remove the dead helper and its
|
||
tests. Replace the old `visual_row` tests with tests that assert screen x positions of the first
|
||
and last Arabic/Hebrew words (fork behavior), not the old reordered strings.
|
||
- Keep document layout (`crates/layout`) untouched: it has its own bidi/line-break logic and is
|
||
not egui text. This migration only changes egui UI text and comment balloons.
|
||
- Add `Context::set_language(Some(lang.code().to_owned()))` in `WordApp::logic` when the effective
|
||
interface language changes, so AccessKit exposes the UI language. Optionally tag the canvas
|
||
document `Ui`/AccessKit node with the document editing language via `UiBuilder::language`; do
|
||
not tag every text run with a guessed language.
|
||
|
||
IME byte replacement:
|
||
|
||
- The fork adds `ImeEvent::DeleteSurroundingBytes { before_bytes, after_bytes }` on macOS and
|
||
Wayland, sent immediately before the `Commit`. The existing char-count
|
||
`DeleteSurrounding` remains for web/legacy paths.
|
||
- Add a match arm next to `keys.rs:384` that:
|
||
1. returns early if a selection exists or an equation is open, as the char path does;
|
||
2. computes `start = off.saturating_sub(before_bytes)` and
|
||
`end = off.saturating_add(after_bytes)` and verifies both are UTF-8 boundaries in the
|
||
paragraph text; if not, refuse the event rather than slice at a non-boundary;
|
||
3. returns immediately when `start == end` (the existing char path does, `keys.rs:656`);
|
||
otherwise selects `start..end`, joins the current typing group, runs `text.backspace` (or
|
||
the same delete command used by the engine), records `ime_join_text` for the following
|
||
commit, and restores the caret if the command is refused;
|
||
4. does not erase paragraph marks/list labels beyond the paragraph.
|
||
- Keep one undo step for delete+commit. Extend the existing `#1480` tests and add:
|
||
emoji modifier/ZWJ replacement, combining Vietnamese accent replacement, a letter after an
|
||
emoji sequence, a deletion-only batch, a `0/0` and clamped-empty byte range, and a commit
|
||
with empty text. Test that outgoing
|
||
surrounding text (document text after the operation) is updated, not merely the event mapping.
|
||
|
||
**E21b/P7b outgoing surrounding text (shared fork prerequisite).** The candidate egui-winit
|
||
never calls `WindowExtWayland::set_ime_surrounding_text`, and `egui::IMEOutput` has no logical
|
||
text (`data/output.rs:100-114`). E21's incoming delete/commit is not full surrounding-text
|
||
support. The complete contract is owned by `egui` plan §6 E21b and winit plan §5.2.3/§5.2.2;
|
||
WordCraft depends on it and does not restate a weaker subset. It provides:
|
||
an owner/context generation present for both Available and Unavailable; a valid empty document
|
||
as Available, explicitly sent as `set_surrounding_text("", 0, 0)` plus commit, distinct from
|
||
Unavailable; a bounded transient payload (logical committed text, UTF-8 byte cursor/anchor,
|
||
preedit excluded, at most 4,000 bytes, explicit unavailable when the selection cannot fit) with
|
||
no full-document allocation for the new payload; W27 cache clearing plus a committed
|
||
disable/enable boundary on owner/support transition; the latest completed pass's descriptor,
|
||
including authoritative `None`, as the only published value; and current-epoch `done` serial
|
||
mismatch applying incoming edits once while deferring outgoing state until a matching serial,
|
||
with obsolete epochs invalidated.
|
||
|
||
Both WordCraft canvas producers (`crates/ui-egui/src/canvas.rs:1556-1561` math/equation caret
|
||
and `:1633-1638` text caret) need real model adapters, not `None`: derive the bounded logical
|
||
paragraph/equation excerpt and byte cursor/anchor from `session.sel`/the equation editor,
|
||
convert scalar/grapheme positions deliberately, exclude `ime_preedit`/`ime_preedit_range`,
|
||
and never send display-reordered bidi text. The adapter updates on commit/delete/selection/
|
||
focus changes and clears on focus loss, password/no-context and widget switch. Existing
|
||
TextEdit buffer cloning is baseline behavior, not part of the new payload allocation.
|
||
|
||
Tests: outgoing text after delete+commit and deletion-only batches; preedit exclusion and caret
|
||
movement; supplementary emoji/combining/RTL logical order; long selection/window limits;
|
||
empty→non-empty and unavailable/password→available; late `Done` and current-epoch serial
|
||
mismatch vs stale epoch; same-window field switch and two windows; a final pass with `None`;
|
||
focus loss/reset/password/no-context clearing; and a native Wayland text-input trace on a real
|
||
compositor. A kittest event test is not native IME proof.
|
||
|
||
Do not make the document engine depend on egui's grapheme implementation. The engine's own
|
||
grapheme/backspace rules remain the source of truth for document editing.
|
||
|
||
### 4.11 Accessibility roles, names and selection
|
||
|
||
- Keep the custom AccessKit document tree in `a11y.rs` (Document, Paragraph, Heading, List,
|
||
ListItem, ListMarker, Table/Row/Cell, TextRun, Image, Link). The fork's bidi must not change
|
||
the logical `TextRun` strings; `set_text_direction` remains based on the layout's `line.rtl`.
|
||
- Ensure every new `Role` assignment has a name. The kittest default check will catch unnamed
|
||
input roles that `is_input` recognizes (`Button`, `TextInput`, `Splitter`, `ComboBox`, ...).
|
||
- `refocus_after_chrome_click` (`lib.rs:2471-2520`) relies on `ctx.memory().focused()` and
|
||
`ctx.cumulative_pass_nr()`. 0.37's `TextEdit` can lose its selection before a clicked widget
|
||
runs. P7c owns the selected #8763 and #8748 fixes; #8764 is an open, unmerged proposal input,
|
||
not the dependency. This is a blocking fork prerequisite, not accepted behavior. The
|
||
approved P8 revision must contain the central fixes with pointer and keyboard tests proving
|
||
the intended range reaches the adjacent action in the same pass.
|
||
- `Role::Splitter` on the ruler grip must not make a passive ruler tick an input; only the grip
|
||
gets the role.
|
||
|
||
### 4.12 GPU, surface and texture behavior
|
||
|
||
WordCraft uses wgpu 30 on native and WebGPU/WebGL2 on web; `graphics.rs` configures Windows
|
||
DX12/FXC and the Intel-Mac indirect-validation skip. wgpu and winit versions do not change, so
|
||
those APIs should compile; revalidate anyway under the new eframe/egui-wgpu:
|
||
|
||
- `apps/wordcraft/src/graphics.rs:26-120` still reaches `WgpuSetup::CreateNew` and
|
||
`InstanceDescriptor`; confirm with `cargo check` on Windows.
|
||
- E2/E3 surface fit fixes should remove a class of large-window configure errors. Add a window
|
||
size/DPI test near the surface limit if hardware permits; otherwise record the unresolved
|
||
hardware gate.
|
||
- Page textures use `TextureOptions::LINEAR` (`canvas.rs:1261-1332`, `print_page.rs:128`,
|
||
`previews.rs`). Do not change filtering/wrap; the upstream #8736 shader cost is about
|
||
predictable filtering, not the app's sampling choice. If a renderer option is changed, test
|
||
the actual page pixels and color emoji, not just compile.
|
||
- Screenshot callback memory: `Arc<ColorImage>` may be large at 2x/4K. The count/byte
|
||
admission, in-flight reservation and completed-slot accounting are part of the design.
|
||
|
||
### 4.13 Clipboard and PRIMARY
|
||
|
||
The app's clipboard is host-owned (`apps/wordcraft/src/clipboard.rs`, `system_clipboard.rs`) and
|
||
supports text, HTML, RTF, MathML, pictures and format names. It uses `wl-clipboard-rs`
|
||
`ClipboardType::Regular` only; there is no application PRIMARY read/write, and middle-click on
|
||
the canvas is autoscroll, not paste. S1 enables native `eframe/clipboard` to preserve the
|
||
0.36 integration-forced OS text clipboard and the `Event::Paste` paths used by canvas and
|
||
dialog TextEdits. This does not add PRIMARY to the app’s canvas or change rich-format
|
||
ownership. Do not route `Event::PasteImage` into a new app-level paste path. Verify:
|
||
|
||
- copy/cut/paste text and formatting through the app's commands, not only egui `TextEdit`;
|
||
- regular Wayland clipboard behavior with `wl-clipboard-rs` and `arboard` after E4/W15, while
|
||
W15P/P2b PRIMARY remains a fork-level prerequisite and is not added to the app's own paste
|
||
path;
|
||
- native `egui-winit/clipboard` is enabled and built-in TextEdit PRIMARY follows W15P;
|
||
- paste text from an independent process into the canvas and a dialog/search TextEdit,
|
||
then copy back to the other process; an in-process fallback must fail this regression;
|
||
- no double clipboard owner or last-writer race on X11/Wayland when both egui and the app write;
|
||
- macOS/Windows native pasteboard formats (HTML, MathML, RTF) unchanged;
|
||
- browser paste picture path in `apps/wordcraft-web/src/web.rs:160-220` still receives events;
|
||
- a regression that middle-click on the canvas starts autoscroll and never pastes PRIMARY.
|
||
|
||
## 5. Lifecycle, root `Ui`, hidden windows and repeated passes
|
||
|
||
### 5.1 What eframe does
|
||
|
||
In 0.37, `eframe` calls `App::logic` and `App::ui` inside `Context::run_ui` for every pass on
|
||
native and web (`crates/eframe/src/native/epi_integration.rs:349-365` and
|
||
`crates/eframe/src/web/app_runner.rs` in the target source). `Context::run_ui` may run the
|
||
closure more than once when a pass calls `request_discard` (default `max_passes = 2`).
|
||
`new_input.take()` gives events/dropped files to the first pass only. A hidden window normally
|
||
goes through `update_logic_only` -> `Context::run_logic`, which updates only viewport/focus
|
||
state and leaves events/time/layout from the previous UI pass. However a pending
|
||
`ViewportCommand::Screenshot` or `RequestPaintWhileHidden` makes eframe run a full hidden pass
|
||
(`is_visible = viewport.info.visible().unwrap_or(true) ||
|
||
actions_requested.any(ActionRequested::wants_paint)`), so `ViewportInfo::visible()` is not a
|
||
phase indicator. After a normally completed `run_ui`, `current_pass_index()` is zero
|
||
(`end_pass` takes the output); do not use a stale nonzero index as evidence of hidden work.
|
||
This is the exact contract the WordCraft code must be audited against.
|
||
|
||
### 5.2 `logic` is currently mostly idempotent; fix the exceptions
|
||
|
||
`WordApp::logic` (`lib.rs:2087-2238`) drains channels (`drain_control`, `zotero::poll`,
|
||
`read_aloud::poll`, `poll_file_dialog`, `drain_inbox`, `drain_pasted`), ticks recovery/autosave,
|
||
issues/collects screenshots, and handles dropped files. Most drains are inherently once-only
|
||
because they `take`/`recv`. The audit must prove, not assume:
|
||
|
||
- **Dropped files are stale in hidden/forced logic passes.** `lib.rs:2208-2230` reads
|
||
`ctx.input(|i| i.raw.dropped_files.clone())` without taking. A hidden `run_logic` frame or a
|
||
forced hidden full pass can see the last shown frame's drops and open/insert them again.
|
||
The browser shell already takes the vector in `apps/wordcraft-web/src/web.rs:119`; native
|
||
`WordApp::logic` still clones it. Change `WordApp::logic` to
|
||
`ctx.input_mut(|i| std::mem::take(&mut i.raw.dropped_files))` and handle the drained vector.
|
||
Apply the same rule to any future input-derived queue read in `logic`; never poll a
|
||
prior pass's event twice.
|
||
- **SmartArt Tab** uses `ctx.input_mut(|i| i.consume_key(...))` (`lib.rs:2106-2118`); consuming
|
||
makes a repeated logic pass harmless. Keep that pattern.
|
||
- **Synthetic input** (`raw_input_hook`, `lib.rs:2325`) runs before each frame, not once per UI
|
||
pass, and can run on a hidden `update_logic_only` tick. Keep at most one unapplied synthetic
|
||
gesture step pending until a full `WordApp::ui` pass consumes it; repeated hidden logic ticks
|
||
must not inject the next step, and a press/release pair stays two ordered events, never
|
||
collapsed. When queued synthetic input needs a UI pass while hidden, request paint through
|
||
the same `RequestPaintWhileHidden` path as 4.4.
|
||
- **Screenshots** replace event collection with the request-owned completion slots in 4.4; a
|
||
repeated pass must not issue the same request twice. `queued_shots` and the request records
|
||
(with their slots/deadlines) remain the one-shot state.
|
||
- **Autosave** sets `last_autosave` before running `file.save` (`lib.rs:2312-2325`); a second
|
||
logic pass in the same frame sees the new timestamp. Keep that ordering; add a test with a
|
||
forced double pass that counts save calls.
|
||
- **Recovery/read-aloud/Zotero** mutate state flags (`recovery_seen`, `read_aloud_at`,
|
||
`zotero` channel state). Add a forced double-pass test that asserts one recovery tick, one
|
||
status transition and one Zotero answer per event.
|
||
- **`report_dirty`** is idempotent; do not move it out of `logic`/`ui`.
|
||
- **Undo transactions** are engine-owned. Do not open/close a transaction in `logic` on every
|
||
pass; only commands do. A repeated pass must not add an undo step. The IME delete+commit path
|
||
explicitly joins the typing group once.
|
||
|
||
### 5.3 Repeated `ui` passes
|
||
|
||
The app has one existing `request_discard` in `widgets.rs:985` (font list sizing). The
|
||
`WordApp::ui` path performs command side effects through widget clicks and `keys.rs`; events
|
||
exist only in the first pass, so the normal multi-pass layout rerun should not duplicate a click
|
||
or keystroke. This must be demonstrated:
|
||
|
||
- add a test-only wrapper that requests a discard once in a `WordApp::ui` pass (or drive the font
|
||
list to a new row count) and assert one command execution, one undo step, one autosave and one
|
||
screenshot request;
|
||
- keep `logic` in the same test to prove it does not replay stale input;
|
||
- if a duplicate is found, fix it with a narrowly scoped one-shot action queue or by deferring
|
||
only the affected layout action. Do not introduce a generic "compatibility layer" or move all
|
||
commands into `logic` wholesale.
|
||
|
||
### 5.4 Root `Ui` and panel ordering
|
||
|
||
`WordApp::ui` receives `&mut egui::Ui` and must continue to use it directly:
|
||
|
||
- `chrome::title_bar(self, ui)`, `ribbon::show(self, ui)`, `chrome::status_bar(self, ui)`,
|
||
`panes::show(self, ui)`, then `CentralPanel::default().show(ui, |ui| canvas::show(self, ui))`
|
||
(`lib.rs:2360-2410`).
|
||
- `backstage::show` uses `Panel::left` + `CentralPanel` on the same root; no recursive
|
||
`root_ui`.
|
||
- Dialogs/windows use `egui::Window`/`Area` on the context; they are not viewports. Keep their
|
||
`Id`/layer/order semantics.
|
||
- Test `Context::run_ui(RawInput, |ui| { app.logic(ui.ctx()); app.ui(ui) })` at 760x480 and
|
||
1440x900, 1x/2x, both themes; assert the central canvas rect equals the root area after
|
||
panel reservations, page rects are unchanged, and clicks at page coordinates still hit the
|
||
same document positions.
|
||
|
||
### 5.5 Logic-only and forced hidden-pass checks
|
||
|
||
Use `Context::run_logic(&RawInput { viewports, focused, ..Default::default() }, |ctx| app.logic(ctx))`
|
||
(the 0.37 API) only for the background/timer contract; it is not a model of a screenshot-driven
|
||
hidden pass. Check:
|
||
|
||
- a pending autosave still fires on its timer without a UI pass;
|
||
- a pending print/Zotero/read-aloud result still drains;
|
||
- stale events from the previous shown frame are not consumed twice;
|
||
- a pending screenshot may force a full hidden `App::logic` + `App::ui` pass; assert exactly one
|
||
request/reply and one command/undo/autosave, and test that no duplicate side effects occur.
|
||
Do not call that path logic-only, and do not infer the phase from `visible()`;
|
||
- a queued capture issued only in `ui` (including settling frames) or pending synthetic input
|
||
on a hidden window sends `RequestPaintWhileHidden`; test that it reaches issuance, that a
|
||
plain wake may run logic only, and measure its repaint cost;
|
||
- after a completed multi-pass `run_ui`, assert `current_pass_index() == 0`; do not claim a
|
||
stale nonzero index;
|
||
- fonts are not initialized in a logic-only test and then assumed laid out; `Context::run_logic`
|
||
performs no pass or layout, so call `run_ui` for layout assertions. (#8535 documents only the
|
||
recursive `root_ui` panic, not font initialization.)
|
||
- repeated hidden logic ticks do not advance the synthetic gesture queue; a full UI pass
|
||
consumes exactly one pending step, and a queued capture/synthetic step on a hidden window
|
||
uses `RequestPaintWhileHidden` to reach issuance.
|
||
|
||
## 6. Behavior contracts and regression assertions
|
||
|
||
### 6.1 Commands, undo and persistence
|
||
|
||
The engine and command registry are not migrated. Tests must still prove the UI dispatch path:
|
||
|
||
- `engine.execute` and keyboard shortcuts run the same command id for: text insert, bold,
|
||
undo/redo, save/saveAs/open, find, print settings.
|
||
- One typing run or IME replacement is one undo step; undo restores text and formatting;
|
||
redo reapplies.
|
||
- Save/reopen: create a synthetic document, type text, apply bold, insert a comment/bookmark,
|
||
save as a temp `.docx`, open it in a fresh `Session`, assert text, formatting, comments and
|
||
selection/page hints that the product expects. Do not assert egui widget ids survive reopen.
|
||
- Autosave writes the same file and does not clear undo; failure shows the existing message and
|
||
disables autosave for that file.
|
||
- `file.close` with unsaved changes still refuses unless `force`/Save, through control and UI.
|
||
|
||
### 6.2 UI geometry and screenshots
|
||
|
||
Fixtures: `wordcraft_engine::sample::sample_document()`, a large synthetic table
|
||
(`tests_perf::big_table_doc`), and a durable evidence `.docx` with Latin/Arabic/Hebrew/CJK/
|
||
Vietnamese/emoji/comments/headers. Fixtures and fonts are licensed and pinned: use
|
||
`CRAFT_FONTS_DIR` at `b076417b5880bd4154bd22f00695b1d28c6bea83` for the app's CI text faces,
|
||
the central craft-fonts fixtures at `2481a8a324b986a45954e90045ed1f8f2efaaf3c` for
|
||
Arabic/Thai/CJK/Japanese coverage, the pinned Noto Sans Hebrew variable fixture
|
||
(`7ef36a2c...`, OFL-1.1) for Hebrew, and the test-only Noto Color Emoji v2.047
|
||
(`39ee3c58...`, OFL-1.1) for document emoji. Record every fixture file SHA256, source and
|
||
licence in the evidence manifest; do not redistribute proprietary system fonts. Run at 1x/2x,
|
||
light/dark, 1440x900 and 760x480.
|
||
|
||
Assertions:
|
||
|
||
- `ui.inspect`/`document.inspect` JSON: page count, page rects, canvas rect, caret screen point.
|
||
Page/canvas geometry for the same fixture and DPI must be unchanged; chrome heights may change
|
||
only after manual review of the 0.37 style deltas.
|
||
- `ui_shot` images: the page area (app-rendered textures) must match the 0.36 baseline exactly at
|
||
the same DPI; chrome comparisons use a documented per-region threshold. Proposed starting
|
||
policy: a candidate passes only if no page-area pixel differs and chrome changed-pixel fraction
|
||
is <=0.5% with max channel delta <=2 after review; any larger change needs a named cause and a
|
||
reviewed replacement image, never a bulk refresh. The exact threshold is frozen in the baseline
|
||
report before candidate measurement, as the shared protocol requires.
|
||
- Ribbon fit: at widths 760, 920, 1200, 1440 and with English/German/Chinese/Japanese, the same
|
||
groups compact/expand at the same widths, or a reviewed delta is recorded. `ribbon_fit` uses
|
||
measured text widths (`ribbon_fit.rs`), so font changes can alter layout even when the code is
|
||
unchanged.
|
||
- Keyboard focus after a chrome click still returns to the canvas (`refocus_after_chrome_click`)
|
||
and a dialog/field keeps focus while open.
|
||
- Accessibility snapshots: `h.unnamed_widgets()` empty; button/input names; document role tree;
|
||
Arabic text logical order in AccessKit; selected/disabled states for ribbon toggles.
|
||
|
||
### 6.3 Accessibility and keyboard
|
||
|
||
- Kittest `AccessKit` tree: Document node named after the file, paragraph/heading/list/table/
|
||
image/link roles, selected text ranges, caret position. Assert text content is logical, not
|
||
visually reversed.
|
||
- Every input widget has a name; `Role::TextInput` fields keep their current values and labels.
|
||
- Tab/Shift+Tab, arrows, Enter/Space, Escape and F10 context menu still trigger the same
|
||
commands as before. A synthetic RawInput test can prove the E7/E19 translation contract for a
|
||
non-Latin layout, but kittest carries no OS keyboard layout: real Russian/Arabic/Hebrew
|
||
shortcut behavior and typing must be observed on native X11/Wayland/macOS. A green kittest
|
||
run is not native layout evidence.
|
||
- RTL/CJK/Thai/Vietnamese/combining-mark text is not clipped or tofu; `MissingGlyphPolicy::Panic`
|
||
is the default in kittest tests; deliberate tofu tests opt out explicitly.
|
||
|
||
### 6.4 IME, bidi and graphemes
|
||
|
||
- Preedit/commit/cancel: one edit and one undo step; candidate window rect follows the caret.
|
||
- `DeleteSurroundingBytes`: exact byte replacement on macOS/Wayland; char-count path remains for
|
||
web; both refuse non-boundary ranges without panicking; deletion-only and empty-commit batches
|
||
work. Inside an equation a byte range has no reliable tree mapping: the request is refused,
|
||
the composition interrupted and only the replacement immediately paired with the rejected
|
||
deletion dropped — the next `Commit`, or an adjacent `Text` + `Commit` pair. Any later
|
||
text/commit in the same raw-input batch is fresh input and must apply, as is a later direct
|
||
commit without a pre-edit (no persistent tombstone). An over-cap equation deletion likewise
|
||
clears the painted pre-edit and any earlier deletion's replacement join before it interrupts.
|
||
The backend's composition interrupt fences protocol-late stale input; an untagged synthetic
|
||
event in a later frame cannot be distinguished from fresh input. These are synthetic contract
|
||
tests; native macOS/Wayland IME traces are separate.
|
||
- E21b outgoing surrounding text: after delete+commit and deletion-only batches, after
|
||
focus/password transitions and with two windows, the payload carries the logical committed
|
||
text and byte cursor/anchor with preedit excluded. A kittest event test is not native IME
|
||
evidence.
|
||
- Bidi: Arabic/Hebrew comment balloon reads from the right, a Latin word inside an Arabic
|
||
paragraph keeps its left-to-right order, brackets mirror correctly, and no double reversal
|
||
occurs in Backstage file names. Test screen x positions of first/last words, not just string
|
||
equality.
|
||
- Grapheme editing: emoji ZWJ/modifiers, regional indicators, combining marks and Indic sequences
|
||
use the engine's cluster rules; caret/hit-test/selection do not split UTF-8.
|
||
|
||
### 6.5 Clipboard and native behavior
|
||
|
||
- Native Linux X11 and Wayland: copy formatted text, paste into another app, read
|
||
HTML/RTF/picture formats; no double data-device failure.
|
||
- Middle-click on the canvas starts autoscroll and never pastes PRIMARY. PRIMARY is tested only
|
||
in built-in `TextEdit` where the app's feature graph actually enables egui clipboard; do not
|
||
add app-level PRIMARY paste.
|
||
- Windows: paste HTML/RTF/MathML and pictures; save/open a document through a real file picker;
|
||
close/quit behavior unchanged.
|
||
- macOS (if evidence available): pasteboard formats, IME, native termination; otherwise mark
|
||
unresolved rather than claiming pass.
|
||
- Browser: WebGPU and WebGL2 fallback, paste pictures, download/save, localStorage prefs,
|
||
keyboard shortcuts in an iframe, resize; actual browser run, not just wasm compile.
|
||
|
||
### 6.6 Deliberate mutation checks
|
||
|
||
Before accepting the suite, introduce and then remove each defect in a scratch branch; each must
|
||
fail the named assertion:
|
||
|
||
| Deliberate defect | Must fail |
|
||
|---|---|
|
||
| Reuse a screenshot token/path for two requests | screenshot correlation test, one reply per request |
|
||
| Deliver a late callback after timeout/cancel | no file, no second reply, retained bytes return to zero |
|
||
| Drop the ruler grip's accessible name | `unnamed_widgets()` fails |
|
||
| Change the ruler grip to `Role::Unknown` | explicit `get_by_role(Role::Splitter, ...)` fails |
|
||
| Remove `default_fonts` from the test build | glyph coverage test or kittest missing-glyph panic |
|
||
| Reintroduce `bidi_display` on fork text | RTL balloon/file-name screen-position test (double reversal) |
|
||
| Ignore `DeleteSurroundingBytes` | IME replacement/undo test |
|
||
| Make `ScrollArea` offset unclamped for page tracking | wheel/ribbon offset test |
|
||
| Force a duplicate logic pass | one-shot autosave/insert/screenshot counter test |
|
||
| Replay a dropped file from a prior pass in a forced hidden pass | stale-drop test counts one open/insert |
|
||
| Make middle-click paste PRIMARY | canvas autoscroll regression fails |
|
||
| Change page texture filtering | page-area pixel comparison |
|
||
|
||
## 7. Validation matrix and managed execution
|
||
|
||
No command below was run for this planning document. Every new host build/test and owned-VM
|
||
command must use `/home/codex/work/maintenance/task.py run`, then `status`, and `finish` after
|
||
all checks and evidence are complete, with a unique `--task`, a fresh `--job-id`, exact
|
||
`--source`, durable `--evidence`, and the allocator's target. Do not run Cargo directly outside
|
||
the allocated target; do not reuse another worker's target/job.
|
||
|
||
### 7.1 Host gates (Linux)
|
||
|
||
Proposed `task.py run` command bodies (the parent supplies task/job/evidence):
|
||
|
||
- `cargo metadata --locked --format-version 1`
|
||
- `cargo check --workspace --all-targets --locked`
|
||
- `cargo clippy --workspace --all-targets --locked -- -D warnings`
|
||
- `cargo test --workspace --locked`
|
||
- `cargo test -p wordcraft-ui-egui --locked`
|
||
- `cargo test -p wordcraft-engine tests_typing --locked`
|
||
- `cargo test -p wordcraft-ui-egui --release big_table_scroll_bench -- --ignored --nocapture`
|
||
- `cargo run --release -p wordcraft-ui-egui --example perf_probe -- <fixture.docx>`
|
||
- `cargo run --release -p wordcraft-ui-egui --example ui_shot -- <script.jsonl>`
|
||
- `cargo check -p wordcraft-web --target wasm32-unknown-unknown --locked`
|
||
- `cargo build --release -p wordcraft --locked`
|
||
- `cargo xtask ci` (fmt, clippy, tests, assets, layers, wasm)
|
||
- `cargo xtask parity`, `cargo xtask assets`, `cargo xtask layers`
|
||
- `cargo xtask deps` (proposed new gate) and its negative tests
|
||
|
||
`cargo xtask wasm` does not include `apps/wordcraft-web`; the separate
|
||
`printing.yml` browser check (`cargo check -p wordcraft-web --target wasm32-unknown-unknown
|
||
--locked`) is mandatory. A real browser run uses `packaging/web/package.sh` (trunk) or a local
|
||
`dist/web` server, with WebGPU and `?webgl` fallback, and the control/inbox workflows.
|
||
|
||
### 7.2 Native Linux
|
||
|
||
Run the release app with `--sample --control 7981` and a temporary settings dir
|
||
(`WORDCRAFT_NO_PREFS` for agent runs where appropriate). Use `ui.screenshot`, `ui.inspect`,
|
||
`ui.click`, `ui.key`, `ui.text`, `ui.wheel`, save/open. Observe X11 and a real Wayland
|
||
compositor (record compositor identity); use `WORDCRAFT_WAYLAND=1` where needed. Check regular
|
||
clipboard, middle-click autoscroll (no PRIMARY paste), file drop, native IME and
|
||
DeleteSurroundingBytes/outgoing surrounding text, real Russian/Arabic/Hebrew keyboard layouts,
|
||
mixed DPI, window resize, a forced hidden screenshot pass, screenshot timeout/cancel and page
|
||
geometry. `ui.key`/kittest injection is not a substitute for OS layout or IME evidence.
|
||
|
||
### 7.3 Windows
|
||
|
||
Use a uniquely named `craft-windows` clone, unused SSH/VNC ports, its own source checkout and
|
||
target. Build with `cargo build --release -p wordcraft --locked` (or the repo's documented
|
||
build/test commands), launch through `Launch-CraftDesktop.ps1`/`guest-exec.py`, and perform a
|
||
visible typed/edited/saved/reopened workflow plus print settings. Observe DX12/FXC startup and
|
||
the screenshot path. Do not claim macOS/Metal or physical audio from Windows software rendering.
|
||
|
||
### 7.4 Web
|
||
|
||
Build the wasm app, run it in a browser over `http://localhost` or HTTPS, exercise WebGPU and the
|
||
`?webgl` fallback, type/save/download/reopen a document, paste a picture, use the control
|
||
protocol if available, resize the iframe, and inspect console/panic output. A `cargo check`
|
||
alone is not browser acceptance.
|
||
|
||
### 7.5 macOS, FreeBSD, other platforms
|
||
|
||
- macOS: required for macOS IME/pasteboard/Metal/native termination claims. If no owned macOS
|
||
evidence is available, mark those rows **unresolved/blocking**, not passed. The server's
|
||
Linux host cannot substitute.
|
||
- FreeBSD: keep the existing nightly/FreeBSD workflow; run after migration and record the
|
||
result. Do not infer from Linux.
|
||
- Physical GPU: the host has an i915 render device but no inherited display session; that is
|
||
availability, not a qualified Vulkan/OpenGL/native compositor result. Record software-rendered
|
||
and real-GPU results separately when both exist.
|
||
|
||
## 8. Performance protocol
|
||
|
||
Use the shared protocol in `craft-libs/docs/egui-037-suite-rollout.md` and make WordCraft-specific
|
||
measurements. Floors: at least 10 warmups and 30 timed steady-operation iterations, at least 20
|
||
fresh processes for startup, and at least 1,000 frames for any frame distribution with p99.
|
||
Licensed pinned fixtures apply; an app may tighten floors, never loosen them.
|
||
|
||
- **S0/P0 instrumentation, before any production edit:** extend `perf_probe`/the bench harness to
|
||
emit raw per-frame samples with `n`, p50/p95/p99 and the full sample list. Raise typing, wheel
|
||
and trackpad workloads to at least 1,000 frames each; do not use `big_table_scroll_bench`'s
|
||
10-idle/40-scroll-frame median/max as tail evidence. Add a measurement-only native process
|
||
harness (environment/flag gated, no behavior change) that records process start, first UI
|
||
pass, each pass begin/end, winit redraw/AboutToWait timestamps where exposed, and screenshot
|
||
readback; samples `/proc/<pid>/stat` and `VmHWM` for 30 s idle; counts repaints/wakeups via
|
||
`Context::set_repaint_observer`; and writes a CSV/JSON trace. Run at least 20 fresh processes
|
||
for cold start. Report harness `WordApp::logic + ui` CPU time separately from presented-frame
|
||
latency/interval; never substitute one for the other. If a presented-frame timestamp cannot
|
||
be observed without changing the renderer, record that gap and use the screenshot-readback
|
||
timestamp only as a clearly labelled proxy. Preserve the 0.36.2 release
|
||
executable and SHA256 with a saved fixture for rollback rehearsal.
|
||
- Every baseline report and every investigated regression includes an equivalent stock-0.37
|
||
control alongside 0.36 and the fork. Fork-only app code need not compile unchanged on stock:
|
||
use the same operation in a pinned minimal adapter/harness and record all three source/lock
|
||
identities. Unavailable equivalence is an attribution limitation, not a reason to omit the
|
||
control.
|
||
- Baseline and candidate: same host, compiler (stable/1.95+), `--release`, `--locked`, features,
|
||
`CRAFT_FONTS_DIR` ref, fixtures, OS, CPU/GPU model, wgpu backend/adapter/driver and host load, 1440x900, 1x and 2x DPI,
|
||
light/dark. Record full SHA and `Cargo.lock` hash for each. Use separate allocated targets and
|
||
alternate run order; no concurrent builds or noisy workloads.
|
||
- Workloads: cold start to first frame (>=20 fresh processes), open sample/large fixture, idle
|
||
30 s (CPU, repaints, wakeups), typing in a long paragraph and a table (>=1,000 frame samples
|
||
per condition, repeating the input as needed), wheel and trackpad scroll (>=1,000 frames),
|
||
zoom in/out, ribbon tab switches, resize sweep, screenshot latency (>=30 requests, report n),
|
||
save/reopen latency, large-table scroll (`big_table_scroll_bench` with an extended sample
|
||
count), peak RSS, texture upload/free counts and bytes, allocation counters where available,
|
||
and font-atlas churn.
|
||
- Use the licensed pinned fixture/font set from section 6.2 for both baseline and candidate;
|
||
record file hashes, licences, DPI, theme and renderer identity.
|
||
- Report raw samples plus median/p95/p99 for frame distributions, median/p95 for discrete
|
||
operations with n, paired baseline/candidate ratios, and peak memory. Label harness CPU time
|
||
separately from presented-frame latency. Do not label a p99 from 20-50 samples as a tail
|
||
estimate; use at least 1,000 frames for frame distributions.
|
||
- Preliminary gates, frozen before candidate measurement: investigate a repeatable >5% median,
|
||
>10% p95 or >5% peak-RSS regression, any new idle busy-loop/repaint, or a lost absolute
|
||
budget. For sub-millisecond changes require the relative increase also to exceed the 0.05 ms
|
||
noise floor or corroborating allocation/instruction counts. Reject noisy comparisons as
|
||
inconclusive and rerun; never widen a threshold or tolerance after seeing a failure; any
|
||
exception needs a documented, pre-measurement tradeoff and review.
|
||
- Specifically reproduce/measure:
|
||
- upstream #8731: plain-text tessellation cost (UI chrome and dialogs); compare 0.36.2,
|
||
stock 0.37 and fork. If confirmed, review a row-level fast path, not a semantic change.
|
||
- upstream #8736: DX12 FXC pipeline creation and startup on Windows; separate renderer
|
||
construction from first frame.
|
||
- upstream #8512: system-font discovery startup if `system_fonts` is ever considered; this
|
||
plan keeps it off.
|
||
- Performance evidence must never be compile duration, a changed golden image, or an
|
||
unpaired run. Preserve baseline/candidate executables or hashes outside disposable targets.
|
||
|
||
## 9. Reviewable PR stages, dependency DAG and rollback
|
||
|
||
All stages are local until the user gives fresh direct approval for each publication. Each stage
|
||
must be independently reviewable and leave `main` buildable. No stage pushes/publishes by itself.
|
||
|
||
```
|
||
S0/P0 baseline harness + instrumentation (no production behavior)
|
||
└─ S1 atomic dependency + required text/capture adapters + craft-libs CL-1/CL-2F (one PR)
|
||
├─ S2 adversarial screenshot/capture tests + visual/role review
|
||
├─ S3 extended native IME/bidi/grapheme tests and optional language policy
|
||
├─ S4 lifecycle/multi-pass/forced-hidden-pass/synthetic-input fixes + tests
|
||
└─ S5 native/web/Windows validation + performance evidence (after S2-S4)
|
||
└─ S6 documentation/progress updates + final adversarial review
|
||
```
|
||
|
||
- **S0/P0:** add the baseline capture scripts/fixtures, instrumentation and evidence layout;
|
||
record 0.36.2 baseline hashes, executable SHA256, pre-existing failures and the saved rollback
|
||
fixture. No dependency or production behavior change. Rehearse old-behavior replay with the
|
||
saved 0.36.2 executable and fixture before S1.
|
||
- **S1:** one atomic dependency PR, after the approved published P8 and completed winit
|
||
revisions exist: pin `=0.37.0`/`=0.30.13`, root patches, bump `egui_kittest`, and move
|
||
`craft-ui`/`craft-print` to the published revision containing CL-1 and CL-2F **in this same PR** (the
|
||
`d27646a` `craft-ui` in `crates/ui-egui/Cargo.toml:12` still requires egui 0.36 and cannot
|
||
compile against the 0.37 `Ui` passed at `crates/ui-egui/src/printing.rs:35`). Include
|
||
`Id::unique`, `scope_id`,
|
||
`Role` (all 34 sites), `MouseWheel.source` (including `examples/perf_probe.rs:106`),
|
||
`ScrollArea` accessors (including `scroll.rs:440`), `ScreenshotCallback`,
|
||
native `eframe/clipboard`, `default_fonts`/glyph policy, accessible-name/role sweep
|
||
and the print `label` adaptation.
|
||
Include CL-2F capture admission/drain/timeout/basic replies, remove duplicate
|
||
`bidi_display`/balloon visual reordering, handle E21 byte deletion and supply both
|
||
canvas E21b producers. These are required at the first fork pin; do not defer them.
|
||
The approved revisions contain P2b async W15P, P7 held-key pairing, P7b/E21b and P7c's
|
||
selected #8763/#8748 fixes; stock 0.37 or an intermediate stage is never pinned. Include the
|
||
minimal compile fixes, feature-identity checks and `cargo metadata` gate. This PR is not
|
||
split into "bump then fix"; the tree builds and `cargo xtask ci` passes at the end. Never pin
|
||
the aiongg research SHAs.
|
||
- **S2:** screenshot callback behavior: request-owned strong slots with weak callbacks, the
|
||
shared 4-request/128 MiB/256 MiB admission, geometry/DPI/zoom epoch cancel/retry,
|
||
`RequestPaintWhileHidden` for queued hidden requests, timeout/cancel/late-result tests, forced
|
||
hidden pass, visual review and explicit role assertions. Adopt `craft_ui::capture` only after
|
||
CL-2F freezes the API in S1; S2 expands stress tests. WordCraft needs CL-1 and CL-2F, while only the DesignCraft/GridCraft
|
||
pilots use the reviewed CL-2 provisional revision. If CL-2F is not available, wait; do not
|
||
publish a provisional adapter.
|
||
- **S3:** extend native grapheme/IME/bidi coverage and optional language policy on Context.
|
||
Required removal of double bidi, E21 deletion and both E21b adapters already land in S1.
|
||
Rollback removes only these later policy/test additions, never restores duplicate reorder.
|
||
- **S4:** lifecycle: stale dropped-file fix, forced multi-pass and forced hidden-pass tests,
|
||
logic-only tests, synthetic one-step gesture retention, autosave/screenshot/undo one-shot
|
||
tests. Can proceed in parallel with S3 after S1.
|
||
- **S5:** evidence: Linux X11/Wayland, Windows clone, browser, optional FreeBSD; performance
|
||
paired report including the stock-0.37 control; screenshots/geometry/accessibility/mutation
|
||
checks. No production edits except fixes found by the evidence, which go back into the owning
|
||
stage.
|
||
- **S6:** update `ROADMAP.md` progress log, `docs/gaps.md`/`docs/roadmap.md`/parity docs only for
|
||
behavior actually changed, and record the migration's status/revision history. No agent/
|
||
model/tool signatures in public text; contributor credit remains username-only.
|
||
|
||
Rollback: restore the previous coherent set, not just one version string: root `Cargo.toml`
|
||
egui family declarations, root `[patch.crates-io]`, `Cargo.lock`, `craft-ui`/`craft-print` rev,
|
||
and the source adaptations in one revert. Keep the old lockfile and baseline branch reachable.
|
||
Preserve the actual 0.36.2 release executable and SHA256 outside the disposable build target,
|
||
plus a saved `.docx` fixture; before S1 and after each stage, restore the prior coherent
|
||
manifests/lockfile/source set, run that executable on the saved fixture, and verify the old
|
||
open/edit/save/reopen behavior. Restoring only an old lockfile is not a rollback rehearsal.
|
||
No app persistence format changes are planned; panel ids are in-memory, preferences are unchanged.
|
||
If required text/IME compatibility cannot be proven, block S1 or restore its full coherent
|
||
predecessor set. Never restore 0.36 manual visual reordering while retaining the fork.
|
||
S3 alone may be reverted only because it contains later policy/tests, not required adapters.
|
||
|
||
## 10. Open questions and blocking unknowns
|
||
|
||
1. **craft-libs PR 199 is open/conflicting at inspection.** Its head `b68de8c` and historical
|
||
`ec8840a` tree must not be merged; port only the `8614e4a` API hunks and `1764bca` README
|
||
changes onto current `4919088`, preserving intervening fixes. The reviewed and published
|
||
CL-1 `craft-ui`/`craft-print` revision is a prerequisite for the WordCraft pin and must land
|
||
in the same atomic dependency stage (section 9).
|
||
2. **Fork publication/reachability.** Final pins must be the completed P8 `storytold/egui`
|
||
and completed `storytold/winit` revisions: approved, reachable and immutable. Their SHAs do
|
||
not exist at planning time and must not be invented; the aiongg candidate commits and stock
|
||
0.37 are research/measurement inputs only. No branch pins and no intermediate production
|
||
stage in the release manifest.
|
||
3. **P7c selection/UI-regression fixes.** P7c owns the selected #8763 and #8748 fixes;
|
||
#8764 is an open, unmerged proposal input. The approved P8 revision must contain the central
|
||
fixes and their pointer/keyboard regressions; a WordCraft workaround is not an accepted
|
||
substitute.
|
||
4. **E21b/P7b and E4/P2b+W15P prerequisites.** The approved revisions must satisfy the complete
|
||
shared contracts in `egui` plan §6 E21b/E4 and winit plan §5.2/§5.3, including the W27
|
||
lifecycle/serial rules, the W15P pointer-or-keyboard synchronous claim and bounded async
|
||
transfer. WordCraft's two canvas producers need real model/preedit/UTF-8 adapters.
|
||
5. **Capacity and platform evidence.** Host build reservations, a Windows clone, a Wayland
|
||
compositor, a browser with WebGPU/WebGL2, and macOS hardware are separate gates. Missing
|
||
evidence remains an explicit blocker, not a pass.
|
||
6. **Upstream #8731/#8736 measurements** are not yet reproduced on WordCraft. They may require a
|
||
reviewed fork patch or a documented tradeoff; do not accept a clamp-only texture change.
|
||
7. **Strict names/glyph sweep.** The exact list of unnamed inputs is unknown until the suite
|
||
runs. Fixing names and glyph coverage is part of S1, required rather than disabling the
|
||
check; most tests use `step`/`run_steps`, so explicit accessibility assertions are required.
|
||
8. **Bidi visual deltas.** Removing `bidi_display` should fix double reversal, but it changes
|
||
exact text placement in Backstage and comment balloons. Review screenshots at 1x/2x and both
|
||
themes; do not bulk-refresh goldens.
|
||
9. **IME byte ranges and native behavior.** macOS and Wayland text-input behavior cannot be
|
||
qualified on this Linux host. The byte path must refuse non-boundaries and be tested in a
|
||
native environment; a passing harness test is not native IME proof. The same applies to
|
||
native keyboard layouts and the screenshot-driven hidden pass.
|
||
10. **S0 instrumentation gap.** Raw frame samples, >=1,000-frame tails, cold start, idle CPU/
|
||
wakeups and RSS are not measurable with the current tools and must be added before
|
||
production edits. Harness CPU time is not presented-frame latency.
|
||
11. **Font/emoji policy.** The actual family order is `Inter, Ubuntu-Light, egui-icons,
|
||
SourceSans, ...` (with `monochrome_emoji_fonts` explicitly enabled on WordCraft's test
|
||
root, not inherited from kittest), not the earlier proposed order. The UI has no color
|
||
emoji; document emoji use a pinned test fixture or a recorded native platform result.
|
||
12. **Craft-libs #198** remains a proposal, not a dependency. Do not silently include its
|
||
locale/formatting scope or font assets.
|
||
13. **`craft-print`/`craft-ui` #192** changes public print fields; confirm no other WordCraft
|
||
caller constructs these structs without the new field.
|
||
14. **Panel ids and any persisted egui state.** The app does not enable egui persistence now;
|
||
if that changes, old panel ids will not load. Record this as an explicit non-goal or add a
|
||
migration key deliberately.
|
||
|
||
## 11. References
|
||
|
||
Immutable or primary sources used for the API and behavior statements above:
|
||
|
||
- egui 0.37.0 release commit:
|
||
<https://github.com/emilk/egui/tree/841a328ba2dd8f6996beb7189ebb7fc00e668a2a>
|
||
- `ScreenshotCallback` and `Context::request_screenshot`:
|
||
<https://github.com/emilk/egui/blob/841a328ba2dd8f6996beb7189ebb7fc00e668a2a/crates/egui/src/data/screenshot_callback.rs>
|
||
and
|
||
<https://github.com/emilk/egui/blob/841a328ba2dd8f6996beb7189ebb7fc00e668a2a/crates/egui/src/context.rs>
|
||
- `Event::MouseWheel` source and 0.37 input events:
|
||
<https://github.com/emilk/egui/blob/841a328ba2dd8f6996beb7189ebb7fc00e668a2a/crates/egui/src/data/input/event.rs>
|
||
- `Ui::scope_id` / `Ui::id`, `Id::unique` / `Id::new`:
|
||
<https://github.com/emilk/egui/blob/841a328ba2dd8f6996beb7189ebb7fc00e668a2a/crates/egui/src/ui.rs>
|
||
and
|
||
<https://github.com/emilk/egui/blob/841a328ba2dd8f6996beb7189ebb7fc00e668a2a/crates/egui/src/id.rs>
|
||
- `Context::root_ui` panic semantics (upstream #8535):
|
||
<https://github.com/emilk/egui/pull/8535>
|
||
- 0.37 release notes, including `default_fonts`, `Role`, screenshot and panel-id changes:
|
||
<https://github.com/emilk/egui/blob/841a328ba2dd8f6996beb7189ebb7fc00e668a2a/CHANGELOG.md>
|
||
- `egui_kittest` 0.37 accessibility/missing-glyph changes:
|
||
<https://github.com/emilk/egui/blob/841a328ba2dd8f6996beb7189ebb7fc00e668a2a/crates/egui_kittest/CHANGELOG.md>
|
||
- Candidate aiongg egui research input (not a production pin):
|
||
`a4f9a17f64a6bae61350fb85804c9df0d9865916`, `ai-0.37`; the inspected checkout's origin is
|
||
`emilk/egui`, and no `storytold/egui` `craft/0.37` ref contained it at inspection.
|
||
- Candidate aiongg winit research input (not a production pin):
|
||
`8b35388bd719e8cc86a95971ba0bc026c9a8f058`, `ai-lang` in `storytold/winit`.
|
||
- Final patch inventories will be linked from the approved published `storytold` revisions
|
||
after publication; do not use the candidate SHAs as immutable production links.
|
||
- craft-libs PR 199: <https://github.com/storytold/craft-libs/pull/199>
|
||
- craft-libs language-support issue 198: <https://github.com/storytold/craft-libs/issues/198>
|
||
- craft-libs current main used for the port:
|
||
`49190884278f16c9a9f5823d7698a8ffb5deb062`.
|
||
- Shared suite rollout/performance contract:
|
||
`craft-libs/docs/egui-037-suite-rollout.md` in the assigned suite checkout.
|
||
- Complete shared E21b/P2b and winit W15P/W27 contracts: `egui/docs/egui-037-migration-plan.md`
|
||
§6 (E4/P2b, E21b) and `winit/docs/egui-037-migration-plan.md` §5.2-5.3 in the planning
|
||
workspace.
|
||
- Pinned font fixtures (craft-fonts `2481a8a...`, Noto Color Emoji v2.047):
|
||
<https://github.com/storytold/craft-fonts/blob/2481a8a324b986a45954e90045ed1f8f2efaaf3c/fonts/manifest.txt>
|
||
and <https://github.com/googlefonts/noto-emoji/blob/22e564626297b4df0a40570ad81d6c05cc7c38bd/fonts/NotoColorEmoji.ttf>.
|
||
- Upstream reports used as risk hypotheses, not reproduced WordCraft results:
|
||
<https://github.com/emilk/egui/issues/8731>,
|
||
<https://github.com/emilk/egui/issues/8736>,
|
||
<https://github.com/emilk/egui/issues/8763>,
|
||
<https://github.com/emilk/egui/pull/8764>,
|
||
<https://github.com/emilk/egui/pull/8748>,
|
||
<https://github.com/emilk/egui/pull/8512>,
|
||
<https://github.com/emilk/egui/pull/8657>,
|
||
<https://github.com/emilk/egui/pull/8577>.
|
||
- Historical compiler diagnostics used only as clues:
|
||
`/home/codex/work/ssd2/task-evidence/egui037-audit-20261022/fork/wordcraft.errors`.
|
||
|
||
## 12. Revision history
|
||
|
||
- 2026-10-23: initial plan. Inventory and source anchors from the assigned checkout; dependency
|
||
target, API refactors, lifecycle/root-`Ui` audit, behavior/evidence matrix, performance
|
||
protocol, staged PR DAG, rollback and blocking unknowns. No tests/builds run; no publication.
|
||
- 2026-10-23: revision 2 after independent review `plan-reviews/wordcraft-v1.md`. Corrected the
|
||
aiongg/reachability identity and PR 199 stale-tree rule; added E21b/P7b and W15P/E4/P2b as
|
||
fork prerequisites; replaced the screenshot channel with request-owned weak slots, count/byte
|
||
admission, in-flight budget, resize revalidation and post-unlock wakeup; documented the
|
||
screenshot-forced hidden pass and that requesting a screenshot schedules a repaint while
|
||
callback arrival does not; moved strict names/glyph work into S1; corrected font order and
|
||
emoji source; added `scroll.rs:440`; added exact Id counts, feature checks, P0 instrumentation,
|
||
native-vs-kittest boundaries, explicit role assertions, middle-click autoscroll preservation,
|
||
#8763/#8764 selection fix and executable rollback rehearsal. No tests/builds run.
|
||
- 2026-10-23: revision 3 after independent review `plan-reviews/wordcraft-v2.md`. Made S1 one
|
||
atomic dependency stage including craft-libs CL-1, replacing the S1/S2 cycle; made P8 and the
|
||
completed winit the only production pins; corrected the E21b/P2b/W15P contracts to the full
|
||
shared plans; adopted the 4/128 MiB/256 MiB capture limits and geometry-epoch rule; added
|
||
`RequestPaintWhileHidden` for queued hidden captures and the synthetic one-step gesture rule;
|
||
fixed `cargo metadata --filter-platform`, the 34th role site and the `perf_probe` wheel site;
|
||
added stock-0.37 controls, warmup/iteration floors and the RSS/noise thresholds; corrected
|
||
P7c/#8764 provenance, font prose and the #8535 citation. No tests/builds run.
|
||
- 2026-10-24: revision 4 after the IME repair review. Bounded the refused byte-counted equation
|
||
replacement to its raw-input batch (a later direct commit applies; no persistent tombstone),
|
||
and moved the simulated IME revision exhaustion behind the engine `test-support` feature that
|
||
only the UI's dev-dependencies enable. No tests/builds run.
|
||
|
||
|
||
## 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/wordcraft` `main` now resolves to `00ccee52354d9dfc48cb21a943b55f258fc76df0`. The owned checkout was fast-forwarded to this revision. Earlier baseline SHAs and line anchors above are retained as historical review evidence; use this new revision and its exact lockfile for current-main integration and paired measurements. The original refs remain reachable.
|
||
|
||
Preserve the separate 64-entry engine UI-intent queue, newest Paste Special deduplication, save/export/print view preservation, linear-time unknown settings, finite font preview sizes and Format Painter cursor. Screenshot admission budgets are a separate queue.
|
||
|
||
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 note — 2026-10-23
|
||
|
||
Implemented in the owned worktree at canonical `00ccee52` without changing the workspace lockfile,
|
||
committing, running Cargo, or publishing:
|
||
|
||
- Coherent 0.37 API port: `egui::Role` (including `Role::TextInput`, `Role::Image` and
|
||
`Role::Splitter` for the table-ruler grip), `Id::unique`, `Ui::scope_id`,
|
||
`MouseWheelSource`, `ScrollArea::clamped_offset`/`horizontal_scroll_offset`, and
|
||
`TextEdit::align`.
|
||
- Screenshot capture now uses `craft_ui::capture::CaptureSlot` with a weak callback. `WordApp`
|
||
owns request identity, an 8 s monotonic deadline (`web_time::Instant` on native and in the
|
||
browser), count (4), per-image (128 MiB) and aggregate (256 MiB) admission, retry accounting,
|
||
an immutable admitted document identity and a monotonic geometry epoch. Geometry is captured
|
||
from the host `RawInput` in the raw-input hook, including `minimized`/`occluded` and a Wayland
|
||
`inner_rect: None` fallback to the same frame's `screen_rect`; it is never read from stale
|
||
`InputState` geometry in a hidden logic tick. A minimized/occluded window and a document
|
||
switch cancel the request with an explicit error instead of retrying or rebinding it. The
|
||
deadline is checked before any successful image is encoded or written; stale geometry retries
|
||
only within the budget, while malformed, oversized and out-of-order completions do not.
|
||
- The engine's 64-entry UI-intent queue remains separate from the four screenshot requests.
|
||
Save/export/print view preservation, Paste Special deduplication, settings loading, font
|
||
preview bounds and the Format Painter cursor are unchanged.
|
||
- `logic` owns screenshot deadlines/results and asks for `RequestPaintWhileHidden` while a
|
||
capture or synthetic step is pending. `ui` issues the one callback command and counts only
|
||
first-pass layout settling. Native and web dropped-file handling moved to the first real UI
|
||
pass so hidden `run_logic` cannot replay stale RawInput.
|
||
- Synthetic input is offered once per host frame, survives hidden ticks, and is removed only
|
||
after a final non-discarded UI pass. Multi-pass and real-RawInput accumulation regressions are
|
||
in `crates/ui-egui/src/lib.rs`.
|
||
- Removed manual visual bidi reordering from Backstage and comment balloons. Comment balloons
|
||
now use `LayoutJob` with `TextDirection::Auto` and start alignment; the screen-position RTL
|
||
regression remains the oracle.
|
||
- Added `ImeEvent::DeleteSurroundingBytes` handling with UTF-8 boundary checks, selection
|
||
replacement, one typing undo group and deletion/empty-commit/emoji/combining-mark tests.
|
||
- Declared `default_fonts` + `monochrome_emoji_fonts` on the UI test root and `clipboard` on
|
||
the native eframe feature list. The exact test font order is pinned in `theme.rs`.
|
||
|
||
### Fresh-review repairs (2026-10-23)
|
||
|
||
The first 0.37 port was reviewed before compilation. These repairs are in the worktree:
|
||
|
||
- Deadlines now use `web_time::Instant` on both native and browser, not `Date.now()` with a
|
||
backward clamp. Screenshot deadline, settling, autosave and animation share the same
|
||
monotonic clock.
|
||
- Capture geometry is taken from the host `RawInput` in `raw_input_hook`, never from stale
|
||
`InputState` geometry. The snapshot includes `minimized`, `occluded` and the Wayland
|
||
`inner_rect: None` fallback to that frame's `screen_rect`. A minimized/occluded surface is
|
||
rejected at admission and cancelled at issue/completion.
|
||
- A screenshot request stores an immutable admitted document id. Issuing or draining after a
|
||
document switch cancels the request with one explicit error; it is never rebound and retries
|
||
only for a same-document geometry change. Malformed/oversized completions are not retried.
|
||
- Admission reserves the encode working set (raw RGBA + worst-case PNG + base64), revalidates
|
||
it at issuance and checks the actual PNG/base64 size before replying. Completion requires the
|
||
exact expected physical dimensions; every reply carries the internal request token.
|
||
- Dropped files and the SmartArt Tab action run before the font warm-up return, so a drop in the
|
||
first two UI passes is not lost. Synthetic input waits for those frames and for a final
|
||
non-discarded UI pass.
|
||
- Chart-data cells use `Align2::RIGHT_TOP` (the deprecated `horizontal_align(Align::Max)`
|
||
changed x only); the regression checks the painted text's top/right position. The rename
|
||
field is tied to its label and the chrome/rename screens are checked for unnamed inputs.
|
||
- `unicode-bidi` is no longer a `ui-egui` dependency. Workspace manifests now name the 0.37
|
||
egui family and `web-time`; `Cargo.lock` and the `craft-ui`/`craft-print` revision containing
|
||
`capture::CaptureSlot` and `Printer::label` remain parent integration gates.
|
||
|
||
### E21b outgoing surrounding text (wired in this worktree)
|
||
|
||
The reviewed E21b outgoing API is wired through the canvas observation path:
|
||
|
||
1. `keys::observe_ime` emits one `IMEOutput` per focused viewport pass with an immutable
|
||
`ImeTarget` owner, context generation, purpose, canvas rectangle, cursor rectangle,
|
||
`should_interrupt_composition`, `change_cause` and `surrounding_text`.
|
||
2. The body producer uses `Document::ime_surrounding` and `VisibleParagraph`: tracked deletions
|
||
are hidden, inline objects contribute their plain text, offsets are logical UTF-8 bytes (never
|
||
display-reordered bidi), and the window is bounded by `MAX_IME_TEXT_BYTES`. It is reported as
|
||
`CommittedText`, with an unavailable revision when the selection spans blocks or cannot map.
|
||
3. The body owner is `(document id, story, body replacement epoch)`; an equation uses its stable
|
||
editing-session token. Undo/Redo and whole-body replacements advance the epoch/session so a
|
||
stale composition cannot be reused.
|
||
4. An equation or an unmappable/over-wide body selection is an explicit `Unavailable`, carrying
|
||
the engine revision that proves a committed edit; no truncated text is fabricated.
|
||
5. `publish_ime_receipt` accepts a successful IME edit only when the classified outcome and the
|
||
committed/unavailable stamps prove the exact transition; mixed availability requires the
|
||
unavailable side's checked revision, and exhausted revisions fail closed.
|
||
|
||
Still outstanding for publication: native Wayland/focus-loss traces, an independent review of
|
||
this repair, final immutable fork/winit pins, and the parent's post-repair full gate rerun.
|
||
Kittest events are not native IME evidence.
|
||
|
||
PR 787 is a historical patch reference only. This repository has no vendored-winit tree and
|
||
none was added; only the fetched PR metadata was inspected, and no vendored winit files or
|
||
security fixes were copied or overridden.
|
||
|
||
All build, test, lockfile, formatting, native GUI and performance gates remain for the parent’s
|
||
managed `task.py` workflow.
|
||
|
||
### Local behavioral qualification, 2026-10-23
|
||
|
||
The pinned craft-fonts corpus enables the strict UI suite without disabling missing-glyph checks. An intermediate run had 816 passed, 36 failed and 2 ignored; the later parent-managed run recorded 941 engine tests passing and 898 UI tests passing, with one doc IME fixture failure and three content-control placeholder undo failures whose source repairs are in the working tree. Those runs are pre-repair evidence only: the post-repair rerun remains required, and this document is not acceptance evidence.
|
||
|
||
Read-only comment balloons now use `craft_ui::text::align_galley_rows` to place native-shaped rows against the card's paragraph start edge. This preserves the fork's logical text and mirrored glyphs. The existing Arabic test was updated to inspect atlas bitmap orientation because `Glyph::chr` now remains logical rather than describing a manually reordered display string. It also verifies an inherited-neutral paragraph and an LTR paragraph in the same balloon. The targeted RTL test passes.
|
||
|
||
Pointer-driven caret changes explicitly interrupt IME after 0.37 stopped treating an already-focused `request_focus` as an interruption. The canvas regression passes while Space/Enter remain non-interrupting. The Ctrl-wheel anchor regression exposed a ScrollArea stale-bounds/previous-momentum defect; its fork repair has independent pre-fix failing controls and is under qualification. Native GUI traces for the wired E21b outgoing adapters, immutable publication pins and paired application performance remain outstanding.
|
||
|
||
|
||
## Latest local qualification — 2026-10-24
|
||
|
||
The candidate starts at `14001aee651090932fe6a6e23898f38b819f8c61` and includes the three-file
|
||
content overlay from observed main `c524027f34be2d1322b23064f80662d3638cf880` (root manifest,
|
||
Cargo.lock and shared-PDF documentation). The canonical lock SHA-256 is
|
||
`7f8878d742ad7ceab8d38dfcf878808bfd82c449088c7563c740e20372840865`. Independent reviews cover
|
||
the migration, stale-composition ownership, grapheme-safe edit/wrap boundaries, deterministic
|
||
pagination fixtures, normal production font features and current-main PDF overlay.
|
||
|
||
The final full CI run passed fmt, strict clippy, tests, i18n-report, doctests, assets, layers and
|
||
WASM (16 crates). Its main test stage reports 4,106 passed, zero failed, four ignored and one
|
||
filtered test across 27 result summaries. Engine: 941 passed; layout: 503 passed; UI: 904 passed,
|
||
two ignored and one filtered. The separate i18n-report stage runs that filtered report test:
|
||
one passed, 4,110 filtered. Those filtered counts are selections over the same test binaries,
|
||
not additional unique tests. The doctest stage selected zero doctests. Earlier narrower runs
|
||
are not added to these totals. Full logs, source hashes and review bindings are retained in the
|
||
local task evidence under `final-landing/wordcraft-local-commit`.
|
||
|
||
A separate normal-dependency production font probe passed for the visible table-style `⊞`
|
||
marker without external/system fonts or dev-dependency font unification. That probe preceded
|
||
the PDF-only overlay; font/runtime inputs remain unchanged. It establishes the bounded glyph
|
||
claim, not a full typography or platform result. Native input/GUI/capture/save/reopen traces,
|
||
paired app performance and memory/noise qualification, platform and installer execution and
|
||
explicit approval before each push/tag/new PR remain outstanding. No publication is claimed.
|
||
|
||
Revision 5: replace stale pending local checks with the actual reviewed-source full-CI pass,
|
||
current immutable pins and bounded remaining acceptance gates; preserve earlier evidence/history.
|
||
|
||
|
||
### Bundled-font notice completion — pending package verification
|
||
|
||
Canonical egui0.37 default_fonts embeds `EGUI_ICONS` (the small emoji-icon-font subset), and
|
||
monochrome_emoji_fonts embeds its parent emoji-icon-font face. WordCraft starts from
|
||
`FontDefinitions::default()` in `theme.rs`. NOTICE now carries the exact upstream MIT text,
|
||
including Copyright (c) 2014 John Slegers, and ATTRIBUTION identifies both dependency assets.
|
||
The PDF notice names the actual pinned 17744430 revision. Shared Linux/web/CLI staging, macOS app
|
||
resources, Windows portable ZIP and unconditional MSI notice components now include NOTICE
|
||
and ATTRIBUTION independently of optional craft-fonts. The existing optional OFL rules remain.
|
||
This documentation/packaging follow-up changes no runtime/font/manifests/lock bytes; its asset,
|
||
staging and fresh review gates are pending. Final packages and platform execution remain
|
||
unqualified. This scoped repair is separate from the six new craft-fonts faces and does not
|
||
claim a complete historical bundled-font audit. Exact upstream license bytes are preserved in
|
||
the local commit evidence.
|