Options: Maximum Iterations and Maximum Change in File > Options > Formulas

The Formulas section had the calculation-mode radios and Enable iterative
calculation but not the two limits that formulas.calculationOptions already
takes. They now sit under the checkbox, disabled while iteration is off, and
commit when the field loses focus. Like Excel, out-of-range entries
(Maximum Iterations outside 1-32767, a negative or non-numeric Maximum
Change) are refused with a message instead of being clamped by the command;
the Enter that committed the field no longer also dismisses that message.

Labels and messages are translated for every interface language. A kittest
covers the disabled state, valid and invalid entries, and undo refreshing
the field. Drops the stale "no Options dialog yet" from gaps.md and
function-parity.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Harrison Booth
2026-10-15 01:19:03 -05:00
co-authored by Claude Opus 5.5
parent e6b6b34970
commit 5bd6cc3d04
16 changed files with 194 additions and 8 deletions
+2 -1
View File
@@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~25 points of "ready for real work" and ~160–240 h away
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (rows auto-fit oversized fonts on open and whole-row/column formatting) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-15 · **Change:** minor (Maximum Iterations and Maximum Change in File › Options) · **Target:** Microsoft Excel (Microsoft 365)
GridCraft aims at full Microsoft Excel parity, and at being better in places: faster, open (XLSX
is the native format), fully scriptable by agents (MCP, control channel, CLI), and on the web.
@@ -132,6 +132,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in
| Date | What landed |
|---|---|
| 2026-10-15 | File › Options › Formulas: Maximum Iterations and Maximum Change fields for iterative calculation, disabled while it is off, with Excel's limits (1 to 32767, 0 or more) |
| 2026-10-12 | Rows auto-fit oversized fonts: opening a file and whole-row/column formatting grow non-custom rows to their tallest font (#833) |
| 2026-10-12 | Number formats: parsing a format code is linear in its length (long runs of `[`, `,` or `/` no longer take quadratic time), and an unclosed `[` is a literal when splitting sections as well as when tokenizing (#810) |
| 2026-10-12 | Number formats: a fraction denominator with 65,536 zeros no longer crashes; format codes are capped at 255 characters like Excel (longer codes display as General, `TEXT` gives `#VALUE!`) (#807) |
+53
View File
@@ -992,10 +992,63 @@ fn options(app: &mut SheetApp, ui: &mut Ui) {
if ui.checkbox(&mut iterative, lang.tr("Enable iterative calculation")).changed() {
app.run_or_alert("formulas.calculationOptions", json!({"iterative": iterative}));
}
// Maximum Iterations and Maximum Change apply only while iteration is on, as in Excel.
ui.add_enabled_ui(calc.iterative, |ui| {
ui.horizontal(|ui| {
ui.add_space(24.0);
egui::Grid::new("backstage_options_iteration").num_columns(2).spacing(vec2(12.0, 6.0)).show(ui, |ui| {
ui.label(lang.tr("Maximum Iterations:"));
let id = ui.make_persistent_id("backstage_max_iterations");
if let Some(text) = crate::widgets::committed_text(ui, id, &calc.max_iterations.to_string(), Some(80.0)) {
match parse_max_iterations(&text) {
Some(n) if n != calc.max_iterations => {
app.run_or_alert("formulas.calculationOptions", json!({"maxIterations": n}));
}
Some(_) => {}
None => refuse(app, ui, "Maximum Iterations must be a whole number from 1 to 32767."),
}
}
ui.end_row();
ui.label(lang.tr("Maximum Change:"));
let id = ui.make_persistent_id("backstage_max_change");
if let Some(text) = crate::widgets::committed_text(ui, id, &calc.max_change.to_string(), Some(80.0)) {
match parse_max_change(&text) {
Some(d) if d != calc.max_change => {
app.run_or_alert("formulas.calculationOptions", json!({"maxChange": d}));
}
Some(_) => {}
None => refuse(app, ui, "Maximum Change must be a number of 0 or more."),
}
}
ui.end_row();
});
});
});
// TODO: add "Set precision as displayed" here once `formulas.calculationOptions` takes
// `precisionAsDisplayed` (PR #1366); the model already stores it.
ui.add_space(6.0);
dim(ui, lang.tr("Calculation settings are saved with the workbook."));
}
/// Refuses a typed setting with a message, leaving the workbook as it was. The Enter that
/// committed the field is consumed so it doesn't also dismiss the message box in this frame.
fn refuse(app: &mut SheetApp, ui: &mut Ui, text: &str) {
ui.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Enter));
app.message = Some(("GridCraft".into(), app.ui.language.tr(text).into()));
}
/// Maximum Iterations as typed: a whole number from 1 to 32767, or `None` (Excel refuses others).
fn parse_max_iterations(text: &str) -> Option<u32> {
let n = text.trim().parse::<u32>().ok()?;
(1..=gridcraft_engine::model::CalcSettings::MAX_ITERATIONS).contains(&n).then_some(n)
}
/// Maximum Change as typed: a finite number of 0 or more, or `None`.
fn parse_max_change(text: &str) -> Option<f64> {
let d = text.trim().parse::<f64>().ok()?;
(d.is_finite() && d >= 0.0).then_some(d)
}
#[cfg(test)]
mod tests {
use super::*;
+4
View File
@@ -587,6 +587,10 @@ const JAPANESE: &[(&str, &str)] = &[
("Save .xlsx workbooks automatically after every change", "変更のたびに .xlsx ブックを自動的に保存する"),
("Workbook calculation", "ブックの計算"),
("Enable iterative calculation", "反復計算を行う"),
("Maximum Iterations:", "最大反復回数:"),
("Maximum Change:", "変化の最大値:"),
("Maximum Iterations must be a whole number from 1 to 32767.", "最大反復回数には 1 から 32767 までの整数を指定してください。"),
("Maximum Change must be a number of 0 or more.", "変化の最大値には 0 以上の数値を指定してください。"),
("Calculation settings are saved with the workbook.", "計算の設定はブックと共に保存されます。"),
];
+4
View File
@@ -323,6 +323,10 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => "Automaticky ukládat sešity .xlsx po každé změně",
"Workbook calculation" => "Výpočet sešitu",
"Enable iterative calculation" => "Povolit iterativní výpočet",
"Maximum Iterations:" => "Maximální počet iterací:",
"Maximum Change:" => "Maximální změna:",
"Maximum Iterations must be a whole number from 1 to 32767." => "Maximální počet iterací musí být celé číslo od 1 do 32767.",
"Maximum Change must be a number of 0 or more." => "Maximální změna musí být číslo 0 nebo větší.",
"Calculation settings are saved with the workbook." => "Nastavení výpočtů se ukládá se sešitem.",
"100%" => "100 %",
"Zoom to\nSelection" => "Přiblížit\nna výběr",
+4
View File
@@ -323,6 +323,10 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => ".xlsx-Arbeitsmappen nach jeder Änderung automatisch speichern",
"Workbook calculation" => "Arbeitsmappenberechnung",
"Enable iterative calculation" => "Iterative Berechnung aktivieren",
"Maximum Iterations:" => "Maximale Iterationszahl:",
"Maximum Change:" => "Maximale Änderung:",
"Maximum Iterations must be a whole number from 1 to 32767." => "Die maximale Iterationszahl muss eine ganze Zahl von 1 bis 32767 sein.",
"Maximum Change must be a number of 0 or more." => "Die maximale Änderung muss eine Zahl größer oder gleich 0 sein.",
"Calculation settings are saved with the workbook." => "Berechnungseinstellungen werden mit der Arbeitsmappe gespeichert.",
"Interface size" => "Größe der Benutzeroberfläche",
"Auto" => "Automatisch",
+4
View File
@@ -322,6 +322,10 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => "Guardar automáticamente los libros .xlsx tras cada cambio",
"Workbook calculation" => "Cálculo del libro",
"Enable iterative calculation" => "Habilitar cálculo iterativo",
"Maximum Iterations:" => "Iteraciones máximas:",
"Maximum Change:" => "Cambio máximo:",
"Maximum Iterations must be a whole number from 1 to 32767." => "Las iteraciones máximas deben ser un número entero de 1 a 32767.",
"Maximum Change must be a number of 0 or more." => "El cambio máximo debe ser un número igual o mayor que 0.",
"Calculation settings are saved with the workbook." => "La configuración de cálculo se guarda con el libro.",
"Interface size" => "Tamaño de la interfaz",
"Auto" => "Automático",
+6
View File
@@ -409,6 +409,12 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => "Enregistrer automatiquement les classeurs .xlsx après chaque modification",
"Workbook calculation" => "Calcul du classeur",
"Enable iterative calculation" => "Activer le calcul itératif",
"Maximum Iterations:" => "Nombre maximal d'itérations :",
"Maximum Change:" => "Écart maximal :",
"Maximum Iterations must be a whole number from 1 to 32767." => {
"Le nombre maximal d'itérations doit être un entier compris entre 1 et 32767."
}
"Maximum Change must be a number of 0 or more." => "L'écart maximal doit être un nombre supérieur ou égal à 0.",
"Calculation settings are saved with the workbook." => "Les paramètres de calcul sont enregistrés avec le classeur.",
_ => return None,
})
+4
View File
@@ -409,6 +409,10 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => "변경할 때마다 .xlsx 통합 문서를 자동으로 저장",
"Workbook calculation" => "통합 문서 계산",
"Enable iterative calculation" => "반복 계산 사용",
"Maximum Iterations:" => "최대 반복 횟수:",
"Maximum Change:" => "변화 한도:",
"Maximum Iterations must be a whole number from 1 to 32767." => "최대 반복 횟수는 1에서 32767 사이의 정수여야 합니다.",
"Maximum Change must be a number of 0 or more." => "변화 한도는 0 이상의 숫자여야 합니다.",
"Calculation settings are saved with the workbook." => "계산 설정은 통합 문서와 함께 저장됩니다.",
_ => return None,
})
+4
View File
@@ -409,6 +409,10 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => "Salvar pastas de trabalho .xlsx automaticamente após cada alteração",
"Workbook calculation" => "Cálculo da pasta de trabalho",
"Enable iterative calculation" => "Habilitar cálculo iterativo",
"Maximum Iterations:" => "Número Máximo de Iterações:",
"Maximum Change:" => "Alteração Máxima:",
"Maximum Iterations must be a whole number from 1 to 32767." => "O número máximo de iterações deve ser um número inteiro de 1 a 32767.",
"Maximum Change must be a number of 0 or more." => "A alteração máxima deve ser um número maior ou igual a 0.",
"Calculation settings are saved with the workbook." => "As configurações de cálculo são salvas com a pasta de trabalho.",
"OK" => "OK",
"Cancel" => "Cancelar",
+4
View File
@@ -409,6 +409,10 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => "Автоматически сохранять книги .xlsx после каждого изменения",
"Workbook calculation" => "Вычисления в книге",
"Enable iterative calculation" => "Включить итеративные вычисления",
"Maximum Iterations:" => "Предельное число итераций:",
"Maximum Change:" => "Относительная погрешность:",
"Maximum Iterations must be a whole number from 1 to 32767." => "Предельное число итераций должно быть целым числом от 1 до 32767.",
"Maximum Change must be a number of 0 or more." => "Относительная погрешность должна быть числом не меньше 0.",
"Calculation settings are saved with the workbook." => "Параметры вычислений сохраняются вместе с книгой.",
_ => return None,
})
+4
View File
@@ -401,6 +401,10 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => "Automatski sačuvaj .xlsx radne sveske posle svake izmene",
"Workbook calculation" => "Izračunavanje radne sveske",
"Enable iterative calculation" => "Omogući iterativno izračunavanje",
"Maximum Iterations:" => "Maksimalan broj iteracija:",
"Maximum Change:" => "Maksimalna promena:",
"Maximum Iterations must be a whole number from 1 to 32767." => "Maksimalan broj iteracija mora biti ceo broj od 1 do 32767.",
"Maximum Change must be a number of 0 or more." => "Maksimalna promena mora biti broj veći ili jednak 0.",
"Calculation settings are saved with the workbook." => "Podešavanja izračunavanja se čuvaju sa radnom sveskom.",
"Left Margin" => "Leva margina",
"Right Margin" => "Desna margina",
+4
View File
@@ -401,6 +401,10 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Save .xlsx workbooks automatically after every change" => "每次更改后自动保存 .xlsx 工作簿",
"Workbook calculation" => "工作簿计算",
"Enable iterative calculation" => "启用迭代计算",
"Maximum Iterations:" => "最多迭代次数:",
"Maximum Change:" => "最大误差:",
"Maximum Iterations must be a whole number from 1 to 32767." => "最多迭代次数必须是 1 到 32767 之间的整数。",
"Maximum Change must be a number of 0 or more." => "最大误差必须是大于或等于 0 的数字。",
"Calculation settings are saved with the workbook." => "计算设置随工作簿一起保存。",
_ => return None,
})
+87 -1
View File
@@ -2,7 +2,7 @@
use std::sync::{Arc, Mutex};
use egui_kittest::kittest::Queryable as _;
use egui_kittest::kittest::{NodeT as _, Queryable as _};
use gridcraft_engine::{Session, core::CellRef, core::Value};
use gridcraft_ui_egui::{Services, SheetApp};
@@ -217,3 +217,89 @@ fn a_cancelled_export_pdf_picker_writes_nothing() {
assert!(h.state().message.is_none());
assert!(h.state_mut().session.take_ui_requests().is_empty());
}
/// Options › Formulas: Maximum Iterations and Maximum Change, with the checkbox that enables them.
#[test]
fn options_sets_maximum_iterations_and_change_only_while_iteration_is_on() {
use egui::accesskit::Role;
let mut session = Session::new();
session.new_workbook();
// Tall enough that the Formulas section is on screen without scrolling.
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 1400.0)).build_ui_state(
|ui, app: &mut SheetApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
SheetApp::new(session, Services::default()),
);
h.run_steps(4);
h.state_mut().run("file.options", serde_json::json!({})).unwrap();
h.run_steps(3);
let calc = |h: &egui_kittest::Harness<'static, SheetApp>| h.state().session.active().unwrap().wb.calc.clone();
// The text field on the same row as `label`.
let field = |h: &egui_kittest::Harness<'static, SheetApp>, label: &str| {
let l = h.get_by_label(label).rect();
h.get_all_by_role(Role::TextInput).map(|n| n.rect()).find(|r| r.left() > l.left() && (r.center().y - l.center().y).abs() < 8.0).unwrap()
};
let disabled = |h: &egui_kittest::Harness<'static, SheetApp>, rect: egui::Rect| {
h.get_all_by_role(Role::TextInput).find(|n| n.rect() == rect).unwrap().accesskit_node().is_disabled()
};
let retype = |h: &mut egui_kittest::Harness<'static, SheetApp>, rect: egui::Rect, text: &str| {
h.input_mut().events.push(egui::Event::PointerMoved(rect.center()));
h.run_steps(1);
h.get_all_by_role(Role::TextInput).find(|n| n.rect() == rect).unwrap().click();
h.run_steps(2);
h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::A);
h.run_steps(1);
for c in text.chars() {
h.input_mut().events.push(egui::Event::Text(c.to_string()));
h.run_steps(2);
}
h.key_press(egui::Key::Enter);
h.run_steps(2);
};
// Iteration is off: the two fields show the defaults, greyed out.
let (iterations, change) = (field(&h, "Maximum Iterations:"), field(&h, "Maximum Change:"));
assert!(disabled(&h, iterations) && disabled(&h, change), "the fields are disabled while iteration is off");
assert_eq!(h.get_all_by_role(Role::TextInput).find(|n| n.rect() == iterations).unwrap().value().as_deref(), Some("100"));
assert_eq!(h.get_all_by_role(Role::TextInput).find(|n| n.rect() == change).unwrap().value().as_deref(), Some("0.001"));
h.get_by_role_and_label(Role::CheckBox, "Enable iterative calculation").click();
h.run_steps(3);
assert!(calc(&h).iterative);
let (iterations, change) = (field(&h, "Maximum Iterations:"), field(&h, "Maximum Change:"));
assert!(!disabled(&h, iterations) && !disabled(&h, change));
retype(&mut h, iterations, "500");
assert_eq!(calc(&h).max_iterations, 500);
retype(&mut h, change, "0.01");
assert_eq!(calc(&h).max_change, 0.01);
assert!(h.state().message.is_none());
// Out-of-range entries are refused with a message and change nothing (the command alone would clamp them).
for bad in ["0", "32768", "12.5", "lots"] {
retype(&mut h, iterations, bad);
assert_eq!(calc(&h).max_iterations, 500, "{bad}");
assert!(h.state_mut().message.take().is_some(), "{bad} is reported");
h.run_steps(2);
}
retype(&mut h, iterations, "32767");
assert_eq!(calc(&h).max_iterations, 32767);
for bad in ["-1", "x"] {
retype(&mut h, change, bad);
assert_eq!(calc(&h).max_change, 0.01, "{bad}");
assert!(h.state_mut().message.take().is_some(), "{bad} is reported");
h.run_steps(2);
}
retype(&mut h, change, "0");
assert_eq!(calc(&h).max_change, 0.0);
// Undo puts the old value back in the field.
h.state_mut().run("edit.undo", serde_json::json!({})).unwrap();
h.run_steps(3);
assert_eq!(calc(&h).max_change, 0.01);
let change = field(&h, "Maximum Change:");
assert_eq!(h.get_all_by_role(Role::TextInput).find(|n| n.rect() == change).unwrap().value().as_deref(), Some("0.01"));
}
+3 -2
View File
@@ -1,6 +1,6 @@
# Function parity: worksheet functions and formulas
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (USDOLLAR added as a compatibility function; MIRR, COUNTIF and INDIRECT edge cases match Excel 365) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-15 · **Change:** minor (iterative calculation settings in File › Options) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
Excel's worksheet functions, category by category, against GridCraft's function library. Part of
[target-app-parity.md](target-app-parity.md); gaps feed [gaps.md](gaps.md).
@@ -79,7 +79,7 @@ PIVOTBY, REGEXTEST…) are all present.
| Implicit intersection `@`, legacy CSE array formulas | done; legacy array range fill (PR #156) |
| Trim references `A1:.E10` (trailing), `A1.:E10` (leading), `A1.:.E10` (both), also on whole rows/columns (`A:.A`) | done; evaluated as `TRIMRANGE(range, code, code)`, adjusted on insert/delete; files store `_xlfn.TRIMRANGE(range,code,code)` (Excel's own file spelling unconfirmed in public docs), `_xlfn._TRO_ALL/_TRO_LEADING/_TRO_TRAILING(...)` read too |
| LET, LAMBDA, named LAMBDAs, ISOMITTED, MAP/REDUCE/SCAN/BYROW/BYCOL/MAKEARRAY | done |
| Iterative calculation for circular references | done: with Enable Iterative Calculation on (`formulas.calculationOptions` `{iterative, maxIterations, maxChange}`, saved as `<calcPr iterate iterateCount iterateDelta>`), loops are evaluated again from their previous values (blank = 0) until no cell on a loop changes by Maximum Change or Maximum Iterations is reached; off, cycles give `#CIRC!`. Differences: no Options dialog yet (command only); GridCraft recalculates on open, so a loop that never settles (`=A1+1`) advances again each time the file is opened |
| Iterative calculation for circular references | done: with Enable Iterative Calculation on (`formulas.calculationOptions` `{iterative, maxIterations, maxChange}`, saved as `<calcPr iterate iterateCount iterateDelta>`), loops are evaluated again from their previous values (blank = 0) until no cell on a loop changes by Maximum Change or Maximum Iterations is reached; off, cycles give `#CIRC!`. Set in File › Options › Formulas (Enable iterative calculation, Maximum Iterations 1 to 32767, Maximum Change 0 or more; the two fields are disabled while iteration is off). Differences: GridCraft recalculates on open, so a loop that never settles (`=A1+1`) advances again each time the file is opened |
| 15-significant-digit comparison semantics | **missing** (#57: `=0.1+0.2=0.3` is FALSE) |
| External workbook references (`[Book.xlsx]Sheet1!A1`) | parsed; links dropped on open, no update |
| Localized function names and `;` separators | **missing** (#45); en-US only |
@@ -108,6 +108,7 @@ workbooks; results checked in as data, not Excel files) and fixing what it finds
| Date | Change | Summary |
|---|---|---|
| 2026-10-15 | minor | Iterative calculation: Maximum Iterations and Maximum Change are set in File › Options › Formulas (dropped "no Options dialog yet") |
| 2026-10-12 | minor | USDOLLAR added as a Compatibility function (not on Microsoft's list, so the counts don't change); MIRR with no positive flow is -1, COUNTIF's bare `"<="` / `">="` skip empty strings, INDIRECT with `a1` = FALSE gives `#REF!` for A1-style text (#738) |
| 2026-10-11 | minor | TEXT / number formats fixed against Excel-cached results: fraction literals and zero fractions, `0` denominators zero padded, `?` pads the thousands separator, booleans use the text section (226 mismatches → 0 in 352 TEXT cells) |
| 2026-10-11 | minor | Iterative calculation: circular references iterate up to Maximum Iterations / Maximum Change when enabled |
+4 -2
View File
@@ -1,6 +1,6 @@
# Gaps
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (rows auto-fit oversized fonts on open and whole-row/column formatting) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-15 · **Change:** minor (§6: Maximum Iterations and Maximum Change in File › Options) · **Target:** Microsoft Excel (Microsoft 365)
Every known shortfall against Excel, one entry each, ranked by user impact (what blocks someone
using GridCraft for real work first). This is the work list: pick from the top, check the open
@@ -62,7 +62,8 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
- **Missing:** 15-significant-digit equality; spill updates after structural edits and cleared
blockers; 3-D span deletion; qualified names after sheet rename.
- **Evidence:** #57, #133, #139, #140, #134; PRs #148, #155, #156, #157. Iterative calculation
for circular references landed (no Options dialog yet).
for circular references landed, set in File › Options › Formulas (Maximum Iterations, Maximum
Change).
- **Impact:** wrong numbers are worse than missing features.
- **Estimate:** 10–15 h. [function-parity.md](function-parity.md#formula-language)
@@ -168,6 +169,7 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
| Date | Change | Summary |
|---|---|---|
| 2026-10-15 | minor | §6: Maximum Iterations and Maximum Change in File › Options › Formulas; dropped the stale "no Options dialog yet" |
| 2026-10-12 | minor | rows auto-fit oversized fonts on open and whole-row/column formatting (#833) |
| 2026-10-12 | minor | Page Layout view: dragging margins on the rulers (#792) removed from the §14 remainder |
| 2026-10-12 | minor | §13: Forecast Sheet Forecast Start (hindcast) landed (#785) |
+3 -2
View File
@@ -1,6 +1,6 @@
# UI parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (horizontal scrollbar in the sheet tab bar, #840) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** minor (File › Options › Formulas: Maximum Iterations and Maximum Change) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md).
**UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured
@@ -26,7 +26,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes
| Task panes (Format Chart, Comments, Watch, Selection, PivotTable fields) | yes | yes | element-level Format pane for shapes and pictures missing |
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | partial | Options dialog and template gallery missing |
| Dark mode, system appearance | yes | yes | done (10-09); follows the system on first run, title-bar switch and File › Display theme (#194, #239) |
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | yes (#174, 10-11): the File tab opens a full-window view (Esc or the back arrow return) with Home, New (blank + the three samples), Open (recent + Browse; the recent list tells when each workbook was last changed, 10-12), Info (location, properties, Protect Workbook), Save, Save As, Print (page preview, active sheet or workbook, fit to page, Page Setup), Export (PDF, CSV, web page), Close and Options (`file.options`: theme, language, formula bar, AutoSave, calculation) | no Account, Share or template gallery beyond the samples; Options applies at once (no OK/Cancel dialog) and holds only the settings GridCraft has |
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | yes (#174, 10-11): the File tab opens a full-window view (Esc or the back arrow return) with Home, New (blank + the three samples), Open (recent + Browse; the recent list tells when each workbook was last changed, 10-12), Info (location, properties, Protect Workbook), Save, Save As, Print (page preview, active sheet or workbook, fit to page, Page Setup), Export (PDF, CSV, web page), Close and Options (`file.options`: theme, language, formula bar, AutoSave, calculation mode and iterative calculation with Maximum Iterations and Maximum Change, 10-15) | no Account, Share or template gallery beyond the samples; Options applies at once (no OK/Cancel dialog) and holds only the settings GridCraft has |
| Dark mode, system appearance | yes | yes | done (10-09) |
| Page Layout view (paper view with rulers and header editing) | yes | yes (#329, 10-11): per-sheet view saved in XLSX, pages with margins and gaps on the print pagination, "Click to add data" pages, rulers (inches or centimetres), headers and footers drawn and edited in place with a Header & Footer tab | margins drag on the rulers (#792, with header and footer margins); header/footer pictures, Different First Page / Odd & Even missing (1–3 h) |
| Multiple windows per workbook | yes | no (#162) | see [hardware-parity.md](hardware-parity.md) |
@@ -161,6 +161,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
| Date | Change | Summary |
|---|---|---|
| 2026-10-15 | minor | File › Options › Formulas: Maximum Iterations (1 to 32767) and Maximum Change (0 or more) under Enable iterative calculation, disabled while it is off; invalid entries are refused with a message |
| 2026-10-12 | minor | The File view's recent list tells when each workbook was last changed, read off the UI thread |
| 2026-10-13 | minor | Horizontal scrollbar in the sheet tab bar: drag, track click pages, frozen columns excluded from the page (#840) |
| 2026-10-12 | minor | Ctrl+wheel zoom steps along a ladder of percentages about 20% apart that never skips 100%, gliding to each step with animations on, so a notch out undoes a notch in (it drifted: 100 → 122 → 101); Excel steps 15 points, see decisions.md |