Commit Graph
743 Commits
Author SHA1 Message Date
prxnvt b40fa4e065 Merge #2145 (2fb31f11, with main at 17b9f6ff) into #2152
Main refuses window.* steps in scripts, so the recorder test's script step that a macro may not
run is app.commands.
2026-10-25 02:29:03 -04:00
prxnvt 2fb31f11f2 Merge #2144 (8ea4e279, with main at 17b9f6ff) into #2145 2026-10-25 02:26:03 -04:00
prxnvt 8ea4e27918 Merge #2151 (63a29800, with main at 17b9f6ff) into #2144
Main adds UiRequest::InsertName (Paste Name, Use in Formula), which puts a name in the cell
editor; a macro step drops it, as it drops a request to start editing a cell.
2026-10-25 02:26:02 -04:00
prxnvt 63a29800a3 Merge main at 17b9f6ff into #2151 2026-10-25 02:19:57 -04:00
prxnvt b995c7c9a0 Engine: a recorded script run keeps the steps that ran
The recorder expanded an automate.runScript step from the workbook as it
stood when the recording stopped, so a script edited or deleted during the
recording changed the saved macro, or dropped the step. The run now leaves
the steps it ran with the recorder (Recorder::ran, by step index, within
the byte budget), and the conversion reads those, falling back to the
workbook only when they could not be kept.
2026-10-25 02:18:58 -04:00
prxnvt 1e34a76428 Merge #2145 (c225caf3, with #2144, #2151, #465 at 3f8e6c28 and main at 6ead1fb5, where #462 is merged) into #2152
# Conflicts:
#	crates/engine/src/cmd/extra.rs
#	docs/architecture.md
2026-10-25 02:18:55 -04:00
prxnvt c225caf30a Merge #2144 (9331b05f, with #2151, #465 at 3f8e6c28 and main at 6ead1fb5, where #462 is merged) into #2145 2026-10-25 02:18:21 -04:00
prxnvt 9331b05f62 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
2026-10-25 02:17:32 -04:00
prxnvt 9fdf03cfb5 Merge #465 (3f8e6c28, with main at 6ead1fb5, where #462 is merged) into #2151 2026-10-25 02:16:47 -04:00
aiongg 17b9f6ff01 Paste Name and Go To: follow-ups from the batch review (#2540)
* Go To: names match in any case and script, as formulas match them

* Paste Name: only the names in view are laid out

* Docs: LAMBDA recursion limit and called error names; one Page Layout view row with the side margin marked unverified

* Go To and Paste Name: cargo fmt
2026-10-25 01:24:24 -04:00
prxnvt 3f8e6c2884 Engine: a JSON save refuses a macro that skipped the check, as an xlsx save does
Every path that adds a macro runs macros::check, and an xlsx save of a
macro that skipped it fails (the stash refuses the oversized item). A
JSON save wrote such a macro instead, and the next open dropped it with
a warning. json_bytes now checks each macro first, so the save fails in
either format with the check's message and the macro stays in the open
workbook. Tests that build hostile JSON files write them directly.
2026-10-25 00:27:42 -04:00
aiongg 24e49d18ad Batch: Subtotal puts summary rows below their detail in the same undo step (#2482) 2026-10-25 04:27:21 +00:00
aiongg f17179ceeb Batch: Show Details turned off says so in plain words; the option is in the command's docs (#2461) 2026-10-25 04:22:02 +00:00
prxnvt e88760b480 Engine: an item too large to read takes no place in a stash listing
stash::items capped the items at MAX_ITEMS before reading them, so one
over the size limit took a place (a readable item past the cap was then
left out with fewer than MAX_ITEMS listed) and one past the cap was
counted in `more` though it could never be listed. Items are now read
first: the first MAX_ITEMS readable ones are listed and only readable
ones past them are counted.
2026-10-25 00:10:14 -04:00
aiongg 87a649c763 Batch: Paste Name lists the names once, finds local names by hash and refuses a name it doesn't list (#2410) 2026-10-25 03:56:45 +00:00
aiongg 50c7ee3971 Batch: undo, redo and rollbacks list the circular references of the restored cells (#2501) 2026-10-25 03:50:42 +00:00
prxnvt 4c11929fe2 Tests: a visible legacy name of the macro storage shape survives a save
The stash reads a legacy _sc_<key> name as data whether or not it is
hidden, so writing key macro_0 removed a person's visible _sc_macro_0
name with no word. The index allocator steps around that key as well;
this test fails without it.
2026-10-24 23:45:04 -04:00
prxnvt da439b71ed Tests: a visible clash, a newer macro and a broken input together, through both formats
One file with a visible name of the storage shape, a macro in a newer
format and one whose input declaration breaks the rules, saved and
reopened xlsx then JSON and JSON then xlsx: the visible name stays, the
newer macro comes back whole and numbered around the name, the broken
one is left out once, and the readable one survives.
2026-10-24 23:45:04 -04:00
prxnvt 3905a4318e Engine: a macro in a newer format is kept unread and saved back unchanged
A macro whose format version is above this build's was dropped on open,
so the next save removed it from the file, and the warning said only
that it couldn't be opened. It is now kept as the file held it
(Workbook::macros_unread, shared between snapshots), never read, listed
or run, and written back unchanged by JSON and xlsx saves, numbered
after the readable macros and around visible names of the storage
shape. Kept macros count toward the 1,000-macro limit after the
readable ones; one too large for an xlsx item is left out. The warning
says it is kept and saved back; damaged and over-limit macros keep
their own warning.
2026-10-24 23:45:04 -04:00
prxnvt 471a40bd72 Model: every field a macro file sets has a limit, and an input declaration is checked as a whole
check_limits capped the name, description, notes, step count, depth and
input names, and left the shortcut, permissions, output anchor, default
range, column types, command ids, Repeat ranges, If cells and values and
Stop messages bounded only by the item size; the input declaration was
stored unchecked (rows [5, 2], a cell input with cols [2, 5]).

Now each of them has a limit (docs/macros.md lists them), one-line
fields refuse control characters, and a command id is letters, digits,
. and _ (every registered id passes, tested). An If value may be null
(Empty and NotEmpty ignore it); Repeat.max stays the runner's to bound.
An input's least rows and columns can't pass its most, a cell input is
one row and one column with no header and at most one column type, and
no input lists more column types than columns. check_limits takes every
struct apart field by field, so a new field doesn't compile there until
its limit is decided. A macro from a file that breaks a rule is left
out with the usual warning, never changed to fit.
2026-10-24 23:39:14 -04:00
prxnvt ac36eee23d Engine: a JSON workbook with no sheets keeps its macros
The JSON reader replaces a workbook with an empty sheet list by a new
one before macros::load reads the file's macro entries, so they were
lost with no warning. The entries are carried across the reset. The
reset itself, which also starts the names and styles afresh, is
unchanged.
2026-10-24 23:35:32 -04:00
prxnvt 3c722f4d86 Engine: a visible name of the macro storage shape no longer blocks an xlsx save
with_names numbered macros 0..N, and stash::set_many refuses to write a
hidden name beside a visible one of the same name, so a visible
_gc_macro_0_0 (from a file, or defined after the file opened, when the
stored names are no longer in the workbook) made every xlsx save of a
workbook with macros fail. The index is GridCraft's choice: macros now
take the free indices from 0 up, skipping every key a visible
workbook-scope name of the storage shape uses (compared as the stash
reads names), in their order. No arithmetic depends on a number from
the file.
2026-10-24 23:35:03 -04:00
aiongg 6194f8241d Merge storytold/gridcraft#2522: Charts: 100% Stacked Area draws each value as a share of its category's total
# Conflicts:
#	ROADMAP.md
2026-10-25 02:54:23 +00:00
aiongg 1e50c72698 Merge storytold/gridcraft#2488: Headers and footers: printing follows the Scale with Document and Align with Page Margins check boxes
# Conflicts:
#	docs/ui-parity.md
2026-10-25 02:54:23 +00:00
aiongg b58846a549 Merge storytold/gridcraft#2482: Outline: Settings puts summary rows above and summary columns left of their detail
# Conflicts:
#	docs/ui-parity.md
2026-10-25 02:54:23 +00:00
aiongg cb1fb89549 Merge storytold/gridcraft#2467: PivotTables: right-clicking a value cell offers Show Details in the cell menu
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/pivot.rs
#	docs/ui-parity.md
2026-10-25 02:46:38 +00:00
aiongg 75f7df9da6 Merge storytold/gridcraft#2461: PivotTables: Enable show details turns Show Details off for a PivotTable
# Conflicts:
#	ROADMAP.md
2026-10-25 02:46:16 +00:00
aiongg 8356b33c78 Merge storytold/gridcraft#2456: Fill handle: Fill Days, Fill Weekdays, Fill Months and Fill Years for a date
# Conflicts:
#	ROADMAP.md
#	docs/ui-parity.md
2026-10-25 02:46:16 +00:00
aiongg 924d36e247 Merge storytold/gridcraft#2421: Status bar: a right-click opens a statistics menu, with Numerical Count, Minimum and Maximum added
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/tabs.rs
#	docs/ui-parity.md
2026-10-25 02:25:39 +00:00
aiongg ae88b40f58 Merge storytold/gridcraft#2414: Go To: lists the named ranges of every sheet and the references entered, and goes to several at once
# Conflicts:
#	docs/ui-parity.md
2026-10-25 02:25:15 +00:00
aiongg 0217dbdfd8 Merge storytold/gridcraft#2410: Formulas: F3 opens Paste Name, which inserts a defined name or pastes the list of names 2026-10-25 02:24:49 +00:00
aiongg 58aa0f1df2 Merge storytold/gridcraft#2504: Circular references: Error Checking and the status bar quote sheet names that need quotes 2026-10-25 02:24:49 +00:00
dotCooCoo 0eb2f1f6f0 Charts: 100% Stacked Area draws each value as a share of its category's total
Insert > Line or Area and the All Charts list offer 100% Stacked Area,
and insert.chart and chart.set take it as type area with subtype
stacked100. Each series' area stacks on the ones before it as its
share of the category's total. When every series on the value axis is
100% stacked and no value is negative, the axis runs from 0% to 100%.
A blank cell counts as zero, and a category whose total is zero stays
on the baseline.

An XLSX area chart with percentStacked grouping now opens as 100%
stacked and is saved back as percentStacked. It used to open as a
stacked area and was saved as stacked.
2026-10-24 16:42:59 -07:00
dotCooCoo 1dd07b7f75 Circular references: Error Checking and the status bar quote sheet names that need quotes
formulas.errorChecking listed each circular reference as Sheet!A1 with
the sheet name as it is, so a sheet named Bob's Q1 gave Bob's Q1!B2,
which a formula does not accept as a reference. The list now quotes the
name with quote_sheet, as references are written elsewhere in the
engine ('Bob''s Q1'!B2), and the status bar's Circular References
label uses the same form.
2026-10-24 14:51:06 -07:00
dotCooCoo b745c136b2 Headers and footers: printing follows the Scale with Document and Align with Page Margins check boxes
The Header & Footer tab now has the Scale with Document and Align with
Page Margins check boxes, and pageLayout.headerFooter takes
scaleWithDoc and alignWithMargins. XLSX already kept both settings,
but nothing else read them.

With Scale with Document on, header and footer text prints at the
sheet's print scale. With it off, the text keeps its point size. With
Align with Page Margins on, the left and right sections line up with
the page's side margins. With it off, they start 0.7 inch from the
paper's edges, or a quarter of the page width on paper narrower than
2.8 inches. The Page Layout view draws headers and footers and takes
clicks on them the same way. Both settings are on by default, so a
sheet printed at a scale other than 100% now prints its header and
footer text at that scale.
2026-10-24 13:01:00 -07:00
dotCooCoo 118cd15d93 Outline: Settings puts summary rows above and summary columns left of their detail
Data > Outline > Settings now has Summary rows below detail and Summary
columns to right of detail. With one cleared, each group's - / + button
sits on the line before the group, its bracket runs down to the last
detail line, and Hide Detail, Show Detail, the level buttons and
Ungroup mark or clear the collapsed flag on that line. Changing a
direction moves the flags of collapsed groups to their new summary
lines.

- model: Sheet::summary_below and summary_right (on by default, also
  for older JSON).
- xlsx: read and written as sheetPr/outlinePr summaryBelow and
  summaryRight; files that set them no longer lose them on save.
- engine: data.outlineSettings {sheet?, summaryBelow?, summaryRight?},
  undoable; outline::groups_with places the summary line.
- ui: a Settings button in the Data tab's Outline group opens the
  dialog; the outline band follows the setting.
2026-10-24 11:34:35 -07:00
dotCooCoo 010eddd25e PivotTables: the cell menu computes a report once for all its cells
The cell menu checked whether to offer Show Details by computing the
whole report again for each new active cell. pivot::detail_cells now
returns every value, subtotal and grand total cell of a report from one
computation, and the menu caches that set per document, revision, sheet
and PivotTable.
2026-10-24 11:27:58 -07:00
dotCooCoo 7c10db07d9 PivotTables: Show Details reports the turned-off option before an out-of-date report
With Enable show details off, Show Details on a value cell of a report whose
source has changed gave the out-of-date error. It now gives the turned-off
message, and a label still reports that it is not a value cell. The message
names pivot.options with enableShowDetails set to true, since there is no
PivotTable Options dialog.
2026-10-24 10:31:09 -07:00
dotCooCoo 30126a709a PivotTables: Enable show details turns Show Details off for a PivotTable
A PivotTable has an Enable show details option, set with the new
pivot.options command ({pivot?, enableShowDetails?: bool}). While it is
off, pivot.showDetails and double-clicking a value cell list no records:
the double-click shows a message and does not open the cell for editing.
Labels and headers still edit. Changing the option is one undo step and
does not refresh the report. pivot.fields reports the setting.

XLSX files keep it: an off option is written as enableDrill="0" on the
pivotTableDefinition, and enableDrill is read on open (on when absent).
2026-10-24 07:46:29 -07:00
dotCooCoo 00edfd0690 Fill handle: Fill Days, Fill Weekdays, Fill Months and Fill Years for a date
When a fill starts from a date, the right-drag fill menu and the Auto
Fill Options button now also offer Fill Days, Fill Weekdays, Fill Months
and Fill Years. edit.autoFill takes them as the modes days, weekdays,
months and years.

A lone date steps by one unit. Two or more dates step by the difference
between the first two in that unit, or by one unit when both fall in
the same one. Fill Weekdays skips Saturday and Sunday, and a start on a
weekend moves to the next weekday first. Fill Months and Fill Years keep
the day of the month, clamped to the month's last day. A source that is
not all dates fills as a plain drag does.

Fills by months or years now keep the time of day of a date with a
time. A plain drag of dates a month apart and Series by Month or Year
used to drop it.
2026-10-24 06:56:26 -07:00
dotCooCoo d0fe8b6423 Tests: the engine's file-system tests each use their own temporary folder
save_fails_upfront_when_the_temp_name_is_taken,
save_keeps_the_destination_access_restrictions and
open_refuses_directories_and_unknowable_lengths in crates/engine/src/io.rs
all worked in temp_dir()/gridcraft-fs-safety and removed it at the start
and the end. cargo test runs them on parallel threads, so one test could
delete the folder while another was writing to it, and the other test
failed. Each test now uses its own folder, named with a short label and
the process id, as the gridcraft-write and gridcraft-sandbox tests in the
same file do.
2026-10-24 04:54:53 -07:00
Dylan M. Taylor a005d8235f M2.9: close protection gaps a trusted script or other sheet would slip through (sec scan findings 17,20,24) (#2420)
- scripts: refuse `window.*` steps like file and link steps — activating
  another window repointed every later step at a different open document
  (finding 17).
- format: on a protected sheet, formatting may not change a cell's
  protection flags: the format-cells permission can't drop `locked` and
  then authorize content edits the password gates (finding 20).
- sheet.moveColumns: the insert/delete protection checks now carry the
  command's own `sheet` parameter, so the sheet modified is the sheet
  authorized, not whichever is active (finding 24).

Tests: unlock-via-format refused, cross-sheet move refused, window switch
refused, plus the positive controls. Engine: 462 tests pass; clippy clean.
2026-10-24 06:15:24 -04:00
Dylan M. Taylor 48d9ba264c M2.9: harden native file open/save (sec scan findings 15,22,25) (#2418)
- read_file: verify the opened file is regular and cap the bytes actually
  taken at the 2 GB limit — a workbook-named symlink to an endless device
  reported length 0 but streamed forever (finding 22).
- write_file: create the temporary file exclusively, so a planted file or
  symlink at the predictable `book.xlsx.sctmp` path makes the save fail
  instead of truncating the link's target (finding 25).
- write_file: inherit the destination's permissions, so saving an
  owner-only workbook doesn't become world-readable through the rename
  (finding 15); new files note restrictive defaults.

Tests: exclusive temp creation vs planted symlink, permissions preserved,
directory refused on open. Engine: 460 tests pass; clippy clean.
2026-10-24 05:46:35 -04:00
Dylan M. Taylor 89d8e81941 M2.9: bound print/export/autofit work in the engine (sec scan findings 18,21,27,28,30) (#2416)
- clip_html: cap wildcard/glob lookback to a 12-byte window instead of scanning
  outgoing text arbitrarily far for a semicolon (finding 18).
- print: wrap_lines binary-searches its break instead of rescanning the line
  from the start for every band (finding 21).
- autofit: per-row merge index instead of scanning every merge per row during
  autofit (finding 27) — behaviour covered by the existing autofit merge test.
- Save as Web Page: the used rectangle bounds the HTML (finding 28, #2387) —
  a tall/wide or sparse far-right sheet now errors instead of growing
  gigabytes of <td></td>.
- print: decode each embedded picture once per document instead of once per
  band that shows it (finding 30).

Engine: 459 tests pass; clippy clean.
2026-10-24 05:46:32 -04:00
dotCooCoo 7bc390d0fb Status bar: a right-click opens a statistics menu, with Numerical Count, Minimum and Maximum added
The status bar always showed Average, Count and Sum for a selection of
several cells and had no menu. A right-click on it now opens a menu with
Average, Count, Numerical Count, Minimum, Maximum and Sum, as in Excel.
Average, Count and Sum are on by default; the other three are off.

The text on the status bar can no longer be selected, so a right-click
on the mode text or a statistic opens the menu as well. The zoom and
view controls handle their own clicks as before.

The choice is a preference set with the new view.statusBar command and
kept in prefs.json. A prefs.json without it keeps the defaults.
2026-10-24 02:39:50 -07:00
dotCooCoo 983d3d7e6a Go To: lists the named ranges of every sheet and the references entered, and goes to several at once
The Go To dialog lists the named ranges of the workbook and of every
sheet, a name scoped to another sheet as Sheet2!Name, without hidden
names, constants or formulas. After them it lists the references
entered in the dialog, newest first, and a double-click on an entry
goes there.

edit.goTo takes several names or references separated by commas
(Price, Type or B14:C22,F19:G30) and selects them all, follows a name
that refers to several areas, and goes to Sheet2!Name on Sheet2. With
record: true it keeps a reference that holds no name among the recent
ones, and with list: true it returns the names and the recent
references.
2026-10-24 01:22:19 -07:00
dotCooCoo 3557b9f6cd Formulas: F3 opens Paste Name, which inserts a defined name or pastes the list of names
formulas.pasteName lists the names the active sheet can use (workbook
names and the sheet's own, without hidden names, in alphabetical order).
With name it puts the name at the caret of the formula being edited,
replacing selected text, or starts =Name; with list: true it writes each
name and the text of what it refers to in two columns from the active
cell. The Paste Name dialog opens from F3 when the workbook has names,
from Use in Formula's new Paste Names… item and from Alt+M, S, P. While
a dialog is open over the edit, the editor takes no keys.

Use in Formula lists the same names, and formulas.useInFormula inserts
at the caret instead of replacing the text being edited.
2026-10-23 23:47:38 -07:00
prxnvt 54bd4eca9d Engine: a run that edited the workbook before opening a dialog is recorded
The recorder left out every call that opened a dialog, so a script with an
edit before a menu-style step lost that edit from the recording. A call is
left out only when it opened a dialog and changed nothing (the dialog's
confirming call is the step); one that changed the workbook is kept.
2026-10-24 02:42:45 -04:00
prxnvt 6cd4cebfb7 Engine: Stop Action runs on its own and is not recorded
Run from inside another command, automate.stopAction rolled back that
command's own group, pulling the workbook from under the rest of its run; and
the recording buffer kept it, so a recording could replay the stop inside the
run it was part of. Nested, the command is refused and the group stays; the
recorder leaves it out (record::NOT_RECORDED).
2026-10-24 02:18:07 -04:00
prxnvt f040b4ecaa Engine: stash listings say how many items were past the cap
stash::items and stash::keys listed the first 10,000 items and let the rest
vanish without a word. They return a Listing { found, more } now, so a caller
can say what it did not list.
2026-10-24 02:18:07 -04:00