diff --git a/ROADMAP.md b/ROADMAP.md index 9f78f93..5964519 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -141,6 +141,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | 2026-10-11 | Selected charts, pictures and shapes can be deleted from the keyboard (#213) | | 2026-10-11 | Ribbon translated into German (266 labels; `de`, `de-DE`, `de-AT`, `de-CH` locale tags resolve to German) | | 2026-10-11 | View › Interface size: a saved 75–200 % scale for the whole window (#230) | +| 2026-10-11 | View › Formula Bar and Zoom 100% reachable from the CLI and MCP (#289) | | 2026-10-10 | Ribbon translated into Japanese, Chinese, Korean, Brazilian Portuguese and Russian with system-language default (#31, #104, #82); CJK/Thai/Hebrew font fallback (#38); clear messages for encrypted and `.xls` files (#17); smooth line charts (#62). Progress docs restructured to the craftrules standard; full re-measure (this page). Initial ribbon key tips (#43), in-place text box editing (#84), copy as HTML table (#36). ODS and XLSB data import (#100, #101); recalc linear in the number of range formulas (#66); RISC-V Linux build; Flatpak repository on Pages; web cell-commit trap fixed | | 2026-10-09 | Community fixes: spill range operator `A1#`, sheet-scoped names, protection refuses locked-cell edits, Insert/Delete Cells adjust references, `DATE(1900,2,29)`, SUMIF sizing, `ROUND` at 15 digits, `_xlpm.` LAMBDA parameters in XLSX, vertical scrollbar, system appearance, border previews; v0.4.0 | | 2026-10-08 | v0.2.0 and v0.3.0 releases; Flatpak, AppImage zsync, branded DMG; desktop log file; DirectX 12 default on Windows | @@ -157,6 +158,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | 2026-10-11 | minor | Worksheet functions 502 / 523 (BAHTTEXT); progress log row | | 2026-10-11 | minor | Worksheet functions 502 / 523 (TRIMRANGE); progress log row | | 2026-10-11 | minor | Selected objects can be deleted from the keyboard (#213) | +| 2026-10-11 | minor | Catalog 258 → 260 / 290: View › Formula Bar and Zoom 100% (#289) | | 2026-10-10 | minor | Readiness-by-audience table with hours to ~95% per audience; full number unchanged (additive weighted sum) | | 2026-10-10 | minor | Added Mainstream practitioner (~50%) and Essentials user (~63%); languages updated for the 10-10 merges (ribbon in ja, zh, ko, pt-BR, ru); localization 5%→10%; full number re-checked (51.6, unchanged) | | 2026-10-10 | minor | Checked against the alpha gate (six core workflows pass): stays alpha | diff --git a/crates/engine/src/cmd/view.rs b/crates/engine/src/cmd/view.rs index d8450cd..ac97040 100644 --- a/crates/engine/src/cmd/view.rs +++ b/crates/engine/src/cmd/view.rs @@ -28,6 +28,11 @@ pub fn specs() -> Vec { cmd!("view.showZeros", "Show Zeros", [], None, "{on?}", has_doc, |s, p| sheet_flag(s, p, "zeros")), cmd!("view.rightToLeft", "Sheet Right-to-Left", ["Page Layout", "Sheet Options"], None, "{on?}", has_doc, |s, p| sheet_flag(s, p, "rtl")), cmd!("view.zoom", "Zoom", ["View", "Zoom"], None, "{percent: 10..400}", has_doc, zoom), + cmd!("view.zoom100", "100%", ["View", "Zoom"], None, "{}", has_doc, |s, _| zoom(s, &json!({"percent": 100}))), + cmd!(query "view.formulaBar", "Formula Bar", ["View", "Show"], None, "{on?: bool}", always, |s, p| { + s.prefs.formula_bar = bool_param(p, "on").unwrap_or(!s.prefs.formula_bar); + Ok(json!({"on": s.prefs.formula_bar})) + }), cmd!("view.zoomToSelection", "Zoom to Selection", ["View", "Zoom"], None, "{viewWidth?, viewHeight?}", has_doc, zoom_selection), cmd!("pageLayout.orientation", "Orientation", ["Page Layout", "Page Setup"], None, "{orientation: portrait|landscape}", has_doc, orientation), cmd!("pageLayout.size", "Size", ["Page Layout", "Page Setup"], None, "{paper: Letter|Legal|A4|A3|A5|Tabloid|Executive}", has_doc, paper), @@ -336,3 +341,28 @@ fn theme(s: &mut Session, p: &Json) -> Result { #[allow(dead_code)] fn unused(_: CellRef) {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zoom_100_resets_the_active_sheet_zoom() { + let mut s = Session::new(); + s.new_workbook(); + s.execute("view.zoom", json!({"percent": 150})).unwrap(); + assert_eq!(s.execute("view.zoom100", json!({})).unwrap()["zoom"], 100); + assert_eq!(s.doc().unwrap().wb.active().unwrap().zoom, 100); + } + + #[test] + fn formula_bar_toggles_without_a_workbook_and_defaults_to_shown() { + let mut s = Session::new(); + assert!(s.prefs.formula_bar); + assert_eq!(s.execute("view.formulaBar", json!({})).unwrap()["on"], false); + assert_eq!(s.execute("view.formulaBar", json!({"on": true})).unwrap()["on"], true); + // A prefs.json saved before the option existed keeps the formula bar shown. + let old: crate::Prefs = serde_json::from_str(r#"{"userName": "Ann"}"#).unwrap(); + assert!(old.formula_bar); + } +} diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index de49a5c..e034345 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -151,6 +151,8 @@ pub struct Prefs { pub r1c1: bool, /// Words added with Add to Dictionary. pub user_dictionary: Vec, + /// View › Show › Formula Bar (an application option in Excel, not saved in the file). + pub formula_bar: bool, } impl Default for Prefs { @@ -164,6 +166,7 @@ impl Default for Prefs { sheets_in_new_workbook: 1, r1c1: false, user_dictionary: vec![], + formula_bar: true, } } } diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index b95d897..041ef09 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -168,11 +168,9 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) - let mut v: Vec = app.session.commands().into_iter().map(|c| serde_json::to_value(c).unwrap_or_default()).collect(); for (id, label, params) in [ ("ui.ribbonTab", "Ribbon Tab", ""), - ("view.formulaBar", "Formula Bar", ""), ("view.collapseRibbon", "Collapse Ribbon", ""), ("view.darkMode", "Dark Mode", ""), ("view.theme", "Display Theme", ""), - ("view.zoom100", "100%", ""), ("view.interfaceScale", "Interface Size", "{percent: 75–200 | \"auto\"} (100 = auto; clamped)"), ("ui.dialog", "Open Dialog", ""), ("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"|\"pt\"|\"de\"} (alias: code)"), @@ -197,12 +195,16 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) - app.ui.system_theme = false; } if let Some(v) = p.get("formulaBar").and_then(Json::as_bool) { - app.ui.formula_bar = v; + let _ = app.run("view.formulaBar", json!({"on": v})); } if let Some(v) = p.get("ribbonCollapsed").and_then(Json::as_bool) { app.ui.ribbon_collapsed = v; } - ok(serde_json::to_value(&app.ui).unwrap_or_default()) + let mut out = serde_json::to_value(&app.ui).unwrap_or_default(); + if let Some(o) = out.as_object_mut() { + o.insert("formulaBar".into(), json!(app.session.prefs.formula_bar)); + } + ok(out) } "ui.dialog" => { app.open_dialog(s("name").unwrap_or(""), p.clone()); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index c99ffe3..60ae8bb 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -45,7 +45,6 @@ pub struct UiState { pub system_theme: bool, pub ribbon_tab: String, pub ribbon_collapsed: bool, - pub formula_bar: bool, pub formula_bar_expanded: bool, pub status_bar: bool, pub recent: Vec, @@ -83,7 +82,6 @@ impl Default for UiState { system_theme: false, ribbon_tab: "Home".into(), ribbon_collapsed: false, - formula_bar: true, formula_bar_expanded: false, status_bar: true, recent: vec![], @@ -334,10 +332,6 @@ impl SheetApp { self.ui.ribbon_tab = p.get("tab").and_then(Json::as_str).unwrap_or("Home").to_string(); Ok(Json::Null) } - "view.formulaBar" => { - self.ui.formula_bar = p.get("on").and_then(Json::as_bool).unwrap_or(!self.ui.formula_bar); - Ok(json!({"on": self.ui.formula_bar})) - } "view.collapseRibbon" => { self.ui.ribbon_collapsed = !self.ui.ribbon_collapsed; Ok(Json::Null) @@ -374,7 +368,6 @@ impl SheetApp { None => Err(format!("missing `percent` ({INTERFACE_SCALE_MIN}–{INTERFACE_SCALE_MAX}, or \"auto\")")), } } - "view.zoom100" => return Some(self.session.run("view.zoom", json!({"percent": 100})).inspect(|_| self.after_engine())), "app.language.set" => { // `{"language": "ja"}`; `code` is accepted as an alias. let code = p.get("language").or_else(|| p.get("code")).and_then(Json::as_str).unwrap_or(""); @@ -673,7 +666,7 @@ impl SheetApp { self.ribbon_keys(&ctx); ribbon::title_bar(self, ui); ribbon::show(self, ui); - if self.ui.formula_bar { + if self.session.prefs.formula_bar { formula_bar::show(self, ui); } if self.ui.status_bar { diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index d9fcc52..601dfac 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -1642,9 +1642,9 @@ fn view(app: &mut SheetApp, ui: &mut Ui) { let s = app.session.active().and_then(|d| d.wb.active().map(|s| (s.show_gridlines, s.show_headings))).unwrap_or((true, true)); let lang = app.ui.language; ui.vertical(|ui| { - let mut fb = app.ui.formula_bar; + let mut fb = app.session.prefs.formula_bar; if ui.checkbox(&mut fb, lang.tr("Formula Bar")).changed() { - app.ui.formula_bar = fb; + act(app, "view.formulaBar", json!({"on": fb})); } let mut g = s.0; if ui.checkbox(&mut g, lang.tr("Gridlines")).changed() { @@ -1660,7 +1660,7 @@ fn view(app: &mut SheetApp, ui: &mut Ui) { app.open_dialog("zoom", json!({})); } if big_button(ui, Icon::Search, "100%", "Zoom to 100%", false).clicked() { - act(app, "view.zoom", json!({"percent": 100})); + act(app, "view.zoom100", json!({})); } if big_button(ui, Icon::Zoom, "Zoom to\nSelection", "Zoom to Selection", false).clicked() { let r = app.grid.cells_rect.unwrap_or(Rect::from_min_size(pos2(0.0, 0.0), vec2(1200.0, 700.0))); diff --git a/docs/control-protocol.md b/docs/control-protocol.md index aeb540d..2b1beaa 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -33,7 +33,7 @@ Errors come back as `{"id": …, "ok": false, "error": "…"}`. | `ui.ribbon` | `{tab}` | Switch ribbon tab | | `ui.dialog` / `ui.dialog.set` / `ui.dialog.confirm` / `ui.dialog.cancel` | `{name}` / `{field, value}` | Open, fill in and confirm dialogs | | `ui.screenshot` | `{path?}` | PNG of the window (base64 when no path) | -| `ui.set` | `{dark?, formulaBar?, ribbonCollapsed?}` | UI preferences | +| `ui.set` | `{dark?, formulaBar?, ribbonCollapsed?}` | UI preferences (`formulaBar` runs `view.formulaBar`) | | `ui.resize` / `ui.focus` | `{width, height}` | Window control | | `app.open` / `app.save` / `app.quit` | `{path}` / `{path?}` | Files and lifetime | diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index e98835d..613b5c0 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -4,7 +4,7 @@ Each entry is an Excel ribbon or menu command (feature names only). It counts as implemented when GridCraft registers a command with the same id. This measures breadth only; the overall assessment (depth, fidelity, effort) is in [target-app-parity.md](target-app-parity.md). -**Overall: 258 / 290 commands implemented (89.0%).** +**Overall: 260 / 290 commands implemented (89.7%).** | Tab | Implemented | Total | Parity | |---|---|---|---| @@ -16,7 +16,7 @@ Each entry is an Excel ribbon or menu command (feature names only). It counts as | Formulas | 23 | 23 | 100% | | Data | 20 | 28 | 71% | | Review | 9 | 18 | 50% | -| View | 18 | 20 | 90% | +| View | 20 | 20 | 100% | | Automate | 3 | 3 | 100% | | Table Design | 12 | 13 | 92% | | Chart Design | 8 | 8 | 100% | @@ -302,7 +302,7 @@ Missing: - `review.allowEditRanges` Allow Edit Ranges (Protect) - `review.showChanges` Show Changes (Changes) -## View (18 / 20, 90%) +## View (20 / 20, 100%) Implemented: @@ -311,7 +311,9 @@ Implemented: - `view.pageLayout` Page Layout (Workbook Views) - `view.customViews` Custom Views (Workbook Views) - `view.ruler` Ruler (Show) +- `view.formulaBar` Formula Bar (Show) - `view.zoom` Zoom (Zoom) +- `view.zoom100` 100% (Zoom) - `view.zoomToSelection` Zoom to Selection (Zoom) - `view.newWindow` New Window (Window) - `view.arrangeAll` Arrange All (Window) @@ -325,11 +327,6 @@ Implemented: - `view.switchWindows` Switch Windows (Window) - `view.macros` Macros (Macros) -Missing: - -- `view.formulaBar` Formula Bar (Show) -- `view.zoom100` 100% (Zoom) - ## Automate (3 / 3, 100%) Implemented: diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 300ccd7..3b8a914 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -336,6 +336,7 @@ The inventory carried over from the 2026-10-07 ROADMAP.md, updated for what land | 2026-10-11 | minor | Worksheet functions 503 / 523 (96.2%; 494 working): PERCENTOF, EUROCONVERT | | 2026-10-11 | minor | Worksheet functions 502 / 523 (96.0%; 493 working): BAHTTEXT | | 2026-10-11 | minor | Worksheet functions 502 / 523 (96.0%; 493 working): TRIMRANGE | +| 2026-10-11 | minor | View › Formula Bar and Zoom 100% are engine commands (CLI/MCP); catalog 258 → 260 / 290 (#289) | | 2026-10-10 | minor | Readiness-by-audience table: hours to ~95% for each of the three numbers (full 650–1,050, mainstream 200–310, essentials 60–100). Full number confirmed as the additive weighted sum over the written dimension weights (no change) | | 2026-10-10 | minor | Added Mainstream practitioner (~50%) and Essentials user (~63%) with written weights and discounts, and user-evidence counts; re-checked the full number after the 10-10 merges (charts 38→30, localization 5→10; still 51.6, ~50%) | | 2026-10-10 | minor | Stage checked against the new alpha gate (six core workflows, all pass): stays alpha |