Merge g3 into g4

# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/edit.rs
#	crates/engine/src/cmd/formulas.rs
#	crates/engine/src/cmd/inspect.rs
#	crates/engine/src/cmd/pivot.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/tabs.rs
#	docs/gaps.md
#	docs/ui-parity.md
This commit is contained in:
aiongg
2026-10-26 02:45:41 +00:00
353 changed files with 24668 additions and 3425 deletions
+82 -2
View File
@@ -1,6 +1,6 @@
# Architecture
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (bound Undo's retained cell storage) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-10 · ** Last updated:** 2026-10-24 · **Change:** minor (engine building blocks for multi-command actions) · **Target:** Microsoft Excel (Microsoft 365)
How GridCraft is built today. Rules for contributors are in [`AGENTS.md`](../AGENTS.md).
@@ -33,7 +33,10 @@ including tests, 2026-10-10.
`Session::execute(id, params)`. Commands are `CommandSpec { id, label, menu, shortcut, params,
enabled, run, journal, undoable }`; ids follow Excel's ribbon (`home.bold`, `insert.chart`,
`data.sortAscending`) plus primitives (`cell.set`, `selection.set`, `range.setValues`).
`Session::execute` catches escaped panics and restores the workbook (never-crash guard).
`Session::execute` catches escaped panics and restores the workbook, its selection and its
undo and redo history as they were before the command (never-crash guard); a group that was
open before the command and that the command ended or rolled back stays ended and keeps a
step from its start to the restored workbook, so Undo still returns to the state before it.
2. **Edits** change the model through copy-on-write bands; undo keeps the previous snapshot.
Undo retains at most 100 steps and trims older steps when its estimated retained cell storage
exceeds 128 MiB. The newest step remains undoable even if that step alone exceeds the budget.
@@ -46,6 +49,82 @@ including tests, 2026-10-10.
5. **Files**: `engine/io.rs` sniffs content (XLSX, XLSB, ODS) before trusting the extension and
dispatches to `crates/xlsx`; imports that lose data return warnings shown to the user.
## UI building blocks
The panels in `crates/ui-egui` share a few pieces, so a new feature reuses them instead of
drawing its own:
- **Colours**: `theme::Tokens`, each with a light and a dark value. The sheet stays white in both
themes, so colours drawn on it (`sheet_ref_colors`, `sheet_control_*`, icons through
`icons::paint_on_sheet`) keep their light values. `theme::contrast_tests` holds every
foreground/background pair to WCAG 2 (4.5:1 for text, 3:1 for glyphs) in both themes; a new
token pair is added there. Icons (`icons.rs`, drawn in code) take their paper and category
colours (`theme::Cat`) from the tokens.
- **Ribbon widgets**: `widgets.rs` has the buttons every tab is built from (`big_button`,
`small_button`, `split_button`, `big_split_button`, `toggle_button`), translated through
`i18n` in one place. Split buttons return the main and arrow responses separately, with
accessible names (`AutoSum`, `AutoSum options`). The ribbon card scrolls a tab wider than the
window, with arrows at its edges; `ribbon::RibbonOverflow` records the groups' size each frame,
and a test keeps every tab within the card's three rows.
- **Code editor**: `widgets::code_editor(ui, id, &mut text, Lang, max_lines)` (`code_editor.rs`)
edits code in the monospace font with `Tokens::syntax` colours: Python (keywords, strings
including triple-quoted ones, numbers, comments, builtins) and LaTeX (commands, scripts,
comments). Tab indents (every selected line, Shift+Tab dedents), Enter keeps the line's
indentation (one level more after a Python colon), both applied in input order with the typing
around them: a pass can carry several events, and the editor's keys that follow an event the
text field acts on (select all, an arrow) wait for the editor's next pass, which puts them
ahead of its input, so the field never gets a Tab or an Enter of a focused editor; and it
scrolls past `max_lines` with the caret in view. The tokenizer and the key edits are pure functions with tests, including
hostile input.
- **Formula bar**: grows with its content from one line to `formula_bar::MAX_LINES` (five), then
scrolls; one line is Excel's 30 px unless the font's rows need more (`formula_bar::one_line`;
the Windows UI font's row is 17.28 px), so every counted line fits whatever the font;
`formula_bar::BarLayout` records where its parts went each frame, for tests.
## Engine building blocks for multi-command actions
Actions made of many commands (macros, a debugger that runs one step per call) use these parts of
`Session` and the engine:
- **Undo groups.** `Session::grouped(label, f)` runs `f` as one undo step and rolls every change
back if `f` fails; `group_begin`, `group_end` and `group_rollback` do the same across calls.
While a group is open on a document, its commands add no undo steps of their own and Undo and
Redo are disabled there. Groups nest; the outermost one makes the step. A command that opens and
closes its own groups still makes one undo step for itself, covering its edits inside and outside
the groups. A command that ends a group open before it leaves the group its step and makes one
of its own for what it changes after the end; a group a command opens and leaves open starts
where the command did, but only when the command succeeds: a command that fails rolls back
every group it opened, as `group_rollback` does. `Session::abort_groups(doc)`, and the command
`automate.stopAction` on the active workbook, roll back the groups left open on a document (a
debugger the person abandons, a caller that dropped its token), so Undo works there again. The
history limit is applied when the outermost command finishes; until then the steps made inside
it are bounded on their own.
- **Recording buffer** (`record.rs`). `Session::start_recording` collects each successful
top-level command with the document, sheet and selection it ran in, without secret parameters.
Commands run by other commands are not recorded, nor is a menu-style call that only opened a
dialog (the dialog's confirming call is the step), and the buffer is independent of the journal
(Automate › Record Actions still records through the journal).
- **Document trust.** `DocState::trust` is `Local` for workbooks made in the session and
`FromFile` for workbooks opened from a file. `Trust` has no default: `DocState::new` takes it,
so every way of making a document says where its content came from. It is also `FromFile` for
workbooks that received a paste from one that writes formulas or validation: Paste, Paste
Special › All, All Except Borders, Formulas, Formulas and Number Formats or Validation, and any
linked paste other than column widths or notes. Values, formats, column widths and notes carry
nothing active. Active content in a `FromFile` workbook asks before it runs. This is provenance,
which no decision changes; `DocState::scripts_trusted` is the separate per-session consent for
stored `automate.*` scripts (`automate.trustScripts`).
- **Workbook data in hidden names** (`cmd/stash.rs`). An item is split over hidden
workbook-scope names `_gc_<key>_0`, `_gc_<key>_1`, … of string literals within Excel's 255- and
8,192-character limits, so it survives an xlsx round trip. Characters XML can't carry (controls
other than tab, line feed and carriage return, U+FFFE, U+FFFF) are stored as the noncharacter
U+FDD0 and four hex digits, as is U+FDD0 itself, and read back as they were. `stash::items`
and `stash::keys` list the items under a key prefix in one pass over the names, at most
10,000 of them (`stash::MAX_ITEMS`). The older single-name `_sc_<key>` items are read as well.
`formulas.defineName` and `formulas.deleteName` refuse a name the workbook holds such an item
under (a hidden workbook-scope name of either shape; a visible one is the person's own), and
`formulas.createFromSelection` skips such labels, unless `force: true` is passed.
## Agent control
- Desktop control channel: `gridcraft --control 7979`, JSON lines on localhost
@@ -71,5 +150,6 @@ rpm, tarball), FreeBSD, and web (WASM; WebGPU or WebGL2). Release workflows in
| Date | Change | Summary |
|---|---|---|
| 2026-10-24 | minor | Engine building blocks for multi-command actions: undo groups, recording buffer, document trust, chunked hidden-name storage |
| 2026-10-14 | minor | Document Undo's retained-cell-storage budget and latest-step guarantee |
| 2026-10-10 | major | Created from the code on main: crates, layers, data flow, agent control, known gaps |
+4 -3
View File
@@ -1,6 +1,6 @@
# Chart parity
> **Last reviewed:** 2026-10-18 · **Last updated:** 2026-10-18 · **Change:** trivial (stale chart notes: stock/combo, treemap/sunburst, box and waterfall kinds survive XLSX round-trip; PRs #158 and #161 merged) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
> **Last reviewed:** 2026-10-18 · **Last updated:** 2026-10-24 · **Change:** minor (100% stacked area charts) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
Excel's chart types and chart elements against GridCraft. Part of
[target-app-parity.md](target-app-parity.md). **Breadth ~50%, ready ~30%, 40–60 h** (estimated).
@@ -17,7 +17,7 @@ Sources: `crates/model/src/features.rs` (`ChartKind`, `Chart`, `Series`), `crate
| Bar | clustered, stacked, 100%, 3-D ×3 | 3 of 6 | no 3-D |
| Line | line, stacked, 100% stacked, with markers ×3, 3-D line | 3 of 7 (line, with markers, stacked) | no 100% stacked; smoothed lines landed (#62) |
| Pie | pie, 3-D pie, pie of pie, bar of pie, doughnut | 2 of 5 (pie, doughnut) | no exploded, pie of pie, bar of pie |
| Area | area, stacked, 100%, 3-D ×3 | 2 of 6 | no 100%, no 3-D |
| Area | area, stacked, 100%, 3-D ×3 | 3 of 6 | no 3-D; 100% stacked drawn as shares of each category's total and kept as `percentStacked` in XLSX |
| X Y (scatter) | markers, smooth lines (±markers), straight lines (±markers) | 2 of 5 | no smooth lines |
| Bubble | bubble, 3-D bubble | 1 of 2 | |
| Stock | high-low-close, open-high-low-close, volume-HLC, volume-OHLC | 1 kind | |
@@ -31,7 +31,7 @@ Sources: `crates/model/src/features.rs` (`ChartKind`, `Chart`, `Series`), `crate
| Waterfall, Funnel | 2 | 2 of 2 | |
| Filled map | 1 | 0 | needs geographic boundary data (licensing decision) |
| Combo | custom combinations, secondary axis | yes (line on secondary axis) | |
| **Total** | ~60 subtypes | **25 (~42%)** | usage-weighted breadth higher (~65%): column, bar, line, pie and scatter dominate |
| **Total** | ~60 subtypes | **26 (~43%)** | usage-weighted breadth higher (~65%): column, bar, line, pie and scatter dominate |
## Chart elements and formatting
@@ -90,6 +90,7 @@ are (#1686). Such series get no `numCache`/`strCache` on save (#1690).
| Date | Change | Summary |
|---|---|---|
| 2026-10-24 | minor | 100% Stacked Area: inserted from the ribbon and All Charts, drawn as shares of each category's total, read and written as `percentStacked` |
| 2026-10-18 | trivial | Stale notes: stock/combo, treemap/sunburst, box and waterfall kinds survive XLSX round-trip (PRs #158, #161) |
| 2026-10-16 | trivial | Series that refer to defined names (`[0]!Name`, `Sheet!Name`) resolve instead of showing `#NAME?`, and are saved qualified, as Excel needs (#1686) |
| 2026-10-16 | minor | Box & whisker quartiles default to exclusive, honor each series on draw and XLSX round-trip; `chart.set {quartiles}` changes the method (#1453) |
+47 -6
View File
@@ -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
@@ -31,18 +58,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, `windows` (as `window.list`) and `activeWindow` |
| `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. Its params may
include `window` or `doc` to run it in that window, as with `engine.execute`; the window commands
(`window.*`, `view.newWindow`, `view.hideWindow`, `view.unhideWindow`, `view.switchWindows`) read
+226
View File
@@ -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
+10 -3
View File
@@ -1,6 +1,6 @@
# Function parity: worksheet functions and formulas
> **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-21 · **Change:** minor (SUBTOTAL and AGGREGATE cap the cells they visit, #2175) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
> **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-25 · **Change:** minor (the LAMBDA row lists the recursion limit and its `#NUM!`, and the error a defined name holding an error gives when called; earlier: number text with accounting parentheses coerces like Excel 365; INDEX with one index on a 2-D reference is #REF! as in Excel; earlier: SUBTOTAL and AGGREGATE cap the cells they visit, #2175) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
Excel's worksheet functions, category by category, against GridCraft's function library. Part of
[target-app-parity.md](target-app-parity.md); gaps feed [gaps.md](gaps.md).
@@ -85,9 +85,10 @@ This improves series preparation; it does not change the model-fitting or season
| SUBTOTAL / AGGREGATE input size | references trim to the used range; all areas, arrays and values of one call are charged against the 16-million-cell limit before any cell is read (skipped hidden, nested and error cells count), and `#NUM!` past it (#2175) |
| Implicit intersection `@`, legacy CSE array formulas | done; legacy array range fill (PR #156) |
| Implicit intersection `@`, legacy CSE array formulas | done, the entered range filled (PR #156) |
| 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 |
| 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 |
| 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!`. 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 |
@@ -122,13 +123,19 @@ workbooks; results checked in as data, not Excel files) and fixing what it finds
| Date | Change | Summary |
|---|---|---|
| 2026-10-25 | minor | LAMBDA row: a LAMBDA that calls itself more than 140 times, and a formula nested past the depth limit, give `#NUM!` (#1809; nesting gave `#CALC!` before). `=Foo(1)` where the defined name Foo holds `#N/A` gives `#N/A`, not `#NAME?` |
| 2026-10-23 | minor | Number text coercion (VALUE, arithmetic, typed entry): a dollar sign before accounting parentheses (`$(1,234)`, `$ (5)`) and a percent sign after them (`(5)%`) read as negatives; `(-5)`, `((5))`, `($-5)`, `$5%`, `$(5)%` and dates or times in parentheses are `#VALUE!`, as observed in Excel 365 |
| 2026-10-23 | minor | INDEX with only a row index on a reference of several rows and columns (`INDEX(A1:C3,2)`, `INDEX(A1:C3,0)`) is `#REF!`, as observed in Excel, so it also fails as a range endpoint and inside other functions; an empty column argument (`INDEX(A1:C3,2,)`) still gives the row. Checked that a column array with one index stays a 1×1 array (`TYPE` 64), which is what Excel returns |
| 2026-10-21 | minor | SUBTOTAL and AGGREGATE charge every argument against the evaluator's 16-million-cell limit before scanning; a value in XFD1048576 no longer makes `SUBTOTAL(9,A:XFD)` read the whole sheet (#2175) |
| 2026-10-19 | minor | LET and LAMBDA names compare case-insensitively in any script, as in Excel: `=LET(dług,100,DŁUG*2)` is 200 (was #NAME?); also LET-bound lambdas called or passed by name (`żar(2)`, `MAP(…,Λ)`), `ISOMITTED`, and formula-bar alias translation (one shared `gridcraft_formula::same_name`, which matches `ß` with `ẞ` and keeps dotless `ı` apart from `i`) |
| 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 |
| 2026-10-14 | minor | Analysis ToolPak value arguments (EOMONTH, WORKDAY, YEARFRAC, COUPDAYS, DISC, BIN2DEC, IM* and the rest, observed in Excel 365) give `#VALUE!` for a multi-cell reference instead of lifting it |
| 2026-10-13 | minor | Clear reports changed stored cells sparsely, including those beyond the first million coordinates; dependent formulas and removed spills update, while clearing empty merges still unblocks spills (#1032) |
| 2026-10-14 | minor | Bounded references up to 1,048,576 cells (`D3:AZ400`) keep their full shape instead of stopping at the used range, so `SUMPRODUCT(D3:AZ400*B3:B400,(A3:A400=x)*(D1:AZ1=y))` broadcasts like Excel and `ROWS`, `COUNTBLANK` and `--(r="")` count the blanks; references that run to the edge of the sheet (`A:A`) still stop at the used range (#1010) |
| 2026-10-12 | minor | USDOLLAR added as a Compatibility function (not on Microsoft's list, so the counts don't change); MIRR with no positive flow is -1, COUNTIF's bare `"<="` / `">="` skip empty strings, INDIRECT with `a1` = FALSE gives `#REF!` for A1-style text (#738) |
+9 -3
View File
@@ -1,6 +1,6 @@
# Gaps
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-22 · **Change:** minor (§20: a native window per workbook window, New Window and Arrange All) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-24 · **Change:** minor (§8: PivotTable Enable show details; earlier: PivotTable Show Details) · **Target:** Microsoft Excel (Microsoft 365)
Every known shortfall against Excel, one entry each, ranked by user impact (what blocks someone
using GridCraft for real work first). This is the work list: pick from the top, check the open
@@ -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)
@@ -84,7 +84,10 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
### 8. PivotTables: no slicers, calculated items, PivotCharts; unverified in Excel (F)
- **Evidence:** `parity-checklist.md` missing `insert.slicer`, `insert.timeline`,
`insert.pivotChart`, `table.insertSlicer`; calculated items and GETPIVOTDATA missing (calculated fields
and values on rows done).
and values on rows done). Show Details (double-click a value, subtotal or grand total cell) lists
its source records on a new sheet. The "Enable show details" option (`pivot.options`
`enableShowDetails`, saved as `enableDrill`) turns it off; it has no PivotTable Options dialog
yet (command only).
- **Impact:** pivots are the analyst's main tool.
- **Estimate:** 20–30 h for beta (25–40 h full).
@@ -180,9 +183,12 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
| Date | Change | Summary |
|---|---|---|
| 2026-10-24 | minor | §8: PivotTable Enable show details: `pivot.options` `enableShowDetails` turns Show Details off for a PivotTable, read and written in XLSX as `enableDrill` |
| 2026-10-23 | minor | §8: PivotTable Show Details: double-clicking a value cell lists its source records on a new sheet (#2364) |
| 2026-10-22 | minor | §20: a native window per workbook window, New Window on the same workbook and Arrange All on the desktop; left: Arrange All across monitors, the web's one window, pen pressure (estimate 10–14 h → 5–8 h) |
| 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 |
+5 -3
View File
@@ -1,6 +1,6 @@
# Localization parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (the Japanese catalog covers the dialogs, messages and menus; Dutch interface language; Romanian interface catalog) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** minor (missing translations are listed by `cargo xtask i18n-report` and never fail a check) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
Per-language status against Excel, which ships its UI in dozens of languages (Windows language
packs cover 100+), with localized function names, argument separators, number and date formats,
@@ -96,8 +96,9 @@ language (none so far) and of localized function names.
## Infrastructure (do first)
1. Extend the catalog from the ribbon to every user-visible string (dialogs, menus, task panes,
status and error messages, command labels, function descriptions), with a missing-key check
in `cargo xtask ci`. Language switching and the system-language default already exist (#31).
status and error messages, command labels, function descriptions). A string a catalog lacks
shows in English and never fails a check: `cargo xtask i18n-report` lists the missing strings
per language, and `cargo xtask ci` runs it first. Language switching and the system-language default already exist (#31).
2. Locale-aware formulas: localized function-name tables and separators at the formula-bar
boundary only (files keep en-US invariant names), locale number and date formats, list
separators in CSV.
@@ -109,6 +110,7 @@ language (none so far) and of localized function names.
| Date | Change | Summary |
|---|---|---|
| 2026-10-23 | minor | Missing translations are reported, never a failure: `cargo xtask i18n-report` prints, per language, the strings other catalogs translate and it lacks (plus Spanish function help rows keyed by English the function catalog no longer has) and always passes; `cargo xtask ci` runs it first. Coverage and per-language parity tests and tests of one language's exact wording are gone; UI tests take the expected text from the catalog. Duplicate-key, placeholder, context-prefix and line-shape checks stay |
| 2026-10-21 | minor | Romanian interface (`ro`, Română): 970 labels covering every key of the other catalogs (`crates/ui-egui/src/i18n/ro.rs`), `ro-*` system locales, the View-tab switcher and `app.language.set`; context keys show no prefix (File › Options test). English formulas and formats; AI-assisted draft pending native-speaker review |
| 2026-10-20 | minor | Japanese: furigana (`rPh` phonetic runs and `<phoneticPr>` in `xl/sharedStrings.xml`) is no longer deleted by an open + save; it is kept on the workbook and written back (a `<phoneticPr>` without runs is kept too, while duplicate texts with different readings keep only the first, reported on open). Display of the readings is still missing (`crates/xlsx/src/tests/phonetic_tests.rs`) |
| 2026-10-20 | minor | Japanese catalog (`i18n/ja.rs`) covers every key the Brazilian Portuguese catalog translates: the 398-label shared core moved verbatim plus 545 dialog, message, Backstage and menu strings — 943 keys, ~12% → ~43%. A test parses `pt.rs`'s source, so the two catalogs can't drift apart again. Portuguese re-measured at 943 (was 923) |
+1 -1
View File
@@ -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
+4 -3
View File
@@ -4,7 +4,7 @@
Each entry is an Excel ribbon or menu command (feature names only). It counts as implemented when GridCraft registers a command with the same id. This measures breadth only; the overall assessment (depth, fidelity, effort) is in [target-app-parity.md](target-app-parity.md).
**Overall: 274 / 290 commands implemented (94.5%).**
**Overall: 275 / 291 commands implemented (94.5%).**
| Tab | Implemented | Total | Parity |
|---|---|---|---|
@@ -17,7 +17,7 @@ Each entry is an Excel ribbon or menu command (feature names only). It counts as
| Data | 23 | 28 | 82% |
| Review | 15 | 18 | 83% |
| View | 20 | 20 | 100% |
| Automate | 3 | 3 | 100% |
| Automate | 4 | 4 | 100% |
| Table Design | 13 | 13 | 100% |
| Chart Design | 8 | 8 | 100% |
| Format | 3 | 3 | 100% |
@@ -324,13 +324,14 @@ Implemented:
- `view.switchWindows` Switch Windows (Window)
- `view.macros` Macros (Macros)
## Automate (3 / 3, 100%)
## Automate (4 / 4, 100%)
Implemented:
- `automate.newScript` New Script (Scripting Tools)
- `automate.allScripts` All Scripts (Scripting Tools)
- `automate.recordActions` Record Actions (Scripting Tools)
- `automate.trustScripts` Trust Scripts (Scripting Tools)
## Table Design (13 / 13, 100%)
+4 -2
View File
@@ -1,6 +1,6 @@
# Roadmap detail
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-11 · **Change:** minor (PivotTable calculated fields) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-23 · **Change:** minor (test command) · **Target:** Microsoft Excel (Microsoft 365)
Forward-looking plan. The one-page summary is [`ROADMAP.md`](../ROADMAP.md); the ranked work
list is [`gaps.md`](gaps.md); numbers and method are in
@@ -100,7 +100,8 @@ to check our files, and Windows machines with the reported GPU drivers.
- `cargo xtask parity` regenerates [`parity-checklist.md`](parity-checklist.md) (ribbon
catalog vs registered commands); `cargo xtask ci` checks it is current.
- `cargo test --workspace`: engine, formula, function, XLSX and UI behaviour.
- `cargo xtask test`: engine, formula, function, XLSX and UI behaviour (cargo-nextest when
installed, else `cargo test`), then the doctests.
- `cargo run --release -p gridcraft-ui-egui --example snapshot -- --sample sales out.png`: look at
the UI offscreen.
- Function coverage: the script method in [function-parity.md](function-parity.md). A
@@ -112,6 +113,7 @@ to check our files, and Windows machines with the reported GPU drivers.
| Date | Change | Summary |
|---|---|---|
| 2026-10-23 | minor | Measuring: tests run through `cargo xtask test` |
| 2026-10-11 | minor | PivotTable calculated fields landed |
| 2026-10-10 | minor | Current focus and M15 updated for the 10-10 merges |
| 2026-10-10 | minor | Added the alpha gate table (six core workflows; passes, stays alpha) |
+1 -1
View File
@@ -342,7 +342,7 @@ The inventory carried over from the 2026-10-07 ROADMAP.md, updated for what land
| 2026-10-12 | minor | Essentials AutoFit evidence: rows auto-fit oversized fonts on open and row formatting (#833) |
| 2026-10-12 | minor | Sheet tabs: reveal the selected tab on navigation and resize while preserving manual scrolling (#681); no score change |
| 2026-10-12 | minor | PivotTables: values on rows (Σ Values in Rows at any position, all three layouts; `pivot.moveField` and the field pane move it; `dataOnRows` round-trips) (no score change) |
| 2026-10-11 | minor | Stability: command-sequence fuzz harness (`crates/engine/tests/command_sequence_fuzz.rs`) checks random command sequences against undo/redo, XLSX round-trip, full-recalc and structure invariants; the sequence bugs it found are fixed (no score change) |
| 2026-10-11 | minor | Stability: command-sequence fuzz harness (`crates/engine/tests/it/command_sequence_fuzz.rs`) checks random command sequences against undo/redo, XLSX round-trip, full-recalc and structure invariants; the sequence bugs it found are fixed (no score change) |
| 2026-10-11 | minor | Password-encrypted workbooks open (#11): Essentials file-opening work 24–36 h |
| 2026-10-11 | minor | Worksheet functions 505 / 523 (96.6%; 496 working): ODDFPRICE, ODDFYIELD, ODDLPRICE, ODDLYIELD |
| 2026-10-11 | minor | Worksheet functions 503 / 523 (96.2%; 494 working): PERCENTOF, EUROCONVERT |
+47 -18
View File
@@ -1,6 +1,6 @@
# UI parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (View › Arrange All lays out the native windows; two windows on one workbook show different sheets without copying it; a dialog stays in its window) · **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
@@ -16,23 +16,24 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes
|---|---|---|---|
| 2026-10-14 | minor | Dialogs (Find and Replace, Go To, Format Cells, Sort and the rest) move by their title bar instead of staying pinned to the centre (#1250) |
| Title bar, Quick Access Toolbar, AutoSave toggle | yes | yes | done; macOS window controls misaligned (#170), Dock icon size (#169) |
| Ribbon: tabs, groups, split buttons, galleries, contextual tabs | yes | yes (Table Design, Chart Design, Format, PivotTable, Header & Footer tabs); Fill Color, Font Color and the Format Cells colour pickers end with Recent Colors and More Colors… (#928) | done; missing contextual tabs for pictures, shapes, sparklines, slicers |
| Ribbon: tabs, groups, split buttons, galleries, contextual tabs | yes | yes (Table Design, Chart Design, Format, PivotTable, Header & Footer tabs); Fill Color, Font Color and the Format Cells colour pickers end with Recent Colors and More Colors… (#928); large split buttons (Formulas › AutoSum); arrows at the card's edges scroll a tab wider than the window | done; missing contextual tabs for pictures, shapes, sparklines, slicers; groups do not collapse when narrow |
| Ribbon collapse, simplified ribbon, customize ribbon/QAT | yes | partial | customization missing (File ▸ Options is the one missing File command) |
| Ribbon: tabs, groups, split buttons, galleries, contextual tabs | yes | yes (Table Design, Chart Design, Format, PivotTable tabs) | done; missing contextual tabs for pictures, shapes, sparklines, header/footer, slicers |
| Ribbon collapse, simplified ribbon, customize ribbon/QAT | yes | partial | customization missing |
| Key tips (Alt / F10 then letters) | yes, every ribbon control | **initial** (#43, 10-10): Alt or F10 shows key tips on Home for alignment, Paste Special and AutoFit plus legacy Alt sequences; other tabs and controls not yet (#33, #49) | 3–5 h |
| Formula bar with Name Box, expand, function hints | yes | yes | done |
| Formula bar with Name Box, expand, function hints | yes | yes; grows with its content from one to five lines (Alt+Enter, wrapped text, or viewing a multi-line cell), then scrolls with the caret in view, the Name Box and buttons staying on the first line; the chevron pins it at five; the in-cell editor shows every line | done; no drag-to-resize handle |
| Sheet tabs: rename, reorder, colour, hide, scroll | yes | yes | Ctrl-drag (Cmd or Option on macOS) drops a copy (#971); active tab revealed on navigation and resize (#681); manual wheel/trackpad browsing preserved; rename focus bug on macOS (#145) |
| Formula bar with Name Box, expand, function hints | yes | yes | done; dynamic-array spill children show the source formula muted and read-only, using current spill ownership |
| Formulas › Error Checking arrow: Circular References | yes | yes: the button opens Error Checking and its arrow opens a menu with Error Checking… and Circular References, which lists a cell on each circular reference as `Sheet!A1`, up to 100, and selects the one picked, on its own sheet (unavailable when there are none) | done; the Error Checking… item and the limit of 100 are GridCraft's own |
| Sheet tabs: rename, reorder, colour, hide, scroll | yes | yes | active tab revealed on navigation and resize (#681); manual wheel/trackpad browsing preserved; rename focus bug on macOS (#145) |
| 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 | customizable statistics partial |
| 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 |
| Dark mode, system appearance | yes | yes | done (10-09); follows the system on first run, title-bar switch and File › Display theme (#194, #239) |
| 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 | 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 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 | margins drag on the rulers (#792, with header and footer margins); header/footer pictures, Different First Page / Odd & Even missing (1–3 h) |
| 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 | yes (10-22, desktop): each workbook window is a native window of its own (New, Open and New Window open one; titles "Book1", or "Book1:1" / "Book1:2" for a workbook's second window, with " — Edited" while unsaved). Each window keeps its own cell draft, dialog, Backstage view, message and grid state, and its ribbon acts on it; on macOS the menu bar acts on the window focused last. Leaving a window enters its draft as Enter would. A window's close button closes that window: one of several on a workbook closes without asking, a workbook's last asks Save / Don't Save / Cancel when unsaved. Quit asks about each unsaved workbook in its window, and Cancel stops it. The last window closed quits on Windows and Linux; macOS keeps running with the menu bar until New or Open. View › Arrange All (dialog: Tiled, Horizontal, Vertical, Cascade, Windows of active workbook; `view.arrangeAll` tiles by default) lays the visible windows out over the main window's monitor, the active one first. Web: one window | Arrange All uses one monitor; a hidden window (View › Hide) has no native window; every window repaints when one does; see [hardware-parity.md](hardware-parity.md) |
| Switch Windows, New Window, unsaved workbooks on quit | yes | yes (10-22): View › New Window opens another window on the same workbook (no copy: saving from either saves it), each with its own selection, active sheet and scroll (showing different sheets copies nothing and leaves the workbook unchanged; Save records the sheet of the window used last); a workbook with several windows titles them "Book1:1", "Book1:2"; File › Close closes one of them without asking and asks only for the last; View › Switch Windows and the macOS Window menu list the open windows (active one checked, unsaved ones marked •) and bring the chosen one forward (`view.switchWindows {window}`, or `{doc}` for a workbook's most recent window); a cell being typed is entered into its own window and workbook before New, Open, Close, Save, Print and window switches (also between two windows on one workbook), and a refused entry stays open and stops the command; quitting (Cmd+Q, the macOS Quit item, the Dock) asks Save / Don't Save / Cancel for each unsaved workbook | |
@@ -51,8 +52,7 @@ this logic, with one undo step.
| Click/drag/Shift/Ctrl selection, multi-area selection | rectangular drag works in one direction only in some cases (#28) |
| Cursor while dragging cells, headers, freeze handles and objects | stable for the active drag (#936); Draw tools keep their crosshair; header arrows and a custom cell cursor remain unimplemented |
| Selection outline glides to a new selection (Excel: "Provide feedback with animation") | done (10-11): 0.12 s, decelerating, with the fill and fill handle; View ▸ Animations (`view.animations`) turns it off. The timing is an estimate, not yet measured against Excel; the system's reduced-motion setting isn't followed yet |
| Fill handle: drag to fill series, double-click to fill down, right-drag menu | fill done (Ctrl+drag swaps copy and series); right-drag menu partial: Copy Cells, Fill Series, Fill Formatting Only, Fill Without Formatting, Series…; Fill Days/Weekdays/Months/Years, Linear/Growth Trend and Flash Fill missing |
| Fill handle: drag to fill series, double-click to fill down, right-drag menu | fill done (Ctrl+drag swaps copy and series); month patterns use the workbook's 1900 or 1904 date system; right-drag menu missing |
| Fill handle: drag to fill series, double-click to fill down, right-drag menu | fill done (Ctrl+drag swaps copy and series; month patterns use the workbook's 1900 or 1904 date system); right-drag menu partial: Copy Cells, Fill Series, Fill Formatting Only, Fill Without Formatting, and from a date Fill Days, Fill Weekdays, Fill Months and Fill Years, then Series…; a left-drag leaves an Auto Fill Options button with the same fill choices (gone once the workbook changes or the selection moves); Linear/Growth Trend and Flash Fill missing |
| Drag-move and drag-copy cells (border drag, Ctrl/Option, Shift to insert) | done (M3) |
| In-cell editing, Enter/Tab movement, Alt/Option+Enter line breaks | done |
| Point mode (click/drag/arrow references), reference colouring, F4 | done; Command/Ctrl-click adds separate arguments with the edit's locale separator, and Shift-click extends the current reference from a fixed anchor on the same sheet. Pointer-event regressions cover repeated/reversed selections, manual separators, sheet switches and keyboard continuation. |
@@ -61,16 +61,16 @@ this logic, with one undo step.
| AutoComplete in columns, function autocomplete and argument tips | done; cell helpers clip to the worksheet and hide when the editor scrolls out (#200) |
| Header resize and double-click AutoFit | done (#42, #88 for header dividers) |
| Data › Advanced Filter | dialog offers in-place or copy-to filtering, list and optional criteria ranges, and unique records; Alt+A,Q opens it (#1622). The engine's copy-to path clears only the result footprint and rejects worksheet overflow (#1031). |
| Outline symbols for grouped rows / columns (band beside the headers, brackets, − / +, level buttons 1…n+1) | done (#373): clicks run `data.hideDetail` / `data.showDetail` / `data.showLevels`, frozen panes and right-to-left sheets included; summary rows/columns are always below/right (the "Summary rows below detail" setting isn't modelled), no "Show outline symbols" option yet, and a block of cells still groups rows without the Rows / Columns dialog |
| Outline symbols for grouped rows / columns (band beside the headers, brackets, − / +, level buttons 1…n+1) | done (#373): clicks run `data.hideDetail` / `data.showDetail` / `data.showLevels`, frozen panes and right-to-left sheets included; Data › Outline › Settings (`data.outlineSettings`) puts summary rows above and summary columns left of their detail, saved as `outlinePr` (the dialog's Automatic styles check box and Apply Styles button are missing), View › Show Outline Symbols (Ctrl+8) hides the bands and is saved with the sheet, and a block of cells still groups rows without the Rows / Columns dialog |
| Reorder selected whole columns by dragging their headers | done (10-11) |
| Freeze panes, split panes | freeze done; split to verify |
| A Table's header names and filter buttons in the column headings once its header row scrolls away | done (#1423): with the active cell in the Table, as in Excel; the button opens the column's filter menu, the rest of the heading still selects the column. Plain AutoFilter ranges keep their letters (Excel does the same) |
| Copy as HTML table for other apps | done (#36, 10-10) |
| Context menu on cells | 16 items (Cut, Copy, Paste, Paste Special, Insert, Delete, Clear Contents, Filter by value, Sort, New Comment, New Note, Format Cells, Pick From Drop-down List, Define Name, Link). Insert Copied Cells (Insert Cut Cells for whole rows/columns) while the clipboard holds a copy (#21). Missing: Insert Cut Cells for a cell block, paste-option icons, Quick Analysis, Get Data from Table, Show Changes, Smart Lookup |
| Context menu on cells | 16 items (Cut, Copy, Paste, Paste Special, Insert, Delete, Clear Contents, Filter by value, Sort, New Comment, New Note, Format Cells, Pick From Drop-down List, Define Name, Link). Insert Copied Cells (Insert Cut Cells for whole rows/columns) while the clipboard holds a copy (#21). Show Details on a PivotTable's value, subtotal or grand total cell (`pivot.showDetails`). Missing: Insert Cut Cells for a cell block, paste-option icons, Quick Analysis, Get Data from Table, Show Changes, Smart Lookup |
| Move and resize charts, pictures and shapes | charts can't be resized (#190) |
| Context menus on row/column headers, sheet tabs, charts, shapes | headers and tabs done; objects partial |
| Quick Analysis (Ctrl+Q) | missing |
| Smooth scrolling, scrollbars | done; vertical scrollbar added 10-09 (#73); horizontal scrollbar in the tab bar (60/40 split, drag, track click pages) 10-13 (#176, #840); the tabs/scrollbar split can't be dragged yet |
| Smooth scrolling, scrollbars | done; vertical scrollbar added 10-09 (#73); horizontal scrollbar in the tab bar (60/40 split, drag, track click pages) 10-13 (#176, #840); dragging the handle at the scrollbar's left edge gives the tabs more or less of the bar; each workbook keeps its share while it is open (not saved in the file) |
| Text overflow into empty neighbours, wrap, rotation, shrink to fit | done |
## Keyboard
@@ -86,9 +86,9 @@ this logic, with one undo step.
| Shortcuts bound to commands | 200+ documented shortcuts per platform | audited below (#110): shortcuts whose feature exists are bound, with the exceptions listed; the rest wait for their features |
| Ctrl+arrow, Ctrl+Shift+arrow, Ctrl+Home/End, Ctrl+Shift+Home/End, Page Up/Down, Alt+Page | yes | done |
| Ctrl+1, Ctrl+; and Ctrl+Shift+; , Ctrl+D/R, Ctrl+E, Ctrl+T/L, Ctrl+K, Ctrl+Shift+L, Ctrl+Shift+~!@#$%^&_ | yes | done |
| F2, F4, F5, F7, F9 family, Shift+F2/F3/F10/F11, F12 | yes | done; F11 chart sheet, F8 extend mode, F3 Paste Name, F4 Repeat missing |
| F2, F3, F4, F5, F7, F9 family, Shift+F2/F3/F10/F11, F12 | yes | done; F11 chart sheet, F8 extend mode, F4 Repeat missing |
| Mac-specific (Cmd variants, Control variants, Fn keys) | yes | done where a feature exists (`keymap.rs` picks the Mac variant) |
| Sequential Alt access keys (Alt, H, N, …) on every ribbon tab | yes | 364-sequence keytip table for all tabs: per-control badges, prefix waiting, dropdowns open for editing (type/arrows/Enter), pending bindings for not-yet-built commands; letters also work with left Alt held (Alt+H, O, A), as in Excel; QAT/Backstage excluded |
| Sequential Alt access keys (Alt, H, N, …) on every ribbon tab | yes | 364-sequence keytip table for all tabs: per-control badges, prefix waiting, dropdowns open for editing (type/arrows/Enter), pending bindings for not-yet-built commands; letters also work with left Alt held (Alt+H, O, A), as in Excel; each Borders letter (H, B, …) draws its own preset, H, B, M opens Format Cells on the Border tab, and Erase Border, Line Color and Line Style are reserved; QAT/Backstage excluded |
| Requests | — | #110 (more shortcuts and hot keys), #33 (sequential Alt; initial support landed in #43) |
### Shortcut audit (#110)
@@ -135,10 +135,13 @@ otherwise. Number-format and border keys match the physical key (Ctrl+Shift+4, w
| Shift+F3, Ctrl+F3, Ctrl+Shift+F3 | Shift+F3, Cmd+F3 (Define Name), Cmd+Shift+F3 | Insert Function, Name Manager, Create from Selection | **added** |
| F5, Shift+F5 (Mac), F7, Shift+F9, Ctrl+Alt+F9, F12, Ctrl+F4 | same | Go To, Find, Spelling, calculate sheet / all, Save As, close | **added** |
| Shift+F10, Alt+Shift+F10, Ctrl+F1 | Shift+F10, Cmd+Option+R | context menu, Error Checking, ribbon | **added** |
| F3 | not checked | Paste Name dialog, only when the workbook has names: OK puts the name at the caret of the formula being edited (or starts `=Name`), Paste List writes each name and what it refers to from the active cell (`formulas.pasteName`) | **added** |
| F1, F4 (repeat), F6 / Shift+F6, F8 / Shift+F8, F11, End mode | same | help, Repeat, pane cycling, extend/add mode, chart sheet | missing (no such feature yet) |
| Ctrl+Q, Ctrl+A after a function name, Ctrl+Shift+U, Ctrl+Alt+P | same | Quick Analysis, Function Arguments, formula bar height, tooltips | missing |
| F1, F3, F4 (repeat), F6 / Shift+F6, F8 / Shift+F8, F11, End mode | same | help, Paste Name, Repeat, pane cycling, extend/add mode, chart sheet | missing (no such feature yet) |
| Ctrl+8, Ctrl+Q, Ctrl+Shift+A, Ctrl+A after a function name, Ctrl+Shift+U, Ctrl+Alt+P | same | outline symbols, Quick Analysis, argument names, Function Arguments, formula bar height, tooltips | missing |
| Ctrl+Q, Ctrl+A after a function name, Ctrl+Alt+P | same | Quick Analysis, Function Arguments, tooltips | missing |
| Ctrl+Shift+A after a function name | Control+Shift+A | argument names: `=SUMIF(` becomes `=SUMIF(range,criteria,sum_range)`, the `(` added when missing; one Undo takes them back | **added** |
| Ctrl+6, Ctrl+8, Ctrl+Q, Ctrl+A after a function name, Ctrl+Shift+U, Ctrl+Alt+P | same | objects, outline symbols, Quick Analysis, Function Arguments, formula bar height, tooltips | missing |
| Ctrl+Shift+U | Control+Shift+U | expand or collapse the formula bar (`view.expandFormulaBar`), on the grid and while editing; on Windows and Linux it no longer underlines | **added** |
| Ctrl+Shift+Page Up / Down, Ctrl+Alt+5, Ctrl+Shift+F1, Ctrl+F5–F10, Alt+F8 / F11 / F12 | same | group sheets, shape cycling, full screen, window keys, macros, VBA, Power Query | missing |
| — | Cmd+= (calculate), Cmd+T (references while editing; F4 works), Shift+F7, Cmd+Shift+L | Mac-only extras | missing (Cmd+= is egui's zoom key; no thesaurus or Modify Cell Style) |
@@ -154,12 +157,19 @@ Seek, Scenario Manager, Data Analysis, Forecast Sheet and others exist. Missing
Solver, Power Query editor, Insert Object, Equation editor, Protect/Allow Edit Ranges,
Workbook Links, Accessibility checker. Like Excel's, every dialog opens centred and moves by its
title bar, keeping its place while it is open (#1250).
Go To lists the named ranges of every sheet (a name scoped to another sheet as `Sheet2!Name`) and
the references entered before; a double-click goes to one. Its Reference box takes several names
or references separated by commas (`Price, Type`, `B14:C22,F19:G30`) and selects them all.
New Formatting Rule's formula box points like the formula bar: a click on the grid writes the
cell's absolute reference at the caret (`=$B$2`) and a drag a range, leaving the selection the rule
applies to as it is (#2348). Other dialogs' reference fields don't take clicks yet.
## Accessibility
AccessKit is enabled, but the grid, ribbon and dialogs are not exposed as an accessible tree with
cell addresses and values, so screen readers can't navigate a sheet; there is no high-contrast
theme or accessibility checker. Excel is strong here. 12–18 h.
theme or accessibility checker. Excel is strong here. 12–18 h. The light and dark themes' colour
pairs (text 4.5:1, glyphs 3:1) are checked by `theme::contrast_tests`.
## Feel
@@ -177,6 +187,21 @@ 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 | Go To lists the named ranges on every sheet, as Excel's does ("The Go to popup window shows named ranges on every worksheet in your workbook"): a name scoped to another sheet appears as `Sheet2!Name`, and hidden names, constants and formulas are left out. It keeps the references entered (newest first, ten at most), and a double-click on an entry goes there. The Reference box and `edit.goTo` take several names or references separated by commas (`Price, Type`, `B14:C22,F19:G30,H21:H29`) and select them all; `edit.goTo {list: true}` returns the list |
| 2026-10-24 | minor | Right-clicking the status bar opens a menu of its statistics, as in Excel ("If you want to customize the status bar, right-click it, and then click the options that you want"): Average, Count and Sum are on by default, and Numerical Count, Minimum and Maximum are new and off by default. The choice is a preference (`view.statusBar`), kept in `prefs.json` |
| 2026-10-24 | minor | Ctrl+Shift+U (Control+Shift+U on a Mac) expands or collapses the formula bar, as in Excel ("Expand or collapse the formula bar. Ctrl+Shift+U"), on the grid and in the cell editor or formula bar while editing. On Windows and Linux it used to underline the selection, as Ctrl+U does. The command is `view.expandFormulaBar` (`{on?}`, toggles without it) |
| 2026-10-24 | minor | Right-clicking a PivotTable's value, subtotal or grand total cell offers Show Details in the cell menu, as in Excel ("Right-click a field in the values area of the PivotTable, and then click Show Details"): it lists the cell's source records on a new sheet, as double-clicking the cell does (`pivot.showDetails`). Labels and headers of a current report don't offer it; in a report that is out of date every cell offers it, and choosing it asks for a refresh |
| 2026-10-24 | minor | Sheet tabs: the handle at the horizontal scrollbar's left edge shows the resize cursor, and dragging it moves the boundary, as in Excel ("Hover the mouse pointer at the edge of the scrollbar until you see the double-headed arrow"). Dragging it right gives the tabs more of the bar, until the scrollbar is 100 px wide (the scrollbar stays 100 px wide after the window is narrowed); dragging it left gives the scrollbar more, down to the New sheet button. Room the tabs don't fill stays empty, with New sheet after the last tab. Each open workbook keeps its own share; a workbook starts at the default (tabs up to 60%) |
| 2026-10-24 | minor | Outline Settings (Data ▸ Outline ▸ Settings, `data.outlineSettings`): clearing Summary rows below detail or Summary columns to right of detail puts the − / + buttons on the line above or left of each group, and Hide Detail, Show Detail and the level buttons mark that line collapsed. Changing a direction moves the marks of collapsed groups. Read and written as `outlinePr summaryBelow` / `summaryRight` |
| 2026-10-23 | minor | F3, Formulas › Use in Formula › Paste Names… and Alt+M, S, P open the Paste Name dialog (F3 only when the workbook has names). It lists the workbook's names and the active sheet's own in alphabetical order, hidden names left out; OK or a double-click puts the selected name at the caret of the formula being edited, in place of selected text, or starts `=Name` in the active cell (`formulas.pasteName` `{name}`), and Paste List writes each name and the text of what it refers to in two columns from the active cell (`{list: true}`). While a dialog is open over the edit, the cell editor takes no keys, and Cancel leaves the selected text selected. The Use in Formula menu lists the same names and inserts at the caret too (`formulas.useInFormula`); it used to list hidden names and other sheets' names, the menu added the name at the end of the text, and the command replaced the text being edited |
| 2026-10-23 | minor | Conditional Formatting ▸ New Rule, formula: clicking a cell on the grid writes its absolute reference at the formula box's caret (`=$B$2`, an empty box starts with `=`), a drag writes a range (`$B$2:$C$4`) and a second click replaces the reference just written, as in Excel ("Selecting cells on the worksheet inserts absolute cell references"). The selection the rule applies to doesn't move, and the box keeps the keyboard. After an operand (`=A1>5`) the click does nothing (#2348) |
| 2026-10-23 | minor | Frozen panes: Page Up, Ctrl+Up and Shift variants that move from the scrolled rows into the frozen ones bring the scrolled rows back to the top, so the rows in between are seen again (Page Up from far down used to end on row 1 with the view still a page down); Ctrl+Left and Alt+Page Up do the same for frozen columns. Other ways of selecting a frozen cell (click, Find, Go To, Undo, formula pointing) leave the view where it is |
| 2026-10-23 | minor | After a left-drag of the fill handle an Auto Fill Options button sits below the filled cells; its menu redoes the fill as Copy Cells, Fill Series, Fill Formatting Only or Fill Without Formatting (undoes the drag's fill, then `edit.autoFill` in that mode). It goes away when the workbook changes or the selection moves (#1403) |
| 2026-10-24 | minor | When the fill starts from a date, the right-drag fill menu and the Auto Fill Options button also offer Fill Days, Fill Weekdays (Saturday and Sunday skipped), Fill Months and Fill Years (`edit.autoFill` with `mode` set to `days`, `weekdays`, `months` or `years`). A lone date steps by one unit; two or more dates step by the difference between the first two in that unit, or one unit when they fall in the same one. Fill Months and Fill Years keep the day of the month, clamped to the month's last day. Every fill by months or years (these two, a drag of dates a month apart, and Series by Month or Year) keeps the time of day of a date with a time, which used to be dropped |
| 2026-10-22 | minor | Show Outline Symbols (`view.showOutlineSymbols`, Ctrl+8): a sheet can hide the outline's level buttons and brackets while keeping its outline, as Excel saves with `showOutlineSymbols="0"` (read and written); the grid then gives the bands no room |
| 2026-10-22 | minor | A number in a repeat-fill format (`*`, as in the Accounting format) that is too wide for its column shows #### like any other number, instead of the fill rebuilding the value so its leading digits were cut off (2,509.00 CHF drawn as "509.00 CHF") |
| 2026-10-22 | minor | View › New Window and Arrange All on the View ribbon; Arrange All tiles, stacks or cascades the native windows; two windows on one workbook keep their own sheet without copying the workbook; a dialog opened in one window stays there when a command run from it switches windows |
| 2026-10-22 | minor | One native window per workbook window on the desktop: per-window drafts, dialogs and ribbons; menu bar commands go to the focused window; closing one of several windows on a workbook doesn't ask; Quit asks in each unsaved workbook's window; the last window closed quits (Windows, Linux) or leaves macOS running |
| 2026-10-22 | minor | New Window opens a second window on the same workbook instead of a copy that saved over the original; windows keep their own selection, sheet and scroll; titles Name:1 / Name:2; Switch Windows, Hide and Unhide work by window; File › Close closes one window of several without asking |
@@ -198,9 +223,11 @@ 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 |
| 2026-10-14 | minor | Alt, H, B border keytips apply the preset each letter names in Excel's Borders menu (they all drew a bottom border, so H,B,N No Border added one): keytips.json entries take optional `params`; H,B,M opens Format Cells on the Border tab; Erase Border, Line Color and Line Style stay reserved |
| 2026-10-13 | minor | Dragging a sheet tab with Ctrl held (Cmd or Option on macOS) drops a copy of the sheet where the tab is released, with the copy cursor (#971) |
| 2026-10-13 | trivial | More Colors… and Recent Colors (#928) |
| 2026-10-13 | minor | Grid drags keep their own cursor over selection borders, fill handles and outside the grid; hover resumes on release (#936) |
@@ -214,6 +241,8 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
| 2026-10-12 | minor | Page Layout view: left/right/top/bottom margins are dragged on the rulers (#792) |
| 2026-10-12 | minor | Alt+Down opens the active filter header's existing popup, with validation priority and mirrored anchoring on right-to-left sheets (part of #441) |
| 2026-10-12 | minor | Paste from the ribbon (button and options menu), the cell and header context menus and the macOS menu bar's Edit menu pastes what is on the system clipboard, as Cmd/Ctrl+V does, instead of only GridCraft's own copy (the web build, which can't read the clipboard on request, still pastes GridCraft's copy). Copy and Cut by keytip (H,C / H,X) publish to the system clipboard like the ribbon buttons. After a control-channel or MCP copy, which leaves the system clipboard alone, Paste pastes that copy until other text is copied. Once a cut has been pasted, Paste (ribbon, menus or Cmd/Ctrl+V) doesn't paste the cut's clipboard text again over the moved cells (Excel pastes a cut once), and the Paste keytip (H,V,P) pastes the system clipboard like the button. Regression test for the Paste button click freeze in 0.5.0 (#273, fixed by #272); the Paste button gets an accessible "Paste" name for screen readers |
| 2026-10-11 | minor | Ribbon: scroll arrows on a tab wider than the window, Formulas › AutoSum as a split button with its function menu, View › Show in two columns so Headings is no longer cut off |
| 2026-10-11 | minor | Theme tokens for icon, reference, on-accent and sheet-control colours; WCAG 2 contrast test over both themes (dark danger red and selected header text adjusted) |
| 2026-10-11 | minor | AutoFit accounts for table and ordinary filter dropdowns, including low zoom; header text excludes the button area |
| 2026-10-11 | minor | Outline band for grouped rows and columns, Show / Hide Detail on the Data tab, numpad navigation keys in shortcuts (#373) |
| 2026-10-11 | minor | Shift+Space / Ctrl+Space no longer start an edit; Control+Space selects the column on Mac (#338) |