Thank you for being patient! We're working hard on resolving the issue
Render plans separate what to render from how to render it. They are pure computations that take sheet state as input and produce a per-row plan describing what changed and what update strategy to use.
When a sheet column needs rendering, the pipeline is:
SheetHandle → RenderPlan.makeForRows() → RenderPlan[] → Binder (DOM)
The render plan is the decision layer. It computes:
The binder then applies these decisions to the DOM.
import { RenderPlan } from "@tento-lona/platform";
const plans = RenderPlan.makeForRows(
context,
rows,
previousState,
resizeOnly,
subcolumnCache,
);
for (const plan of plans) {
switch (plan.updateType) {
case "full-rebuild":
// Create new cell content from scratch
break;
case "content":
// Rebind existing cell with new data
break;
case "style-only":
// Update height, span mode, time state CSS only
break;
case "none":
// Skip this cell entirely
break;
}
}
| Type | When | Cost |
|---|---|---|
full-rebuild | New cell, type changed, or structure changed | High — creates DOM |
content | Same cell, new data | Medium — rebinds plugins |
style-only | Resize, or cell is hidden/spanned | Low — CSS only |
none | Nothing changed | Zero |
Each row gets a render mode based on how its timescale compares to the sheet's timescale:
| Mode | Condition | Example |
|---|---|---|
normal | Row timescale ≤ sheet timescale | Day row on day sheet |
stacked | Row timescale < sheet timescale (with stacking layout) | Day row on month sheet |
spanned | Row timescale > sheet timescale (spannable) | Year row on day sheet |
Spanned rows have skipContent: true — their content is rendered by
fixed children rather than per-column cells.
The mode depends on the timescale relationship AND the row descriptor's
spannable and stackable flags:
| Timescale | Spannable | Stackable + layout | Mode |
|---|---|---|---|
| row < sheet | any | yes | stacked |
| row < sheet | any | no | normal |
| row = sheet | any | any | normal |
| row > sheet | yes | any | spanned |
| row > sheet | no | any | normal |
| custom | any | any | normal |
spannable: true on the descriptor. Without it,
a month row on a day sheet renders normal (one cell per column).The render plan is a pure function. To determine what changed, it
compares the current row state against a PreviousState map that the
caller tracks:
interface PreviousState {
type: string; // row type last rendered
isStacked: boolean; // was stacked last time
cellCount: number; // stacking cell count
isNew: boolean; // true if cell slot was just created
}
When no previous state exists for a row (first render), the plan produces
full-rebuild. When previous state matches current state, it produces
content. When the type or structure changed, it produces full-rebuild
with a diff indicating what changed.
Every plan includes a TimeState for the column:
interface TimeState {
inThePast: boolean;
isCurrent: boolean;
isFuture: boolean;
}
This is used by the binder to apply visual styling (dimmed past columns, highlighted current column).
The render plan context provides the sheet layout, column info, and viewport dimensions:
interface Context {
sheet: LayoutProvider; // timeScale + layout (row heights, stacking)
column: Column; // the column being rendered
now: DateTime<Utc>; // current time (for time state)
visibleWidthPx: number; // viewport width (for static span detection)
resolvedWidthPx: number; // column width (for span size calculation)
mapper: ColumnMapper; // column offset resolution
}
Render plans are tested with JSON fixtures:
{
"name": "new row produces full-rebuild",
"input": {
"sheetTimeScale": "day",
"column": { "date": "2026-03-15" },
"now": "2026-03-15T12:00:00Z",
"rows": [{ "id": "r_...", "type": "number", "timeScale": "day" }],
"previousState": {},
"resizeOnly": false
},
"expected": [
{ "updateType": "full-rebuild", "mode": "normal" }
]
}
Sheet-referencing fixtures test against real SDK descriptors:
{
"sheet": "timescale-variety",
"input": {
"sheetTimeScale": "day",
"previousState": "from-rows",
"resizeOnly": false
},
"expected": {
"all": { "updateType": "content" }
}
}
See tento/tento-lona-js/tests/fixtures/render-plan-*.fixture.json and
tento/tento-lona-js/tests/sheets/*.sheet.json for the full test suite.