Skip to content

Flyweight grid: 20,000,000 cells ​

Ground truth lives in columnar typed arrays; rendered cells are disposable flyweight facades created per render pass; a sparse reactive overlay materializes per observation and evicts with the viewport. ~55% of cells hold real Excel-syntax formulas. Everything costs proportional to what is observed — never to what exists.

Nothing downloads until you click — the model code and the formula parser load on demand, then one more click creates all 20,000,000 cells in your browser.

What to notice ​

  • Creation fills ~95 MB of typed arrays — not 20 million objects. The reactive layer allocates only for cells something actually observes.
  • Edit a cell at row 1,000,000 and the column totals react — the dependency graph is discovered per observation, not precomputed.
  • The measured protocol, numbers and design live in RESULTS.md and DESIGN.md; the deeper story is in Flyweight Pattern guide.

The source ​

The heart of the pattern — the sheet (columnar ground truth + sparse overlay) and the cell facade:

ts
/**
 * FlyweightCell — the disposable facade. THREE fields; everything else is
 * plain getters delegating to the sheet's tracked accessors, so:
 *
 *   - construction allocates one near-empty object (ivue runs nothing at
 *     `new`, and plain getters de-optimize to native prototype getters);
 *   - reads are tracked through whatever effect performs them — a facade in
 *     a template subscribes the component exactly like a real cell would;
 *   - the reactive state lives on the SHEET's sparse overlay, so facades are
 *     created per render and dropped on scroll with zero loss.
 *
 * The reference `FormulaCell` (demo/formula) holds its own ref + computed;
 * this holds NOTHING — that is the flyweight move.
 */
import { Reactive } from '../../../ivue';
import { FlyweightLogic } from '../FlyweightLogic';
import type { FlyweightSheet } from './FlyweightSheet';

class $FlyweightCell {
  constructor(sheet: FlyweightSheet.Model, row: number, col: number) {
    this.sheet = sheet;
    this.row = row;
    this.col = col;
  }

  readonly sheet: FlyweightSheet.Model;

  readonly row: number;

  readonly col: number;

  /** Resolved value — tracked point read through the sheet. */
  get value(): FlyweightLogic.CellValue {
    return this.sheet.valueAt(this.row, this.col);
  }

  /** The literal text (formula source / number text). */
  get source(): string {
    return this.sheet.sourceAt(this.row, this.col);
  }

  get isFormula(): boolean {
    return this.sheet.kindAt(this.row, this.col) === FlyweightLogic.Kind.Formula;
  }

  get display(): string {
    return FlyweightLogic.Class.displayOf(this.value);
  }

  get cssClass(): string {
    return FlyweightLogic.Class.cssOf(this.value, this.isFormula);
  }

  write(input: string): void {
    this.sheet.write(this.row, this.col, input);
  }
}

export namespace FlyweightCell {
  export const $Class = $FlyweightCell;
  export let Class = Reactive($Class);
  export type Model = InstanceType<typeof Class>; // raw-instance type — collections, parameters, returns
  export type Instance = typeof Class.Instance;
}
ts
/**
 * FlyweightSheet — 20M cells with NO cell objects at rest.
 *
 * Ground truth is columnar (`kind` Uint8Array + lazily-allocated Float64Array
 * per column + a sparse Map for text/edited-formula sources). Reactivity is a
 * SPARSE OVERLAY that materializes per observation:
 *
 *   - fine tier   — a version ref per OBSERVED cell (rendered / onCell /
 *                   small range). Precise: conditional dependencies shift.
 *   - coarse tier — a version ref per 4,096-row BLOCK, subscribed only by
 *                   LARGE ranges. =SUM(A1:A1000000) costs 245 edges, not 1M.
 *   - formula computeds — cached on demand; each carries ONE sync watcher
 *                   (the derived-write bridge) that bumps its own block when
 *                   its value changes, so coarse subscribers see through
 *                   formulas — including their out-of-range inputs.
 *
 * Writes are O(observers-of-that-cell): update the arrays, bump the fine ref
 * IF IT EXISTS and the block ref IF IT EXISTS (peek-only). A write to a
 * never-observed cell allocates nothing and notifies no one.
 *
 * The dependency graph is still DISCOVERED, never hand-built — the same
 * onCell/onRange seam as the formula grid (reference: demo/formula/Sheet.ts),
 * with the parser reading through this sheet's tracked accessors.
 */
import FormulaParser from 'fast-formula-parser';
import { pauseTracking, resetTracking } from '@vue/reactivity';
import { computed, ref, watch, type ComputedRef, type Ref, type WatchStopHandle } from 'vue';
import { Reactive } from '../../../ivue';
import { Static } from '../../../Static';
import { FlyweightLogic } from '../FlyweightLogic';

class $FlyweightSheet {
  /** The parser's error class, read off the parser module once per class —
   *  a static so a subclass can substitute the error shape it evaluates to. */
  protected static get $FormulaError() {
    return (
      FormulaParser as unknown as {
        FormulaError: new (error: string, details?: unknown) => FlyweightLogic.CellValue;
      }
    ).FormulaError;
  }

  constructor(rows: number, cols: number = FlyweightLogic.Class.COLS) {
    this.rows = rows;
    this.cols = cols;
    this.blockCount = Math.ceil(rows / this.Logic.BLOCK_ROWS);

    // Hoist the METHODS once for the seeding loop (a late read of the
    // mutable slot, so a subclass swap is still honored). Static()'s
    // bound functions are exactly plain-function speed — the per-call
    // cost is the ACCESSOR, so read it once per method, not 9M times.
    // Measured in-browser on this loop (fresh-page medians):
    //   hoisted bound methods ......... plain-function speed (~30ms/9M)
    //   Logic.method() per call ....... ~85ms/9M (accessor each call)
    const { isDataCol, numDataValue } = this.Logic;

    // Seed the columnar ground truth. Data columns fill Float64Arrays
    // numerically (no string round-trips — this is the whole creation cost);
    // formula columns are a single Uint8Array.fill.
    const columns: FlyweightSheet.Column[] = new Array(cols);
    for (let col = 0; col < cols; col++) {
      const kind = new Uint8Array(rows);
      let numbers: Float64Array | null = null;
      if (isDataCol(col)) {
        numbers = new Float64Array(rows);
        for (let row = 0; row < rows; row++) {
          const value = numDataValue(row, col);
          if (value !== null) {
            kind[row] = FlyweightLogic.Kind.Number;
            numbers[row] = value;
          } // blanks stay FlyweightLogic.Kind.Blank
        }
      } else {
        kind.fill(FlyweightLogic.Kind.Formula);
      }
      columns[col] = { kind, numbers, text: new Map() };
    }
    this.columns = columns;

    this.parser = new FormulaParser({
      onCell: (cellRef) => this.pointValue(cellRef.row, cellRef.col),
      onRange: (rangeRef) => this.rangeValues(rangeRef as FlyweightSheet.RangeRef)
    });
  }

