Merge main into function updates

This commit is contained in:
Harrison Booth
2026-10-30 20:59:31 -05:00
313 changed files with 22604 additions and 4146 deletions
+14 -3
View File
@@ -1,6 +1,6 @@
# Architecture
> **Last reviewed:** 2026-10-10 · ** Last updated:** 2026-10-24 · **Change:** minor (engine building blocks for multi-command actions) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** trivial (the hidden names that hold macros; earlier: macro engine on the engine building blocks) · **Target:** Microsoft Excel (Microsoft 365)
How GridCraft is built today. Rules for contributors are in [`AGENTS.md`](../AGENTS.md).
@@ -98,12 +98,15 @@ Actions made of many commands (macros, a debugger that runs one step per call) u
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
command runs on its own: a step of a script or macro may not stop the action it is part of, and
a recording leaves it out, so playing the recording back never stops the run. 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
dialog (the dialog's confirming call is the step; a run that changed the workbook before a step
of it opened a dialog is recorded), 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,
@@ -120,11 +123,17 @@ Actions made of many commands (macros, a debugger that runs one step per call) u
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.
10,000 of them (`stash::MAX_ITEMS`) with a count of the rest, so a caller can say what it did
not list. 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.
The macro engine (`engine/src/macros/`, commands in `cmd/macros.rs`, described in
[`macros.md`](macros.md)) is built on these: a run is an undo group, the recorder reads the
recording buffer, the policy asks before a macro in a `FromFile` workbook runs, and xlsx keeps
macros as chunked hidden names (`_gc_macro_<n>_<k>`: macro `n`, chunk `k`).
## Agent control
- Desktop control channel: `gridcraft --control 7979`, JSON lines on localhost
@@ -150,6 +159,8 @@ rpm, tarball), FreeBSD, and web (WASM; WebGPU or WebGL2). Release workflows in
| Date | Change | Summary |
|---|---|---|
| 2026-10-25 | trivial | The hidden names that hold macros are `_gc_macro_<n>_<k>` |
| 2026-10-24 | minor | Macro engine on the building blocks (`macros/`, `cmd/macros.rs`) |
| 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 |
+3 -1
View File
@@ -1,6 +1,6 @@
# Chart parity
> **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
> **Last reviewed:** 2026-10-18 · **Last updated:** 2026-10-29 · **Change:** minor (selected chart source outlines stay beneath floating objects) · **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).
@@ -38,6 +38,7 @@ Sources: `crates/model/src/features.rs` (`ChartKind`, `Chart`, `Series`), `crate
| Element | Excel | GridCraft |
|---|---|---|
| Move and resize the chart object | yes | yes (corner handle, Format Chart Area Size fields) |
| Selected chart source-data outline | visible on uncovered cells, beneath floating objects | painted above cells and below charts, shapes, pictures and slicers; retains pane clipping and right-to-left cell geometry (#2861) |
| Chart title, axis titles | text, linked to cells, full formatting | text only |
| Legend | position, overlay, formatting, per-entry delete | position only |
| Data labels | value, category, series, percentage, from cells, callouts, position | one on/off flag |
@@ -90,6 +91,7 @@ are (#1686). Such series get no `numCache`/`strCache` on save (#1690).
| Date | Change | Summary |
|---|---|---|
| 2026-10-29 | minor | Selected charts highlight their source cells beneath floating objects, so the blue outline no longer crosses the chart; uncovered source cells remain highlighted (#2861) |
| 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) |
+2 -1
View File
@@ -23,7 +23,8 @@ The control port is reachable by every program on the machine, and by a web page
`{"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.
connection is closed. At most 16 connections may be waiting to send their token at once; past that a new
connection is closed before anything is read. 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.
+4 -3
View File
@@ -1,6 +1,6 @@
# File-format parity
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-21 · **Change:** minor (.xls dialog sheets are skipped instead of read as worksheets; `.xls` import reads each sheet's display options from WINDOW2: right-to-left, gridlines, headings, zeros, formulas; XLSX text boxes and shapes keep their paragraphs' right-to-left direction on save; CSV/TSV/TXT can open with every field kept as text via `file.open {csvAsText}`; formulas over Excel's 8,192 characters, and `%` or call chains past the nesting limit, are parse errors instead of stack overflows when a file is opened; XLSX PivotTables read and write `rowGrandTotals`/`colGrandTotals` the way Excel does, so a table saved with only one grand total opens with the right one; HTML export keeps right-to-left; CSV and text import detects likely Windows-1256 Arabic when invalid UTF-8 has Arabic byte runs; other invalid UTF-8 remains Windows-1252; HTML export places pictures and charts, as inline SVG, at their anchors) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-26 · **Change:** minor (`file.exportCsv {values: true}` writes raw values for automation; .xls dialog sheets are skipped instead of read as worksheets; `.xls` import reads each sheet's display options from WINDOW2: right-to-left, gridlines, headings, zeros, formulas; XLSX text boxes and shapes keep their paragraphs' right-to-left direction on save; CSV/TSV/TXT can open with every field kept as text via `file.open {csvAsText}`; formulas over Excel's 8,192 characters, and `%` or call chains past the nesting limit, are parse errors instead of stack overflows when a file is opened; XLSX PivotTables read and write `rowGrandTotals`/`colGrandTotals` the way Excel does, so a table saved with only one grand total opens with the right one; HTML export keeps right-to-left; CSV and text import detects likely Windows-1256 Arabic when invalid UTF-8 has Arabic byte runs; other invalid UTF-8 remains Windows-1252; HTML export places pictures and charts, as inline SVG, at their anchors) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
Every format Excel opens or saves, with GridCraft's read and write support, fidelity and tests.
Part of [target-app-parity.md](target-app-parity.md). **File formats: ~50% ready, 80–120 h**
@@ -66,9 +66,9 @@ values) is still dropped.
| Format | Excel | GridCraft read | GridCraft write | Notes |
|---|---|---|---|---|
| `.csv` UTF-8 / Windows / Mac / MS-DOS | R/W | yes (UTF-8 and UTF-16 BOMs, UTF-8, likely Windows-1256 Arabic detection, otherwise Windows-1252 fallback; delimiter detection) | yes (UTF-8 without BOM, CRLF; a file opened as CSV is saved with the separator it was read with, so a `;` file stays `;`; otherwise `,`) | Excel writes the **displayed** values; we write raw values. No encoding choice on save. Re-import of a single-column export splits text (#127) A used range that would write more than 512 MiB (two far-apart cells span the whole rectangle) is refused with an error instead of being expanded (#528). |
| `.csv` UTF-8 / Windows / Mac / MS-DOS | R/W | yes (UTF-8 and UTF-16 BOMs, UTF-8, likely Windows-1256 Arabic detection, otherwise Windows-1252 fallback; delimiter detection) | yes (UTF-8 without BOM, CRLF; a file opened as CSV is saved with the separator it was read with, so a `;` file stays `;`; otherwise `,`) | Excel writes the **displayed** values, and so do we; `file.exportCsv {values: true}` writes raw values (full precision, ISO 8601 dates) for automation. No encoding choice on save. Re-import of a single-column export splits text (#127) A used range that would write more than 512 MiB (two far-apart cells span the whole rectangle) is refused with an error instead of being expanded (#528). |
| `.txt` tab-delimited, Unicode text | R/W | yes (`.txt`, `.tsv`, `.tab`; likely Windows-1256 Arabic detection for invalid UTF-8) | yes | Text Import Wizard options partly via Text to Columns |
| `.csv` UTF-8 / Windows / Mac / MS-DOS | R/W | yes (UTF-8 and UTF-16 BOMs, UTF-8, likely Windows-1256 Arabic detection, otherwise Windows-1252 fallback; delimiter detection) | yes (UTF-8 without BOM, CRLF) | Writes the **displayed** values like Excel (number formats applied, formula results, never column-width `#####`). No encoding choice on save. Re-import of a single-column export splits text (#127) A used range that would write more than 512 MiB (two far-apart cells span the whole rectangle) is refused with an error instead of being expanded (#528). |
| `.csv` UTF-8 / Windows / Mac / MS-DOS | R/W | yes (UTF-8 and UTF-16 BOMs, UTF-8, likely Windows-1256 Arabic detection, otherwise Windows-1252 fallback; delimiter detection) | yes (UTF-8 without BOM, CRLF) | Writes the **displayed** values like Excel (number formats applied, formula results, never column-width `#####`); `file.exportCsv {values: true}` writes raw values instead. No encoding choice on save. Re-import of a single-column export splits text (#127) A used range that would write more than 512 MiB (two far-apart cells span the whole rectangle) is refused with an error instead of being expanded (#528). |
| `.txt` tab-delimited, Unicode text | R/W | yes (`.txt`, `.tsv`, `.tab`; likely Windows-1256 Arabic detection for invalid UTF-8) | yes (displayed values) | Text Import Wizard options partly via Text to Columns |
| `.prn` formatted text (space-delimited) | R/W | no | no | |
| `.dif`, `.slk` (SYLK) | R/W | no | no | Small, legacy |
@@ -115,6 +115,7 @@ refresh. Counted under features (Get & Transform, ~10% ready), not here.
| 2026-10-20 | minor | A workbook theme's major/minor per-script typefaces (`<a:font script="Arab" typeface="…"/>`, also Hebr, Jpan and the rest) are read and written back in file order after `<a:cs>`; they were dropped before. A hostile file is held to 64 entries of 256 characters |
| 2026-10-20 | trivial | A workbook theme's major/minor East Asian (`<a:ea>`) and complex-script (`<a:cs>`) typefaces are read and written back, so a Japanese theme's fonts (e.g. 游ゴシック) survive open + save; they were deleted before because the model had nowhere to keep them. Rendering still resolves fonts without them (follow-up) |
| 2026-10-19 | minor | XLSX font charset and family values are preserved on save, retaining the character set and family used for substitute font selection |
| 2026-10-26 | trivial | `file.exportCsv {values: true}` writes raw cell values (full-precision numbers, ISO 8601 dates and times) for automation; the default stays Excel's displayed text (#2648) |
| 2026-10-19 | minor | CSV and text import decodes likely Windows-1256 Arabic; other invalid UTF-8 retains the Windows-1252 fallback |
| 2026-10-19 | minor | .xls import skips dialog sheets (WsBool `fDialog`), which share the worksheet BOF type and were read as empty worksheets |
| 2026-10-19 | minor | `.xls` import reads WINDOW2: right-to-left sheets (common in older Arabic and Hebrew files) open right-to-left, and hidden gridlines, headings and zero values and Show Formulas carry over |
+6 -3
View File
@@ -1,6 +1,6 @@
# Function parity: worksheet functions and formulas
> **Last reviewed:** 2026-10-31 · **Last updated:** 2026-10-31 · **Change:** minor (Student-t density uses a stable large-degrees-of-freedom gamma ratio, matching Excel at degrees through 10 billion; earlier: 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
> **Last reviewed:** 2026-10-31 · **Last updated:** 2026-10-31 · **Change:** minor (Student-t density uses a stable large-degrees-of-freedom gamma ratio, matching Excel at degrees through 10 billion; earlier: ECMA.CEILING listed with USDOLLAR as not on Microsoft's list; IMSQRT reproduces the stray real part Excel gives for negative reals, and the other IM* differences from Excel are listed; the LAMBDA row lists the recursion limit and its `#NUM!`, and the error a defined name holding an error gives when called; number text with accounting parentheses coerces like Excel 365; INDEX with one index on a 2-D reference is #REF! as in Excel; 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).
@@ -20,8 +20,8 @@ Excel's worksheet functions, category by category, against GridCraft's function
evaluator's special forms in `crates/calc/src/eval.rs` (`SPECIAL_FUNCTIONS`: IF, IFS, CHOOSE,
OFFSET, INDIRECT, INDEX, SUBTOTAL, AGGREGATE, TEXT, LET, LAMBDA, MAP, REDUCE, SCAN, BYROW, BYCOL,
MAKEARRAY…), excluding the test-only `ADD2`. Every one is an Excel name.
- **Not on Microsoft's list:** USDOLLAR, a legacy function Excel still evaluates (like DOLLAR),
is in our library as a Compatibility function for files that use it. It is not counted above.
- **Not on Microsoft's list:** USDOLLAR and ECMA.CEILING, legacy functions Excel still evaluates (like DOLLAR),
are in our library as Compatibility functions for files that use them. They are not counted above.
- **Working** excludes stubs that return an error by design: the 7 CUBE functions (`#N/A`, no
cube connections) and WEBSERVICE and FILTERXML (`#VALUE!`). Excel for Mac also lacks
WEBSERVICE and FILTERXML; Excel for Windows has them.
@@ -90,6 +90,7 @@ This improves series preparation; it does not change the model-fitting or season
| 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 |
| IMSQRT and the other IM* functions against Excel's text | IMSQRT follows Excel's polar route, so a negative real gives Excel's stray real part: `IMSQRT("-4")` is `1.22514845490862E-16+2i` (checked in Excel 16.0 on 79 inputs). Differences kept: IMSQRT of a number with a part above about 1E154 or below about 1E-155 gives the exact root (Excel gives `#NUM!` or 0); IMPOWER, IMEXP, IMSIN, IMCOS, IMSINH and IMCOSH keep exact results where Excel shows the same kind of noise (`IMPOWER("-4",2)` is `16-3.92047505570758E-15i`) or different trailing digits near multiples of pi/2; IMCSC and IMCSCH differ by one unit in the 15th digit; IMARGUMENT returns 16–17 digits where Excel returns 15; the text of IM* and COMPLEX results differs for some magnitudes (Excel writes `COMPLEX(1E-10,1)` as `0.0000000001+i` but `COMPLEX(0.000000123456789012345,1)` as `1.23456789012345E-07+i`, and an integer past 15 digits rounded and zero-padded) |
| External workbook references (`[Book.xlsx]Sheet1!A1`) | parsed; links dropped on open, no update |
| Localized function names and `;` separators | **missing** (#45); en-US only |
| Long flat operator chains (`=A1+A2+…`, `-`, `*`, `/`, `^`, `&`, comparisons) | done: no operand limit below Excel's 8,192-character formula limit (Excel, observed: `=1+1+…` with 4,096 operands is exactly 8,192 characters with the `=` and calculates; one more character is refused). Evaluated (#484), printed (#628), stored, copied, adjusted, saved and reopened without recursing once per operand, on a 1 MiB stack (#916); written to XLSX as plain formulas, not dynamic arrays; Spanish entry accepts them |
@@ -124,6 +125,8 @@ workbooks; results checked in as data, not Excel files) and fixing what it finds
| Date | Change | Summary |
|---|---|---|
| 2026-10-31 | minor | Student-t density uses an asymptotic log-gamma ratio for large degrees of freedom, avoiding cancellation while preserving bounded work; checked against Excel at 100 million and 10 billion degrees |
| 2026-10-27 | minor | ECMA.CEILING added to the functions that are in our library but not on Microsoft's list (with USDOLLAR), so the counts don't change |
| 2026-10-26 | minor | IMSQRT uses Excel's polar route with its cosine reduction, so `IMSQRT("-4")` is `1.22514845490862E-16+2i` as in Excel (exact `2i` before); the rows list the IM* differences still open |
| 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 |
+326
View File
@@ -0,0 +1,326 @@
# Macros
> **Last updated:** 2026-10-25 · **Change:** minor (recording macros and the management commands; Record Macro takes a name, shortcut and description; the macro interface: Automate › Macros, the Macros and Record Macro dialogs, the step editor, shortcuts)
GridCraft macros are typed, declarative lists of commands, not VBA. Every step runs a GridCraft
command (the same ids the ribbon, CLI and MCP use) or one of three bounded control steps. A macro
declares the inputs it works on, runs as one undo step, rolls all workbook changes back on an
error (a file it already wrote stays), checks where its results will land before it writes them,
and does not touch files without permission.
Agents run macros with `macro.run` like any other command.
This page describes the engine, its commands and the macro interface in the window.
## A macro
```json
{
"name": "Totals",
"description": "Adds a total next to the amounts",
"inputs": [{"name": "Input", "header": true, "cols": [2, 2], "colTypes": ["text", "number"]}],
"output": {"anchor": "Input.right(2).cell(1,2)", "onConflict": "ask"},
"steps": [
{"type": "command", "id": "cell.set", "params": {"cell": "Output", "input": "Total"}},
{"type": "command", "id": "cell.set", "params": {"cell": "Output.offset(1,0)", "input": "=SUM({{Input.data.col(2)}})"}},
{"type": "repeat", "over": "Input.data", "of": "rows", "max": 1000, "body": [
{"type": "if", "cell": "Row.cell(1,2)", "op": "gt", "value": 100,
"then": [{"type": "command", "id": "home.bold", "params": {"range": "Row"}}]}
]},
{"type": "stop", "message": "Done."}
],
"permissions": []
}
```
- **Steps**: `command` (`id`, `params`), `repeat` (each row or column of `over`, at most `max`
times), `if` (`cell` compared with `value` by `eq`, `ne`, `gt`, `ge`, `lt`, `le`, `empty`,
`notEmpty` or `contains`; `then` and `else` lists) and `stop` (`message`; `rollback: true`
undoes the run). Every step can carry `enabled`, `breakpoint` and `note`. The short form
`{"command": "cell.set", "params": {…}}` is accepted for a command step.
- **Inputs**: `kind` (`cell` or `range`), `rows` and `cols` as `[min, max]` (0 for no maximum;
rows exclude the header), `header`, `colTypes` (`number`, `text`, `date`, `bool`, `any`),
`nonEmpty` and `defaultRef`. These declare what a range must look like for the macro to work
on it. An input's name is letters, digits and `_`, starting with a letter, and is neither a
token of its own (`Output`, `Row`, `Column`, `Name`) nor something that reads as a cell
reference (`Q1`), so a step written with plain A1 text keeps meaning the cell. The declaration
is checked as a whole: neither `rows` nor `cols` may have a least above its most, a `cell` input
has one row and one column (`[1, 1]`, or the default `[1, 0]`), no header and at most one column
type, and no input lists more column types than it allows columns. A run binds each input
to the range it is given, its default, or (for the first input) the selection, and refuses
with messages that name the cells: "Input column 2 (Amount) must hold numbers; C4 holds text."
- **Limits**: 1,000 macros a workbook, 10,000 steps a macro, Repeat and If nested 4 deep, and 64
inputs a macro. Names (of macros and inputs) and command ids are at most 255 characters;
descriptions, step notes, Stop messages and If values (as JSON) 4,096; references and tokens
(`output.anchor`, `defaultRef`, a Repeat's `over`, an If's `cell`) 1,024; a shortcut 64. A
macro lists at most 256 permissions, each a command id, and an input at most 16,384 column
types. One-line fields hold no line breaks or other control characters, and a command id is
letters, digits, `.` and `_`. A command step's `params` are bounded only by the size of the
whole macro (4,194,304 characters of JSON). A macro read from a file that breaks any of these is
left out with a warning; it is never changed to fit. When a macro runs, a Repeat whose range has
more rows or columns than its `max` is refused, and a run executes at most 100,000 steps (each
pass through a Repeat counts), passes its commands at most 256 MB of parameters, all steps
together, and takes at most 60 seconds, all three shared by the footprint dry runs and the run
itself (`timeLimit` to change the time; the web build has no clock, so there only the step and
parameter limits apply). The debugger gives each of its actions the time limit on its own.
## Tokens
References in step parameters can be tokens, worked out when the step runs:
| Token | Means |
|---|---|
| `Input`, or another input's name | the range the input is bound to |
| `Output` | the output rule's anchor |
| `Row`, `Column` | the current row or column of the Repeat around the step |
| `Name:Total` | a defined name, or a table's data |
Methods narrow or move a range and chain: `col(2)` or `col("Amount")`, `row(1)`, `cols(2..3)`,
`rows(2..)`, `cell(r,c)` (relative to the top-left cell; `cell(0,1)` is the cell above),
`offset(r,c)`, `resize(h,w)`, `data` (without the header row), `below(n)` and `right(n)`.
`col("Amount")` looks the name up in an input's header row, directly or after `data`; on a range
without a header row it is refused. A defined name can hold periods: `Name:Sales.Q1` is the name
`Sales.Q1`, and `Name:Sales.Q1.cell(1,1)` its first cell, because the longest name or table that
exists is used and methods follow it.
Write a token as the whole value of a reference parameter (`cell`, `range`, `target`, keys ending
in `Cell` or `Range`…), as `{"$ref": "Input.col(2)"}` anywhere, or as `{{Input.col(2)}}` inside
any string, such as a formula; `{{{{` in a string is a literal `{{`, and `{"$text": "Output"}`
anywhere is the text as it is, never read as a token or for `{{…}}` (a defined name called
`Output`, or `Dear {{name}}`). Under the general keys that
other commands use for plain words (`to`, `from`, `at`, `area`, `source`, `target`,
`destination`, `location`), a bare `Row` or `Column` stays text (a replacement word, a pivot
area); write `Row.col(2)`, `{{Row}}` or `{"$ref": "Row"}` there. Plain A1 references stay
absolute; a `Name:` anchor follows inserted rows. A reference with a sheet name is never a token,
so `Output!A1` is a cell on a sheet called Output, even when a token or input has that name.
`Input.data` on a range that is only a header row, and `Name:` of a table with no data rows, are
refused rather than read as the header.
## Running
`macro.run {name, inputs?, onConflict?, overwrite?, approve?, timeLimit?}`:
1. **Policy.** File commands other than Properties, exports and printing (such as Open, Save
and Close), app, window (including New Window, Hide and Unhide), automate and macro commands,
and Undo and Redo, never run in a macro. File › Properties runs, and so do exports and
printing (`file.exportPdf`, `exportCsv`, `exportHtml`, `print`, `printPreview`, `saveBytes`)
that only produce bytes. Reading or writing a file (`data.getData`, `data.fromTextCsv`,
`insert.picture` or `pageLayout.background` with a `path`, or an export or print with a
`path`) needs the command in the macro's `permissions`. Any `path` other than `null` counts,
including a blank one and a token, and the policy is checked again on the parameters as they
are when the step runs. A macro that touches files, or that came with a workbook opened from
a file, runs once the person approves it: the call fails with `approve: …` and runs with
`approve: true`. Approval lasts for the session, until the macro changes. Every step other
than an approved file step runs inside the file sandbox that scripts use, so no other
parameter reaches a file either.
2. **Footprint.** Unless the conflict rule is `overwrite`, the macro first runs in dry-run mode on
the real workbook, and everything is put back. Cells it would write outside its inputs that
hold data, and sheets and objects it would delete, are conflicts, handled by `onConflict` (or
the macro's `output.onConflict`): `ask` (the call fails with `confirm: …`; `overwrite: true`
answers it), `fail`, `overwrite`, `shiftDown` or `shiftRight` (Insert Cells makes room, then
the footprint is checked again). Steps that touch files, and Solver, end the dry run early;
the footprint then says it is partial. Unless the rule is `overwrite`, the run also checks
every step as it goes, since the footprint can miss a write: it is partial, or a value such as
RAND or NOW sends the run another way than the dry run. A step that writes over cells, sheets
or objects that held data when the run started stops the run and rolls the workbook back (a
file already written stays): it asks (`confirm: …`) under `ask` and fails otherwise, since
shifting can't make room that late. Moving cells that hold data, as inserting or deleting rows
or cells above them does, counts as writing over them, in the footprint and in this check, and
so does a formula whose references such a move rewrites: both compare cells by place, so they
may ask about data that only moved, never the other way.
3. **Run.** One undo step, "Run Macro: Totals". Any error rolls the whole run back and names the
step: `Step 3.1 (cell.set): …` (paths are 1-based; `2.1` is inside a Repeat, `4.then.1` inside
an If). The message ends "Nothing was changed.", or names the files the run already wrote,
which stay. An input the macro doesn't have, an input that isn't reference text, and
`onConflict`, `overwrite`, `approve` or `timeLimit` of the wrong kind are refused rather than
ignored. When a step inserts, deletes or moves sheets, rows, columns or cells, the inputs, the
output and the range of a Repeat in progress move with their cells, as references in formulas
do; a step that deletes one of them, or changes the size of a range a Repeat is going over,
stops the run.
`macro.check {name, inputs?, onConflict?, overwrite?, timeLimit?}` reports the bound inputs, the
output anchor, any approval needed and the footprint, and puts back everything the dry run
changed. `ok` says whether `macro.run` with the same parameters would go ahead without a question;
when it wouldn't, `errors` says why: the run would fail at a step, a Stop step rolls it back, it
would write over data under `ask` or `fail`, shifting can't make room (the check shifts and
checks again, then puts everything back), or the run would go past
the step or parameter limit counting the dry runs before it. The random numbers RAND gives move
on, as after any recalculation.
## Debugging
`macro.debug {action, …}` runs a macro a step at a time. The actions are:
- `start` with `name` and the options of `macro.run` (`inputs`, `onConflict`, `overwrite`,
`approve`, `timeLimit`), refused as `macro.run` refuses them when they are of the wrong kind. The
checks, the approval and the footprint come first, as for `macro.run`; then the run pauses
before its first step.
- `step` runs the next step; `continue` runs to the next breakpoint or the end; `runTo` (`step`:
a path) runs to that step, stopping at a breakpoint on the way.
- `stop` ends the run: `keep: true` keeps the changes as one undo step, otherwise they are rolled
back.
- `state` answers without running anything (the default while a macro is being debugged).
`breakpoints` (a list of step paths of the macro, at most 10,000) can be given with any action and
replaces the list set before; `breakpointsOnly: true` makes the list replace the steps' own
breakpoint flags, also when it is given without a list. An unknown action, a breakpoint or `runTo`
path the macro doesn't have, and `keep` or `breakpointsOnly` that isn't `true` or `false` are
refused before anything changes. The time limit applies to each action on its own (`start` with
its dry runs, and each `step`, `continue` or `runTo`); the step and parameter limits apply to the
whole run, counting the dry runs.
Each answer gives the next step with its parameters as they would run (a Repeat over whole rows or
columns shows the part of the sheet in use, as the run goes over it), the parameters of the step
that just ran, the steps the run has executed (the dry runs not counted), and what the run has
changed in its workbook: `changed` lists at most 500 entries (cell ranges, and sheets added,
deleted or renamed) and `changedCells` counts the changed cells on the sheets it had at the start. `shifted` names the rule that made
room at the start, and `wroteFiles` the files the run wrote, which stay when the run is rolled
back; the answer to a rolled-back run says so in `message`. A run that ends (its last step, a
Stop step, or a failed step, which rolls it back) or is stopped answers with `state` `finished`,
`stopped` or `failed`.
While a macro is being debugged, its workbook is locked between steps: only `macro.debug`, queries
(commands that only read), commands that look around (`selection.*`, Select All, `sheet.activate`,
`sheet.next` and `sheet.previous`, Go To, Go To Special, Find, Copy and the zoom commands), Switch
Window, Close (which ends the run with its workbook) and Automate › Stop Action run there.
Everything else, undoable or not, saving included, is refused with "Finish or stop the macro
first", also when another command runs it (stopping Record Actions saves a script), and so is
`macro.check`, whose dry run runs the macro's steps. Other
workbooks stay editable. The person may look around between steps: before the next step runs, the
debugger puts the active sheet and the selection back where the run left them, so the run goes on
as `macro.run` would run it. A zoom change made meanwhile is part of the run, and is undone with
it. Stop Action rolls the run back and ends it, and its answer names the macro. The debugger runs
on its own, never as a step of a script or macro, and is never recorded.
## Recording
`macro.record` toggles recording. Starting takes `relative` (default true; `false` for absolute
references) and, from the Record Macro dialog, `name`, `shortcut` and `description`: the macro's
name, its Ctrl+Shift shortcut letter (`"Ctrl+Shift+M"` or `"M"`) and its description. They are
checked at once, so a name that is taken (a kept one included), too long or holds line breaks, or a
letter that GridCraft or another macro uses, is refused before anything is recorded. Without a name
the macro is called `Macro1`, `Macro2` and so on. A `name` or `description` given when stopping wins
over the one given when starting; a name that another macro has by then gets a suffix, such as `Tidy
(2)`, so stopping never replaces a macro, and a shortcut another macro took meanwhile is left off,
which the result says in `shortcutDropped`. `cancel: true` ends a recording without saving it, the
way out of one whose macro can't be saved. A parameter the call would ignore is refused: `relative`
or `shortcut` when stopping, `stop: false` while recording, anything beside `cancel: true`, and one
of the wrong kind. The macro is saved in the workbook where recording started, even if another one
is active; if that workbook was closed, stopping ends the recording without saving it. When saving
the recording fails (a name the check refuses, a workbook at its macro limit, a macro being debugged
in that workbook), recording goes on, so stopping again keeps what was recorded. The person recorded
the macro, so in a workbook opened from a file it runs without the approval a macro that came with
the file needs, until it changes.
A recording keeps up to 100,000 commands and 64 MB of them as JSON, and a macro holds up to
10,000 steps and 4,194,304 characters of JSON. The steps past any of these limits, and steps
recorded in another workbook, are left out and counted in `dropped`.
With relative references the selection when recording starts is the example input: references in
and near it on its sheet become tokens (on another sheet `C4` stays that sheet's `C4`), and the
input's shape, header and column types are inferred from it as it was when recording started,
before the recorded steps changed it. Recorded text that playback would read as a token stays
text: a reference parameter that reads as one (a defined name called `Output`) is written
`{"$text": "Output"}`, and `{{` in a string is written `{{{{`. Toggles such as Bold are recorded
with the state they set (`"on": true`), also inside a script run, so playing them back sets them
rather than flipping them.
Commands a macro may not run are left out and listed in `skipped`, also from the steps of a macro
or script run while recording. A macro run while recording is replaced by its steps when it has
no inputs, no output and only plain commands (otherwise it is left out and listed), and an
Automate script run by the steps it ran, without a step that only opened a dialog (a menu-style
call without its parameters). Both are kept as they ran, so editing or deleting the macro or
script before the recording stops changes nothing; a run whose steps did not fit in the
recording's 64 MB is left out and listed.
Undo while recording works as in Excel: `edit.undo` is left out (a macro never runs it), and the
step it undid stays in the recording, so playing the recording back makes that edit again.
GridCraft keeps the Ctrl+Shift letters it uses itself on some platform: F, G, H, J, K, L, R, S, T
and Z (`macros::RESERVED_SHORTCUTS`). A shortcut a file brings with it on one of these letters is
kept but does not run the macro.
## Other commands and storage
`macro.list`, `macro.get`, `macro.save {macro, replace?}`, `macro.delete` and `macro.rename
{name, to}`. Saving under a name a macro has is refused unless `replace: true` is passed, and
renaming to one is refused; names compare ignoring the case of the letters A to Z. A renamed macro is checked
against every limit again, its size included. Saving, renaming and deleting are undoable.
Macros are saved in the workbook: as a field in GridCraft's JSON format, and in xlsx as hidden
defined names (`_gc_macro_<n>_<k>`, the macro's JSON split into string literals within Excel's
limits), which other spreadsheet applications keep but do not run. A visible name of that shape
is someone's own name: opening a file leaves it alone, and saving stores the macros under indices
it does not use.
Each macro carries its format version, `v` (1 in this version; a missing `v` reads as 1). When a
file is opened, a macro in a newer format (a `v` above 1) is kept unread, with a warning: this
version can't show or run it, and saving writes it back unchanged, so the newer
version gets it back whole. It is never read in part. Kept macros count toward the 1,000-macro
limit after the readable ones, and one too large for an xlsx file is left out. They count when a
macro is added too (saving, or stopping a recording, is refused once readable and kept macros
together reach 1,000, and the message says how many are kept), so a save holds no more macros
than the next open keeps. Their names are taken: saving or renaming a macro to one is refused, and
a recording gets a suffix. A macro that is
damaged or over the limits is left out with one warning, and the rest of the workbook and its
other macros open; a macro that was left out is not in the saved file.
## In the window
- **Automate › Macros group.** Macros opens the Macros dialog. Record Macro opens the Record
Macro dialog (name, Ctrl+Shift shortcut letter, description, Use Relative References); OK
starts recording and stays off while the name or the letter cannot be used, with the reason
under the fields. While recording, the button reads Stop Recording, the status bar shows a red
dot, "Recording · N steps" and Stop, and Use Relative References is locked. Run Macro runs the
macro run last (or the only one); its menu lists the recent macros, then every macro. New
Macro opens the step editor on a new macro.
- **Macros dialog** (Automate › Macros, View › Macros, Alt+F8). It lists the workbook's macros
(with their shortcuts), then its Automate scripts, and says when the file carries a VBA project.
Scripts that came with an opened file stay disabled until Trust Scripts (`automate.trustScripts`)
trusts them for the session. Run runs the selected macro on the selection or the selected
script; Step Into, Edit and Create open the step editor; Delete asks first; Options sets a
macro's shortcut letter and description. The dialog keeps the result of `view.macros`, as before
macros existed.
- **Step editor.** A resizable window that works on a draft and saves it with `macro.save`; in a
narrow app window it is no larger than the window and its toolbar wraps onto a second row. The
step list shows each step's number, a breakpoint dot in the gutter (click to set or clear), a
category icon, a summary, an error marker and an enabled checkbox; Repeat bodies and If branches
are indented under their step, and rows drag to reorder within their list. Add Step opens a
command picker (control steps first; commands a macro may never run are left out); Duplicate and
Delete act on the selected step. The inspector's Details tab edits the selected step, with
reference boxes that take a typed reference or a selection on the grid and switch between a
token relative to the example input (Rel) and an absolute reference (Abs); the Inputs tab sets
each input's shape, header and column types, and Use current selection as example infers them;
the Output tab sets the anchor and what to do when results would land on data. The editor
belongs to the workbook it opened in: while another workbook is active it says so and Save,
Check, Run and Debug wait until that workbook is active again, and closing the workbook closes
the editor. It keeps its window until it closes itself: another dialog asked for meanwhile is
refused with a message, and closing the app first asks about the editor's debug run and unsaved
steps, then about unsaved workbooks.
- **Debug bar.** Check dry-runs the macro and colours the grid: inputs blue, the cells it writes
green, the cells among them that hold data red. Run runs it as one undo step. Step, Continue and
Run to Here start the debugger when needed (`macro.debug` with the editor's breakpoints and
`breakpointsOnly: true`, so a dot cleared while paused takes effect at once); the current step is
selected, its parameters are shown as they will run, and its target is outlined on the grid.
Stop asks whether to keep the changes so far or undo them. While a macro is debugged the status
bar says "Debugging" with the macro's name, and Show reopens the editor.
- **Questions.** A macro that needs approval offers Run; a macro whose output would land on data
offers Overwrite, Shift Down and Shift Right. Each button runs the command again with
`approve: true` or the chosen `onConflict`, in the workbook that asked: an answer given while
another workbook is active does nothing and says why.
- **Shortcuts.** Ctrl+Shift+letter (⌘⇧letter on a Mac) on the grid runs the macro with that
letter. The letters GridCraft uses itself keep their own meaning.
Commands with empty parameters never open these dialogs: `macro.record {}` starts recording
under the next free name, and the dialogs are opened only from the ribbon, the menus and keys.
## Automate scripts and VBA
Automate scripts (`automate.recordActions`, `newScript`, `allScripts`, `runScript`,
`deleteScript`) are an older, separate feature. They keep their commands, parameters, storage
(`_sc_scripts`) and results, and View › Macros (`view.macros`) still lists them. Macro commands
neither read nor change their storage; a recording keeps the steps a script run ran while it
recorded. A workbook can hold a script and a macro with the same name.
A VBA project in an `.xlsm` or `.xltm` file is preserved on save and never run. GridCraft macros
are stored apart from it.
+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: 275 / 291 commands implemented (94.5%).**
**Overall: 276 / 292 commands implemented (94.5%).**
| Tab | Implemented | Total | Parity |
|---|---|---|---|
@@ -16,7 +16,7 @@ Each entry is an Excel ribbon or menu command (feature names only). It counts as
| Formulas | 23 | 23 | 100% |
| Data | 23 | 28 | 82% |
| Review | 15 | 18 | 83% |
| View | 20 | 20 | 100% |
| View | 21 | 21 | 100% |
| Automate | 4 | 4 | 100% |
| Table Design | 13 | 13 | 100% |
| Chart Design | 8 | 8 | 100% |
@@ -299,7 +299,7 @@ Missing:
- `review.translate` Translate (Language)
- `review.showChanges` Show Changes (Changes)
## View (20 / 20, 100%)
## View (21 / 21, 100%)
Implemented:
@@ -323,6 +323,7 @@ Implemented:
- `view.unhideWindow` Unhide (Window)
- `view.switchWindows` Switch Windows (Window)
- `view.macros` Macros (Macros)
- `macro.record` Record Macro (Macros)
## Automate (4 / 4, 100%)
+7 -4
View File
@@ -1,6 +1,6 @@
# Target-app parity: GridCraft vs Microsoft Excel
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-22 · **Change:** minor (several windows: a native window per workbook window, New Window on the same workbook, Arrange All; no score change) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-25 · **Change:** minor (automation: macro interface; earlier: catalog count 276 / 292, as `cargo xtask parity` measures; automation: typed macros in the engine; several windows: a native window per workbook window, New Window on the same workbook, Arrange All; no score change) · **Target:** Microsoft Excel (Microsoft 365)
This is the authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it,
[`gaps.md`](gaps.md) lists every shortfall, and the area checklists hold the detail:
@@ -13,7 +13,7 @@ generated ribbon checklist is [`parity-checklist.md`](parity-checklist.md).
| Number | Value | Kind |
|---|---|---|
| Feature breadth (weighted by use) | **~75%** | estimated from the area table below; anchored on two measurements |
| Ribbon and menu catalog | **274 / 290 (94.5%)** | measured: `cargo xtask parity` (`crates/engine/src/catalog.rs` ∩ registered command ids) |
| Ribbon and menu catalog | **276 / 292 (94.5%)** | measured: `cargo xtask parity` (`crates/engine/src/catalog.rs` ∩ registered command ids) |
| Worksheet functions, by name | **513 / 523 (98.1%)** | measured: registered functions vs Microsoft's published function list (method below) |
| Worksheet functions that do real work | **504 / 523 (96.4%)** | measured: excludes 9 stubs (7 CUBE functions, WEBSERVICE, FILTERXML) |
| **Ready for real work** | **~50%** | estimated; written weights below (51.6 computed) |
@@ -54,7 +54,7 @@ long `.xls` track, so about half parallelizes (≈30–50 wall-clock hours with
COPILOT and PY: **523 functions**. Ours: every `f!(…)` spec in `crates/functions/src/*.rs` plus
`SPECIAL_FUNCTIONS` in `crates/calc/src/eval.rs` (evaluator special forms), minus the test-only
`ADD2`: **505**, all of them Excel names.
- **Ribbon catalog:** `crates/engine/src/catalog.rs` (290 unique ids across 15 tabs) against the
- **Ribbon catalog:** `crates/engine/src/catalog.rs` (292 unique ids across 15 tabs) against the
313 command ids registered in `crates/engine/src/cmd/*.rs`, recomputed from source by script and
identical to the committed `parity-checklist.md`. The catalog is our own transcription of
Excel's main ribbon tabs; it leaves out most contextual tabs (Picture Format, Shape Format,
@@ -93,7 +93,7 @@ performance 8, stability 8, localization 4, hardware 2, platforms 2, ecosystem 2
| Get & Transform, external data, data model | 5 | ~15% | ~10% | 60–100 | Import of CSV/TSV, JSON, HTML. No Power Query editor, no From Web, database or folder sources, no connections or refresh, no relationships or data model, no external workbook links (dropped with a warning) |
| Insert objects | 3 | ~55% | ~45% | 25–40 | Pictures, 8 shape kinds (Excel has ~170), text boxes, own icon set, ink with Ink to Shape, hyperlinks, checkboxes. Missing: SmartArt, WordArt, equations, signature line, objects, 3-D models, screenshot, pictures in cells (PR #105) |
| Review, comments, protection, collaboration | 4 | ~55% | ~40% | 35–55 | Threaded comments, notes, spelling (system dictionary), thesaurus, sheet and workbook protection, Allow Edit Ranges with Excel-compatible password hashes (#307). Missing: comment navigation, Show Changes, notes-to-comments conversion, accessibility checker, co-authoring, opening password-encrypted files (refused with a message since #17) |
| Automation | 4 | ~30% | ~20% | 12–20 | Recorded, editable GridCraft scripts (Automate tab). VBA is out of scope by owner decision and is **dropped** when an `.xlsm` is opened (warning shown). No Office Scripts compatibility |
| Automation | 4 | ~30% | ~20% | 12–20 | Recorded, editable GridCraft scripts (Automate tab). Typed macros in the engine (`macro.*`: recorder, runner with input checks and a footprint dry run, debugger; [macros.md](macros.md)), recorded from Automate › Macros, edited and debugged in a step editor, and run from the Macros dialog or a Ctrl+Shift shortcut. VBA is out of scope by owner decision: an `.xlsm` keeps its VBA project unchanged on save and never runs it. No Office Scripts compatibility |
| **Weighted** | 100 | **~75%** | **~59%** | **300–485** | |
## Dimensions
@@ -334,11 +334,14 @@ The inventory carried over from the 2026-10-07 ROADMAP.md, updated for what land
| Date | Change | Summary |
|---|---|---|
| 2026-10-25 | minor | Automation: macro interface (Automate › Macros, the Macros and Record Macro dialogs, the step editor, shortcuts) (no score change) |
| 2026-10-25 | trivial | Catalog count 276 / 292 in the summary table and the method, as `cargo xtask parity` measures (no score change) |
| 2026-10-22 | minor | Several windows on the desktop: a native window per workbook window, New Window on the same workbook, Switch Windows and Arrange All; "one window" removed from UI/UX fidelity, Hardware and the interaction-fidelity discount; no score change |
| 2026-10-21 | minor | Page Setup paper sizes: `B4`/`B5`/`B6` are the JIS sizes Japanese Excel uses (ISO B4/B5/B6 by name), and Japanese Postcard, Japanese Double Postcard, the chou/kaku envelopes and A6 are added; PDF MediaBox and `file.print` follow, and the Size menu lists them |
| 2026-10-18 | trivial | Stale chart notes: all chart kinds survive XLSX round-trip (PRs #158, #161) and trendlines landed |
| 2026-10-16 | minor | Advanced Filter copy-to clears only its actual output and rejects overflow without changing cells (#1031) |
| 2026-10-15 | minor | Row auto-fit: line breaks count only in wrapping cells (Excel: wrap off shows one line, AutoFit gives one line); unwrapped multi-line cells from files no longer grow rows on open (no score change) |
| 2026-10-21 | minor | Automation: typed macros in the engine and `macro.*` commands, no interface yet (no score change); catalog 274 / 290 → 275 / 291 (View › Macros › Record Macro) |
| 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) |
+18 -5
View File
@@ -1,10 +1,10 @@
# UI parity
> **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
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; earlier: editing a hyperlink loads its existing fields; earlier: AutoSum over a selected row puts its total to the right; ribbon catalog count 276 / 292) · **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
separately in [parity-checklist.md](parity-checklist.md) (274 / 290).
separately in [parity-checklist.md](parity-checklist.md) (276 / 292).
Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes),
`crates/engine/src/cmd` (shortcuts in command specs), the owner's black-box notes
@@ -106,7 +106,7 @@ otherwise. Number-format and border keys match the physical key (Ctrl+Shift+4, w
| Ctrl+S, Ctrl+Shift+S, Ctrl+O, Ctrl+N, Ctrl+W | Cmd | save, save as, open, new, close | present |
| Ctrl+F, Ctrl+H, Ctrl+G, Ctrl+K, Ctrl+T | Cmd (Ctrl+H) | find, replace, go to, hyperlink, table | present |
| Ctrl+D, Ctrl+R, Ctrl+E, Ctrl+1, Ctrl+9 / 0, Ctrl+Shift+9 / 0 | Cmd (Ctrl+E on Mac: see below) | fill down/right, Flash Fill, Format Cells, hide/unhide | present |
| Ctrl+- , Ctrl+Shift+= | Cmd | Delete / Insert cells dialog | present |
| Ctrl+- , Ctrl+Shift+= , Ctrl+numpad + | Cmd | Delete / Insert cells dialog | present |
| Ctrl+; , Ctrl+` | Control+; | current date, show formulas | present (Mac Control+; added) |
| Arrows, Shift / Ctrl / Ctrl+Shift+arrows, Home, Ctrl+Home / End, Page Up / Down, Ctrl+Page Up / Down | Cmd+arrows | navigation and extension | present |
| Tab, Shift+Tab, Enter, Shift+Enter, Ctrl+Space, Shift+Space, Delete, Esc | same | entry and selection | present |
@@ -142,7 +142,8 @@ otherwise. Number-format and border keys match the physical key (Ctrl+Shift+4, w
| 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+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 |
| Alt+F8, Ctrl+Shift+letter | Option+F8, Cmd+Shift+letter | Macros dialog, a macro's own shortcut (letters GridCraft uses keep their meaning) | **added** |
| Ctrl+Shift+Page Up / Down, Ctrl+Alt+5, Ctrl+Shift+F1, Ctrl+F5–F10, Alt+F11 / F12 | same | group sheets, shape cycling, full screen, window keys, 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) |
On Windows, AltGr arrives as Ctrl+Alt: a Ctrl+Alt key that also types a character is treated as
@@ -151,9 +152,15 @@ a character (e.g. Ctrl+Alt+A for `ä`) never trigger a shortcut, Ctrl+letter one
## Dialogs
Insert Hyperlink loads an existing cell link's address, display text and ScreenTip for editing
(#2876). Keeping the display text unchanged preserves the cell's number or formula; Cancel
keeps the original link and an edit can be undone in one step.
Format Cells, Insert Function/Formula Builder, Find and Replace, Go To and Go To Special, Sort,
Name Manager, Data Validation, Paste Special, Conditional Formatting manager, Page Setup, Goal
Seek, Scenario Manager, Data Analysis, Forecast Sheet and others exist. Missing or partial: Options,
Seek, Scenario Manager, Data Analysis, Forecast Sheet, Macros (macros, Automate scripts and a
note on a VBA project; Run, Step Into, Edit, Create, Delete, Options), Record Macro, the macro step
editor (Excel's VBA editor has no counterpart here) and others exist. Missing or partial: Options,
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).
@@ -187,8 +194,14 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
| Date | Change | Summary |
|---|---|---|
| 2026-10-29 | minor | Formula pointing from the keyboard: Ctrl+arrow (⌘arrow) moves the pointed cell to the edge of its data and Ctrl+Shift+arrow (⌘⇧arrow) extends the pointed range there, as the same keys move and extend the selection, so typing `=COUNTIF(`, clicking the top of a column and pressing ⌘⇧↓ points at the whole column of data; the keys used to move or extend by one cell. A range reaching the sheet edge is written as whole columns or rows (`A:B`). The editor reads the modifiers of the arrow's own key event, so a keystroke pressed and released within one frame (a control client's `ui.key`, an automation tool) keeps its Shift and Ctrl |
| 2026-10-29 | minor | Insert Hyperlink opens with an existing link's address, display text and ScreenTip (#2876). An address-only edit preserves the cell's content and formula; Cancel leaves the link unchanged, and Undo restores the edit |
| 2026-10-27 | minor | Ctrl with the numpad's + inserts rows, columns or cells as Ctrl+Shift+= does, as in Excel; it did nothing because the numpad key arrives without Shift |
| 2026-10-25 | minor | AutoSum (Alt+=, Home › AutoSum) over a selected row puts its total to the right of the row, or in its last cell when that is empty, as in Excel; it wrote a one-cell total under each cell. A selection's empty last column takes each row's total and its empty last row each column's, with the block's total in the corner when both are empty |
| 2026-10-25 | minor | Paste Name lays out only the names in view, so a workbook with thousands of names opens it at once; the arrow keys still scroll the chosen name into view. Go To matches a `Sheet2!Name` reference and hides a workbook name behind the active sheet's own name in any case and script (`Größe` = `GRÖSSE`), as formulas do. The two Page Layout view rows are one; the 0.7 in side margin used when Align with Page Margins is off is marked unverified |
| 2026-10-24 | minor | Macro step editor: step list with breakpoints, drag to reorder and a command picker, an inspector with reference boxes, a debug bar and grid overlays |
| 2026-10-24 | minor | Automate › Macros group (Record Macro, Use Relative References, Run Macro), recording status in the status bar, Macros dialog with macros beside scripts, Record Macro dialog, Ctrl+Shift+letter macro shortcuts; View › Macros keytips open the dialogs |
| 2026-10-24 | minor | Ribbon catalog count 275 / 291 → 276 / 292 (View › Macros › Record Macro) |
| 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) |