Something went wrong

Thank you for being patient! We're working hard on resolving the issue

Sheets - Lona Docs Log in

Sheets

Sheets are the core data structure in Lona. Each sheet contains a tree of rows, where each row has a type, optional label, and date-indexed cell data.

Package note. The Sheet value type — its stores, invalidation, canonical cell cache — lives in @tento-lona/sheets. The SDK layer (@tento-lona) exposes it as SheetHandle (a type alias) and wraps it with a SheetsAccessor that owns the shared canonical-cell store across every open sheet. Most app code works with SheetHandle; reach for @tento-lona/sheets APIs (SheetsAccessor, CanonicalBackend, RangeCellHandle) when writing library or plugin code.

Listing sheets

const sheets = await client.sheets.list();

for (const sheet of sheets) {
  console.log(`${sheet.id} — ${sheet.title}`);
}

Opening a sheet

client.open(key) fetches the sheet and returns a SheetHandle:

const sheet = await client.open(sheetKey);

console.log(sheet.id);    // sheet UUID
console.log(sheet.title); // sheet title or null

Creating a sheet

const created = await client.sheets.create({
  title: "My Sheet",
  rows: [
    { type: "number", label: "Weight" },
    { type: "text", label: "Notes" },
  ],
});

Updating a sheet

Update sheet metadata with client.sheets.update(). Updates are fire-and-forget — the SDK syncs to the server without blocking the caller:

// Update title
client.sheets.update(sheetKey, { title: "New Title" });

// Update description
client.sheets.update(sheetKey, { description: "Weekly tracker" });

// Update frozen rows (pinned at top)
client.sheets.update(sheetKey, { numFrozenRows: 2 });

// Update sheet width (undefined = default)
client.sheets.update(sheetKey, { width: 1200 });

The local cache is updated immediately. The SDK emits SheetMetadataChanged so the UI can refresh. Changing numFrozenRows or width also emits FrozenRowsChanged / SheetWidthChanged so layout effects rerun. See Invalidation.

Reordering sheets

Sheets are ordered by fractional positions. To reorder, compute a new position between two neighbors and send it to the server:

import { Rational } from "@tento-core/rational";

// Position between two sheets
const position = Rational.midpoint(
  sheetAbove?.position,  // upper bound (or undefined for first)
  sheetBelow?.position,  // lower bound (or undefined for last)
);

const result = await client.sheets.reorder(sheetKey, { position });

Recanonicalization

Repeated insertions between items cause fractions to grow (e.g., inserting between 1/3 and 1/2 produces 2/5, then 3/8, 5/13, ...). When numerator or denominator exceeds a threshold, the server recanonicalized all positions — reassigning them as 1/n, 2/n, ..., (n-1)/n to reset the fractions to small integers while preserving order.

const result = await client.sheets.reorder(sheetKey, { position });

if (result.recanonicalized) {
  // Server reset all sheet positions — update the full list
  updateSheetCache(result.sheets);
} else {
  // Only the moved sheet changed position
  updateSingleSheet(sheetKey, position);
}

Recanonicalization is transparent — the server handles it automatically. Clients just need to check whether the response includes updated sheets and refresh their cache accordingly.

Deleting a sheet

Delete calls feedback.confirmDelete() before proceeding:

await client.sheets.delete(sheetKey);

The SDK orchestrates: confirm → delete on server → remove from cache → emit SheetListChanged.

Access control

Share sheets with other users:

// Grant access
await client.sheets.grantAccess(sheetKey, {
  userId: "other-user-id",
  role: "editor",
});

// List who has access
const acl = await client.sheets.getAcl(sheetKey);

// Revoke access
await client.sheets.revokeAccess(sheetKey, "other-user-id");

SheetHandle

client.open() returns a SheetHandle — the primary interface for working with a sheet's rows, cells, and tree structure.

PropertyTypeDescription
sheet.idUuidSheet UUID
sheet.titlestring | nullSheet title
sheet.numFrozenRowsnumberRows pinned at the top
sheet.widthnumber | undefinedExplicit width override (px); undefined = viewport picks
sheet.rowsRowsAccessorRow lookup and operations
sheet.cellsCellAccessorCell fetch and cache
sheet.editablebooleanWhether current user can edit

Fetching cell data

sheet.cells.fetch(range) loads cells for all rows in a date range:

import { NaiveDate } from "@tento-chrono";

const today = NaiveDate.today();
const start = today.addDays(-60);
await sheet.cells.fetch(new NaiveDate.Range(start, today));

For a single row with progressive-load callbacks (loading shimmer → partial data → final data), use RangeCellHandle — see Cells.

Tree structure

Access the row tree to navigate parent/child relationships:

const tree = sheet.tree();

for (const node of tree.nodes) {
  const row = sheet.row(node.id);
  console.log(`${row?.label} (${row?.type})`);
}

node.id is a RowLocalId — pass it directly to sheet.row(id); no string conversion needed.

See also

  • Rows — row types, tree navigation, and row operations
  • Cells — reading, writing, and progressive cell loading
  • Client ArchitectureLonaSdk.Client setup