Commit Graph
1680 Commits
Author SHA1 Message Date
prxnvt 551eda7e8e Engine: the xlsx copy gets an empty macro list, not a copy of the shared one cleared
out.macros shares its list with the workbook being saved, so clear() copied
every macro first.
2026-10-23 23:08:49 -04:00
prxnvt 43b0e39e8e Engine: set_many refuses one key given twice
Two items under one key, in any case, would leave two hidden names of one
name, of which an xlsx file keeps one, and get would mix their chunks. A
caller's mistake, refused before anything changes. Regression test.
2026-10-23 23:08:49 -04:00
prxnvt bbf36000d4 Engine: the recorder counts a dropped step without serializing it
Once a step has been dropped, or the step count is full, push serialized
each later step only to count it; a file.open step holds a whole file. The
checks that need no size come first.
2026-10-23 23:08:36 -04:00
prxnvt 0df540f166 Merge #2151 (44a2f4e2, with #465 at 673af7ec, #462 at 712bb766 and main at 530a8cc8) into #2144
# Conflicts:
#	docs/macros.md
2026-10-23 22:36:10 -04:00
dotCooCoo c22787e5bf Merge main into fix/lambda-recursion-stack 2026-10-23 19:32:12 -07:00
Helen Kwok b389d9067a XLSX: write accounting formats 42 and 44 with their code
builtin_id already wrote the currency formats 5-8 as custom formats
because readers resolve those ids by locale. The accounting formats
with a currency, 42 and 44, are resolved the same way but were written
as bare ids, so LibreOffice showed a 44 cell holding -0.14 as "- 0.14"
where US Excel shows "$ (0.14)". Treat them like 5-8.
2026-10-24 10:17:32 +08:00
prxnvt 44a2f4e2f1 Merge #465 (673af7ec, with #462 at 712bb766 and main at 530a8cc8) into #2151
# Conflicts:
#	docs/macros.md
2026-10-23 22:08:05 -04:00
prxnvt 00b63468e6 Engine: macro tokens resolve only where they mean a reference, and refuse instead of guessing
Six fixes from a review of the resolver, each with a regression test that
fails without it:

- A bare Row or Column under a general reference key (to, from, at, area,
  source, target, destination, location) stays text: Change Spelling's
  replacement word and a pivot field's area were being rewritten into the
  current row's A1 range. Row.col(2), {{Row}} and {"$ref": "Row"} still
  resolve there, and the bare word still resolves under cell and range keys.
