← All examples
13

Canonical Grid model

Scenario Console

A custom Component surface reads model values and writes the selected scenario.

Scale
Large
Source
13-surface-scenario-console.grid
Length
48 lines
Collection
Apps & surfaces
Level
Intermediate
Runtime
Trusted custom UI
Version
1.0.0

Watch it in Grid

See this model in motion.

Watch the model respond in the product, then inspect the exact source and checkpoints on this page.

What this model gives you

A trusted Component that reads revenue, scenario, and order rows while exposing the current write-ownership boundary.

15 min to study · Source reviewed 2026-08-25

Continue with guided practice

What to notice

  • Explicit read/write allowlists
  • Reactive model hooks
  • Trusted custom UI
  • Binding ownership enforcement

Requirements

  • Trusted Component runtime
  • Current source is read-only for Scenario
  • No connector

Expected checkpoint

A known state for this walkthrough.

With the exported 0.61.0 source, where Scenario is formula-owned despite the Component write allowlist.

Revenue
720,000Revenue
Scenario
baseScenario
Orders
3 rowsOrders
Select write
rejected; Scenario remains baseOwnership contract
01 · Declare

The config narrows the Component contract

The surface declares exactly three reads and one requested write.

02 · Subscribe

Injected hooks follow model state

useValue and useRows render values without duplicating them in UI state.

03 · Enforce

An allowlist cannot create write ownership

model.set is rejected until upstream changes Scenario to an input binding.

13-surface-scenario-console.grid
Get Grid
MODEL "Scenario Console"
DESCRIPTION "A custom Component surface reads model values and writes the selected scenario."
VERSION "1.0.0"
AUTHOR "AI Agent"
TAGS "surface-template", "custom-ui", "component", "surfaces"

Revenue IS currency = 720000
Scenario = "base"
Orders = [
  { id: "ORD-1", customer: "Acme", amount: 42000 },
  { id: "ORD-2", customer: "Northstar", amount: 88000 },
  { id: "ORD-3", customer: "River Co", amount: 56000 }
]

ScenarioConsole!type = "component"
ScenarioConsole!config = """
version = 1
kind = "component"
title = "Scenario Console"

[bindings]
reads = ["Revenue", "Scenario", "Orders"]
writes = ["Scenario"]
"""
jsx ScenarioConsole!source = <jsx>
function App() {
  const revenue = model.useValue("Revenue");
  const scenario = model.useValue("Scenario");
  const orders = model.useRows("Orders");

  return (
    <ui.Stack gap={12}>
      <ui.Metric label="Revenue" value={revenue} format="currency" />
      <ui.Select
        label="Scenario"
        value={String(scenario ?? "")}
        options={["bear", "base", "bull"]}
        onChange={(next) => model.set("Scenario", next)}
      />
      <ui.Table columns={orders.columns} rows={orders.rows} empty="No orders yet." />
    </ui.Stack>
  );
}

render(<App />);
</jsx>

END MODEL