Commit Graph
477 Commits
Author SHA1 Message Date
Sebastian Whincop aee898595f Accuracy: round an exact tie at the 16th significant digit toward zero
Excel shows 15 significant digits. When a number is an exact tie at the 16th
(its exact decimal expansion is 15 digits, a 5, then only zeros), Excel
rounds it toward zero: TEXT(1234567890123455,"0") is 1234567890123450.
GridCraft took Rust's `{:.14e}`, which rounds that tie to even, so any tie
with an odd 15th digit came out one unit too high (…460).

decimal_digits now formats 16 digits (correct unless the 16th is a 5),
and only on a 5 checks the exact expansion (a 30-digit screen, then the
full one) and truncates a true tie. Non-ties, including values just above
or below a tie, round as before. One format serves almost every number.

Tests: ties of every 15th-digit parity, negatives, fractional ties, the
neighbours just above and below a tie, the carry after fifteen 9s
(999999999999999.5 shows 999999999999999), and unchanged ordinary cases.

Evidence: Windows Excel 16.0.20430 TEXT() over a 174-row probe and a 24-row
carry probe agree on every row except those under a percent format and
General over negatives (the two bugs named below). The suite's number-format
golden goes from 183,843 to 184,900 of 187,810 passing with no row that
passed before failing now.

	modified:   crates/numfmt/src/decimal.rs
	modified:   crates/numfmt/src/tests.rs
2026-10-11 16:22:36 +13:00
webdevtodayjason fae90c8e4a selection.move: clamp extreme dr/dc to the sheet edge instead of overflowing 2026-10-10 21:05:58 -05:00
pkibiego 2f078962a0 xlsx: bump quick-xml to 0.41 for the namespace-declaration DoS (RUSTSEC-2026-0195)
The ODS and XLSB readers parse untrusted package metadata and content through
`quick_xml::NsReader`. In 0.38.4 that is RUSTSEC-2026-0195 (CVSS 7.5): the namespace
resolver pushes a binding for every `xmlns` / `xmlns:*` declaration on a start tag, with
no cap on how many there are, and `NsReader::read_event` reaches that path - not only
`read_resolved_event`.

None of our caps bound it. MAX_PART (512 MiB), MAX_TOTAL (1 GiB), MAX_ENTRIES,
MAX_CELLS, MAX_TEXT and MAX_DEPTH bound part size, entry count, cells, text and nesting,
never declarations per tag.

Measured with `gridcraft-cli info` on crafted .ods files, before this change:

     754 B, 0 declarations  ->    8.6 MB, exit 0
     24 KB, 1,000,000       ->   52.6 MB, exit 0
    187 KB, 8,000,000       ->  367.8 MB, exit 0
    770 KB, 33,000,000      ->    1.46 GB, exit 0

About 45 bytes of resident memory per declaration, roughly 1,900x the file size, and no
error at all. At MAX_PART a ~1 MB file reaches ~1.9 GB, and every part is held in memory
while the package is read. Both readers share the path, so a downloaded .ods or .xlsb
was enough. Nothing panicked, so `Session::execute` never saw it.

0.41.0 caps declarations per start tag at 256 (DEFAULT_MAX_DECLARATIONS_PER_ELEMENT,
raisable through NamespaceResolver::set_max_declarations_per_element) and returns
`NamespaceError::TooManyDeclarations` beyond it. The same fixtures after:

     754 B ->    8.6 MB, ok         24 KB ->   19 MB, error
    187 KB ->  101.8 MB, error     770 KB ->    397 MB, error

with "start tag declares more than 256 namespace bindings". The residual peak is the
part itself: MAX_PART still lets one part decompress to 256 MiB, the existing intended
bound.

API changes for 0.41 (0.x bumps may break; all four are in the xlsx crate):
- `resolve_element` and `resolve_attribute` moved off `NsReader` on to the resolver:
  `reader.resolver_mut().resolve_element(..)`. `attr` and `tag` in ods.rs take
  `&mut NsReader` for that.
- `Attribute::unescape_value` is deprecated in favour of `normalized_value`, which also
  applies the attribute-value normalization the XML specification requires.
- `BytesText::xml_content` now takes the document's XML version.

This also clears RUSTSEC-2026-0194, the quadratic duplicate-attribute check. xml.rs
already passed `with_checks(false)`, but ods.rs and xlsb.rs used the checked iterator, so
they were on the quadratic path as well.

Regression test: `ods_tests::ods_namespace_declaration_bomb_is_rejected` asserts that a
tag with thousands of declarations is an error and that a document with a handful still
reads. It fails on 0.38.4 (the tag is accepted) and passes on 0.41.0.

