mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
View: Formula Bar and Zoom 100% as engine commands (#289)
- view.zoom100 sets the active sheet's zoom to 100% (the ribbon's 100%
button runs it).
- view.formulaBar {on?} toggles a formulaBar preference (Prefs, saved in
prefs.json on desktop; an application option, not in the file). The UI
shows the formula bar from it; the ribbon checkbox, native menu and
ui.set {formulaBar} go through the command.
- The UI-only interceptions and their entries in engine.commands are gone,
so the CLI and MCP can run both. Parity checklist regenerated (260 / 290).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9f896c1249
commit
be028cc6cc
+4
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
**Stage: alpha** · next: beta, ~25 points of "ready for real work" and ~160–240 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (full re-measure against Excel for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-11 · **Change:** minor (View › Formula Bar and Zoom 100% as engine commands, catalog 260 / 290, #289) · **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.
|
||||
@@ -21,7 +21,7 @@ This page is the summary. The detail is in:
|
||||
|
||||
| Number | Value | Kind |
|
||||
|---|---|---|
|
||||
| **Feature breadth** (weighted) | **~75%** | estimated, anchored on two measurements: ribbon catalog 258 / 290 (89.0%), worksheet functions 501 / 523 (95.8%; 492 working, 94.1%) |
|
||||
| **Feature breadth** (weighted) | **~75%** | estimated, anchored on two measurements: ribbon catalog 260 / 290 (89.7%), worksheet functions 501 / 523 (95.8%; 492 working, 94.1%) |
|
||||
| **Ready for real work** | **~50%** | estimated from written weights (51.6); [target-app-parity.md](docs/target-app-parity.md) |
|
||||
| **Mainstream practitioner** | **~50%** | estimated: typical analyst/finance user, weekly features, × 0.90 interaction × 0.92 stability × 0.88 file exchange; [method](docs/target-app-parity.md#mainstream-practitioner) |
|
||||
| **Essentials user** | **~63%** | estimated: core features only, × 0.92 launch/stability × 0.95 clarity × 0.90 opening sent files; [method](docs/target-app-parity.md#essentials-user) |
|
||||
@@ -131,6 +131,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in
|
||||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 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 |
|
||||
@@ -142,6 +143,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 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 |
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,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 {
|
||||
@@ -163,6 +165,7 @@ impl Default for Prefs {
|
||||
sheets_in_new_workbook: 1,
|
||||
r1c1: false,
|
||||
user_dictionary: vec![],
|
||||
formula_bar: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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%", ""),
|
||||
("ui.dialog", "Open Dialog", ""),
|
||||
("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"|\"pt\"} (alias: code)"),
|
||||
("app.language.english", "Interface Language: English", ""),
|
||||
@@ -196,12 +194,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());
|
||||
|
||||
@@ -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>,
|
||||
@@ -64,7 +63,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![],
|
||||
@@ -294,10 +292,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)
|
||||
@@ -319,7 +313,6 @@ impl SheetApp {
|
||||
}
|
||||
Ok(json!({"mode": self.ui.theme_mode()}))
|
||||
}
|
||||
"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("");
|
||||
@@ -608,7 +601,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 {
|
||||
|
||||
@@ -1636,9 +1636,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() {
|
||||
@@ -1654,7 +1654,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)));
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Target-app parity: GridCraft vs Microsoft Excel
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (first full assessment; re-measure against Excel for Mac 16.113.4, replacing the estimates that lived in ROADMAP.md) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-11 · **Change:** minor (View › Formula Bar and Zoom 100% as engine commands, catalog 260 / 290, #289) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
|
||||
This is the authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it,
|
||||
[`gaps.md`](gaps.md) lists every shortfall, and the area checklists hold the detail:
|
||||
@@ -13,7 +13,7 @@ generated ribbon checklist is [`parity-checklist.md`](parity-checklist.md).
|
||||
| Number | Value | Kind |
|
||||
|---|---|---|
|
||||
| Feature breadth (weighted by use) | **~75%** | estimated from the area table below; anchored on two measurements |
|
||||
| Ribbon and menu catalog | **258 / 290 (89.0%)** | measured: `cargo xtask parity` (`crates/engine/src/catalog.rs` ∩ registered command ids) |
|
||||
| Ribbon and menu catalog | **260 / 290 (89.7%)** | measured: `cargo xtask parity` (`crates/engine/src/catalog.rs` ∩ registered command ids) |
|
||||
| Worksheet functions, by name | **501 / 523 (95.8%)** | measured: registered functions vs Microsoft's published function list (method below) |
|
||||
| Worksheet functions that do real work | **492 / 523 (94.1%)** | measured: excludes 9 stubs (7 CUBE functions, WEBSERVICE, FILTERXML) |
|
||||
| **Ready for real work** | **~50%** | estimated; written weights below (51.6 computed) |
|
||||
@@ -332,6 +332,7 @@ The inventory carried over from the 2026-10-07 ROADMAP.md, updated for what land
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 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 |
|
||||
|
||||
Reference in New Issue
Block a user