From 5bd6cc3d04701b1f70442f2a2eaae5d285a0f3e8 Mon Sep 17 00:00:00 2001 From: Harrison Booth Date: Thu, 15 Oct 2026 00:57:17 -0500 Subject: [PATCH 1/5] 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 --- ROADMAP.md | 3 +- crates/ui-egui/src/backstage.rs | 53 +++++++++++++++++++ crates/ui-egui/src/i18n.rs | 4 ++ crates/ui-egui/src/i18n/cs.rs | 4 ++ crates/ui-egui/src/i18n/de.rs | 4 ++ crates/ui-egui/src/i18n/es.rs | 4 ++ crates/ui-egui/src/i18n/fr.rs | 6 +++ crates/ui-egui/src/i18n/ko.rs | 4 ++ crates/ui-egui/src/i18n/pt.rs | 4 ++ crates/ui-egui/src/i18n/ru.rs | 4 ++ crates/ui-egui/src/i18n/sr.rs | 4 ++ crates/ui-egui/src/i18n/zh.rs | 4 ++ crates/ui-egui/tests/file_menu.rs | 88 ++++++++++++++++++++++++++++++- docs/function-parity.md | 5 +- docs/gaps.md | 6 ++- docs/ui-parity.md | 5 +- 16 files changed, 194 insertions(+), 8 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 070552d07..2c06f07d9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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) | diff --git a/crates/ui-egui/src/backstage.rs b/crates/ui-egui/src/backstage.rs index 922baabc5..bc41222e1 100644 --- a/crates/ui-egui/src/backstage.rs +++ b/crates/ui-egui/src/backstage.rs @@ -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 { + let n = text.trim().parse::().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 { + let d = text.trim().parse::().ok()?; + (d.is_finite() && d >= 0.0).then_some(d) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 061698df9..17b098de2 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -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.", "計算の設定はブックと共に保存されます。"), ]; diff --git a/crates/ui-egui/src/i18n/cs.rs b/crates/ui-egui/src/i18n/cs.rs index 7ed8372c7..deaff822c 100644 --- a/crates/ui-egui/src/i18n/cs.rs +++ b/crates/ui-egui/src/i18n/cs.rs @@ -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", diff --git a/crates/ui-egui/src/i18n/de.rs b/crates/ui-egui/src/i18n/de.rs index c20c7d1b3..efa20cac7 100644 --- a/crates/ui-egui/src/i18n/de.rs +++ b/crates/ui-egui/src/i18n/de.rs @@ -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", diff --git a/crates/ui-egui/src/i18n/es.rs b/crates/ui-egui/src/i18n/es.rs index 4ce231bca..508f04945 100644 --- a/crates/ui-egui/src/i18n/es.rs +++ b/crates/ui-egui/src/i18n/es.rs @@ -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", diff --git a/crates/ui-egui/src/i18n/fr.rs b/crates/ui-egui/src/i18n/fr.rs index 5d20ab0ca..dec45571b 100644 --- a/crates/ui-egui/src/i18n/fr.rs +++ b/crates/ui-egui/src/i18n/fr.rs @@ -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, }) diff --git a/crates/ui-egui/src/i18n/ko.rs b/crates/ui-egui/src/i18n/ko.rs index 1902ad301..566044d57 100644 --- a/crates/ui-egui/src/i18n/ko.rs +++ b/crates/ui-egui/src/i18n/ko.rs @@ -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, }) diff --git a/crates/ui-egui/src/i18n/pt.rs b/crates/ui-egui/src/i18n/pt.rs index fb9236cdb..74d4c6268 100644 --- a/crates/ui-egui/src/i18n/pt.rs +++ b/crates/ui-egui/src/i18n/pt.rs @@ -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", diff --git a/crates/ui-egui/src/i18n/ru.rs b/crates/ui-egui/src/i18n/ru.rs index 765f6bb32..f53611517 100644 --- a/crates/ui-egui/src/i18n/ru.rs +++ b/crates/ui-egui/src/i18n/ru.rs @@ -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, }) diff --git a/crates/ui-egui/src/i18n/sr.rs b/crates/ui-egui/src/i18n/sr.rs index cbf18d4c4..8f724c22b 100644 --- a/crates/ui-egui/src/i18n/sr.rs +++ b/crates/ui-egui/src/i18n/sr.rs @@ -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", diff --git a/crates/ui-egui/src/i18n/zh.rs b/crates/ui-egui/src/i18n/zh.rs index 00175925e..8f77aeb6d 100644 --- a/crates/ui-egui/src/i18n/zh.rs +++ b/crates/ui-egui/src/i18n/zh.rs @@ -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, }) diff --git a/crates/ui-egui/tests/file_menu.rs b/crates/ui-egui/tests/file_menu.rs index 691eef367..33059c14e 100644 --- a/crates/ui-egui/tests/file_menu.rs +++ b/crates/ui-egui/tests/file_menu.rs @@ -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")); +} diff --git a/docs/function-parity.md b/docs/function-parity.md index 760ffd6ac..7b2f5f03e 100644 --- a/docs/function-parity.md +++ b/docs/function-parity.md @@ -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 ``), 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 ``), 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 | diff --git a/docs/gaps.md b/docs/gaps.md index 03e8672c5..61b5f7422 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -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) | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 22e221e69..34b6ed1e4 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -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 | From 1062604fee0a6f4bf9394dd9d578f3595ede44a5 Mon Sep 17 00:00:00 2001 From: Harrison Booth <73434116+Harrison-Booth@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:32:50 -0500 Subject: [PATCH 2/5] i18n: Russian Maximum Change reads as a change limit The Russian labels for Maximum Change said relative error, which is a different quantity. They now say maximum change. --- crates/ui-egui/src/i18n/ru.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/i18n/ru.rs b/crates/ui-egui/src/i18n/ru.rs index 3101d4b35..97033fe4e 100644 --- a/crates/ui-egui/src/i18n/ru.rs +++ b/crates/ui-egui/src/i18n/ru.rs @@ -418,9 +418,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> { "Workbook calculation" => "Вычисления в книге", "Enable iterative calculation" => "Включить итеративные вычисления", "Maximum Iterations:" => "Предельное число итераций:", - "Maximum Change:" => "Относительная погрешность:", + "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.", + "Maximum Change must be a number of 0 or more." => "Максимальное изменение должно быть числом не меньше 0.", "Calculation settings are saved with the workbook." => "Параметры вычислений сохраняются вместе с книгой.", "Insert Sheet" => "Вставить лист", "Rename" => "Переименовать", From a80c7ff570ed25a72a86d921461f6abf3a5bd94e Mon Sep 17 00:00:00 2001 From: Harrison Booth <73434116+Harrison-Booth@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:43:24 -0500 Subject: [PATCH 3/5] i18n: Japanese labels for the iteration settings live in ja.rs The merge from main moved the Japanese catalog into i18n/ja.rs, but the feature's old table stayed in i18n.rs and was never used, which failed clippy's dead-code lint. Drop the table and add the four new labels and messages to ja.rs. --- crates/ui-egui/src/i18n.rs | 421 ---------------------------------- crates/ui-egui/src/i18n/ja.rs | 4 + 2 files changed, 4 insertions(+), 421 deletions(-) diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 5d3a831b1..345037b52 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -296,427 +296,6 @@ pub fn set_current(ctx: &egui::Context, language: Language) { ctx.data_mut(|d| d.insert_temp(lang_key(), language)); } -/// English label → Japanese. Keys are the exact interface strings (command ids, file names and -/// document text are never translated); anything absent falls back to English. -const JAPANESE: &[(&str, &str)] = &[ - ("Paste", "貼り付け"), - ("Paste Values", "値の貼り付け"), - ("Paste Formulas", "数式の貼り付け"), - ("Paste Formatting", "書式の貼り付け"), - ("Transpose", "行列を入れ替え"), - ("Paste Link", "リンクの貼り付け"), - ("Paste Special…", "形式を選択して貼り付け…"), - ("Insert Copied Cells", "コピーしたセルの挿入"), - ("Insert Copied Cells…", "コピーしたセルの挿入…"), - ("Insert Cut Cells", "切り取ったセルの挿入"), - ("Insert Paste", "挿入貼り付け"), - ("Cut (⌘X)", "切り取り (⌘X)"), - ("Copy (⌘C)", "コピー (⌘C)"), - ("Format Painter (double-click to keep it on)", "書式のコピー/貼り付け (ダブルクリックで固定)"), - ("Bold (⌘B)", "太字 (⌘B)"), - ("Italic (⌘I)", "斜体 (⌘I)"), - ("Underline (⌘U)", "下線 (⌘U)"), - ("Double Underline", "二重下線"), - ("Strikethrough", "取り消し線"), - ("Underline", "下線"), - ("Increase Font Size", "フォントの拡大"), - ("Decrease Font Size", "フォントの縮小"), - ("Wrap Text", "折り返して全体を表示"), - ("Merge & Center", "セルを結合して中央揃え"), - ("Merge Cells", "セルの結合"), - ("Merge Across", "横方向に結合"), - ("Unmerge Cells", "セル結合の解除"), - ("Fill Color", "塗りつぶしの色"), - ("Font Color", "フォントの色"), - ("Borders", "罫線"), - ("Top\nAlign", "上揃え"), - ("Middle\nAlign", "中央揃え (縦)"), - ("Bottom\nAlign", "下揃え"), - ("Left", "左"), - ("Right", "右"), - ("Center", "中央"), - ("Horizontal", "水平"), - ("Vertical Text", "縦書き"), - ("Decrease Indent", "インデントを減らす"), - ("Increase Indent", "インデントを増やす"), - ("Orientation", "方向"), - ("Page Layout|Orientation", "印刷の向き"), - ("Angle Counterclockwise", "左回りに回転"), - ("Angle Clockwise", "右回りに回転"), - ("Rotate Text Up", "文字列を上に回転"), - ("Rotate Text Down", "文字列を下に回転"), - ("Accounting Number Format", "通貨表示形式"), - ("Percent Style", "パーセント表示"), - ("Comma Style", "桁区切りスタイル"), - ("Increase Decimal", "小数点以下の桁数を増やす"), - ("Decrease Decimal", "小数点以下の桁数を減らす"), - ("More Accounting Formats…", "その他の通貨表示形式…"), - ("Conditional\nFormatting", "条件付き書式"), - ("Conditional Formatting", "条件付き書式"), - ("Format\nas Table", "テーブルとして書式設定"), - ("Cell\nStyles", "セルのスタイル"), - ("Format Cells…", "セルの書式設定…"), - ("Format Cell Alignment…", "セルの配置…"), - ("Format\nPane", "書式ウィンドウ"), - ("Format", "書式"), - ("Insert", "挿入"), - ("Insert Cells…", "セルの挿入…"), - ("Delete", "削除"), - ("Delete Cells…", "セルの削除…"), - ("Insert Sheet Rows", "シートの行を挿入"), - ("Insert Sheet Columns", "シートの列を挿入"), - ("Delete Sheet Rows", "シートの行を削除"), - ("Delete Sheet Columns", "シートの列を削除"), - ("Row Height…", "行の高さ…"), - ("AutoFit Row Height", "行の高さを自動調整"), - ("Column Width…", "列の幅…"), - ("AutoFit Column Width", "列の幅を自動調整"), - ("Default Width…", "標準の幅…"), - ("Hide Rows", "行を表示しない"), - ("Unhide Rows", "再表示"), - ("Hide Columns", "列の表示/非表示"), - ("Unhide Columns", "再表示"), - ("Lock Cell", "セルのロック"), - ("Clear", "クリア"), - ("Clear All", "すべてクリア"), - ("Clear Formats", "書式のクリア"), - ("Clear Contents", "数式と値のクリア"), - ("Clear Comments and Notes", "コメントとメモのクリア"), - ("Clear Hyperlinks", "ハイパーリンクのクリア"), - ("AutoSum", "オート SUM"), - ("AutoSum (⌘⇧T)", "オート SUM (⌘⇧T)"), - ("Flash\nFill", "フラッシュ フィル"), - ("Flash Fill", "フラッシュ フィル"), - ("Fill", "フィル"), - ("Clear Print Area", "印刷範囲のクリア"), - ("Find &\nSelect", "検索と選択"), - ("Find…", "検索…"), - ("Replace…", "置換…"), - ("Go To…", "ジャンプ…"), - ("Go To Special…", "セルを選択…"), - ("PivotTable", "ピボットテーブル"), - ("Recommended\nCharts", "推奨グラフ"), - ("Table", "テーブル"), - ("Pictures", "画像"), - ("Shapes", "図形"), - ("Icons", "アイコン"), - ("Text\nBox", "テキスト ボックス"), - ("Comment", "コメント"), - ("New\nComment", "新しいコメント"), - ("New Note", "新しいメモ"), - ("Show/Hide Note", "メモの表示/非表示"), - ("Header &\nFooter", "ヘッダーとフッター"), - ("Text to\nColumns", "区切り位置"), - ("Link", "リンク"), - ("Symbol", "記号"), - ("Sparklines", "スパークライン"), - ("Checkbox", "チェックボックス"), - ("Insert checkboxes in the selected cells", "選択したセルにチェックボックスを挿入"), - ("Header & Footer", "ヘッダーとフッター"), - ("Draw with ink", "インクで描画"), - ("Eraser", "消しゴム"), - ("Ink to\nShape", "インクを図形に変換"), - ("Select\nObjects", "オブジェクトの選択"), - ("Themes", "テーマ"), - ("Margins", "余白"), - ("Left Margin", "左余白"), - ("Right Margin", "右余白"), - ("Top Margin", "上余白"), - ("Bottom Margin", "下余白"), - ("Header Margin", "ヘッダーの余白"), - ("Footer Margin", "フッターの余白"), - ("Size", "サイズ"), - ("Print\nArea", "印刷範囲"), - ("Set Print Area", "印刷範囲の設定"), - ("Breaks", "改ページ"), - ("Insert Page Break", "改ページの挿入"), - ("Remove Page Break", "改ページの解除"), - ("Reset All Page Breaks", "すべての改ページを解除"), - ("Print\nTitles", "印刷タイトル"), - ("Sheet\nRight-to-Left", "シートを右から左へ"), - ("Sheet Right-to-Left", "シートを右から左へ"), - ("Narrow", "狭い"), - ("Wide", "広い"), - ("Normal", "標準"), - ("Portrait", "縦"), - ("Landscape", "横"), - ("Custom Margins…", "ユーザー設定の余白…"), - ("Insert\nFunction", "関数の挿入"), - ("Name\nManager", "名前の管理"), - ("Define Name", "名前の定義"), - ("Use in Formula", "数式で使用"), - ("Create from Selection", "選択範囲から作成"), - ("Trace Precedents", "参照元のトレース"), - ("Trace Dependents", "参照先のトレース"), - ("Remove Arrows", "矢印の解除"), - ("Show Formulas", "数式の表示"), - ("Error Checking", "エラーチェック"), - ("Evaluate Formula", "数式の検証"), - ("Watch Window", "ウォッチ ウィンドウ"), - ("Calculation\nOptions", "計算方法の設定"), - ("Calculate Now", "再計算実行"), - ("Calculate Sheet", "シートの再計算"), - ("Automatic", "自動"), - ("Automatic Except for Data Tables", "データ テーブル以外は自動"), - ("Manual", "手動"), - ("Sort &\nFilter", "並べ替えとフィルター"), - ("Sort", "並べ替え"), - ("Sort A to Z", "昇順"), - ("Sort Z to A", "降順"), - ("Custom Sort…", "ユーザー設定の並べ替え…"), - ("Filter", "フィルター"), - ("Reapply", "再適用"), - ("Get Data\n(Text/CSV)", "データの取得 (テキスト/CSV)"), - ("Remove\nDuplicates", "重複の削除"), - ("Data\nValidation", "データの入力規則"), - ("Data Validation", "データの入力規則"), - ("Data Validation…", "データの入力規則…"), - ("Circle Invalid Data", "無効なデータをマーク"), - ("Clear Validation", "入力規則のクリア"), - ("Group", "グループ化"), - ("Ungroup", "グループ解除"), - ("Show Detail", "詳細データの表示"), - ("Hide Detail", "詳細を表示しない"), - ("Subtotal", "小計"), - ("Data\nAnalysis", "データ分析"), - ("Data Analysis", "データ分析"), - ("Solver", "ソルバー"), - ("What-If\nAnalysis", "What-If 分析"), - ("Data Table…", "データ テーブル…"), - ("Scenario Manager…", "シナリオ マネージャー…"), - ("Goal Seek…", "ゴール シーク…"), - ("Forecast\nSheet", "予測シート"), - ("Spelling", "スペルチェック"), - ("Check\nAccessibility", "アクセシビリティ チェック"), - ("Threaded Comments", "スレッド化されたコメント"), - ("Show\nComments", "コメントの表示"), - ("Previous", "前へ"), - ("Next", "次へ"), - ("Previous Comment", "前のコメント"), - ("Next Comment", "次のコメント"), - ("Show All Notes", "すべてのメモを表示"), - ("Convert to Comments", "コメントに変換"), - ("Protect\nWorkbook", "ブックの保護"), - ("Allow Edit\nRanges", "範囲の編集を許可"), - ("Allow Edit Ranges", "範囲の編集を許可"), - ("Unprotect\nSheet", "シート保護の解除"), - ("Protect Sheet…", "シートの保護…"), - ("Comments", "コメント"), - ("Notes", "メモ"), - ("Page Break\nPreview", "改ページ プレビュー"), - ("Page\nLayout", "ページ レイアウト"), - ("Formula Bar", "数式バー"), - ("Gridlines", "目盛線"), - ("Headings", "見出し"), - ("Zoom", "ズーム"), - ("100%", "100%"), - ("Zoom to\nSelection", "選択範囲を拡大"), - ("Freeze\nPanes", "ウィンドウ枠の固定"), - ("Freeze Panes", "ウィンドウ枠の固定"), - ("Freeze Top Row", "最上行の固定"), - ("Freeze First Column", "最左列の固定"), - ("Unfreeze Panes", "ウィンドウ枠固定の解除"), - ("Dark Mode", "ダーク モード"), - ("Display theme", "表示テーマ"), - ("Animations", "アニメーション"), - ( - "Glide the selection outline to the cell you move to instead of jumping there", - "選択範囲の枠を、移動先のセルまで滑らかに動かします(瞬時には切り替えません)", - ), - ("System", "システム"), - ("Light", "ライト"), - ("Dark", "ダーク"), - ("Interface language", "表示言語"), - ("Interface size", "表示サイズ"), - ("Auto", "自動"), - ("Drag to freeze columns", "ドラッグして列を固定"), - ("Drag to freeze rows", "ドラッグして行を固定"), - ("Command\nPalette", "コマンド パレット"), - ("Agent\nControl", "エージェント制御"), - ("Action\nJournal", "操作ジャーナル"), - ("About\nGridCraft", "GridCraft について"), - ("Share", "共有"), - ("Add Chart\nElement", "グラフ要素を追加"), - ("Change\nChart Type", "グラフの種類の変更"), - ("Switch\nRow/Column", "行/列の切り替え"), - ("Select\nData", "データの選択"), - ("Data Labels: None", "データ ラベル: なし"), - ("Data Labels: Show", "データ ラベル: 表示"), - ("Legend: None", "凡例: なし"), - ("Legend: Bottom", "凡例: 下"), - ("Legend: Top", "凡例: 上"), - ("Legend: Right", "凡例: 右"), - ("Gridlines: None", "目盛線: なし"), - ("Gridlines: Show", "目盛線: 表示"), - ("Chart\nTitle", "グラフ タイトル"), - ("Delete\nChart", "グラフの削除"), - ("Convert\nto Range", "範囲に変換"), - ("Table\nStyles", "テーブル スタイル"), - ("Top 10 Items", "上位 10 項目"), - ("Top 10%", "上位 10%"), - ("Bottom 10 Items", "下位 10 項目"), - ("Bottom 10%", "下位 10%"), - ("Above Average", "平均より上"), - ("Below Average", "平均より下"), - ("Clear Rules from Entire Sheet", "シート全体からルールをクリア"), - ("Clear Rules from Selected Cells", "選択したセルからルールをクリア"), - ("Constants", "定数"), - ("Data Bars", "データ バー"), - ("Color Scales", "カラー スケール"), - ("Icon Sets", "アイコン セット"), - ("Home", "ホーム"), - ("Draw", "描画"), - ("Page Layout", "ページ レイアウト"), - ("Formulas", "数式"), - ("Data", "データ"), - ("Review", "レビュー"), - ("View", "表示"), - ("Automate", "自動化"), - ("Table Design", "テーブル デザイン"), - ("Chart Design", "グラフ デザイン"), - ("Ready", "準備完了"), - ("Enter", "入力"), - ("Edit", "編集"), - ("Point", "ポイント"), - ("Calculate", "計算"), - ("Select destination and press Enter or choose Paste", "貼り付け先を選び、Enter キーを押すか [貼り付け] を選びます"), - ("General", "標準"), - ("Number", "数値"), - ("Currency", "通貨"), - ("Accounting", "会計"), - ("Date", "日付"), - ("Time", "時刻"), - ("Percentage", "パーセンテージ"), - ("Fraction", "分数"), - ("Scientific", "指数"), - ("Text", "文字列"), - ("Special", "特殊"), - ("Custom", "ユーザー設定"), - ("Short Date", "日付"), - ("Long Date", "長い日付"), - ("More Number Formats…", "その他の表示形式…"), - ("Financial", "財務"), - ("Logical", "論理"), - ("Date &\nTime", "日付/時刻"), - ("Lookup &\nReference", "検索/行列"), - ("Math &\nTrig", "数学/三角"), - ("More\nFunctions", "その他の関数"), - ("Sheet Options|View", "画面"), - ("Print", "印刷"), - // Page Layout view and the Header & Footer tab. - ("Click to add data", "クリックしてデータを追加"), - ("Add header", "ヘッダーの追加"), - ("Add footer", "フッターの追加"), - ("(none)", "(なし)"), - ("Header", "ヘッダー"), - ("Footer", "フッター"), - ("Page\nNumber", "ページ\n番号"), - ("Number\nof Pages", "ページ数"), - ("Current\nDate", "現在の\n日付"), - ("Current\nTime", "現在の\n時刻"), - ("File\nPath", "ファイルの\nパス"), - ("File\nName", "ファイル名"), - ("Sheet\nName", "シート名"), - ("Go to\nHeader", "ヘッダーに\n移動"), - ("Go to\nFooter", "フッターに\n移動"), - ("Page Number", "ページ番号"), - ("Number of Pages", "ページ数"), - ("Current Date", "現在の日付"), - ("Current Time", "現在の時刻"), - ("File Path", "ファイルのパス"), - ("File Name", "ファイル名"), - ("Sheet Name", "シート名"), - ("Go to Header", "ヘッダーに移動"), - ("Different First Page", "先頭ページのみ別指定"), - ("Different Odd & Even Pages", "奇数/偶数ページ別指定"), - ("Go to Footer", "フッターに移動"), - ("Ruler", "ルーラー"), - // File › Backstage view. - ("File", "ファイル"), - ("Back", "戻る"), - ("New", "新規"), - ("Open", "開く"), - ("Info", "情報"), - ("Save", "上書き保存"), - ("Save As", "名前を付けて保存"), - ("Export", "エクスポート"), - ("Close", "閉じる"), - ("Options", "オプション"), - ("Blank workbook", "空白のブック"), - ("Recent", "最近使ったアイテム"), - ("No recent workbooks yet.", "最近使ったブックはまだありません。"), - ("More workbooks", "その他のブック"), - ("Modified", "更新日時"), - ("Not available", "利用できません"), - ("Just now", "たった今"), - ("Yesterday", "昨日"), - ("{n} min ago", "{n} 分前"), - ("{n} h ago", "{n} 時間前"), - ("{n} days ago", "{n} 日前"), - ("{n} weeks ago", "{n} 週間前"), - ("{n} months ago", "{n} か月前"), - ("{n} years ago", "{n} 年前"), - ("Browse…", "参照…"), - ("Not saved yet", "まだ保存されていません"), - ("Location", "場所"), - ("Sheets", "シート"), - ("Protect Workbook", "ブックの保護"), - ( - "Sheet structure is locked: sheets can't be added, moved, renamed or deleted.", - "シートの構成はロックされています。シートの追加、移動、名前の変更、削除はできません。", - ), - ( - "Lock the sheet structure so sheets can't be added, moved, renamed or deleted.", - "シートの構成をロックして、シートの追加、移動、名前の変更、削除をできないようにします。", - ), - ("Properties", "プロパティ"), - ("Title", "タイトル"), - ("Subject", "件名"), - ("Author", "作成者"), - ("Company", "会社名"), - ("Keywords", "キーワード"), - ("Description", "説明"), - ("No workbook is open.", "ブックが開かれていません。"), - ("Save as type", "ファイルの種類"), - ("Excel Workbook (*.xlsx)", "Excel ブック (*.xlsx)"), - ("CSV UTF-8 (Comma delimited) (*.csv)", "CSV UTF-8 (コンマ区切り) (*.csv)"), - ("Web Page (*.html)", "Web ページ (*.html)"), - ("PDF (*.pdf)", "PDF ファイル (*.pdf)"), - ("Download", "ダウンロード"), - ( - "A CSV file, web page or PDF is a copy: the workbook keeps its own file.", - "CSV ファイル、Web ページ、PDF はコピーです。ブックは元のファイルのままです。", - ), - ("Save the workbook under another name or in another folder.", "ブックを別の名前または別のフォルダーに保存します。"), - ("Print Active Sheets", "作業中のシートを印刷"), - ("Print Entire Workbook", "ブック全体を印刷"), - ("Fit Sheet on One Page", "シートを 1 ページに印刷"), - ("Page Setup…", "ページ設定…"), - ("Page {page} of {pages}", "{page} / {pages} ページ"), - ("Nothing to print.", "印刷する内容がありません。"), - ("Previous page", "前のページ"), - ("Next page", "次のページ"), - ("Create PDF", "PDF の作成"), - ("Save the sheet as a PDF document that keeps its page layout.", "ページ レイアウトを保ったまま、シートを PDF ドキュメントとして保存します。"), - ("Change File Type", "ファイルの種類の変更"), - ("CSV (Comma delimited)", "CSV (コンマ区切り)"), - ("Save the active sheet as comma-separated text.", "作業中のシートをコンマ区切りのテキストとして保存します。"), - ("Web Page (.html)", "Web ページ (.html)"), - ("Save the workbook as a web page.", "ブックを Web ページとして保存します。"), - ("Options|General", "全般"), - ("Options|Save", "保存"), - ("Show formula bar", "数式バーを表示する"), - ("Enable AutoComplete for cell values", "セルの値にオートコンプリートを使用する"), - ("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.", "計算の設定はブックと共に保存されます。"), -]; - /// `name` lowercased with the Latin accents that occur in language names folded away, so a /// byte-wise sort reads alphabetically. Other scripts are left as they are. fn name_sort_key(name: &str) -> String { diff --git a/crates/ui-egui/src/i18n/ja.rs b/crates/ui-egui/src/i18n/ja.rs index 2df4cfc11..1a9ea50d4 100644 --- a/crates/ui-egui/src/i18n/ja.rs +++ b/crates/ui-egui/src/i18n/ja.rs @@ -411,6 +411,10 @@ pub(super) const CORE: &[(&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.", "計算の設定はブックと共に保存されます。"), ]; From 3ef94601d5e0d385a607ec055aaaa720ed259a80 Mon Sep 17 00:00:00 2001 From: Harrison Booth <73434116+Harrison-Booth@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:47:17 -0500 Subject: [PATCH 4/5] Options: commit iteration fields only while iteration is on; accept the locale's decimal comma for Maximum Change Fields still holding unconfirmed text when the checkbox turns iteration off are no longer committed, and Maximum Change parses with the interface's decimal separator as well as a dot. --- crates/ui-egui/src/backstage.rs | 16 +++++++++------- crates/ui-egui/tests/it/file_menu.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/crates/ui-egui/src/backstage.rs b/crates/ui-egui/src/backstage.rs index ead3f81c4..3ceef246f 100644 --- a/crates/ui-egui/src/backstage.rs +++ b/crates/ui-egui/src/backstage.rs @@ -1001,13 +1001,14 @@ fn options(app: &mut SheetApp, ui: &mut Ui) { 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.add_enabled_ui(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)) { + // Text left in the field when iteration is switched off (it loses focus) is dropped. + if let Some(text) = crate::widgets::committed_text(ui, id, &calc.max_iterations.to_string(), Some(80.0)).filter(|_| iterative) { match parse_max_iterations(&text) { Some(n) if n != calc.max_iterations => { app.run_or_alert("formulas.calculationOptions", json!({"maxIterations": n})); @@ -1019,8 +1020,8 @@ fn options(app: &mut SheetApp, ui: &mut Ui) { 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) { + if let Some(text) = crate::widgets::committed_text(ui, id, &calc.max_change.to_string(), Some(80.0)).filter(|_| iterative) { + match parse_max_change(&text, lang.formula_locale()) { Some(d) if d != calc.max_change => { app.run_or_alert("formulas.calculationOptions", json!({"maxChange": d})); } @@ -1051,9 +1052,10 @@ fn parse_max_iterations(text: &str) -> Option { (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 { - let d = text.trim().parse::().ok()?; +/// Maximum Change as typed: a finite number of 0 or more, or `None`. The interface's decimal +/// separator is accepted as well as a dot. +fn parse_max_change(text: &str, locale: gridcraft_engine::formula::FormulaLocale) -> Option { + let d = text.trim().replace(locale.decimal_separator(), ".").parse::().ok()?; (d.is_finite() && d >= 0.0).then_some(d) } diff --git a/crates/ui-egui/tests/it/file_menu.rs b/crates/ui-egui/tests/it/file_menu.rs index 916185665..6b4cbc6a7 100644 --- a/crates/ui-egui/tests/it/file_menu.rs +++ b/crates/ui-egui/tests/it/file_menu.rs @@ -415,6 +415,34 @@ fn options_sets_maximum_iterations_and_change_only_while_iteration_is_on() { 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")); + + // A value typed but not confirmed with Enter is dropped when the checkbox switches iteration off. + let iterations = field(&h, "Maximum Iterations:"); + h.input_mut().events.push(egui::Event::PointerMoved(iterations.center())); + h.run_steps(1); + h.get_all_by_role(Role::TextInput).find(|n| n.rect() == iterations).unwrap().click(); + h.run_steps(2); + h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::A); + h.input_mut().events.push(egui::Event::Text("777".into())); + h.run_steps(2); + h.get_by_role_and_label(Role::CheckBox, "Enable iterative calculation").click(); + h.run_steps(3); + assert!(!calc(&h).iterative); + assert_eq!(calc(&h).max_iterations, 32767, "an unconfirmed entry is not committed once iteration is off"); + assert!(h.state().message.is_none()); + + // Spanish accepts a decimal comma in Maximum Change, and still a dot. + h.get_by_role_and_label(Role::CheckBox, "Enable iterative calculation").click(); + h.run_steps(3); + h.state_mut().run("app.language.set", serde_json::json!({"language": Language::Es.code()})).unwrap(); + h.state_mut().run("file.options", serde_json::json!({})).unwrap(); + h.run_steps(3); + let change = field(&h, "Cambio máximo:"); + retype(&mut h, change, "0,02"); + assert_eq!(calc(&h).max_change, 0.02); + retype(&mut h, change, "0.5"); + assert_eq!(calc(&h).max_change, 0.5); + assert!(h.state().message.is_none()); } /// Share › Export as Web Page (and the native File menu's Save as Web Page) asks for a name in its From a2aa72c3cb82ec068370191905eab0e9f8ba70be Mon Sep 17 00:00:00 2001 From: Harrison Booth <73434116+Harrison-Booth@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:54:20 -0500 Subject: [PATCH 5/5] Docs: drop the stale Options-dialog-missing note from the Backstage row --- docs/ui-parity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 95605a3af..c258628b2 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -28,7 +28,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes | Sheet tabs: rename, reorder, colour, hide, scroll | yes | yes | active tab revealed on navigation and resize (#681); New sheet (+) right after the last tab, the strip capped at 60% of the bar (#943); manual wheel/trackpad browsing preserved; next/previous skips Hidden and VeryHidden sheets and retains the current sheet and selection when no visible destination exists; rename focus bug on macOS (#145) | | Status bar: Sum/Count/Average and more, view buttons, zoom | yes | yes | customizable statistics partial | | Task panes (Format Chart, Comments, Watch, Selection, PivotTable fields) | yes | yes; PivotTable source-field list scrollbar sits at the right edge of its pane | 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 | +| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | partial | 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 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) |