Commit Graph
384 Commits
Author SHA1 Message Date
prxnvt 20d2a3b998 Model: a macro name with control characters is refused on every path
Only parse_macro refused a name holding a line break or other control
character. Macros that came from the macros field of a JSON workbook
were checked with check_limits alone, so a file could bring in a macro
named "a\nb" that macro.save and macro.rename would refuse. The check
now lives in Macro::check_limits, so every path applies it, and such a
macro is dropped with the usual warning when the file is opened.
2026-10-22 02:26:32 -04:00
prxnvt a4b4872602 Model: macros as typed command lists, saved in the workbook
A macro is plain data in the model (model/src/macros.rs): a name, a
description, an optional shortcut, declared inputs (shape, header,
column types, non-empty, default reference), steps, an output rule
(anchor and what to do on a conflict) and the commands it may run that
read or write files. A step runs a command (id and params) or is one of
three bounded control steps: Repeat over the rows or columns of a range,
If on one cell's value, and Stop with a message. Every step carries
enabled, breakpoint and note flags. These are GridCraft macros, not VBA;
a VBA project in a file stays preserved and is never run.

Workbook::macros holds them. The JSON format keeps the field. In xlsx,
io::save_bytes writes each macro as one chunked hidden-name item,
_gc_macro_<n>, and io::open_bytes takes the items back out of the names,
so the names never linger in the open document.

Limits are checked whenever a macro is read from a file or a command:
at most 1,000 macros, 10,000 steps each, Repeat and If nested 4 deep,
names and notes of bounded length, and at most 4,194,304 characters of
JSON, so that saving a workbook never fails because of a macro; nesting
is checked before the steps are deserialized. What is over or damaged
is dropped with one warning, and clashing names get a suffix.

Automate scripts keep their own storage (_sc_scripts) and are neither
read nor changed by this.
2026-10-22 02:26:32 -04:00
prxnvt 471114e296 Engine: a visible name of the item shape is not workbook data
The item store read, listed and removed every workbook-scope name of the `_gc_<key>_<n>` shape,
visible or not, so saving an item removed a person's own visible name that happened to look like
one. Items are the hidden names the store writes; a legacy `_sc_<key>` name still reads. A visible
`_gc_` name is left alone by every operation, and writing an item that would need that name is
refused before anything changes: a hidden name beside it with the same name is the one an xlsx
file drops, so the item would be lost on save.
2026-10-22 02:24:55 -04:00
prxnvt 2287f63a95 Engine: a group rolled back inside a command keeps the redo history
A command that changed the workbook cleared the redo history even when an open group owned
its step. When the group was then rolled back inside the same command (the debugger's Stop
with Undo Changes), the rollback put the redo history back and the command's bookkeeping cleared
it again. Now only a command that makes a step of its own clears the redo history; a group
clears it when it ends as a step, and a rollback restores it.
2026-10-22 02:00:54 -04:00
prxnvt eb3eaa052d Engine: a command keeps its group's name only for a group it ended itself
The record of the last group step (document, history length, epoch) outlived the Undo of that
step. A later command that opened and closed its own groups at the same history length, after
rolling one back, passed the epoch check (the record's epoch merely differed from the one the
command started with) and took the name of the nested edit it had made instead of its own.

The step must now come from a group ended during the command (an epoch past the command's
starting one), and a rollback drops a record of a step it truncated away.
2026-10-22 01:36:01 -04:00
prxnvt 0457f102a0 Engine: pasting only formats from a file leaves the destination's trust alone
Paste Special › Formats copies cell styles and merged areas. It copies no conditional formats,
formulas or validation, so it carries nothing that runs. It still marked the destination
FromFile: copying a formula cell from an opened file and pasting its formats into a new workbook
made that workbook ask before running its own active content.

Formats pastes now count as passive, like values, column widths and notes. The comment and
docs/architecture.md no longer say that pasted formats carry trust.
2026-10-21 17:27:20 -04:00
prxnvt ab27d35386 Engine: a full undo history keeps one step per command that runs its own undo groups
When the history held 100 steps, group_end pushed its step and dropped the oldest one at once,
so execute's collapse compared against a length that no longer matched. A command that was one
group kept the group's step and added its own, losing two old steps. After test.editThenGroup,
the second Undo brought back the intermediate state after the outside edit.

