Something went wrong

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

Per-Type Plugins - Lona Docs Log in

Per-Type Plugins

Every canonical (id, role) in the catalog maps to one class per platform, all implementing a shared contract. New platforms drop in by supplying their surface type + one class per id.

The catalog

BUILTIN_RENDER_PLUGIN_SPECS is the canonical 45-entry list:

  • 8 alias specs (weather, timezone, data, task, task-list, calendar, calendar-list, calendar-allday)
  • 10 layout specs (block, group, header, column, grid, canvas, timeline[row], timeline[column], timeline-legend, sheet-footer)
  • 27 renderer specs (column.text, column.number, column.task, timeline[row].event, grid.task, canvas.bar, …)

All three lists are exposed as role-grouped arrays:

import {
  ALIAS_PER_TYPE_SPECS,
  LAYOUT_PER_TYPE_SPECS,
  RENDERER_PER_TYPE_SPECS,
  PER_TYPE_SPECS,            // alias + layout + renderer, in canonical order
  BUILTIN_RENDER_PLUGIN_SPECS,
} from "@tento-lona/sheets";

PerTypeSpec

interface PerTypeSpec extends RenderPluginSpec {
  id: string;
  role: "alias" | "layout" | "renderer" | "source";
  aliases?: readonly string[];
  legacyIds?: readonly string[];
  dataProvider?: PluginDataProvider;
  cellValueFormatter?: (value: CellValue | null, ctx: { rowType: string; pluginId: string }) => string;
}

Slots are shared across platforms — a cellValueFormatter on COLUMN_NUMBER_SPEC runs in CLI and DOM alike, so a snapshot test on CLI catches a corresponding DOM regression.

PerTypeAdapter

Every platform defines one class implementing this for each spec:

interface PerTypeAdapter<TSurface, TContext> {
  readonly spec: PerTypeSpec;
  render(ctx: TContext): TSurface;
  onCellRecycled?(surface: TSurface): void;
  onRowBound?(ctx: TContext): void;
}

TSurface is the platform's render output type:

  • DOM — HTMLElement
  • CLI — CliPerTypeSurface (label + tags)
  • TUI — TuiCellFormat (value + glyph + alignment)

TContext is the platform's per-render context, also platform-defined.

Per-platform manifests

Each platform mirrors the rows-side directory layout:

tento/tento-lona-js/sheets/src/plugins/
├── alias/        ← 8 specs
├── layout/       ← 10 specs
└── renderer/     ← 27 specs

tento/tento-lona-js/sheets-ui/src/plugins/{alias,layout,renderer}/...
                            ← DOM classes, one per spec
tento/tento-lona-js/cli/plugins/{alias,layout,renderer}/...
                            ← CliPerTypePlugin instances, one per spec

The cross-platform parity test (tento/tento-lona-js/sheets/tests/plugins/cross-platform-parity.deno.test.ts) asserts each platform's manifest mirrors BUILTIN_RENDER_PLUGIN_SPECS exactly (same order, same (id, role) pairs).

Resolving a binding

Given a SheetRow, the binding resolvers find the plugin that owns its cell / row / chrome surface:

import {
  RenderPluginRegistry,
  resolveCellRenderBinding,
  resolveRowRenderBinding,
  resolveChromeRenderBinding,
} from "@tento-lona/sheets";

const registry = new RenderPluginRegistry();
for (const spec of PER_TYPE_SPECS) registry.plugin(spec);

const cellBinding = resolveCellRenderBinding(row, registry);
const plugin = registry.get(cellBinding.pluginId, cellBinding.pluginRole);

Adding a new plugin

  1. Add tento/tento-lona-js/sheets/src/plugins/<role>/<id>.ts exporting <NAME>_SPEC: PerTypeSpec.
  2. Register it in tento/tento-lona-js/sheets/src/plugins/index.ts in the appropriate role-grouped array.
  3. Add tento/tento-lona-js/sheets-ui/src/plugins/<role>/<id>.ts (DOM impl).
  4. Add tento/tento-lona-js/cli/plugins/<role>/<id>.ts (CLI impl).
  5. Re-run cross-platform-parity.deno.test.ts — it asserts every manifest stays in sync.

See also

  • Variants & Facades — what the per-type plugin renders
  • Scene & Diff — what the per-type plugin produces (a node in the scene)
  • __design_docs/lona-rows/rowkey/per-type-plugin-plan.md