diff --git a/Cargo.lock b/Cargo.lock index 7a189e317c..2e080ac679 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.8" @@ -1957,8 +1963,10 @@ dependencies = [ "pollster 1.0.1", "rfd", "serde_json", + "windows", "winit", "winresource", + "zbus", ] [[package]] @@ -1991,7 +1999,7 @@ dependencies = [ "gridcraft-engine", "gridcraft-mcp", "log", - "quick-xml 0.41.0", + "quick-xml 0.42.0", "serde_json", "zip 3.0.0", ] @@ -2082,6 +2090,7 @@ version = "0.13.0" name = "gridcraft-ui-egui" version = "0.13.0" dependencies = [ + "craft-i18n", "craft-ui", "craft-update", "craft-window", @@ -2129,7 +2138,7 @@ dependencies = [ "gridcraft-formula", "gridcraft-functions", "gridcraft-model", - "quick-xml 0.41.0", + "quick-xml 0.42.0", "sha1 0.11.0", "sha2 0.11.1", "thiserror 2.0.21", @@ -4313,6 +4322,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "log", + "smithay-client-toolkit 0.19.2", + "tiny-skia", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -4845,6 +4865,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "strum" version = "0.28.0" @@ -5074,6 +5100,31 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.8.4" @@ -6184,6 +6235,7 @@ dependencies = [ "raw-window-handle", "redox_syscall 0.4.1", "rustix 0.38.44", + "sctk-adwaita", "smithay-client-toolkit 0.19.2", "smol_str", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 4c234044c5..30602bc605 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,8 @@ gridcraft-pdf = { path = "crates/pdf" } # craft-update: app-specific pretend versions apply to startup and manual checks. craft-update = { git = "https://github.com/storytold/craft-libs", rev = "04aa1703b32d299e54f78fcde4182f2b393a9116", version = "=0.1.0", features = ["egui"] } +craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0" } + # foundation thiserror = "2" serde = { version = "1", features = ["derive", "rc"] } @@ -39,7 +41,7 @@ chrono = { version = "0.4.45", default-features = false, features = ["std", "clo web-time = "1.1.0" rayon = "1" regex = { version = "1", default-features = false, features = ["std", "unicode", "perf"] } -quick-xml = "0.41" +quick-xml = "0.42" zip = { version = "3", default-features = false, features = ["deflate"] } sha2 = { version = "0.11", default-features = false } # Decrypting password-protected workbooks ([MS-OFFCRYPTO]): AES and SHA-1, pure Rust (wasm-safe). diff --git a/README.md b/README.md index d9dc5eb363..fe7bf7564a 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ cargo run --release -p gridcraft -- --sample sales --control 7979 # + JSON con cargo xtask ci # fmt, clippy, tests, assets, layering, wasm ``` -Choose Español under View → Interface language for [Spanish formula names, decimal commas, and semicolon arguments](docs/formula-input.md). +Choose Español or Русский under View → Interface language for [localized formula names, decimal commas, and semicolon arguments](docs/formula-input.md). Workbooks, clipboard exchange, and programmatic APIs retain canonical English syntax. ### Web diff --git a/ROADMAP.md b/ROADMAP.md index 3d1a1d0d8e..d7f37a70e9 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-25 · **Change:** minor (AutoSum over a selected row puts its total to the right, and fills a selection's empty last row and column; earlier: macro engine and `macro.*` commands; macro interface on the Automate tab) · **Target:** Microsoft Excel (Microsoft 365) +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-25 · **Change:** minor (transpose formula references with the pasted cell; web page host integration; XLS notes, hyperlinks and frozen panes; Animation effects follows the system setting; Function Library and recently used functions; AutoSum over a selected row puts its total to the right, and fills a selection's empty last row and column; earlier: macro engine and `macro.*` commands; macro interface on the Automate tab) · **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. @@ -135,8 +135,13 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | Date | What landed | |---|---| +| 2026-10-26 | Paste Special ▸ Transpose turns a formula's references with the cell that carries it, as Excel does: the row offset becomes the column offset and the other way round, so `=A3+1` copied from A3:A4 and pasted at C3 reads the cell to its left (`=C3+1`) instead of keeping the offset it had in the column; a reference pinned with `$` is left standing, a whole row or column comes back as a cell range open to the sheet's edge, and one off the grid is `#REF!` (#2450) | +| 2026-10-26 | Text past 32,767 characters behaves like Excel (#2635): `&` and CONCATENATE cut their result there, CONCAT and TEXTJOIN give `#CALC!`, and a typed or CSV-read cell is cut, so GridCraft never writes a cell Excel refuses to open | | 2026-10-25 | AutoSum over a selection does what Excel does: a selected row gets one total to its right (in its last cell if that is empty), not a one-cell total under each cell; a block whose last column is empty gets each row's total there, one whose last row is empty each column's, and with both the corner gets the total of the block; otherwise totals go under each column as before | -| 2026-10-24 | Macro interface ([macros.md](docs/macros.md#in-the-window)): an Automate › Macros group (Record Macro, which turns into Stop Recording, Use Relative References, Run Macro with recent macros); "Recording · N steps" with Stop in the status bar; a Record Macro dialog (checked name, free Ctrl+Shift shortcut, description); the Macros dialog lists macros beside Automate scripts and a VBA project, with Run, Step Into, Edit, Create, Delete and Options; Ctrl+Shift+letter macro shortcuts; questions that offer Run, Overwrite, Shift Down or Shift Right; a step editor with a breakpoint gutter, drag to reorder, a command picker, reference boxes that switch between relative tokens and absolute references, Inputs and Output tabs, a debug bar (Check, Run, Step, Continue, Run to Here, Stop with keep or undo) and grid overlays for inputs, writes, conflicts and the current step | +| 2026-10-24 | Animation effects setting in File › Options (Use system setting, On, Off): GridCraft follows the system's animation setting (Windows Animation effects, macOS Reduce motion, the Linux/BSD desktop's setting, the browser's), and Off stops eased scrolling and zooming, the selection glide and the marching dashes | +| 2026-10-24 | Recently Used lists the functions most recently entered in a formula from any source (ribbon, control channel, MCP, scripts), kept with the preferences and returned by `formulas.recentlyUsed`; the Insert Function dialog takes Excel's title and a working category list (Most Recently Used, All, each category) | +| 2026-10-24 | Formulas › Function Library as a big Insert Function button, the AutoSum split button and three columns of labelled small buttons, each category book in its own colour; category menus end with Insert Function… on that category; More Functions cascades into the remaining categories; Home › Editing shares the AutoSum menu; the MR key tip opens Recently Used | +| 2026-10-25 | Page host mode: a page can host GridCraft in its own document (no iframe) with `data-host="page"` on the canvas, talking over DOM `CustomEvent`s with the same messages as the `?host=parent` parent bridge, which is now documented in the hosting guide | | 2026-10-24 | Go To lists the named ranges of every sheet (`Sheet2!Name` for a name scoped to another sheet; hidden names, constants and formulas left out) and the references entered before, a double-click goes to an entry, and the Reference box takes several names or references separated by commas (`Price, Type`, `B14:C22,F19:G30`) | | 2026-10-24 | Status bar: a right-click opens a menu that sets which selection statistics show (Average, Count and Sum by default; Numerical Count, Minimum and Maximum are new), kept with the preferences (`view.statusBar`) | | 2026-10-24 | Ctrl+Shift+U (Control+Shift+U on a Mac) expands and collapses the formula bar, on the grid and while editing (`view.expandFormulaBar`); on Windows and Linux it used to underline | @@ -164,12 +169,18 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | 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-15 | Function help follows the interface language: argument hints, autocomplete, Insert Function (search and readable categories) and the Formulas-tab menus read per-language tables of descriptions and argument names; Spanish keeps its 10 functions in the new format | | 2026-10-15 | Row auto-fit counts a cell's line breaks only when the cell wraps, as Excel does: a file whose unwrapped cells hold multi-line text no longer opens with tall rows (typing a break still turns wrap on and grows the row) | +| 2026-10-14 | XLSX import warns when a pivot cache is missing, malformed or too large, naming the cache and saying its PivotTable was kept as plain cells, instead of dropping the PivotTable silently (#1039) | + +| 2026-10-14 | SORT, SORTBY and Data > Sort order numbers exactly like Excel, so 0.3 sorts before `=0.1+0.2` whatever the input order; `=` and COUNTIF keep their 15-digit comparison | | 2026-10-14 | Lookups (MATCH, XMATCH, VLOOKUP, HLOOKUP, XLOOKUP, LOOKUP) compare numbers exactly like Excel, so a computed key such as `=0.1+0.2` no longer finds 0.3; `=` and COUNTIF/SUMIF keep their 15-digit comparison | | 2026-10-14 | Functions: EOMONTH, EDATE, WORKDAY, NETWORKDAYS, YEARFRAC, WEEKNUM, the bond and coupon functions, DISC, BIN2DEC, IM* and the other Analysis ToolPak functions give `#VALUE!` for a reference to more than one cell in a value argument, as Excel 365 does, instead of spilling; array constants and computed arrays still lift | | 2026-10-14 | Protect Sheet on a protected sheet: Home › Format, its keytips (HOP, RPS) and the tab menu now read Unprotect Sheet… and unprotect (straight away without a password); a protected sheet that forbids selecting locked (or unlocked) cells is enforced for clicks, arrows and Go To, and the Protect Sheet dialog has the select options (#1346) | +| 2026-10-14 | XLSX self-contained `extLst` entries are kept on save: x15 and x14 workbook properties, the workbook compatibility version and Excel for Mac entries, alongside calcFeatures; extensions that point at other content (slicers, timelines, data model, Google Sheets data) stay dropped and are named in an open warning | +| 2026-10-14 | `.xls` (Excel 97-2003) files open with their formatting: fonts, solid and pattern fills, borders (with diagonals), alignment (rotation, indent, wrap, shrink), locked and hidden cells, the workbook's custom colour palette, its Normal font and its user-defined cell styles | | 2026-10-14 | XLSX: formulas opened with dynamic-array metadata (`cm`) keep it on save, with `xl/metadata.xml`. Before, a formula returning one value lost it (11 of 40 corpus files that have the part lost it entirely), and Excel recalculated some of them with implicit intersection (`SUMPRODUCT(IFERROR(...))` changed value) | | 2026-10-14 | Recalculation: a dynamic array's anchor given a new formula that reads its old spill area (`A1` was `=SEQUENCE(3)`, becomes `=SUM(A2:A3)+1` or `=SEQUENCE(1,A2+1)`) sees that area empty instead of the old values | | 2026-10-14 | Recalculation: when two arrays of one recalculation want the same empty cells, the formulas that read the one laid out second (at its anchor or over the cells it would have spilled into) read `#SPILL!` and empty cells instead of its values | +| 2026-10-14 | `.xls` import reads cell notes (author, multi-line text, shown or hidden), hyperlinks (web, mailto, absolute and relative files, places in the workbook, tooltips) and frozen panes, plus gridlines and the other sheet display options (#121) | | 2026-10-14 | Border keytips: each Alt, H, B letter applies the preset Excel's Borders menu gives it (H,B,O bottom, H,B,B bottom double, H,B,N no border, H,B,A all borders and the rest); they all drew a bottom border before. H,B,M opens Format Cells on the Border tab | | 2026-10-13 | Formula-entry Command/Ctrl-click inserts another reference with the locale's argument separator; Shift-click extends from a stable, sheet-aware anchor. Pointer tests cover repeated clicks, reversed ranges, ordinary replacement, typed separators, Spanish syntax, sheet switches and keyboard continuation. Readiness estimates unchanged. | | 2026-10-13 | `formulas.evaluate` (`gridcraft-cli eval`, MCP `evaluate_formula`) refuses what entering the formula in a cell refuses, with the reason (`=SUM(1;2)`: "expected `,` or `)`"), instead of `#NAME?` for any formula that doesn't parse | @@ -245,8 +256,11 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | Date | Change | Summary | |---|---|---| | 2026-10-25 | minor | Progress log: AutoSum over a selected row, and a selection's empty last row and column | -| 2026-10-24 | minor | Macro interface on the Automate tab; progress log row | +| 2026-10-24 | minor | Animation effects setting that follows the system's; progress log row | | 2026-10-24 | minor | Macro engine and `macro.*` commands; catalog 275 / 291 → 276 / 292 (View › Macros › Record Macro); progress log row | +| 2026-10-24 | minor | Progress log: Recently Used and Insert Function categories | +| 2026-10-24 | minor | Progress log: Function Library | +| 2026-10-25 | minor | Page host mode (`data-host="page"` on the canvas): host GridCraft in its own document over DOM `CustomEvent`s, same messages as the `?host=parent` parent bridge; progress log row | | 2026-10-23 | minor | Progress log: accounting negatives in number text | | 2026-10-23 | minor | Progress log: INDEX with one index on a 2-D reference is #REF! | | 2026-10-22 | trivial | Progress log: formula hardening (#2163, #2162) | @@ -258,15 +272,22 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | 2026-10-20 | minor | Dutch (nl) listed under other shipped languages; progress log row | | 2026-10-19 | minor | Progress log: long flat operator chains (#916) | | 2026-10-19 | trivial | Progress log: desktop theme portal read timeout | +| 2026-10-17 | minor | Conditional formatting: 2-/3-colour scale editor with per-stop type/value/colour, and in-place rule editing (part of #1165) | | 2026-10-16 | minor | Progress log: undo steps share picture and macro bytes | | 2026-10-16 | minor | Progress log: floating pictures' textures bounded and kept per workbook | | 2026-10-16 | trivial | Progress log: web preferences kept between visits (#1562) | | 2026-10-16 | minor | Progress log: interface fonts held once instead of twice | +| 2026-10-14 | trivial | Progress log: pivot cache import warnings (#1039) | +| 2026-10-14 | minor | Progress log: `.xls` import reads cell formatting | + +| 2026-10-14 | minor | Progress log: sorts order numbers exactly | | 2026-10-14 | minor | Progress log: lookups compare numbers exactly | | 2026-10-14 | minor | Progress log: Analysis ToolPak value arguments reject multi-cell references | +| 2026-10-14 | trivial | Progress log: XLSX self-contained extLst entries kept on save | | 2026-10-14 | minor | XLSX dynamic-array `cm` metadata kept on save; progress log row | | 2026-10-14 | minor | Progress log: spill anchor reading its old spill area | | 2026-10-14 | minor | Progress log: formulas reading an array that loses a spill conflict | +| 2026-10-14 | minor | Progress log: `.xls` import of notes, hyperlinks and frozen panes (#121) | | 2026-10-13 | trivial | Progress log: More Colors… and Recent Colors in the colour menus (#928) | | 2026-10-13 | minor | Multi-level category axes (read, drawn, written); progress log row | | 2026-10-13 | trivial | Progress log: shared pictures written once on XLSX export (#985) | diff --git a/apps/gridcraft-cli/src/check/package.rs b/apps/gridcraft-cli/src/check/package.rs index 6b97fa4951..b8276d8bee 100644 --- a/apps/gridcraft-cli/src/check/package.rs +++ b/apps/gridcraft-cli/src/check/package.rs @@ -12,11 +12,8 @@ pub(super) const REBUILT_KINDS: [&str; 1] = ["calcchain"]; // ---------------------------------------------------------------- package validation -fn local_name(name: &[u8]) -> &[u8] { - match name.iter().rposition(|&b| b == b':') { - Some(i) => name.get(i + 1..).unwrap_or(name), - None => name, - } +fn local_name(name: &str) -> &str { + name.rsplit(':').next().unwrap_or(name) } /// `(element local name, attributes)` for every start/empty element of an XML part. @@ -37,12 +34,12 @@ fn xml_elements(xml: &[u8]) -> Result, String> { .map(|a| { let v = match a.normalized_value(quick_xml::XmlVersion::Explicit1_0) { Ok(v) => v.into_owned(), - Err(_) => String::from_utf8_lossy(&a.value).into_owned(), + Err(_) => a.value.to_string(), }; - (String::from_utf8_lossy(local_name(a.key.as_ref())).into_owned(), v) + (local_name(a.key.as_ref()).to_string(), v) }) .collect(); - out.push((String::from_utf8_lossy(local_name(e.name().as_ref())).into_owned(), attrs)); + out.push((local_name(e.name().as_ref()).to_string(), attrs)); } Ok(Event::Eof) => break, Ok(_) => {} @@ -303,9 +300,9 @@ fn holds_something_uncached(zip: &mut Zip<'_>, name: &str, marker: &'static str, let take = f.size().min(MAX_XML_PART).min(*budget); *budget -= take; let mut reader = quick_xml::Reader::from_reader(std::io::BufReader::new(f.take(take))); - let wanted: Option<&'static [u8]> = match marker { - "comment" => Some(b"comment"), - "shape" => Some(b"shape"), + let wanted: Option<&'static str> = match marker { + "comment" => Some("comment"), + "shape" => Some("shape"), _ => None, }; let mut buf = Vec::new(); diff --git a/apps/gridcraft-web/Cargo.toml b/apps/gridcraft-web/Cargo.toml index 41059d3792..b760867b1f 100644 --- a/apps/gridcraft-web/Cargo.toml +++ b/apps/gridcraft-web/Cargo.toml @@ -26,4 +26,4 @@ rfd = { workspace = true } wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" js-sys = "0.3" -web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "HtmlIFrameElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration", "Clipboard", "ClipboardItem", "Navigator", "MessageEvent", "UrlSearchParams", "AddEventListenerOptions", "Storage"] } +web-sys = { version = "0.3", features = ["Window", "Document", "Element", "Event", "EventTarget", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "HtmlIFrameElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration", "Clipboard", "ClipboardItem", "Navigator", "MessageEvent", "UrlSearchParams", "AddEventListenerOptions", "Storage", "MediaQueryList", "CustomEvent", "CustomEventInit"] } diff --git a/apps/gridcraft-web/src/host.rs b/apps/gridcraft-web/src/host.rs index 2286023e52..f7e3671be9 100644 --- a/apps/gridcraft-web/src/host.rs +++ b/apps/gridcraft-web/src/host.rs @@ -1,17 +1,38 @@ -//! The `postMessage` bridge to a same-origin parent page (`?host=parent`), for pages that embed -//! GridCraft in an `