  readonly rows: number;

  readonly cols: number;

  // --- ground truth (plain, non-reactive) ---
  protected readonly columns: FlyweightSheet.Column[];

  // --- the sparse reactive overlay (empty until observed) ---
  protected readonly cellVersions = new Map<number, Ref<number>>();

  protected readonly blockVersions = new Map<number, Ref<number>>();

  protected readonly formulaCache = new Map<number, FlyweightSheet.FormulaEntry>();

  protected readonly adHocCache = new Map<string, ComputedRef<FlyweightLogic.CellValue>>();

  /** Blocks per column (fine↔coarse key math). */
  protected readonly blockCount: number;

  /** ONE parser for the whole sheet (reference: formula grid). */
  protected readonly parser: FormulaParser;

  /** Cycle guard — a cell re-entered mid-evaluation is a cycle → #REF!. */
  protected readonly evaluating = new Set<number>();

  /** When non-null, tracked reads record (row,col) — dep tracing. */
  protected readonly trace = { recording: null as Array<[number, number]> | null };

  /** The logic seam. A subclass overrides THIS to swap the whole config/
   *  mapping layer (`protected override get Logic() { return
   *  WideGridLogic.Class }`) — every read below, including the seeding
   *  loop's one-time destructure, follows the override. A single read
   *  costs one tracked getter call; only PER-CALL reads in the 9M-loop
   *  were ever a cost, and the destructure below avoids exactly that. */
  protected get Logic() {
    return FlyweightLogic.Class;
  }

  /** The one cast per class: instance code reads its own statics here. */
  protected get self() {
    return this.constructor as typeof $FlyweightSheet;
  }

  // --- keys ---
  protected cellKey(row: number, col: number): number {
    return col * this.rows + row;
  }

  protected blockKey(row: number, col: number): number {
    return col * this.blockCount + (row >> this.Logic.BLOCK_SHIFT);
  }

  // --- version-ref plumbing ---
  /** Subscribe the current effect to a cell (get-OR-CREATE — observation). */
  protected trackCell(row: number, col: number): void {
    const cellKey = this.cellKey(row, col);
    let versionRef = this.cellVersions.get(cellKey);
    if (!versionRef) {
      versionRef = ref(0);
      this.cellVersions.set(cellKey, versionRef);
    }
    void versionRef.value;
  }

  /** Subscribe the current effect to a block (get-or-create — observation). */
  protected trackBlock(blockKey: number): void {
    let versionRef = this.blockVersions.get(blockKey);
    if (!versionRef) {
      versionRef = ref(0);
      this.blockVersions.set(blockKey, versionRef);
    }
    void versionRef.value;
  }

  /** Notify a cell's observers — PEEK-ONLY (unobserved cells cost nothing). */
  protected bumpCell(row: number, col: number): void {
    const versionRef = this.cellVersions.get(this.cellKey(row, col));
    if (versionRef) versionRef.value++;
  }

  /** Notify a block's observers — peek-only. */
  protected bumpBlock(row: number, col: number): void {
    const versionRef = this.blockVersions.get(this.blockKey(row, col));
    if (versionRef) versionRef.value++;
  }

  // --- raw reads ---
  /** UNTRACKED ground-truth value (blank→null). No refs, no observation. */
  rawAt(row: number, col: number): FlyweightLogic.CellValue {
    const column = this.columns[col];
    switch (column.kind[row]) {
      case FlyweightLogic.Kind.Number:
        return column.numbers![row];
      case FlyweightLogic.Kind.Text:
        return column.text.get(row) ?? '';
      case FlyweightLogic.Kind.Formula:
        return this.sourceAt(row, col); // raw view of a formula = its source
      default:
        return null;
    }
  }

  /** The literal text of a cell (formula source / number text / text). */
  sourceAt(row: number, col: number): string {
    const column = this.columns[col];
    const override = column.text.get(row);
    if (override !== undefined) return override;
    switch (column.kind[row]) {
      case FlyweightLogic.Kind.Number:
        return String(column.numbers![row]);
      case FlyweightLogic.Kind.Formula:
        return this.Logic.patternSource(row, col) ?? '';
      default:
        return '';
    }
  }

  kindAt(row: number, col: number): FlyweightLogic.Kind {
    return this.columns[col].kind[row] as FlyweightLogic.Kind;
  }

  // --- tracked reads ---
  /**
   * The TRACKED point read — what rendered cells, facades and onCell use.
   * Formula cells resolve through their cached computed (which carries its
   * own fine ref for source edits); everything else takes a fine ref here.
   */
  valueAt(row: number, col: number): FlyweightLogic.CellValue {
    if (row < 0 || row >= this.rows || col < 0 || col >= this.cols) return null;
    if (this.columns[col].kind[row] === FlyweightLogic.Kind.Formula) {
      return this.formulaValue(row, col).value;
    }
    this.trackCell(row, col);
    return this.rawAt(row, col);
  }

  /** onCell seam (1-based, like the parser). */
  protected pointValue(oneBasedRow: number, oneBasedCol: number): FlyweightLogic.CellValue {
    if (this.trace.recording) this.trace.recording.push([oneBasedRow, oneBasedCol]);
    return this.valueAt(oneBasedRow - 1, oneBasedCol - 1);
  }

  /**
   * onRange seam. Small ranges read per-cell (fine tier — precise).
   * Large ranges subscribe BLOCKS, then read ground truth with tracking
   * PAUSED; formula cells inside resolve through their cached computeds
   * (transitive observation, priced) whose derived-write watchers keep the
   * block tier truthful.
   */
  protected rangeValues(range: FlyweightSheet.RangeRef): FlyweightLogic.CellValue[][] {
    const startRow = range.from.row - 1;
    const startCol = range.from.col - 1;
    const endRow = Math.min(range.to.row - 1, this.rows - 1);
    const endCol = Math.min(range.to.col - 1, this.cols - 1);
    const cellCount = (endRow - startRow + 1) * (endCol - startCol + 1);

    if (this.trace.recording) {
      for (let row = startRow; row <= endRow; row++)
        for (let col = startCol; col <= endCol; col++)
          this.trace.recording.push([row + 1, col + 1]);
    }

    const values: FlyweightLogic.CellValue[][] = [];

    if (cellCount <= this.Logic.FINE_RANGE_LIMIT) {
      for (let row = startRow; row <= endRow; row++) {
        const rowValues: FlyweightLogic.CellValue[] = [];
        for (let col = startCol; col <= endCol; col++) rowValues.push(this.valueAt(row, col));
        values.push(rowValues);
      }
      return values;
    }

    // Coarse tier: subscribe every covered block (tracked), …
    const firstBlock = startRow >> this.Logic.BLOCK_SHIFT;
    const lastBlock = endRow >> this.Logic.BLOCK_SHIFT;
    for (let col = startCol; col <= endCol; col++) {
      for (let block = firstBlock; block <= lastBlock; block++) {
        this.trackBlock(col * this.blockCount + block);
      }
    }

    // …then read with tracking paused (no fine edges from this range).
    pauseTracking();
    try {
      for (let row = startRow; row <= endRow; row++) {
        const rowValues: FlyweightLogic.CellValue[] = [];
        for (let col = startCol; col <= endCol; col++) {
          rowValues.push(
            this.columns[col].kind[row] === FlyweightLogic.Kind.Formula
              ? this.formulaValue(row, col).value
              : this.rawAt(row, col)
          );
        }
        values.push(rowValues);
      }
    } finally {
      resetTracking();
    }
    return values;
  }

