Templates hide grouped columns as width="0" hidden="1". Show Detail and the level buttons only cleared hidden, so the columns stayed invisible; they now reset a zero size, as Unhide does.
range.fill parsed the input once at the active cell, then put back the old
style of every target that already held a cell, so 5%, $5, 3/4/2026 or
=DATE(...) lost their automatic format there (0.05, 5, 46085), and empty
targets took the active cell's style. Each target now gets what
input_to_cell gives for it (parsed once per distinct style), as typing does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI installs the stable toolchain, and Rust 1.95's clippy fails
'cargo clippy --workspace --all-targets -- -D warnings' on main:
- xls/mod.rs: collapsible_match. The XF record's size check becomes a
match guard; a full table still ignores the record (the catch-all arm).
- passthrough_tests.rs: nonminimal_bool. !is_some_and(x) is written as
is_none_or(!x).
No change in behaviour.
Signed-off-by: MH <q6rrzz@gmail.com>
Cells are painted left to right and a right-to-left sheet is mirrored
afterwards, so an explicit Align Right ended up on the left (and Align
Left on the right). Excel keeps an explicit alignment on the side it
names in either sheet direction; General alignment follows the sheet.
The alignment is now swapped before painting (painted_halign), for
straight and rotated text alike.
Signed-off-by: MH <q6rrzz@gmail.com>
Undo stored the selection at the moment of undoing (wherever the user had
clicked) in the redo entry, so redo jumped back there instead of to the
cells it changed. The redo entry now keeps the undone step's selection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Backspace clears the cell (edit.clearContents) and opens an empty editor.
Committing that editor ran cell.set "" on the already-empty cell, which
recorded a second, empty undo step, so Ctrl+Z appeared to do nothing and
the next Ctrl+Z undid an unrelated earlier edit. Committing empty text
into an empty cell now runs no command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/data.rs:550 (custom_ok, used from apply_filter at :623)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
file.open takes {csvAsText: true}: a CSV, TSV or TXT file opens with every field as the text
it holds, with no number, date or boolean conversion, so identifiers such as GTINs, SKUs and
ZIP codes keep their digits and leading zeros and save back unchanged. The default is
unchanged. The CsvOptions.parse_values switch already existed in the reader.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
While pointing at another sheet, a reference without a sheet name (A1) was outlined on the
sheet being shown instead of the sheet the edit started on.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Excel counts TRUE/FALSE and error values as strings in a cache field's
<sharedItems>. GridCraft wrote containsString="0" (and counted booleans and
errors as separate kinds for containsMixedTypes) for such columns, which made
the file unopenable in Excel, even when the column wasn't used in the pivot.
Add Stats::strings() (text, booleans, errors) and use it for
containsSemiMixedTypes, containsString and containsMixedTypes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid's look() and print's cell_look() layered a rule's fill, font and
borders onto the cell style but not its num_fmt, so a cell under a rule with a
number format kept its own. Take the rule's code when it isn't General (in a
differential style General means "not set", as layer_under treats it).
Print also re-formatted every non-General cell from its own style through
display::cell_text; format with the look's code instead, through a new
display::value_text_as that value_text now calls (CSV/Text output unchanged).
Show Formulas keeps cell_text.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Crostini's cros-host-fonts mounts the host's fonts under
/usr/share/fonts/chromeos (noto/, notocjk/, crosextra/), which none of the
fixed Linux path lists reached: Thai, Hebrew, CJK and Symbols 2 fell back to
egui's Latin-only faces and drew tofu with the fonts installed, and the UI and
cell roles missed Noto Sans and Carlito. Add those folders to each list, with a
structural test that every list probes them.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A line that is not a JSON object with a string method, including an HTTP
request line, bytes that are not UTF-8 or a line over 64 MiB, gets one
error reply and the connection closes before anything after it is read.
Request lines are read with a bound instead of buffering them whole.
A hostile file could abort any process that opens it: a cell formula of a
million `%` (`1%%%…`) or a 200,000-term sum parsed into trees deep enough to
overflow the stack when printed, saved, evaluated or dropped, even in release
builds. Postfix operators (`%`, invoking a result) built their nesting in a
loop that the 256-level limit did not see.
- `parse` refuses formula text longer than Excel's 8,192 characters
(`MAX_FORMULA_LEN`, counted in characters, the leading `=` excluded). This
also bounds the depth of the left-deep trees long operator chains build.
- Each postfix `%` and each call of a result counts towards the nesting limit.
- A file's formula that is refused keeps its text and cached value, as other
unparsable formulas do.
Regression tests: formula (small-stack thread: long `%`, invoke and operator
chains are errors; the limit counts characters, not bytes) and xlsx
(`hostile_formula_text_does_not_overflow_the_stack`: open, save and drop a
workbook with such formulas and a defined name on a 1 MiB stack). Both abort
with a stack overflow without the fix.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>