Commit Graph
2901 Commits
Author SHA1 Message Date
FredApps fca22f6e93 Merge pull request #1155 from vangel76/fix/chart-work-budgets
Charts: work and memory are bounded by series x points; ranges are clipped before evaluation (#1147)
2026-11-08 12:08:35 +01:00
FredApps 300b074894 Merge pull request #3325 from vangel76/fix/binom-poisson-accuracy
BINOM.DIST, POISSON.DIST: Loader mass and recurrence sums, error 1e-11 -> 1e-14 (#3324)
2026-11-08 12:08:16 +01:00
FredApps 9dd3b51bc6 Merge pull request #3383 from vangel76/fix/3350-ctrl-tab-arrows
Tabs: Ctrl-click on the sheet arrows jumps to the first or last sheet
2026-11-08 12:07:28 +01:00
FredApps 4a93722ea6 Merge pull request #2691 from vangel76/perf/calc-range-memo-2681
perf(calc): SUM/COUNT/AVERAGE over big ranges reuse constant-only results
2026-11-08 12:06:13 +01:00
FredApps 6338cef450 Merge pull request #3578 from vangel76/fix/3577-json-formula-trees
Cache formula trees when a workbook is read from JSON
2026-11-08 12:05:30 +01:00
FredApps 24119e49fe Merge pull request #3581 from vangel76/perf/3575-store-shifts
Move whole rows when inserting or deleting rows and columns
2026-11-08 12:05:00 +01:00
FredApps cbd0879c44 Merge pull request #3591 from vangel76/perf/engine-adjust-if-changed
Structural edits rewrite only the formulas they change
2026-11-08 12:03:48 +01:00
FredApps 0057fc00c9 Merge pull request #3579 from vangel76/perf/adjust-unchanged-check
Formulas: adjust_if_changed reports when a structural edit leaves a formula alone
2026-11-08 12:02:12 +01:00
vangel76andClaude Opus 5.5 9ab297ced1 perf(engine): rewrite only the formulas a structural edit changes
rewrite_all_formulas borrows each tree with Formula::with_expr and calls
adjust_if_changed, so a formula the edit leaves alone is neither copied
nor printed, and a changed one is printed once. A legacy array whose
range moves while its formula stays the same still gets the new range.

Sheet rename and delete compare the adjusted tree with the old one
instead of printed text, and replace only formulas that changed. Delete
no longer rebuilds every formula in the workbook, so unrelated formulas
keep their array range and dynamic-array mark.

A stored non-canonical formula text is no longer re-printed by an edit
that does not change the formula; a test pins this.

Part of #3576

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 04:06:21 +01:00
vangel76andClaude Opus 5.5 2ee261c0e8 perf(model): move whole rows when inserting or deleting rows and columns (#3575)
CellStore::shift_rows, shift_cols, shift_cols_in_rows and take_range removed
and re-set every affected cell one by one. They now take whole row maps (or
the split-off part of one) out of the bands and put them back at their new
position, so a row shift costs a few map operations per row instead of a
band lookup, a remove and a set per cell. take_range does this when the
block reaches the last column.

Behaviour is unchanged, including blank cells left behind by get_mut, cells off the grid (they
are still dropped, and still clear the cell they land on), cells shifted off
the sheet, and copy-on-write: bands without moved cells stay shared with undo
snapshots. A differential test runs 12,000 random operations against the old
per-cell code and compares contents, length, iteration order and band sharing.

shift_rows_in_cols, and take_range on a block with columns to its right, keep
moving cell by cell: the row-map version measured no faster there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 03:51:57 +01:00
vangel76andClaude Opus 5.5 7cb0088361 perf(formula): adjust_if_changed skips formulas a structural edit leaves alone
adjust had no way to report that an edit does not affect a formula, so
callers copied, rebuilt and printed every formula in the workbook.
adjust_if_changed borrows the formula and returns None when nothing
moves. Its check reuses adjust_ref on each reference into the edited
sheet, on a copy without the sheet name, so it cannot disagree with
adjust. adjust_ref and shift_ref take the reference by value and no
longer clone sheet names.

An integration test keeps the previous implementation as an oracle
over every edit kind and a randomized property test.

Fixes #3576

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 03:04:24 +01:00
vangel76andClaude Opus 5.5 4bba98fd50 perf(model): cache formula trees when reading a workbook from JSON
A Formula read from JSON had no cached tree (`expr` is not saved), so
every graph rebuild and every recalculation parsed each formula again.
Reading one now parses its text once and keeps the tree, as
Formula::new does. The saved text is kept as written.

Fixes #3577

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 02:57:51 +01:00
640fbf22f6 fix(edit): paste, Ctrl+Enter, Fill and Copy skip rows a filter hides (#1101) (#1175)
* Paste, Ctrl+Enter and Fill skip rows hidden by a filter (#1101)

Repeating one cell or row down a filtered selection (paste of a single
cell or row, Ctrl+Enter, Fill Down/Up/Right/Left) now leaves the rows an
applied filter hides alone, as Excel does. A taller pasted block still
goes straight down, and rows hidden by hand are still written, also as
in Excel. Each case was measured in Excel for Microsoft 365.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2bJ3LxrZ6YnmppgrfwHwr

* Copying a filtered range takes only the rows showing (#1101)

A copied range with rows hidden by an applied filter is split into its
runs of showing rows and packed like a multi-area copy (formulas as
values), so pasting it inside GridCraft no longer brings the hidden rows
back. One run is copied as a plain range, keeping formulas. Cut still
moves every row. All as measured in Excel for Microsoft 365.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2bJ3LxrZ6YnmppgrfwHwr

* Find the rows a filter hides without rescanning every table per row

filtered_rows asks the sheet for its filter row spans once instead of
checking every table for each hidden row.

* Paste over filtered rows leaves their validation rules alone

When one row is tiled over filtered rows, paste_validations no longer
clears or copies rules on the rows the filter hides.

* Filtered copy packs links and validation with the rows showing

slide_together now moves hyperlinks and validation rules with their rows, and a
filtered whole-row copy records the packed rows as its selection so Insert
Copied Cells inserts only the rows copied.

* Group validation paste options and format the new tests

* Pack a filtered copy in one pass over notes, merges, links and spills

slide_together finds each entry's area by search instead of scanning every map
once per area, so a filter leaving thousands of separate runs no longer costs
runs times map size on every copy.

---------

Co-authored-by: Richard <richard1912@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: skylar <hi@skylar.cc>
2026-11-07 19:30:06 -06:00
vangel76andClaude Sonnet 5.5 05a5845234 Merge upstream/main into perf/calc-range-memo-2681
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 18:38:18 +01:00
vangel76andClaude Sonnet 5.5 8e5bcf1575 Merge upstream/main into fix/binom-poisson-accuracy
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 18:37:33 +01:00
vangel76andClaude Sonnet 5.5 93dab0c420 Merge upstream/main into fix/3350-ctrl-tab-arrows
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 18:36:49 +01:00
FredApps dea064b857 Merge pull request #3547 from FredApps/fix/chart-table-series-index-1150
fix(chart): pass series index to data-table keys
2026-11-07 17:47:58 +01:00
vangel76 9ba79c3e38 fmt 2026-11-07 17:31:41 +01:00
vangel76andClaude Sonnet 5.5 6a7cf9cf66 Chart: bound the category axis by the point cap, not the raw cell count
Read only the cells the category levels use and cut the category dimension
at the per-series cap, so a multi-level axis keeps all its levels.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:31:27 +01:00
vangel76andClaude Sonnet 5.5 c35878948a Merge upstream/main into fix/chart-work-budgets
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:31:27 +01:00
vangel76andClaude Sonnet 5.5 42370639e0 Merge upstream/main into perf/calc-range-memo-2681
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:29:04 +01:00
FredApps 6b19e445f4 fix(chart): pass series index to data-table keys (#1150) 2026-11-07 17:27:25 +01:00
vangel76andClaude Sonnet 5.5 05cffdceb1 Tab strip: test the Previous arrow reveal for the already active first tab
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:27:07 +01:00
vangel76andClaude Sonnet 5.5 87cb0f11d2 Tab strip: reveal the edge tab after a repeated Ctrl-click on an arrow
When the edge sheet was already active nothing changed, so the strip was not
scrolled back to it. A Ctrl-click on the arrow now asks for the reveal itself.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:22:54 +01:00
vangel76andClaude Sonnet 5.5 12f69f99bf BINOM.DIST: keep small-k masses whose complement power underflows
The small-k product also needs (1 - p)^(n - k) to stay a normal number;
otherwise it returned 0 for BINOM.DIST(15,1100,0.5,FALSE) (exact
2.1367646606291324e-298) and the cumulative form built on it. Those cases
now use the logarithmic formula. Reference grid gets k = 10..25 at n = 1100.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:20:53 +01:00
vangel76andClaude Sonnet 5.5 76002fe0bd Reuse remembered range sums for every COUNT block
COUNT never reads the running sum, so the zero-sum gate that keeps SUM
and AVERAGE additions in order does not apply to it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:58:19 +01:00
FredApps 505cae34c8 Merge pull request #1150 from vangel76/fix/chart-combo-logic-cleanup
Charts: one combo plot-kind function, no dead scale helpers (#1146)
2026-11-07 16:52:46 +01:00
FredApps ee1b5df3db Merge pull request #2693 from vangel76/perf/calc-expr-arc-2685
calc: rebuild the dependency graph from shared syntax trees
2026-11-07 16:51:36 +01:00
FredApps cbd765332c Merge pull request #2686 from vangel76/perf/stat-select-2679
perf: select instead of sort in MEDIAN, PERCENTILE, QUARTILE, LARGE, SMALL
2026-11-07 16:51:02 +01:00
FredApps b9e5e0ff89 Merge pull request #3309 from vangel76/fix-autosum-gap-3291
fix(autosum): stop the guessed range at a blank cell
2026-11-07 16:50:28 +01:00
FredApps 66bb6ae239 Merge pull request #3331 from vangel76/contributors-vangel76-names
contributors: set names for vangel76
2026-11-07 16:49:24 +01:00
vangel76andClaude Sonnet 5.5 bc1625248e Chart: cap category levels in the clean-data check, recompute overflowing moving-average windows, keep disjoint stacked hairline spans apart
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:25:23 +01:00
FredApps 4fc2c9c3b3 Merge pull request #3524 from helenkwok/web/page-api
gridcraft-web: window.gridcraft, the page command API from craft-web-api
2026-11-07 15:38:37 +01:00
FredApps 546a546943 Merge pull request #3508 from ZIDAN44/fix/3484-ctrl-shift-arrow-active-line
Selection: Ctrl+Shift+Arrow searches the active cell's line (#3484)
2026-11-07 15:38:30 +01:00
FredApps 8774fa45fb Merge pull request #3507 from ZIDAN44/fix/3485-scroll-per-window
View: a sheet keeps its own scroll position when sheets move (#3485)
2026-11-07 15:38:25 +01:00
FredApps 8325dbe50e Merge pull request #3506 from ZIDAN44/fix/3446-sort-dialog-column-label
Sort: the closed Sort by box names the column, as its list does (#3446)
2026-11-07 15:38:20 +01:00
Helen Kwok fb17d0d1d5 gridcraft-web: say a call the app already took still finishes after a timeout 2026-11-07 10:28:03 +08:00
Helen Kwok 89ec06f041 gridcraft-web: window.gridcraft, the page command API from craft-web-api
The web shell queues the page's calls as control requests (the channel the desktop control server uses) and answers them on the next frame. A call the page gave up on is released, so the app skips it instead of running it late. The API is installed in every mode; opening, saving, the file name and the unsaved flag stay with the host messages.
2026-11-07 08:03:07 +08:00
Zinadin ZidanandOlivier 20473a4fae Selection: Ctrl+Shift+Arrow searches the active cell's line (#3484)
Ctrl+Shift+Arrow searched from the selection's moving corner, so after
Ctrl+Shift+Right from A2 the next Ctrl+Shift+Down searched the far column
and ran to the last row. As in Excel, it now searches the active cell's
column (Up/Down) or row (Left/Right) from the edge being moved, and moves
only that edge. A diagonal move from command params keeps the old jump.

Co-Authored-By: Olivier <5681030+tutis@users.noreply.github.com>
2026-11-07 00:27:08 +06:00
Zinadin ZidanandOlivier 1688b43ae2 View: a sheet keeps its own scroll position when sheets move (#3485)
Scroll positions were stored by (window, sheet index), and nothing re-keyed
them when sheets were inserted, deleted or moved. A new PivotTable sheet,
which goes in front of its source, opened at the source's position, and the
source took over whatever was stored at its new index. Positions are now
keyed by (window, sheet uid), so they follow the sheet through inserts,
deletes, moves and undo. A closed window's positions are dropped. Saving the
active cell still happens when no window is active.

Co-Authored-By: Olivier <5681030+tutis@users.noreply.github.com>
2026-11-07 00:27:07 +06:00
Zinadin Zidan 7d3ede854f Sort: the closed Sort by box names the column, as its list does (#3446)
The Sort dialog's closed "Sort by" box showed only the stored column
letter ("B") while its open list showed "B: Revenue". It now looks the
letter up in the dialog's column labels and shows the same label, falling
back to the bare letter for a column outside the data block.
2026-11-07 00:27:07 +06:00
FredApps 6a8eb4ff42 Merge pull request #3487 from storytold/house-rules
Copy the shared house rules into AGENTS.md
2026-11-06 14:59:10 +01:00
FredApps 543497acff docs: preserve local guidance in house-rules rollout 2026-11-06 12:37:04 +01:00
FredApps 620a91904f Copy the shared house rules into AGENTS.md.
The blocks come from craft-contrib at dde09ea31e2e457f5b4653a6e76f7cdf55944bdf. CLAUDE.md imports AGENTS.md. The house-rules workflow checks the copy.
2026-11-06 11:50:01 +01:00
ff35c6b89d xlsx: stop memory amplification from declared sizes (#2173) (#2197)
* xlsx: stop memory amplification from declared sizes (#2173)

Hyperlinks: drop targets over 8,192 bytes before copying them into each cell, cut ScreenTips to 255 characters, and count the 10,000-cell cap across all <hyperlinks> blocks of a sheet. Conditional formats: cap the sqref copies made by extra rules at 1M ranges per sheet. Styles: build each custom number format once and share its Arc; cut font names to 255 characters. Shared formulas: keep cached values for dependents of over-long masters, and charge each expansion against a 2 GiB per-workbook budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Import the hyperlink caps in sheet_read/parts after the merge

Upstream switched parts.rs to an explicit super:: import list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* xlsx: charge conditional-format ranges per sheet; cap number format codes (#2173)

- read_cf keeps a cumulative per-sheet count of the range cells retained
  rules hold, instead of multiplying the current block by the rule count,
  and charges only rules that are kept.
- Custom and inline number format codes longer than 255 characters (Excel's
  limit) are dropped with a warning; their cells show General.
- The shared NumFmt map is built only for ids a cell or differential
  format refers to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* xlsx: keep over-long number format codes as stored (#2173)

an_overlong_number_format_code_does_not_stop_the_open requires a code
past 255 characters to round-trip; sharing one NumFmt per referenced id
already removes the amplification. Keep the referenced-id sharing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* xlsx: bound only what a shared formula costs beyond an ordinary cell; count characters for the length warning

Review follow-up for #2197 (@skylartaylor):
- Each dependent of a shared formula now pays only its cost beyond an ordinary formula cell
  (SHARED_FORMULA_FREE_COST, 4 KiB by shared_cost), and the per-workbook budget for that excess
  drops from 2 GiB to 256 MiB. Many ordinary shared formulas still all expand; a long master
  copied into thousands of cells stops at the budget and keeps the cached values.
- The over-long-master warning now uses the parser's own test (bytes, then characters), so an
  unparseable multibyte master no longer gets a false length warning.

Refs #2173

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-06 10:52:53 +01:00
Harrison BoothandClaude Sonnet 5.5 21b64df0fe Paste of a whole-column or whole-row copy no longer aborts the app (#2018)
* Paste: whole-column paste and validation without per-cell blowup

edit.paste of a whole-column or whole-row copy walked every cell of the
copied block (up to the whole sheet) and, for each one, re-cut every
validation rule and pushed a one-cell rule. Over a 2M-cell block that is
quadratic and the fuzz soak aborted on 34-68 GB allocations.

- Paste visits only cells that hold something in the source or the
  destination; blank onto blank changes nothing (and with an operation
  stays blank, as in Excel, instead of becoming 0).
- Validation pastes as ranges: the pasted rectangle is cut once out of
  the destination's rules and each source rule comes along as one rule
  over its translated parts (Skip Blanks or not, as Excel does).
- Whole columns and rows paste whole, so destination content further
  down the columns is replaced, as in Excel.
- A paste that does not fit on the sheet is refused, as Excel does,
  instead of pasting part of the block (whole columns below row 1, whole
  rows right of column A, a whole column transposed, a block at XFD).
- Paste Link, the one paste that writes every cell, is capped at
  1,048,576 cells; tiled validation at 100,000 ranges.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Paste: visit destination cells once per paste, add link, transpose and tiling tests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Paste: visit link-only cells so a cut keeps their hyperlink

* Paste validation: Skip Blanks keeps the destination rule under a blank source cell with no rule

* Paste validation: Skip Blanks collects clearing cuts only when a rule is under the paste, merges them into runs, and limits only generated areas

* Paste validation: annotate the clearing cut list

* Qualify paste validation context type

* Fix paste-special clippy warnings

* engine: batch validation range cuts

* Fix range type inference

* Annotate validation range index type

* Avoid shadowing test session helper

* Keep validation rows after the final cut

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 22:24:59 +01:00
Harrison BoothandClaude Sonnet 5.5 292b784f02 fix(calc): apply "Set precision as displayed" in calculation (#1366)
* ft-121.7.9: apply Set precision as displayed in calculation (#1343)

With the option on, a formula result (and each element of a spill) is
rounded to its cell's number format before it is stored and before other
formulas read it. Numbers typed, pasted or reformatted while it is on are
rounded at once, and turning it on through formulas.calculationOptions
{precisionAsDisplayed} rounds every number constant, as Excel does.

The rounding is NumberFormat::round_as_displayed in the numfmt crate,
which shares the renderer's digit logic (fixed, scientific, fraction).
General, date/time, text, digitless and conditional formats keep the full
value, as observed in Excel through COM. Nothing changes when the flag is
off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ft-121.7.9: round conditional formats by the sign's section under precision as displayed (#1343)

Excel does round formats with a condition when "Set precision as
displayed" is on; the PR kept them at full value. Observed through COM
with synthetic values (and in gridcraft-corpus
excel/precision-displayed/pad-on-conditional-sections.xlsx): conditions
are ignored and the sign picks the section, the first for positives and
the second for negatives. `[<1]0.00;0` shows 2+1/3 as 2 but stores 2.33,
and stores -10/3 as -3.

Two more observed details: with a condition and a single number section,
negatives keep the full value (`[<1]0.00`), unless an `@` section follows
(`[<1]0.00;@` rounds them by the first section); and a completely empty
section passes to the next one (`0.0;;0.000` stores -2.333 as -2.333,
`;0.0` stores 2.333 as 2.3), for formats with or without a condition.

Display is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* engine: recalculate spill anchors when a spill cell is reformatted under precision as displayed

round_edited_as_displayed only looked at stored-cell changes, but a spill
destination lives in Sheet::spill, so reformatting it (or its row or column) left
the anchor out of the changed set and the element unrounded. Add each spill anchor
whose range holds a changed cell or a changed row/column style to the changed set.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* numfmt: drop a cast the fraction merge made redundant

* precision as displayed: keep locked constants on protected sheets, round new sheets, fix manual-mode comment

* precision as displayed: name the input sheet in the new-sheet histogram test

* precision as displayed: use six values in the new-sheet histogram test

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:24:49 +01:00
FredApps c402953828 Merge pull request #3425 from ZIDAN44/fix/2874-german-insert-cells
i18n: German context menus' Insert reads "Zellen einfügen", not a second "Einfügen" (#2874)
2026-11-05 15:03:10 +01:00
FredApps 0a58f94517 Merge remote-tracking branch 'origin/main' into pr 2026-11-05 14:49:58 +01:00
FredApps 6ea9f538a7 Merge pull request #2593 from helenkwok/web/host-page
Web: page host mode, so a page can host GridCraft in its own document (#2592)
2026-11-05 13:58:25 +01:00