Compare commits

...
121 Commits
Author SHA1 Message Date
Dylan M. Taylor d3e8dd7226 Merge main into release 2026-11-04 21:08:15 -05:00
skylar taylor-barrick 919a6b58fe Merge pull request #2203 from Higgs32584/fix/2181-link-open-confirmation
link: require confirmation for programmatic link.open (#2181)
2026-11-05 00:30:08 +01:00
skylar taylor-barrick e8bdd0efd6 Merge pull request #2527 from RedBearAK/xml-node-cap
XLSX: the XML element cap counts only elements kept in the tree
2026-11-05 00:29:00 +01:00
Dylan M. Taylor 3162ac09f3 Pass the Linux signing key to the release workflow (#3380) 2026-11-04 17:45:51 -05:00
Dylan M. Taylor 632e4a1bc5 formula, calc: LET/LAMBDA names looked up through a map; ragged array literals refused before reserving (#3312) 2026-11-03 22:57:52 -05:00
Dylan M. Taylor e5f6fa8197 ui-egui: reference colours through a map; cap fill-character repeats per cell (#3311) 2026-11-03 22:57:23 -05:00
Dylan M. Taylor f77ed98cdb chart: a series without values takes no category-span entries (#3300) 2026-11-03 22:56:56 -05:00
Dylan M. Taylor 22c5dfe0df engine: protected sheets gate filter reapply, outline, line sizes and floating pictures; pivot destination resolved before the structure check; user-limited edit ranges stay locked (#3296) 2026-11-03 22:56:06 -05:00
Dylan M. Taylor cdd3a59dbc Treemap rows keep running aggregates; General-fit width is clamped (#3290) 2026-11-03 22:55:35 -05:00
Dylan M. Taylor bebe7de877 engine: cap header and footer sections at 64 KiB when fields expand (#3284)
The 10,000-character header spec was bounded, but each &A, &F or &Z
added the whole sheet name or path again, and a JSON workbook's sheet
name has no length limit. A field that would take a section past
64 KiB is now left out.
2026-11-03 22:55:07 -05:00
Dylan M. Taylor 4421bc6790 engine: weekday fill series moves by whole weeks (#3272)
edit.fillSeries with dateUnit "weekday" stepped one calendar day at a
time, so a large step cost work proportional to its size for every
cell. It now uses add_weekdays, which moves whole weeks arithmetically.
2026-11-03 22:54:17 -05:00
Dylan M. Taylor 017c80572c functions: match TEXTSPLIT/TEXTBEFORE/TEXTAFTER delimiters through an index, within a work limit (#3263)
* functions: match TEXTSPLIT/TEXTBEFORE/TEXTAFTER delimiters through an index, within a work limit

The delimiter matcher compared every delimiter, duplicates included, at
every position of the text. Delimiters are now deduplicated and grouped
by the character a match is anchored on, and a search that would
compare more than 100 million characters returns #VALUE!.

* functions: pass float literals in the delimiter test
2026-11-03 22:53:47 -05:00
Dylan M. Taylor a31178f519 functions: NUMBERVALUE strips trailing percent signs in one pass (#3262)
Each trailing % copied the rest of the text again, so a long run of
them made one call quadratic. The signs are now counted and cut off in
place.
2026-11-03 22:53:18 -05:00
Dylan M. Taylor 184c0ec84d ods: charge a cell's text when the cell is read, not when its row ends (#3257)
The importer kept every cell of a row and only charged their text to
the 64 MiB allowance when the row finished, so one row of long cells
could hold up to 16,384 MiB before being refused. Text is now charged
as each cell finishes; the row charges only its repeats.
2026-11-03 22:52:28 -05:00
Dylan M. Taylor 065f05dbec Security: MODE counts through an index, FREQUENCY bins by binary search (#3198)
* functions: MODE counts through an index, FREQUENCY bins by binary search

MODE/MODE.SNGL/MODE.MULT searched the list of distinct numbers seen so far
for every input, and FREQUENCY scanned the sorted bins for every data value,
so both were quadratic in computed arrays. MODE now keys counts by value
(-0 folded into 0, NaN never merged) and keeps first-occurrence order;
FREQUENCY finds the first bin at or above each value with partition_point
over the order it already sorts. Results are unchanged.

* address review: FREQUENCY keeps NaN data in the overflow bin and NaN bins empty
2026-11-03 22:51:58 -05:00
Dylan M. Taylor db05400e67 calc: list each dependent once when a change spreads (#3190)
* calc: list each dependent once when a change spreads

* calc: check the dependency closure against a walk of every cell

Random graphs of single-cell, narrow and wide range reads (some removed or unsettled), random seeds and changed areas: the closure with shared taken records and area lookups equals the one listing every dependent of every changed cell.
2026-11-03 22:51:29 -05:00
Dylan M. Taylor c0ea297615 calc: cap the text one & over arrays may build in all (#3189)
* calc: cap the text one & over arrays may build in all

* address review: concatenation over arrays stops at the byte budget without reserving the whole broadcast
2026-11-03 22:51:11 -05:00
Dylan M. Taylor 74111e5a93 check: charge relationship parts to the package's decompression budget (#3185)
* check: charge relationship parts to the package's decompression budget

* address review: charge bytes read before a package part decompression error
2026-11-03 22:50:40 -05:00
Dylan M. Taylor 014f030a15 engine: PivotTable filters test each record against a set of selected labels (#3279)
Every source record scanned the filter's whole selection, so a large
selection multiplied by many records was slow even when nothing passed.
Each filter's labels are lowercased into a set once per computation.
2026-11-03 18:10:48 -05:00
Dylan M. Taylor a305229287 PDF export: wrap text with running widths (#3208)
wrap_lines split an over-long word by re-measuring and copying the
rest of the word for every piece it cut, and only checked its line
limit once the word was done, so one long word in a narrow text box
cost word length x pieces. Each new word also re-measured the whole
line built so far.

Line and piece widths are now running sums of glyph widths (the same
sum text_width makes), so every character is measured once, and the
line limit is checked while a word is being split. The lines produced
are unchanged; a test compares them with the old wrapper.
2026-11-03 16:11:47 -05:00
Dylan M. Taylor df137b8ee8 xlsx: repair repeated sheet names through a set of assigned names (#3211)
Importing a workbook whose sheets share a name rescanned every sheet for
every suffix tried, so a file of repeated names opened in cubic time.
SheetNames keeps the assigned names and the next suffix per stem.
2026-11-03 10:53:20 -05:00
Higgs32584andClaude Opus 5.5 e12b462b90 Port the link.open confirmation check into the merged insert.rs (#2181)
Upstream folded cmd/insert/*.rs back into insert.rs, so the merge left
the policy check in a file that is no longer compiled. Restore that file
to upstream and apply the check in the live open_link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 10:45:50 -05:00
Brandon Thomas ed2347782d Merge PR #3252 (author's fix: an expanded formula bar leaves room for the worksheet)
# Conflicts:
#	crates/ui-egui/src/formula_bar.rs
2026-11-03 10:00:05 -05:00
Brandon ThomasandClaude Opus 5.5 865a789c73 Review fixes for #3223, #3253, #3254, #3252, #3239, #3244 and #3268
- #3223: sniff the first 64 KiB for a web page and skip JSON and files over the text-import cap, so
  every CSV/TSV is no longer decoded in full before the size check.
- #3253: SYLK relative offsets use checked arithmetic (no overflow on R[9223372036854775807]); at
  most 1024 column-width records are kept; regression test.
- #3254: a single selected merged cell searches the whole sheet, as one selected cell does.
- #3252: the formula bar's height cap is half the room below the ribbon, not half the window, so
  the worksheet stays visible in a short window (CodeRabbit Major).
- #3239, #3244, #3252, #3268: es/de/cs/zh/ko/ru/pt entries for the new interface strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:59:27 -05:00
Brandon Thomas 166234eff0 Merge PR #3268 2026-11-03 09:55:39 -05:00
Brandon Thomas 6c386f79ed Merge PR #3252 2026-11-03 09:55:39 -05:00
Brandon Thomas 5ae7ecfab9 Merge PR #3244 2026-11-03 09:55:39 -05:00
Brandon Thomas 0081e4f29e Merge PR #3239 2026-11-03 09:55:38 -05:00
Brandon Thomas ebf762a03a Merge PR #3254 2026-11-03 09:55:38 -05:00
Brandon Thomas 3bdfa6ed71 Merge PR #3253 2026-11-03 09:55:38 -05:00
Brandon Thomas d906bcbb6f Merge PR #3223 2026-11-03 09:55:38 -05:00
Brandon Thomas d7b53fcc20 Merge PR #3267 2026-11-03 09:55:37 -05:00
Brandon Thomas 31156f7949 Merge PR #3251 2026-11-03 09:55:37 -05:00
Brandon Thomas 502fac89fa Merge PR #3237 2026-11-03 09:55:37 -05:00
Brandon Thomas 56cc52eb39 Merge PR #3250 2026-11-03 09:55:37 -05:00
Brandon Thomas a6b7cf5c2a Merge PR #3243 2026-11-03 09:55:37 -05:00
Brandon Thomas 85f09f05d6 Merge PR #3240 2026-11-03 09:55:36 -05:00
Brandon Thomas cb4b7af1e3 Merge PR #3229 2026-11-03 09:55:36 -05:00
Brandon Thomas 0670337908 Merge PR #3231 2026-11-03 09:55:36 -05:00
Brandon Thomas 2e27cbbf05 Merge PR #3226 2026-11-03 09:55:36 -05:00
Brandon Thomas 0a12da6e52 Merge PR #3221 2026-11-03 09:55:36 -05:00
Brandon Thomas 5ff51dd142 Merge PR #3219 2026-11-03 09:55:36 -05:00
Brandon Thomas affae49e7c Merge PR #3216 2026-11-03 09:55:36 -05:00
Brandon Thomas 89d658406e Merge PR #3238 2026-11-03 09:55:36 -05:00
Brandon Thomas a10d220cea Merge PR #3234 2026-11-03 09:55:35 -05:00
Brandon Thomas b148f06e9c Merge PR #3228 2026-11-03 09:55:35 -05:00
Brandon Thomas b5d888d3cc Merge PR #3225 2026-11-03 09:55:35 -05:00
Brandon Thomas 76145982c5 Merge PR #3217 2026-11-03 09:55:35 -05:00
Hector VillarrealandClaude Opus 5.5 1a7fa68376 #1246: an expanded formula bar leaves room for the worksheet
Addresses review comment 4456174694: the drag/expand cap was half the
window, so in a 400 pt window a ~195 pt bar left the grid about 1.6 pt
under the status bar and sheet tabs. The cap is now also limited by
the room left under the ribbon, keeping the status bar, sheet tabs,
column headings and two rows below the bar.

Test: ui-egui formula_bar_expand::an_expanded_formula_bar_leaves_room_for_the_worksheet_in_a_short_window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 08:52:05 -06:00
Higgs32584andClaude Opus 5.5 fe062f57a6 Refuse synthetic control-channel input as OK in Open Hyperlink (#2181)
A ui.key Enter or ui.click on OK reached egui as ordinary input and
could confirm the openLink prompt. A pass that carries synthetic input
now keeps the prompt open and opens nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:48:20 -05:00
Higgs32584 94dd42ba31 Merge upstream main into fix/2181-link-open-confirmation 2026-11-03 09:45:59 -05:00
Hector VillarrealandClaude Opus 5.5 72a9c5b47d #2120: test that clearing notes and links keeps Allow Edit Ranges
Addresses the follow-up in review 5753664551: the protection test had
no range exception, so a regression that refused locked notes and
links inside an Allow Edit Range would have passed. The new test
clears a link in a range without a password, is refused a note in a
password range, then clears both once that range is unlocked.

Test: engine tests::clearing_links_and_notes_keeps_allow_edit_ranges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 08:21:50 -06:00
Hector VillarrealandClaude Opus 5.5 dfb3b1224b #2120: clearing links and notes respects sheet protection
Addresses review comment 4455670315:

- Clear Hyperlinks and Clear Comments removed links and notes on
  locked cells of a protected sheet: protection compares stored cells
  only, so their removal went unseen (for stored cells too, and now
  for unstored ones since the whole range is cleared). `clear` now
  refuses a locked cell's note or link with locked_cell_error, which
  keeps Allow Edit Ranges and session-unlocked ranges.
- A removed note or link on a cell with nothing stored wasn't added
  to the changed cells, so a recorded macro's footprint missed it.

Tests: engine tests::clearing_links_and_notes_respects_sheet_protection,
tests::clearing_links_and_notes_records_their_cells.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 08:08:07 -06:00
Hector VillarrealandClaude Opus 5.5 58e876a4d8 #2120: Remove Hyperlink clears links on cells with nothing stored
Addresses review comment 4455436875: `clear` clipped each range to the
sheet's used range (stored cells) before removing links and notes, so
a link from a file on an empty cell outside it was left in place, and
nothing was removed on a sheet with no cells. Notes and links are now
cleared over the whole range; cells and merges still only within the
used range.

Test: engine tests::clear_hyperlinks_removes_links_on_cells_with_nothing_stored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 07:52:14 -06:00
Daniele MoriandClaude Opus 5.5 d976bbdcba Point mode: keep the sheet qualifier when pointing with the arrow keys
point_move wrote a bare reference, so on another sheet Shift+arrow after a header
drag turned 'Other sheet'!B:D into B:E, which reads as the formula's own sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 14:48:29 +01:00
Daniele MoriandClaude Opus 5.5 ee6b9fdcee Options: keep the active row's band visible on a faded page
In Page Layout view the "Click to add data" pages are faded after their cells are
painted, which all but hid the band on them. The band goes on again over the fade.
Tests also cover Page Layout view and frozen panes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 14:31:35 +01:00
Daniele MoriandClaude Opus 5.5 b3bce6cc81 Point mode: Shift+arrow after a header drag moves the dragged end
After a drag over C:E, point_cell held C and no anchor was set, so Shift+Right
moved the wrong end and gave D:E. The drag now records the pointer's end as the
moving one and the opposite corner as the anchor, so it gives C:F, and rows work
the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 14:28:35 +01:00
Hector VillarrealandClaude Opus 5.5 2caf435a38 #2012: split a parsed sheet-qualified name at the last !, sheet as is
Addresses review comments 4455115540 and 4455115557. The parser stores
`'Sheet'!Name` as `Sheet!Name` with the sheet already unquoted:

- Evaluator::name split it at the first `!`, so a series named
  `'a!b'!Title` showed #NAME?.
- chart_name unquoted it again, trimming spaces, so a chart reference
  to `' Sales '!Data` was saved as `Sales!Data`.

Both now split at the last `!` and look the sheet up as is.

Tests: chart tests::a_series_named_by_a_name_on_a_sheet_with_a_bang_resolves,
xlsx tests::chart_book_names::chart_names_keep_spaces_around_a_quoted_sheet_name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 07:21:22 -06:00
Daniele MoriandClaude Opus 5.5 38a7067ca2 Options: highlight the active cell's row, as a global preference
On a wide table the eye loses its place following a row from the active
cell. File > Options > General gets "Highlight the active cell's row", off
by default: a tint across the whole row of the active cell, over the fills
and under the text, in Normal and Page Layout views. It is a preference for
every sheet and workbook, so nothing goes into the file, the workbook does
not become dirty and there is no undo step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:59:41 +01:00
Daniele MoriandClaude Opus 5.5 f4f69beec2 Point mode: dragging over row or column headers extends the reference
A press on a header while a formula waits for a reference inserted C:C, but the
drag that followed did nothing, so dragging from column C to E left C:C. The drag
now follows the pointer, as it does over cells, in either direction and for rows
too, keeping the sheet qualifier, the earlier arguments of a Ctrl press and the
far end of a Shift press.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:45:53 +01:00
Dylan M. Taylor 92cdc2a975 xlsx: rename repeated table headers without retrying handed-out suffixes (#3213)
table_columns restarted its suffix search at 2 for every duplicate
header, lowercasing and hashing every candidate already in use, so a
table of repeated headers was exported in quadratic time. The next
suffix to try is now remembered per lowercased base.
2026-11-03 06:45:15 -05:00
Hector ViovandClaude Opus 5.5 fe6ab7c5c0 #3101: clear the tab bar's cached rectangles when a control is hidden
Addresses review comment 4453674677: after a frame drew both controls,
turning off the horizontal scroll bar or the sheet tabs left
app.grid.hscroll / app.grid.tab_split holding the old rectangles.
sheet_tabs now clears both before any visibility return.

Test: tab_split::hiding_the_controls_clears_their_cached_rectangles
toggles both options on one live harness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 05:02:44 -06:00
Hector VillarrealandClaude Opus 5.5 2bdec6c0a1 #941: a window hidden while parked drops its key tip request; parity row names the badge-less keys
Addresses CodeRabbit review on #3250:
- "Clear parked keytip requests when their window becomes hidden": a
  window hidden from another window (View › Hide while it isn't in
  front) kept the request it had parked, and showing it again opened
  the control. follow_active_window now drops the waiting request of
  every parked window that is hidden.
- "Correct the no-badge description in the parity table": only Alt+I,
  D, V and T (no ribbon tab) paint no badges.

Test: legacy_keytips::a_window_hidden_from_another_window_drops_its_parked_request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:44:40 -06:00
Hector VillarrealandClaude Opus 5.5 533c0dadaf #2225: a field that only looks quoted keeps the tabs and line breaks in it
Addresses CodeRabbit review on #3231, "Reparse consumed separators when a
field is not quoted": parse_tsv read "a<TAB>b"x as one quoted field,
then fell back to its raw text without the separator it had consumed,
so the cells "a, b"x and c pasted back as two. The new copy (which no
longer quotes a cell holding only a quote) makes that text. A field
whose closing quote doesn't end it, or that never closes, is now read
again as unquoted text, so its tabs and line breaks separate cells.
The text read again is bounded by the input's length, so hostile input
stays linear.

Test: a_field_that_is_not_really_quoted_keeps_its_separators.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:19:49 -06:00
Hector VillarrealandClaude Opus 5.5 68e87f0a68 #941: a hidden window drops its waiting key tip request
Addresses CodeRabbit review on #3250, "Clear deferred key-tip requests
when parking a hidden window": a hidden window isn't drawn, so a request
it parked would fire whenever it was shown again (the Symbol gallery
opening long after the keys). Parking a hidden window now drops it; the
rest of its interface state is kept.

Test: legacy_keytips::a_hidden_window_drops_its_waiting_key_tip_request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:01:25 -06:00
Hector VillarrealandClaude Opus 5.5 2adfd5571f #1786: ink is translucent by its width in points, while drawn and once committed
Addresses CodeRabbit review on #3229, "Use the same translucency
threshold as committed ink": the preview judged the pen by its width in
points, the committed stroke by its width on screen, so at 50% a 12 pt
highlighter turned opaque once committed and at 200% a 6 pt pen turned
translucent. Both now use chartview::ink_color, which goes by the
stroke's width in points, so the two agree and neither changes with
the zoom.

Test: ink_render::ink_translucency_follows_the_pen_not_the_zoom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:58:23 -06:00
Hector VillarrealandClaude Opus 5.5 d964c1cb66 #941: a waiting key tip request stays with the window that made it
Addresses CodeRabbit review on #3250, "Keep deferred requests with their
window": the request waiting for the next frame was shared across
windows, so the root window, drawn first, could take a second window's
Alt+I, S. It is now parked with each window's interface state and moved
into the one-frame request when that window's ui begins.

Test: legacy_keytips::a_waiting_key_tip_request_stays_with_its_window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:39:43 -06:00
Hector VillarrealandClaude Opus 5.5 9ce599d978 #941: a control key tip expands a collapsed ribbon before it opens the control
Addresses CodeRabbit review on #3250, "Expand the ribbon for a same-tab
control request": with the Insert tab chosen but the ribbon collapsed,
Alt+I, S went to the same-tab branch and the Symbol gallery was never
drawn. A collapsed ribbon is now expanded like a tab switch, and the
request waits for the frame that draws the control.

Test: legacy_keytips::alt_i_s_expands_a_collapsed_ribbon_on_the_insert_tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:25:52 -06:00
Hector VillarrealandClaude Opus 5.5 3c72d5f7aa #3224: Find and Replace within the selected range
edit.find and edit.replace take within: selection, which searches only
the selected cells; Find Next then moves the active cell through them
and keeps the range selected, as Excel does. The Find and Replace
dialog uses it whenever more than one cell is selected (Excel searches
a selected range; one selected cell still means the whole sheet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:05:18 -06:00
Hector VillarrealandClaude Opus 5.5 6fcb94f7e3 #941: Alt+I, S opens the Symbol gallery instead of re-setting the active cell
Addresses CodeRabbit review on #3250, "Open a symbol picker instead of
re-parsing the cell": the legacy key ran insert.symbol with no symbol,
which set the active cell to its own text again (re-reading it: text
could turn into a number, a Python cell lost its code). It now routes to
the ribbon's Symbol control: a control key tip whose control is on
another tab shows that tab first and hands the request to the next
frame, when the control is drawn, and the Symbol button opens its
gallery on its key tip. Nothing is inserted until a symbol is picked.

Test: legacy_keytips::alt_i_s_opens_the_symbol_gallery_and_leaves_the_cell_alone
(keytips regenerated with cargo xtask keytips).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:03:58 -06:00
adifiedio e56a722bb4 Clipboard: refuse oversized pasted pictures before copying their pixels; document paste as desktop-only (#699) 2026-11-03 14:25:14 +05:30
Hector VillarrealandClaude Opus 5.5 94b4f6a3a2 #1481: open SYLK (.slk) files
A small reader for the SYLK text format: cell values (numbers, text,
booleans, errors), formulas converted from R1C1 to A1 as Excel does
(relative/absolute references, ranges, whole rows and columns, shared
formulas) with their cached results, number formats (P / F;P) and
column widths (F;W). Import only; saving as .slk says so. The sheet is
named after the file, and damaged records are skipped with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 02:51:39 -06:00
adifiedio ac27137cd8 Clipboard: paste a picture from the system clipboard at the active cell (#699)
Cmd/Ctrl+V dropped egui's PasteImage event, and the ribbon/menu Paste only read
text, so screenshots and images copied in a browser reported "Nothing to paste".
Both now encode the pixels as PNG (bounded to 16384 px per side, 64 Mpx) and run
insert.picture as a floating picture anchored at the active cell, as Excel does.
Text on the clipboard still wins. New host service: Services::paste_image (arboard).

Fixes #699, #3018, #3182.
2026-11-03 14:14:34 +05:30
Hector VillarrealandClaude Opus 5.5 dea1359c7e #1246: drag the formula bar's bottom edge to resize it
The bottom edge of the formula bar is a resize handle: dragging it sets
the height in whole lines (up to half the window; one line collapses
the bar). Expanding again (the chevron, Ctrl+Shift+U) returns to the
dragged height instead of a fixed five lines. The drag previews live
and commits once through view.expandFormulaBar {lines}, which is also
scriptable; the height is kept with the UI preferences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 02:41:57 -06:00
Hector VillarrealandClaude Opus 5.5 3c0749e685 #941: Excel 2003 menu access keys (Alt+I, R; Alt+D, S; ...)
Adds 43 legacy sequences to keytips.json (Insert, Edit, Format, Data,
View, Tools menus and File > Page Setup), each running the command or
dialog the modern key tip already uses. Alt+I, D, V and T are implicit
levels with no ribbon tab; badges are not painted under them, as in
Excel. The generator checks Options (a Backstage page) by its page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 02:31:45 -06:00
Hector VillarrealandClaude Opus 5.5 f9edafa800 #2120: a linked cell's menu edits, opens or removes its link
Right-clicking a cell with a link only offered "Link…", whose dialog was
titled Insert Hyperlink, so changing a link wasn't obvious. As in Excel,
the cell menu of a linked cell now offers Edit Hyperlink… (the same
dialog, filled with the link and titled Edit Hyperlink), Open Hyperlink
(as clicking the link: a reference selects its cell, a web or e-mail
address asks first) and Remove Hyperlink (edit.clearHyperlinks). A cell
without a link keeps "Link…".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:59:12 -06:00
Hector VillarrealandClaude Opus 5.5 a774d18350 #3089: the personal-information flag's doc names the XLSX writer
Addresses CodeRabbit review on #3238, "Limit the remove_personal_info
doc comment to XLSX saves": only the XLSX writer leaves out the author
and company; the workbook's own serialization still holds them. The
comment now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:53:43 -06:00
Hector VillarrealandClaude Opus 5.5 e89ba799e9 #2012: chart names and macro examples unquote sheet names like formulas do
Three places still split a `Sheet!Name` reference and stripped the
sheet's quotes by hand: the chart resolver (is a series name a defined
name?), the XLSX chart writer (the name's cache) and the macro editor's
example references. The first two left a doubled quote in the name, so
a series named `'Bob''s'!Title` (a name on the sheet "Bob's") wasn't
found and showed that text instead of the name's value; they also split
at the first `!`, so a quoted sheet holding one broke. All three now use
core's split_sheet, the one reading formulas use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:49:01 -06:00
adifiedio 56f0679fed Frozen panes: test the scrolled freeze-handle drag and the frozen text's painted extent (#2544)
Review follow-up: a column-handle drop on the last frozen column while
scrolled, which fails if freeze_boundary measures with x(c + 1), and a check
that frozen A1's text lies inside its clip, not only that the clip is A1.
2026-11-03 13:13:29 +05:30
Hector VillarrealandClaude Opus 5.5 c35ab0f4f4 #1482: every Paste Special option has its Excel access key
Paste Special only knew the letters F, V, T, C and E. Excel gives every
option one: A (All), N (Validation), X (All except borders), W (Column
widths), R (Formulas and number formats), U (Values and number formats);
O, D, S, M, I for the operations; B for Skip blanks; and L for the
Paste Link button, which pastes at once. One table now maps each letter
to what it does; Enter still pastes with the chosen options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:41:55 -06:00
Hector VillarrealandClaude Opus 5.5 846cc09d67 #3101: show or hide the sheet tabs and scroll bars of a workbook
Excel can hide the sheet tabs, the horizontal scroll bar and the
vertical scroll bar of a workbook window (File › Options › Advanced ›
Display options for this workbook), saved with the workbook. GridCraft
already read and wrote these settings (#3057) but always showed all
three, and had no command or option for them.

New commands view.showSheetTabs, view.showHorizontalScrollBar and
view.showVerticalScrollBar {on?} (toggling without `on`, undoable,
saved with the workbook), and a "Display options for this workbook"
section in File › Options. The tab bar follows them: without the tabs
the scroll bar takes the bar, without the scroll bar the tabs do, and
with neither the bar isn't shown; without the vertical scroll bar the
grid takes its width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:37:34 -06:00
Hector VillarrealandClaude Opus 5.5 48c52e14af #3166: a web page opens after a leading comment and keeps its non-breaking spaces
Addresses CodeRabbit review on #3223:
- "Skip leading HTML comments before the prefix check": a page starting
  with an exporter's <!-- comment --> was read as CSV text. Complete
  leading comments are now skipped before looking for the page's start.
- "Preserve nonbreaking spaces in HTML text imports": split_whitespace
  turned New&nbsp;&nbsp;York into "New York". The decoded cell text is
  used as is (ordinary whitespace is already collapsed), so the
  non-breaking spaces stay, as Excel keeps them.

Test: a_web_page_table_opens_after_a_leading_comment_and_keeps_nonbreaking_spaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:36:40 -06:00
Hector VillarrealandClaude Opus 5.5 f0557ec443 #3089: keep "remove personal information on save" and honour it
Excel's per-workbook privacy setting (Trust Center › Privacy Options ›
Remove personal information from file properties on save, set by the
Document Inspector) is saved as <workbookPr filterPrivacy="1"/>, and
while it is on Excel leaves the author and company empty on save.
GridCraft dropped the attribute, so a cleaned workbook came back with
the setting off, and it wrote the author and company back.

Workbook.remove_personal_info now keeps the setting (read and written
as filterPrivacy), and while it is on the writer leaves dc:creator and
Company out. Other properties are written as before. Note authors and
lastModifiedBy are the rest of the issue (the latter overlaps #881).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:25:29 -06:00
adifiedio 17f733a81e Frozen panes: frozen columns keep their text, gridlines and borders while scrolling (#2544)
The grid took a column's right edge to be where the next column starts,
`x(col + 1)`. At the freeze line the next column is in the scrolled pane
and moves with the scroll, so everything measured that way inside the
frozen columns shrank as the sheet scrolled right:

- text overflowing from a frozen cell was clipped at the scrolled
  column's edge, until only a character or two showed;
- the frozen columns' gridlines stopped short, with a stray vertical
  line drawn inside them;
- a merged range's borders under the last frozen column ended early;
- the freeze handle measured the last frozen column wrongly when
  dropped while scrolled.

Geo gains `x_end`/`y_end` (a column's or row's own far edge) and
`same_col_pane`, and every such measurement uses them. Text overflows
only into empty cells of its own pane. In Page Layout view the
gridlines now stop at each page's last column instead of running into
the margin.

Fixes #2544
2026-11-03 12:55:11 +05:30
Hector VillarrealandClaude Opus 5.5 914a76a9d8 #1442: Ctrl+. skips hidden corners and stays inside the selected area
Addresses CodeRabbit review on #3226, "Skip hidden corner destinations"
and "Keep merged destinations inside the selected area": with column D
hidden, Ctrl+. on B2:D3 made D2 active; with B1:C1 merged and column C
selected, it made B1 active, outside the selection. A corner in a hidden
row or column, or one whose merged cell starts outside the selected
area, is now passed over like a locked corner on a protected sheet.

Test: next_corner_skips_hidden_corners_and_stays_inside_the_selected_area.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:21:40 -06:00
Hector VillarrealandClaude Opus 5.5 e23994b7cb #3166: a web page's table keeps its characters, its shown text and its header formats
Addresses CodeRabbit review on #3223:
- "Decode HTML before parsing its cells": a page in Windows-1252 read as
  lossy UTF-8, so "Café" lost its é. The page is now decoded as a CSV is
  (BOM, UTF-8, else Windows-1252/1256); decode_text is exported for it.
- "Keep visible cell text when csv_as_text is enabled": opened as text, a
  cell with x:num took the recorded number ("4,50" became "4.5"). The
  recorded number is used only when values are read.
- "Preserve the imported format when styling a header": a date in a <th>
  lost its date format when made bold and centred. Header styling now
  builds on the style the CSV reader gave the cell.

Test: a_web_page_table_keeps_its_characters_text_and_header_formats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:17:35 -06:00
Hector VillarrealandClaude Opus 5.5 5acb4af789 #1442: Ctrl+. skips corners the sheet can't select and stops once on a merged corner
Addresses CodeRabbit review on #3226:
- "Check the next corner against protected-sheet selection rules": on a
  sheet protected without Select Locked Cells, Ctrl+. could make a locked
  corner active. It now goes on to the next corner it may select.
- "Keep the active cell at a merged cell's anchor": a corner inside a
  merged cell made that cell active instead of the merge's top-left
  cell. The corner now lands on the top-left cell, and corners in the
  same merged cell are one stop.

Selection::next_corner_by takes where each corner leads (or None to skip
it); next_corner keeps its plain behaviour.

Tests: next_corner_skips_corners_the_sheet_protection_does_not_let_you_select,
next_corner_stops_once_on_a_merged_corner_at_its_top_left_cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:08:01 -06:00
Hector VillarrealandClaude Opus 5.5 c121fb5901 #2809: keep a workbook's R1C1 reference style through open and save
A workbook saved by Excel in R1C1 reference style says so in
<calcPr refMode="R1C1"/>. GridCraft didn't read the attribute, so
opening and saving such a file switched it back to A1 style for the
next person who opened it in Excel. The calculation settings now keep
it (CalcSettings.r1c1, serde default false) and the writer puts it back;
A1 stays the default and isn't written. GridCraft itself still shows
and takes A1 references; the R1C1 display and entry are the rest of
the issue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:07:29 -06:00
Hector VillarrealandClaude Opus 5.5 fe965e7581 #2225: copy quotes only cells with a tab or line break
Copy wrapped any cell that held a double quote in quotes (and doubled
its quotes), so copying `say "hi"` into another app gave
`"say ""hi"""`. Excel quotes only text with a tab or line break; a cell
with just quotes goes on the clipboard as it is.

Pasting text read a field that starts with a quote as quoted even when
its closing quote doesn't end the field, so `"hi" there` (which Excel
copies as it is) pasted as `hi there`. A field is now quoted only when
its closing quote is followed by a tab, a line end or the end of the
text; otherwise it is the text as written, as Excel pastes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:02:40 -06:00
Hector VillarrealandClaude Opus 5.5 2fe3bfc077 #1786: the live pen stroke is drawn as the ink it commits
While dragging with a pen, the stroke under the pointer was always
painted blue at 2 pt, whatever pen or highlighter was chosen, so the
preview and the committed ink disagreed (a highlighter previewed as a
thin blue line). The preview now uses the colour and width draw.stroke
stores (pen blue and 2 pt by default, 0.5-40 pt, whole points) and the
translucency Ink shapes are painted with above 8 pt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:59:09 -06:00
Hector VillarrealandClaude Opus 5.5 49f2ef515b #1609: write page margins as their decimals
The page margins are kept as f32 and were widened to f64 when written,
so a 0.3-inch margin was saved as left="0.30000001192092896" (and 1.1
as 1.100000023841858). Each margin now goes through its shortest
decimal form first, so the file says 0.3, as Excel writes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:52:58 -06:00
Hector VillarrealandClaude Opus 5.5 8b2550c9cf #1442: Ctrl+. moves to the next corner of the selection
Excel's Next Corner of Selection (Ctrl+., Control+. on a Mac) moves the
active cell clockwise to the next corner of the selected area (top-left,
top-right, bottom-right, bottom-left) and keeps the area selected; the
new active cell is also the anchor, so Shift+arrows then move the
opposite corner. GridCraft had no such command.

New command selection.nextCorner (Selection::next_corner) bound to
Ctrl+. on every platform. A one-row or one-column area goes end to end;
a single cell stays where it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:44:15 -06:00
Hector VillarrealandClaude Opus 5.5 d5c892f238 #2630: mark the checkbox mapping bag with its extRef
A workbook with checkboxes saved by GridCraft made Excel repair it
("Repaired records: Format from /xl/styles.xml (Styles)") and drop every
checkbox. The feature property bag part GridCraft writes for checkboxes
left out extRef="XFComplementsMapperExtRef" on the XFComplements bag,
the bag the cell formats' xfComplement indexes into. With it, Excel
opens the file without repairing and shows the checkboxes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:37:00 -06:00
Hector VillarrealandClaude Opus 5.5 ef8ac5e6db #1456: GROUPBY refuses oversized subtotals before building them, without recursion
Addresses CodeRabbit review on #3221, "Check the output size before
with_subtotals builds subtotal lines": the size limit was checked only
after every subtotal line existed. A row with 4000 key columns at total
depth 4000 nested with_subtotals 4000 levels deep and overflowed the
stack (the process aborted) before the late check could refuse it.

with_subtotals now works without recursion: each line gets an id for its
key prefix at every level, a stable sort by those ids keeps each group
together in first-appearance order, and one walk adds the subtotals.
The output size (with the header and total lines when present) is
checked while the groups are counted, before any subtotal line is built.

Test: groupby_subtotals_past_the_size_limit_are_refused_before_they_are_built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:36:42 -06:00
Hector VillarrealandClaude Opus 5.5 ba1c62469d #3136: a move on another sheet keeps the active sheet's selection
Addresses CodeRabbit review on #3216, "Keep the selection on the sheet it
describes": moving Two!B1:B2 with Sheet1 active selected A1:A2 on
Sheet1, where nothing moved. The selection belongs to the active sheet,
so range.move now keeps it when the moved cells are on another sheet; a
move on the active sheet still selects the moved cells.

Test: drag_and_drop_on_another_sheet_stays_on_that_sheet (now also
checks the selection).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:31:59 -06:00
Hector VillarrealandClaude Opus 5.5 44460f23cf #3166: open a web page's table as a sheet, also when it is named .xls
Web apps' "Export to Excel" often writes an HTML table and names it
.xls. GridCraft read such a file as CSV text, so it opened as lines of
markup in column A. Excel opens the table (after warning that the
format and the extension don't match).

Opening a file whose bytes are a web page with a table now reads the
first table as a sheet, through the clipboard HTML table reader: the
cells go through the CSV reader, so values read as they do in a CSV
(and stay text with csvAsText); <th> cells are bold and centred; the
sheet takes the page's <x:Name> or the file's name. A page that isn't
named .htm/.html opens with a warning saying what it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:27:49 -06:00
Hector VillarrealandClaude Opus 5.5 044599c6ef #1448: a legacy "Count Numbers of" caption follows a new function
Addresses CodeRabbit review on #3219, "Recognize the legacy Count
Numbers caption when the function changes": workbooks saved before
Count Numbers was captioned "Count of" carry "Count Numbers of Val"
(optionally with a number suffix). pivot.valueSettings now treats that
as an automatic caption of a Count Numbers field, so changing the
function renames it; a custom name stays.

Test: legacy_count_numbers_caption_follows_the_function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:27:25 -06:00
Hector VillarrealandClaude Opus 5.5 6874e0b689 #3136: range.move pastes on the range's sheet and asks before replacing spilled values
Addresses CodeRabbit review on #3216:
- "Pass the resolved sheet to the paste step": a sheet-qualified move
  (`Two!B1:B2` with Sheet1 active) was checked on Two but pasted on the
  active sheet, replacing its data unasked. range.move now calls
  paste_special with the range's sheet.
- "Check spilled values in the drop destination": cells showing a dynamic
  array's spilled values hold no cell of their own, so the drop replaced
  them without the prompt. They now count as data, as in Excel
  (ISBLANK of a spill cell is FALSE, COUNTA counts it).

Tests: drag_and_drop_on_another_sheet_stays_on_that_sheet,
drag_and_drop_asks_before_replacing_spilled_values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:13:27 -06:00
Hector VillarrealandClaude Opus 5.5 1fd10711a5 #1456: GROUPBY total depth 2 or more adds subtotals
GROUPBY's total_depth only ever added one Total line. In Excel a depth
of N adds a subtotal line for each group of the first N - 1 key columns
(after the group, or before it when the depth is negative) and labels
the last line "Grand Total"; a depth greater than the number of key
columns, or above 1 with field_relationship 1 (a flat table), is
#VALUE!. The subtotals aggregate the group's source rows, so AVERAGE and
COUNT subtotals are right too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:10:34 -06:00
Hector VillarrealandClaude Opus 5.5 7bea3d1d6a #1448: PivotTable ranks are dense; Count Numbers is captioned "Count of"
Show Values As › Rank Largest to Smallest counted every larger sibling,
so ties skipped ranks: 5, 4, 3, 3, 4 ranked 1, 2, 4, 4, 2 where Excel
gives 1, 2, 3, 3, 2. It now counts the distinct larger values.

A new Count Numbers value field was captioned "Count Numbers of Units";
Excel captions it "Count of Units", as GridCraft's XLSX reader already
names a value field saved without a name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:06:14 -06:00
Hector VillarrealandClaude Opus 5.5 283789c1f4 #2339: write the XLDAPR extension URI in lower case next to rich values
A workbook with an in-cell picture and a dynamic-array formula wrote
the XLDAPR futureMetadata extension URI in upper case
({BDBB8CDC-…}), and Excel refuses such a workbook. The metadata part
written for dynamic arrays alone, which Excel opens, spells the same
URI in lower case; the rich-value writer now does too.

Together with #1533 (content types and the XLRICHVALUE URI) this lets
Excel open a workbook with an in-cell picture and a spilled formula.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:54:23 -06:00
Hector VillarrealandClaude Opus 5.5 5361e2c08a #3136: ask before a drag and drop replaces data
Dragging the selection's border onto cells that hold data replaced them
without asking, so dragging column B onto column A silently destroyed A.
Excel asks "There's already data here. Do you want to replace it?" first
(its "Alert before overwriting cells" option, on by default).

The drop now runs a new `range.move {range?, to, overwrite?}` command:
a cut and paste that returns a confirm question when the destination
holds data other than the moved cells; the grid shows it with OK and
Cancel, and OK runs it again with `overwrite: true`. A refused move
leaves the sheet and the clipboard as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:43:02 -06:00
Dylan M. Taylor 178f3eb745 Protection: authorize the sheet a column move changes, and refuse edits that unlock locked cells (#3196)
- sheet.moveColumns resolved its target sheet from `sheet`, or a
  `Sheet!` range or cell, but its two protection checks were passed
  only the `sheet` parameter, so a qualified range moved columns of
  a protected sheet while the checks authorized the active one. Both
  checks now get the resolved sheet index.
- check_protection compared only cell content, so a sort or duplicate
  removal moving an unlocked cell with equal content onto a locked one
  left the content as it was but made the cell editable. The commit
  check now also refuses a change that leaves a previously locked cell
  unlocked (effective lock, row and column formats included).
2026-11-02 23:45:20 -05:00
Dylan M. Taylor e8ccb5eb15 check: find volatile defined names with a worklist instead of repeated passes (#3187) 2026-11-02 23:33:31 -05:00
Brandon Thomas e1604d1791 Merge PR #3132 2026-11-02 22:36:51 -05:00
Brandon Thomas 74d9641e25 Merge PR #3173 2026-11-02 22:33:26 -05:00
ljkx 342f4b401d Check list validation against the list before the entry
A source that takes in the cell being checked (=$A1:$A2 on A2:A3 is A2:A3 at A3) listed the value being typed, so any new value passed. The list is now read from the workbook as it was before the entry, as in Excel.
2026-11-03 04:26:30 +01:00
Brandon Thomas 95d9ed1234 Merge PR #3161 2026-11-02 22:21:00 -05:00
Brandon Thomas 3d3ff84087 Merge PR #3179 2026-11-02 22:21:00 -05:00
ljkx 179664610c Read relative list validation sources from the cell being checked
A list source such as =$B1:$B2 is written for the rule's top-left cell.
list_items evaluated it there for every cell, so the dropdown and the
check showed and accepted the top-left cell's items everywhere. It now
takes the cell and shifts the source to it, as Excel does.
2026-11-03 03:23:48 +01:00
ljkx fb6dc688bc Rewrite rule formulas for the new top-left cell when cells are removed from a rule
Clearing validation from cells (or setting a new rule, or pasting a
validation over them) and Clear Rules on selected cells shrink a rule's
areas. When that removes the rule's top-left cell, the relative formulas
were left written for the old cell, so the cells left checked other
cells: clearing A1 off a custom `A1>10` on A1:A9 made A2 check A1.

Rewrite the relative references of validation formulas, and of
conditional format expressions and comparison values, for the new
top-left cell. Absolute references and literal lists stay as they are.
2026-11-03 00:18:50 +01:00
Aimar Mendez e5f265bb74 Ribbon test: pin macOS for the AutoSum tooltip, so it passes on a Mac
Since #2729 the ribbon picks a tooltip's shortcut by the OS egui reports
(`ctx.os()`), which egui_kittest pins to Nix unless told. The test still
chose the expected text with cfg!(target_os = "macos"), so on a Mac it
looked for "AutoSum (⌘⇧T)" while the harness drew "AutoSum (Alt+=)",
and `cargo xtask ci` failed there while the Linux gate stayed green.

The harness now pins macOS and the test expects the ⌘⇧T tooltip on every
host; widgets::tests already covers the Windows/Linux "Alt+=" text.

Verified on macOS 27 (arm64): `cargo xtask ci` passes all 10 steps (with
CRAFT_FONTS_DIR at the gate's craft-fonts commit); on main (7bb397a3)
it fails at test with "tooltip with the shortcut".
2026-11-02 22:19:21 +00:00
Neil Cawse eea1fc276f Charts: decimate area top and baseline at paired horizontal indices 2026-11-02 17:52:23 +00:00
ljkx adcb66697c Data validation and conditional formats: anchor relative formulas on the top-left cell of all areas (#3152)
Excel writes the relative formulas of a multi-area rule for the top-left
cell of the whole sqref (lowest row, lowest column), whatever order the
areas are listed in. GridCraft anchored them on the first area listed, so
a validation on `H62:H68 H26:H27 H93:H95` with `MOD($H26,$F26)=0` checked
row 26 - 36 at H26 and refused valid input.

Add `RangeRef::top_left` and use it in validation checks, list sources,
pasting a validation, conditional format lookups and the formulas the
xlsx writer generates for text, blank and error rules.
2026-11-02 18:29:23 +01:00
Ken KölzerandClaude Sonnet 5.5 de254a0a6e Ribbon: Font Size value is centered in its box (fixes #3155)
The size box draws its value with a frameless TextEdit. The theme floors
a text edit at interact_size.y (22 pt), and egui's text edit style aligns
its text to the top of that, which wins over the child layout's
Align::Center. The digits sat 3.5 pt above the middle of the 24 pt box,
4 pt above the Font name's baseline. The text edit now aligns its text
LEFT_CENTER.

The new test checks that the value is centered in its box and on the
Font name label's baseline. It fails without the change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 14:41:27 +01:00
Higgs32584andClaude Opus 5.5 9ac6b67e18 Merge origin/main into fix/2181-link-open-confirmation
Port the #2181 link.open confirmation changes into main's split files:
cmd/insert.rs -> cmd/insert/links.rs (open_link), dialogs.rs -> dialogs/show.rs
(OK handling, open_confirmed_link), grid.rs -> grid/interact.rs (follow_link doc).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:54:30 -04:00
Michael Higgins de0c5a0d96 Merge branch 'storytold:main' into fix/2181-link-open-confirmation 2026-10-26 18:00:36 -04:00
Michael Higgins 40929d80c5 Merge branch 'main' into fix/2181-link-open-confirmation 2026-10-25 10:21:07 -04:00
RedBearAKandClaude Fable 5.1 0b7deaa3d8 XLSX: the XML element cap counts only elements kept in the tree
`parse_streaming` counts every element it attaches to a parent against
MAX_XML_NODES, but the cells of a worksheet are attached to their `row`
while the row is open, and the row is then handed to the callback and
dropped. So the count grew with every cell of a sheet although the tree
never held more than one row, and a workbook with a sheet of about one
million ordinary cells (12,274 rows by 103 columns) was reported damaged
after 6,310 rows since #2413. Shared strings, streamed by `si`, had the
same exposure: a table of about a million plain strings, or fewer
rich-text ones.

The count now returns to its value at the start of a split element when
that element is handed out, so a streamed part of any size passes while a
part that keeps a million elements in its tree is still refused.
Regression test: 5,001 streamed rows of 100 cells are read whole; the same
part kept in a tree is refused.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 17:45:15 -08:00
Higgs32584andClaude Opus 5.5 594557e000 Merge origin/main into fix/2181-link-open-confirmation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:57:34 -04:00
Higgs32584andClaude Opus 5.5 79214fd287 link: require confirmation for programmatic link.open (#2181)
link.open is reachable by the control channel, CLI, MCP and workbook
scripts, so it could open https://attacker/?d=<cell data> without the
user seeing it. By default (LinkPolicy::Confirm) it now refuses web and
e-mail addresses with an error and opens no dialog; references still
navigate and other schemes stay refused. A host may opt in with
LinkPolicy::Allow (Rust API only, no command sets it).

The UI's Open Hyperlink prompt no longer runs a command on OK: it calls
Session::open_confirmed_link with the address it showed, kept in a
private field, so ui.dialog.confirm can't press OK and ui.dialog.set
can't swap the address behind the prompt text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:50:07 -04:00

Diff Content Not Available