* UI: RefEdit reference boxes in dialogs; Goal Seek picks its cells on the grid A reference box (RefEdit) is a text field that takes a range by selection on the grid. While a box is the one being picked into (it was clicked into or collapsed), clicking or dragging on the sheet writes the absolute reference into it, with the sheet name when the selection is on another sheet; typing still edits it, and the box takes the keyboard back after a pick. Focusing another text field of the dialog ends picking. The collapse button beside each box shrinks the dialog to that box, at the dialog's top-left corner, so the sheet is free for selecting. Escape, Enter or the expand button restore the dialog. The sheet and selection from before picking are restored before the dialog runs its command (references without a sheet name mean the sheet the dialog was opened on) and when it closes. The button is focusable, works with Space and Enter, and is named for screen readers; the icons are drawn in code. Goal Seek's Set cell and By changing cell are reference boxes. The snapshot example's --refedit key=range collapses the open dialog to a box and selects a range, to render the collapsed state. * UI: Solver and Data Analysis take their ranges with RefEdit boxes Form dialogs gain a Ref field, drawn as a reference box with its collapse button. Data Analysis uses it for the input, variable, X and Y and bin ranges, and for Output Range, where picking or typing a reference also chooses that output option. Solver Parameters uses reference boxes for the objective, the variable cells and both sides of a new constraint. * UI: Data Analysis keeps New Worksheet Ply when chosen after picking an Output Range The Output Range box chose its option on every frame while it was the box being picked into, so clicking New Worksheet Ply after using the box reverted to Output Range on the next frame. The box now chooses Output Range only when it is clicked into or its text changes (typed or picked), and choosing another output option ends picking into it, so a later selection on the grid doesn't fill it either. * UI: RefEdit boxes remember the dialog's sheet by name The sheet a dialog was opened on was kept by index, so a sheet inserted or moved while picking (the ribbon stays usable) made a range on the new sheet lose its sheet name, and closing the dialog went back to whichever sheet now had that index. The sheet is now kept by name, as New Formatting Rule's formula box does. * UI: switching sheets while picking into a RefEdit box doesn't fill it A reference on another sheet carries the sheet name, so after a click on another sheet tab the selection never matched the one last seen, and the switch alone wrote that sheet's remembered selection into the box, replacing a typed reference. The box now notes which sheet its last seen selection was on: showing another sheet only refreshes it, and a selection made on that sheet still fills the box. * UI: a RefEdit box for one reference takes only the area being selected A selection of several areas (Ctrl+click) was written into every box as a comma-joined union, which Goal Seek, Solver's objective and constraints, and Data Analysis then refused as an invalid reference. Solver's variable cells still take every area; the other boxes take the area being selected. * UI: RefEdit boxes follow the dialog's sheet when it is renamed The dialog's own sheet was kept by name only, so renaming it while a reference box picked made references on it carry the new name as if it were another sheet, and running or closing the dialog could not put the selection back. The sheet's index and the sheet count are now kept with the name: when the name disappears while the count stays the same, the sheet at that index was renamed and its new name is taken. Inserting, moving or deleting other sheets still follows the name. * UI: a deleted dialog sheet is never taken for another sheet The dialog's own sheet was taken as renamed whenever its name was gone and the sheet count matched the last frame's, so deleting it was read as a rename a frame later (or after a sheet was inserted), and the surviving sheet at that index became the dialog's sheet: unqualified references ran against it and Cancel put the old selection on it. The sheet names of the last frame are now kept in order. A rename is taken only when the list keeps its length and only the home sheet's entry changed; any other loss of the name marks the sheet as deleted for good. Once it is deleted, picks carry their sheet's name, closing leaves the selection where it is, and OK is refused with a message, since references without a sheet name would otherwise run against whichever sheet is shown. * UI: RefEdit dialogs keep their sheet until they close OK put the saved selection back and dropped the dialog's sheet with it. When the command then failed, the dialog stayed open without its sheet: a later deletion of that sheet went unnoticed and a second OK ran references without a sheet name against the sheet shown, as it also did when the person had only moved to another sheet after the failed OK. The sheet is now kept, and followed through renames and deletion, until the dialog closes; only the saved selection is put back once. A later OK or Cancel shows the dialog's sheet again (or is refused once it is deleted), and picking again on that sheet saves its selection anew. * UI: Solver's Model Range is a RefEdit box * UI: a collapsed RefEdit box keeps picking when something else is clicked * UI: a dialog opened over a picking RefEdit dialog puts the selection back * UI: arrow keys select on the grid while a dialog is collapsed to a RefEdit box * UI: F4 cycles the anchors of the reference in a RefEdit box * UI: tests for Enter while collapsed and OK after a cross-sheet pick * Docs: RefEdit boxes' keyboard route, Model Range and click-elsewhere behaviour * UI: a confirmed RefEdit dialog stays on the sheet its command showed * UI: scripted dialog cancel and confirm end a RefEdit pick like the buttons * UI: RefEdit boxes run their selection and sheet commands through the app * Docs: RefEdit dialogs after OK, scripted cancel and confirm * UI: a RefEdit dialog's OK stops when the picked sheet's draft cannot be saved * Docs: ui.dialog.confirm applies to command-backed dialogs; a draft that cannot be saved stops OK * UI: Cancel of a RefEdit dialog is never a confirmation, and says when it cannot go home * Docs: cancelling a RefEdit dialog when its sheet cannot be shown again * UI: a RefEdit dialog follows its sheet's rename only through the engine's recorded steps * UI: F4 in a RefEdit box splits areas only at commas outside quoted sheet names * Docs: how a RefEdit dialog follows its sheet's rename * UI: a RefEdit dialog's sheet is told from a new sheet given its name The dialog followed its sheet by name and read the recorded steps only once the name was gone, so a sheet deleted and recreated under its own name between two frames was taken for the dialog's sheet, and OK resolved unqualified references against the replacement. The home sheet is now followed through every recorded step since the dialog last looked: a sheet with its name is the same sheet only while its file id is unchanged (a sheet inserted in the session has none), a rename keeps the file id and the cells, and a deleted sheet is remembered as it was, so only an undo that brings that very sheet back revives it. When the steps are not accounted for, a sheet with the name is taken for it, as before. A sheet never saved to a file and replaced inside one undo group is the one case nothing in the model tells apart; the code comment says so. * Docs: how a RefEdit dialog tells its sheet from a new sheet given its name * UI: a RefEdit dialog follows a sheet that came from a file by its file id A sheet read from a file keeps the file's sheetId, which no step changes and which a sheet inserted in the session lacks, so the dialog's sheet is found by it wherever a recorded step puts it and whatever the step renames it to: a group that renames the sheet and inserts a new one under its old name follows the rename instead of refusing OK. When the step also copied the sheet (a copy keeps the id), the one keeping the name is taken. A sheet never saved to a file is still followed by name, with the rename rule as before. * Docs: a RefEdit dialog follows a sheet that came from a file by its file id * UI: a RefEdit box the dialog stops showing is no longer picked into A reference box compares the grid selection with the one it last saw only while it is drawn, so a box hidden while it was the one being picked into (Solver's Options view, the constraint value once the operator is int or bin) took a selection made meanwhile when it came back. After the dialog's window is drawn, a target box that was not drawn in the frame ends the pick, whatever hid it; Model Range no longer needs its own rule when the Load/Save area is hidden. * UI: the Name Box keeps its keys while a dialog is collapsed to a RefEdit box The collapsed picker took Escape, Enter and the arrow keys out of the frame's input whoever had the keyboard. The Name Box, drawn before the dialogs, had already read an arrow when the picker removed the same event and moved the grid selection, and the box then took that selection and the keyboard; its Escape expanded the dialog instead of only leaving the box. The picker now acts on those keys only when no other text field owns the keyboard, judged by the focus at the end of the frame before as well as now, since egui drops the focus for Escape before anything is drawn. The other field's Escape and Enter are still used up, so the dialog neither closes nor confirms on them. * Docs: a sentence break in the ui.dialog row of the control protocol * UI: a RefEdit dialog keeps its sheet by the sheet's identity, not by file id or history The dialog's sheet was followed by name through the recorded undo steps, with the file's sheet id telling a saved sheet from a new one; a copy made with sheet.move keeps that id, so once the original was deleted the copy, as the only sheet of the id, was taken for the dialog's sheet and OK ran against it. The model now gives every sheet an identity while the app runs (SheetUid: fresh for every sheet added, inserted, copied or read, kept through renames, moves, undo and redo), so Home keeps the sheet's uid and finds the sheet by it each time it is needed. The history walk, the rename rule and the file-id and Arc comparisons are gone, with the undetectable single-step case they left: a sheet given the name, a replacement inside one undo group and a copy are all other sheets, and an undo that brings the original back makes it the dialog's again. Nothing of the unaccounted-steps fallback is kept, since the identity always answers. Regression test: copy the dialog's sheet, delete the original, OK is refused; undo, OK runs on the original. The identity tests no longer need a workbook that came through a file. * UI: Solver's Reset All, Load, Save and Replace act on the dialog's sheet, as Solve does Only Solve went through the dialog's home-sheet checks before running (dialogs::show): Reset All, Load Model, Save Model and Replace ran their commands straight from the form, on whichever sheet was active. After a reference box picked on another sheet and the dialog expanded, that sheet was still shown, so Reset All removed its model instead of the dialog's sheet's, and Load and Save read and wrote the model range on it. The four buttons now run only after the same checks as Solve: the dialog's sheet must still exist (else the command is refused with the deleted-sheet message) and is shown again with the selection from before picking put back (a failure to show it, a text-box draft that could not be saved, is shown as the dialog's error and the command does not run). Goal Seek has no other buttons, and Data Analysis's Replace goes through the confirm path that Solve uses, so neither needed a change. Regression tests: after a cross-sheet pick, Reset All clears the dialog's sheet's model and leaves the other sheet's; Save and Replace write the dialog's sheet; Load reads it. * UI: Protect Sheet opened while a reference box picks on another sheet acts on the dialog's sheet * Docs: a scripted Goal Seek after a cross-sheet pick cancels the dialog before data.goalSeek * UI: the Protect Sheet labels follow the dialog's sheet while a reference box picks on another * Docs: a scripted Goal Seek checks that the dialog's sheet still exists after cancel * Control: ui.dialog.cancel replies sheetDeleted when the dialog's own sheet is gone, even if its name was reused * M2: allow shared-runner variance in the pathological math timing gate * M2: refuse sheet protection actions after a reference dialog home is deleted --------- Co-authored-by: FredApps <FredApps@users.noreply.github.com>
GridCraft
Spreadsheets and data; an open-source, high-performance modern spreadsheet application, built in pure Rust.
A fast, open-source, clean-room take on modern spreadsheet workflows. It runs natively on macOS,
Windows, Linux and BSD, and in the browser via WebAssembly.
By the ArtCraft team.
GridCraft on getartcraft.com · ArtCraft · All Crafting Apps
Quarterly Sales Dashboard: a table with a totals row, conditional formatting, sparklines and two live charts — built entirely from GridCraft commands (File → New → Sample).
Note
ArtCraft is a community of artists from all walks of life. Painters, photographers, filmmakers, illustrators, designers, animators, hobbyists, and people who picked up a pencil last week. If you make things, you're one of us. Come say hi on Discord.
A look around · Why GridCraft · Quick start · For agents · Architecture · Roadmap · The Crafting Apps · License and credits
A look around
Why GridCraft
- Familiar. Ribbon, formula bar, Name Box, sheet tabs, keyboard shortcuts, fill handle, point-mode formula entry with coloured references, Format Cells, Paste Special, conditional formatting, tables, charts and more. Everything is right where you expect it.
- Compatible. XLSX is the native format: styles, themes, formulas (including dynamic arrays),
tables, conditional formats, validation, comments, hyperlinks, charts, pictures, sparklines,
pivot tables and print settings round-trip. CSV and TSV too.
Legacy 97-2003
.xlsworkbooks open with their values, formulas and number formats (formatting and objects not yet). XLSB worksheet data can be imported, using saved values instead of formulas. ODS worksheet data can be imported, using saved values instead of formulas. Static pictures can live inside cells, fitting their size and moving with the data. - Capable. A dependency-graph calculation engine with dynamic arrays and spilling, 500+ worksheet functions plus LET, LAMBDA, MAP, REDUCE, SCAN, BYROW, BYCOL and MAKEARRAY, structured table references, a comprehensive number-format language, sort and AutoFilter, Flash Fill, data validation, outlining and subtotals.
- Fast. Copy-on-write workbooks (undo snapshots are nearly free), incremental recalculation, and a virtualised grid that only lays out what's on screen.
- Agent-native. Every ribbon button, menu item, dialog, keystroke and mouse gesture is a command that agents can run over a JSON control channel, an MCP server, or the CLI.
- Everywhere. One Rust codebase for desktop and the web. No subscription, no licence server, no telemetry.
Quick start
cargo run --release -p gridcraft # desktop app
cargo run --release -p gridcraft -- --sample sales # open a sample (sales, budget, grades)
cargo run --release -p gridcraft -- book.xlsx # open a workbook
cargo run --release -p gridcraft -- --sample sales --control 7979 # + JSON control channel
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. Workbooks, clipboard exchange, and programmatic APIs retain canonical English syntax.
Web
cd apps/gridcraft-web && trunk build --release # → dist/web (serve it with any static server)
cd apps/gridcraft-web && trunk serve --release # http://127.0.0.1:8771 (?sample=sales opens a sample)
You need trunk and the wasm32-unknown-unknown target. Open uses the
browser's file picker (dropping files works too) and Save downloads the workbook.
Ribbon access keys
Press and release left Alt on Windows/Linux/BSD, or press F10 on any platform, then type a sequence below one key at a time. Keytips appear on the supported Home controls and menu items. On macOS, use F10 (Fn+F10 if required by your keyboard); Option remains available for entering accented characters.
| Sequence after Alt/F10 | Action |
|---|---|
H A L / H A C / H A R |
Align left / center / right |
H V S or legacy E S |
Open Paste Special |
H O I or legacy O C A |
AutoFit selected columns |
In Paste Special, T/V/F/C select formats/values/formulas/comments, and E toggles Transpose. Enter applies the selected options; Escape cancels the dialog. These letters also work with Alt held on Windows/Linux/BSD.
Escape backs out one keytip level; Alt/F10 again exits keytips. Clicking, scrolling, or switching windows cancels navigation. Access keys do not start while editing text or using another dialog. This is initial support for these Home and legacy paths; other ribbon commands and full mouse-free navigation remain to be implemented. Browsers or desktop environments may reserve Alt/F10 for their own menus.
Logs
The desktop app writes its log to standard error and to gridcraft.log in the logs folder
next to its settings: ~/.config/gridcraft/logs on Linux and BSD ($XDG_CONFIG_HOME/gridcraft/logs
when set), ~/Library/Application Support/GridCraft/logs on macOS and
%APPDATA%\GridCraft\logs on Windows. Each start moves the previous log to gridcraft.1.log and
that one to gridcraft.2.log, so the log of a run that crashed survives the next start; attach
them to a bug report. A log file stops growing at 16 MiB. By default GridCraft's own crates log
at info and everything else at warn; RUST_LOG replaces that with env_logger-style
directives, e.g. RUST_LOG=debug or RUST_LOG=warn,gridcraft_engine=debug (a trailing *
matches a prefix: gridcraft*=debug). Runs with GRIDCRAFT_NO_PREFS set (no saved settings)
log to standard error only.
For agents: MCP, CLI and the control channel
claude mcp add gridcraft -- gridcraft-cli mcp # headless MCP server
gridcraft-cli mcp --connect 7979 # MCP bridged to a running window
gridcraft-cli eval '=XLOOKUP("b",{"a","b"},{1,2})' # → 2
gridcraft-cli run --sample budget --cmd 'home.bold={"range":"B4:F4"}' --out budget.xlsx
gridcraft-cli cat budget.xlsx --range B4:F15 # aligned text table
gridcraft-cli commands --search chart # every command and its params
The MCP server offers tools such as read_range, write_range, set_cell, evaluate_formula,
format_range, insert_chart, create_table, sort_range, filter and execute_command
(which reaches every one of GridCraft's commands). The desktop app's control channel adds real
pointer and keyboard input, dialogs and screenshots. See docs/mcp.md,
docs/cli.md and docs/control-protocol.md.
Architecture
GridCraft is an engine-first Cargo workspace with enforced layering (cargo xtask layers). The
egui frontend is a separate crate, so the UI can be swapped without touching the engine.
| Layer | Crates |
|---|---|
| L0 | core (addresses, values, errors, dates, input parsing) · numfmt (number format codes) |
| L1 | formula (lexer, parser, printer, reference adjustment) · functions (worksheet function library) |
| L2 | model (workbook, sheets, copy-on-write cells, styles, tables, charts…) · calc (dependency graph, evaluator, dynamic arrays) · xlsx (XLSX, CSV, plus XLSB and ODS data import) |
| L3 | chart (toolkit-free chart layout and rendering) |
| L5 | engine (session, commands, history, clipboard, fill, sort, filter, file I/O) |
| L6 | ui-egui (spreadsheet UI, control channel) · mcp (MCP server) |
| L7 | apps/gridcraft, apps/gridcraft-cli, apps/gridcraft-web |
- Contributor and agent rules (clean-room, asset policy, never-crash, quality gates):
AGENTS.md - Bundled assets: each one is listed with its licence in
ATTRIBUTION.md - Releases: signed macOS, Windows, Linux, BSD and web builds; see
docs/releasing.md
Roadmap
GridCraft is in alpha: the core workflow works end to end, but depth, fidelity and file
compatibility are still rough. It covers 89% of standard ribbon commands in our catalog
(docs/parity-checklist.md) and 513 standard worksheet functions
(docs/function-parity.md); this is a parity inventory, not a count of working implementations. Weighted by what spreadsheet users rely on, we
estimate it is about 50% ready for real work. Next up: XLSX fidelity on real-world files, very large
workbooks and multi-threaded recalc, chart depth, text in every script, and .xls import. Stage,
numbers and estimates are in ROADMAP.md; the ranked work list is docs/gaps.md.
Downloads
Download GridCraft from GitHub: the latest release has every build listed below, and all releases has earlier versions and their notes. <ver> in the file names is the version number, and SHA256SUMS.txt lists a checksum for every file.
Windows
| Build | Installer | Portable |
|---|---|---|
| x64 (64-bit Intel/AMD) | gridcraft-<ver>-windows-x64.msi |
gridcraft-<ver>-windows-x64-portable.zip |
| arm64 (Snapdragon and other ARM PCs) | gridcraft-<ver>-windows-arm64.msi |
gridcraft-<ver>-windows-arm64-portable.zip |
| x86 (32-bit) | gridcraft-<ver>-windows-x86.msi |
gridcraft-<ver>-windows-x86-portable.zip |
Installers and executables are code-signed. The MSI installer offers an optional desktop shortcut (unchecked on a new installation).
If the app doesn't open on Windows: the desktop app initializes only DirectX 12 by default.
Letting wgpu also create an OpenGL instance can crash some graphics drivers (AMD's
atio6axx.dll) before the window appears, so the app would flash in Task Manager and quit.
WGPU_BACKEND overrides the default for troubleshooting (for example dx12 or vulkan). In
PowerShell, from the folder containing the executable:
$env:WGPU_BACKEND = "vulkan"
& .\gridcraft.exe
Remove-Item Env:WGPU_BACKEND # restore the default for later launches
An explicit gl override can bring the driver crash back on affected systems, and vulkan can
bring back a crash in some Intel UHD Graphics drivers (igvk64.dll, #120). The macOS, Linux and
web backend defaults are unchanged.
If the app is slow on an older Windows PC, or DirectX 12 can't start: when DirectX 12 finds no
graphics card with a driver for it, it only offers the Windows-provided software renderer, which draws every
frame on the processor. In that case GridCraft uses OpenGL instead, without setting anything; if
the first renderer fails to start, it tries the other one once. The log file records the choice
(lines starting with graphics:). WGPU_BACKEND still overrides it (and then nothing else is
tried), e.g. $env:WGPU_BACKEND = "gl" or "dx12".
DirectX 12 shaders are compiled with FXC, which is part of Windows, so no dxcompiler.dll or
dxil.dll is needed, and a copy another program put on PATH can no longer stop the app at start
("Parent device is lost", #339). $env:WGPU_DX12_COMPILER = "dxc" (or "auto") selects wgpu's DXC
handling again for troubleshooting.
macOS
| Build | File | Notes |
|---|---|---|
| App, universal (Apple silicon + Intel) | gridcraft-<ver>-macos-universal.dmg |
Signed and notarized |
| Command-line tool, universal | gridcraft-cli-<ver>-macos-universal.zip |
Signed and notarized |
Linux
| Format | x86_64 | aarch64 (ARM64) | Notes |
|---|---|---|---|
| AppImage | gridcraft-<ver>-linux-x86_64.AppImage |
gridcraft-<ver>-linux-aarch64.AppImage |
Runs anywhere; updates itself with AppImageUpdate (.zsync files) |
| Flatpak | gridcraft-<ver>-linux-x86_64.flatpak |
gridcraft-<ver>-linux-aarch64.flatpak |
Sandboxed; flatpak install --user <file> |
| Debian/Ubuntu | gridcraft-<ver>-linux-x86_64.deb |
gridcraft-<ver>-linux-aarch64.deb |
|
| Fedora/RHEL/openSUSE | gridcraft-<ver>-linux-x86_64.rpm |
gridcraft-<ver>-linux-aarch64.rpm |
|
| Tarball | gridcraft-<ver>-linux-x86_64.tar.gz |
gridcraft-<ver>-linux-aarch64.tar.gz |
Unpack anywhere |
FreeBSD
| Build | File |
|---|---|
| x86_64 | gridcraft-<ver>-freebsd-x86_64.tar.gz |
Web (WebAssembly)
| Build | File | Notes |
|---|---|---|
| Static site | gridcraft-web-<ver>.zip |
Runs in a modern browser; host it on any static server |
The Crafting Apps
GridCraft is one of the Crafting Apps: free, open-source creative tools from the ArtCraft team, each written from scratch in Rust and each able to stand on its own.
| App | What it's for | Code | Learn more | |
|---|---|---|---|---|
| PhotoCraft | Image editing: layers, masks, type and real PSD files | GitHub | Website | |
| VectorCraft | Vector illustration | GitHub | Website | |
| FilmCraft | Video editing, color and sound | GitHub | Website | |
| LightCraft | Photo library and raw development | GitHub | Website | |
| PdfCraft | Reading, organizing and protecting PDFs | GitHub | Website | |
| EffectCraft | Motion graphics and visual effects | GitHub | Website | |
| DesignCraft | Page layout and publishing | GitHub | Website | |
| FigCraft | Interface design and prototyping | GitHub | Website | |
| WordCraft | Word processing and documents | GitHub | Website | |
| SoundCraft | Audio recording and editing | GitHub | Website | |
| DeckCraft | Presentations and slide decks | GitHub | Website | |
| CADCraft | Computer-aided design | GitHub | Website | |
| GridCraft | Spreadsheets and data | GitHub | Website | |
| TextureCraft | 3D texture painting | GitHub | Website | |
| Craft Anything | See how any software works, then build your own | GitHub | Website | |
| SolidCraft | Parametric 3D CAD by Tangency Labs (community app) | GitHub |
And ArtCraft itself, our AI image and video studio for artists who want real control.
Come make things with us
Our Discord is where artists of every kind hang out: people who paint, shoot, draw, cut film, set type, and people still figuring out what they like to make. Share what you're working on, ask for help, tell us what's broken, or tell us what you wish these tools could do. Whatever your medium and however long you've been at it, you're welcome here.
discord.gg/artcraft · getartcraft.com · The Crafting Apps · GridCraft
License and credits
GridCraft is dual-licensed under MIT or Apache-2.0, at your option. Copyright (c) 2026 ArtCraft Team and the GridCraft contributors. Required notices are in NOTICE.
Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed with its author, source and license in ATTRIBUTION.md.
All UI icons are drawn in code and are original; the sample workbooks and every screenshot in this README were made with GridCraft itself. GridCraft bundles no fonts: it uses the fonts installed on your system.
The ArtCraft name, wordmark and logos in docs/brand/ are trademarks of the
ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as
part of this repository and GridCraft, under docs/brand/LICENSE-brand.txt.
Forks and modified versions must remove them.
Made by the ArtCraft team and community.





