cells

The cell data model, storage pattern, values, formulas, inline styles, and cell types.

Cell Storage

Cells are stored in a nested Y.Map structure within each sheet:

rows: Y.Map<Y.Map<Cell>>
       ^         ^
       |         └── Inner map: colId → Cell
       └── Outer map: rowId → inner map

This two-level nesting provides efficient row-level operations:

  • Insert/delete row: Add or remove a single entry in the outer map
  • Move row: Reorder in rowOrder array; the row’s cell data stays intact
  • Access cell: Two map lookups: rows.get(rowId)?.get(colId)
// Read a cell
const rowMap = rows.get(rowId) as Y.Map<Cell> | undefined
const cell = rowMap?.get(colId) as Cell | undefined

// Write a cell
let rowMap = rows.get(rowId) as Y.Map<Cell>
if (!rowMap) {
    rowMap = new Y.Map()
    rows.set(rowId, rowMap)
}
rowMap.set(colId, cellData)
Sparse storage

Only cells with actual content or formatting are stored. Empty cells have no entry in the map. When all cells in a row are cleared, the empty inner Y.Map can be garbage collected by deleting the outer map entry.

Cell Data Model

The Cell interface defines all possible fields on a cell. All fields are optional – an empty object {} represents a blank cell with all defaults.

Value Fields

Field Type Description
v string | number | boolean Display value (the computed/entered value)
f string Formula string (starts with =, e.g. "=SUM(A1:A10)")

Cell Type and Format

Field Type Description
ct CellType Cell type descriptor (see below)

The ct object describes how the cell value should be interpreted and formatted:

interface CellType {
    t?: 't' | 'n' | 's' | 'b' | 'g'  // Type code
    fa?: string                         // Format code
    s?: Array<{ v: string }>           // Rich text segments
}

Type Codes (ct.t)

Code Meaning Example Values
g General Auto-detected type
n Number 42, 3.14, -100
s String "Hello", "ABC"
t Time/Date "2026-01-15", "14:30:00"
b Boolean true, false

Format Codes (ct.fa)

The fa field contains a format pattern string compatible with spreadsheet number formatting:

Pattern Description Example Output
"General" Auto-format (default) 1234.5
"0" Integer 1235
"0.00" Two decimal places 1234.50
"#,##0" Thousands separator 1,235
"#,##0.00" Thousands + decimals 1,234.50
"0%" Percentage 12%
"0.00%" Percentage with decimals 12.35%
"$#,##0.00" Currency $1,234.50
"yyyy-mm-dd" Date 2026-01-15
"h:mm:ss" Time 14:30:00

Rich Text Segments (ct.s)

When a cell contains rich text (mixed formatting within a single cell), the ct.s array holds text segments:

{
    "ct": {
        "s": [
            { "v": "Bold text" },
            { "v": " and normal text" }
        ]
    }
}

Each segment contains a v field with the text content. Additional formatting fields may appear on individual segments.

Font Styling

All styling is inline on the cell – there is no named style or style inheritance system.

Field Type Default Description
bl number 0 Bold: 0 = off, 1 = on
it number 0 Italic: 0 = off, 1 = on
un number 0 Underline: 0 = off, 1 = on
cl number 0 Strikethrough: 0 = off, 1 = on
ff number 0 Font family code (0 = default)
fs number 10 Font size in points
fc string Font color (hex, e.g. "#333333")

Alignment

Field Type Default Description
ht number Horizontal: 1 = left, 2 = center, 3 = right, 4 = justify
vt number Vertical: 0 = top, 1 = middle, 2 = bottom

Cell Appearance

Field Type Default Description
bg string Background color (hex, e.g. "#ffeb3b")
tb number 0 Text wrap: 0 = off, 1 = on
tr number 0 Text rotation in degrees

Formula Storage

Formulas are stored as plain strings in the f field:

{
    "v": 150,
    "f": "=SUM(A1:A10)"
}
  • The f field contains the formula string as entered by the user (always starts with =)
  • The v field contains the last computed result
  • Formula references use A1-style notation (e.g. A1, B2:D10, Sheet2!A1)
  • The CRDT stores only the formula string – no ID-based cell references
  • Formula evaluation is delegated to the FortuneSheet calculation engine at runtime
A1 references vs ID-based addressing

Internally, Calcillo uses random IDs for rows and columns in the CRDT. However, formulas use traditional A1-style references. The conversion between column IDs (from colOrder) and letter codes (A, B, C…) happens at the application layer. This means formulas may need adjustment when rows or columns are inserted or deleted – this is handled by the formula engine, not the CRDT.

Default Stripping

To minimize CRDT storage overhead, fields that match their default values are stripped when saving a cell. The following fields are omitted when they match these defaults:

Field Default Value
bl 0
it 0
un 0
cl 0
ff 0
fs 10
tb 0
tr 0

Fields with no default (like v, f, fc, bg, ht, vt) are stored whenever present.

Cell Addressing

Cells are addressed internally by their (rowId, colId) pair. For display, the column position in colOrder is converted to a letter code:

Position Letter Code
0 A
1 B
25 Z
26 AA
27 AB

Row position in rowOrder is converted to a 1-based number. For example, the cell at position (row index 2, col index 0) displays as A3.