Merge pull request #295 from vangel76/feat/view-formula-bar-zoom100

View: Formula Bar and Zoom 100% as engine commands (#289)

# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/control.rs
#	crates/ui-egui/src/lib.rs
#	docs/target-app-parity.md
This commit is contained in:
Brandon Thomas 2026-10-11 11:45:32 -04:00
commit 4f7144d753
9 changed files with 52 additions and 24 deletions

View File

@ -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 |

View File

@ -28,6 +28,11 @@ pub fn specs() -> Vec<CommandSpec> {
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<Json> {
#[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);
}
}

View File

@ -151,6 +151,8 @@ pub struct Prefs {
pub r1c1: bool,
/// Words added with Add to Dictionary.
pub user_dictionary: Vec<String>,
/// 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,
}
}
}

View File

@ -168,11 +168,9 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) -
let mut v: Vec<Json> = 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());

View File

@ -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<String>,
@ -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 {

View File

@ -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)));

View File

@ -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 |

View File

@ -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:

View File

@ -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 |