Commit Graph
2134 Commits
Author SHA1 Message Date
PranavandFredApps 71b130be2d Data: Solver Load/Save Model and Reset All (#3086)
* Data: Solver Load/Save Model and Reset All

data.solverSaveModel writes a model (the sheet's saved model, or one
given as data.solver params) down a column of its sheet as formulas:
the objective and goal (=MAX(...), =MIN(...) or =cell=value), the
variable cells (=COUNT(...)), one =AND(relation) per constraint, which
shows TRUE while the constraint holds, and the method and its options
as an array constant. It refuses a range that overlaps the model's
cells or runs off the sheet.

data.solverLoadModel reads such a range (or reads down from its first
cell), checks the model and makes it the active sheet's model.
data.solverReset (Reset All) removes the active sheet's model. Each is
one undo step.

The Solver Parameters dialog has Reset All and Load/Save buttons; the
dialog can also be opened with fields that go over the saved model.

* Data: Solver Reset All also clears a model read from workbook-level names

A sheet's Solver model can come from workbook-level names, which every
sheet without a model of its own reads. Reset All removed only the
sheet-level names, so such a model stayed and the dialog showed it
again.

Removing the workbook-level names would change every other sheet that
reads them. Reset All instead leaves them in place and writes an empty
sheet-level solver_opt over them on the active sheet only; the result
says masked: true. saved_model now reads a sheet's own names when the
sheet has a solver_opt, and the workbook-level names only otherwise,
never a mix of both, and treats an empty solver_opt as no model. The
mask survives XLSX and is replaced when a model is saved on the sheet.

* Data: Solver Save Model clears the rest of a longer model saved at the same cell

Saving a model over a longer one saved at the same cell left the old
model's last rows on the sheet, so Load Model from that cell read the new
options row followed by stale constraint rows and refused the range.
Save Model now clears what is left of a model saved there before (only
when those cells hold a complete model written by Save Model, and never
the new model's own cells), keeping the cells' formatting, in the same
undo step. Load Model reading down from one cell also stops at the
options row, so formulas just below a saved model no longer break it.

* Data: Solver Load Model keeps the Evolutionary convergence and generations

Save Model writes Convergence and the generations without improvement in
its options row and Load Model reads them, but the sheet's saved model
(hidden names) did not hold them, so after Load Model, or any solve,
they fell back to their defaults. The saved model now keeps them for the
Evolutionary method: Convergence as solver_cvg, which means the same in
Excel, and the generations as gridcraft_solver_stall, because Excel's
solver_mni is a time in seconds. Saving a model again and Reset All
remove the GridCraft name with the solver_* names.

* Data: Solver Load Model refuses a range with a gap

Load Model from a range of several cells kept only the cells that hold a
formula, so the rows below a cell without one moved up. A constraint cell
overwritten by a constant gave a model without that constraint, and an
empty cell before the options row gave the default options, both without
an error. Load Model now refuses a range in which a cell before the last
formula holds no formula, and names that cell. Empty cells after the
model are still allowed.

* Data: Solver removes only its own names when it saves or resets a model

Saving a model (by a solve or Load Model) and Reset All removed every
name of the sheet that starts with solver_, so a user's own name such as
solver_budget was deleted with the model. They now remove only the
Solver's names: the ones Excel's Solver writes (solver_opt, solver_adj,
solver_lhs1, solver_rel1, solver_rhs1 and so on) and GridCraft's own.
The names are matched by name rather than by the hidden flag, so a model
whose names lost that flag is still replaced in full.

* Data: Solver Save Model refuses a place where the options cannot spill

The options row that Save Model writes is an array constant of eight
values, which spills into the seven cells to the right of its first
cell. Content in any of those cells blocked the spill and the options
cell showed #SPILL!. Save Model now checks the whole spill range before
it writes anything and refuses, naming the cell, when a cell there holds
content or another formula's spilled values or is a cell of the model,
when merged cells or a table meet the row (a dynamic array cannot spill
into a table), or when the range runs past the last column. It also
refuses a place where the model's column would cover another formula's
spilled values, which that formula would lose to #SPILL!. The options of
a model saved at the same cell before may be replaced, as before.

* Data: Solver Reset All and Load Model drop the old model's options

Reset All cleared the model fields of the Solver Parameters form but
kept the Evolutionary options (Convergence, Mutation Rate, Population
Size, Random Seed, generations without improvement and Require Bounds),
and Load Model kept any of them that the loaded model omits, such as all
of them for a GRG Nonlinear or Simplex LP model. A new Evolutionary model
then solved with the old options, for example with Require Bounds off.
Both now remove the model's options from the form first (one list of
their keys), so an option the model doesn't set takes its default.

* Data: Solver Save Model asks before it replaces data

Save Model wrote its formulas over whatever the model range held.
It is now refused, with the sheet unchanged, when a cell of the
range holds a value or a formula (a model saved there before
included), unless `overwrite: true` is passed. The message names
the first such cell and carries the same "already holds data"
phrase as Data Analysis, so the Solver Parameters dialog asks
"Replace it with the model?" with Replace and Don't Replace. The
question belongs to the range it asked about: editing the range,
Load or Reset All drops it. The check runs after the spill checks,
so the question is asked only when replacing would succeed. A save
remains one undo step.

Tests: the engine test refuses a value, a formula and an earlier
model, accepts only a true boolean, and undoes and redoes the
replacing save in one step; the dialog test covers Don't Replace,
an edited range and Replace. Both fail without the change.

* Data: Solver Load Model and Reset All tests for limits and undo

Tests for behaviour already in place: reading down from one cell of
a column of 5,000 formulas stops after the most rows a model takes,
a range larger than a model is refused before any cell is read, and
malformed or hostile cells and ranges (unbalanced brackets, an
overflowing number, an unknown sheet, 10,000 nested brackets, a
non-text `from`) each give an error and leave no model. Undoing
Reset All brings the model back with its options (the population
size).

* Data: Solver Load Model refuses a model without its options row

Load Model from one cell read down to the first cell without a
formula, and a range dropped its trailing cells without formulas.
A constraint cleared in the middle of a saved model, or its options
row cleared or replaced by a constant, therefore left a prefix of
the model, which was loaded as the whole model with default options
and replaced the sheet's model. Every load now requires the model's
last row to be the options row ({...}) that Save Model writes last;
otherwise it is refused, naming the cell without a formula when
reading down from one cell, and the sheet's model stays as it was.

The test clears a middle constraint, clears the options row and
replaces it by a constant, loads from the first cell and as two
ranges, and checks after each refusal that the old model (three
constraints, Simplex LP) is unchanged. It fails without the change.

* Data: Solver Load Model refuses an options row cut short

The options row of a saved model was read leniently: a missing value
or one that isn't a number took its default, and values after the
sixteenth were not read at all. A row cut short or edited by hand,
such as ={1}, therefore loaded the model with default options in
place of the saved ones (here: GRG Nonlinear in place of Simplex
LP). The row must now hold at least the eight numbers Save Model
writes (method, non-negative, population size, mutation rate, random
seed, require bounds, convergence, generations without improvement),
and every value in it, however many, must be a number; otherwise
Load Model is refused and the sheet's model stays as it was. Only
the first sixteen values are kept. More numbers than eight are still
read, so a row with options a later version adds loads too.

The test loads rows of one value, seven values, a text value among
the eight, after them and after the sixteenth, and a TRUE, from the
first cell and as a range, checking the old model after each
refusal, and a row of ten numbers, which loads. It fails without the
change.

---------

Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-01 20:13:00 +01:00
aiongg 29b17a6ff8 Merge pull request #467 from prxnvt/pr/function-library
UI: Formulas › Function Library as labelled buttons with category menus; Recently Used and Insert Function categories
2026-11-01 13:44:57 -05:00
FredApps e1e72426e5 Fix merged keytip sequence count for HBJ and MR 2026-11-01 19:09:04 +01:00
FredApps dc095444f2 Merge current main into PR2597 for hosted validation 2026-11-01 19:06:22 +01:00
FredApps 10b763e7a4 Merge current main into PR467 and preserve both documentation histories 2026-11-01 19:06:22 +01:00
Brandon ThomasandClaude Opus 5.5 ea1d52fe8f Review fixes for #3042: "" with parse: false keeps the cell's formatting; correct the write_range parse description
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:20:54 -05:00
Brandon Thomas 653227fe6e Merge PR #3077 2026-11-01 12:20:06 -05:00
Brandon Thomas e129c072a1 Merge PR #3075
# Conflicts:
#	crates/xlsx/src/read.rs
#	docs/file-format-parity.md
2026-11-01 12:20:06 -05:00
Brandon Thomas bc173ead8f Merge PR #3062
# Conflicts:
#	ROADMAP.md
#	docs/file-format-parity.md
2026-11-01 12:19:55 -05:00
Brandon Thomas 7f1574b573 Merge PR #3052
# Conflicts:
#	docs/file-format-parity.md
2026-11-01 12:19:55 -05:00
Brandon Thomas 6a3aa58a07 Merge PR #3043
# Conflicts:
#	ROADMAP.md
2026-11-01 12:19:55 -05:00
Brandon Thomas e6a8d4a8b6 Merge PR #3042 2026-11-01 12:19:44 -05:00
Brandon Thomas 49ed44cb02 Merge PR #3040 2026-11-01 12:19:44 -05:00
Brandon Thomas 229b6001ec Merge PR #3083 2026-11-01 12:18:01 -05:00
Brandon Thomas 1975360120 Merge PR #3050 2026-11-01 12:18:01 -05:00
Brandon Thomas 81ee96fa45 Merge PR #3041 2026-11-01 12:18:01 -05:00
scks1 60a267b4ef #3057: select visible workbook views and normalize tab ratios 2026-11-01 08:06:44 -05:00
Harrison SmithandClaude Opus 5.5 a1e0383056 #2871: unwrap Google Sheets' wrapper only when GridCraft calculates the formula
A formula calling a Google-only function (GOOGLETRANSLATE, QUERY,
ARRAYFORMULA...) opened as #NAME? where the wrapper had shown the cached
value, as Excel does. The XLSX reader now unwraps a cell formula only when
every function inside is known: read_xlsx_with takes the predicate (the
xlsx crate can't depend on the function library) and the engine passes
gridcraft_calc::is_known_function. Unwrapping stays in the reader so shared
formulas are shifted from the unwrapped master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:08:40 -06:00
aiongg a30644c872 fix(quit): bring the asking window to the front on a Dock quit
Quit from the Dock reaches GridCraft while it is behind other apps, hidden or minimized. The Save Changes question opened in the workbook's window, but nothing brought that window forward when it was already the active one, so the app looked stuck. The window is restored, shown and focused in that order, since macOS ignores Focus for a minimized window or a hidden app.
2026-11-01 04:26:47 +00:00
Harrison SmithandClaude Opus 5.5 1c7e9a2f90 #2964: compare menu item params as JSON values; test non-menu prefixes
Review follow-up. item_letter_with compared the compact params string, so a key
order or spacing difference between keytips.json and the caller would miss the
item. It now takes a serde_json::Value and compares parsed values. Tests add
prefixes that must not open the Borders menu (a finished sequence, another
group's level, another control) and an unknown preset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:45:37 -06:00
Harrison SmithandClaude Opus 5.5 2b4a9fc0f3 #2964: give Inside Borders a key tip (HBJ)
Review follow-up. Inside Borders is GridCraft's own Borders menu item (Excel's
menu has none), so it had no letter and was the one preset the keyboard
couldn't reach from the open menu. It takes J, unused under HB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:40:56 -06:00
Harrison SmithandClaude Opus 5.5 c55a7e35aa #2964: Alt, H, B opens the Borders menu with its item letters
At the "HB" level every Borders sequence (HBA..HBW) shares the prefix, so
control_badge drew no badge and nothing opened: after B the screen showed no
key tips at all, although the next letter still applied its preset. The
Borders menu now opens at its group level, as Excel's does, with each preset's
letter (told apart by its params) and More Borders' M. A letter or Esc changes
the prefix, which closes the menu as before; clicking the arrow still toggles it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:48:37 -06:00
Harrison SmithandClaude Opus 5.5 2c0a00fbae #2871: open Google Sheets' DUMMYFUNCTION export wrapper as the original formula
Google Sheets' XLSX export writes a formula that uses a function it thinks
Excel lacks as IFERROR(__xludf.DUMMYFUNCTION("<formula, quotes doubled>"),
<cached value>). GridCraft showed the wrapper as the formula.

fmla::from_file, which every cell formula read goes through (plain, shared
master and t="array"), now recognises exactly that shape: IFERROR (with or
without _xlfn.) of a single-string __xludf.DUMMYFUNCTION call, any case.
If the string parses, the formula inside replaces the wrapper, as the whole
formula or nested in a larger one (parenthesised unless it is a single
operand). The cell keeps the file's cached value.

Bounded on hostile input: inner text over MAX_FORMULA_LEN is not parsed,
only one level is unwrapped, and a result that no longer parses (nesting
past the parser's limit) keeps the file's formula. Text that doesn't parse
keeps the wrapper.

Saving writes the real formula with Excel's _xlfn. prefixes, as Excel does
with the _xlfn functions it knows. Keeping cached results for functions
GridCraft can't calculate is #2770 and not part of this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:19:20 -06:00
Simon Koch-Sultan 9276827b1a xlsx: keep the workbook view settings across an open + save (#3057)
The reader took only activeTab from <workbookView> and the writer wrote
that alone back, so a workbook Excel saved with the sheet tabs and both
scroll bars hidden (and a non-default tab split) opened with all three
shown and lost the settings on save.

Workbook gains a view field (WorkbookView: show_sheet_tabs,
show_horizontal_scroll, show_vertical_scroll, tab_ratio; the format
defaults are shown/shown/shown/600). The reader fills it from the
<workbookView> attributes; the writer emits each attribute only when it
differs from the default, so an ordinary workbook file is unchanged.

Regression test: workbook_view_settings_survive_a_round_trip reads a
package carrying the hidden settings, checks the model, and checks the
written workbook.xml keeps all four attributes (and that a default
workbook still writes the bare element).

Scope: this is the data-loss half of #3057. The app does not act on the
settings yet and File > Options has no controls for them; that is the
other half the ticket was split into.
2026-10-31 15:31:07 -04:00
Daniel LubrykaandClaude Sonnet 5.5 d25445f4e3 M2.1: document the .xls style helpers
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 19:14:33 +01:00
Daniel LubrykaandClaude Sonnet 5.5 879ea7385f M2.1: test that .xls fonts keep their family and charset
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 19:01:26 +01:00
Daniel LubrykaandClaude Sonnet 5.5 f2a75eca1b M2.1: keep BIFF8 font family and charset when importing .xls fonts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 18:47:15 +01:00
Zarvinx 9654c2030a #3035: assert Copies updates before Escape 2026-10-31 18:21:13 +01:00
Zarvinx f2bef898e8 #3035: let Esc leave File-page fields before closing 2026-10-31 18:09:27 +01:00
Max Murphy 85bfe358dc Ribbon: each window keeps its own tab, so Table Design stays active (#2903) 2026-10-31 16:58:14 +01:00
Max Murphy 72b1310a5a #2765: CSV and text open detect each column's date order 2026-10-31 16:57:58 +01:00
Max Murphy 8fff311210 range.setValues: report the strings typed input read, and clear "" with parse: false (#2255)
The default still reads a string with typed input, which is en-US: "05.04.2026"
from a dd.mm.yyyy export became 4 May, "2.500" became 2.5, and the command said
nothing (#2255). `parse: false` (PR #2267) lets a caller keep the text, but a
script that does not pass it still cannot notice the change.

The result now names them: `converted` lists the strings typed input read as a
number (dates and times included), a boolean or an error, with the cell, the
input and what the cell shows, and `convertedCount` is the total. It is capped
at 20 entries and 32 characters per string, so a big write does not put a second
copy of its data in the result. A string written as a formula (`=1+1`) and a
leading apostrophe are not conversions. `cells` and the default reading of the
values are unchanged.

Two `parse: false` fixes from the review of #2267:

- An empty string clears the cell, as `null` and the default path do. Storing an
  empty text cell left a cell that is not blank: `values: [["", ""]]` gave
  COUNTA 2 and ISBLANK FALSE.
- MCP `write_range` takes `parse`, so an agent can opt out too; the tool's
  description and docs/mcp.md say so.

Tests: the issue's examples under both settings, the report and its cap, the
empty string, and an XLSX save/reopen that keeps the text as text and the
default's date as a date.
2026-10-31 16:57:38 +01:00
Max Murphy 40b254c306 fix(ui): refuse a locked cell's edit before a draft blocks Save (#2945) 2026-10-31 16:52:49 +01:00
Max Murphy 81aee9e075 calc: form the MAP result size in u64 so wasm32 cannot wrap the cap (#2165)
`broadcast_len`, the MAX_CELLS guard shared by MAP, the binary operators,
TEXT and array IF, multiplied `rows` and `cols` as `usize`. On wasm32 a
broadcast such as 65536 x 65536 (2^32 cells) wraps that product to 0 and
slips past the cap, after which the allocation and loop run far past
MAX_CELLS. The product is now formed in u64 and converted back only after
the cap check, so the guard means the same on every target.

`single`, the per-cell scalar check in MAP and MAKEARRAY, compared
`rows * cols` with 1. A constructed array stores exactly `rows * cols`
values, so it now tests `data.len()` directly: the same answer without an
unchecked product.

Tests cover MAX_CELLS exactly, one cell over, the 2^32 pair that wraps to
zero on a 32-bit usize, a full column x row (2^34), and MAP itself just
over the cap and at 16384 x 16384.

Closes #2165
2026-10-31 16:41:24 +01:00
Brandon Thomas 59fd17ae56 Merge PR #2980
# Conflicts:
#	docs/function-parity.md
2026-10-31 10:17:09 -04:00
Brandon Thomas 8069ad33ef Merge PR #2979
# Conflicts:
#	docs/function-parity.md
2026-10-31 10:17:09 -04:00
Brandon Thomas 82a78a9d21 Merge PR #2657
# Conflicts:
#	docs/function-parity.md
2026-10-31 10:17:09 -04:00
Brandon Thomas ac27e3da14 Merge PR #2793 2026-10-31 10:17:09 -04:00
prxnvt 590d5e4492 UI: Insert Function: Enter on the category list doesn't confirm the dialog
egui clicks a focused control on Enter as on Space, so Enter on the
focused category list opened it, and the dialog's OK handling took the
same key press and inserted the selected function. Enter in the open
list did the same while picking a category.

The dialog now leaves Enter to the category list while the list has the
focus or its menu is open (or was open as the frame started, since Enter
on a row closes it in that frame). Enter in the search box or on the
function list still confirms.

Test: function_library::enter_on_the_category_list_does_not_confirm_insert_function
tabs to the list, presses Enter (the list opens, the dialog stays open,
nothing is inserted), presses Enter again in the open list (still open,
nothing inserted), then Shift+Tab back to the search box, where Enter
confirms.
2026-10-31 07:03:16 -04:00
NetterSilverandClaude Haiku 5.5 671e194bb9 Make the Merge & Center ribbon label part of its split button
Fixes #2920

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 11:37:17 +01:00
prxnvt c5a80ff791 UI: Insert Function lets Tab reach the category list
The search box asked for the keyboard focus every frame, so Tab moved to the
category list for one frame and the focus went back to the search box. The
dialog now gives the search box the focus once, when it opens, through the
module's take_first_focus, as the other dialogs do.

Test: insert_function_lets_tab_reach_the_category_list (the search box has
the focus and takes typing; after Tab the category list keeps it over several
frames; Space opens the list).
2026-10-31 04:00:56 -04:00
prxnvt 474472f999 Engine: Recently Used follows the formulas entered; Insert Function category list
formulas.recentlyUsed scanned the session journal for cell.set inputs.
It missed formulas entered by range.* commands, lost them when the
journal dropped its oldest entries (a large file open or a long script
is enough), and started empty in every session. The Function Library's
Recently Used menu and the Insert Function dialog list what it returns.

- Prefs::recent_functions (a new engine module, recent.rs) holds the
  ten functions most recently entered in a formula, newest first.
  Session::execute updates it for every cell.* and range.* command it
  journals, from any source (the interface, the control channel, MCP or
  a script). It reads at most 2,000 of the formulas a command enters,
  starting from the last one; formulas longer than Excel's limit are
  skipped. formulas.recentlyUsed
  returns it. It never reads the journal back, so the journal's caps do
  not affect it.
- The list is kept with the other engine preferences (prefs.json, the
  browser's storage on the web), so it survives a restart as Excel's
  does. It is read leniently: a malformed value loses only this list. A
  fresh install starts from common functions.

The Insert Function dialog is titled "Insert Function", as in Excel (it
said "Formula Builder"), and gains a working category list: Most
Recently Used, All, then each category in Excel's order. A category may
be given by label or by engine id, so a Function Library menu or the
control channel (ui.dialog.set) opens it on one, and a new search
switches to All. The dialog keeps its translated labels and its search
in the interface language. Each row shows the function's name and its
category in two columns; the category column starts after the widest
name shown, so a long name in any font or formula language stays clear
of it. A function counts as recently used once its formula is entered,
not when OK is pressed. The unused "Formula Builder" translations are
removed.

Tests: engine unit tests for reading functions from formulas (strings,
unknown names, over-long and malformed formulas), the formulas a
command enters (newest first, within the budget), the ten-name list
and the lenient read of prefs.json; tests/it/recently_used.rs for
cell.set and range.setValues from the session, a failed command, a
cleared journal and a save and reload of the preferences. UI tests for
the menu's order and the dialog's title, categories, engine-id
category, search and OK, and for the category column with a wide font.
2026-10-31 04:00:56 -04:00
prxnvt 6aa5a78828 UI: Formulas › Function Library as labelled buttons with category menus
The Function Library group was a big Insert Function button followed by
AutoSum and seven big category buttons that all drew the same icon, the
widest group on the Formulas tab. It is now laid out as Excel lays it
out when the window is not wide enough for a big button per category:
big Insert Function and AutoSum buttons, then the categories as
labelled small buttons in columns of three:

  Recently Used    Text                 Math & Trig
  Financial        Date & Time          More Functions
  Logical          Lookup & Reference

- The category buttons draw a new code-drawn icon, Icon::FnBook, a book
  whose spine and glyph (star, coins, question mark, letter, clock,
  magnifier, theta, dots) take the category's colour from the theme's
  category palette. icons::paint_accent draws an icon with an accent
  colour, and widgets::small_button_accent is a small button that uses
  it; small_button is unchanged for its callers.
- AutoSum stays the big split button: the icon inserts SUM, the label
  and arrow open the AutoSum menu, which now reads Sum, Average, Count
  Numbers, Max, Min and More Functions… and is shared with Home ›
  Editing. Its tooltip names the control only, so the shortcut comes
  from the platform lookup (Alt+= on Windows and Linux).
- Each category menu lists its functions with their descriptions on
  hover and ends with Insert Function…, which opens the dialog with
  that category. More Functions cascades into Statistical, Engineering,
  Cube, Information, Compatibility and Web. Recently Used lists what
  the engine's formulas.recentlyUsed returns.
- The function categories, the function list (built once) and the menus
  live in a new function_library module; the dialog's category labels
  come from it.
- keytips.json gains MR for Recently Used, Excel's sequence. Labelled
  small and big buttons report their label to accessibility.
- The one-line labels (Date & Time, Lookup & Reference, Math & Trig,
  More Functions) replace the two-line keys in every interface
  language, with the same words. Recently Used is translated in Czech,
  German, Spanish, French, Japanese, Korean, Portuguese, Russian and
  Chinese; the other languages show it in English, their fallback.

Tests: tests/it/function_library.rs covers the layout (columns of three
level with the tab's other columns), each category menu and its Insert
Function… row, the More Functions cascade, the AutoSum split button and
its menu, and the MR key tip. A ribbon unit test keeps Command-key
glyphs out of the ribbon's text. chart_resize picks the chart menu's
Delete now that Home › Cells' Delete has an accessible name.
2026-10-31 03:35:06 -04:00
Dylan M. Taylor 981fad14e0 Merge pull request #3012 from aiongg/docs-tidy
docs: tidy agent, contributor and asset docs
2026-10-31 01:06:39 -04:00
prxnvt 8c0c2fc64b Fuzzer: switching Normal, Page Layout or Page Break Preview isn't an undo difference
The view commands are not undo steps and don't mark the workbook changed (as in
Excel), so undoing everything leaves a sheet's view mode as it is. The undo
check now resets the view mode with the other view state before comparing.
Seed 177 (`view.pageBreakPreview` on the sales sample) failed the [undo]
invariant without this.
2026-10-31 01:02:33 -04:00
prxnvt 1dcff1742f Undo and redo keep each sheet's view (Normal, Page Layout, Page Break Preview)
A sheet's view is not an undo step: Excel doesn't undo a switch between
Normal, Page Layout and Page Break Preview, and view.normal,
view.pageLayout and view.pageBreakPreview are noundo here. But undo and
redo swap in a whole snapshot of the workbook, so undoing an earlier edit
put back the view the sheet had then: set A1, switch to Page Break
Preview, undo, and the sheet was in Normal again (redo switched it back).

Undo and redo now keep each sheet's current view (extra::keep_view_modes),
as they already keep the Watch Window list, carrying it through each step
of a multi-step undo or redo. Sheets are matched by name; while a rename
is undone or redone, by position under the name the restored snapshot
doesn't have. A view kept this way doesn't make a saved
workbook dirty, as set_view doesn't.

Tests: undo and redo with each view, undoing a rename and a new sheet, a
rename and a new sheet undone and redone with steps: 2, and redo back to
the saved state staying clean. Each fails without the change (the view
reverted to Normal).
2026-10-31 01:02:33 -04:00
aiongg 79722c7666 updates: show check results with craft-update's CheckResultDialog 2026-10-31 04:42:26 +00:00
aiongg 5f7137ca01 docs: tidy agent, contributor and asset docs 2026-10-31 04:17:38 +00:00
Brandon Thomas 0db7d97f2e Merge PR #3008
# Conflicts:
#	docs/function-parity.md
2026-10-30 23:06:47 -04:00
Brandon Thomas 4d1233d0a2 Merge PR #3007
# Conflicts:
#	docs/function-parity.md
2026-10-30 23:06:47 -04:00