Commit Graph
720 Commits
Author SHA1 Message Date
RichardandClaude Opus 5.5 bf9a202222 Compute the Top 10 filter threshold once per column, not once per row (#661)
apply_filter cloned and sorted the column's numbers for every row it tested,
O(n^2 log n). The threshold is now computed once per criterion with the other
column stats. Adds a test for top/bottom items and percent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ut3hr3a13szpQdQ7gbYbfc
2026-10-12 20:29:00 +11:00
MarekSpodBiedry 7d9c0a4dd9 Center Across Selection: center text over the empty cells to its right (#479) 2026-10-12 08:41:31 +02:00
MarekSpodBiedry 9da1814cdf Refuse a PivotTable that cannot fit before aggregating all column groups (#515) 2026-10-12 08:39:35 +02:00
MarekSpodBiedry 5334ae5cbc Keep manually hidden and collapsed rows when removing an AutoFilter (#508) 2026-10-12 08:39:12 +02:00
MarekSpodBiedry 5fbd726a11 Treat non-object params as empty in themeColors, background and drawBorder (#516) 2026-10-12 08:38:11 +02:00
MarekSpodBiedry c20f4c2fcb Fail Web Page export on sheets taller than its row limit instead of dropping cells (#514) 2026-10-12 08:38:04 +02:00
MarekSpodBiedry baac184ed8 Reject row/column insert counts past the worksheet and clamp delete counts instead of overflowing (#531) 2026-10-12 08:37:46 +02:00
MarekSpodBiedry 3846376937 Refuse Total Row on a table that ends on the last worksheet row (#533) 2026-10-12 08:37:37 +02:00
MarekSpodBiedry 2d0fb534a2 Fix saving a custom view or script when the stored list is not a JSON object (#517) 2026-10-12 08:37:04 +02:00
MarekSpodBiedry 7464ce6e6e Return an error when a weekday fill runs past the supported dates (#535) 2026-10-12 08:36:31 +02:00
MarekSpodBiedry 125f460cef Paste Special: copy the validation rule with the Validation and All options (#562) 2026-10-12 08:36:13 +02:00
MarekSpodBiedry 76d3879a94 Fix data.filterBy inside a table installing the sheet AutoFilter instead of toggling the table button (#547) 2026-10-12 08:35:51 +02:00
MarekSpodBiedry 0ae1eafc9b Open files by their content, not a mismatched extension (#569) 2026-10-12 08:34:52 +02:00
MarekSpodBiedry ff14224ff9 Convert TODAY and NOW to the workbook date system in 1904 workbooks (#578) 2026-10-12 08:34:40 +02:00
MarekSpodBiedry 8427681d7c Tables: names with a hyphen or other operator characters are rejected (#576) 2026-10-12 08:34:22 +02:00
MarekSpodBiedry 40dfdd8331 Restore frozen panes when showing a custom view (#577) 2026-10-12 08:33:43 +02:00
MarekSpodBiedry 34e1d373bf fix(engine): CSV named History keeps a valid sheet name (#570) 2026-10-12 08:33:17 +02:00
MarekSpodBiedry c13cda2229 Column sort rejects a zero row key with a parameter error instead of panicking (#534) 2026-10-12 08:31:04 +02:00
MarekSpodBiedry b5827f4198 Spelling: skip suggestions for words longer than 64 characters (#532) 2026-10-12 08:30:19 +02:00
MarekSpodBiedry 1f16c2e268 fix(calc): recalculate dependents of a cleared spill past 65536 cells (#558) 2026-10-12 08:29:06 +02:00
MarekSpodBiedry 593776f0de Keep a defined name that shares a table's name when the table is renamed (#574) 2026-10-12 08:28:36 +02:00
MarekSpodBiedry c5d43a33e6 Reject table.resize to a range that does not overlap the table (#575) 2026-10-12 08:27:34 +02:00
MarekSpodBiedry c45212ac68 Refuse inserting cells that would push values off the worksheet (#580) 2026-10-12 08:27:27 +02:00
MarekSpodBiedry a4c6ac3eff Reject out-of-range numeric filterBy columns instead of overflowing (#573) 2026-10-12 08:27:08 +02:00
prxnvt 019d5de98e Data: Solver Answer and Limits reports
Builds on the Solver of #327. data.solver takes reports: [answer, limits]
and writes them on new sheets before the model's sheet once a solution
is found. The Answer report lists the objective and variable cells with
their original and final values and each constraint's value, formula,
status (Binding or Not Binding) and slack; cells are named by the
nearest labels to their left and above. The Limits report minimises and
maximises each variable with the others held at the solution, under the
model's constraints, and gives the objective at both ends (Simplex LP
and GRG Nonlinear, without integer constraints).

data.solverReport writes the reports for a given solution and model,
so data.solver's result (which now carries the original values and the
model) can be passed to it as is. The Solver Results dialog offers the
two reports; they are written after Keep Solver Solution or Restore
Original Values.
2026-10-11 22:25:08 -04:00
prxnvt fa2b9566fb Data: Solver Evolutionary method
Builds on the Solver of #327. method: evolutionary runs a genetic search
for models that aren't smooth: tournament selection, blend crossover and
Gaussian mutation inside the variable bounds, feasibility-first ranking
and integer rounding, then a compass search around the best member. It
stops when the population converges, when the best member stops
improving for a number of generations, or at the evaluation budget. A
seeded generator makes runs repeatable. Population size, mutation rate,
random seed, convergence and Require Bounds are options; the method and
its options are saved in the hidden solver_* names. The Solver
Parameters dialog offers the method.
2026-10-11 22:09:10 -04:00
webdevtodayjason fb7bcefaa4 OFFSET: huge offsets and sizes give #REF! instead of overflowing 2026-10-11 20:50:23 -05:00
skylar taylor-barrick 587d879f13 Print: adopt craft-print with native settings and job feedback 2026-10-12 03:25:47 +02:00
Dejan PortandClaude Sonnet 5 7ba3676f5f fix: satisfy stricter clippy lints CI's newer stable toolchain catches
CI failed with cargo clippy --workspace --all-targets -- -D warnings,
not a test. The actual cause: I'd been validating locally against
rustc/clippy 1.95.0 (the toolchain this machine had installed before
now), while CI installs plain "stable" (1.99.0 as of this run) — two
lints new since 1.95.0 fired there and nowhere in my local checks:

- clippy::int_plus_one on `src.end.row + 1 <= cap` in grid.rs,
  simplified to `src.end.row < cap` (same meaning, no overflow risk
  change: row is a u32 and cap is already saturating-clamped below
  MAX_ROWS - 1, so src.end.row + 1 can't wrap).
- clippy::disallowed_methods on the new fill_handle_perf example's
  three Instant::now() calls — the project's own lint from the time-
  panic fix (#125 history): reading the clock traps on wasm. This
  example is native-only (a `cargo run --release` benchmark, never
  built for wasm), so it gets the same #![allow(...)] perf.rs already
  carries, with the same justification.

Updated the local toolchain to stable (rustup update stable) and
re-ran cargo clippy --workspace --all-targets -- -D warnings clean
before this push, to actually match what CI checks instead of
guessing again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 02:52:07 +02:00
Dejan PortandClaude Sonnet 5 e7d590c8a4 style: satisfy cargo fmt and clippy on the fill-handle perf change
cargo fmt reformatted the new example's while-loop condition onto its
own line; clippy's nonminimal_bool caught a negated is_some_and that
reads more plainly as is_none_or.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 02:07:45 +02:00
Dejan PortandClaude Sonnet 5 43a118c543 perf: fill-handle double-click scans a dense neighbour column ~2.4x faster
Follow-up to #210, which the reviewer correctly rejected: the stated
cause (a sparse column with a stray distant value) doesn't hold, since
the scan already stops at the first empty cell. This is a different,
narrower fix that doesn't change behaviour for any input — it never
truncates a long fill, unlike #210's cap — only how fast a genuinely
long, densely-populated run is scanned.

grid.rs's fill-handle double-click called Sheet::value_ref once per
row to find where the neighbouring column's data ends. Each call does
a band lookup + row lookup + column lookup (three BTreeMap probes) via
CellStore::get. For a real dense run (e.g. a filled column near the
1,048,576-row sheet limit) that's a lot of point lookups on the UI
thread in one frame: ~59ms for a 1,000,000-row run in a release build
(measured with the new fill_handle_perf example), likely worse under
wasm where GridCraft actually runs in the browser.

- crates/model/src/store.rs: CellStore::contiguous_value_run walks
  each band's row BTreeMap with `range` (one in-order pass per band)
  instead of one `get` per row, so a dense run costs roughly one
  tree-traversal step per row instead of one binary search per row.
  Deliberately narrower than `has`/`get`: a stored cell always decides
  the row on its own (even with an empty value), matching what
  `value_ref`'s callers actually test for, not `has`'s "formula counts
  regardless of its cached value" (used for Ctrl+arrow navigation).
- crates/model/src/sheet.rs: Sheet::last_contiguous_nonempty layers
  `spill` back in wherever contiguous_value_run finds no stored cell,
  exactly where plain `value_ref` would fall through to it.
- crates/ui-egui/src/grid.rs: the fill-handle double-click now calls
  this once instead of looping over value_ref.

Result: 999,999-row dense run, 59.2ms -> 24.7ms (2.40x), same answer
(asserted in the example). Four new tests in sheet.rs, including a
300-trial property test against a naive value_ref-loop reference
(spills, formula-with-empty-value, gaps, blanks) — all pass, plus the
full existing suite (`cargo test -p gridcraft-model -p gridcraft-ui-egui
-p gridcraft-engine`; the only failures are #210-era gridcraft-engine
CSV tests already failing on main, unrelated to this change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 02:01:49 +02:00
SteamSmithandClaude Opus 5.5 3424229ee3 io: remove the .sctmp temporary file when a save fails
write_file writes `<path>.sctmp` and renames it over the workbook. When the
write or the rename failed (a full disk, or the target locked by another
app such as Excel or a sync client), the temporary file was left beside the
user's workbook. Remove it on either failure and report the same error.

Adds io::tests::failed_save_leaves_no_temporary_file, which fails before
this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:59:42 -04:00
Brandon Thomas 3b19cb80e6 Merge pull request #412 from jsymes21-byte/keytips-table-refactor
Keytips: full Excel sequential Alt table — 364 sequences, badges on every control, generated tests (fixes #33, #49)

# Conflicts:
#	contributors/people.toml
2026-10-11 18:06:06 -04:00
Brandon Thomas a34fe4a7af Merge pull request #436 from LloydNicholson/feat/open-encrypted
Open password-encrypted workbooks (Agile and Standard encryption) (#11)

# Conflicts:
#	ROADMAP.md
#	docs/file-format-parity.md
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-11 18:06:04 -04:00
Brandon Thomas 0a54876e2c Merge pull request #425 from neilcawse/test/command-sequence-fuzz
Engine: command-sequence fuzz harness (undo/redo, XLSX round-trip, full-recalc invariants) and the bugs it found
2026-10-11 18:06:03 -04:00
Brandon Thomas 69b5f78e86 Merge pull request #424 from neilcawse/fix/trendlines
Charts: read, write and draw trendlines

# Conflicts:
#	docs/gaps.md
2026-10-11 18:06:01 -04:00
Brandon Thomas dcd469d12e Merge pull request #419 from neilcawse/fix/function-fixes
Functions: MODE/VALUE/SUMPRODUCT text args, ADDRESS [Book]Sheet, Excel-style lookup bisection, SUBTOTAL skips filtered rows

# Conflicts:
#	docs/file-format-parity.md
#	docs/function-parity.md
2026-10-11 18:05:41 -04:00
Brandon Thomas 80c2efe0f1 Merge pull request #421 from neilcawse/fix/iterative-calc
Calc: iterative calculation for circular references (Maximum Iterations, Maximum Change)

# Conflicts:
#	docs/function-parity.md
2026-10-11 18:05:39 -04:00
Brandon Thomas 74d57ea9fd Merge pull request #422 from neilcawse/fix/csv-display
File formats: CSV/TSV export writes displayed values; templates and .xlsm saved with their own content types

# Conflicts:
#	crates/engine/src/io.rs
#	crates/xlsx/src/tests/roundtrip.rs
#	docs/file-format-parity.md
2026-10-11 18:05:33 -04:00
Brandon Thomas a96f19f02b Merge pull request #409 from shrshk/fix/formula-arg-count-363
Refuse formulas with the wrong argument count on entry (#363)
2026-10-11 18:05:18 -04:00
Brandon Thomas 5432419cd5 Merge pull request #427 from vedran-dobos/csv-keep-delimiter
CSV: saving keeps the separator the file was opened with (#426)
2026-10-11 18:05:14 -04:00
Vedran DobosandClaude Opus 5.5 b13ea3e2f3 CSV: saving keeps the separator the file was opened with (#426)
A `;`-separated CSV (the usual CSV where `,` is the decimal separator) was
rewritten with `,` on save: read_csv sniffed the delimiter but the result was
dropped, and save_bytes passed a hard-coded b','. file.save, Save As .csv,
file.exportCsv, file.saveBytes and gridcraft-cli convert all went through it.

The workbook now remembers the separator it was read with
(Workbook::csv_delimiter, set by read_csv) and a CSV save writes it back.
Workbooks not read from CSV, and .tsv/.txt sources saved as .csv, keep `,`.

Regression tests: `;`, `,` and tab .csv round trips byte for byte, new and
.xlsx-origin workbooks save with `,`, and open-by-path + edit + file.save on a
`;` file keeps `;` (also through exportCsv and saveBytes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:46:27 -07:00
Brandon Thomas e6773dac3c Merge pull request #433 from derechopiano/fix/autofit-filter-button-spacing
Fix AutoFit width for table and AutoFilter dropdowns
2026-10-11 18:05:13 -04:00
MarekSpodBiedry 88df1670ba Scatter charts of two unlabelled columns plot the first as X for one series (#428) 2026-10-11 23:38:27 +02:00
lloydandClaude Opus 5.5 1532349227 Encrypted workbooks no password opens are named before asking for one (#11)
gridcraft_xlsx::check_encryption parses EncryptionInfo without a
password, so RC4, Extensible, certificate-only and rights-management
(IRM) files, and damaged ones, get their real message instead of a
password prompt first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:29:28 +02:00
lloydandClaude Opus 5.5 aad58d4c75 Engine: file.open decrypts password-protected workbooks (#11)
Without a password, file.open returns an error carrying
io::PASSWORD_REQUIRED (never a dialog). A decrypted workbook gets no save
path, so Save never replaces the encrypted original.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:29:28 +02:00
Shirish Veerabattini 3936f3b79c engine: correct which functions the entry check skips
IF, IFERROR, CHOOSE, SWITCH and INDEX are in the registry and are checked; the comment listed IF as skipped.

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 16:10:04 -05:00
derechopiano 1a6c864235 home.autofitColumnWidth: reserve filter dropdown space 2026-10-11 22:09:51 +01:00
Shirish Veerabattini 478f01d236 functions: keep DATEDIF's original description, add the units
Restores "Counts whole years, months or days between two dates." and
appends the units, in the multi-line form rustfmt gives a long entry.

Drops the 256-argument test case: the parser already refuses more than
255 arguments, so it never reached the entry check. (993c108's message
says entry "also refuses" them; it already did.)

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 15:57:33 -05:00
James cd5f3f0a71 T6: pending keytips resolve at runtime and go live when their command lands
- Action::Pending now consults the engine catalog at fire time: silent
  no-op while the command is missing, runs it once it exists - no table
  edit needed when a future feature lands.
- Stripped stray backticks from 7 pending target ids so catalog lookup
  can ever match them.
- gridcraft-engine: doc(hidden) register_test_command hook so UI tests
  can prove the go-live path without touching production code.
- Tests: pending_sequences_are_silent_no_ops (no dialog/toast/journal),
  pending_sequences_go_live_when_the_command_lands (fake command fires
  via its reserved sequence), every_sequence_terminates_without_panicking
  (all 413 sequences pressed end-to-end).
2026-10-11 23:40:04 +03:00