The history limit now applies after the outermost command has collapsed its steps, and when a
group ends outside any command. A document with an open group is trimmed when the group ends.
2026-10-21 17:25:45 -04:00
prxnvt f4e05534a3 Engine: a command that runs its own undo groups makes one undo step for itself
execute treated any change in the group epoch as 'the group owns the step'. A command that edited outside its own group, or whose group rolled back before it edited, left that edit with no undo step or only part of it. Now only a group that was open before the command (or is still open after) owns the step; a command that opened and closed its own groups replaces their steps with one entry for the command.
2026-10-21 15:23:08 -04:00
prxnvt 83ae63f793 Engine: a stash item with only later chunks reads as missing and falls back to the legacy name
With chunk _gc_k_2 but no _gc_k_0, get returned Some("") and never looked at _sc_k. It now takes the legacy path unless chunk 0 exists.
2026-10-21 15:18:15 -04:00
prxnvt e165eaf3a6 Engine: a linked paste of values from a file carries the file's trust
A linked paste writes formulas even when it pastes values, but the values exception skipped the trust update, so a Local workbook stayed Local after linking to a FromFile one. The exception now applies only to unlinked pastes.
2026-10-21 15:18:15 -04:00
prxnvt 7d1fee8c78 Engine: workbook data in chunked hidden names (cmd/stash.rs)
GridCraft keeps workbook data in hidden defined names so it travels
inside the xlsx file. The existing items write one name holding one
string literal, which past Excel's 255-character literal and
8,192-character formula limits gives a file Excel does not accept.

cmd/stash.rs stores an item `key` as hidden workbook-scope names
_gc_<key>_0, _gc_<key>_1, ..., each a formula of string literals of at
most 255 characters (UTF-16 units, as Excel counts them) joined with &,
at most 8,192 characters per name. Reading concatenates the chunks in
order and stops at a gap; it also accepts the older single _sc_<key>
name. Names are matched case-insensitively and only at workbook scope.
Keys are limited to letters, digits, _ and ., items to 4,194,304
characters, and junk around or inside the literals, duplicate or
out-of-order chunks, non-JSON and over-deep JSON all read safely.
keys(prefix) lists the stored items that start with a prefix.

Custom views, scripts, the watch list and scenarios keep their current
storage; nothing reads or writes the new names yet.

docs/architecture.md describes the engine building blocks added in this
series: undo groups, the recording buffer, document trust and this
storage.
2026-10-21 14:05:48 -04:00
prxnvt b7bce3bc09 Engine: document trust for workbooks opened from files
Active content such as macros must not run only because a workbook was
opened. DocState gets a trust state: Local for workbooks made in the
session (new workbooks and samples) and FromFile for anything opened
with file.open. Active content in a FromFile workbook asks before it
runs. A second window on a workbook shares its trust.

The clipboard carries the trust of its source document. Pasting
formulas, formats (conditional formats hold formulas) or validation
from a FromFile workbook into another workbook makes that workbook
FromFile too; pasting values, column widths or notes carries nothing
active and changes nothing. Sheet moves and copies stay inside one
workbook, so its trust is unchanged. document.inspect reports the trust.
2026-10-21 14:05:48 -04:00
prxnvt d4f1e61c99 Engine: a recording buffer of top-level commands, apart from the journal
A macro recorder needs more than a journal index: the journal is
trimmed past 10,000 entries, it also holds the commands that other
commands run (so playback would run them twice), and it keeps no
selection, which relative references need.

Session now tracks how deeply execute calls are nested. While recording,
a buffer (record.rs) keeps each successful, journaled top-level call with
the document uid, the active sheet (index and name) and the selection it
ran in, then its id and parameters without secrets, as the journal
stores them. Queries, failed commands and the command that starts
recording are not kept. The buffer holds up to 100,000 steps and counts
any it drops. Session::start_recording, stop_recording, is_recording and
depth are the interface.

Automate › Record Actions keeps recording through the journal as before.
2026-10-21 14:05:41 -04:00
prxnvt acfd38f309 Engine: undo groups make several commands one undo step
Session::grouped(label, f) runs f as one undo step on the active
document. On success the group leaves exactly one undo entry holding the
workbook from before the group; on error it restores the workbook,
selection, undo and redo stacks as they were and returns the error.
group_begin, group_end and group_rollback do the same across separate
calls, for an action that runs one step per call.

