Companion film
The model is the app
Declare data, logic, and interface together, then open the same model as a working pipeline board.
16 secGrid 0.61.0
Open film page →Guided build · 17
Extend one CRM model with Table, Kanban, and Layout, then compare Map and custom Component patterns honestly.
You will finish with: A working multi-surface CRM plus a pattern map for choosing built-in views and trusted custom UI.
One reactive CRM model presented through Table, Kanban, and Layout, followed by a comparison with the Map and custom Component patterns in the neighboring canonical examples.
The working build stays in the Pipeline CRM file. Use the downloadable Completed solution to compare your final Table, Kanban, and Layout declarations. The other canonical examples are an architectural tour, not files to paste together: surfaces remain views over named model bindings, and write access still follows the language ownership contract.
Start with the Pipeline CRM example:
Accounts = [
{ name: "Acme", owner: "Mina", stage: "lead", value: 42000 },
{ name: "Northstar", owner: "Owen", stage: "qualified", value: 88000 },
{ name: "River Co", owner: "June", stage: "proposal", value: 56000 }
]
TotalPipeline IS currency = SUM(MAP(Accounts, row => row.value))
Checkpoint:
Accountscontains three rows andTotalPipeline = 186,000.
Everything that follows binds to these symbols. Do not copy the pipeline calculation into a surface configuration.
The Table surface maps record fields to columns. The Kanban surface maps stage to columns and uses name and owner for cards.
Both configurations point at Accounts:
[columns]
bind = "Accounts"
[items]
bind = "Accounts"
column = "stage"
title = "name"
assignee = "owner"
Checkpoint: the Table shows three rows; the board shows one card in Lead, Qualified, and Proposal. The model still owns one
Accountsvalue.
Editable writeback is available only when the bound source accepts writes. A visual drag must not invent permission to mutate a read-only value.
Open the Store Map example and inspect its binding pattern:
[[layers]]
name = "Stores"
bind = "Stores"
lat = "latitude"
lng = "longitude"
label = "name"
The map renderer handles presentation. Coordinates and revenue remain ordinary model data.
Checkpoint: three Bay Area locations render and
TotalRevenue = 328,000.
The Revenue Dashboard example binds Layout tiles to named formulas:
MonthlyRevenue = [120000, 135000, 142000, 150000, 168000]
Pipeline = 186000
Forecast IS currency = SUM(MonthlyRevenue#) + Pipeline
AverageMonth IS currency = AVERAGE(MonthlyRevenue#)
Checkpoint:
Forecast = 901,000andAverageMonth = 143,000. The Layout shows both without reimplementing either formula.
Change a monthly value and watch the formula update first; the tile follows the binding.
The Scenario Console declares exactly what its Component may read and write:
[bindings]
reads = ["Revenue", "Scenario", "Orders"]
writes = ["Scenario"]
Inside the Component, injected helpers subscribe to model values. The configuration also declares an intended write allowlist:
const revenue = model.useValue("Revenue");
const scenario = model.useValue("Scenario");
const orders = model.useRows("Orders");
<ui.Select
label="Scenario"
value={String(scenario ?? "")}
options={["bear", "base", "bull"]}
onChange={(next) => model.set("Scenario", next)}
/>
Checkpoint: the screen shows revenue
720,000, scenariobase, and three orders.
There is an important 0.61.0 ownership caveat in this exported canonical example: Scenario = "base" is a formula-owned binding, not an input. The writes = ["Scenario"] allowlist narrows
what a Component may request, but it does not make a formula-owned symbol externally writable.
Under the documented contract, the select write is rejected. Treat the exported screen as a read
and permission-boundary inspection, not a working scenario selector.
To test the interaction, open the downloadable Writable Component example instead. Its source owns the target explicitly:
input Scenario = "base"
Its Component keeps the same narrow writes = ["Scenario"] declaration, so both required layers
agree: the model owns a writable input and the UI is allowlisted to request that one write. This is
a site-owned corrective practice artifact; it does not alter or silently replace the exported
canonical example.
Custom Component execution requires a Grid environment that permits trusted custom UI. Built-in surfaces remain the simpler lower-trust path.
Return to the Pipeline CRM source and add two named results before END MODEL:
UpsideMultiplier IS percentage = 10pct
UpsidePipeline IS currency = TotalPipeline * (1 + UpsideMultiplier)
Then add a Layout surface with two static tiles whose formulas are =TotalPipeline and
=UpsidePipeline. Give it a unique namespace such as Pipeline_Summary so it can coexist with
Accounts_Table and Pipeline_Board.
Checkpoint: the CRM now has three surfaces over one formula layer. Total Pipeline is
186,000and Upside Pipeline is204,600.
Change Acme's authored value from 42,000 to 52,000. The Table and Kanban still show the same
three records, while TotalPipeline becomes 196,000 and UpsidePipeline becomes 215,600.
Add a third Layout tile bound to UpsideMultiplier. Keep every calculation in Grid formulas
rather than surface configuration.
Next, follow Build your first Grid app for the complete App Builder path: routed pages, guarded actions, read-only Preview, capability review, App Mode, and a packaged App Home.