  // --- formulas ---
  /**
   * The cached computed for a formula cell — created on first observation.
   * THIN on purpose: the computed and watcher bodies are small pointers to
   * named, directly testable methods on the prototype.
   */
  protected formulaValue(row: number, col: number): ComputedRef<FlyweightLogic.CellValue> {
    const cellKey = this.cellKey(row, col);
    let entry = this.formulaCache.get(cellKey);
    if (!entry) {
      const value = computed<FlyweightLogic.CellValue>(() => this.evaluateCell(row, col));
      const stopBridge = watch(
        value,
        (newValue, oldValue) => this.onFormulaValueChanged(row, col, newValue, oldValue),
        { flush: 'sync' }
      );
      entry = { value, stopBridge };
      this.formulaCache.set(cellKey, entry);
    }
    return entry.value;
  }

  /**
   * The DERIVED-WRITE BRIDGE: when a formula's value changes, bump the
   * cell's block so coarse subscribers invalidate even though the
   * underlying write happened somewhere else entirely.
   */
  protected onFormulaValueChanged(
    row: number,
    col: number,
    newValue: FlyweightLogic.CellValue,
    oldValue: FlyweightLogic.CellValue
  ): void {
    if (newValue !== oldValue) this.bumpBlock(row, col);
  }

  /** Evaluate a cell by its CURRENT kind (formulas through the parser). */
  protected evaluateCell(row: number, col: number): FlyweightLogic.CellValue {
    // Source edits / kind flips invalidate this computed via the fine ref.
    this.trackCell(row, col);
    const kind = this.columns[col].kind[row];
    if (kind !== FlyweightLogic.Kind.Formula) return this.rawAt(row, col);

    const source = this.sourceAt(row, col);
    const body = this.Logic.stripFormula(source);
    if (body.trim().length === 0) return null;

    const cellKey = this.cellKey(row, col);
    if (this.evaluating.has(cellKey)) return new this.self.$FormulaError('#REF!');
    this.evaluating.add(cellKey);
    try {
      // COLUMNAR FAST PATH: a bare aggregate over one range is computed
      // linearly over ground truth with the SAME reactive semantics (fine
      // tier small / block tier large, formulas via cached computeds). The
      // general parser's range aggregation is O(n²) in range size —
      // measured 27ms @ 10k cells → 40s @ 200k — so bulk aggregation
      // belongs to the columnar layer, exactly as desktop engines
      // special-case their range ops.
      const aggregate = this.Logic.matchSimpleAggregate(body);
      if (aggregate) return this.fastAggregate(aggregate);
      return this.parser.parse(body, {
        row: row + 1,
        col: col + 1,
        sheet: 'Sheet1'
      }) as FlyweightLogic.CellValue;
    } catch (error) {
      return error instanceof (this.self.$FormulaError as unknown as Function)
        ? (error as FlyweightLogic.CellValue)
        : new this.self.$FormulaError('#ERROR!');
    } finally {
      this.evaluating.delete(cellKey);
    }
  }

  /**
   * A live ad-hoc formula over the sheet (the demo's totals bar) — a cached
   * computed evaluating `body` through the same parser/seams, so a large
   * range inside it costs blocks, not cells. Thin: the computed is a pointer
   * to the named, directly testable evaluateAdHocFormula method.
   */
  liveFormula(body: string): ComputedRef<FlyweightLogic.CellValue> {
    let cached = this.adHocCache.get(body);
    if (!cached) {
      cached = computed<FlyweightLogic.CellValue>(() => this.evaluateAdHocFormula(body));
      this.adHocCache.set(body, cached);
    }
    return cached;
  }

  protected evaluateAdHocFormula(body: string): FlyweightLogic.CellValue {
    try {
      const aggregate = this.Logic.matchSimpleAggregate(body);
      if (aggregate) return this.fastAggregate(aggregate);
      return this.parser.parse(body, {
        row: 1,
        col: 1,
        sheet: 'Sheet1'
      }) as FlyweightLogic.CellValue;
    } catch (error) {
      return error instanceof (this.self.$FormulaError as unknown as Function)
        ? (error as FlyweightLogic.CellValue)
        : new this.self.$FormulaError('#ERROR!');
    }
  }

  /**
   * Linear aggregation over a range with the same observation semantics as
   * rangeValues: small ranges take fine per-cell tracking, large ranges take
   * block subscriptions + paused reads (formula cells through their cached
   * computeds; the derived-write bridge keeps blocks truthful). Numbers
   * aggregate; blanks/text are skipped (COUNT counts numbers, Excel-style);
   * an error value propagates.
   */
  protected fastAggregate(aggregate: FlyweightLogic.SimpleAggregate): FlyweightLogic.CellValue {
    const startRow = aggregate.startRow - 1;
    const startCol = aggregate.startCol - 1;
    const endRow = Math.min(aggregate.endRow - 1, this.rows - 1);
    const endCol = Math.min(aggregate.endCol - 1, this.cols - 1);
    const cellCount = (endRow - startRow + 1) * (endCol - startCol + 1);
    const isFineTier = cellCount <= this.Logic.FINE_RANGE_LIMIT;

    if (!isFineTier) {
      const firstBlock = startRow >> this.Logic.BLOCK_SHIFT;
      const lastBlock = endRow >> this.Logic.BLOCK_SHIFT;
      for (let col = startCol; col <= endCol; col++) {
        for (let block = firstBlock; block <= lastBlock; block++) {
          this.trackBlock(col * this.blockCount + block);
        }
      }
      pauseTracking();
    }
    try {
      let sum = 0;
      let count = 0;
      let min = Infinity;
      let max = -Infinity;
      for (let col = startCol; col <= endCol; col++) {
        const column = this.columns[col];
        for (let row = startRow; row <= endRow; row++) {
          let cellValue: FlyweightLogic.CellValue;
          if (isFineTier) {
            cellValue = this.valueAt(row, col);
          } else if (column.kind[row] === FlyweightLogic.Kind.Formula) {
            cellValue = this.formulaValue(row, col).value;
          } else {
            cellValue = this.rawAt(row, col);
          }
          if (typeof cellValue === 'number') {
            sum += cellValue;
            count++;
            if (cellValue < min) min = cellValue;
            if (cellValue > max) max = cellValue;
          } else if (this.Logic.isFormulaError(cellValue)) {
            return cellValue; // errors propagate, Excel-style
          }
        }
      }
      switch (aggregate.fn) {
        case 'SUM':
          return sum;
        case 'AVERAGE':
          return count === 0 ? new this.self.$FormulaError('#DIV/0!') : sum / count;
        case 'COUNT':
          return count;
        case 'MIN':
          return count === 0 ? 0 : min;
        case 'MAX':
          return count === 0 ? 0 : max;
        default:
          return new this.self.$FormulaError('#VALUE!'); // unreachable — union is exhaustive
      }
    } finally {
      if (!isFineTier) resetTracking();
    }
  }