- {{{{ in a string is a literal {{, so text that needs the braces can be
  written; an unknown token is still an error.
- Input.data on a range that is only a header row, and Name: of a table
  with no data rows, are refused instead of reading the header.
- below(0), below(-3) and right(0) are refused like resize(0, 1) is, instead
  of becoming 1.
- A defined name on a sheet whose name holds a dollar sign resolves: the
  dollar anchors are stripped from the reference part only.
- quote_sheet is the engine's one rule (gridcraft_core::quote_sheet), which
  quotes a sheet named like a cell reference, an R1C1 token or a boolean and
  leaves a period unquoted.

docs/macros.md says which keys take a bare token and how to write a literal
brace.
2026-10-23 22:02:45 -04:00
prxnvt 673af7ecdc Merge #462 (712bb766) into #465 2026-10-23 22:00:39 -04:00
prxnvt 4672b6da51 Model: an input name that reads as a cell reference is refused
A macro could declare an input called Q1, AB12 or Sum1, and a step written
with plain A1 text, as a recorded one is, would then resolve to the input's
range instead of the cell. check_limits refuses such names; the docs say so.
Regression test.
2026-10-23 21:48:53 -04:00
NetterSilverandClaude Haiku 5.5 b47c83f6aa Reject unmatched '.' and ',' in NUMBERVALUE text
Found while testing crates/functions/src/text.rs:326-340

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 01:48:12 +00:00
prxnvt 712bb766ec Engine: a grouped action that panics is rolled back before the panic goes on
Session::grouped ran its action without a panic guard, so a caller outside
a command that caught the panic found the group still open: Undo and Redo
disabled, the partial edits kept, and the next grouped action nested in the
abandoned group. The group is now rolled back and the panic resumed.
Regression test.
2026-10-23 21:46:25 -04:00
prxnvt 8219c18b92 Engine: macro storage shares the list, saves in one pass, keeps unknown names, checks once
- Workbook::macros is a shared list (Macros, a copy-on-write newtype): a
  workbook copy made for a cell edit or an undo snapshot holds the same list,
  and the undo budget charges a list once a snapshot owns its own copy.
  Before, every mutating command copied every step of every macro, and a
  hundred undo entries kept a hundred copies the budget never saw.
- Saving to xlsx drops the last save's items and writes the macros in one
  pass each (stash::set_many), instead of a scan of every name per macro.
- A hidden _gc_macro_<x>_<k> name with a key this build did not write is
  kept on open and on save, as a visible one is, instead of being dropped
  without a warning.
- macros::check is the one check for every path that adds a macro (JSON, a
  file, built in code): the model's limits and the xlsx size limit.

Regression tests for each.
2026-10-23 21:45:54 -04:00
prxnvt 259b6e67e8 Model: Repeat and If nest four deep, as the limit says
The depth check refused any step list at the fifth level, so a fourth Repeat
or If could not even hold an empty body: three levels nested, not the four
MAX_MACRO_DEPTH and the docs state. The check now counts the Repeat or If
steps themselves, in the model's limits and in the parser's pass over the
JSON. The test asks for four and refuses a fifth.
2026-10-23 21:45:54 -04:00
prxnvt 3ca1252571 Engine: a group a panicking command ended keeps its step
A group open before a command and ended or rolled back inside it lost its
undo step when the command then panicked: the restored history held no entry
for the group's changes, so Undo skipped the state before the group. The
panic path now pushes the outermost such group's step from its start to the
restored workbook. The group stays ended, because its token went with the
command. Regression test for a group ended and one rolled back.
2026-10-23 21:36:09 -04:00
prxnvt cef58da39e Merge #462 (d7c3c183, with main at 530a8cc8) into #465 2026-10-23 21:25:35 -04:00
prxnvt d7c3c18345 Engine: the panic guard restores entries a nested Undo removed
Truncating the undo history to its length at the command's start cannot
bring back an entry that a nested edit.undo popped. The guard now keeps the
undo and redo history as they were and puts both back. The regression test
gains the undo-then-panic case.
2026-10-23 20:47:40 -04:00
prxnvt 8440427c53 Tests: a linked paste of column widths or notes copies its metadata and no cell
The source now carries a note and a column width, and the test checks that
the destination received them while its trust stays Local and no cell is
written.
2026-10-23 20:15:05 -04:00
prxnvt d1e51a4af4 Engine: a later stash chunk on its own is not a listed item
stash::keys reported a key that get read as missing, because only a later
chunk of it existed. keys now counts chunk 0 and legacy names only, as get
does. Regression assertions in the hostile-names test.
2026-10-23 20:08:40 -04:00
prxnvt 456cf73886 Engine: a headless call that fails for want of a file is not recorded
file.open with no file succeeds by queuing a dialog, which execute_headless
turns into an error for callers with no UI; the recording kept the step.
Recorder::truncate forgets it and keeps the byte count right. Regression test.
2026-10-23 20:08:40 -04:00
prxnvt a3b5f8e45c Engine: a command that panics leaves the undo history as it found it
The panic guard restored the workbook and selection but not the history, so
a nested edit made before the panic left a no-op undo step and the redo
history it had cleared stayed lost. The guard now truncates the steps made
during the command and puts the redo history back. Regression test, and the
architecture note says what the guard restores.
2026-10-23 20:08:40 -04:00
prxnvt 6b7c48f119 Engine: a stash chunk number with leading zeros names someone else's name
`_gc_watch_00` parsed as chunk 0, so a hidden one was read as the item's first
chunk and a visible one stopped `_gc_watch_0` from being written. Only the
chunk number as `set` writes it counts now. Regression test.
2026-10-23 20:01:21 -04:00
prxnvt 66e69cb946 Engine: a linked paste carries the file's trust whatever it pastes
Paste Link writes a formula into every cell whatever `what` says, so a linked
paste of formats from a file now makes the destination FromFile as a paste of
formulas does. Column widths and notes write no cells, linked or not, and
still carry nothing. Regression test for the three kinds.
2026-10-23 20:01:21 -04:00
Harrison Booth 35067fa9ca Read accounting negatives like $(1,234) in number text
Typed entry, VALUE and arithmetic now read $(1,234), $ (5) and (5)% as
negative numbers, as Excel 365 does. Parentheses no longer nest: (-5),
((5)), $5% and dates in parentheses stay text. This changes main's
((100)) from 100 to text; the no-recursion guard from #2180 is kept and
its test now asserts the new result.
2026-10-23 16:57:24 -05:00
Harrison Booth 86f4114514 Merge remote-tracking branch 'origin/main' into gc-lookup-exact-number-compare
# Conflicts:
#	ROADMAP.md
#	docs/function-parity.md
2026-10-23 16:55:41 -05:00
Harrison Booth e883c84e7b Merge origin/main into the Analysis ToolPak value-argument branch
Resolves conflicts in ROADMAP.md, docs/function-parity.md and
crates/calc/src/tests.rs: both sides' progress-log rows and tests are kept.
2026-10-23 16:55:37 -05:00
Harrison Booth 9fd7693d4e Merge fork's border keytips fix into main 2026-10-23 16:39:13 -05:00
Harrison Booth 1ce4781d3e Accuracy: PMT to Excel's last bits via ln_1p/exp_m1
PMT computed (1 + rate)^nper with powf, which rounds 1 + rate first and
drops the low bits of a small rate. Over 400 synthetic probes that left
PMT up to about 1000 ULPs from Excel (59 bit-exact); the ln_1p/exp_m1 form
is within 4 ULPs (244 bit-exact). A loan schedule guarded by
IF(p - SUM(principal) > 0, ...) turns on those bits: in 3000 synthetic
schedules main disagreed with Excel on the number of live periods in 1382,
the fix in 823.

Also follows Excel at the edges: a rate of -1 or less is #NUM!, a huge
nper gives -pv*rate instead of #NUM!, a tiny rate no longer collapses, and
a small negative rate keeps its payment. IPMT, PPMT, CUMIPMT and CUMPRINC
pick this up through pmt_calc. FV and PV are unchanged: Excel's own FV/PV
sit further from the exact value than the current powf form does.

The tests allow the one-ULP libm difference between platforms.
2026-10-23 16:24:18 -05:00
Harrison Booth 74b2bda75c Merge origin/main into gc-index-2d-ref-one-index 2026-10-23 16:02:22 -05:00
prxnvt 080b6d993b Engine: a grouped action that closes its document succeeds
Session::grouped ended its group after the action, and when the action had closed the group's document (file.close, or file.open taking the place of an untouched new workbook) the group had gone with it, so the action was reported as failed although it had done what it was asked. A group that is gone because its document is gone now counts as ended; a group ended by someone else while the document is open is still an error.
2026-10-23 16:36:34 -04:00
prxnvt 6ab8d97118 Engine: the undo steps made inside a command are bounded while it runs
The history limit is applied once the outermost command has finished, so the lengths its bookkeeping compares stay true (round 3). That left the steps nested commands make unbounded until then: a script of up to 100,000 steps, or a subtotal with a step per group, kept every one of them alive, each with the cells it retains, and before this branch every push trimmed the history at once.

Each nested command now bounds the steps made since the latest mark on its document to the undo limit and byte budget, dropping the oldest of them. The marks are the starts of the running commands, the starts of the open groups and the end of the last group: the positions a command or group collapses its steps by when it finishes, so nothing before them moves. What the bound drops is only what the final trim would drop.
2026-10-23 16:35:45 -04:00
prxnvt 114088d589 Engine: a command's changes after ending a group, or before opening one it leaves open, are not lost or a step of their own
Two boundaries of the group rules were wrong. A command that ended a group open before it was treated as grouped whatever it did next: with an unchanged group (no step) its later edit had no undo entry at all, and with nested edits after the end the entry was the nested command's. The group's step now stays the group's, and the command makes one step of its own, under its name, for what changed after the end, from the workbook the group ended at.

A command that edited and then opened a group it leaves open (a debugger's start) left its nested entries in the history, so one Undo after the group ended brought back a state from inside the command. Such a group now takes the command's start as its own, and its step covers the command's earlier edits.

The record of where the last group ended now holds the workbook and selection it ended at, the history length then and whether it made a step; it lives until the outermost command finishes. A rollback records where it ended too, unless the document is back exactly where the last recorded step left it.
2026-10-23 16:33:42 -04:00
prxnvt 5ca886507c Engine: writing a stash item checks for a clashing visible name in one pass over the names
The check ran once per chunk, each a scan of every defined name: an item of many chunks in a workbook with thousands of names compared every pair. One pass now parses each visible workbook-scope name and refuses when it is a chunk of this key below the chunk count. The behaviour and its test are unchanged.
2026-10-23 16:21:48 -04:00
prxnvt a8e8bf851e Engine: the recording buffer is bounded in bytes and keeps the start of a recording
The buffer kept at most 100,000 steps but each step keeps its full parameters, and a file.open step holds the whole file as base64, so a recording of a few opens of a large workbook kept tens of MB alive. Like the journal, the buffer now also stops at 64 MB of steps serialized as JSON. A step that does not fit is counted as dropped, and once a step is dropped every later one is too, so what the buffer holds is always how the recording began, never a sequence with gaps.
2026-10-23 16:17:57 -04:00
prxnvt 18ec913c4e Engine: a command keeps its group's name only for a step that is exactly the group's
The record of the last group step held the document, the history length and a group epoch, and a command reused the group's label when the lengths matched and the epoch was newer. A direct edit after the group (no nested command, so the length still matches) then left the step named after the group alone.

The record now holds the workbook the group's step led to (a weak reference, so it keeps nothing alive). A command keeps the group's name only when the one entry it added starts from the command's own starting workbook and ends at its result, which is provenance by identity rather than by position: an entry a nested command made, a step of an earlier command undone since, and a group step the command edited past all name the step after the command. A later group that rolled back changes nothing, and the rollback no longer needs to clear the record.
2026-10-23 16:17:57 -04:00
prxnvt 602d4450a2 Merge main into #462 2026-10-23 16:10:43 -04:00
Marco Arnulfo 4b2ee2e2dd Conditional formatting: New Rule remembers its sheet by name, so moving or inserting sheets while it is open keeps the rule there (#2348) 2026-10-23 22:07:22 +02:00
Brandon ThomasandClaude Opus 5.5 7cf44d0fbb Integration and review fixes for #2367, #2369 and #2387
- #2369: its test joins the single ui-egui integration-test binary (tests/it), as main now builds them.
- #2367: a cell set to empty text is a blank entry and doesn't grow the AutoFilter (CodeRabbit).
- #2387: MOD's range check also covers the sign-corrected remainder (CodeRabbit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:05:43 -04:00
Marco Arnulfo 45f06f50da Conditional formatting: New Rule keeps the range it was opened for when pointing on another sheet (#2348) 2026-10-23 22:05:24 +02:00
Brandon Thomas 81fd0f6f7d Merge PR #2387 2026-10-23 16:05:03 -04:00
Brandon Thomas 68da23e2eb Merge PR #2386 2026-10-23 16:05:01 -04:00
Brandon Thomas fba4224ffc Merge PR #2375 2026-10-23 16:04:59 -04:00
Brandon Thomas 2a4cc2a5b6 Merge PR #2369 2026-10-23 16:04:58 -04:00
Brandon Thomas ca0c3afbab Merge PR #2367 2026-10-23 16:04:56 -04:00
Brandon Thomas 87935eb245 Merge PR #2366 2026-10-23 16:04:55 -04:00
Marco Arnulfo d5aa6fbcbc Conditional formatting: New Rule's formula box takes references clicked or dragged on the grid (#2348) 2026-10-23 21:56:39 +02:00
NetterSilverandClaude Haiku 5.5 c746eea876 Fix MOD returning wrong sign and magnitude for tiny divisors
Found while testing crates/functions/src/math.rs:305-319

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 21:43:22 +02:00
aiongg 15a705e234 i18n: missing translations are reported, never a failure (#2383)
A string a catalog lacks shows in English. Coverage, key-parity and
per-language count tests and tests of one language's exact wording are
removed; UI wiring tests take their expected text from the catalog.
cargo xtask i18n-report lists the missing strings per language and
always passes; cargo xtask ci runs it first.
2026-10-23 15:39:59 -04:00
Marco Arnulfo 43a5a3c435 PivotTables: Show Details checks the report is up to date before looking at the cell 2026-10-23 21:00:07 +02:00
Marco Arnulfo 7111812b87 PivotTables: Show Details refuses an out-of-date report, and reports its errors instead of editing the cell 2026-10-23 20:41:47 +02:00