diff --git a/ROADMAP.md b/ROADMAP.md index 070552d07..9f6331848 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-13 · **Change:** minor (sheet navigation stays on visible worksheets at boundaries) · **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-12 | Next/previous sheet navigation stops at the first/last visible worksheet instead of activating a hidden boundary sheet; the current selection is retained | | 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) | @@ -193,6 +194,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Progress log: hidden-sheet navigation at workbook boundaries | | 2026-10-12 | minor | Rows auto-fit oversized fonts on open and whole-row/column formatting (#833); progress log row | | 2026-10-12 | minor | Progress log: multi-area row/column delete (#758) | | 2026-10-12 | trivial | Progress log: XLSX chart sheets kept on save (#727) | diff --git a/crates/engine/src/cmd/sheet.rs b/crates/engine/src/cmd/sheet.rs index 242bc8079..163773399 100644 --- a/crates/engine/src/cmd/sheet.rs +++ b/crates/engine/src/cmd/sheet.rs @@ -941,17 +941,18 @@ fn activate(s: &mut Session, p: &Json) -> Result { Ok(json!({"sheet": i, "name": d.wb.active().map(|s| s.name.clone())})) } -fn step_sheet(s: &mut Session, dir: i64) -> Result { +fn step_sheet(s: &mut Session, dir: isize) -> Result { let d = s.doc()?; - let n = d.wb.sheets.len() as i64; - let mut i = d.wb.active_sheet as i64; - for _ in 0..n { - i = (i + dir).clamp(0, n - 1); - if d.wb.sheet(i as usize).is_some_and(|s| s.visibility == Visibility::Visible) { - break; + let mut i = d.wb.active_sheet; + while let Some(next) = i.checked_add_signed(dir) { + let Some(sheet) = d.wb.sheet(next) else { break }; + if sheet.visibility == Visibility::Visible { + s.doc_mut()?.wb_switch_sheet(next); + return ok(); } + i = next; } - s.doc_mut()?.wb_switch_sheet(i as usize); + // No visible sheet in this direction: keep the current sheet and its selection. ok() } diff --git a/crates/engine/tests/hidden_sheet_navigation.rs b/crates/engine/tests/hidden_sheet_navigation.rs new file mode 100644 index 000000000..f457f4bdb --- /dev/null +++ b/crates/engine/tests/hidden_sheet_navigation.rs @@ -0,0 +1,78 @@ +//! Sheet navigation must never activate hidden sheets when it reaches a workbook boundary. + +use std::sync::Arc; + +use gridcraft_engine::{Session, model::Visibility}; +use serde_json::json; + +fn workbook(hidden: &[usize], active: usize) -> Session { + let mut session = Session::new(); + session.new_workbook(); + for name in ["Second", "Third", "Fourth"] { + session.execute("home.insertSheet", json!({"name": name})).unwrap(); + } + for sheet in hidden { + session.execute("sheet.hide", json!({"sheet": sheet})).unwrap(); + } + session.execute("sheet.activate", json!({"sheet": active})).unwrap(); + session.execute("selection.set", json!({"cell": "C7"})).unwrap(); + session +} + +#[test] +fn next_stays_on_the_last_visible_sheet() { + let mut session = workbook(&[2, 3], 1); + for _ in 0..2 { + session.execute("sheet.next", json!({})).unwrap(); + let doc = session.active().unwrap(); + assert_eq!(doc.wb.active_sheet, 1); + assert_eq!(doc.selection.active.a1(), "C7"); + } +} + +#[test] +fn previous_stays_on_the_first_visible_sheet() { + let mut session = workbook(&[0, 1], 2); + for _ in 0..2 { + session.execute("sheet.previous", json!({})).unwrap(); + let doc = session.active().unwrap(); + assert_eq!(doc.wb.active_sheet, 2); + assert_eq!(doc.selection.active.a1(), "C7"); + } +} + +#[test] +fn navigation_skips_hidden_sheets_in_both_directions() { + let mut session = workbook(&[1, 2], 0); + session.execute("sheet.next", json!({})).unwrap(); + assert_eq!(session.active().unwrap().wb.active_sheet, 3); + session.execute("sheet.previous", json!({})).unwrap(); + let doc = session.active().unwrap(); + assert_eq!(doc.wb.active_sheet, 0); + assert_eq!(doc.selection.active.a1(), "C7"); +} + +#[test] +fn navigation_with_one_visible_sheet_is_a_noop() { + let mut session = workbook(&[0, 2, 3], 1); + for command in ["sheet.previous", "sheet.next"] { + session.execute(command, json!({})).unwrap(); + let doc = session.active().unwrap(); + assert_eq!(doc.wb.active_sheet, 1); + assert_eq!(doc.selection.active.a1(), "C7"); + } +} + +#[test] +fn very_hidden_sheets_are_skipped_at_interior_and_boundary_positions() { + let mut session = workbook(&[1, 3], 0); + // VeryHidden is an imported XLSX state; sheet.hide only creates Hidden sheets. + let wb = Arc::make_mut(&mut session.active_mut().unwrap().wb); + for index in [1, 3] { + Arc::make_mut(&mut wb.sheets[index]).visibility = Visibility::VeryHidden; + } + for (command, expected) in [("sheet.next", 2), ("sheet.next", 2), ("sheet.previous", 0)] { + session.execute(command, json!({})).unwrap(); + assert_eq!(session.active().unwrap().wb.active_sheet, expected); + } +} diff --git a/docs/gaps.md b/docs/gaps.md index 03e8672c5..43051451e 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-13 · **Change:** minor (§9: hidden-sheet navigation at workbook boundaries fixed) · **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 @@ -85,6 +85,8 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha- - **Estimate:** 20–30 h for beta (25–40 h full). ### 9. Keyboard-first users: key tips, shortcuts, context menus (U) +- Next/previous sheet navigation now skips Hidden and VeryHidden worksheets without activating a + hidden sheet at the workbook boundary. Other keyboard gaps below remain. - **Evidence:** #33, #49 (key tips: initial Home-tab support landed in #43 on 10-10, the rest of the ribbon not yet), #110, #28 (selection), 59 command shortcuts vs Excel's 200+. - **Estimate:** 12–20 h. [ui-parity.md](ui-parity.md) @@ -168,6 +170,7 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha- | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | §9: next/previous sheet commands retain the current visible sheet and selection when there is no visible destination | | 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 34cb502f5..6e55dccc0 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-19 · **Change:** minor (Ctrl+Enter fill gives each cell the automatic number format typing gives it; keytip letters typed with Alt held, #1846) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (next/previous sheet navigation stops at visible-sheet boundaries; Ctrl+Enter fill gives each cell the automatic number format typing gives it; keytip letters typed with Alt held, #1846) · **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 @@ -21,7 +21,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes | Ribbon collapse, simplified ribbon, customize ribbon/QAT | yes | partial | customization missing | | Key tips (Alt / F10 then letters) | yes, every ribbon control | **initial** (#43, 10-10): Alt or F10 shows key tips on Home for alignment, Paste Special and AutoFit plus legacy Alt sequences; other tabs and controls not yet (#33, #49) | 3–5 h | | Formula bar with Name Box, expand, function hints | yes | yes | done | -| Sheet tabs: rename, reorder, colour, hide, scroll | yes | yes | active tab revealed on navigation and resize (#681); manual wheel/trackpad browsing preserved; rename focus bug on macOS (#145) | +| Sheet tabs: rename, reorder, colour, hide, scroll | yes | yes | active tab revealed on navigation and resize (#681); 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 | 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 | @@ -163,6 +163,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12. |---|---|---| | 2026-10-19 | minor | Ctrl+Enter (`range.fill`) gives each cell what typing the input there gives: a General cell gets the automatic number format (`5%`, `$5`, `3/4/2026`, `=TODAY()`) even when it already held a value, and a cell with its own format, a formatted row or column, or Text keeps it. Cells that held a value used to keep General (`5%` showed 0.05), and empty cells took the active cell's format | | 2026-10-18 | minor | Keytip letters typed with left Alt held continue the sequence (Alt+H, O, A / Alt+H, O, I ran nothing before: Alt+letter cancelled keytips); other Alt combinations, AltGr and macOS Option are unchanged (#1846) | +| 2026-10-12 | minor | Next/previous sheet navigation no longer activates hidden worksheets at either workbook boundary; command tests cover Hidden, VeryHidden and selection retention | | 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 |