  // --- writes ---
  /**
   * THE single write path. O(1) storage update + O(observers) notification.
   * Never allocates reactive state (peek-only bumps).
   */
  write(row: number, col: number, input: string): void {
    const column = this.columns[col];
    const trimmed = input.trim();
    if (this.Logic.isFormulaText(input)) {
      column.kind[row] = FlyweightLogic.Kind.Formula;
      column.text.set(row, input);
    } else if (trimmed.length === 0) {
      column.kind[row] = FlyweightLogic.Kind.Blank;
      column.text.delete(row);
    } else {
      const numeric = Number(trimmed);
      if (!Number.isNaN(numeric) && Number.isFinite(numeric)) {
        if (!column.numbers) column.numbers = new Float64Array(this.rows);
        column.kind[row] = FlyweightLogic.Kind.Number;
        column.numbers[row] = numeric;
        column.text.delete(row);
      } else {
        column.kind[row] = FlyweightLogic.Kind.Text;
        column.text.set(row, input);
      }
    }
    this.bumpCell(row, col);
    this.bumpBlock(row, col);
  }

  // --- diagnostics ---
  /**
   * Which cells does (row,col)'s formula CURRENTLY read? Re-parses once with
   * the read-tap on — it walks the same onCell/onRange path Vue tracks, so
   * the set IS the live dependency set (and visibly SHIFTS across an IF's
   * branch boundary). 1-based in/out, like the formula grid's traceDeps.
   */
  traceDeps(oneBasedRow: number, oneBasedCol: number): Array<[number, number]> {
    const row = oneBasedRow - 1;
    const col = oneBasedCol - 1;
    if (this.columns[col]?.kind[row] !== FlyweightLogic.Kind.Formula) return [];
    const body = this.Logic.stripFormula(this.sourceAt(row, col));
    if (body.trim().length === 0) return [];

    const previousTracer = this.trace.recording;
    this.trace.recording = [];
    pauseTracking();
    try {
      this.parser.parse(body, {
        row: oneBasedRow,
        col: oneBasedCol,
        sheet: 'Sheet1'
      });
    } catch {
      /* keep whatever reads happened before the error */
    } finally {
      resetTracking();
    }
    const recorded = this.trace.recording;
    this.trace.recording = previousTracer;

    const seenKeys = new Set<number>();
    const dependencies: Array<[number, number]> = [];
    for (const [row1, col1] of recorded) {
      const dedupeKey = row1 * (this.cols + 1) + col1;
      if (!seenKeys.has(dedupeKey)) {
        seenKeys.add(dedupeKey);
        dependencies.push([row1, col1]);
      }
    }
    return dependencies;
  }

  /** The observation census — the law, measurable. */
  stats() {
    return {
      fineRefs: this.cellVersions.size,
      blockRefs: this.blockVersions.size,
      formulaComputeds: this.formulaCache.size,
      adHocFormulas: this.adHocCache.size
    };
  }

  /**
   * Release a formula cell's cached computed (stops its derived-write
   * watcher). Production ties this to viewport/refcount eviction — see
   * DESIGN.md honest boundaries.
   */
  releaseFormula(row: number, col: number): void {
    const cellKey = this.cellKey(row, col);
    const entry = this.formulaCache.get(cellKey);
    if (entry) {
      entry.stopBridge();
      this.formulaCache.delete(cellKey);
    }
  }

  /**
   * Viewport-tied eviction: release overlay entries (fine refs + formula
   * computeds) for all rows OUTSIDE [keepStart, keepEnd]. Row-scoped and
   * column-agnostic. Block refs are kept (bounded: ≤ blockCount × cols).
   *
   * SAFETY relies on dependency LOCALITY: a released fine ref / computed
   * must not have live dependents outside the kept range. In this layout
   * the longest dependency reach is the running-sum chain (RUNSUM_BLOCK =
   * 50 rows), so callers must keep a margin ≥ that around the viewport.
   * A production impl replaces this with refcounts; documented boundary.
   *
   * Correctness after release is by re-materialization: the next
   * observation of a released cell creates a fresh ref/computed over the
   * unchanged ground truth.
   */
  evictOutsideRows(keepStart: number, keepEnd: number): number {
    let released = 0;
    for (const [cellKey, entry] of this.formulaCache) {
      const row = cellKey % this.rows;
      if (row < keepStart || row > keepEnd) {
        entry.stopBridge();
        this.formulaCache.delete(cellKey);
        released++;
      }
    }
    for (const cellKey of this.cellVersions.keys()) {
      const row = cellKey % this.rows;
      if (row < keepStart || row > keepEnd) {
        this.cellVersions.delete(cellKey);
        released++;
      }
    }
    return released;
  }

  /** Drop the entire overlay (watchers stopped). Ground truth untouched. */
  releaseAll(): void {
    for (const entry of this.formulaCache.values()) entry.stopBridge();
    this.formulaCache.clear();
    this.cellVersions.clear();
    this.blockVersions.clear();
    this.adHocCache.clear();
  }
}

export namespace FlyweightSheet {
  export const $Class = Static($FlyweightSheet); // anchor — it declares statics
  export let Class = Reactive($Class);
  export type Model = InstanceType<typeof Class>; // raw-instance type — collections, parameters, returns
  export type Instance = typeof Class.Instance;

  /* Types */

  export interface RangeRef {
    from: { row: number; col: number };
    to: { row: number; col: number };
  }

  export interface Column {
    kind: Uint8Array;
    /** Allocated on the first numeric write — formula columns never pay. */
    numbers: Float64Array | null;
    /** Sparse: typed text AND user-edited formula sources (pattern overrides). */
    text: Map<number, string>;
  }

