mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-30 05:37:00 -06:00
Merge branch 'main' into pr/macros-ui
This commit is contained in:
@@ -6,14 +6,41 @@ Start the desktop app with a control port:
|
||||
gridcraft --control 7979 # or GRIDCRAFT_CONTROL_PORT=7979
|
||||
```
|
||||
|
||||
Then send newline-delimited JSON to `127.0.0.1:7979`. Every request line gets one reply line:
|
||||
## Authentication
|
||||
|
||||
The control port is reachable by every program on the machine, and by a web page that POSTs
|
||||
`text/plain` to `127.0.0.1:7979`, so every connection must prove it knows the app's token
|
||||
(#777). **There is no way to turn this off.**
|
||||
|
||||
- The app makes a token at start (256 random bits from the operating system, kept in memory) and
|
||||
writes it to a file only you can read: `$XDG_RUNTIME_DIR/gridcraft/control-<port>.token` (else
|
||||
`~/.cache/gridcraft/…`; Windows: `%USERPROFILE%\.gridcraft\…`; the folder is mode 0700 and the
|
||||
file 0600 on Unix). The path is printed on stderr. The token itself is never printed or logged.
|
||||
- Or choose the token yourself: `GRIDCRAFT_CONTROL_TOKEN=…` (preferred) or `--control-token …`
|
||||
(visible in the process list), at least 16 characters. The app then writes no file.
|
||||
- The first line of a connection must be
|
||||
`{"id":0,"method":"auth","params":{"token":"…"}}`; the reply is
|
||||
`{"id":0,"ok":true,"result":{"authenticated":true}}`. Any other first line (a wrong or missing
|
||||
token, another method, text that isn't JSON such as an HTTP request, no line within 10 s, or
|
||||
one longer than 4 KiB) gets `{"ok":false,"error":"authentication required"}` and the
|
||||
connection is closed. After that, a line that isn't JSON also closes the connection.
|
||||
- `gridcraft-cli send`/`mcp --connect` and `gridcraft_mcp::Remote` do this themselves: they use
|
||||
`GRIDCRAFT_CONTROL_TOKEN` if set, else the token file of that port (so they work for the same
|
||||
user on the same machine without setup). Your own client reads the file or the variable.
|
||||
|
||||
## Requests
|
||||
|
||||
Then send newline-delimited JSON to `127.0.0.1:7979` (after the `auth` line). Every request line gets one reply line:
|
||||
|
||||
```json
|
||||
{"id": 1, "method": "engine.execute", "params": {"command": "cell.set", "params": {"cell": "B2", "input": "=SUM(A1:A9)"}}}
|
||||
{"id": 1, "ok": true, "result": {"cell": "B2", "value": "45"}}
|
||||
```
|
||||
|
||||
Errors come back as `{"id": …, "ok": false, "error": "…"}`.
|
||||
Errors come back as `{"id": …, "ok": false, "error": "…"}`. Asynchronous work that
|
||||
finishes later than the reply (a `ui.screenshot` capture) adds its own `request` token to
|
||||
the reply so the client can match it; a capture that completes after its 8 s deadline returns
|
||||
the timeout error and writes no file.
|
||||
|
||||
A line that is not a JSON object with a string `method` (an HTTP request, text that isn't JSON,
|
||||
bytes that aren't UTF-8, a line over 64 MiB) gets one error reply and the server closes the
|
||||
@@ -30,18 +57,32 @@ connection; nothing sent after it runs. Blank lines are skipped.
|
||||
| `sheet.read` | `{range?, sheet?, formulas?, formatted?}` | Values (or formulas / displayed text) of a range |
|
||||
| `cell.get` | `{cell?}` | One cell: value, formula, displayed text, style, merge, comment, link |
|
||||
| `ui.inspect` | | UI state: ribbon tab, editor, open dialog, Backstage page, message, grid rect, scroll, perf |
|
||||
| `ui.click` / `ui.drag` / `ui.move` | `{x, y, toX?, toY?, button?, count?, shift?, cmd?, alt?}` | Real pointer input in window points; refused with an error if it would leave more than 10,000 pointer events queued |
|
||||
| `ui.cell.click` / `ui.cell.drag` | `{cell}` / `{from, to}` | Pointer input at a cell's centre (works in point mode while editing); same 10,000-event queue limit |
|
||||
| `ui.key` / `ui.text` | `{key, shift?, cmd?, alt?, ctrl?}` / `{text}` | Keyboard input (typing in the grid starts editing) |
|
||||
| `ui.click` / `ui.drag` / `ui.move` | `{x, y, toX?, toY?, button?, count?, shift?, cmd?, alt?}` | Real pointer input in window points; refused with an error if it would leave more than 10,000 synthetic events or 8 MiB of synthetic input queued (pointer, key, text and move events share this budget) |
|
||||
| `ui.cell.click` / `ui.cell.drag` | `{cell}` / `{from, to}` | Pointer input at a cell's centre (works in point mode while editing); same shared queue limits |
|
||||
| `ui.key` / `ui.text` | `{key, shift?, cmd?, alt?, ctrl?}` / `{text}` | Keyboard input (typing in the grid starts editing); same shared queue limits, and one `ui.text` string may be at most 1 MiB |
|
||||
| `ui.edit.begin` / `ui.edit.text` / `ui.edit.commit` / `ui.edit.cancel` | `{text?}` / `{move?: down\|right\|up\|left\|none}` | Drive the cell editor |
|
||||
| `ui.ribbon` | `{tab}` | Switch ribbon tab |
|
||||
| `ui.dialog` / `ui.dialog.set` / `ui.dialog.confirm` / `ui.dialog.cancel` | `{name}` / `{field, value}` | Open, fill in and confirm dialogs; `confirm` runs the dialog's command with the same parameters as its OK button |
|
||||
| `ui.backstage` (via `engine.execute`) | `{page?: home\|new\|open\|info\|saveAs\|print\|export\|options\|account\|close}` | Open the File tab's Backstage view on a page, or close it (`ui.inspect` reports the open page as `backstage`) |
|
||||
| `ui.screenshot` | `{path?}` | PNG of the window (base64 when no path). Animations are settled first: the picture shows the final state |
|
||||
| `ui.screenshot` | `{path?}` | PNG of the window (base64 when no path). Animations are settled first: the picture shows the final state. On the native app it works while hidden or occluded when the platform still reports the current content size (the request asks for a hidden paint); a minimized window has no capturable size, so its request stays queued until the window is restored and otherwise reports the 8 s timeout. In a **hidden browser tab** the web runner does not implement that paint request, so the capture waits until the tab is shown again or reports the 8 s timeout. Up to 4 outstanding requests; retained images are limited to 128 MiB each and 256 MiB in total (each request reserves the largest image the ±1-pixel rounding rule can retain), one capture's transient encoding working set to 256 MiB, a base64 reply to 64 MiB, and retained images plus one capture's encoding working set to 512 MiB; a request that would exceed a budget is refused without evicting earlier ones. See [Screenshot budgets](#screenshot-budgets). A resize/DPI/zoom/visibility/occlusion change during capture is retried within the bounded retry budget or reported instead of saving stale pixels; the identity intentionally includes zoom and occlusion even though the expected physical size may be unchanged, so a capture never crosses layout generations. Every reply — success, refusal or timeout — carries the request `token` as `request` |
|
||||
| `ui.set` | `{dark?, formulaBar?, ribbonCollapsed?, animations?}` | UI preferences (`formulaBar` runs `view.formulaBar`) |
|
||||
| `ui.resize` / `ui.focus` | `{width, height}` | Window control |
|
||||
| `app.open` / `app.save` / `app.quit` | `{path, password?}` / `{path?}` | Files and lifetime. `password` opens a password-protected workbook; without it, such a workbook returns an error (never a dialog) |
|
||||
|
||||
### Screenshot budgets
|
||||
|
||||
`ui.screenshot` keeps up to four requests outstanding. Each admitted request reserves the largest
|
||||
image the ±1-pixel rounding rule can retain: 128 MiB per image and 256 MiB in total across
|
||||
requests, and a request that would exceed a budget is refused without evicting earlier ones.
|
||||
Encoding is bounded separately: one capture's transient working set may be 256 MiB (the larger of
|
||||
the retained image plus its RGBA copy, or, for a base64 reply, the image plus the PNG stream plus
|
||||
the encoded text), the base64 reply itself may be 64 MiB, and retained images plus one capture's
|
||||
encoding working set may be 512 MiB. Base64 text counts toward the encoding working set, so a
|
||||
large pathless capture can be refused there or by its base64 length even when its retained image
|
||||
fits. `path` writes the PNG to a file instead of returning base64 text and so avoids those two
|
||||
base64 costs, but it does not bypass the per-image, aggregate, encoding-working-set or peak
|
||||
limits.
|
||||
|
||||
Any other method name that is a command id (e.g. `home.bold`) runs that command.
|
||||
|
||||
Use `engine.execute` with `{command: "view.theme", params: {mode: "system"}}`
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
# egui 0.37 local port — follow-ups and gates
|
||||
|
||||
Status: local implementation on GridCraft `b8771d51fb4da60bd48c13cf313d6d6014dc5e66` with canonical
|
||||
immutable dependency pins. Final managed full CI
|
||||
`egui037-gridcraft-candidate-20261024-full-ci-queue-07` succeeded at 2026-10-24 05:21 UTC after both
|
||||
accessibility repairs: all ten stages passed, with 2,249 tests passed, none failed and one existing
|
||||
release timing experiment ignored. The representative explicit accessibility/capture checks
|
||||
passed. The required local precommit CI gate is complete for the formatted runtime source;
|
||||
this final refresh changes only the two migration documents. Native/platform/performance
|
||||
acceptance remains separate. No code
|
||||
push or PR publication is authorized; each requires fresh human approval of contents and validation.
|
||||
|
||||
## Current integration inputs
|
||||
|
||||
- Root Cargo patches select the egui family at `173cc7375d35dc5fbc5b5d0994fdf787f547c5d1`
|
||||
and winit at `ae48c756a3b81125eb796c0cc6dccf60990d1181`. Selected egui packages are 0.37.0;
|
||||
there is no machine-local path override or second egui 0.36 source.
|
||||
- `craft-ui` and `craft-print` both pin `c488caa9fbc2fb1a86e4674887e9b80b598ebbfa`.
|
||||
The shared capture slot is consumed through that revision. Canonical Cargo.lock SHA256 is
|
||||
`98209e84412b8ce08bf290ab706c20909389ca6d1e6668aca929f717ad55ea2f`.
|
||||
- CI, release and FreeBSD select craft-fonts `b076417b5880bd4154bd22f00695b1d28c6bea83`.
|
||||
Local validation uses command-scoped private Git mirrors for unpublished immutable inputs;
|
||||
clean remote fetching after approved publication remains a separate gate.
|
||||
- The shared capture contract fits GridCraft's requirements without changes: one slot per request,
|
||||
`take()` at most once, `cancel()`/drop makes late callbacks inert, a per-image byte bound, and a
|
||||
viewport waker that runs after the slot lock is released. GridCraft adds only its admission
|
||||
budget, request records, geometry epoch and output routing around it.
|
||||
- The `CaptureSlot` callback holds only a weak reference and never encodes, writes a file or sends
|
||||
a control reply; GridCraft drains the slot from `logic` and writes outside the slot.
|
||||
|
||||
## Capture lifecycle repair (2026-10-23)
|
||||
|
||||
The previous managed run of this checkout (`egui037-gridcraft-candidate-20261023-tests-01`)
|
||||
finished with 154 passed and 8 failed: five screenshot/lifecycle tests dropped an unapplied
|
||||
`TexturesDelta`, two more lifecycle tests failed on the same atlas delta, and
|
||||
`formula_bar::spill_tests::formula_bar_click_and_theme_presentation` reported the Name Box
|
||||
`TextInput` without an accessible name. Those failures are historical; final full CI07 passed the repaired lifecycle and
|
||||
accessibility tests.
|
||||
|
||||
The local source now addresses the fresh review's capture findings:
|
||||
|
||||
- A completion that arrives after the request deadline is refused with the timeout error before
|
||||
any encoding or file write; timeouts, geometry mismatches, oversize images and removed
|
||||
requests all release their reservation. Every screenshot reply carries its `request` token.
|
||||
- Screenshot deadlines and AutoSave pacing use a monotonic clock (`std::time::Instant` natively,
|
||||
`performance.now()` on wasm). `now_ms()` stays the wall clock for user-visible timestamps such
|
||||
as Backstage's "last changed".
|
||||
- `CaptureGeometry::current` reads `ViewportInfo` (refreshed by `run_logic`) for the viewport
|
||||
`inner_rect` and `native_pixels_per_point * zoom`, falling back to the last screen rect; a
|
||||
hidden resize is therefore seen without a UI pass. Issuance and drain update the geometry and
|
||||
its epoch through one helper, and the slot is created at issuance with a per-image bound equal
|
||||
to the aggregate reservation (which includes the accepted one-pixel rounding).
|
||||
- A request admitted while the window has no size (minimized or not laid out) reserves
|
||||
`UNKNOWN_CAPTURE_BYTES` and waits for a real size; it times out with a size-specific message.
|
||||
Drain runs before admission, and settling counts one non-discarded egui pass per request.
|
||||
- The geometry identity intentionally includes `zoom`, `visible` and `occluded` even when the
|
||||
expected physical size is unchanged. A capture is bound to one layout generation; the two-retry
|
||||
budget followed by an explicit error is the accepted bounded failure, not a reason to compare
|
||||
only the expected size.
|
||||
- A synthetic step consumed by a font-gated `ui` early return is offered again on the next pass
|
||||
(the hook tracks the `cumulative_pass_nr` at injection), and font-set changes wait while an
|
||||
input step is pending. Re-offering the step is safe: the early-returning pass handled no input,
|
||||
so egui's state changes from those events (keys down, pointer down) have not applied a step.
|
||||
- The Name Box and the formula-bar/in-cell editors carry explicit accessible names, so the strict
|
||||
kittest accessibility check passes without disabling it or the missing-glyph policy.
|
||||
|
||||
### Shared eframe web follow-up: `RequestPaintWhileHidden`
|
||||
|
||||
Native `egui-winit` handles `ActionRequested::wants_paint()` for `RequestPaintWhileHidden` and
|
||||
paints the hidden viewport. The pinned `eframe` web runner does not: a hidden tab runs only
|
||||
`run_logic`, and `handle_viewport_commands` handles `Screenshot`/`SetTheme` while every other
|
||||
command — including `RequestPaintWhileHidden` — falls into its unhandled-command log. A queued
|
||||
`ui.screenshot` or a queued `ui.key`/`ui.text` step in a hidden browser tab therefore cannot reach
|
||||
its `ui` pass until the tab is visible; the screenshot request reports its timeout instead. The
|
||||
fix belongs in the shared eframe web runner (paint a hidden tab on request, with the same
|
||||
queued-work trigger GridCraft sends from `logic` on native), not in a GridCraft-local channel.
|
||||
GridCraft therefore sends `RequestPaintWhileHidden` only off wasm (a visible web tab still gets
|
||||
`request_repaint`). The control protocol documents this web limitation instead of claiming hidden
|
||||
web screenshots work.
|
||||
|
||||
## E21b — application qualification remains open
|
||||
|
||||
The final canonical egui fork includes `IMEOutput::surrounding_text`, owner/context generation
|
||||
and the Wayland outgoing adapter; the earlier statement that no shared producer/adapter exists is
|
||||
superseded. GridCraft must qualify its actual formula-bar, in-cell and text-box editing paths,
|
||||
including available-empty versus unavailable, stale epoch/serial deletion and commit, deletion-only
|
||||
batches, composition ownership and two-surface isolation. Full CI07 is not a native IME or complete
|
||||
E21b/W27 acceptance result. No application-local API stub is introduced.
|
||||
|
||||
## Review repair (2026-10-24)
|
||||
|
||||
- `RequestPaintWhileHidden` is sent only for a queued/settling request that has a capturable size;
|
||||
an issued capture relies on the `Screenshot` command's `wants_paint`, and a minimized request no
|
||||
longer forces full hidden frames while it waits.
|
||||
- A slot-level per-image refusal (an image larger than the reservation) now spends the same bounded
|
||||
retry as a smaller image, instead of failing only when the window grew.
|
||||
- The shared deterministic harness, the `Text Box` accessible-name translations, the explicit
|
||||
`PasteImage`/`MiddleClickPaste` no-op choice, the lifecycle once-per-host-frame tests
|
||||
(autosave, title, print poll, discard after issuance) and wheel unit/source/phase coverage are in
|
||||
the working tree. Final full CI07 exercised these repairs and the explicit accessibility
|
||||
assertions below successfully.
|
||||
|
||||
## Remaining acceptance gates
|
||||
|
||||
- Required local CI is complete: full CI07 passed i18n-report, fmt, strict workspace all-target
|
||||
clippy, workspace tests, doctest, assets (34 attributed files), layers, WASM, parity and keytips.
|
||||
The existing timing-sensitive `recalc_with_range_formulas_scales_linearly` test remains ignored
|
||||
by the normal CI command; it is not a performance result. No additional CI execution is claimed
|
||||
for this documentation-only refresh.
|
||||
- Strict `egui_kittest` harness qualification completed for the selected end states: `tests/common/mod.rs` now installs
|
||||
`theme::pinned_font_definitions()` (bundled faces plus the pinned craft-fonts input, no host
|
||||
system scan), `SheetApp::use_prepared_fonts()` keeps that set through the first `logic` pass, and
|
||||
`MissingGlyphPolicy::Panic` stays on. Since `run_steps` does not invoke kittest's accessibility
|
||||
checker, selected ribbon, locale, pane/menu, dialog, active-editor, backstage, text-box and
|
||||
capture/repaint end states now call `check_accessibility()` explicitly; these assertions passed in CI07,
|
||||
without claiming coverage of every UI state. `CRAFT_FONTS_DIR` (and
|
||||
`CRAFT_FONTS_REQUIRED=1` for acceptance) is the CI contract; without it the deterministic
|
||||
bundled set is used and the all-catalog language test records the `Ja`/`Ko`/`Zh` gap.
|
||||
- End-to-end hidden/occluded `ui.screenshot` with a capturable current size and a minimized
|
||||
request that waits for restore (or times out), through a real wgpu/kittest or native window; the
|
||||
per-image/aggregate budget stress cases, resize-back/DPI/zoom epoch retries, and native
|
||||
clipboard/PRIMARY/IME evidence. No native hidden/minimize/restore run has been performed for
|
||||
this source.
|
||||
- Native Linux, Windows clone, browser and macOS workflows; performance, text-tessellation,
|
||||
Windows renderer-construction and print measurements against the recorded baseline.
|
||||
- Accessibility roles/names beyond the mechanical `Role` port (G3) and native screen-reader smoke
|
||||
tests.
|
||||
|
||||
## Font provisioning (2026-10-23, font-coverage integration)
|
||||
|
||||
The font gap is addressed in the font set itself. Test harnesses keep
|
||||
`MissingGlyphPolicy::Panic` and the enabled accessibility-check policy; the representative explicit
|
||||
checks above are needed to execute that policy on `run_steps` fixtures. No test calls `allow_missing_glyphs()`
|
||||
or disables the accessibility check, and production still renders Tofu for a character no face
|
||||
has.
|
||||
|
||||
- `gridcraft-ui-egui` enables `egui/default_fonts` and `egui/monochrome_emoji_fonts`. egui 0.37
|
||||
moved Noto Emoji and emoji-icon-font behind the monochrome feature, and with `default_fonts`
|
||||
alone no installed face has `✕` or `📋` (Hack, which the theme adds as a fallback, has the
|
||||
arrows and triangles but neither of those).
|
||||
- `theme::font_definitions_with` appends egui's bundled `Hack` face (arrows, triangles, `↳`) behind
|
||||
the UI/cell and emoji faces of every family, and appends the optional craft-fonts faces at the
|
||||
very end. The order is: app/bundled faces, Hack/emoji, system script fallbacks, permitted craft
|
||||
faces last. A craft face never displaces a system face that already has the glyph.
|
||||
- `crates/ui-egui/build.rs` embeds the faces named by `CRAFT_FONTS_DIR` through
|
||||
`crates/ui-egui/src/craft_fonts_policy.rs`, the single policy shared by the build script and the
|
||||
tests. Nothing reads a fixture path from Cargo configuration: the checked `.cargo/config.toml`
|
||||
holds only the `xtask` alias, so every local and managed run sets the variable explicitly. With
|
||||
it unset the generated table is empty and the app builds, tests and runs; a script that no
|
||||
installed system face and no bundled face covers stays the platform gap it was before (Tofu in
|
||||
production, and the strict kittest harness still panics on it by design).
|
||||
- Required faces, matched by exact family and style and by their exact manifest script tags:
|
||||
`BIZ UDPGothic` `Regular` and `Bold` (`Jpan,Latn`), `NanumGothic` `Regular` (`Hang,Kore`),
|
||||
`Noto Sans Symbols` `Regular` (`Zsym`) and `Noto Sans Symbols 2` `Regular` (`Zsym`).
|
||||
`NanumGothic` has no space, and `Noto Sans Symbols` is not `Noto Sans Symbols 2`.
|
||||
- `CRAFT_FONTS_REQUIRED=1` turns a missing `CRAFT_FONTS_DIR`, a missing `fonts/manifest.txt`, a
|
||||
malformed manifest row, a missing font file, an unexpected style of a required family or any
|
||||
missing required face into a build error.
|
||||
- Barred families never enter the font set, although the shared repository keeps them for other
|
||||
consumers: `Adobe*`, `Source Han*`, `Source Sans*`, `Source Serif*`, `Noto Sans CJK*` and
|
||||
`Noto Serif CJK*`. There is deliberately no broad `Noto Sans` prefix, because it would also
|
||||
reject the permitted `Noto Sans Symbols` faces.
|
||||
- Web (`wasm32`) embeds only the explicit allowlist: `BIZ UDPGothic` `Regular` and `Bold`,
|
||||
`NanumGothic` `Regular`, `Noto Sans Symbols` `Regular` and `Noto Sans Symbols 2` `Regular`; no
|
||||
CJK collection and no document-only face. Those five files are 12,970,840 bytes raw. Raw font
|
||||
bytes, raw wasm and compressed wasm are **not measured yet**: the size gate is a parent
|
||||
acceptance item, not a claim made here.
|
||||
- Native builds opt into the OFL `LXGW WenKai Lite` face from craft-fonts'
|
||||
`fonts/simplified-chinese-manifest.txt` (the same face DeckCraft uses). The barred `Noto Sans CJK`
|
||||
collection still never enters; system Han faces still lead when installed. `CRAFT_FONTS_REQUIRED=1`
|
||||
native builds require the Chinese manifest, ordinary builds tolerate an older checkout without
|
||||
it, and `WEB_ALLOWED` excludes it. The Chinese samples join the strict script test only when the
|
||||
face is embedded.
|
||||
- Native also embeds every other non-barred manifest row (for example `BIZ UDMincho`,
|
||||
`Shippori Mincho`, `Noto Sans Arabic/Georgian/Hebrew/Thai`). They are OFL and asset-policy safe,
|
||||
but the actual set is wider than the five required faces; the size gate must measure it rather
|
||||
than citing only the web allowlist.
|
||||
- No-font/default mode: the bundled faces supply `↘ ◀ ↑ 📋` and the rest of the interface's
|
||||
symbols, which `tests/symbol_glyphs.rs` asserts strictly. `✕`, `✓` and `⌃` have no bundled egui
|
||||
face, so chrome draws them instead of painting them: the Symbol gallery and the close/check
|
||||
controls paint the code-drawn `Icon::Check`/`Icon::Close` (`icons::fallback_for_symbol`,
|
||||
`widgets::glyph_button`), and a tooltip whose symbol is missing falls back to `^`
|
||||
(`theme::symbol_fallback` through `theme::available_text`). Only workbook text still needs a real
|
||||
glyph, because its characters are the user's data. The test documents `✓`/`⌃` as optional-input
|
||||
gaps rather than claiming them.
|
||||
- Extended acceptance mode (`CRAFT_FONTS_DIR` set, and `CRAFT_FONTS_REQUIRED=1` for release):
|
||||
`tests/symbol_glyphs.rs` requires the exact union `↘ ✕ ◀ ↑ 📋 ✓ ⌃` in both `Proportional` and
|
||||
`CELL` plus the Korean interface labels, and fails instead of skipping when a loaded or required
|
||||
set lacks a face. `⌃` comes from `Noto Sans Symbols`, `✕ ◀ ✓` from `Noto Sans Symbols 2`,
|
||||
`↘`/`↑` from either Symbols face (NanumGothic also has them), and `📋` from egui's emoji faces.
|
||||
- The required faces live in the pinned `craft-fonts` checkout at
|
||||
`b076417b5880bd4154bd22f00695b1d28c6bea83`. Point `CRAFT_FONTS_DIR` at that checkout: the older `fixtures/craft-fonts` snapshot has no Korean or
|
||||
symbol rows, and a loaded set without the required faces fails the extended checks by design.
|
||||
- `theme::fallback_covers_its_script` is strict whenever a system or craft-fonts face provides the
|
||||
script, now including Arabic and Georgian; its skip prints the script name and sample.
|
||||
- The production positive-rectangle fix in `formula_bar.rs`, the `Role::DisclosureTriangle`
|
||||
queries, the linear per-line editor-height assertions and the explicit accessible names for the
|
||||
Name Box and both editors are unchanged.
|
||||
|
||||
|
||||
## Explicit G1 accessibility repair — 2026-10-24
|
||||
|
||||
Seven existing integration tests now check rendered ribbon/light-dark, each supported locale,
|
||||
pivot pane and open menu, fifteen dialogs, active cell/formula edits, backstage pages and active
|
||||
text-box/committed states. The existing capture callback test now uses a full SheetApp Harness,
|
||||
asserts the Name Box tree is present, and checks accessibility before issuance and after callback
|
||||
drain/repaint. Its manually supplied pixels still test callback lifecycle rather than GPU readback;
|
||||
the explicit no-GPU renderer prevents kittest from silently turning it into a platform test.
|
||||
One-callback, request-token, one-reply and released-reservation assertions remain.
|
||||
|
||||
All eight affected tests (seven integration tests and the capture unit test) passed in full CI07.
|
||||
The four CI06 failures exposed unnamed Options/ribbon ComboBoxes, custom format input and pivot
|
||||
layout control; visible localized labels or precise semantic names now identify them. The fresh
|
||||
source review found no blocker; CI07's formatting changed only wrapping/trailing commas in those
|
||||
four reviewed production files. Original roles, input sequences and behavior assertions remain.
|
||||
Full CI05 is historical pre-repair evidence and CI06 remains a failed run.
|
||||
|
||||
Actual native screenshots/hidden/minimize/restore, screen-reader output, application E21b IME and
|
||||
platform/performance acceptance remain open. Full CI07 uses the canonical lock, required craft-fonts
|
||||
input, command-scoped exact-source mirrors and the private Vulkan loader; it is not host-loader or
|
||||
clean public-fetch qualification. Exact job/log hashes, review union, complete final source inventory
|
||||
and documentation-only delta are preserved in local task evidence under
|
||||
`egui037-migration-20261022/final-landing/gridcraft-final-qualification`.
|
||||
|
||||
Revision: 2026-10-24 — replaced pending accessibility/CI statements with actual full CI07 results,
|
||||
corrected the shared outgoing IME inventory, and retained native/performance/publication gates.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -88,7 +88,7 @@ This improves series preparation; it does not change the model-fitting or season
|
||||
| Array lifting: an array in a scalar argument evaluates the function per element | done. The Analysis ToolPak functions (EOMONTH, EDATE, WORKDAY(.INTL), NETWORKDAYS(.INTL), YEARFRAC, WEEKNUM, the COUP*, PRICE*, YIELD*, ODD*, TBILL*, ACCRINT*, AMOR*, DISC, DURATION, CUMIPMT/CUMPRINC, EFFECT, NOMINAL, DOLLARDE/FR, XNPV/XIRR rate and guess, FVSCHEDULE principal, ISEVEN/ISODD, QUOTIENT, MROUND, FACTDOUBLE, SQRTPI, RANDBETWEEN, SERIESSUM, the base conversions, CONVERT, DELTA, GESTEP, ERF/ERFC, BESSEL*, COMPLEX and IM* except IMSUM/IMPRODUCT) give `#VALUE!` for a reference to more than one cell in a value argument, as Excel 365 does in both dynamic-array and legacy formulas; array constants and computed arrays (`A1:A3+0`) still lift, and range arguments (holidays, schedule, cash flows) and the .INTL weekend accept ranges. Difference: BYROW/BYCOL pass their row or column to the LAMBDA as values, so `BYROW(A1:B3,LAMBDA(r,EOMONTH(r,0)))` lifts where Excel gives `#VALUE!` |
|
||||
| Trim references `A1:.E10` (trailing), `A1.:E10` (leading), `A1.:.E10` (both), also on whole rows/columns (`A:.A`) | done; evaluated as `TRIMRANGE(range, code, code)`, adjusted on insert/delete; files store `_xlfn.TRIMRANGE(range,code,code)` (Excel's own file spelling unconfirmed in public docs), `_xlfn._TRO_ALL/_TRO_LEADING/_TRO_TRAILING(...)` read too |
|
||||
| LET, LAMBDA, named LAMBDAs, ISOMITTED, MAP/REDUCE/SCAN/BYROW/BYCOL/MAKEARRAY | done. A LAMBDA that calls itself more than 140 times, or a formula nested past the depth limit, gives `#NUM!` (the target app allows deeper recursion, #1581). Calling a defined name that holds an error value gives that error, not `#NAME?` |
|
||||
| Iterative calculation for circular references | done: with Enable Iterative Calculation on (`formulas.calculationOptions` `{iterative, maxIterations, maxChange}`, saved as `<calcPr iterate iterateCount iterateDelta>`), loops are evaluated again from their previous values (blank = 0) until no cell on a loop changes by Maximum Change or Maximum Iterations is reached; off, cycles give `#CIRC!`. Differences: no Options dialog yet (command only); GridCraft recalculates on open, so a loop that never settles (`=A1+1`) advances again each time the file is opened |
|
||||
| Iterative calculation for circular references | done: with Enable Iterative Calculation on (`formulas.calculationOptions` `{iterative, maxIterations, maxChange}`, saved as `<calcPr iterate iterateCount iterateDelta>`), loops are evaluated again from their previous values (blank = 0) until no cell on a loop changes by Maximum Change or Maximum Iterations is reached; off, cycles give `#CIRC!`. Set in File › Options › Formulas (Enable iterative calculation, Maximum Iterations 1 to 32767, Maximum Change 0 or more; the two fields are disabled while iteration is off). Differences: GridCraft recalculates on open, so a loop that never settles (`=A1+1`) advances again each time the file is opened
|
||||
| 15-significant-digit comparison semantics | done (#57): `=0.1+0.2=0.3` is TRUE, and MATCH and COUNTIF compare the same way. Arithmetic keeps full precision, as Excel does — `=(0.1+0.2)-0.3` is 5.55E-17 |
|
||||
| External workbook references (`[Book.xlsx]Sheet1!A1`) | parsed; links dropped on open, no update |
|
||||
| Localized function names and `;` separators | **missing** (#45); en-US only |
|
||||
@@ -131,6 +131,7 @@ workbooks; results checked in as data, not Excel files) and fixing what it finds
|
||||
| 2026-10-19 | minor | Long flat operator chains (#916): 4,096 operands (Excel's 8,192-character limit) no longer overflow the stack when entered, filled, adjusted or saved; chains past 64 operands are no longer saved as dynamic-array formulas; Spanish entry no longer refuses chains past 256 operands |
|
||||
| 2026-10-17 | minor | 15-significant-digit comparison moved from missing to done (#57), and the resolved caveats on the 3-D reference, dynamic-array and legacy-array rows cleared (#140, #133, #139, #156) |
|
||||
| 2026-10-16 | minor | FORECAST.ETS gap interpolation uses a forward-only known-point scan instead of rescanning each gap for every missing value; numerical behavior and missing-point limits are preserved (#1042) |
|
||||
| 2026-10-15 | minor | Iterative calculation: Maximum Iterations and Maximum Change are set in File › Options › Formulas (dropped "no Options dialog yet") |
|
||||
| 2026-10-15 | minor | MEDIAN and AGGREGATE median use an overflow-safe midpoint (#1579); regressions cover finite extremes, tiny values and dependent recalculation |
|
||||
| 2026-10-15 | minor | ADDRESS quotes a sheet name the way Excel does: names that read as an A1 cell (`A1`) or an R1C1 reference (`R1C1`, `RC`) and names starting with `.` get quotes; `TRUE` / `FALSE` stay bare (#1458) |
|
||||
| 2026-10-14 | minor | MATCH, XMATCH, VLOOKUP, HLOOKUP, XLOOKUP and LOOKUP compare numbers exactly in every match and search mode, as Excel 365 does: `MATCH(0.1+0.2,{0.3},0)` is `#N/A` and 0.3 sorts below 0.30000000000000004 in approximate and binary search. `=`, SWITCH and the criteria functions (COUNTIF(S), SUMIF(S), AVERAGEIF(S), MAXIFS…) keep comparing at 15 significant digits, also as observed |
|
||||
|
||||
+2
-1
@@ -65,7 +65,7 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
|
||||
equality (#57, so `=0.1+0.2=0.3` is TRUE and MATCH and COUNTIF agree), spill updates after
|
||||
structural edits and cleared blockers (#133, #139; PRs #148, #157), 3-D span deletion (#140),
|
||||
and qualified names after a sheet rename (#134). Iterative calculation for circular references
|
||||
landed too (no Options dialog yet, which is §13's).
|
||||
landed too, with its settings in File › Options › Formulas.
|
||||
- **Impact:** wrong numbers are worse than missing features, so this entry is kept until the
|
||||
oracle suite of §15 can say the category is clear rather than only these five reports being so.
|
||||
- **Estimate:** — [function-parity.md](function-parity.md#formula-language)
|
||||
@@ -185,6 +185,7 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
|
||||
| 2026-10-23 | minor | §8: PivotTable Show Details: double-clicking a value cell lists its source records on a new sheet (#2364) |
|
||||
| 2026-10-20 | minor | §11: the Japanese catalog covers the dialogs, messages and menus — 943 keys (~43%), matching the pt-BR coverage; a test keeps the two catalogs in step |
|
||||
| 2026-10-17 | minor | §6: the five reports it tracked are closed and their PRs merged (#57, #133, #139, #140, #134; PRs #148, #155, #156, #157); the entry stays open for §15's oracle suite |
|
||||
| 2026-10-15 | minor | §6: Maximum Iterations and Maximum Change in File › Options › Formulas; dropped the stale "no Options dialog yet" |
|
||||
| 2026-10-15 | minor | §13: Solver prepares reports before committing the solution, model and report sheets together; report refusal preserves the workbook and Undo/Redo (#1050) |
|
||||
| 2026-10-13 | trivial | §4: multi-level category axes landed |
|
||||
| 2026-10-12 | minor | §9: next/previous sheet commands retain the current visible sheet and selection when there is no visible destination |
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ Two backends:
|
||||
| Mode | Command | What it drives |
|
||||
|---|---|---|
|
||||
| Headless (default) | `gridcraft-cli mcp [--in book.xlsx \| --sample budget]` | An in-process engine session — no window. |
|
||||
| Remote | `gridcraft-cli mcp --connect 7979` | A running app started with `gridcraft --control 7979` (TCP JSON lines on `127.0.0.1`). |
|
||||
| Remote | `gridcraft-cli mcp --connect 7979` | A running app started with `gridcraft --control 7979` (TCP JSON lines on `127.0.0.1`, authenticated with the app's token: it is read from the app's token file or `GRIDCRAFT_CONTROL_TOKEN`, see [`control-protocol.md`](control-protocol.md)). |
|
||||
|
||||
## Claude Code
|
||||
|
||||
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
# UI parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (macro interface on the Automate tab; ribbon catalog count 276 / 292; earlier: Paste Name lays out only the names in view; Go To matches names in any case and script; one Page Layout view row, with the header and footer side margin marked unverified; earlier: Go To lists the named ranges of every sheet and the references entered before, and takes several names or references separated by commas; F3 Paste Name dialog with Paste List; right-clicking the status bar opens a menu of its statistics, with Numerical Count, Minimum and Maximum added; Ctrl+Shift+U expands and collapses the formula bar; Fill Days, Fill Weekdays, Fill Months and Fill Years in the fill-handle menus for a date; Show Details in the cell menu of a PivotTable value cell; dragging the handle between the sheet tabs and the horizontal scrollbar resizes the tabs; Outline Settings: summary rows above and summary columns left of their detail; New Formatting Rule's formula box takes references clicked or dragged on the grid, #2348; Page Up and Ctrl+Up into frozen rows, Ctrl+Left and Alt+Page Up into frozen columns bring the scrolled area back; Auto Fill Options button after a left-drag of the fill handle (#1403); Show Outline Symbols, Ctrl+8; a too-wide number in a repeat-fill format such as Accounting shows #### instead of losing its leading digits; Esc and Enter close a data validation alert over the cell editor; Ctrl+Enter fill gives each cell the automatic number format typing gives it; keytip letters typed with Alt held, #1846; Number Stored as Text indicator and Convert to Number) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (AutoSum over a selected row puts its total to the right) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
|
||||
Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md).
|
||||
**UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured
|
||||
@@ -29,9 +29,9 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes
|
||||
| Sheet tabs: rename, reorder, colour, hide, scroll | yes | yes | active tab revealed on navigation and resize (#681); New sheet (+) right after the last tab, the strip capped at 60% of the bar (#943); manual wheel/trackpad browsing preserved; next/previous skips Hidden and VeryHidden sheets and retains the current sheet and selection when no visible destination exists; rename focus bug on macOS (#145) |
|
||||
| Status bar: Sum/Count/Average and more, view buttons, zoom | yes | yes; a right-click on the status bar opens a menu that turns the statistics on and off (Average, Count, Numerical Count, Minimum, Maximum, Sum; `view.statusBar`) | done for the statistics; the other status bar options are not customizable |
|
||||
| Task panes (Format Chart, Comments, Watch, Selection, PivotTable fields) | yes | yes; PivotTable source-field list scrollbar sits at the right edge of its pane | element-level Format pane for shapes and pictures missing |
|
||||
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | partial | Options dialog and template gallery missing |
|
||||
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | partial | template gallery missing |
|
||||
| Dark mode, system appearance | yes | yes | done (10-09); follows the system on first run, title-bar switch and File › Display theme (#194, #239); icons, reference colours, primary buttons and the comments pane follow the theme, and a unit test holds every token pair to WCAG 2 contrast in both themes |
|
||||
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | yes (#174, 10-11): the File tab opens a full-window view (Esc or the back arrow return) with Home, New (blank + the three samples), Open (recent + Browse; the recent list tells when each workbook was last changed, 10-12), Info (location, properties, Protect Workbook), Save, Save As, Print (page preview, active sheet or workbook, fit to page, Page Setup), Export (PDF, CSV, web page), Close and Options (`file.options`: theme, language, formula bar, AutoSave, calculation) | no Account, Share or template gallery beyond the samples; Options applies at once (no OK/Cancel dialog) and holds only the settings GridCraft has |
|
||||
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | yes (#174, 10-11): the File tab opens a full-window view (Esc or the back arrow return) with Home, New (blank + the three samples), Open (recent + Browse; the recent list tells when each workbook was last changed, 10-12), Info (location, properties, Protect Workbook), Save, Save As, Print (page preview, active sheet or workbook, fit to page, Page Setup), Export (PDF, CSV, web page), Close and Options (`file.options`: theme, language, formula bar, AutoSave, calculation mode and iterative calculation with Maximum Iterations and Maximum Change, 10-15) | no Account, Share or template gallery beyond the samples; Options applies at once (no OK/Cancel dialog) and holds only the settings GridCraft has |
|
||||
| Dark mode, system appearance | yes | yes | done (10-09) |
|
||||
| Page Layout view (paper view with rulers and header editing) | yes | yes (#329, 10-11): per-sheet view saved in XLSX, pages with margins and gaps on the print pagination, "Click to add data" pages, rulers (inches or centimetres), headers and footers drawn and edited in place with a Header & Footer tab, whose Different First Page and Different Odd & Even Pages give the first page and the even pages their own header and footer (#344), and whose Scale with Document and Align with Page Margins set whether header and footer text follows the print scale and the side margins, on paper and in the view | margins drag on the rulers (#792, with header and footer margins); header/footer pictures missing (1–3 h). Unverified: with Align with Page Margins off, header and footer text sits 0.7 in from the paper edge, a value not yet measured in the target app |
|
||||
| Multiple windows per workbook | yes | no (#162) | see [hardware-parity.md](hardware-parity.md) |
|
||||
@@ -189,6 +189,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-25 | minor | AutoSum (Alt+=, Home › AutoSum) over a selected row puts its total to the right of the row, or in its last cell when that is empty, as in Excel; it wrote a one-cell total under each cell. A selection's empty last column takes each row's total and its empty last row each column's, with the block's total in the corner when both are empty |
|
||||
| 2026-10-25 | minor | Paste Name lays out only the names in view, so a workbook with thousands of names opens it at once; the arrow keys still scroll the chosen name into view. Go To matches a `Sheet2!Name` reference and hides a workbook name behind the active sheet's own name in any case and script (`Größe` = `GRÖSSE`), as formulas do. The two Page Layout view rows are one; the 0.7 in side margin used when Align with Page Margins is off is marked unverified |
|
||||
| 2026-10-24 | minor | Macro step editor: step list with breakpoints, drag to reorder and a command picker, an inspector with reference boxes, a debug bar and grid overlays |
|
||||
| 2026-10-24 | minor | Automate › Macros group (Record Macro, Use Relative References, Run Macro), recording status in the status bar, Macros dialog with macros beside scripts, Record Macro dialog, Ctrl+Shift+letter macro shortcuts; View › Macros keytips open the dialogs |
|
||||
@@ -223,6 +224,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
||||
| 2026-10-16 | minor | Web printing registers one-shot `afterprint` cleanup before opening the dialog; missing content windows and setup, listener or print failures also release the hidden iframe and PDF blob URL (#1054) |
|
||||
| 2026-10-16 | minor | The shared UI clock delegates to the core Unix-millisecond clock, which reads Date.now() on wasm, instead of returning zero there (#1026). Native clock contract tested; browser timing remains unverified |
|
||||
| 2026-10-16 | trivial | Web: preferences (theme, interface size, language, editing options…) are kept in the browser's `localStorage` between visits, as the desktop app keeps `ui.json` and `prefs.json`; before, a reload brought back light mode and the defaults (#1562) |
|
||||
| 2026-10-15 | minor | File › Options › Formulas: Maximum Iterations (1 to 32767) and Maximum Change (0 or more) under Enable iterative calculation, disabled while it is off; invalid entries are refused with a message |
|
||||
| 2026-10-15 | minor | Number-format named colors use their RGB values; indexed `[Color1]`–`[Color56]` resolve through the shared legacy palette in grid and PDF (#1452) |
|
||||
| 2026-10-14 | minor | Spill-child formula bar uses the anchor formula with disabled theme text and blocks formula-bar edit entry; selection and recalculation restore ordinary behavior |
|
||||
| 2026-10-14 | minor | PivotTable source-field list fills the pane width so its scrollbar stays at the list's right edge |
|
||||
|
||||
Reference in New Issue
Block a user