Verified: `cargo xtask ci`; the full xlsx suite (80 tests) plus the malformed-input
tests; and the four measurements above, before and after, with no panics either way.
2026-10-11 03:39:32 +03:00
webdevtodayjason a788c41faa #200: keep formula helpers inside the worksheet
Fixes #200
2026-10-10 18:29:25 -05:00
Dejan Kocić a0dc1abce1 fix: Save toolbar button and saveChanges dialog discard an in-progress cell edit
Clicking the Save icon in the toolbar, or the Save button in the "save
changes?" dialog, called file.save directly without first committing a
cell edit still in progress (e.g. double-click a cell, type a new value,
then click Save with the mouse instead of pressing Enter). The pending
text in EditState was never flushed into the document, so the saved
file still held the pre-edit value, silently.

The File > Save menu item already guards against this
(app.commit_edit(...) before running the command); this applies the
same guard to the toolbar icon and the saveChanges dialog's Save button.
2026-10-11 00:35:45 +02:00
Zarvinx ac87c269ff Fix #213: delete selected objects from keyboard 2026-10-11 01:02:18 +03:00
MarekSpodBiedry 36c591883b Resize charts from the corner handle and the Size fields, keep typed chart and table names (#190)
The corner drag now grabs the chart where the press began and the selected object's corner shows a resize cursor. The Format Chart Area Size fields, chart and axis title fields and the Table Name field reloaded their value from the workbook every frame, so typed or dragged edits were thrown away; they now keep a draft until the field loses focus (Escape cancels, like sheet tab rename).
2026-10-10 23:52:49 +02:00
MarekSpodBiedry 5722dc5355 Do not draw the selection over the frozen pane when it lies only in the scrolling pane (#198) 2026-10-10 23:52:14 +02:00
MarekSpodBiedry 27be1753c2 Read the browser clock for NOW(), TODAY() and year-less dates on wasm (#202) 2026-10-10 23:43:32 +02:00
Zarvinx 271119d9e3 Build: satisfy Rust 1.95 clippy 2026-10-11 00:30:51 +03:00
Zarvinx 7a7e23cab7 Grid: reorder selected columns by header drag 2026-10-11 00:30:24 +03:00
Dejan Kocić d27dec0312 web: print straight to the system print dialog, not just export
The web build had no way to actually print — Ctrl+P (Cmd+P, declared on
file.print's shortcut) did nothing at all, and the ribbon's "Print…" menu
item ran file.print and silently discarded the result. file.print already
builds a PDF and returns it as base64 specifically "for the UI to hand to
the system print dialog" per its own doc comment — this wires that up.

- ui-egui: new Services::print hook (web: hand PDF bytes to the browser's
  print flow directly, no download); SheetApp::print_now runs file.print,
  decodes the base64 result, and calls the hook.
- ui-egui: Key::P now dispatches to print_now (was unbound); the ribbon's
  "Print…" menu item calls print_now instead of discarding file.print's
  result.
- gridcraft-web: implements the hook (Blob -> object URL -> hidden iframe
  -> iframe.contentWindow.print() on load).

Desktop builds are unaffected (no Services::print set there; print_now
falls back to the existing alert-on-error behaviour with no UI action on
success, same as before this change).
2026-10-10 22:50:36 +02:00
Mikkel Georgsen c9525ab5fb Cell references: an invalid reference is an error instead of the active cell
cell_param returned None for any cell argument it could not parse, so every
command that falls back to the selection (cell.set, view.freezePanes,
formulas.trace*, insert.* and others) silently used the active cell instead:
set_cell with cell "ZZZZ0" wrote A1 and reported success. It also dropped a
sheet prefix, so "Other!B2" was applied to the active sheet.

cell_param now returns Result<Option<CellRef>>. An absent or null value is still
None, so the selection fallback is unchanged; an unparsable or non-string value
is an error; a sheet prefix naming a sheet other than the one the command works
on is an error.

cell_param_on is for the commands that resolve their own sheet (cell.get, links,
comments, notes, checkboxes, chart placement, paste): they accept a prefix naming
that sheet. cell.set resolves its own target sheet and still writes to another
existing sheet.
2026-10-10 22:41:40 +02:00
Mikkel Georgsen f69861f7e3 Cell: cell.set without an input is an error
cell.set treated a missing input as an empty string, so a call that forgot the
input (or sent it under the wrong key) cleared the cell and reported success.
It now returns an error when neither `input` nor `value` is given. An explicit
empty string still clears the cell.
2026-10-10 22:41:40 +02:00
Brandon ThomasandClaude Opus 5.5 90b3c41eec Fix-up: #83's array-export test expects #156's legacy-array read (cached values refilled on recalc)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:59:42 -04:00
GyuriX e8d935298f Linux: the display theme asks the desktop, not winit
winit reports no system theme on Linux — `ActiveEventLoop::system_theme()` is
`None` there on every desktop — so `System` in the appearance setting never saw
the desktop's choice and fell back to light. A new `desktop_theme` module asks
the XDG Desktop Portal's Settings interface for
`org.gnome.desktop.interface color-scheme` on a thread of its own, so no
D-Bus call is ever made from the frame loop, and the app follows the answer,
including a change made while it runs.

egui resolves `ThemePreference::System` from what winit reports and offers no
way to set that itself, so where a desktop answered, the app hands egui the
concrete theme instead. What is drawn is unchanged on macOS and Windows, where
winit answers and the same resolution happens a step earlier; the saved
preference is still `system`, so a file keeps meaning the same thing.

The colour scheme is asked for under its GNOME schema name first, which GNOME's
portal and XFCE's both answer, then under the older freedesktop one, which KDE
answers. A desktop that answers neither leaves the user's own choice alone.
2026-10-10 21:55:20 +02:00
Brandon ThomasandClaude Opus 5.5 c6392e6380 Fix-up: chartex frames use the (open, close) anchor tags after merging #161 onto the move-only anchor change
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:52:46 -04:00
Brandon Thomas 88d0c772fb Merge pull request #161 from nihilist-narwhal/fix-chart-kinds-roundtrip
Charts: histogram, box & whisker, waterfall, funnel, treemap and sunburst keep their kind through XLSX
2026-10-10 15:50:48 -04:00
Brandon Thomas f23281db8c Merge pull request #144 from harrisonoconnorhover/codex/cross-sheet-cut
Preserve formula references when cutting cells across sheets

# Conflicts:
#	ROADMAP.md
#	crates/formula/src/adjust.rs
#	crates/formula/src/tests.rs
2026-10-10 15:48:08 -04:00
Brandon Thomas 49fbae245a Merge pull request #156 from nihilist-narwhal/fix-legacy-array-formulas
Legacy array formulas: fill the range they were entered in

# Conflicts:
#	crates/engine/src/cmd/edit.rs
#	crates/ui-egui/src/formula_bar.rs
2026-10-10 15:47:56 -04:00
Brandon Thomas de0c715662 Merge pull request #186 from MarekSpodBiedry/issue-126
Write comment and hyperlink maps with A1 keys so JSON save works (#126)

# Conflicts:
#	crates/engine/src/tests.rs
2026-10-10 15:47:35 -04:00
Brandon Thomas b07249de75 Merge pull request #160 from itrycodinganditsbad/fix-month-fill-clamp
fill: clamp month and year date series to the month's last day

# Conflicts:
#	crates/engine/src/tests.rs
2026-10-10 15:47:32 -04:00
Brandon ThomasandClaude Opus 5.5 c15a51c959 Fix-up: move the #179 table-style test module to the end of tables.rs after merging #149
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:47:19 -04:00
Brandon Thomas dd185ff12f Merge pull request #157 from nihilist-narwhal/fix-blocked-spill-dependents
Recalc: formulas over a blocked spill see #SPILL!; no spilling in tables
2026-10-10 15:47:12 -04:00
Brandon Thomas c3cfc92186 Merge pull request #155 from nihilist-narwhal/fix-range-blank-cells
Ranges keep their size past the used range; spilled blanks show 0
2026-10-10 15:47:12 -04:00
Brandon Thomas 9fea6235f7 Merge pull request #148 from nihilist-narwhal/fix-stale-recalc-values
Recalc: stale spills and values after edits (fixes #133, #139)
2026-10-10 15:47:12 -04:00
Brandon Thomas 84a414a705 Merge pull request #158 from nihilist-narwhal/fix-chart-kinds-combo-stock
Charts: combo and stock charts keep their kind in XLSX
2026-10-10 15:47:11 -04:00
Brandon Thomas 1e5edbd43c Merge pull request #154 from nihilist-narwhal/fix-repeated-headers
Repeated headers: number pivot fields and table columns in linear time
2026-10-10 15:47:11 -04:00
Brandon Thomas 5c456df4b7 Merge pull request #152 from nihilist-narwhal/fix-whole-sheet-selection
Whole-sheet selection: Fill and Remove Duplicates no longer run out of memory or time
2026-10-10 15:47:10 -04:00
Brandon Thomas 92fb7cd1b3 Merge pull request #149 from nihilist-narwhal/fix-table-header-edits
Tables: typing in the header row renames the column; Go To a table selects its data

# Conflicts:
#	crates/engine/src/tables.rs
2026-10-10 15:47:10 -04:00
Brandon Thomas 5545e72944 Merge pull request #188 from MarekSpodBiedry/issue-145
Release focus when the sheet tab rename ends (#145)
2026-10-10 15:46:34 -04:00
Brandon Thomas 960c79a328 Merge pull request #187 from MarekSpodBiedry/issue-57
Compare numbers at 15 significant digits like Excel (#57)
2026-10-10 15:46:33 -04:00
Brandon Thomas 645782f5e3 Merge pull request #185 from MarekSpodBiedry/issue-135
Report an error when file commands get no file in MCP and CLI (#135)
2026-10-10 15:46:32 -04:00
Brandon Thomas 87d64f20e1 Merge pull request #184 from MarekSpodBiedry/issue-136
Honor search in the desktop engine.commands handler so connected list_commands filters (#136)
2026-10-10 15:46:32 -04:00
Brandon Thomas d031c8f37e Merge pull request #183 from MarekSpodBiedry/issue-140
Shrink 3-D sheet spans when an end sheet is deleted (#140)
2026-10-10 15:46:31 -04:00
Brandon Thomas cda29c5b73 Merge pull request #182 from MarekSpodBiedry/issue-141
Deliver Export as PDF as a browser download (#141)
2026-10-10 15:46:30 -04:00
Brandon Thomas c21123e375 Merge pull request #181 from MarekSpodBiedry/issue-127
Keep comma when a semicolon or tab is missing from some CSV lines (#127)
2026-10-10 15:46:29 -04:00
Brandon Thomas 75ede09917 Merge pull request #180 from MarekSpodBiedry/issue-129
Keep the untouched part of a data validation range when clearing or replacing part of it (#129)
2026-10-10 15:35:45 -04:00
Brandon Thomas 14402fc5a3 Merge pull request #179 from MarekSpodBiedry/issue-130
Clamp table style number to 1 so zero-numbered styles don't underflow (#130)
2026-10-10 15:35:44 -04:00
Brandon Thomas e9fb005eed Merge pull request #178 from MarekSpodBiedry/issue-134
Rename sheet-qualified defined names with their sheet (#134)
2026-10-10 15:35:44 -04:00
Brandon Thomas 5578c09e55 Merge pull request #159 from itrycodinganditsbad/fix-min-no-args
functions: MIN() accepts zero arguments
2026-10-10 15:35:43 -04:00
Brandon Thomas 00df1908a7 Merge pull request #153 from nihilist-narwhal/fix-jump-without-direction
selection.move: Ctrl+arrow without a direction no longer loops forever
2026-10-10 15:35:42 -04:00
Brandon Thomas c4e4aeb293 Merge pull request #151 from nihilist-narwhal/fix-current-region
Current region: grow past 10,000 rows, in linear time
2026-10-10 15:35:42 -04:00
Brandon Thomas 20aa531d8c Merge pull request #150 from nihilist-narwhal/fix-paste-special-operations
Paste Special operations: keep text, errors and formulas
2026-10-10 15:35:41 -04:00
Brandon ThomasandClaude Opus 5.5 11ef0e6b04 Maintainer fix-up: compare clearly different numbers directly, skipping the 15-digit text round trip on the sort/lookup hot path
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:35:13 -04:00
Brandon ThomasandClaude Opus 5.5 dfd3675324 Maintainer fix-up: drop wall-clock assertions from the repeated-headers test (flaky under load; Instant::now is a disallowed method)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:35:10 -04:00
Brandon ThomasandClaude Opus 5.5 f75ded37e7 Merge pull request #105 from harrisonoconnorhover/codex/cell-pictures
Add static pictures inside cells with XLSX roundtrip

Rebased onto main (conflicts with #83/#98 and main resolved by maintainer); the contributor's branch does not allow maintainer edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:02:44 -04:00
Brandon ThomasandClaude Opus 5.5 59c8e36d36 Merge pull request #83 from harrisonoconnorhover/codex/nonspilling-array-export
Preserve array evaluation for non-spilling XLSX formulas

Rebased onto main with a maintainer fix-up (LET bindings via split_last/as_chunks for clippy, no slice indexing); the contributor's branch does not allow maintainer edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:02:43 -04:00
Brandon ThomasandClaude Opus 5.5 5b15827ede Maintainer fix-up: leave English formula display untouched, match engine formula detection
English (and languages without a formula table) now shows stored formula
text verbatim instead of re-parsing it every frame; localized entry no longer
trims leading spaces before formula detection (" =1" stays text, as in the
engine). Adds a display round-trip test for string literals containing
separators, decimal commas and the intersection operator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:49:52 -04:00
Brandon Thomas 3e40014eb0 Merge pull request #98 from harrisonoconnorhover/codex/spanish-formulas
Support common Spanish formula names and semicolon arguments
2026-10-10 14:49:52 -04:00