  export interface FormulaEntry {
    value: ComputedRef<FlyweightLogic.CellValue>;
    /** Stops the derived-write bridge watcher (see formulaValue). */
    stopBridge: WatchStopHandle;
  }
}
ts
/**
 * Page controller for the flyweight grid, authored per the ivue operating
 * manual. The composition-API version of this logic carried seven
 * `computed()`s; under the doctrine exactly ONE survives (`visibleRows`,
 * render suppression) — every other derivation is a plain getter at zero
 * bytes per instance. The one computed is THIN: its body delegates to the
 * directly testable `buildVisibleRows()` method.
 */
import {
  computed,
  getCurrentScope,
  onMounted,
  onScopeDispose,
  ref,
  shallowRef,
  watch,
  type ComputedRef
} from 'vue';
import { Reactive } from '../../ivue';
import { Static } from '../../Static';
import { FlyweightLogic } from './FlyweightLogic';
import { FlyweightCell } from './model/FlyweightCell';
import { FlyweightSheet } from './model/FlyweightSheet';

class $FlyweightGridPage {
  /**
   * Scroll has physical walls: Chrome's compositor does scroll math in
   * FLOAT32 (dead past 2^24 = 16,777,216 px — a 28M px scroller stops at
   * ~row 599,186); Firefox caps element height at ~17.9M px. Cap the
   * physical height under both and map scroll ratio → virtual offset (the
   * scaled scrollbar every big-grid engine uses; ~2.4:1 at 1M rows).
   */
  protected static readonly MAX_SCROLL_HEIGHT = 12_000_000;

  /** Eviction margin ≫ the 50-row running-sum reach (dependency locality). */
  protected static readonly EVICT_MARGIN_ROWS = 512;

  constructor() {
    // Viewport-tied eviction, debounced so a fast flick doesn't thrash.
    // Plain watch: the constructor runs in setup() context, so the
    // component scope owns and stops it on unmount.
    watch(
      () => this.startRow,
      () => this.scheduleEviction()
    );

    onMounted(() => {
      this.timers.census = setInterval(() => this.pollCensus(), 500);
      this.installHarness();
    });

    // Timers die with the component (watchers are component-scoped already).
    if (getCurrentScope()) {
      onScopeDispose(() => {
        if (this.timers.census) clearInterval(this.timers.census);
        if (this.timers.evict) clearTimeout(this.timers.evict);
      });
    }
  }

  /** The one cast per class: instance code reads its own statics here. */
  protected get self() {
    return this.constructor as typeof $FlyweightGridPage;
  }

  // --- state ---
  get sheet() {
    return shallowRef<FlyweightSheet.Model | null>(null);
  }
  get creationMs() {
    return ref(0);
  }
  get scrollTop() {
    return ref(0);
  }
  /** Template-ref target — destructured by the SFC for ref="scrollEl". */
  get scrollEl() {
    return ref<HTMLElement | null>(null);
  }
  get editing() {
    return ref<{ row: number; col: number } | null>(null);
  }
  get draft() {
    return ref('');
  }
  /** Polled diagnostics, not model state — refreshed on an interval. */
  get census() {
    return ref({
      fineRefs: 0,
      blockRefs: 0,
      formulaComputeds: 0,
      adHocFormulas: 0
    });
  }

  // --- non-reactive infra (timers) ---
  protected readonly timers = {
    census: null as ReturnType<typeof setInterval> | null,
    evict: null as ReturnType<typeof setTimeout> | null
  };

  // --- derived (plain getters) ---
  get hasModel() {
    return this.sheet.value !== null;
  }
  get modelCells() {
    return this.sheet.value ? this.sheet.value.rows * FlyweightLogic.Class.COLS : 0;
  }
  get naturalHeight() {
    return this.sheet.value ? this.sheet.value.rows * FlyweightLogic.Class.ROW_HEIGHT : 0;
  }
  get totalHeight() {
    return Math.min(this.naturalHeight, this.self.MAX_SCROLL_HEIGHT);
  }
  get scrollScale() {
    return this.naturalHeight > this.totalHeight
      ? (this.naturalHeight - FlyweightLogic.Class.VIEWPORT_HEIGHT) /
          (this.totalHeight - FlyweightLogic.Class.VIEWPORT_HEIGHT)
      : 1;
  }
  /** Position in CONTENT space (0 … naturalHeight − viewport). */
  get virtualTop() {
    return this.scrollTop.value * this.scrollScale;
  }
  get startRow() {
    return Math.max(
      0,
      Math.floor(this.virtualTop / FlyweightLogic.Class.ROW_HEIGHT) - FlyweightLogic.Class.OVERSCAN
    );
  }
  get endRow() {
    const visibleCount = Math.ceil(
      FlyweightLogic.Class.VIEWPORT_HEIGHT / FlyweightLogic.Class.ROW_HEIGHT
    );
    return this.sheet.value
      ? Math.min(
          this.sheet.value.rows,
          this.startRow + visibleCount + FlyweightLogic.Class.OVERSCAN * 2
        )
      : 0;
  }
  /** Pin the window band under the physical scroll position (degenerates
   *  to startRow × FlyweightLogic.Class.ROW_HEIGHT when scale = 1). */
  get offsetY() {
    return (
      this.scrollTop.value - (this.virtualTop - this.startRow * FlyweightLogic.Class.ROW_HEIGHT)
    );
  }

  /**
   * The ONLY cell objects in existence — facades for the visible window.
   * The one surgical computed() on this page: without the cache, the
   * 500ms census poll would re-render the component and a plain getter
   * would rebuild ~520 facades per poll; cached, an unchanged window
   * returns the same array instance and the v-for never re-patches.
   */
  // computed: render-suppression — see above
  get visibleRows(): ComputedRef<FlyweightGridPage.PageRow[]> {
    return computed(() => this.buildVisibleRows());
  }

  /** Live full-column totals (block tier: 245 edges each). liveFormula is
   *  cached on the sheet, so rebuilding this array per render is pointer
   *  work — a plain getter suffices. */
  get totals(): { label: string; total: ComputedRef<FlyweightLogic.CellValue> }[] {
    const sheet = this.sheet.value;
    if (!sheet) return [];
    const lastRow = sheet.rows;
    return [
      {
        label: `SUM(A1:A${lastRow})`,
        total: sheet.liveFormula(`SUM(A1:A${lastRow})`)
      },
      {
        label: `AVERAGE(B1:B${lastRow})`,
        total: sheet.liveFormula(`AVERAGE(B1:B${lastRow})`)
      },
      {
        label: `SUM(D1:D${lastRow})`,
        total: sheet.liveFormula(`SUM(D1:D${lastRow})`)
      }
    ];
  }

