Skip to content

parazeeknova/papyrus

Repository files navigation

Papyrus

Realtime collaborative spreadsheet with a local-first document model, CRDT-based syncing, worker-driven evaluation, and a virtualized grid built to stay responsive on 10K+ row datasets.

Links

  • Full Feature List: features at github wiki !
  • Live app: vercel link deployed
  • Loom Video: video
Home Find & Replace Share menu
Home Find & Replace Share

Core

  • The workbook is modeled as a Yjs document, so cells, sheets, formats, column metadata, and workbook metadata all live in one conflict-tolerant source of truth.
  • The web client is local-first: IndexedDB persists workbooks for instant reloads, while Firestore acts as the durable cloud snapshot and sharing registry.
  • Realtime collaboration is handled by an Elysia WebSocket service that sends room snapshots on join and incremental Yjs updates after that.
  • Presence is intentionally separate from workbook state. Active users, selections, typing state, and viewer/editor access are transported as ephemeral collaboration metadata.
  • Formula support is deliberately shallow and defendable for assignment scope: cell refs, ranges, arithmetic, and SUM, AVERAGE, MIN, MAX, COUNT.
  • Large-sheet responsiveness comes from sparse cell storage, TanStack row and column virtualization, and a Web Worker that recalculates formulas off the main thread.

Architecture

flowchart TB
  subgraph Client["Next.js Client"]
    UI["Dashboard + Spreadsheet UI"]
    Store["Zustand Store\nhydration, commands, write state"]
    YDoc["Yjs Workbook Doc\ncells, sheets, formats, meta"]
    Worker["Formula Worker\ndependency graph + recompute"]
    IDB[("IndexedDB\nlocal persistence")]
  end

  subgraph Realtime["Sync Layer"]
    WS["Elysia WebSocket Room\nsnapshot, presence, incremental Yjs fanout"]
    Cache[("Room Cache\nserver-side persisted room state")]
  end

  subgraph Cloud["Firebase"]
    Auth["Firebase Auth\nGoogle / session identity"]
    Snapshots[("Firestore\nworkbook snapshots")]
    Sharing[("Firestore\nsharedWorkbooks policy")]
  end

  UI --> Store
  Store <--> YDoc
  Store <--> Worker
  YDoc <--> IDB

  Store -- initial bootstrap / merge --> Snapshots
  Store -- debounced durable sync --> Snapshots
  Store -- collaborator identity --> Auth

  Store -- snapshot seed + incremental updates --> WS
  WS -- snapshot / sync / presence --> Store
  WS <--> Cache
  WS -- access validation --> Sharing
  Auth -- owner token --> WS
Loading

More Info

  • Logical row capacity is 100,000, with the visible window virtualized instead of rendering the full sheet into the DOM.
  • A 10K+ row CSV is a strong path for this architecture because import stores only populated cells, the grid renders only visible rows and columns, and recalculation stays off the main React thread.
  • The live collaboration path and the durable cloud path are intentionally split: Yjs + WebSocket handles low-latency edits and conflict-friendly merging, while Firestore handles debounced persistence, durable restore, and sharing metadata.
  • Firestore writes are lease-guarded to reduce remote snapshot clobbering when multiple authenticated clients are open on the same workbook.
  • Structural operations like insert, delete, and reorder are supported, but they are heavier than plain editing because they rewrite sparse cell maps and formula references.

Packages

 
 
 

Contributors