Commit Graph
1983 Commits
Author SHA1 Message Date
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 d64b30b3a4 Funnel chart: hide value labels when data labels are off (#544) 2026-10-12 08:29:56 +02:00
MarekSpodBiedry 358334c755 Return #VALUE! from FIND and SEARCH when the start is past the text end (#541) 2026-10-12 08:29:56 +02:00
MarekSpodBiedry 2eb5ef5b0d Return #NUM! from PROB for zero probabilities (#539) 2026-10-12 08:29:48 +02:00
MarekSpodBiedry 30fe8c4cae Fix chart rasterizer panic on negative rectangle extents (#542) 2026-10-12 08:29:48 +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 afe9a6301f SUBTOTAL 109-111 leave out nested subtotals like 1-11 (#556) 2026-10-12 08:28:53 +02:00
MarekSpodBiedry ea6bb98fc7 fix(xlsx): keep leading and trailing whitespace in document properties on reopen (#554) 2026-10-12 08:28:50 +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 afd61a7c4a Parse two-digit years in spaced month-name dates (#566) 2026-10-12 08:27:32 +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
webdevtodayjason 1ea44bf00b INDIRECT: R1C1 offsets past the sheet give #REF! instead of overflowing 2026-10-11 23:45:46 -05:00
prxnvt 76c9f4f2af Build: the ui-egui build script reads CARGO_MANIFEST_DIR when it runs
env! fixes the path to contributors/ when the build script is compiled.
After a checkout is moved or copied, the compiled script keeps looking in
the old location: the About window loses its contributor list, and
because the rerun-if-changed path no longer exists, Cargo reruns the
script and rebuilds the UI crate on every build. Reading the variable at
run time follows the checkout.
2026-10-11 23:29:44 -04:00
nihilist-narwhal c7701f6e13 Functions given by name: GROUPBY(…,SUM), BYROW(…,SUM), MAP(…,ABS)
Excel lets a worksheet function be passed by name where a function is
expected. In GridCraft GROUPBY(A2:A9,B2:B9,SUM) and PIVOTBY gave #NAME?
(their documented form), and BYROW, BYCOL, MAP, REDUCE, SCAN and
MAKEARRAY gave #VALUE!. A name that is a worksheet function, and not a
LET/LAMBDA variable or a defined name, now works as a function there.
Reading a file, the _xleta. prefix Excel writes for these names is
removed.
2026-10-11 22:09:17 -05:00
webdevtodayjason 88cf4cf92d INDEX: a huge area number gives #REF! instead of overflowing 2026-10-11 21:32:01 -05: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
Bryan MarkandClaude Opus 5.5 481f1c2b8b File view: the recent list tells when each workbook was last changed
The recent workbooks on the File view's Home and Open pages gain a
Modified column: "Just now", "5 min ago", "3 h ago", "Yesterday",
"4 days ago", "3 weeks ago", "5 months ago", "2 years ago", or
"Not available" for a file that can't be reached.

The file system is never asked while drawing. A thread asks for each
file's metadata and the list shows each answer as it comes back, so a
network drive that is asleep or a share that is away doesn't hold up
the view. One thread asks at a time; a view opened anew asks anew. Only
metadata is read: no workbook is opened until the user picks one.

The list tells ages rather than dates because the UI has no time zone
to tell a date by. From two days on a count is never one, so each
language needs one form of "days", "weeks", "months" and "years"; the
ten strings are in the nine catalogs, with the count placed by a {n}
in each, as "Page {page} of {pages}" does.

A long name or folder now stops short of the column instead of running
under it. The web build, which has no files to ask about, and a narrow
window show the list as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:38:55 +09: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 c59e94297c fix(i18n): correct scattered wrong French translations and add 2 missing keys
The completeness test's failure (missing "Different First Page") was
only the visible tip: fr.rs was missing that key and "Different Odd &
Even Pages" outright, but also had roughly 50 *present* keys mapped to
the wrong value — reading as a real, different English string's French
translation instead of its own. The completeness test only checks that
a translation exists and is non-empty, not that it's correct, so none
of this was caught before.

Examples found by inspection (key => previous wrong value => what that
value actually means):
  "Click to add data"  => "Fermer"                        (= Close)
  "Header"              => "Informations"                  (= Info)
  "Ruler"                => "Enregistrer sous"              (= Save As)
  "Info"                 => "Insérer les cellules copiées…" (= Insert Copied Cells…)
  "Animations"          => "Auteur"                        (= Author)
  "Auto"                 => "Retour"                        (= Back)
  "Previous Comment"    => "Imprimer tout le classeur"      (= Print Entire Workbook)

The wrong values aren't a local shuffle (adjacent keys swapped) — "Info"
drew a value that belongs near the start of the key list while "Info"
itself is near the end, so whatever produced this scattered entries
from unrelated parts of the file onto each other. Given the extent and
that it wasn't contained to one block, patching individual lines risked
missing more of them, so this regenerates every one of the 381 entries
from the English source text (same source list i18n-serbian used),
rather than auditing the existing file entry by entry.

Verified: the existing completeness test (translations_are_unique_...)
now passes end to end — it was failing on a clean main checkout before
this change, purely from the 2 missing keys. Full cargo test -p
gridcraft-ui-egui --lib: 75/75 (was 74/75 on main).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 02:22:00 +02:00
Dejan PortandClaude Sonnet 5 b60d641083 i18n: add Serbian (Latin script) interface translations
Adds Language::Sr ("sr"), following the existing per-language module
pattern (crates/ui-egui/src/i18n/<lang>.rs with a translate(text) ->
Option<&'static str>). All 381 strings in the JAPANESE master list are
covered, verified by the existing completeness test.

Latin script (sr-Latn), ekavian dialect — the common default for
digital/software Serbian. Cyrillic (sr-Cyrl) could follow as a
separate variant if wanted; this keeps the PR to one script, matching
how every other language here is a single variant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 02:14:17 +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
tydude001andClaude Opus 5.5 3a8f6f5aa5 AutoFilter: the value list scrolls across the menu's full width (Fixes #432)
The value list kept egui's default auto_shrink, so its scroll bar sat mid-menu after the longest
value and the wheel only scrolled it with the pointer over a value. Keep the height shrinking but
not the width: the scroll bar is at the menu's right edge and the wheel works anywhere over the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:04:55 -05: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
Dejan PortandClaude Sonnet 5 faba681c99 fix: select a newly inserted chart from Recommended Charts
Resubmit of the selection half of #224 against current main, as
requested — the Delete-key half was a duplicate of #219 and is
already merged.

Insert > Recommended Charts left the prior cell/range selection in
place instead of selecting the new chart, unlike every other object
insertion path (see insert.chart in dialogs.rs, which already selects
the chart it creates). With the selection left on the source data,
pressing Del right after inserting deleted that data and left an
empty chart behind (#214).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 01:31:29 +02:00
Brandon ThomasandClaude Opus 5.5 3926d21aa9 Fix-up: #423's table-name round-trip test sets #433's Table.filter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:08:49 -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 63e60de76c Merge pull request #413 from pechnikov2026-star/i18n-fr
i18n: add French (fr) interface
2026-10-11 18:06:05 -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 61e3dd99f7 Merge pull request #416 from neilcawse/feat/oracle-check
CLI: check subcommand - cached-value calc oracle and XLSX round-trip fidelity on any corpus
2026-10-11 18:06:02 -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 f170b89a92 Merge pull request #420 from neilcawse/fix/structref
Formulas: trim-reference operators (A1:.E10, A1.:E10, A1.:.E10); read unbracketed [[#This Row],Col]

# Conflicts:
#	docs/function-parity.md
#	docs/gaps.md
2026-10-11 18:05:40 -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 47b2cfbcd0 Merge pull request #415 from neilcawse/ci/pr-gate
CI: run cargo xtask ci on pull requests and pushes to main
2026-10-11 18:05:19 -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 e2bcb712fd Merge pull request #418 from neilcawse/fix/numfmt-text
Numfmt: fraction literals and zero fractions, `?` pads thousands separator, TEXT booleans through text section
2026-10-11 18:05:17 -04:00
Brandon Thomas 936c6a055a Merge pull request #423 from neilcawse/fix/xlsx-names-xlpm
XLSX: strip _xlpm. from LET/LAMBDA parameters on read, keep line breaks in table columns, allow leading backslash in table names

# Conflicts:
#	docs/file-format-parity.md
2026-10-11 18:05:16 -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
Brandon Thomas 1841658978 Merge pull request #439 from itrycodinganditsbad/rotated-text-align
Rotated text follows the cell's alignment
2026-10-11 18:05:12 -04:00