Merge #2151 (9fdf03cf, with #465 at 3f8e6c28 and main at 6ead1fb5, where #462 is merged) into #2144

# Conflicts:
#	crates/engine/src/lib.rs
#	docs/macros.md
This commit is contained in:
prxnvt
2026-10-25 02:17:32 -04:00
52 changed files with 2316 additions and 584 deletions
+33 -15
View File
@@ -1,6 +1,6 @@
# Architecture
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** minor (engine building blocks for multi-command actions) · **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).
@@ -34,7 +34,9 @@ including tests, 2026-10-10.
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, its selection and its
undo and redo history as they were before the command (never-crash guard).
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.
@@ -59,24 +61,40 @@ Actions made of many commands (macros, a debugger that runs one step per call) u
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. The history limit is applied when the outermost command finishes; until
then the steps made inside it are bounded on their own.
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
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, and the buffer is independent of the journal
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; 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, and 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`).
`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. The older single-name `_sc_<key>`
items are read as well.
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`) 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.
## Agent control
@@ -103,6 +121,6 @@ rpm, tarball), FreeBSD, and web (WASM; WebGPU or WebGL2). Release workflows in
| Date | Change | Summary |
|---|---|---|
| 2026-10-23 | minor | Engine building blocks for multi-command actions: undo groups, recording buffer, document trust, chunked hidden-name storage |
| 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 -2
View File
@@ -1,6 +1,6 @@
# Gaps
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-20 · **Change:** minor (the Japanese catalog covers the dialogs, messages and menus) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-23 · **Change:** minor (§8: 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
@@ -84,7 +84,8 @@ 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 isn't modelled yet.
- **Impact:** pivots are the analyst's main tool.
- **Estimate:** 20–30 h for beta (25–40 h full).
@@ -178,6 +179,7 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
| Date | Change | Summary |
|---|---|---|
| 2026-10-23 | minor | §8: PivotTable Show Details: double-clicking a value cell lists its source records on a new sheet (#2364) |
| 2026-10-20 | minor | §11: the Japanese catalog covers the dialogs, messages and menus — 943 keys (~43%), matching the pt-BR coverage; a test keeps the two catalogs in step |
| 2026-10-17 | minor | §6: the five reports it tracked are closed and their PRs merged (#57, #133, #139, #140, #134; PRs #148, #155, #156, #157); the entry stays open for §15's oracle suite |
| 2026-10-15 | minor | §13: Solver prepares reports before committing the solution, model and report sheets together; report refusal preserves the workbook and Undo/Redo (#1050) |
+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) |
+31 -15
View File
@@ -40,16 +40,28 @@ no macro debugger, recorder or management commands in this version, and no macro
`{"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`. 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. 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,
100,000 steps executed per run and 60 seconds a run, both 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 limit applies).
`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) and takes at most 60 seconds, both 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 limit applies).
## Tokens
@@ -118,13 +130,17 @@ approval needed and the footprint, without changing anything.
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.
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 that is damaged, over the limits or in a newer format (a `v` above 1) is
left out with one warning, and the rest of the workbook and its other macros open. A newer
macro is never read in part, so a save can't write it back with its newer fields missing; a
macro that was left out is not in the saved file.
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. 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.
## Automate scripts and VBA