Aller au contenu
← Retour aux projets

Sheets

#Sheets

image

This repo holds two things, sharing one lineage of IronCalc work:

  • lib/@vektorapp/spreadsheet, a Solid spreadsheet library built on IronCalc's engine and a port of its canvas grid renderer. vektor installs it from this repo as a git dependency, so its build in dist/ is committed. See lib/README.md.
  • app/ — Sheets, the desktop editor described below.

The two fixes in app/patches/ (pixel-smooth scrolling, bounded text wrapping) are re-implemented in the library's port; see lib/grid/README.md.


#Sheets

A minimal, offline desktop table editor. It embeds the IronCalc spreadsheet UI (WebAssembly engine + React grid) inside a Tauri shell, and reads/writes real .xlsx files. Single file at a time — no accounts, no cloud, no sharing.

#Features

  • Open / edit / save .xlsx workbooks, .csv files, and IronCalc's native .ic format.
  • Formulas, formatting, multiple sheets — the full IronCalc grid.
  • Native menu bar — File ▸ New / Open… / Save / Save As… with ⌘N / ⌘O / ⌘S / ⌘⇧S.
  • File ▸ Export ▸ Excel Workbook (.xlsx) or CSV (.csv, active sheet) — a copy, without changing the file you're editing.
  • Open files by dragging them onto the window, or by double-clicking an .xlsx / .csv / .ic in Finder (see Install on macOS).
  • Fully offline; nothing leaves your machine.

#How it works

IronCalc splits across the JS/Rust boundary:

  • Frontend (app/src/) — React + Vite. The IronCalc WASM engine edits the workbook in memory. It only understands IronCalc's native .ic byte format.
  • Backend (app/src-tauri/) — Rust. All .xlsx.ic conversion happens here via the ironcalc crate, exposed through three commands (open_file, save_file, save_file_as) that also drive the native file dialogs. CSV is parsed/rendered here too (see below).

#CSV

Opening a .csv builds a one-sheet workbook: the separator is sniffed (,, ; or tab), quoted fields follow RFC 4180, and non-UTF-8 files fall back to Latin-1. Fields go in through set_user_input, so numbers, dates and = formulas are recognised exactly as if typed into the grid.

Saving a document whose path ends in .csv writes CSV back — the active sheet only, as displayed values (formulas become their results, formatting is lost). Use Save As… with .xlsx to keep everything.

#The vendored IronCalc packages

IronCalc's .ic format is bitcode-serialized and is only compatible between a WASM build and the Rust crate when both come from the same commit. The published npm packages don't line up with any released crate, so this repo vendors WASM + UI built from source at tag v0.7.1 (matching ironcalc = "0.7.1" in app/src-tauri/Cargo.toml):

  • app/vendor/ironcalc-wasm/@ironcalc/wasm, wasm-pack --target web
  • app/vendor/ironcalc-workbook/@ironcalc/workbook, the React UI (vite build)

The UI is built with the local patches in app/patches/ applied on top of the tag: the grid scrolls smoothly by the pixel instead of jumping a whole row/column, and it no longer re-wraps every visible cell's text on every frame. See app/patches/README.md.

To rebuild them (e.g. to bump the pinned version), run task vendor (see app/scripts/build-ironcalc.sh) then bun install to refresh the copies under node_modules.

#Prerequisites

  • Bun and Rust (Tauri needs a native toolchain).
  • mise install provides Bun + Task; Rust is expected system-wide.
  • To rebuild the vendored packages you also need wasm-pack, the wasm32-unknown-unknown target, and python3.

#Develop

task dev        # or: bun run tauri dev

First run compiles the Rust backend (a few minutes); afterwards the frontend hot-reloads.

#Build

The library builds separately, and its output is committed:

task build:lib  # vite + tsc -> dist/, which is in git
task build:app  # or, from app/: bun run tauri build  -> native bundle in app/src-tauri/target/release/bundle

task web runs just the Vite frontend in a browser, but file open/save need the Tauri shell.

#Install on macOS

task install    # build, copy to /Applications, sign, register file associations

Sheets then appears under Open With for .xlsx, .csv and .ic, and double-clicking one opens it (Get Info ▸ Open with ▸ Change All… to make it the default).

#Why double-clicking a downloaded .xlsx fails

tauri build leaves the bundle linker-signed only — codesign -dvvv reports Info.plist=not bound and Sealed Resources=none. app/scripts/sign-macos.sh re-signs it ad-hoc, which binds Info.plist and seals resources; task build and task install run it automatically, and task sign re-runs it alone.

That makes the bundle assessable, but ad-hoc is not trusted:

$ spctl -a -vvv -t exec /Applications/Sheets.app
/Applications/Sheets.app: rejected

macOS will not launch a Gatekeeper-rejected app to open a quarantined document — any file that arrived via a browser, Mail or Slack carries com.apple.quarantine. Finder reports this as:

Apple could not verify "….xlsx" is free of malware that may harm your Mac.

The block happens inside LaunchServices before Sheets starts, so it cannot be handled in app code. Notarization is the only fix. (spctl --add, the old per-app whitelist, was removed in macOS 15 — see man spctl; the documented replacement is an MDM SystemPolicyRule profile.) Opening the same file from File ▸ Open inside a running Sheets is unaffected, since no LaunchServices assessment is involved.

#Signing + notarizing properly

Requires a paid Apple Developer Program membership ($99/yr). A free Apple ID issues only "Apple Development" certificates, which cannot be notarized.

  1. Get a Developer ID certificate. Xcode ▸ Settings ▸ Accounts ▸ (your Apple ID) ▸ Manage Certificates ▸ +Developer ID Application. Then confirm it landed in the login keychain:

    security find-identity -v -p codesigning
    # 1) ABC123…  "Developer ID Application: Your Name (TEAMID)"
    
  2. Store notarization credentials once. Create an app-specific password at appleid.apple.com ▸ Sign-In and Security ▸ App-Specific Passwords.

    xcrun notarytool store-credentials "sheets" \
      --apple-id "you@example.com" --team-id TEAMID --password "xxxx-xxxx-xxxx-xxxx"
    
  3. Build. Tauri signs the app and, when the APPLE_* notarization variables are present, submits it to Apple and staples the ticket — all before the .dmg is assembled.

    export APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)"
    export APPLE_ID="you@example.com"
    export APPLE_PASSWORD="xxxx-xxxx-xxxx-xxxx"   # the app-specific password
    export APPLE_TEAM_ID="TEAMID"
    task install
    

    With APPLE_SIGNING_IDENTITY set, sign-macos.sh deliberately does not re-sign — that would invalidate the stapled notarization ticket. It only refreshes the LaunchServices registration and prints the resulting state.

  4. Verify. The build prints both of these; they should read:

    Gatekeeper: accepted source=Notarized Developer ID
    Stapled:    The validate action worked!
    

Notarization is what makes the hardened runtime mandatory, which is why the build passes --options runtime with app/src-tauri/entitlements.plist.

#Licence

The library in lib/ is MIT (see LICENSE). IronCalc, which both halves build on, is dual-licensed MIT OR Apache-2.0.

Nouvelle version disponible.