OpenCalc · alpha · successor to Casual Sheets

A spreadsheet engine you can put anywhere.

OpenCalc is an embeddable spreadsheet engine written in Rust. It reads and writes .xlsx, calculates it, renders a live canvas grid, and runs the whole engine client-side in WebAssembly — so co-editing scales without sticky sessions and the editor keeps working when the network hiccups. It is the ground-up successor to Casual Sheets: same job, rewritten as a deterministic engine instead of a web app.

Apache-2.0. Alpha — the engine, editor and SDK are live and the co-editing stack runs, but interfaces still move and the npm line is published at 0.0.0. Pin what you test against.

calc.casualoffice.org/editor.html

Open one of your own .xlsx files in it. Nothing is uploaded — the engine, the calculation, and the save-back all happen in the browser.

Status
alpha
Functions
347 / 356
Engine
Rust → WASM
License
Apache-2.0
Why it is built this way

The engine runs in the browser.
The server only orders operations.

No sticky sessions

The server orders operations rather than rendering pixels. A client can go offline briefly without the document freezing, and nodes scale horizontally without consistent hashing — any node relays to the document’s leader, so a plain load balancer works.

Nothing is lost silently

Unmodelled parts of a workbook — VBA, OLE objects, form controls, custom XML — are retained byte-for-byte. Anything the model genuinely cannot keep is counted and named in a compatibility report rather than dropped quietly.

Determinism is a gated contract

The same input and version produce identical model, values, layout and bytes. Time and randomness are supplied, never sampled — an engine that reaches for the wall clock cannot be tested, replayed, or agreed on by two hosts.

Access is enforced, not hidden

configure({ access: "view" }) is enforced inside the engine, not by removing buttons. The editor is a custom element with a shadow root, so host CSS cannot reach in and its CSS cannot reach out.

What it does

A real spreadsheet, not a grid widget.

Formats

.xlsx round-trips as a semantic fixed point, gated in CI. CSV/TSV/PSV with RFC 4180 quoting. It reads what other writers actually emit: OOXML booleans in either spelling, theme + tint and legacy indexed colours, shared formulas, and multi-area sqref.

ƒ

Calculation

347 of the spec’s 356 functions. Dynamic arrays that spill and refuse (#SPILL!) rather than overwrite. LET and LAMBDA with first-class function values, plus MAP/REDUCE/SCAN/BYROW/BYCOL. Automatic or manual mode, taken from the file’s own <calcPr>.

Features

Pivot tables with eleven aggregates and GETPIVOTDATA; charts written as real chart parts, not pictures; conditional formatting; data validation; tables and autofilter; comments; number formats including sections and [Red].

The editor

A WASM canvas grid: shared inline / formula-bar editing, autocomplete and argument hints, reference picking, F4 anchor cycling, a range finder, find & replace, multi-sheet tabs, and undo/redo.

Collaboration

Server-mediated OT with presence, resume and pipelining. Version history is kept at the host — a version is taken before each save, so history holds what the document was rather than what it became.

WOPI for file stores

Nextcloud, ownCloud, SharePoint, Moodle and Alfresco install an editor the same way. Start the adapter, paste one discovery URL into their settings, and set OPENCALC_WOPI_ALLOWED_HOSTS. Only formats that round-trip unchanged are advertised — a format it would rewrite is silent data loss with an administrator’s blessing.

Embed it

One element in a web app, one session in a Rust host.

In a web app
npm install @opencalc/sheet
import "@opencalc/sheet";        // <opencalc-sheet style="height:600px">

const sheet = document.querySelector("opencalc-sheet");
await sheet.ready;

await sheet.configure({ access: "view" });  // enforced in the engine,
await sheet.open(bytes, "budget.xlsx");     // not by hiding buttons

sheet.on("cellsChanged", async (e) => {
  if (e.source !== "api") persist(await sheet.save());
});

The WebAssembly binary is served from your origin — a Web Worker cannot be constructed from a cross-origin URL, and a CDN would foreclose ever moving the engine off the main thread. opencalc-assets copies it into a directory you serve, and the element refuses to boot on a JavaScript ↔ engine version skew rather than returning a wrong answer.

In a Rust host
use casual_calc_sdk::{Environment, SessionConfig, WorkbookSession};

let config = SessionConfig::new()
    .with_limits(limits)        // what an untrusted upload may allocate
    .with_undo_depth(200)
    .with_environment(Environment { now: today_serial, seed });

let mut session = WorkbookSession::open_with(bytes, config)?;

with_limits bounds what an untrusted upload may allocate. Environment supplies the clock and the seed, which is what makes a session replayable. Published npm packages: @opencalc/sheet, @opencalc/react, @opencalc/engine.

Honest status

Alpha — live, but still moving.

Working today

The engine, the browser editor, the co-editing server, the npm SDK and the WOPI adapter all run. Two containers gets you co-editing over a link; a cluster is two nodes behind nginx coordinating through Redis.

Known gaps

No PDF export and no .ods support yet. Interfaces still move while the SDK sits at 0.0.0 — pre-1.0, a rename is a minor version.

Relationship to Casual Sheets

Casual Sheets is the shipping TypeScript web app and is what sheet.casualoffice.org runs today. OpenCalc is its successor and is not yet a drop-in replacement — both are developed in the open, and the engine is where new work goes.

Try it, then embed it. The editor above is the real engine. When you want it in your own app, npm install @opencalc/sheet — or run docker compose up from the repo for the two-container co-editing demo.