While a group is open on a document, undoable commands on it add no
undo steps of their own, so a long group neither grows the history nor
pushes older entries past the undo limit, and Undo and Redo are
disabled there. Groups nest: the outermost one makes the step, and a
failing inner group rolls back only its own changes. Closing a document
drops its groups, and a command that panics drops the groups it opened.
history.list reports whether a group is open.

Insert Copied Cells folds its nested steps into one undo step by
counting revisions; inside a group the nested steps push no entries, so
it now leaves the history alone there instead of removing entries from
before the group.
2026-10-21 14:05:41 -04:00
Brandon Thomas b402359fa4 Merge pull request #1938 from Mouhand-Kaddo:fix/html-export-rtl
# Conflicts:
#	docs/file-format-parity.md
#	docs/localization-parity.md
2026-10-19 12:32:51 -04:00
Brandon Thomas ab6fd48856 Merge pull request #1867 from HyakoV3:fix/1808-csv-as-text
# Conflicts:
#	docs/file-format-parity.md
2026-10-19 12:22:05 -04:00
Brandon Thomas 12ee27a177 Merge pull request #1943 from Mouhand-Kaddo:fix/table-name-length
# Conflicts:
#	docs/localization-parity.md
2026-10-19 12:22:01 -04:00
Brandon Thomas 8d562e9b56 Merge pull request #1879 from Silvertree2010:fix/h2804-custom-autofilter-is-less-than-l 2026-10-19 12:21:34 -04:00
Brandon Thomas 937cfd1e91 Merge pull request #1903 from justinjohn0306:fix/1855-backspace-undo 2026-10-19 12:21:29 -04:00
Brandon Thomas 8e8e2ba73c Merge pull request #1920 from vedran-dobos:range-fill-formats 2026-10-19 12:19:51 -04:00
Brandon Thomas 851583365f Merge pull request #1934 from Mouhand-Kaddo:fix/arabic-name-length 2026-10-19 12:19:23 -04:00
HaSh3003 5eae222fbc Outline: + shows zero-width hidden columns at the default width (fixes #1714)
Templates hide grouped columns as width="0" hidden="1". Show Detail and the level buttons only cleared hidden, so the columns stayed invisible; they now reset a zero size, as Unhide does.
2026-10-19 18:21:54 +03:00
Mouhand-Kaddo eee872e429 M12.AR16: limit table names to 255 characters 2026-10-19 17:04:06 +04:00
Mouhand-Kaddo a10ead1c09 M12.AR11: mark right-to-left sheets and Arabic text in HTML export 2026-10-19 15:59:46 +04:00
Mouhand-Kaddo fb7b23dbad M12.AR8: count characters, not bytes, in name length limits 2026-10-19 15:33:05 +04:00
Vedran DobosandClaude Opus 5.5 6ac8ad9216 fix(edit): Ctrl+Enter gives each cell the number format typing gives it
range.fill parsed the input once at the active cell, then put back the old
style of every target that already held a cell, so 5%, $5, 3/4/2026 or
=DATE(...) lost their automatic format there (0.05, 5, 46085), and empty
targets took the active cell's style. Each target now gets what
input_to_cell gives for it (parsed once per distinct style), as typing does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:18:34 +02:00
justinjohn0306andClaude Opus 5.5 dcd4d1d13f #1855: redo selects the redone cell
Undo stored the selection at the moment of undoing (wherever the user had
clicked) in the redo entry, so redo jumped back there instead of to the
cells it changed. The redo entry now keeps the undone step's selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:16:13 +05:30
NetterSilverandClaude Haiku 5.5 4f3853d4a9 Hide blank and text rows for numeric ordering custom filters
Found while testing crates/engine/src/cmd/data.rs:550 (custom_ok, used from apply_filter at :623)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 20:25:07 +02:00
Eduardo GarciaandClaude Sonnet 5.5 95f8206bb9 #1808: file.open can keep every CSV/TSV field as text
file.open takes {csvAsText: true}: a CSV, TSV or TXT file opens with every field as the text
it holds, with no number, date or boolean conversion, so identifiers such as GTINs, SKUs and
ZIP codes keep their digits and leading zeros and save back unchanged. The default is
unchanged. The CsvOptions.parse_values switch already existed in the reader.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 12:49:32 -03:00
Dante BertuzziandClaude Opus 5.5 19f4387cca Conditional formatting: show a rule's number format (fixes #1163) (#1769)
The grid's look() and print's cell_look() layered a rule's fill, font and
borders onto the cell style but not its num_fmt, so a cell under a rule with a
number format kept its own. Take the rule's code when it isn't General (in a
differential style General means "not set", as layer_under treats it).

Print also re-formatted every non-General cell from its own style through
display::cell_text; format with the look's code instead, through a new
display::value_text_as that value_text now calls (CSV/Text output unchanged).
Show Formulas keeps cell_text.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:37:09 -06:00
NetterSilverandClaude Haiku 5.5 217a8907ff Reject unknown value-field func in insert.pivotTable (#1757)
Found while testing crates/engine/src/cmd/pivot.rs:678 (func_param at :248)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-17 18:34:28 -06:00
Harrison O'Connor-Hoover 79e85c1b80 fix(edit): recalculate every cell changed by large clears (#1032) (#1128) 2026-10-15 13:38:13 -06:00
Brandon Thomas ebe0138d30 Merge main into #855
# Conflicts:
#	ROADMAP.md
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-13 01:52:08 -04:00
Brandon Thomas be003d44af Merge main into #863
# Conflicts:
#	crates/xlsx/src/tests/passthrough_tests.rs
2026-10-13 01:40:22 -04:00
Brandon Thomas be45ccab31 Merge main into #811
# Conflicts:
#	ROADMAP.md
#	crates/numfmt/src/parse.rs
#	crates/numfmt/src/tests.rs
2026-10-13 01:05:45 -04:00
Brandon Thomas 2d0a3f8a47 Merge main into #780
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/insert.rs
#	crates/engine/src/tests.rs
2026-10-13 01:02:28 -04:00
Brandon Thomas 20d6923cb6 Merge main into #766
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/sheet.rs
2026-10-13 01:00:59 -04:00
Brandon Thomas c6ab899259 Merge #824 fix-up 2026-10-13 00:57:02 -04:00
Brandon ThomasandClaude Opus 5.5 20309f6e99 Fix-up: close the Save As link test the merge left open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:57:02 -04:00
Brandon Thomas 9c773166f9 Merge #594 fix-up 2026-10-13 00:55:57 -04:00
Brandon ThomasandClaude Opus 5.5 623997a19e Fix-up: close the CSV history-name test the merge left open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:55:57 -04:00
Brandon Thomas 9972a75f10 Merge main into #824
# Conflicts:
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/tests.rs
#	crates/xlsx/src/tests/passthrough_tests.rs
2026-10-13 00:53:41 -04:00
Brandon Thomas 0068c8f517 Merge main into #649
# Conflicts:
#	crates/ui-egui/src/grid.rs
2026-10-13 00:48:39 -04:00
Brandon Thomas b464a7f242 Merge main into #594
# Conflicts:
#	crates/engine/src/io.rs
2026-10-13 00:48:36 -04:00
Brandon Thomas 2f33d43546 Merge main into #591
# Conflicts:
#	crates/engine/src/cmd/data.rs
2026-10-13 00:41:59 -04:00
Brandon ThomasandClaude Opus 5.5 6405b00cf7 Fix-up: repair test files after merging the conflicting PRs
- restore closing braces the line-level merges of side-by-side tests dropped
- new ChartData literals carry the palette field
- the filter-menu shortcut passes the row with the column
- #688's tab tests account for #851's All Sheets button
- Serbian margins for the ruler-margin labels

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:34:01 -04:00
Marco Arnulfo c7cb91ea53 Merge remote-tracking branch 'origin/main' into fix/xlsx-xml-maps 2026-10-13 06:33:03 +02:00
Brandon Thomas 94ee7efdf9 Merge main into #853
# Conflicts:
#	ROADMAP.md
#	docs/file-format-parity.md
2026-10-13 00:08:58 -04:00
Brandon Thomas 6dd8f7abe8 Merge main into #796
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/tests.rs
2026-10-13 00:08:27 -04:00
Brandon Thomas e5f31a02f3 Merge main into #786
# Conflicts:
#	ROADMAP.md
#	docs/gaps.md
2026-10-13 00:08:15 -04:00