  get activeRef() {
    const editing = this.editing.value;
    return editing ? FlyweightLogic.Class.colLabel(editing.col) + (editing.row + 1) : '';
  }
  get activeSource() {
    const editing = this.editing.value;
    return editing && this.sheet.value ? this.sheet.value.sourceAt(editing.row, editing.col) : '';
  }
  get activeRefLabel() {
    return this.activeRef || 'fx';
  }
  get activeSourceLabel() {
    return this.activeSource || 'click a cell to see + edit its formula';
  }
  get viewportStyle() {
    return { height: `${this.totalHeight}px` };
  }
  get rowsStyle() {
    return { transform: `translateY(${this.offsetY}px)` };
  }

  // --- methods ---
  protected buildVisibleRows(): FlyweightGridPage.PageRow[] {
    const sheet = this.sheet.value;
    if (!sheet) return [];
    const pageRows: FlyweightGridPage.PageRow[] = [];
    for (let row = this.startRow; row < this.endRow; row++) {
      const cells: FlyweightCell.Model[] = new Array(FlyweightLogic.Class.COLS);
      for (let col = 0; col < FlyweightLogic.Class.COLS; col++)
        cells[col] = new FlyweightCell.Class(sheet, row, col);
      pageRows.push({ row, cells });
    }
    return pageRows;
  }

  createModel() {
    this.editing.value = null;
    const startedAt = performance.now();
    const sheet = new FlyweightSheet.Class(FlyweightLogic.Class.ROWS_1M, FlyweightLogic.Class.COLS);
    this.creationMs.value = performance.now() - startedAt;
    this.sheet.value = sheet;
    this.pollCensus();
    // eslint-disable-next-line no-console
    console.log(
      `[flyweight] created ${(FlyweightLogic.Class.ROWS_1M * FlyweightLogic.Class.COLS).toLocaleString()} cells in ${this.creationMs.value.toFixed(1)}ms`
    );
  }

  onScroll(event: Event) {
    this.scrollTop.value = (event.target as HTMLElement).scrollTop;
  }

  /** Header label for a 1-based `v-for="column in COLS"` column. */
  headerLabel(columnNumber: number) {
    return FlyweightLogic.Class.colLabel(columnNumber - 1);
  }

  /** The 1-based, thousands-grouped row number the gutter shows. */
  rowNumber(row: number) {
    return (row + 1).toLocaleString();
  }

  isEditing(row: number, col: number) {
    const editing = this.editing.value;
    return !!editing && editing.row === row && editing.col === col;
  }

  edit(cell: FlyweightCell.Model) {
    this.editing.value = { row: cell.row, col: cell.col };
    this.draft.value = cell.source;
  }

  commitEdit() {
    const editing = this.editing.value;
    if (editing && this.sheet.value)
      this.sheet.value.write(editing.row, editing.col, this.draft.value);
    this.editing.value = null;
  }

  /** The edit's blur commits — its own handler, so a subclass can treat a
   *  blur apart from an Enter. */
  onEditBlur() {
    this.commitEdit();
  }

  /** Enter in the edit commits. */
  onEditEnter() {
    this.commitEdit();
  }

  pollCensus() {
    const sheet = this.sheet.value;
    if (sheet) this.census.value = sheet.stats();
  }

  scheduleEviction() {
    if (this.timers.evict) clearTimeout(this.timers.evict);
    this.timers.evict = setTimeout(() => {
      const sheet = this.sheet.value;
      if (!sheet) return;
      sheet.evictOutsideRows(
        Math.max(0, this.startRow - this.self.EVICT_MARGIN_ROWS),
        this.endRow + this.self.EVICT_MARGIN_ROWS
      );
      this.pollCensus();
    }, 300);
  }

  scrollToRow(row: number) {
    const scrollEl = this.scrollEl.value;
    if (!this.sheet.value || !scrollEl) return;
    const targetPx =
      (row * FlyweightLogic.Class.ROW_HEIGHT - FlyweightLogic.Class.VIEWPORT_HEIGHT / 2) /
      this.scrollScale;
    const clamped = Math.max(
      0,
      Math.min(targetPx, this.totalHeight - FlyweightLogic.Class.VIEWPORT_HEIGHT)
    );
    scrollEl.scrollTop = clamped;
    this.scrollTop.value = clamped;
  }

  /** Measurement/verification harness (same idea as the reference grids). */
  protected installHarness() {
    (window as unknown as { __fw: unknown }).__fw = {
      rows: () => (this.sheet.value ? this.sheet.value.rows : 0),
      cols: FlyweightLogic.Class.COLS,
      createModel: () => this.createModel(),
      hasModel: () => this.hasModel,
      creationMs: () => this.creationMs.value,
      stats: () => (this.sheet.value ? this.sheet.value.stats() : null),
      scrollToRow: (row: number) => this.scrollToRow(row),
      editCell: (row: number, col: number, input: string) =>
        this.sheet.value?.write(row, col, input),
      cellText: (row: number, col: number) => {
        const cellEl = document.querySelector(
          `[data-grid-cell][data-row="${row}"][data-col="${col}"]`
        );
        return cellEl ? (cellEl.textContent || '').trim() : null;
      },
      cellValue: (row: number, col: number) => {
        const value = this.sheet.value?.valueAt(row, col);
        return value && typeof value === 'object' ? String(value) : (value ?? null);
      },
      startRow: () => this.startRow
    };
  }
}

export namespace FlyweightGridPage {
  /* Identity */

  export const $Class = Static($FlyweightGridPage); // anchor — it declares statics
  export let Class = Reactive($Class);
  export type Instance = typeof Class.Instance;

  /* Types */

  /** One rendered row: its index plus the flyweight cells leased to it. */
  export interface PageRow {
    row: number;
    cells: FlyweightCell.Model[];
  }
}
vue
<script setup lang="ts">
/**
 * Standalone demo: 20 columns × 1,000,000 rows (20,000,000 cells).
 *
 * The SFC is now a thin shell per the ivue operating manual: ONE raw
 * `FlyweightGridPage` instance drives the template; only the template-ref
 * target is destructured. All logic lives as named methods on the class.
 */
import './grid.css';
import { FlyweightLogic } from './FlyweightLogic';

const Logic = FlyweightLogic.Class;
import { FlyweightGridPage } from './FlyweightGridPage';

const page = new FlyweightGridPage.Class();

// THE STATE DESTRUCTURE — every Ref/Computed the template touches, grouped.
// Plain getters (hasModel, modelCells, totals, offsets…) and methods stay
// dotted on the instance.
const {
  // state refs
  creationMs,
  census,
  draft,
  // computed refs
  visibleRows,
  // element refs
  scrollEl
} = page;
</script>

