Thank you for being patient! We're working hard on resolving the issue
Every row in a sheet has a role that governs its behavior, and a wire type that names its presentation. The role determines the row's base class and which APIs are available on it. Typed access to domain data — calendar events, tasks, weather — flows through facades attached to rows at construction time.
This doc is the reference for the role hierarchy, the facade registry, and
the RowTypeSpec metadata table that drives defaults and subsumption.
| Role | Base class | What it owns |
|---|---|---|
source | SourceRow | Data — user edits, provider pushes, formula output |
layout | LayoutRow | A rendering container (timeline, canvas, grid, list, column) |
renderer | RendererRow | A single filled slot inside a layout |
alias | AliasRow | A Figma-style template instance with props + overrides |
The role lives on the row's immutable descriptor; it's fixed at
construction and never changes.
import { SourceRow, LayoutRow, RendererRow, AliasRow } from "@tento-lona/sheets";
const row = sheet.row({ label: "Weight" });
switch (row?.descriptor.role) {
case "source": // row is SourceRow — has cells(), setCell(), facades
case "layout": // row is LayoutRow — renders children into slots
case "renderer": // row is RendererRow — fills one slot
case "alias": // row is AliasRow — instance of a template
}
Owns or computes the row's cells. Every user-editable row, every provider-
backed row (calendar, weather, tasks, garmin, etc.), and every formula row is
a SourceRow subclass. Two concrete subclasses:
DataSource — user or provider cells. Reads/writes through
sheet.stores.sourceCellData.FormulaDataSource — cells owned by the formula engine. Writes
reject; the engine recomputes on dependency changes.ComputedDataSource — cells synthesized by a registered computed
source (see COMPUTED_SOURCE_BUILDERS).Typed cell access for scalar dtypes lives on the auto-hydrated column.*
renderer child (renderer-as-editor). Typed access for object dtypes goes
through facades — see below.
A container that provides rendering slots for its children. Timeline,
calendar-month, canvas, grid, column, list, group, block, header — each is a
distinct LayoutRow subclass.
Layout rows don't own cells; they arrange the output of their children.
A single filled slot inside a parent layout. Renderers are named
<layout>.<renderer> — for example:
column.text, column.number, column.checkmark, column.graphcanvas.hrule, canvas.line, canvas.points, canvas.regression-bandtimeline[row].event, timeline[column].numbergrid.event, grid.text, list.event, list.numberWhen a SourceRow needs to render as a standalone row (not subsumed by an
ancestor), the framework hydrates a default renderer child via
resolveDefaultRendererId + hydrateDefaultRenderer.
A template instance in the Figma sense — an alias carries a reference to a
master template plus a prop bag and an override bag. Editing the master
propagates to every instance; per-instance overrides live on the alias row
itself. Calendar-list is the canonical example — the calendar row holds a
virtual alias to the :calendar-list-template master.
const aliasRow = sheet.row({ type: "calendar-list" });
if (aliasRow instanceof AliasRow) {
const instance = aliasRow.instance();
console.log(instance.template, instance.props, instance.overrides);
await aliasRow.setProp("showWeekNumbers", true);
}
A facade is a typed API over a row's cells, attached at row
construction by a FacadeFactory. You obtain one via row.as(FACADE_KEY).
The facade returns null when the row's shape isn't compatible — so the
optional-chain pattern is always safe.
| Facade | Key | Matches | Typical use |
|---|---|---|---|
| Events | EVENTS_FACADE | source rows with dtype="obj" + event-shape cells | Calendar events, meetings |
| Tasks | TASKS_FACADE | source rows with task-shape cells | Todo / task lists |
| Weather | WEATHER_FACADE | source rows with weather-shape cells | Daily observations, forecasts |
| Data | DATA_FACADE | source rows with scalar cells + graph props | Time-series + graph config |
| Scheduling link | SCHEDULING_LINK_FACADE | source rows with scheduling-link attrs | Availability + booking slots |
import {
EVENTS_FACADE,
TASKS_FACADE,
WEATHER_FACADE,
DATA_FACADE,
SCHEDULING_LINK_FACADE,
} from "@tento-lona/sheets";
import { NaiveDate } from "@tento-chrono";
const calendarRow = sheet.row({ type: "calendar" });
const events = calendarRow?.as(EVENTS_FACADE);
if (events) {
const today = NaiveDate.today();
// Raw Events for a date
for (const event of events.eventsOn(today)) {
console.log(event.summary, event.start);
}
// Hydrated EventItem overlapping a UTC range
const item = events.find("event-id-123");
// Mutations route through row.setCell
await events.addEvent({ id: "...", summary: "...", start: ..., end: ... });
}
Facades are registered per-sheet. A factory is { key, build(row) }; the
registry walks every factory at row construction and installs the facades
whose build(row) returns non-null.
import { FacadeKey, FacadeRegistry, SourceRow } from "@tento-lona/sheets";
class MyFacade {
constructor(private readonly row: SourceRow) {}
query(): readonly MyThing[] { /* ... */ }
}
export const MY_FACADE = new FacadeKey<MyFacade>("my-facade");
export const MY_FACADE_FACTORY = {
key: MY_FACADE,
build(row: SourceRow): MyFacade | null {
if (row.descriptor.dtype !== "obj") return null;
// ... additional shape gating ...
return new MyFacade(row);
},
};
// Register at sheet setup
sheetStores.facades.register(MY_FACADE_FACTORY);
Per-wire-type static metadata lives in a single registry, ROW_TYPE_SPECS,
keyed by wire type string ("calendar", "weather", "formula", …). The
spec carries:
| Field | Purpose |
|---|---|
defaultAttributes(row) | Seed attribute bag for new rows of this type |
childSpecs | Declarative child-row seeds expanded at hydrate time |
subsumedBy | Layout keys that absorb this row (don't render standalone) |
activeLayout(timescale) | Which layout key this row provides at a given timescale |
rendersChildren(attrs) | Whether children render (e.g. collapsed blocks return false) |
heightBounds | Min/max height clamp enforced by setHeight |
import { ROW_TYPE_SPECS, reconcileAttributes } from "@tento-lona/sheets";
const spec = ROW_TYPE_SPECS.get("calendar");
const defaults = spec?.defaultAttributes?.(rawRow) ?? {};
const attrs = reconcileAttributes(defaults, rawRow.attributes);
reconcileAttributes(defaults, instance) merges static defaults with the
row's wire attributes. Instance values win at the top level; the children
visibility bag is deep-merged per preset so instances can hide a specific
default child without re-specifying the rest.
When a parent layout's activeLayout(timescale) returns a key that appears
in a child's subsumedBy list, the child doesn't render as a standalone row
— the parent's slot paints its cells. Example: calendar-list declares
subsumedBy: ["calendar.day", "calendar.week"], so in day or week view it
vanishes as a row and its events feed the calendar layout. In month view,
the calendar's activeLayout("month") returns "calendar.month" (not in
the list), so calendar-list renders as its own row.
All of the above — roles, facades, ROW_TYPE_SPECS, RowLocalId — live in
@tento-lona/sheets. The SDK (@tento-lona) re-exports many of them but the
canonical home is the rows package.
yuzu-js/core, @tento-chrono ← pure data-structures
↑
@tento-lona/sheets ← rows + facades + layout math (no DOM)
↑
@tento-lona ← SDK: clients, HTTP backend, preferences
↑
@tento-lona/sheets-ui ← sheet rendering, plugins (uses DOM)
↑
lona-so ← application
RowKey, RowLocalId, RowId