Architecture Deep Dive
Mog is an open-source spreadsheet engine built from scratch in Rust. This document describes the system architecture, covering the layered dependency model, compute core, rendering pipeline, collaboration protocol, file I/O, and platform targets.
Version: 0.1.0-alpha
Overview
Mog follows an OS-style layered architecture. Each layer has a well-defined role, a strict dependency direction, and a public API boundary. The dependency flow is:
The system comprises 100+ packages (53 TypeScript, 56 Rust crates) with clean boundaries between them. Each package has its own package.json or Cargo.toml, can be tested independently, and exposes a deliberate public API. Layers may only depend downward — never upward or laterally across layer boundaries.
Beneath the TypeScript layers sits the Rust compute core — 21 crates that handle formula evaluation, dependency tracking, recalculation, number formatting, file parsing, and collaboration. The core compiles to WebAssembly for browsers, N-API for Node.js, and PyO3 for Python, all from a single annotated source.
Architecture Diagram
The following diagram shows the full stack from application layer down to the Rust compute core. Each box represents a layer with its major subsystems.
Apps — End-user applications (the spreadsheet editor, dashboard builder, etc.) that compose shell components and call kernel APIs. Apps own routing, layout, and user-facing state.
Shell — Reusable UI components that render data views. Each view (GridCanvas, KanbanBoard, Timeline, Calendar, Gallery, Form) is a self-contained package that reads from the kernel and writes back through its API.
Kernel — The application logic layer. Exposes a typed API for cell operations, table management, record manipulation, and relation traversal. Houses cross-cutting services (clipboard, undo/redo, notifications) and bridge modules that translate between TypeScript domain models and Rust compute results.
Contracts — Pure TypeScript interface definitions. No runtime code. Every type shared across packages is defined here, ensuring type safety without introducing runtime coupling.
Compute Core — The Rust engine. All numerically intensive work — formula parsing, evaluation, dependency graph traversal, recalculation, formatting, file I/O — happens here. Compiled to WASM, N-API, or PyO3 depending on the target.
Compute Core
The compute core is a workspace of 21 Rust crates that implement the spreadsheet engine. It provides 582 Excel-compatible functions and handles all computationally intensive operations client-side, with no server dependency.
Crate Map
Formula Parser
The parser crate implements a hand-written recursive descent parser for the Excel formula language. It produces a typed AST that the evaluation engine walks. The parser handles cell references (A1, $A$1), structured references (Table1[Column]), array formulas, implicit intersection, and dynamic arrays. Error positions are tracked at byte granularity for precise diagnostics.
Dependency Graph and Recalculation
The graph crate maintains a directed acyclic graph of cell dependencies. When a cell value changes, the engine performs a topological traversal to recompute only the affected subgraph. Cycle detection runs at edit time. The graph supports range dependencies, cross-sheet references, and dynamic array spill ranges. Recalculation is incremental by default; a full recalc can be forced when needed.
Bridge Framework
Mog uses a custom proc-macro framework to generate FFI bindings from a single annotated Rust API. Annotate a function or struct with #[bridge::api] and the macro generates:
- •WASM bindings via
wasm-bindgenfor browser and Web Worker targets - •N-API bindings via
napi-rsfor Node.js native addons - •PyO3 bindings for native Python modules
#[bridge::api]impl Worksheet { /// Set a cell value by address. pub fn set_cell(&mut self, address: &str, value: CellValue) -> Result<()> { let coord = parse_address(address)?; self.document.set(coord, value)?; self.graph.mark_dirty(coord); Ok(()) }
/// Evaluate all dirty cells. pub fn calculate(&mut self) -> Result<CalcResult> { self.graph.recalculate(&mut self.document) }}This single definition produces type-safe bindings for all three targets. TypeScript type declarations are generated automatically. The bridge handles value marshalling, error propagation, and memory management across the FFI boundary.
Rendering Pipeline
Mog renders everything to HTML Canvas. There is no DOM-based grid. This gives precise control over layout, text rendering, and scroll performance at scale. The pipeline is designed for 60fps rendering even with large datasets.
Binary Wire Protocol
Viewport data is streamed from the Rust compute core to the canvas renderer via a compact binary protocol. The format is designed for zero-copy reads on the JavaScript side:
The 36-byte header encodes viewport bounds and offsets into the string pool and format palette. Each cell record is a fixed 32-byte struct, enabling direct indexing without parsing. String values are deduplicated in a shared pool. Format definitions (font, color, borders, alignment) are stored in a palette and referenced by index, avoiding per-cell duplication. The result: zero allocations per cell on the JavaScript side.
Spatial Indexing
The canvas system uses spatial indexing (R-tree) for hit-testing. When a user clicks or hovers, the engine performs a point query against the spatial index rather than iterating cells. This keeps interaction latency constant regardless of sheet size. The index covers cells, merge regions, drawing objects, chart boundaries, and overlay elements.
Overlay Layers
The renderer composites multiple layers: the cell grid (background), selection highlights, drag-and-drop indicators, resize handles, auto-fill previews, comment markers, and conditional formatting overlays. Each layer is an independent canvas that can be redrawn without touching the others, minimizing repaint cost during interactive operations like selection dragging or column resizing.
CRDT Collaboration
Mog's collaboration layer is built on Yrs, the Rust port of the Yjs CRDT library. Every edit produces a CRDT operation that can be merged with concurrent edits from other peers without conflict.
Cell Identity Model
Unlike traditional spreadsheets that address cells by grid position (row, column), Mog assigns each cell a stable UUID. This is the Cell Identity Model. When a user inserts a row, cells below that row shift visually, but their identity (and therefore their CRDT key) does not change. This means:
- •Concurrent structural changes (insert row + edit cell) compose correctly without manual conflict resolution.
- •Formula references are stable across inserts, deletes, and moves. A formula referencing cell
Xcontinues to reference that cell regardless of how the grid reshapes. - •Undo/redo operates on identity, not position, producing correct results even when other peers have modified the structure.
Offline and Peer-to-Peer
Because the CRDT is embedded in the client, Mog works fully offline. Edits accumulate as local CRDT operations and merge when the peer reconnects. No central server is required for conflict resolution — any two peers can sync directly. A collaboration server is available for relay and persistence, but the correctness of merges depends only on the CRDT, not the server.
Canvas System
The canvas system is split into 8 packages plus 6 drawing sub-packages. This separation keeps rendering logic modular and testable.
| Package | Responsibility |
|---|---|
| engine | Core canvas abstraction, render loop, coordinate system |
| grid-renderer | Cell painting, text layout, borders, merge regions |
| grid-canvas | Interactive grid with scroll, selection, editing |
| drawing-canvas | Drawing layer for shapes, images, charts |
| overlay | Selection, drag handles, resize indicators |
| spatial | R-tree spatial index for hit-testing |
| lab | Development sandbox for visual testing |
Drawing Sub-Packages
Drawing operations are further decomposed into 6 sub-packages, each handling a specific drawing primitive:
This decomposition mirrors the OOXML drawing specification, making it straightforward to map between the internal drawing model and the XLSX file format.
File I/O Pipeline
Mog includes a native XLSX parser and writer implemented entirely in Rust. The parser handles the full OOXML specification — worksheets, shared strings, styles, charts, pivot tables, conditional formatting, data validation, named ranges, and drawing objects.
Client-Side Processing
Because the parser runs in Rust compiled to WebAssembly, all file processing happens client-side. A user can open, edit, and save an Excel file without any data leaving their browser. This has meaningful implications for privacy, latency, and offline support.
User drops .xlsx file │ ▼Browser FileReader → ArrayBuffer │ ▼WASM: xlsx crate parses OOXML ZIP ├── Worksheets → Document model ├── SharedStrings → String pool ├── Styles → Format palette ├── Charts → Chart model └── Drawings → Drawing model │ ▼Kernel hydrates from Document model │ ▼Canvas renders viewportPDF Export
PDF export follows the same client-side pattern. The Rust pdf crate renders the document model to PDF format in WASM, producing a downloadable file without a server round-trip. Page layout, headers, footers, and print areas are respected.
Platform Targets
One Rust codebase, three platform targets. The custom #[bridge::api] proc-macro framework generates platform-specific bindings from a single annotated API definition, ensuring feature parity and type safety across all targets.
WebAssembly (Browser)
The primary target. The Rust core compiles to a .wasm binary via wasm-bindgen. It runs in a Web Worker to avoid blocking the main thread. Communication with the main thread uses the binary wire protocol over postMessage with Transferable ArrayBuffers for zero-copy transfer.
N-API (Node.js)
Native Node.js addon built with napi-rs. Prebuilt binaries are published for macOS (arm64, x64), Linux (x64, arm64), and Windows (x64). No Rust toolchain required for consumers. Ideal for server-side XLSX processing, headless spreadsheet evaluation, and CI pipelines.
PyO3 (Python)
Native Python extension built with PyO3 and distributed as a wheel. Integrates with the Python data ecosystem — pandas DataFrames, NumPy arrays, and Jupyter notebooks. Use Mog as a formula engine, XLSX parser, or spreadsheet-in-a-library from Python.
Package Structure
The repository contains 100+ packages: 53 TypeScript packages and 56 Rust crates. Each package is independently versioned, tested, and published. The monorepo uses pnpm workspaces for TypeScript and a Cargo workspace for Rust.
mog/├── packages/ # TypeScript packages (53)│ ├── contracts/ # Pure type definitions│ ├── kernel/ # Application logic layer│ │ ├── api/ # Cell, Table, Record APIs│ │ ├── services/ # Clipboard, Undo, Notifications│ │ └── bridges/ # Pivot, Schema, Chart bridges│ ├── shell/ # UI view components│ │ ├── grid-canvas/ # Spreadsheet grid view│ │ ├── kanban-board/ # Kanban board view│ │ ├── timeline/ # Timeline / Gantt view│ │ └── ... # Calendar, Gallery, Form│ ├── canvas/ # Canvas rendering system (8 pkgs)│ │ ├── engine/│ │ ├── grid-renderer/│ │ ├── overlay/│ │ ├── spatial/│ │ └── drawing/ # 6 drawing sub-packages│ └── apps/ # End-user applications│ ├── spreadsheet/│ └── dashboard/├── crates/ # Rust crates (56 total, 21 core)│ ├── compute-core/ # Workspace root│ ├── parser/ # Formula parser│ ├── functions/ # 582 Excel functions│ ├── graph/ # Dependency graph│ ├── collab/ # CRDT layer (Yrs)│ ├── xlsx/ # XLSX parser/writer│ ├── wire/ # Binary wire protocol│ ├── bridge/ # FFI proc-macro framework│ └── ...├── pnpm-workspace.yaml└── Cargo.toml # Workspace rootEvery package exports a clean public API and declares its dependencies explicitly. Internal modules are not re-exported. This enforcement means any package can be extracted, replaced, or consumed independently — a property that matters as the ecosystem grows around the core engine.