<template>
  <section class="fw-page">
    <header>
      <h1>Flyweight Grid — 20 × 1,000,000 <small>(20,000,000 cells)</small></h1>
      <p class="fw-sub">
        Columnar ground truth · flyweight cell facades · two-tier discovered dependency graph.
        Google Sheets caps at 10M cells — this document cannot exist there. The census below is the
        law, live:
        <em>cost ∝ observed, never ∝ existing.</em>
      </p>
    </header>

    <div class="fw-controls">
      <button class="fw-btn" @click="page.createModel()">create model (20M cells)</button>
      <template v-if="page.hasModel">
        <span class="fw-stat"
          ><b>{{ page.modelCells.toLocaleString() }}</b> cells</span
        >
        <span class="fw-stat"
          ><b>{{ creationMs.toFixed(1) }}</b> ms create</span
        >
        <span class="fw-stat"
          ><b>{{ census.fineRefs.toLocaleString() }}</b> fine refs</span
        >
        <span class="fw-stat"
          ><b>{{ census.blockRefs.toLocaleString() }}</b> block refs</span
        >
        <span class="fw-stat"
          ><b>{{ census.formulaComputeds.toLocaleString() }}</b> formula computeds</span
        >
      </template>
    </div>

    <template v-if="page.hasModel">
      <!-- Live totals over the FULL million rows (block tier: 245 edges each) -->
      <div class="fw-totals">
        <span v-for="entry in page.totals" :key="entry.label" class="fw-total">
          <code>{{ entry.label }}</code> =
          <b>{{ Logic.displayOf(entry.total.value) }}</b>
        </span>
      </div>

      <div class="fx-bar">
        <span class="fx-name">{{ page.activeRefLabel }}</span>
        <span class="fx-val">{{ page.activeSourceLabel }}</span>
      </div>

      <div ref="scrollEl" class="gc-grid-scroll" @scroll="page.onScroll">
        <div class="gc-inner">
          <div class="gc-head">
            <div class="gc-rownum gc-head-cell">#</div>
            <div v-for="col in Logic.COLS" :key="col" class="gc-cell gc-head-cell">
              {{ page.headerLabel(col) }}
            </div>
          </div>
          <div class="gc-viewport" :style="page.viewportStyle">
            <div class="gc-rows" :style="page.rowsStyle">
              <div v-for="pageRow in visibleRows" :key="pageRow.row" class="gc-row">
                <div class="gc-rownum">
                  {{ page.rowNumber(pageRow.row) }}
                </div>
                <div
                  v-for="cell in pageRow.cells"
                  :key="cell.col"
                  class="gc-cell"
                  :class="cell.cssClass"
                  data-grid-cell
                  :data-row="cell.row"
                  :data-col="cell.col"
                  :title="cell.source"
                  @click="page.edit(cell)"
                >
                  <input
                    v-if="page.isEditing(cell.row, cell.col)"
                    class="gc-edit"
                    v-model="draft"
                    autofocus
                    @blur="page.onEditBlur()"
                    @keyup.enter="page.onEditEnter()"
                  />
                  <template v-else>{{ cell.display }}</template>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    <div v-else class="fw-empty">
      No model yet — click <b>create model (20M cells)</b>. Creation fills ~95&nbsp;MB of typed
      arrays and allocates <b>zero</b> reactive state.
    </div>
  </section>
</template>
ts
import { Static } from '../../Static';

/**
 * Config, column layout and value mapping for the flyweight grid — a
 * static capability class rather than a bag of exports, so a variant
 * grid EXTENDS it (`class $WideGrid extends FlyweightLogic.$Class`
 * overriding COLS or patternSource) instead of forking the file.
 * Nothing here is reactive and nothing imports the parser — the same
 * division of labor as the formula grid's `FormulaLogic`
 * (reference, not imported).
 *
 * Layout — 20 columns × 1,000,000 rows = 20,000,000 cells, ~55% formulas
 * (matching the formula grid's density story), expressed as per-column
 * PATTERNS so formula sources cost 20 closures instead of 11M strings:
 *
 *   A–D  (0–3)   numeric source data (deterministic, ~7.7% blanks)
 *   E    (4)     =A{r}+B{r}            point arithmetic
 *   F    (5)     =C{r}-D{r}            point arithmetic
 *   G    (6)     =SUM(A{r}:D{r})       small range → fine tier
 *   H    (7)     =IF(A{r}>0,B{r},C{r}) conditional dependency (marquee)
 *   I    (8)     =H{r}*2               formula-on-formula (point)
 *   J    (9)     =J{r-1}+A{r}          running sum, block-reset every 50
 *   K…T  (10–19) even = data, odd = =<left1>{r}+<left2>{r}  (cross mesh)
 *
 * HOT PATH: Static()'s bound methods are plain-function speed once
 * read — the only per-call cost is reading the method through the
 * accessor inside a loop. FlyweightSheet's 9,000,000-call seeding loop
 * therefore destructures the methods it needs once
 * (`const { isDataCol, numDataValue } = this.Logic`) — one read of the
 * sheet's overridable Logic seam, so BOTH swap axes are honored: a
 * class swapped into the namespace slot, and a sheet subclass
 * overriding `get Logic()` to route the whole layer elsewhere — and
 * measures at or better than the original module functions (77-82ms vs
 * 89ms for 20M cells, in-browser medians). Ordinary call counts can use
 * `FlyweightLogic.Class.method()` directly and never notice.
 */
class $FlyweightLogic {
  static readonly COLS = 20;
  static readonly ROWS_1M = 1_000_000;

  /** Rows per coarse invalidation block (2^12). 1M rows → 245 blocks/column. */
  static readonly BLOCK_SHIFT = 12;
  static readonly BLOCK_ROWS = 1 << 12; // 4096

  /** Ranges up to this many cells subscribe per-cell (fine tier, precise);
   *  larger ranges subscribe per-block (coarse tier, O(blocks)). */
  static readonly FINE_RANGE_LIMIT = 64;

  /** Running-sum reset block — keeps dependency chains shallow (reference:
   *  the formula grid's RUNSUM_BLOCK rationale). */
  static readonly RUNSUM_BLOCK = 50;

  /** Row-windowing geometry for the demo UI. */
  static readonly ROW_HEIGHT = 28;
  static readonly VIEWPORT_HEIGHT = 448;
  static readonly OVERSCAN = 4;

  protected static readonly AGG_RE =
    /^\s*(SUM|AVERAGE|MIN|MAX|COUNT)\(\s*([A-Z]+)(\d+)\s*:\s*([A-Z]+)(\d+)\s*\)\s*$/i;

