Commit Graph
631 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 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
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
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 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
Michael HatzandClaude Opus 5.5 8cf8425745 Print checkbox test: check that the unchecked box is outlined
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:52:22 +01:00
Michael HatzandClaude Opus 5.5 8f93ba2e9c Print checkbox cells as boxes, not TRUE/FALSE (#2972)
A checkbox cell drew a box on screen but printed and exported to PDF
its value as TRUE or FALSE. The print path now draws the box as the
grid does: white with a grey outline when unchecked, green with a
white tick when checked. Other values in a checkbox cell still print
as text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:14:20 +01:00
Brandon Thomas 39020af9e9 Merge PR #2955 2026-10-30 15:42:31 -04:00
Brandon Thomas 6f77f68fbc Merge PR #2666 2026-10-30 15:42:10 -04:00
MarekSpodBiedry 6f067cce8a Refusal to shift cells off the worksheet names GridCraft, not Excel (#2941) 2026-10-30 14:59:45 +01:00
skylar taylor-barrick 6ba1e19857 Merge pull request #1422 from switch87/fix/1419-clear-skips-filtered-rows
Clear: Delete and the Clear commands skip the rows a filter hides (#1419)
2026-10-29 14:18:46 -06:00
Dylan M. Taylor 355840a2a2 Merge branch 'prxnvt/pr/macros-ui' into stack/record
Brings in the macro interface: the Macros dialog and editor, the
Automate ribbon section, drag-and-drop of macro steps, and the
macro overlay on the grid. Ported into the split module layout on main.

Clippy gate: moved wildcard imports to explicit lists, named the
test edit tables, boxed the overlay layer tuple, switched the editor
to craft_ui::ComboBox per clippy.toml, and simplified an is_some_and
in the LP solver.
2026-10-28 23:07:55 -04:00
Dylan M. Taylor 3fc35e5d3b Merge branch 'stack/debug' into stack/record
# Conflicts:
#	crates/engine/src/cmd/extra.rs
2026-10-28 22:15:51 -04:00
Dylan M. Taylor 3923ab3dca Merge branch 'stack/check' into stack/debug
# Conflicts:
#	crates/engine/src/cmd/extra.rs
2026-10-28 22:08:49 -04:00
Dylan M. Taylor 70bcab56e2 Merge branch 'stack/run' into stack/check 2026-10-28 22:04:06 -04:00
Dylan M. Taylor 708481b615 Merge branch 'stack/tokens' into stack/run
# Conflicts:
#	crates/engine/src/cmd/edit.rs
#	crates/engine/src/cmd/insert.rs
2026-10-28 22:03:59 -04:00
Dylan M. Taylor 7bf8ed7495 Merge remote-tracking branch 'origin/main' into stack/engine
# Conflicts:
#	crates/engine/src/cmd/extra.rs
2026-10-28 21:59:05 -04:00
Dylan M. Taylor 9717ccbac1 Refactor engine for strict Clippy checks 2026-10-28 10:52:53 +00:00
aiongg 4b3fd23458 refactor(engine): move cmd/whatif.rs items to cmd/whatif/ 2026-10-26 20:43:39 +00:00
aiongg 506bc8a0f2 refactor(engine): move cmd/sheet.rs items to cmd/sheet/ 2026-10-26 20:43:39 +00:00
aiongg 1a1726bb9d refactor(engine): move cmd/format.rs items to cmd/format/ 2026-10-26 20:43:39 +00:00
aiongg bbff147bf1 refactor(engine): move cmd/extra.rs items to cmd/extra/ 2026-10-26 20:43:39 +00:00
aiongg 0e2335531d refactor(engine): move cmd/data.rs items to cmd/data/ 2026-10-26 20:43:39 +00:00
aiongg 99f0be5f29 refactor(engine): move cmd/pivot.rs items to cmd/pivot/ 2026-10-26 20:43:39 +00:00
aiongg 73c52d49b7 refactor(engine): move cmd/print.rs items to cmd/print/ 2026-10-26 20:43:39 +00:00
aiongg 0069d91e90 refactor(engine): move cmd/edit.rs items to cmd/edit/ 2026-10-26 20:43:39 +00:00
aiongg 5a7d2f259e refactor(engine): move cmd/insert.rs items to cmd/insert/ 2026-10-26 19:04:52 +00:00
aiongg 58465af95a refactor(engine): move cmd/solver.rs items to cmd/solver/ 2026-10-26 19:04:52 +00:00
aiongg a8db625e6b style: cargo fmt on the moved test files 2026-10-26 18:15:59 +00:00
aiongg 8c338b7cbe refactor(engine): move cmd/whatif tests to src/cmd/whatif/tests.rs 2026-10-26 18:15:42 +00:00
aiongg 6fee0e9134 refactor(engine): move cmd/solver tests to src/cmd/solver/tests.rs 2026-10-26 18:15:42 +00:00
aiongg 1f5c4747df refactor(engine): move cmd/print tests to src/cmd/print/tests.rs 2026-10-26 18:15:41 +00:00
aiongg 2396bb4f64 refactor(engine): move cmd/pivot tests to src/cmd/pivot/tests.rs 2026-10-26 18:15:41 +00:00
aiongg 2b808def4f refactor(engine): move cmd/edit tests to src/cmd/edit/tests.rs 2026-10-26 18:15:40 +00:00
aiongg 97c2a984e0 refactor(engine): move cmd/data tests to src/cmd/data/tests.rs 2026-10-26 18:15:39 +00:00
aiongg e95c29b1fe refactor(engine): move crates/engine/src/cmd/mod.rs to crates/engine/src/cmd.rs 2026-10-26 18:15:25 +00:00
Harrison Booth 9515c29969 CSV export: file.exportCsv values:true writes raw values for automation (#2648)
The default stays Excel's displayed text, byte for byte.
2026-10-26 12:34:18 -05:00
prxnvt c801e50e40 Merge #2152 (f8e1f8df, with main at f6b0cec6) into #478
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/io.rs
#	crates/ui-egui/src/dialogs.rs
#	crates/ui-egui/src/lib.rs
#	docs/target-app-parity.md
#	docs/ui-parity.md
2026-10-26 00:32:55 -04:00
prxnvt f8e1f8dfc0 Merge #2145 (4d26e395, with main at f6b0cec6) into #2152
# Conflicts:
#	crates/engine/src/io.rs
#	crates/engine/src/lib.rs
#	docs/target-app-parity.md
2026-10-26 00:29:13 -04:00
prxnvt 4d26e395fc Merge #2550 (d4096eee, with main at f6b0cec6) into #2145
# Conflicts:
#	crates/engine/src/lib.rs
2026-10-26 00:26:35 -04:00
prxnvt d4096eee5a Merge #2144 (34eee860, with main at f6b0cec6) into #2550 2026-10-26 00:24:57 -04:00
prxnvt 34eee86053 Merge #2151 (6781aa49, with main at f6b0cec6) into #2144
# Conflicts:
#	crates/engine/src/io.rs
#	crates/engine/src/lib.rs
2026-10-26 00:24:11 -04:00
prxnvt 6781aa4927 Merge main (f6b0cec6) into #2151 2026-10-26 00:21:54 -04:00
Pranav 1cd45cc909 Merge branch 'main' into pr/macros-ui 2026-10-25 23:54:09 -04:00
Pranav e85297e26c Merge branch 'main' into pr/macros-engine 2026-10-25 23:49:20 -04:00
Pranav b552b7a929 Merge branch 'main' into pr/macros-record 2026-10-25 23:49:08 -04:00
Pranav 910b50c1dd Merge branch 'main' into pr/macros-run 2026-10-25 23:48:13 -04:00