The config narrows the Component contract
The surface declares exactly three reads and one requested write.
Canonical Grid model
A custom Component surface reads model values and writes the selected scenario.
What this model gives you
15 min to study · Source reviewed 2026-08-25
Continue with guided practiceExpected checkpoint
With the exported 0.61.0 source, where Scenario is formula-owned despite the Component write allowlist.
The surface declares exactly three reads and one requested write.
useValue and useRows render values without duplicating them in UI state.
model.set is rejected until upstream changes Scenario to an input binding.
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