  /** Spreadsheet column label: 0→A, 25→Z, 26→AA … */
  static colLabel(colIndex: number): string {
    let label = '';
    let remaining = colIndex + 1;
    while (remaining > 0) {
      const letterIndex = (remaining - 1) % 26;
      label = String.fromCharCode(65 + letterIndex) + label;
      remaining = Math.floor((remaining - 1) / 26);
    }
    return label;
  }

  /** Which columns hold numeric source data (the rest are formula patterns). */
  static isDataCol(col: number): boolean {
    if (col <= 3) return true;
    return col >= 10 && col % 2 === 0;
  }

  /**
   * Deterministic numeric seed for a data cell — NUMERIC (no string
   * round-trip: 9M cells fill straight into Float64Arrays). ~7.7% blanks →
   * null. Same (row,col) → same value on every build.
   */
  static numDataValue(row: number, col: number): number | null {
    const seed = row * this.COLS + col;
    if (seed % 13 === 5) return null;
    const value = ((seed * 2654435761) % 100000) / 100 - 500; // −500 … 500
    return Math.round(value * 100) / 100;
  }

  /**
   * The default formula SOURCE for a formula-column cell — generated on
   * demand from the column pattern (row is 0-based; emitted refs are
   * 1-based). Data columns return null (their ground truth lives in the
   * typed arrays).
   */
  static patternSource(row: number, col: number): string | null {
    const rowNumber = row + 1;
    switch (col) {
      case 0:
      case 1:
      case 2:
      case 3:
        return null;
      case 4:
        return `=A${rowNumber}+B${rowNumber}`;
      case 5:
        return `=C${rowNumber}-D${rowNumber}`;
      case 6:
        return `=SUM(A${rowNumber}:D${rowNumber})`;
      case 7:
        return `=IF(A${rowNumber}>0,B${rowNumber},C${rowNumber})`;
      case 8:
        return `=H${rowNumber}*2`;
      case 9:
        return row % this.RUNSUM_BLOCK === 0
          ? `=A${rowNumber}`
          : `=J${rowNumber - 1}+A${rowNumber}`;
      default:
        if (col % 2 === 0) return null;
        return `=${this.colLabel(col - 1)}${rowNumber}+${this.colLabel(col - 2)}${rowNumber}`;
    }
  }

  /** 1-based column index from a spreadsheet label: A→1, Z→26, AA→27 … */
  static colIndexFromLabel(label: string): number {
    let index = 0;
    for (let position = 0; position < label.length; position++)
      index = index * 26 + (label.charCodeAt(position) - 64);
    return index;
  }

  /**
   * Detect a formula body that is EXACTLY one aggregate over one contiguous
   * range — the shape the columnar fast path can compute LINEARLY.
   * Everything else returns null and takes the general parser. This matters
   * because the stock parser's range aggregation is O(n²) in range size
   * (measured: 27ms @ 10k cells → 40s @ 200k) — bulk aggregation belongs to
   * the columnar layer.
   */
  static matchSimpleAggregate(body: string): FlyweightLogic.SimpleAggregate | null {
    const match = this.AGG_RE.exec(body);
    if (!match) return null;
    const aggregate = match[1].toUpperCase() as FlyweightLogic.SimpleAggregate['fn'];
    const startCol = this.colIndexFromLabel(match[2].toUpperCase());
    const startRow = parseInt(match[3], 10);
    const endCol = this.colIndexFromLabel(match[4].toUpperCase());
    const endRow = parseInt(match[5], 10);
    if (startRow < 1 || startCol < 1 || endRow < startRow || endCol < startCol) return null;
    return { fn: aggregate, startRow, startCol, endRow, endCol };
  }

  /** Does literal text start (after leading spaces) with '='? */
  static isFormulaText(text: string): boolean {
    const trimmed = text.trimStart();
    return trimmed.length > 0 && trimmed[0] === '=';
  }

  /** Strip the leading '=' to get the formula body. */
  static stripFormula(text: string): string {
    return text.trimStart().slice(1);
  }

  /** Structural FormulaError detection (no parser import here). */
  static isFormulaError(
    value: FlyweightLogic.CellValue
  ): value is { _error?: string; error?: string } {
    return typeof value === 'object' && value !== null && ('_error' in value || 'error' in value);
  }

  /** Resolve a NON-formula literal: '' → null, numeric → number, else text. */
  static evalLiteral(text: string): FlyweightLogic.CellValue {
    const trimmed = text.trim();
    if (trimmed.length === 0) return null;
    const numeric = Number(trimmed);
    return !Number.isNaN(numeric) && Number.isFinite(numeric) ? numeric : text;
  }

  /** Display string for a resolved value. */
  static displayOf(value: FlyweightLogic.CellValue): string {
    if (value == null) return '·';
    if (this.isFormulaError(value)) return String(value.error ?? value._error ?? '#ERR');
    if (typeof value === 'number') {
      return Number.isFinite(value)
        ? value.toLocaleString('en-US', { maximumFractionDigits: 2 })
        : String(value);
    }
    if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE';
    return String(value);
  }

  /** CSS class from error-ness, sign, and formula-ness. */
  static cssOf(value: FlyweightLogic.CellValue, isFormula: boolean): string {
    let base: string;
    if (this.isFormulaError(value)) base = 'gc-err';
    else if (value == null) base = 'gc-zero';
    else if (typeof value === 'number')
      base = value < 0 ? 'gc-neg' : value > 0 ? 'gc-pos' : 'gc-zero';
    else base = 'gc-text';
    return isFormula ? base + ' gc-formula' : base;
  }
}

export namespace FlyweightLogic {
  /* Identity */

  export const $Class = Static($FlyweightLogic); // raw — children extend this
  export let Class = $Class; // selected — callers read this

  /* Types */

  /** Cell kind tags in the columnar `kind` array. A plain enum, not a
   *  const enum: const enums cannot live in a namespace under
   *  isolatedModules (Vite), and the inlining they buy is noise here. */
  export enum Kind {
    Blank = 0,
    Number = 1,
    Text = 2,
    Formula = 3
  }

  /** A value a cell can resolve to (FormulaError detected structurally). */
  export type CellValue = number | string | boolean | null | { _error?: string; error?: string };

  export interface SimpleAggregate {
    fn: 'SUM' | 'AVERAGE' | 'MIN' | 'MAX' | 'COUNT';
    /** 1-based bounds, parser convention. */
    startRow: number;
    startCol: number;
    endRow: number;
    endCol: number;
  }
}

The template is the whole wiring layer: one new FlyweightGridPage.Class(), the element-ref destructure, and markup that reads named members. Twenty million cells, and the SFC still owns no state — every derivation the rows need is a getter or a method on the page class.

Open in StackBlitz ⚡ — the playground boots with this example's route and file active.

Released under the MIT License.