Grid Views
Grid Views
Start with Your first reusable Grid View or the composition cookbook; this page is the full reference.
A Grid View declares a custom surface in model source. Its indentation describes what the person sees, changes, and does; ordinary Grid expressions keep their existing meaning and run in the native runtime.
input qty IS integer = 2 VALIDATE 1..10 PRESENT Slider
price IS currency = 19.99
total IS currency = qty * price
STATE placed = FALSE
SURFACE Order AS "Order"
qty AS "How many?"
[price, total]
"Place order" -> placed = TRUE
"Order placed." IF placed
ENDSURFACE is contextual. A complete declaration header requires a newline;
surface = 4 and SURFACE Order = 5 retain their existing binding meanings.
Use spaces for indentation and align END with its declaration header.
A value may have an AS label and an IF or UNLESS guard. The last guard on
the line applies to the whole view. Parenthesize an expression-level guard.
Brackets arrange outline items across commas and down semicolons; write
show [1, 2] when the array itself is the value to display.
Text positions such as a page title, button label, confirmation or download
filename accept backtick interpolation. Comments and parentheses around the
template preserve that meaning: confirm `Save {Quantity}?` and
confirm /* review */ (`Save {Quantity}?`) read the same live binding.
The text example shows
these forms together. Literal-only positions, including a drawer title, page
path and table action's column heading, keep their existing restrictions.
MATCH subject opens a block whose lines are the ordinary arms, one case per
line; the right side of each arrow is an outline line and may carry its own
indented block. The arm grammar and the exhaustiveness check are the call
form's: a missing CHOICE arm is GRID_MATCH_NON_EXHAUSTIVE_ADT, never a
silent gap.
CHOICE Load = Waiting | Ready(rows: array) | Failed(message: string)
STATE current = Load.Waiting()
SURFACE Desk
# One arm per case; a missing arm is the ordinary MATCH diagnostic.
MATCH current
Load.Waiting() -> "Loading…"
Load.Ready(rows) -> rows
ELSE "Nothing to show yet."
Load.Failed(message) -> Alert message tone :danger
ENDA declaration's trailing PRESENT Slider chooses its usual element. An explicit
kind on the view line takes precedence; ownership and show/edit still decide
whether the placement can write. A local or imported element alias may also be
used: PRESENT QuantityField. Native elements must declare a value-bearing
subject; composites must accept exactly one parameter. A composite's subject
controls must match show or edit; an incompatible direction is a diagnostic.
Explicit element names inside the composite avoid recursively selecting its own
presentation. See declared presentation.
Use named ARRANGE regions for merged cells:
SURFACE Dashboard
ARRANGE [header, header; navigation, content]
header: "Overview"
navigation: "Sections"
content: "Details"
ENDRepeated names must fill one rectangle. Every region has exactly one labelled content block; the compiler reports missing, unused, duplicate, or nonrectangular regions. Positions and spans become static layout metadata, without model reads.
In Code, select ARRANGE in the outline to edit Named regions. Add or remove
rows and columns, then choose the region for each cell. Rename a region without
changing its content. Add region creates a name and placeholder content;
assign it at least one cell. Remove [name] and content stages removal and
lists the affected regions before Apply layout changes source. Retained
content keeps its bindings, guards, comments and embedded payloads. Nested and
flat labelled blocks are supported, and the whole edit uses one editor Undo step.
Every region must remain one filled rectangle, with a distinct name and at least
one cell; the compiler explains invalid arrangements without applying them.
To start a layout, choose Named region layout in Add element or page.
Select a region label such as content: to add elements inside it or move
existing elements there. Content written on the same line, such as
content: NumberInput quantity, appears as a separate selectable child with its
own properties. The region's visibility condition stays on the region.
Simple text, binding and option edits keep the inline spelling. Structural
changes and native event/slot edits expand the affected region onto indented
lines automatically, preserving conditions, comments and multiline payloads.
The expansion and requested change share one editor Undo step. A container
inside a region has its own Inside selected element destination, distinct
from Inside selected region. Moves across declaration, page or repeater scopes
show a source review before Apply. Matrix comments stay
in place when names or cell assignments change. Resizing retains every matrix
comment in its original order above the new cells, inside the matrix brackets;
Properties explains this before applying. Comment text and line endings are
preserved even when their former rows or columns are removed. The visual matrix
supports up to 64 rows, 64 columns and 256 regions; larger matrices remain in
Code. See the
region layout example.
Pages use PAGE Name(parameters) AS "Title" AT "path/:parameter". Each
parameter must name a declared input, default or local; the URL carries
its value. Declare a parameter local when only the View reads it.
A page's lines may be written flat after its PAGE line or indented beneath
it. Either way, the locals, named actions and ELEMENTs among them belong to
that page alone; declare one before the first PAGE to share it across pages.
A shell drawer keeps part of the View beside every page in the workbench's left
or right drawer. Write it before the first PAGE, at most one per side:
input CustomerSearch = ""
input ShowClosed = FALSE
SURFACE Orders
# The filters stay open while the viewer moves between pages.
DRAWER left "Filters"
CustomerSearch AS "Customer"
ShowClosed AS "Include closed orders"
PAGE Queue AS "Open orders"
"Orders waiting for review"
PAGE Archive AS "Closed orders"
"Orders already settled"
ENDThe side is left or right; the optional title is plain text ("Filters" or
AS "Filters"). Outside the workbench (Run mode, print, packaged apps and
exports) the drawer's lines stay in the page where they are written. Its
locals, named actions and ELEMENTs belong to the drawer, as a page's do. A
line such as Drawer ShowFilters side :left is still the standard in-canvas
Drawer element. In Builder mode, Shell drawer in Add element or page
adds one before the first page, and deleting the drawer's outline item removes
it with its lines.
Reusable ELEMENT Name(parameters) … END declarations hold view outlines or
contracts that map phrases to properties, lenses, slots, and events. Contracts
are libraries; the outline scanner does not contain a widget vocabulary.
Phrases match longest first and may continue on directly indented lines. A
blank line detaches a continuation. Operator-led phrases start their own line.
Boolean split lenses name each callback and its fixed value:
open means open, set by onOpen(TRUE), onClose(FALSE) : boolean. A close-only
lens names just onClose(FALSE). These callbacks use the same write guards and
Form staging as other lenses.
A contract phrase that begins with show, such as react-day-picker's show outside days, is read as the element's phrase on its indented line rather
than as a show placement.
Composite parameters can declare amount: number (read-only),
amount: edit number (a direct writable input/local), or
approve: action(number) (a named action with one argument). Supported value
roots are number, string, boolean, array, object and unknown.
source: read table requires a resident table handle and preserves native
paging with private query parameters. It has no literal default or edit table
variant; declare row actions separately with their existing permissions.
Source packages retain this handle contract and recheck the receiving binding.
Known incompatible types and callback arities are refused at admission.
Trailing read parameters may have literal defaults, such as
title: string = "Amount"; edit and action parameters require explicit arguments.
Unannotated parameters retain their existing inferred behavior.
Scoped content uses BODY "summary"(amount) in the definition and
SLOT "summary"(current) at its use. The definition supplies expressions;
the fill declares distinct aliases while keeping caller scope. Repeated BODY
placements must agree on arity. Defaults keep definition scope and per-instance
locals. The cookbook explains ownership and current limits.
Source packages preserve and verify these interfaces. App-template export
refuses explicit parameter contracts and scoped slots instead of erasing them.
local declares private browser state. Add remembered after the initial value
and its clauses (local tab = :queue remembered) to keep a local's last
committed value in the viewer's browser across reloads of the same model,
partition and View; a stored value that no longer fits the declaration's
initial, tag or VALIDATE is discarded. Nothing reaches the host or other
viewers, and a sensitive local cannot be remembered.
A Grid expression that reads a local or a viewer fact runs in the
viewer's browser, on the same Grid evaluator as the model: a placed value such
as show (ListPrice * quantity), a guard such as "Wide" IF viewer.width >= 640 AND open, or a local action's value such as quantity = MIN(quantity + 1, 99). The compiler collects those expressions into the surface's browser
module. Model values they name are read from the host as usual; nothing a
local holds reaches the model except as an action's payload. The module is
checked against the portable function catalog when the model compiles, so a
function it cannot run is GRID_VIEW_NOT_STAGEABLE naming that function.
Rows held in a local work the same way: FOR line IN lines IF line.amount > 50 key line.id, a computed field such as Text line.amount * (1 + TaxRate), or a
computed column in a Table over the local runs in the browser too, and each
row keeps its stored fields. Rows prepared by JavaScript (FOR r IN <js>…</js>)
are filtered and computed in that JavaScript. Event fields and callbacks
handed to imported components still use JavaScript glue. The
price check example
places a private total, a shipping guard and a clamped local action; the
private rows example
filters and extends a local list.
In the body of a FOR, such an expression may also read the row and that
row's own locals. Each row keeps its own value: with local qty = 1 in the
body, show (item.price * qty) shows each row's price times that row's
quantity, and editing one row's quantity recomputes that row only. The row can
come from the model or from a local. An expression in an inner FOR cannot
read the outer row or the outer row's locals. A filter or computed column over
model rows that reads a local also runs in the browser; a resident table handle
cannot, because its rows stay on the host. The
private quantities example
adds a private quantity and cost to each model row.
A local inside an element belongs to that element by position: the second
Card in a block is card-1, so inserting another Card above it hands its
local, remembered value included, to the new one. A literal key on the
element's line pins the element instead, and everything inside it:
SURFACE Desk
Card key :summary
local open = FALSE remembered
edit open
ENDThe key is a symbol, nonempty string, finite number or boolean; sibling keys
must differ, and a computed key is refused because it has no value when the
View compiles. Each placement of an ELEMENT owns its own locals, including
those declared in a nested block of its body.
SCRIPT <js>…</js> and inline
<js>…</js> contain real JavaScript; STYLE <css>…</css> contains real CSS. These
payloads are verbatim. Tags are the canonical form; the js"…" string form
still parses and the formatter rewrites it to the tag. Inline <js> reads the model bindings
it names, by their exact spelling, and each such name is a recorded read of the
View; USING … AS renames a value, passes a transport status (pending Quote)
or hands a value to a SCRIPT export, which receives nothing it was not given.
JavaScript is never a second evaluator for Grid expressions.
Code checks named helpers against known standard/native property contracts and
native asks signatures at each use. For example, a helper returning text for
Stack gap receives a type error at that helper's Grid reference. Multiple uses
of the same helper keep their own expectations. Ordinary property helpers
receive a context object; a bare named asks helper receives positional
arguments. The JavaScript module remains editable at its declaration. These
checks also supply contextual parameter types inside unannotated local helper
declarations, using the existing TypeScript worker. Multiple callers contribute
a union of input types, and author annotations take precedence. Completion and
diagnostics still point to authored source; the generated types are not edits.
Imported library helpers remain outside these named-export checks.
A css"…" typed literal (css"""…""" across lines) is verbatim text as well,
but it is not a stylesheet. Placed or shown in a View it renders as read-only
code, the way <js>…</js>, json"…" and the other code-text literals do, and it is
never injected; no element takes it as a style phrase. The surface's
stylesheet is only STYLE <css>…</css>.
# A css literal is verbatim text; a View shows it as code and never injects it.
Snippet = css".hero { padding: 2rem; border-radius: var(--radius); }"
SURFACE Desk
Snippet AS "Copy this rule"
ENDEvery setting an imported component declares is a phrase (ruling 37). Settings
no model value can supply — refs, component-valued props and callbacks with
nonserializable arguments — take JavaScript at the use site: ref <js>(el) => el && el.focus()</js>, render cell cell naming a SCRIPT
export, or an imported element by name for a component-valued setting (as Anchor). A model value there is GRID_ELEMENT_PHRASE_TYPE. className and
style are the universal class and style phrases.
A surface can react to the keyboard, the window and its own lifecycle. Write
an on event at the surface's top level, or directly in a PAGE to scope it
to that page. Its body is an ordinary arrow body:
STATE refreshed = 0
SURFACE Desk
local q = ""
on key "/" -> focus q
on key "mod+r" -> refreshed = refreshed + 1
on visible -> refreshed = refreshed + 1
q AS "Search"
ENDThe events are key "<combo>", resize, visible, mount and unmount. A
plain key is ignored while someone is typing in a field. A row source can be
prepared in the browser with <js>, with no model round trip:
input Requests = [{id: 1, customer: "Ada"}]
SURFACE Queue
local q = ""
q AS "Filter"
FOR r IN <js>Requests.filter(r => r.customer.includes(q))</js> key r.id
Text r.customer
ELSE "No matching requests."
ENDA line that is one <jsx>…</jsx> block is a React subtree (ruling 36). It
sees the model bindings its {…} expressions name, each local with a
React-style setter, viewer, and imported elements by their tag names:
input Forecast = [3, 5, 8]
USE "react:recharts" EXPOSING (LineChart)
SURFACE Outlook
local q = ""
<jsx><input value={q} onChange={e => setQ(e.target.value)} placeholder="Filter" /></jsx>
<jsx><LineChart data={Forecast} width={Math.max(viewer.width, 240)} /></jsx>
ENDAn indented block under a <jsx> line is its children, so a React wrapper
can hold ordinary outline lines. To edit a model input from JSX, name its
setter: setQty(5) writes the input qty as a placed control would. For any
other change, declare a named action and call it; each call is one ordinary
action run:
input qty IS integer = 2 VALIDATE 1..10
STATE decision = ""
SURFACE Review
decide(choice) -> decision = choice
<jsx><section className="card">{children}</section></jsx>
qty AS "How many?"
<jsx><button onClick={() => decide("approved")}>Approve</button></jsx>
ENDA <jsx> line does not have the ui.* kit or the model.* verbs: use outline
elements, setters and named actions instead, or write the whole surface as
React (below) when you need them.
A model value that holds JSX text still displays as code.
An element may place itself where the data can stop it. Each level reads its own row's children, so only the levels the data has are shown:
input Folders = [
{id: "docs", name: "Docs", children: [{id: "specs", name: "Specs", children: []}]}
]
SURFACE Files
ELEMENT Folder(f)
Text f.name
FOR child IN f.children key child.id
Folder child
ELSE "Empty."
END
FOR f IN Folders key f.id
Folder f
ELSE "No folders."
ENDExpansion stops at 32 levels and says so. A self-placement outside any FOR,
IF or MATCH is GRID_VIEW_RECURSION, since nothing could end it.
Eject to Component turns a view into a React module: its outline as JSX,
one G.* element per line, which you can edit by adding ordinary React
between the elements. The module runs through the same renderer as the view.
A whole React program as the surface
When the surface is entirely your own React, put the program in one <jsx>
block and end it with render(…). This is a Component surface declared in
source: it has the ui.* kit, the model.* verbs and every React hook in
scope.
input Price = 2
input Quantity = 3
Revenue = Price * Quantity
SURFACE Pricing AS "Price check"
<jsx>
function Panel() {
// Reads are the names this program states.
const price = useCell("Price");
const { Quantity, Revenue } = useCells(["Quantity", "Revenue"]);
return (
<ui.Stack>
<ui.Text>{price} x {Quantity} = {Revenue}</ui.Text>
<ui.NumberInput value={Quantity} onChange={value => setCell("Quantity", value)} />
</ui.Stack>
);
}
render(<Panel />);
</jsx>
ENDYou do not list what the program reads and writes. The compiler takes the
names from the calls themselves, so the surface above may read Price,
Quantity and Revenue and write Quantity, and nothing else.
- Write each name as a string literal. A name the code computes, such as
useCell(key), isGRID_VIEW_NOT_STAGEABLE, because nothing can be derived from it. - A write must name an input or a cell. Writing a formula or
STATEisGRID_VIEW_NOT_WRITABLE. - The program must be the surface's only line. A
<jsx>block that does not end inrender(…)is a React subtree inside an ordinary view.
A Component created before this form keeps working. To move it, open it in Code and choose Convert to SURFACE. The review shows the contract you declared by hand beside the one derived from the program, and lists what narrows and what widens. Your program is carried over unchanged. Accepting the review opens the change in Whole model as one Undo step; nothing is applied until you save.
An imported element can name a tier-2 adapter with adapter exportName inside
its ELEMENT … FROM "react:…" contract. Define that export in the surface's
or source library's SCRIPT <jsx>…</jsx>. JSX stays verbatim until the selected
browser preparation step; the model compiler never executes it. For example,
inside an existing locked element contract:
subject means value, set by onChange : number
caption means label : string
on reset tells onReset()
parts label, value
adapter renderControlThe corresponding module can wrap the locked export:
import { Control } from "react:your-controls@1";
import { useRef } from "react";
export function renderControl({ subject: [value, setValue], caption, tell, parts, className }) {
const root = useRef(null);
return <label ref={root} className={className} {...parts.label}>
{caption}
<Control value={value} onChange={setValue} {...parts.value} />
<button onClick={() => tell("on reset")}>Reset</button>
</label>;
}Here your-controls represents your installed, locked package, and Control
must be one of its admitted exports. Adapter props use contract phrase names;
a lens is [value, setValue], the default body is children, and named slots
retain their phrase names. Multiword phrases use bracket access. tell accepts
the full declared event phrase and its positional arguments. parts.label is
{ "data-part": "label" }: spread it onto the actual styleable DOM element, or
onto a component that forwards DOM attributes. An adapter chooses that mapping;
a foreign component's internal DOM is not guessed. parts, tell, className
and the default children slot cannot collide with other adapter phrase names.
Hooks and refs use the host's React instance. Static imports resolve only to the contract's locked package references (or an unambiguous package name), React, React DOM, and the React JSX runtime. Unlisted imports refuse loading. Adapters retain ordinary lens conversion, validation, Form staging and event write guards. They require the existing trusted workspace or package sandbox, are React-only, and are prepared as self-hosted JavaScript modules in published packages; the package does not evaluate JSX source or load a JSX compiler at runtime.
See the SCRIPT contract exercise.
Use SCRIPT <jsx>…</jsx> when a helper produces React elements or
component-valued properties. It is real JavaScript with JSX, with the same
explicit Grid value bridge and trust requirements. React is available, and
import React, { useMemo } from "react" uses the renderer's existing React
instance. Component imports use the View's exact locked library references.
Code checks JSX and maps errors and completion edits to the original block;
headless View CHECKs use the same React declarations. Ordinary <js> scripts
do not gain React globals. Preview prepares selected JSX when loading the module;
packaged Views and Vue/Svelte/Solid archives prepare it during export and load
ordinary module files. Library scripts retain their separate namespaces.
See the JSX helper example,
which supplies a custom day button through an admitted component property.
Contract-selected adapters also support lens pairs, tell and slot wiring as
described above. The adapter example
wraps a pinned chooser, focuses it through a private ref, and retains ordinary
Grid selection and event handling. Code completes control.subject and
parts.help, and reports invalid contract fields and tell phrases at their
original JSX bytes. Locked component imports also offer the native props retained
by their contracts. For example, changing the chooser's options={options} to
options={1} reports a type error at that JSX attribute. Named/default imports
and unambiguous package aliases use the exact admitted export list; unknown
exports and unadmitted imports still report errors.
Re-importing a package also projects bounded component prop declarations into
its library lock. These retain required and unmapped props, unions and callback
signatures for Code, without loading package code. Older locks use the contract
as a fallback. Recursive serializable shapes are retained as bounded type
graphs (a record repeated across props is one definition); a React-node value
is unknown, while the record that holds it stays structural; shapes past 16
levels, 2,048 distinct types for a contract (4,096 for a component) or the
byte limits become unknown. These are bounded declarations, not a copy of
the package type graph. Each View
owns its import declarations, so two Views can use different package versions
without sharing types. Headless CHECK uses the same projected declarations and
source maps, without loading package code.
Formatting does not wait for a lock. The formatter's syntax-only parse reads
an element whose declaration is unavailable, such as a React import that is
not yet locked, by shape: the word after its name is its subject, its indented
lowercase lines are its phrases, and its on … lines are its events. Nothing
is compiled from it; compiling still requires the import and its lock and
reports the use exactly as before.
Unknown event phrases and extra captured arguments are refused; a declared but
unused event does nothing. Lens setters retain the original conversion, revision,
permission and Form rules even if an event shares the native callback name.
A phrasebook can replace the adapter while retaining the locked component.
An imported contract with parts needs an explicit component adapter before it
is placed. Spread parts.label, parts.value, and the other declared attributes
on the corresponding elements; the compiler diagnoses a missing adapter with
the repair spelling. Declaring names cannot identify a third-party DOM node.
Missing exports and render/event failures use source-bound reporting; imported
selections link to their caller's USE while retaining the definition origin.
Adapter authoring metadata is bounded to 32 KiB, 64 events and 64 parts per use.
Native event contracts can retain their callback signature after tells, for
example on pick tells onPick(chosen) : (chosen: { label: string }) => void.
Known literal inputs and locals also check tuple lengths and positions, optional
trailing tuple entries, literal choices and discriminated record alternatives.
Lens converter glue receives separate model-side and foreign-side expectations:
goes in maps the model type to the declared element value type, and comes out
maps back. Known scalar and record types are retained; raw collection wire shapes
remain unknown. Grid lambda converters diagnose known return mismatches during
admission. The raw model value is not checked against the foreign phrase type
before its declared read converter runs.
Write the converters as contract lines under the imported element. subject goes in as … maps the model value to what the component expects, and subject comes out as … maps the component's value back before the write. A Grid lambda
on goes in as is hoisted into the model; a Grid lambda on comes out as runs
in the rule, on the raw payload; JavaScript glue runs in the browser on either
side. A converter that is neither a Grid lambda nor glue is
GRID_ELEMENT_ARGUMENTS. This example needs its package locked first, with
the View Library installation command printed by GRID_LIBRARY_NOT_LOCKED.
input DueDate IS date = date"2026-10-01"
# The component wants ISO text; the model keeps a date.
ELEMENT DayPicker FROM "react:react-day-picker@10.0"
subject means selected, set by onSelect
subject goes in as d => TEXT(d, "yyyy-MM-dd")
subject comes out as s => DATEVALUE(s)
END
SURFACE Desk
DayPicker DueDate
ENDImported SCRIPT helpers are checked in isolated library documents with their
contract parameter and result types. Their diagnostics point to the caller's
USE statement; imported bytes cannot be edited through that anchor. Code and
headless CHECK share helper contextual typing and source-map handling.
These checks use admitted literal data; they do not evaluate formulas. Dynamic values retain the ordinary conservative type checks.
The importer now preserves the types of admitted event values, including dates,
records and optional fields; older pinned contracts need an explicit re-import
to gain these annotations. Inline JavaScript in the handler receives the declared
values and Code checks member access and offers completion. Untyped event values
remain unknown in Code. Event values shadow same-named locals; an explicit
USING alias overrides that capture. A selected event member retains its type
through USING, and the event scope ends with its handler. Named helpers used
inside an arrow receive captured fields on their normal context object.
Trailing raw DOM events remain excluded. Rest, overloaded and nonserializable
callbacks need adapters. Grid admission also checks finite imported record,
array, union and callback shapes against inferred property/lens values. In event
handlers it checks nested declared fields, nullable payloads and array accesses.
Unknown aliases, index signatures and complex foreign types stay open. This
bounded contract reader does not execute foreign code or validate runtime payloads.
Grid autocomplete uses those declared records too: payload.customer. offers
customer fields, and payload.items[1]. offers the array item's fields. Optional
access retains ?; choosing a name with spaces inserts quoted brackets. A union
offers fields common to its non-null alternatives, and shadowed parameters keep
their own scope. Unknown aliases and arbitrary function results do not acquire
invented fields. These hints have a separate bounded authoring budget; a large
contract remains usable even when Code withholds some suggestions.
See the typed day-click exercise.
Built-in Kanban, Calendar and Map handlers also carry event types into Code.
Destination columns, calendar start/end values and map selection identifiers are
strings; calendar patches map column names to strings. Known model row fields
supply completion and scalar hints for card, event, feature and row.
Numeric fields allow numbers or exact numeric text, so narrow them before using
representation-specific methods. Nested, dynamic and resident-table fields stay
unknown; previous Kanban column values are unknown because the source column
can hold any value. Field hints are bounded to 256 names and 16 KiB; omitted
names retain unknown access. Grid handlers also check direct field reads against
complete known row shapes: card.titel reports the declared fields, and
column.value explains that the destination column is a string. This covers
model/local updates, guards, notifications and page arguments. LET/LAMBDA
parameters retain their own scope. Unknown or truncated rows, nested data and
opaque imported callback types stay open. These checks do not validate runtime
payloads.
In Grid handlers, type card. (or the event's row, event or feature name)
to choose a known field. Suggestions work inside balanced calls and retain
lexical scope: a local parameter named card does not borrow the event's fields.
Choosing a field containing spaces or punctuation inserts bracket syntax, such
as card["order total"]; optional reads retain their ?. A reusable element
used with different row schemas offers only fields common to those uses.
Unknown event records do not offer unrelated model names. Large or incomplete
expressions that cannot establish scope keep source editing without field
suggestions. Nested built-in row inference and complex foreign callback types
remain separate work; finite imported event records use their declared shapes.
The ticket-selection exercise
uses a private browser message and never writes model inputs. Calendar's end
column setting can be written on its own indented line; a closing END,
END SURFACE or END ELEMENT still needs the declaration's indentation.
The formatter retains foreign payload bytes, comments, and expression spelling.
Imported operator-led phrases use their admitted contract vocabulary through
format_source_with_resolver. The compiler formatter CLI can resolve local
.grid libraries with --mode fmt --library-root <directory>; imports must
remain inside that directory. Without a resolver it preserves unknown imported
phrases rather than guessing their boundaries. Native package contracts use the
same locked resolver supplied to the compiler.
An arrow can pin its rule identity with a literal key:
"Place order" key :place_order -> placed = TRUEChanging the label or moving the arrow through presentation wrappers keeps its touch input and outcome identity within that page or composite instance. Keys must be unique in that scope and be nonempty strings/symbols, finite numbers, or booleans. A model expression cannot be a key. Without a key, a literal-label arrow uses its existing path and label. An interpolated label uses canonical syntax: comments, parentheses and locations do not rename the action, while changing the label or a hole expression does. Current input values do not participate in its identity.
Client event actions use the recognized event kind and, for keyboard events, the decoded key and modifiers. Comments and equivalent key escapes do not rename their touch inputs or outcomes. V3 changes existing unkeyed template and event action IDs; literal-label and explicitly keyed button IDs stay the same. These changes apply on the V3 development branch.
Actions, outcomes and pacing
An arrow body that writes model values runs as one rule in the runtime. A body
that writes only local values runs in the browser and never reaches the
model. One body cannot write both.
Viewer actions
Viewer actions run only for the person who clicked, in source order, after the model writes commit. They never reach the runtime. Each one adds the capability shown to the View's manifest.
| Action | Effect | Capability |
|---|---|---|
go Page(args), go back |
Navigate | app.route |
notify "text" tone :success |
Show a notice | app.notify |
copy expr |
Put a value on the clipboard | clipboard.write |
download expr AS "name.csv" |
Hand the viewer a file | file.download |
focus Name |
Move focus to a placed control | app.focus |
A notice's tone is :info, :success, :warning or :danger. Notices use
the View's live region, and a danger notice is announced assertively. A download
needs a file name without a path. focus names a binding or local that the View
places as an interactive control.
toggle name flips a boolean local. The other local actions are
name = value, name += number and name -= number, where the value is a
literal, another browser value or JavaScript glue, and RESET name, which
restores the declared initial value.
input qty IS integer = 2 VALIDATE 1..10 MESSAGE "Order between one and ten."
STATE placed = FALSE
Summary = `Order of {qty}`
SURFACE Order
local showNotes = FALSE
local notes = ""
qty
# Commit the order, then confirm it on this viewer only.
"Place order" ->
placed = TRUE
notify "Order placed." tone :success
focus qty
"Copy summary" -> copy Summary
"Download summary" -> download Summary AS "order.txt"
# Browser-only state: nothing reaches the model.
"Notes" -> toggle showNotes
notes IF showNotes
ENDThe compiler derives the View's capability manifest from what it places and
does: model.read for reads, model.write for lenses and touch inputs,
app.route for go, and the viewer-action capabilities above. ALLOW, once
per surface at its top level, states a ceiling for that manifest. A View that
needs more than it allows is GRID_VIEW_CAPABILITY_EXCEEDS, which names the
missing capability, so review becomes a diff of the derived manifest against
the stated intent.
input qty IS integer = 2
total IS currency = qty * 19.99
SURFACE Order
# The derived manifest may not exceed this ceiling.
ALLOW model.read, model.write, app.route
qty AS "How many?"
Metric total
"Checkout" -> go Receipt
PAGE Receipt
"Thanks for your order."
ENDOutcomes and ELSE
Each model arrow reports an outcome. The button is busy while the action runs,
and it finishes as done, rejected or failed. rejected means a VALIDATE
contract or write guard refused a write; the message is the contract's
MESSAGE. failed means an effect in the body errored after the values
committed. When effects are still running, or held by undoable within, the
View treats the committed values as done and runs the viewer actions. A later
effect failure is reported on the same button.
Every click ends with an outcome, so a button never stays busy. A click that
THROTTLE, DEBOUNCE or SERIAL LATEST skips finishes quietly as
superseded: nothing ran for it, and no viewer actions or ELSE follow. If no
result arrives within 30 seconds, the click fails with "No result arrived for
this action within 30 seconds. It may still complete; check before trying
again." and its ELSE runs. If a viewer action fails after the values
committed, the button still reports done and a notice says that a follow-up
step failed; ELSE does not run.
An ELSE block directly after the arrow runs when the action is refused, in
place of the viewer actions, and when a later effect fails. It contains viewer
actions only, and outcome.message reads the message:
input amount IS currency = 1000 VALIDATE <= 5000 MESSAGE "Amounts above 5,000 need a manager."
SURFACE Request
amount
# A refused write runs the ELSE block instead of the follow-up actions.
"Add 1,000" ->
amount += 1000
notify "Amount raised." tone :success
ELSE
notify outcome.message tone :danger
ENDPrediction
A body whose model writes are all plain = assignments of literals, browser
values (a local, a row field or an event field) or JavaScript glue results is
predicted. The View shows the new values at once, and the committed result
confirms or replaces them; a glue result is predicted with exactly the value
the click sends, and the glue runs once. Bodies with +=, -=, ?=, table
mutations, effects or a sensitive local in the payload (directly or through
glue) are not predicted; their button stays busy for one round trip. In the viewer-action example above, placed = TRUE is
predicted.
Pacing clicks
A model arrow admits every click in order, keeping up to 256 waiting clicks.
A phrase on its own line in the body changes that, with the meaning the same
clause has on a WHEN rule (see rules and schedules,
§2.1 and §6.1):
| Phrase | Effect |
|---|---|
THROTTLE 1s |
Run the first click, then ignore clicks for the duration |
DEBOUNCE 300ms |
Wait for a quiet period, then run the latest click |
SERIAL LATEST |
Keep only the newest click waiting behind a run in flight |
SERIAL QUEUE n |
Keep up to n waiting clicks, in order |
SERIAL LEDGER |
Keep every waiting click |
CONCURRENT k … |
Allow up to k runs with effects in flight |
STATE refreshes = 0
STATE saves = 0
SURFACE Dashboard
# Run the first click at once, then ignore clicks for one second.
"Refresh" ->
refreshes += 1
THROTTLE 1s
# Keep only the newest click waiting behind a run in flight.
"Save draft" ->
saves += 1
SERIAL LATEST
ENDA rate phrase may also end the arrow line, as in
"Refresh" -> refreshes += 1 THROTTLE 1s. These phrases apply to arrows that
write model values; on an arrow that only changes locals or runs viewer
actions they have no effect, and the compiler warns where they are written.
Control edits pace themselves: lens writes coalesce through a short write
buffer, and a text
control writes when it loses focus (commit on :blur, the default).
commit on :change writes as the person types:
input notes = ""
SURFACE Editor
# Save each change without waiting for the field to lose focus.
notes commit on :change
ENDUndo and redo
Undo is opt-in per arrow, because the model is shared: restoring a value can
overwrite someone else's later change. Mark an arrow undoable and the open
view offers it on an Undo/Redo bar and on Ctrl/Cmd+Z, Shift+Ctrl/Cmd+Z and
Ctrl+Y. Text fields keep their own undo.
STATE decision = "pending"
STATE approvals = 0
SURFACE Review
"Approve" ->
decision = "approved"
approvals += 1
undoable
ENDThe runtime reports what the click actually committed, and undo reverses that:
- A value written only with
+=or-=moves back by its own amount, so other people's changes to it survive. - Any other value (
=,?=,*=,/=) is restored. If someone has changed it since, the Undo button says whose change it would overwrite. - Table mutations (
table T += …,^=,*=byid,-=byid) restore the rows they touched. Awhereform offers no undo for that click.
Effects such as HTTP calls cannot be taken back once they run. Either hold them so an undo can cancel them first, or say how to reverse them:
input amount = 20
STATE sent = FALSE
SURFACE Payments
"Send reminder" ->
sent = TRUE
HTTP_JSON("https://example.test/remind", amount)
undoable within 10s
"Charge" ->
HTTP_JSON("https://example.test/charge", amount)
undo -> HTTP_JSON("https://example.test/refund", amount)
ENDundoable within <duration> (up to 10min) commits the values at once and holds
the effects: an undo inside the window cancels them, and after it they run.
undo -> … declares the reversal; it runs on undo with the values of the
original click, unless held effects were cancelled instead. Redo runs the arrow
again for either form. History belongs to the open view: it holds 50 steps,
a reload clears it, and other viewers never see it.
When you are signed in and the host keeps an undo journal, the host also keeps
your plain undoable clicks (not held or undo -> ones) for 30 days. Reopen
the View, or open it in another session, and Earlier beside Undo and Redo
lists them, newest first, whenever that View's own Undo history is empty.
Choosing Undo or Redo there replays the change through the host under your
account. Only you can list or replay your entries. Ctrl/Cmd+Z stays with the
open view's own history. Hosts without durable model tables, and signed-out or
anonymous viewers, keep only the browser history above.
CHECK "description" blocks retain isolated interaction checks for tooling and
are not rendered. Syntax errors recover at outline lines so later siblings
remain available to the editor.
Code completion offers standard and imported element names, the phrases their contracts declare, and closed property choices. The compiler supplies exact replacement locations, including unfinished view lines; comments and embedded JavaScript/CSS remain outside these Grid suggestions.
Create and convert in Code
Choose New Grid View in the surface explorer or the model
Code toolbar. Name the View, then choose Add to source draft. Whole model
opens before insertion. The starter includes a page, an editable greeting, and
an isolated interaction check. Existing model source is preserved; the compiler
selects the insertion point, including inside an explicit MODEL block.
The rich Code editor records the addition as one Undo step and keeps Whole model
open with the new View's inspector. Try creating Preview, use Code Undo to
remove the addition, and Redo to restore it. Save in Code when ready to apply the
new declaration; creation pauses source autosync until that explicit Save.
The plain editor fallback can still stage a View, with its existing limited
editor commands.
For an existing App, choose Convert to Grid View in its Code surface toolbar. Apply or discard pending surface-file changes first. Review the candidate source, compiler feedback, translation notes, and document differences. The conversion keeps the original App and opens a separately named View in Whole model. In the rich Code editor, the reviewed source is one Undo step, preserving earlier history. The View's inspector appears when the compiler recognizes its declaration. If Code is still loading, Cancel conversion stops the pending insertion. A candidate with gaps can still be opened for correction. Source autosync is paused until an explicit Save. Changes to the App, model source, partition or installed dependency environment before insertion require a fresh conversion; an editor refusal never retries the insertion automatically.
With the canonical examples/canonical/grid-view-component-migration.json App,
review the component mappings, open the draft, then use Code Undo and Redo before
saving. The original App source and the generated component declarations stay
separate. The plain editor retains direct staging and its limited commands.
The source outline displays the authored hierarchy beside the preview. Selecting an item reveals its source and available Properties. Edit literal text, labels, page titles and paths, button captions, and unavailable reasons. For supported declarations, the inspector also groups bindings, visibility and availability conditions, row sources, actions, and named-region arrangements. While a text or expression property update is pending, Cancel update keeps the entered field draft and prevents the pending result from applying. You can retry immediately. Cancel does not reverse an update already applied; use Code Undo for that. Changed field contracts or withdrawn host editing support retire pending updates automatically. Component options, reusable arguments, event handlers, slots and availability requirements offer Cancel option update with the same retained-draft behavior. If an imported contract changes its fields or limits, obsolete drafts and pending requests are retired even when its contract name is unchanged. Record defaults, field additions, renames and removals offer Cancel record update. The pending source edit is canceled while the entered default, field name and new-field draft remain available for retry. Equivalent analysis refreshes keep these drafts; a changed record declaration contract clears obsolete edits.
Update source applies the selected property as one editor undo step; normal Code saving applies the declaration to the model. Selecting an element in the accepted preview opens its matching outline item and available fields. Selecting a generated record-form field also selects its matching default in Record fields, including nested paths. This works in Inspect copy as well. If a nested field comes from a computed or referenced default, selecting it opens the containing default expression supplied by the compiler. Properties identifies the preview field and explains that editing the expression changes the whole nested default. The inspector requires one matching compiler-owned record and expression; computed root schemas or ambiguous owners retain their existing source guidance. The nested defaults example shows an address selected from two named records. These controls edit source defaults, not the field's current runtime value.
Explicit standard elements and native components declared with ELEMENT ... FROM
also expose an options panel, as do embedded views such as Calendar, Map and
Kanban. Existing properties
appear first; Add property lists the remaining contract-supported properties.
Numbers use bounded numeric fields, closed choices use dropdowns, and boolean
options use Yes/No. Choose Expression or binding for a dynamic value;
input/read binding slots use that mode directly. Text values are quoted by the
compiler, so quotes and line breaks do not need manual escaping.
Automatic standard controls show the chosen type in the panel heading. Bare
placements such as qty AS "How many?" offer shared options such as help, size,
spacing and width while keeping automatic type selection. For a direct binding,
Use Slider (or the current component type) writes an explicit component use
and exposes its full options. This is a deliberate choice to pin the component
type, with one undo step; later model type changes no longer choose a different
component for that placement. The binding, label, guard, comments and model-owned
validation stay intact. show and edit placements offer this explicit-type
action where supported. In examples/canonical/grid-view-order.grid, select
qty AS "How many?", choose Use Slider, then edit its step or other options.
Custom PRESENT controls use the same panel. Shared options remain automatic;
Use [component] pins a caller-visible local or imported component and opens
its argument or native property controls. The compiler retains the effective
label, binding, guards, body and required model access. This is a source change,
so the preview refreshes and component-local draft state may restart. A component
private to its library offers shared options with an explanation; the inspector
does not expose a private name as a caller invocation.
Automatic tables and maps also expose shared options. Use Table preserves
columns, filters, paging and matrix headers, then opens the table settings.
Columns whose names could be mistaken for settings, such as empty or rows,
gain an explicit show prefix in the same Undo step. Use Map opens the
embedded map configuration, including basemap and zoom, while retaining its data
binding, label and visibility condition. Direct bindings offer these actions;
literal and computed placements retain their existing admission semantics.
Generated record forms expose shared options while keeping their generated
fields and Save action. For an edit Profile placement, Edit form options
uses the equivalent automatic Profile spelling before opening those controls.
Record fields lists the source defaults of an input or local object literal,
including nested fields. Select a field to open its exact source or change its
default expression. The compiler checks the edit, preserves the other fields
and validation, and applies one Code Undo step. Plain aliases lead to their input;
component-local defaults belong to the shared definition, while each instance
keeps its own state. These source edits do not write the current live record.
Local defaults must be literal values. Sensitive defaults stay in Code, and
computed or indirect schemas link back to the owning declaration.
Use Add field to choose a parent record, name and default. An object default,
such as {city: ""}, creates a nested record. Empty records can receive their
first field here. Select a field or nested record to rename or remove it; removing
a nested record removes its children together. These operations update the owning
declaration and generated form in one Code Undo step, retaining surrounding
comments and the other field values. Parenthesized default expressions retain
their source spelling. Renaming shows compiler-proven field uses and updates their source together,
including nested paths, plain aliases, private locals and model field-validation
targets and operands. Dynamic keys, shared expressions with different record
owners, embedded code, whole-record writes and CHECK record fixtures or
comparisons can leave unresolved uses. CHECK resets do not carry field keys;
unrelated fixture records retain their source. Their
source links explain the refusal; no partial rename is applied. A user-defined
function with a built-in member-access name keeps its own semantics: its literal
arguments are not proven field keys. Review linked opaque uses in Code before
renaming the record field. This changes
the source schema; already stored record values still require migration.
Private records inside pages, component instances, repeated bodies and Tree row
slots keep their declaration owner. Imported source component records are
editable in the receiving source; imported library declarations stay with their
library. Choose the private record under Validation owner when a repeated
control also exposes a collection owner. Row and scoped-slot parameters retain
their separate scope, even when they share the outer record's name. Shared
record-contract keys require review with their uses; the refusal links to the
local contract declaration. A computed default value is not an input-record
schema editor: follow its definition in Code.
Removing a field does not rewrite remaining references; review them in Code.
Model-input field rules can be reused through named record contracts, including nested address rules shared by several owners.
Whole-record VALIDATE and individual model-input field rules can coexist:
input Order = {amount: 2, city: "London"} VALIDATE <> BLANK
VALIDATE FIELD Order.amount > 0 MESSAGE "Enter a positive amount"
VALIDATE FIELD Order.city <> "" MESSAGE "Enter a city"The generated Form saves the record as one input. Native validation checks the whole record and its field rules against that proposed write; the fields do not become independent inputs. Field failures appear at their exact paths, with a summary for errors that have no matching control. Whole-record failures stay at Form level. Save checks the contract again even if no Check values action was run, and a refused save retains the draft for correction.
When model field rules are present, a rename includes their proven targets and
operand references. Removal still requires reviewing the linked declarations
in Code; its conservative links include all model field rules. Additions and
default edits remain available. Private record locals can apply shared literal
rules in their own scope. Every reached private record bound, including a direct
or imported literal, admits at most 64 KiB of JSON-serialized value. Comments,
grouping and source locations do not count, and unused bounds are not checked.
The total expanded private constraints remain limited to 8 MiB per View.
VALIDATE ROWS Lines USING LineRules applies a
contract to every row of a private list of records, and a failure names the row
and field; the
private row contract example
uses it on two independent draft tables. At model scope, the same syntax checks
native input rows, including exact nested collections such as Order.lines.
Bounds read the proposed batch; a rejected write leaves the owner unchanged and
identifies the first failing row for each field clause. See the
model row example and the field-rule reference
for exact-key syntax, ownership limits and the remaining development verification.
The record-field example
combines an alias target, a live bound and a whole-record rule; its source is also
used by the native proposed-batch regression test.
The inventory is bounded to 256 leaf defaults and 256 schema members per record,
32 path levels, 64 KiB per expression, 16,384 field/container/rule-link associations and
512 KiB of field and path text across an analysis. New names accept up to 1 KiB
without quotes, backslashes or control characters. Large records or unsupported
source layouts explain the Code editing boundary. The
automatic controls example
demonstrates a record form, a table with named columns and a map.
When converting an existing App to a View in Code, the review includes a Page mapping table. It shows each original page name, its name in Code, route, starting-page status and old/new position. Choose Show source to select that page in the candidate without changing your draft. The starting page appears first because View page order selects its starting page. Invalid names and names that differ only by case receive distinct aliases; valid names are reserved before aliases are chosen. Buttons use the same names as the generated pages. Renaming and reordering stay visible in the document comparison, and duplicate routes remain an explicit migration gap.
The page-migration App document
shows a quote page with a non-Grid name, an existing Page_1, and two order pages
whose IDs differ only by case. The last page is home. Conversion produces
Page_3, Page_2, Page_1, then Orders; the page table retains their original
identities and positions. The buttons reach the new order, quote and home pages,
including an uppercase route with surrounding slashes. The original App remains
available while you review and save the candidate.
The Component mapping table reviews each App library component's original
name and library ID, its Code declaration name, and whether source was generated.
Names with spaces, View keywords, standard-control names and conflicts with other
components or the receiving model get distinct Component_N names. Existing valid
names are reserved first. Show source selects the complete declaration;
unsupported templates remain listed with their translation notes.
The component-migration App document
includes a “Résumé card,” a Card, an existing Component_1, and two names differing
only by case. Convert it as Desk in a model declaring input Price = 3 to also
see model and View-name conflicts. Review the mappings, open a declaration, then
stage the candidate in Code. Library IDs, versions and descriptions survive in
source annotations; exporting later uses the current Code declaration name.
The original App and remaining document differences stay available for review.
The selected App migration operation retains portable component-library templates
as ELEMENT declarations. The compiler also exposes a portable component bridge
for standard nodes with concrete App binding parameter types, named content slots
and default content. Exported templates pass the normal App component package validator and can
be instantiated with new bindings and body content. Local state, captured model
bindings, runtime glue and unsupported properties require
an explicit mapping; the bridge refuses them. Original template node identities remain a reported migration difference.
In Code, choose Export component, select a top-level ELEMENT and set each
parameter to Any value, Number, Text, Boolean, or Range or table. Prepare
component checks the complete current draft using the model’s installed
imports. Review the parameters and named content slots, then Download .gridui.
Import that file through the App Builder’s component library and bind its
parameters to the receiving model. A draft containing only reusable elements
also supports this flow; it does not need a SURFACE or a source save first.
Changing the draft, model, partition, imports, or parameter choices discards the
prepared file. Compiler refusals appear in the dialog and leave source intact.
The reusable amount editor
is a complete example: export Editor with amount set to Number.
To bring an App package directly into Code, choose Import component. Open
its .gridui file, select one component, and choose Name in Code. Review
component source checks it against this model’s current draft and installed
imports. Review the generated ELEMENT declaration, then choose Add to source
draft. Code opens Whole model, pauses automatic source deployment, and adds the
declaration before END MODEL (or at the end of an unwrapped model). Use it from
a View’s Add element controls. Import is one Undo step; Save applies it.
The package must be version 1, at most 1 MiB, and contain 1–256 components. Names use letters, digits or underscores, start with a letter or underscore, and have at most 80 characters. Choose a different name if a component, function, model binding or namespace already uses it. Changing source, model, partition, imports or the chosen name invalidates the review. Unsupported templates fail without inserting partial source.
Import preserves the component’s library ID, tier, version, description,
parameter labels/types/defaults and slot labels in a # @grid-component-v1
JSON comment directly beneath its ELEMENT header. Keep that comment with the
definition. It is bounded to 64 KiB; individual text fields accept up to 16 KiB.
Export component starts with those parameter types and shows the retained
version, description, labels and defaults in its review. Changing a type still
runs the compiler’s normal component checks. Renaming a parameter or slot requires
updating its metadata before export; malformed or duplicate annotations refuse
export instead of silently dropping details. Code marks these metadata problems
as warnings at the source annotation.
This metadata is for App reuse. It does not change Grid formulas, argument arity or default values: Grid callers still supply each argument. App library import assigns its own local ID; the downloaded definition retains the source-owned ID. Template node IDs are regenerated. The estimate card demonstrates version 3, an App default binding and a labeled body slot. Export it, import it into another model’s Code and prepare it again to review those details.
For an element with local state or model actions, choose Grid source component
(.gridcomponent) as the package type in Export component. Review its source
and parameter types before downloading it. The
stateful counter example
has two instances with separate drafts and an Apply action that writes the
receiving model's explicit amount argument.
Open a .gridcomponent file through Import component, review its source, and
add it to the draft. Place it in a View and Save to compile its locals and rules
in that model. App Builder also accepts source packages through Import View
component…, with binding review and separate source/App saves. Portable
.gridui templates remain available through the component library. Export and
import do not execute the component or copy model values, compiled rules, live
state, or trust approvals.
A version 1 source package contains one top-level ELEMENT, its name and
concrete parameter types, within 1 MiB. Export includes the local element and
named action helpers it uses, including helpers used by other helpers, action
callbacks passed to local elements, and custom PRESENT controls.
They appear as nested declarations inside the exported component, preserving
existing nested overrides and keeping receiving-model helpers separate. The
import review lists their names; review their source before saving. Pass model
reads and write targets as parameters. Captured bindings, model-relative
references and calls whose standalone dependencies cannot be established remain
refused. A named action is invoked from an action body; it does not become a
function for ordinary formulas. Shared Grid functions and imported dependencies
still belong in a Grid library.
The component helpers example
splits a counter across DraftField, ApplyControls and CounterFields.
Export Counter as a Grid source component with amount set to Number. Import
it as QuoteCounter or create a complete View with amount bound to the
receiving Estimate input. The review includes all three helpers. Two instances
keep independent drafts, and Apply writes only the chosen instance's value.
The named action example
exports ActionCounter with bumpDraft, writeAmount and applyAmount nested
inside it. DraftControls receives the Apply action as a callback. Export with
amount set to Number, import with an explicit receiving-model binding, and
review the named action list before saving. Bumping one draft leaves the other
unchanged; Apply sends only the chosen draft to the bound input.
Helper source and captured bindings are checked again in the receiving model. Import also checks the receiving context: if a bare caption would turn into a binding there, quote it before exporting. Embedded payload bytes stay intact; normal View trust and library requirements still apply. Changing package type or host support retires the prepared review.
To create a complete View during import, select Create a View instance, enter
its View name, and supply each parameter's model binding. For the counter,
choose CounterView and bind amount to Estimate. Review the generated element
and SURFACE together before Add to source draft. Both are one Undo step;
Save compiles the new View. Repeat with a different View name to create another
instance with separate local state and action rules. Bindings must be declared
model names or cell/range references; formulas and literal values are not binding
arguments. The compiler refuses missing/extra parameters and occupied names.
After saving, choose Insert View… in App Builder, select the saved View and
review its bindings before insertion. You can also choose Import View
component… directly in Builder: open the source package, name its View, choose
bindings and review source, then Save and insert. Source and App placement
are saved separately. Failed placement retains an App draft for recovery; Undo
removes the placement and leaves the reusable View in Code.
Place the same saved View twice to give each placement independent local edits.
Labels, help, validation messages, page headings and tab panels address their own
rendered instance. Controls retain those associations through value updates and
keyed row reordering. Both placements still share the model bindings you chose;
create separately bound Views when different model inputs are required. To try
it, save the source component example,
insert its Estimates View twice, and use the Draft fields and Bump actions in
each placement. Code selection and source navigation continue to identify the
shared declaration.
A composite declares BODY "heading" where named content belongs. Its indented
children are defaults. At the use site, SLOT "heading" supplies replacement
children; an empty SLOT "heading" suppresses the defaults. Bare BODY receives
unnamed caller children (or an explicit SLOT "body"). Defaults resolve in the
component’s scope, while supplied content resolves in the caller’s scope. Names
are case-insensitive in Grid, with the original declaration spelling retained in
App exports. Fill each slot once; put conditional content inside the slot rather
than guarding the slot header. Names accept up to 256 UTF-8 bytes without control
characters, with at most 64 BODY placements per component.
The named-slot card
demonstrates an overridden heading, unnamed body content and a default footer.
Export ReviewCard with amount set to Number. In another model, use Code’s
Import component, name it EstimateCard, review and add it, then insert
EstimateCard in a View and bind amount to an input. You can also try Undo
immediately after import to remove the declaration. App imports preserve the slot
names and default children; inserted content keeps its own bindings. Select a
composite in Code to add, update or remove its declared slot fills through
Add property, event or slot. Removing a fill restores its defaults. Bare BODY
also offers the Content editor. Completion supplies the exact SLOT headers.
Portable App export retains each slot’s Stack container settings. For example,
BODY "heading" padding 12 gap 4 class :heading keeps that layout when callers
replace the content. Use an explicit quoted name, including "body", before
container phrases. View runtime behavior still needs an explicit mapping.
An explicit composite with labels or layout settings uses a closed argument
list, for example OrderSummary("Order total", total) AS "Summary" help "Details".
Following shared property lines belong to that component; a blank line starts
caller body content. Arguments named width, size or other phrase words stay
arguments inside the parentheses. Bare argument expressions retain their
existing meaning. The reusable arguments example
includes both this spelling and a custom automatic quantity editor.
Direct model input bindings also show Input validation, owned by the named
or cell-address input declaration. Choose Open input declaration to open Whole model while
keeping the View inspector beside it. Enter the clause after VALIDATE, such as
BETWEEN 1 AND 10 MESSAGE "Choose 1–10", then add, update or remove validation.
The change shares Code's undo history and applies everywhere the input is used.
For the order example, select qty AS "How many?", open its input declaration,
and change the validation clause to BETWEEN 1 AND 20. The declaration keeps
its existing default, type and presentation settings. Automatic controls may
choose a different presentation when their input contract changes.
These controls follow direct named inputs, including qualified names and supported native main bindings, and direct A1/R1C1 cell inputs. Plain aliases also lead back to the input; the panel names the alias and the declaration it owns. Calculations, typed or lazy bindings, cycles, and aliases with their own validation do not authorize this shortcut. Cell-address controls keep the input's validation, format and preferred presentation.
Select a table, picker or one of its columns to open Table columns. The
selector lists display columns, editable fields and row actions in source order.
Add a column, change its value or heading, set a literal FORMAT, alignment,
width, remove it, or move it between neighboring editable columns or row actions.
Display columns also offer Text/Badge/Metric presentation; editable fields keep
their input control. Each change uses one checked Code transaction
and selects the resulting column. Attached comments move with their column;
filters, paging and table empty messages keep their source. Complex column
settings retain an Open column in Code action.
Add row action creates a button caption and a Grid action, such as
SelectedRequest = row.id. An optional unique action key keeps its identity
through caption changes and moves. Select an existing unkeyed action to add its
key from the same field, then choose Update row action. The compiler checks
uniqueness before applying the change in one Code Undo step. Existing explicit
keys remain in the source header; changing them deliberately stays in Code.
Use semicolons between inline steps; open nested steps from the outline to edit
their source.
The selected row action also exposes Action requirements, with paired
conditions and explanations. Updating a caption or inline body preserves nested
steps, visibility, requirements and refusal handlers. Moving or removing an
action carries its whole block, including an attached sibling ELSE. The
compiler checks row-field references and model writes before changing source.
Nonliteral captions retain their source editing explanation and navigation.
With no explicit columns, the table shows all source fields. Adding the first
column or row action starts an explicit list; removing the last returns to all
fields. Use
row.amount for an explicit row read or a computed value such as
row.amount * 2. Editable cells instead takes a plain field name and requires
an admitted keyed array/table source. It retains the heading, literal format,
alignment and width; for example edit amount FORMAT "$#,##0.00" AS "Amount".
The compiler refuses computed or unwritable row targets. Formatted fields show
native display text while resting and unrounded values in display units while
editing. An untouched blur makes no write. Enter saves and Escape cancels; failed
saves keep the draft with a retry. A concurrent value change keeps the draft and
requires Reapply my value before overwriting it. Sorting, filtering and table
paging retain scalar and structured drafts by model partition, owner, parent keys, row key and
field. Returning to a hidden row restores its draft and opening types; a changed
current value still requires explicit reapplication. Native table drafts also
keep the key's original carrier: changing
an exact numeric key to text with the same displayed digits starts a separate
draft. Changing the key's native tag also separates drafts conservatively.
This retention rule does not change the model's equality rules. Saves settle even while a
row is hidden: success clears the draft, while rejection keeps it for retry.
These drafts remain private to the open View and participate in its unsaved-change
navigation protection. Leaving the page after approval discards them; they are
not recovery storage across browser reloads. Leave the Format field empty to inherit the model's FORMAT <tag>
policies. The native formatter selects the most specific policy from each field's
actual value tag, falling back through its tag ancestors. Reactive policy changes
refresh the display without reinterpreting an active draft; an explicit column
pattern takes priority. A blank policy inherits its ancestor. Pending, invalid or
failed policies explain the problem instead of displaying an earlier policy as
current. Exact numeric strings keep their text under inherited numeric policies.
The inherited row formats example
shows a reactive number policy, currency and percent fields, editable cells and
an explicit override.
In the nested model rows example, open Details, change the JSON without saving, then choose Next and Previous on that table. The first row's draft returns. Cancel discards it; Save value submits it through the original owner and validation.
Retargeting a named row alias to a different model record or stored list also changes its draft owner. An old draft cannot appear in the replacement owner's editor, even if the table, displayed alias and row keys have the same names. Changing only the filter keeps the owner and its drafts.
Declared field tags also survive plain JSON replacements and browser-local rows. The compiler retains tags shared by every literal row. Conditional records and row sets keep only tags shared by both outcomes; IFERROR/IFNA fallbacks must agree too. Whole-row FILTER, SORT, SORTBY, TAKE, DROP and UNIQUE operations keep these hints. A FILTER fallback must have the same hints. Column transformations, disagreeing branches and unknown schemas do not acquire an invented common tag. A live refined tag takes precedence over the declaration hint. The same metadata selects generated record controls, including nested local records. Record fields inherit model formats; numeric fields keep their editing units in a staged Form draft. Dates, date-times, text and checkboxes show a formatted readout beside the original editable value. Date controls keep civil dates, and zoned date-times retain their supplied offset. An ISO-looking text field remains text. Save still commits the record once; formatting does not rewrite its values. The parent record's own format does not leak into individual fields. These scalar controls remain editable while formatting loads or retries; print uses the selected formatted text. A policy with no match retains the normal display. Exact numeric text and structured editors retain their existing presentation. Currency, percent and unit literals in local defaults use the native literal lowerer; decimal and bigint defaults preserve their exact text. The field formats example combines a reactive currency policy, a nested record Form and private local rows. The computed field formats example uses a conditional invoice default and a selected row list. Generated controls keep currency, percentage and date presentation. The Form stages raw field values and saves one checked input; changing a column format in Code overrides its inherited policy, and removing it restores inheritance. Declared date/datetime row values use the native formatter's temporal carrier, including plain JSON replacements. Ordinary date-looking text stays text; live refined tags and resident table schemas take precedence over declaration hints.
Resident model tables and table views use the current handle's column type and unit. Currency columns select their concrete currency policy, percent columns select percentage policies, physical units use the native tag hierarchy, and date/datetime columns retain date formatting. An explicit column format still wins. Schema changes refresh the display; active drafts keep their editing units. Blank numeric columns retain numeric entry and the keyed native write. Only requested pages cross the table binding boundary; format selection reads no model data. The native table example uses an existing Ledger table and explains the required schema.
Per-row local hints for mixed/computed schemas still need integration.
Plain eager aliases, including chains to named or cell-address inputs/defaults, retain keyed editing at the owning declaration. The runtime reads and rewrites that owner in one commit; the browser sends only the selected key and new value. Native table aliases keep their keyed update. Other computed transformations, typed, lazy or cyclic aliases and aliases with their own validation assertion do not gain this write access. A local that shadows a model input does not become a model row lens.
Record-list editors and pickers retain the exact spelling of their identity
field. They use id when present; otherwise a single case variant such as ID
or Id is accepted. If several variants exist without an exact id, declare
one unambiguous identity before editing. Typed keys keep their native type through
nested and structured editors. See the
row identity example.
An editable identity field can be renamed, but its replacement must be nonblank
and unique in the owning list. The native commit checks the current owner,
including rows hidden by a filter. A rejected rename preserves the whole owner;
choose another key and retry. The same rule applies to private local lists.
A model record can own the editable list: Table Order.shipping.lines updates
that stored list inside the Order input/default. Plain aliases to Order work
too. A plain eager name for the stored path also preserves ownership:
Lines = Order.shipping.lines can feed Table Lines or a reusable
LineItems(Lines) element. Chained names such as Shipping = Order.shipping
and Lines = Shipping.lines keep the same original owner and validation.
The named expression remains a calculation, not a new input. See the
stored-path alias example.
A two-argument FILTER also preserves whole-record ownership:
Lines = FILTER(Order.shipping.lines, line => line.amount > 0) can feed the same
editor. Boolean masks work too. Filters may be chained or used as the source of
a keyed FOR that contains child editors. The compiler follows at most 32
filters; three-argument fallback filters and row-shaping calculations do not
acquire editable ownership. See the
filtered rows example.
A filter controls which rows are displayed. As with FOR … IF, it does not
constrain writes: an edit can move a row out of the result, and a captured edit
can still address a row whose filter membership changed. The current owning
collection must contain exactly one matching key at every level. Missing or
duplicate keys reject the edit, including duplicates outside the filtered result.
The original owner's validation always checks the proposed value. Inputs used
only by validation bounds, including computed bounds, travel with the native
rule's event snapshot just like inputs used by the action itself.
The generated rule reads the current order and replaces only the selected row field, preserving parent fields, neighboring rows and native field values. Scalar and record/list editors, sorting and acknowledgements use the same controls. The shipping lines example shows both kinds of edit.
Code's column Properties edits these declarations, and Row source validation opens the containing input's whole-value rule. It keeps navigation to the displayed row expression separate from navigation to the input declaration. Repeated readouts over the same stored path inherit that contract context. It is not a contract on each row or field. Dynamic field keys, dotted INDEX fallback keys, calculated collection aliases remain outside this write contract.
A table can also edit a list inside an enclosing model row:
SURFACE Desk
FOR order IN Orders key order.id
Table order.lines
edit amount
ENDEach enclosing repeater needs an explicit stored-field key, such as order.id
or batch.code. Nested rows and reusable elements retain those keys; the final
table rows need id. The initial records must establish common fields across
all parents. Up to 32 stored-field/key steps are admitted. The
nested orders example
includes two enclosing lists and scalar/record editors in a shared element.
Edits read the current owning input and retain neighboring rows and parent fields.
Missing or duplicate keys reject the whole edit with an explanation. Drafts stay
with their keyed parents through reordering; changed fields require explicit
reapplication. Nested edits use the updated native host's guarded View action.
Code's shared table definition offers the same column controls and containing
input validation; those changes share Code Undo.
A declared local array can also use edit amount. The compiler keeps this edit
private to the open View, including Table draft.lines inside a local record and
lists owned by repeated elements. The initial records establish the editable
fields and stable id key. Numeric and text keys remain distinct. Edits find the
current row by its key, preserve other fields and records, and check the whole
local declaration's validation before saving. Removed rows, missing/duplicate
keys and conflicting values keep the draft with an explanation. A changed field
requires Reapply my value; it cannot silently overwrite a newer value.
Private table columns also support record and list fields. Choose Edit record
or Edit list, change the JSON value, then Save value (or Command/Ctrl+Enter).
Cancel or Escape discards the draft; leaving the field does not save it.
Use null to clear a field. Initial array/record hints retain the appropriate
editor for blank fields. Save replaces only that field at its current row key;
newer neighboring fields and other rows survive. A conflict retains your draft,
shows the current value and requires Reapply my value. Failed saves retain
the draft for another attempt. An unchanged save writes nothing.
The editor accepts plain JSON up to 64 KiB, 4,096 values and 32 nested levels.
Keep integers beyond 9,007,199,254,740,991 as text. Nested lists retain their shape,
including lists of pairs; records with kind or value fields remain records.
This edits literal data, not formulas. Whole-local validation still applies;
individual field contracts remain incomplete.
The catalog draft example
shows record/list edits, blank repair and reset. Column Properties uses the same
checked edit source declaration and Code Undo as scalar columns.
Model-backed input/default record lists support the same editor for stored
record/list columns, including through plain aliases. Existing native field and
list-position types are retained: currencies keep their units, dates and exact
numbers keep their text, and matrices keep their axes. Retained value types
shows up to 32 tagged paths. Native type changes also cause a conflict, even
when the displayed JSON is unchanged. Reapplying uses the types from when the
draft was opened. Use Code to change an existing value's type. New fields and
list positions become literal Grid values; null clears a value.
A save sends the typed row key and field value through the existing generated model action and waits for its outcome. It does not replace the displayed row. Scalar columns use the same native transport for root lists, stored paths, aliases and filtered rows. Editing an amount keeps its currency or unit; exact numbers and dates retain their native text. A type change during an edit keeps the draft and requires Reapply my value, using the draft's opening type. Blank or error scalar fields can be repaired using the declared column type. The inherited row formats example includes a currency column whose edits retain USD. The model catalog example shows a currency inside a record and a list of pairs. The editor bounds include the native representation as well as the visible JSON. Native record carriers are required for rows; opaque JSON rows, resident table blobs and unsupported native field kinds retain an explanation and source editing. Full native-host journey validation remains pending.
Sorting, paging, native formats, print and the column Properties editor use the
same controls as model tables. Reset restores the local's default, and closing
the View discards its private changes. The
private estimates example
combines a nested local picker with independent repeated estimate lists.
Nested tables can follow keyed enclosing rows back to the same private local.
For example, FOR order IN orders key order.id can contain a
Table order.lines with edit amount. Further keyed levels and stored record
paths work too, including rows passed to reusable elements. Each enclosing list
needs an explicit stored-field key. The initial records must establish the
editable column across all nonempty instances. The
private orders example
edits line items through order and batch keys; reordering either level preserves
ownership. A removed or duplicate ancestor refuses the edit. Literal dotted
field names that conflict with a nested path must be renamed before editing. Whole-local
validation and reset still belong to the original local declaration.
For native model repeats that own nested edits, row-local state and child table
drafts retain the original key carrier. Changing a parent's exact numeric key
to text with identical displayed digits creates a separate instance. Returning
the original native key restores its own state. This also applies to explicit
stored keys such as a batch's code; read-only repeats still allow unkeyed rows.
Computed JavaScript collections, bare literals, native slot values and nested model collections still have no admitted private local write owner. Column Properties edits a caller-owned nested table; shared element definitions remain authored in Code.
Tables, pickers, columns, row actions and controls inside a repeater also expose
Row source validation. This is the contract on the whole surrounding collection,
not a separate rule for each row or field. Open row source in Code selects the
authored collection expression; Open input declaration or Open local
declaration selects the declaration that owns its contract. Plain aliases lead
to their admitted owner, and a View local takes precedence over a same-named model
input. Nested tables and repeaters use their nearest row source. A private
collection such as draft.orders, or order.lines inside a repeat, also links
to its containing local declaration. Properties explains that the rule validates
the entire local, including its other fields and lists. This works for read-only
collections too; it does not require an inline row-edit lens. Repeated body
locals retain their exact declaration even when an outer local has the same name.
Try the nested validation example:
select Amount, open its row expression or the draft declaration, then
change the whole-draft rule through Code Undo.
Pickers over compiler-proven native record lists keep exact numeric keys, dates
and semantic tags when writing a model selection. Multiple selections retain
each key through range selection, removal and Form submission. The selected
field is separate from the row identity used by editable cells: selecting
name does not change an edit's owning id. See the
exact asset keys example.
Private browser-local pickers keep their literal-value behavior.
Newly compiled native model pickers use the model's equality rules for selected rows, removal and range selection. A bigint and an equal ordinary integer match; text with the same digits stays separate. Editable rows also check duplicates using native equality. Row retention and range anchors keep the owning row ID separate from the selected field, so repeated selected values do not move an anchor to an earlier row. See the reading equality example. Read-only native rows without a separate row ID retain the clicked row within the current source snapshot. Repeated selected values do not move the range anchor; replacing that snapshot retires the anchor. See the unowned readings example.
If another control in the same Form stages a plain value for an admitted model picker, the model previews its converted selection and equality evidence together. The proposal stays staged until Submit. Replacing or discarding the draft retires that preview. A host without native preview support refuses the interaction rather than guessing how the value should compare.
If native key evidence is missing or still loading, the View refuses the edit or selection until that evidence is available. Key evidence and scoped parent publications have a separate bounded admission budget; a large collection does not consume a single field's edit allowance. Individual structured edits keep their existing size and depth limits.
If another control in the same Form stages a plain value for an admitted model picker, the model previews the converted selection and equality evidence together. The proposal stays staged until Submit. Replacement or discard retires the preview; a host without native preview support refuses interaction. Proven stored collections inside keyed repeats keep their parents through reordering, with refusal for missing, duplicate or changed parent keys. See the nested reading example. Tree row slots retain this preview path through their native occurrence keys, including Trees inside keyed repeats. See the Tree reading example. Sibling pickers reading the same projection share their pending model preview. Leaving one picker does not cancel the others; stale model responses refuse interaction and are never reused as a later preview.
A picker retains separate owners for its selected value and its rows. A repeated
control bound to another input also keeps that input's own contract. Choose
Validation owner to switch between them. Updating or removing the row-source
clause changes only the owning declaration, through one Code Undo step. In
examples/canonical/grid-view-columns.grid, select edit amount or the repeated
Metric to find Requests; select the picker itself to also inspect
SelectedRequest. The picker's RequestRows alias keeps edits and validation
at Requests; the source navigation button selects the alias expression. The
example's <> BLANK requires a supplied value; it does not require a nonempty array.
Computed collections and native table expressions without a direct editable
input/local owner offer source navigation and an explanation. The inspector does
not guess an upstream input or treat a native table handle's declaration contract
as validation of stored rows. If the authoring inventory limit is reached, source
navigation remains available and additional row-source edits are withheld.
Computed JavaScript collections and foreign or ambiguous origins do not borrow
a containing declaration's contract. Admitted stored column names remain row
fields in Code even when the schema is known only at runtime.
Individual table-row contracts remain outstanding; this row-source editor changes
the whole input or local declaration's VALIDATE clause. Model-input record
members may separately use VALIDATE FIELD.
That syntax does not grant validation authority over array elements, resident
table rows or private-local fields.
Local collections can also supply tables and repeated controls. For example,
FOR request IN requests key request.id reads a stored ID from a View-local
array without creating a model calculation. Nested collections such as
FOR line IN request.lines key line.id read the enclosing row. Dotted local
collections, literal arrays and empty arrays use the same renderer and paging;
empty lists display their ELSE sentence. Stable keys retain a row's Form drafts
when the collection is reordered.
A sibling ELSE block immediately after a repeated button handles a refused
action for that row. It retains the row's values and outcome.message; it does
not run the successful follow-up actions. ELSE "No requests." remains the
list's empty-state sentence. The same sibling handler form works for named
native-control events, and editing or removing an event in Properties includes
its whole refusal block while leaving adjacent events and body content intact.
Try the request refusal example:
choosing Northwind leaves the selection unchanged and reports its review
requirement; choosing Contoso succeeds. All state stays in the open View.
Try the private shortlist example.
Choose a customer, clear the list, and restore its default. In Properties, change
Row identity to another stored field through a checked source edit and Undo.
These actions affect the open View's locals. They do not write a model array.
Locals declared in a repeated component or directly inside a FOR body belong
to that element path and row key. Reordering, paging away or temporarily removing
a row keeps its draft; restoring the same key restores that state. A different
key starts from the declared default. Closing and reopening the View resets all
locals. Inputs, actions, reset and validation use the same row-owned value;
View-wide locals remain shared within the open View.
Try the private row drafts example.
Increase Ada's quantity, hide the requests, then restore them. Only Ada retains
the changed quantity. Select RequestDraft request.customer in Properties to
edit the shared validation declaration with Code Undo. A local declared directly
inside the repeater likewise edits its own declaration, even if an outer local
has the same name. Private-copy setup lists View-wide values; edit individual row
drafts in the preview and their shared defaults or validation in Properties.
Stateful repetitions require a key, including ancestors of nested stateful
repetitions. Keys may be text, finite numbers or booleans; numeric 1 and text
"1" are distinct. Missing or duplicate keys show an error at the repeater,
without combining drafts. Native handles rely on the host's stable row identity;
array keys are checked across the complete collection, including other pages.
Browser rows support stored fields and direct field keys. Grid calculations,
computed keys and FOR … IF over browser-local data are refused with
GRID_VIEW_NOT_STAGEABLE. Use an explicit JavaScript row source to filter or
compute its fields first, or use a model collection for Grid expressions. Inline
JavaScript and named SCRIPT exports can provide an array or record immediately;
USING retains explicit model reads. Use row.amount when a SCRIPT source has
no inferred schema and amount also names a model value. A Promise or invalid scalar result reports
an error at the row source. Ordinary model row calculations keep the native
path. This does not add local-array inline editing.
Width accepts a literal such as 140 or "12rem". Literal column FORMAT
patterns use Grid's native TEXT formatter in previews and published Views,
including decimal places, grouping, percentages, negative sections, scientific
notation and dates. Sorting, selection and inline edits keep the original values.
An unavailable host or failed batch shows a concrete explanation and unformatted
values; Retry formatting retries the display operation. Stale responses cannot
replace a newer page. Print/PDF waits for the displayed formats and asks you to
resolve failures before capture. Patterns support up to 256 UTF-8 bytes and
scalar text up to 16 KiB; larger values need a shorter display expression.
See the formatted statement example.
Text, Metric, Stat and Badge values also honor declared FORMAT patterns and
host-resolved tag or cell format policies through the native formatter. An
explicit format choice on the control wins; otherwise the current host metadata
wins over the declaration hint. View-local patterns work in private copies too.
For example, Balance = -25 FORMAT "0.00;(0.00)" displays (25.00) in an
inferred Metric. Dates retain their native scalar kind, and declared unit labels
remain visible.
Scalar displays share bounded requests, show a fallback with recovery guidance when formatting is unavailable, and participate in print/PDF and HTML readiness. Changing a value, pattern, host or preview cancels its old display result. Scalar controls with no format pattern create no formatting request. Full native-host acceptance and row-field policy propagation remain separate work.
Numeric inputs also use the native pattern and the same precedence. At rest, they show the formatted value; focusing exposes the unrounded number in display units. Leaving without a change never writes a rounded value. Native section selection supplies decimal precision, percent/thousands scaling and locale separators, including quoted literals that look like formatting instructions. Sliders project bounds and steps into those units and write model units. An active draft retains its editing units if the pattern or host changes.
Plain decimal or scientific input is accepted in the active format's units.
Numeric controls and editable table cells also accept the native pattern's
prefixes and suffixes across sections, including $12.50, ($12.50), localized
percentages and scaled values such as 1.25K. Fraction patterns accept 1 1/2,
3/2, negative fractions and their unit labels. A fixed display denominator does
not restrict entry: entering 3/8 under a sixteenths pattern stores 0.375.
Focusing still exposes the unrounded decimal, and untouched blur never writes.
Try the workshop quote:
enter 2 3/16 as the cut length and paste ($15.75) into a positive charge.
The private line table uses the same entry behavior. In Properties, change the
Length column's format to # ?/?, then Undo the source change.
Invalid grouping, zero denominators, unsafe fraction integers and ambiguous signs or scales retain the draft with an explanation. For example, a pattern that uses the same decoration for both signs cannot recover the sign from pasted text; enter a signed decimal instead. Form Save stays disabled until an invalid field is repaired. New host or format responses cannot reinterpret an active draft. Plain decimals always use that draft's current display units.
Exact decimal/bigint strings retain their separate text path. Date/time formats, literal-only sections and text embedded between numeric digits do not gain a numeric inverse; their existing controls remain in effect. Older hosts retain their current-section decimal entry. If the host cannot provide a valid editing plan, the control explains the failure and offers basic editing plus Retry formatting. Pending plans block new numeric edits; retries and late results cannot replace an active draft.
The editor supports up to 256 columns within the shared analysis budget; larger lists remain in Code. Source changes, selection changes or unavailable host tools cancel pending edits. Inline region tables expand in the same Undo transaction when needed.
Open examples/canonical/grid-view-columns.grid, select its Requests picker,
change the Customer heading to Account, move the display columns, and undo.
The edit amount column retains its keyed model write and the Choose action
continues to select row.id.
A local declared directly inside a View shows Local state validation when
you select its declaration or a directly bound control. Open local declaration
locates its source. Each open View keeps its own state; editing this declaration
does not change a same-named model input. Local validation accepts literal values,
not model references. Defaults, sensitive, remembered, comments, FORMAT and
PRESENT remain intact, and edits share Code's undo history.
Select a reusable element to inspect the input or View-local contracts passed as
plain arguments, along with locals in its definition and the helper components
it uses. This includes nested definitions, transitive helpers, native slots and
caller content consumed through BODY. Unused definitions and unconsumed caller
content do not contribute local contracts. Choose a
Validation owner when more than one declaration is involved. Component-local
edits change the shared definition for every instance; each instance still keeps
its own state. The panel opens that exact declaration before editing it outside
the current source section. Imported component locals remain in their library's
source, while caller-owned input arguments can still be edited here.
Nested definition paths distinguish same-named helpers in the owner selector.
Only compiler-admitted instances contribute owners; repeated instances of one
definition share one entry. If the bounded inventory cannot include all nested
owners, the inspector explains the limit and directs you to Code.
Open examples/canonical/grid-view-validation.grid to compare the shared
Orders!B2 input, the private trialQuantity local and two QuantityPreview
instances. Select the alias display to find the shared input. Select either
QuantityPreview, choose draftQuantity (QuantityPreview local), change its
validation to BETWEEN 1 AND 20, and undo the source change. Both instances use
the definition's contract, while their drafts and the View's trial remain separate.
Choose trialStep (QuantityPreview › DraftControls local) to edit the nested
helper's contract or open its exact declaration.
Row lenses, complex indirect bindings and computed-cell assertions still require Code. A row edit updates its array/table field; a validation on the whole input is not a field-level rule. Ambiguous owners and unresolved or unsupported contract references cannot be applied through the panel.
For native components, the panel reads the admitted contract, including imported
aliases such as Kit.Chooser and local phrasebook extensions. Fixed presets have
an Add property or Remove property action without a value field. A preset
such as spacing(size) presents its named arguments in order; enter 4 to apply
spacing (4). Two-way properties request an input or local binding, and conditional
properties explain the setting they require. Contract choices, argument counts,
variant conditions and writable bindings are checked by the same compiler as Code.
The inspector edits the caller's use site; the locked library stays intact.
Embedded view options follow the renderer contract. Column fields accept names
such as Order title and quote them safely. Closed choices, such as Calendar's
view and Map's basemap, use dropdowns. Fixed configuration values do not offer
a dynamic expression switch; binding fields still accept model expressions.
Array settings use constant Grid expressions—for example [45, -73] for Map's
center. The compiler checks their types and refuses changing data in a fixed
configuration field. Other settings stay in source.
Native components and embedded views also expose declared events in Add
property or event. Select an event to edit its handler source; the field lists
the values supplied by that event. For example, Kanban's on select handler can
use card in on select -> selected = card.id. Keep the event name and edit
its actions, key, guard or nested refusal actions, then choose Add event or
Update event. Remove event removes that handler and its refusal actions.
The compiler checks the event's scope and preserves neighboring settings, slots
and events. Event changes share Code's undo history. This is a handler source
field; it does not invoke the event.
Below component options, open a named event's requirements section to add,
update or remove its individual conditions and explanations. The same controls
appear when selecting the handler in the outline. Each event lists its own
supplied values and keeps separate drafts. Open handler in Code reveals the
exact declaration. Removing a requirement retains its nested action steps and
refusal handlers; it does not remove the event. These controls cover native
components and embedded views such as Kanban. Component header Availability
remains separate. Try the ticket selection example:
select the Kanban, open on select requirements, and edit the selection check
or the card.id check. Each change shares Code's Undo history.
Declared native slots appear in Add property, event or slot. Select a slot
to edit its content source, including its header and indented child elements.
The field lists any values supplied by a scoped slot: for row(entry) holds renderRow, the row content can use Text entry.label. Choose Add slot,
Update slot or Remove slot to apply one checked, undoable edit. The slot
name stays fixed, and neighboring settings, events and other slots are preserved.
An explicit body block uses these controls too. Components declaring
body holds children also offer Content for child elements written without
a slot header. Use Add content, Update content or Remove content;
the compiler checks names in the caller's scope. If content is interleaved with
properties or other slots, the field shows those content blocks in order.
Updating gathers them at the first content location and preserves intervening
settings, events and named slots. Attached comments and multiline payloads travel
with their content. The operation is one undo step. A named body or alias prevents
adding unnamed content over it, and existing unnamed content prevents adding a
conflicting named body. If both forms are already present, resolve them in Code.
Direct native arrows such as Kit.Button label "Save" key :save -> save()
also expose their header options. Additions go before key and ->; property
updates preserve the action, guard, key and refusal handlers. On activation
edits the inline actions and lists the event and supplied values selected by
the component contract (onPress, with onClick as its fallback).
A separate Action requirements section adds, updates and removes body-level
requirements. Component Availability stays in the options panel. For example,
with Ready, HasChanges and save() declared in the caller's model:
# Keep the control visible with an explanation while preparation is incomplete.
Kit.Button label "Save" needs Ready ELSE "Prepare first." KEY :save -> save()
needs HasChanges ELSE "There are no changes to save."The header condition controls availability; the indented requirement is checked before the action runs. Each is edited in its own section. Action requirements can use values supplied by the selected activation event; those event values are not available to component header options. The panel lists the event values. Removing a requirement retains its nested action steps, other checks and refusal handlers. Other nested action steps remain in Code. Named handlers offer their own requirement controls in an ordinary component block; their complete action source remains editable through the handler field.
Selecting a caller-owned use of a reusable Grid element exposes an arguments and options
panel with the parameter names from its definition. Choose Text, Number,
Yes/No or Expression or binding, then Update argument. Text is quoted
safely; bindings and named actions are checked in the caller's scope. Arguments
are required, so the panel updates their values rather than adding or removing
positions. Commas, call parentheses, guards, comments and caller content
are preserved. Imported aliases use the same controls without changing the
library. If an imported parameter changes identity, refresh analysis before editing.
The canonical examples/canonical/grid-view-arguments.grid example uses
OrderSummary("Order total", total): select that instance to edit heading as
Text or amount as an expression or binding.
The Reusable component section identifies the shared definition behind the
selected instance. Open component definition selects its declaration in Code;
when it lives outside the current View, Whole model opens with the View inspector
retained. A native component labels this section Component contract. Shared
body or contract changes affect its instances; argument and option controls edit
only the selected placement. In the canonical
examples/canonical/grid-view-component-helpers.grid example, select either
Counter(Estimate) and open the same ELEMENT Counter(amount) definition.
For an imported definition, the section names the owning library, including a private helper selected by PRESENT. Open import in Code selects the model's USE statement; Review component dependency opens the available dependency tools. Edit the shared definition in its owning library. These links require current analysis and preserve the caller's source when opening an import.
The same panel offers shared presentation options, including help, width, spacing
and Availability. They apply to this placement without changing the element's
definition. Argument names remain separate even when a parameter is named width
or size. The canonical example includes width and availability controls.
Adding the first shared option encloses bare arguments in a call when needed. Caller content that resembles a property gains a blank separator to keep its meaning. Removing the last header option may move a remaining property from a continuation onto the header. Arguments, guards, comments and content remain intact; these source adjustments and the requested edit share one Undo step.
Add property, Update property and Remove property each produce one
checked, undoable source edit. The compiler preserves neighboring phrases,
comments, guards and child content, and refuses unknown choices, invalid literal
ranges or unresolved expressions. Removing a property restores its declaration's
normal default behavior. Native components expose their positional subject as
Main binding or Main value, including add, update and remove controls.
Two-way bindings require a writable input or local value; value contracts use
their declared type. A named alias supplying the same property prevents adding
a conflicting subject. Conflicting aliases, duplicate phrases or nested
declarations stay in their existing source editors. Contract always settings
are fixed by the component.
Availability edits a supported control's needs condition and explanation
together. Enter a Grid expression in Available when and plain text in
Unavailable explanation, then add or update the pair with one Undo step.
The control remains visible when the condition is false and shows the explanation.
This includes standard, automatic, embedded, admitted native and reusable Grid
controls, with reusable arguments and shared options in the same panel.
Ordinary arrows and direct native arrows offer Action requirements. Each requirement has its own pair of fields, including requirements nested around other actions. Updates preserve neighboring requirements, action keys and refusal handlers. Use Add another requirement to append a condition and explanation after the existing checks. Remove requirement removes that check while keeping its nested actions and other requirements. Nested actions move up one indentation level; comments, multiline values and refusal handlers remain intact. Each change uses one Undo step. Cancel closes an unfinished addition. Adding a requirement to an inline region action expands its content and edits it in the same Undo step. See the availability example. Availability expresses readiness; it does not grant authorization to run an action. The runtime checks a requirement over model values again when the action runs. If it no longer holds, the run is refused with the explanation and nothing changes. This covers a value that changed after the page rendered, and an action that arrived from an agent or another client. A requirement that reads a browser-local value is checked in the browser only.
To share availability across controls, select an element or container and choose Wrap in availability. Enter Available when and choose When unavailable: Show an explanation keeps the block visible with disabled controls and an explanation; Go to a page or View hides it and redirects when the condition fails. Enter the destination's declared name and any arguments. The compiler checks that destination. Create availability group wraps the selected block, its comments and attached refusal actions. Add or move more controls into the resulting group through the outline.
Select an existing needs block to change its condition, explanation or redirect
with Update group. Remove wrapper, keep contents removes the availability
or redirect and lifts its contents into the surrounding block. Arrange →
Remove group and contents deletes the complete block instead. A separate
visibility condition is retained when removing only the availability wrapper. Comments, multiline payloads,
keys and refusal handlers remain intact. Inline regions expand automatically in
the same Undo step. The canonical availability example groups its shipping and
payment controls; select that group to try updating or removing its wrapper.
Wrapping or lifting a supported block that contains local, reusable or named-action declarations opens Review this group change. It shows the affected declarations and the exact before/after source. Apply reviewed group change applies that checked candidate in one Undo step; Cancel group change keeps the current source. A lifted declaration can become visible in the surrounding block, and a wrapped declaration can lose that visibility. The compiler refuses duplicate names, undefined references and invalid destinations. This does not move or migrate stored values. Condition-only updates keep the declaration scope and apply directly. Draft, selection or capability changes discard a pending review; Apply rechecks the current editor. Keep shared declarations at View level when only the placed controls should share availability. Reusable definition bodies, page-containing blocks and other unsupported source scopes stay in Code. Table row actions use their own requirement controls; a generic availability wrapper is not a table column. A control's presentation options do not replace its input contract; use Input validation for supported owners. A bare binding that shares a contract phrase's name can be parenthesized in Code to make the boundary explicit before editing it in the panel.
For complete source blocks, Arrange offers Move up, Move down, Duplicate, and Remove where supported. These actions include the element's contents and use one editor undo step. Consecutive leading comments at the same indentation travel with their element; a blank line separates unattached comments. Internal and trailing inline comments and original line endings are preserved. Use Move to to place an existing element inside another section, form or page, or after another element. The destination list excludes the selected subtree. Structural indentation changes; multiline literals and embedded JavaScript/CSS keep their exact contents. The compiler checks the resulting hierarchy and bindings before returning the undoable source change.
Moves across repeater, page or declaration scopes open Review this move. Review the current and destination scopes, declarations carried with the block, and the exact before/after source. Apply reviewed move applies that checked candidate in one Undo step; Cancel move leaves the draft unchanged. Names resolve at the destination: a valid same-spelling name may denote a different row or declaration there. Repetition, page action identities and component-local state may change with placement. Unknown references and invalid source still prevent the move. Changing the draft, selection, destination inventory or host support discards the review; Apply also checks the current model, partition, editor, version and edit permission. Moves within unchanged scopes keep the direct operation. Older hosts keep scope-changing moves in Code.
Try the request desk move example: move the repeated caption into Summary's Card, then Undo. A row-dependent field cannot follow it unless its row binding is valid at the destination. Pages use their own page controls; moving a block containing page definitions is refused. End of View appends after the last page in a paged View.
Pages can be reordered and removed with both nested content and flat placements
following their headers. A page's own locals, reusable elements and named
actions move and are removed with it. Declarations that belong to the whole
View stay in the source, including scripts, styles, policies, themes, checks
and next steps written between pages. Their source and relative order are
preserved. Page order determines the starting page; removing the last named page
leaves the View's shared content. A page still referenced by a link or check cannot
be removed until those references are changed in Code.
Select a page and change Page name in Properties, then choose Rename page.
If needed, Open Whole model to rename page keeps that page selected while
opening all source. The compiler updates local and qualified go destinations,
NEXT links, route guards, and CHECK references to the page name. Existing titles,
URLs and route parameters stay the same; inherited titles and paths become
explicit AS and AT phrases. All replacements share one Code Undo step.
A conflicting page name/title or a name that would redirect an existing link is
refused. Shared definitions with unqualified destinations need a View qualifier
first, such as go Orders!Review(selectedOrder); a foreign component whose
navigation cannot be preserved needs a change in its defining library. Script
text and external links are not rewritten. Save applies the renamed source.
Select a page and open Duplicate page to copy it with a new name and path. The suggested values are unused; overlapping routes, including parameterized routes, are refused. Both nested content and flat placements following the page header are copied after the original, along with attached comments and exact multiline payloads. The copy becomes the selected outline item and uses one Code undo step. Model bindings and navigation destinations remain unchanged; the copy gets its own page and action identities. Route parameters keep their bindings and order. Explicitly authored titles are preserved; otherwise the title derives from the new name.
In examples/canonical/grid-view-pages.grid, select PAGE Details(...) and
choose Duplicate page. The suggested DetailsCopy at orders/:id/copy
shares selectedOrder and submitted with the original, and its Review button
still goes to the Review page. To create independent model data, change those
bindings explicitly after copying. The example declares reviewNote before the
first page, so it remains a single shared local. Its flat Text placement belongs
to Details and is copied with that page. A page's own locals, elements and named
actions are copied with it; declarations that belong to the whole View,
including their attached comments and exact embedded payloads, are never
duplicated or deleted by page controls.
To try a rename in the same example, select PAGE Review(...), open Whole model,
and rename it to Approval. Its Review button now goes to Approval(selectedOrder);
the displayed title remains Review order and its URL remains review/:id.
Use Undo to restore the page name and every changed reference together.
Inline array members, branch alternatives, reusable definitions and embedded modules also remain in Code. Named layout regions have their own coordinated editor described above.
Use Find in View outline to locate existing elements by their name, kind or parent path, including inside collapsed sections. Selecting a result expands its ancestors and opens source and Properties. Results remain readable while analysis is stale, but source navigation waits for current analysis. The list shows up to 50 matches; narrow your search to find a particular element.
Use Add element or page to choose a placement: At View root, After selected element, or Inside selected element for supported containers. The compiler's standard control catalog supplies available elements and binding fields. Add labels, sections, displayed values, editable inputs, action buttons and pages. Input controls use an existing editable model input or local value. For a page, choose a name, title and optional path; the suggested name is unused, and conflicting names or paths are refused. New pages are appended using At View root, so existing placements keep their page. For other elements, the root choice appends to the source; in a paged View this follows the last page. Select a page and Inside selected element to choose its contents. Add element or Add page inserts a checked source draft with editor Undo. Use Find element to search names and descriptions, including imported components. Results respect the selected placement. Clearing search keeps the selected component and its entered fields; choosing another component starts with that component's defaults. Pressing Enter in search does not insert anything. If refreshed component contracts or available placements change, the panel clears obsolete fields and cancels pending insertion. Equivalent analysis refreshes preserve the fields you are editing. Local and imported components also appear, including definitions that have not yet been used. The choices follow the selected View's imports, aliases and local phrasebooks. Library-private helpers and another View's definitions stay out of that View's palette. Reusable elements list their required arguments in order; enter Grid expressions or bindings, and put text in double quotes. Native components expose their declared main value, writable binding or preset arguments. Remaining native properties, events and slots can be added through Properties after insertion.
The compiler checks the new use in its chosen scope, including argument counts, writable targets and native library locks. A missing library, unresolved name or invalid component body leaves the draft unchanged and shows the reason. Adding an element does not install its library. Edits stay in the calling View, preserving library definitions, existing comments and editor Undo. The form supports up to 256 fields totaling 64 KiB; larger argument lists remain in Code. Creating new model inputs also remains in Code. The panel appears only for an editable current draft. See the reusable element example.
Preview at Panel width, Phone, Tablet, Desktop, or a custom size from 240 to 2560 pixels. Fit to panel scales the display without changing the chosen viewport. CSS media queries use that viewport, and resizing preserves in-progress form drafts. This is a layout preview; trusted JavaScript and native libraries still share the workbench environment, so it is not a device emulator or an isolated script sandbox.
Inspect is the default: clicking an element reveals source without invoking its control. Choose Interact with live model to use controls that can change the connected model. Escape returns to inspection. Editing source also revokes live interaction until the draft is applied and live interaction is selected again. The Page controls navigate within the preview without changing the workbench URL. Page parameters require experimentation or live interaction because restoring them can write model inputs; each parameter must be a valid single route segment. Path placeholders map to page parameters in declaration order. Open page asks before leaving unsaved Form fields, then waits for the View's page inputs to be accepted. A refused input shows its message beside the picker; the current page and Form draft stay in place. While opening, the page controls and preview canvas wait for that attempt. Choosing Inspect, resetting, reloading, changing source or closing the preview cancels a pending page check. Returning to live interaction does not revive it. Cancellation does not undo a parameter write that the host already accepted.
Inspect pauses a live Form without discarding its fields. Returning to live interaction resumes that draft. Starting another copy, leaving a copy, or opening a CHECK setup asks before replacing unsaved Form fields; canceling keeps the same session. Resizing does not replace a draft. Reset copy and Reload preview remain explicit discard actions and need no second confirmation. Try a staged field in the validation example while switching modes; use the pages example to try parameter changes from the picker.
Choose Experiment in a copy to try an analyzed source draft against a private native model copy. You can change inputs, use local state and forms, navigate pages, and run model actions supported by that copy. Reset copy discards its changes and starts again from the current model inputs and source draft. Leaving experimentation or changing source discards the copy; switching to Live does not transfer experimental inputs, form drafts, or page parameters.
Choose Inspect copy to pause interactions while keeping the private copy and its Form drafts. Select a rendered element to open its draft source and Properties, even before the View's first Save. Turn off Inspect copy or choose Experiment in a copy to resume the same session. Preparation tools are hidden while inspecting. Writes already accepted by the host can still finish; queued new writes are refused. Editing source still retires the copy. Copy selections do not open live-model value inspection.
New Views can use this private preview before their first Save, including Views staged from an App conversion. Until the View is saved, Code explains that no saved preview exists and keeps Live disabled. Saving returns the preview to Inspect; choose Live explicitly to interact with the saved model. Opening a saved View in Code loads its compiled preview without first visiting another layer. If that request fails, the preview reports the error; use Reload preview to retry.
Open Set up inputs and local state to prepare values outside the preview viewport. Choose Model inputs or View local state, find an input, and stage its current value. Stage up to 256 values and apply them together. Values are literal numbers, text, Yes/No, Blank or structured JSON; formulas remain in source. Model inputs keep their validation and normal rules. Locals use the View's own constraints. Apply the two scopes separately. Restore default removes a model input override or restores a local's compiled initial value. If an input changes after staging, reload the staged values before trying again; that discards the staged edits. Each value is limited to 64 KiB and a batch to 3 MiB.
Save or load a preparation keeps a selected set of staged values for another experiment. Stage model inputs or View locals, give the preparation a name, then choose Save staged preparation. This downloads a JSON file without applying the values. Default-restoration choices and value types are retained. Save the two scopes separately. To capture a value you already changed in the preview, stage its current value first.
After resetting or reopening the copy, choose Open preparation file. The file must match this View and source identity. Loading opens its staged values for review and uses fresh copy revisions when applying them. Only listed values are affected; omitted values remain as they are. You can edit the preparation before applying it. Model inputs keep their normal validation and rules; locals keep their View constraints. Files contain up to 256 values, 64 KiB per value and 3 MiB overall. They contain selected values and source identity, not model source, page, rule clock, external resources, runtime or library snapshots.
Start from a CHECK setup reopens an existing CHECK's preparation in a fresh
private preview. Choose a CHECK, then Open CHECK setup in a copy. Its initial
given, reset and on steps run in order, stopping before the first interaction or
expectation. References read the results of preceding setup steps. Model givens
use the same validated native fixture path as a CHECK run, without firing normal
input-change rules. reset Name clears a model input's override to restore its
base/default, or restores a local's declared initial value. For a model input,
given Name = BLANK does the same: Grid treats a blank input write as a clear,
so setup cannot stage a blank model input whose default is not blank. A CHECK
reaches that state by pressing an action whose body runs CLEAR Name, which
pins a blank override. A local does hold BLANK. Local setup writes use the
View's constraints, and on opens the declared page with its current parameter
values. Later interactive edits run
normal rules. Inputs omitted from the setup retain their copied current values.
Controls and page inputs stay unavailable until setup finishes. Reset copy starts over with the same CHECK setup; Experiment in a copy starts without it. A failed or interrupted setup requires a fresh copy. Setup supports 1 to 256 initial steps and never runs later interactions or expectations. Use Run checks for the complete CHECK and its result. Changing source or host support returns to Inspect.
Save setup in Code turns staged inputs or locals into a named source CHECK. Stage current values or load a preparation, edit the values and default choices, enter a CHECK name, then choose Save staged setup in Code. This adds a compiler-checked block to the current draft and selects its header. Code Undo reverses the insertion. The source change ends the current preview; open the new CHECK setup in a copy to try it again. Add interactions and expectations in Code before using Run checks to test behavior.
Use type "Ada" in textbox "Name" when a CHECK must keep focus and leave
text as a draft. Follow it with key "Enter" in textbox "Name" to reproduce
an Enter commit, or key "Escape" in textbox "Name" to cancel the draft.
enter continues to fill the field and move focus away. Key steps accept Enter,
Escape, the four arrows, Home, End, Tab and Space; they do not accept executable
key sequences or modifier combinations. See the keyboard draft example.
Tree checks address named rows as treeitem, for example
key "ArrowLeft" in treeitem "Root draft" and
press treeitem "Root draft". A named Tree container can supply a tree
control expectation. Recording retains row navigation and selection gestures;
ambiguous row labels require distinct accessible names before capture.
Authored controls rendered outside the preview canvas in a React-owned portal cannot be captured. Their gestures explicitly invalidate the recording before running the control action. Review pauses those controls as well as the canvas; recorder tools remain available. Describe such interactions in Code.
Commit or discard existing Form drafts before starting a recording. Value expectations describe committed model/private values; a staged target refuses value capture. Use a reviewed control-value expectation to assert the visible draft, or commit/discard it before adding the value expectation.
Interaction recording also retains initialized private locals inside repeated
rows, including state kept while a row is off-page. Use Refresh values
after admitting or restoring rows to review their current expectation targets. Generated CHECK targets use
local "declaration" in row "captured instance": both strings come from the
compiled View and its exact row occurrence. Keep these addresses intact; they
are not row numbers. Setup restores the model inputs, global locals and page
before seeding row-local values. A value expectation requires the addressed row
to have rendered in the copy; seed data alone cannot satisfy it. Showing another
row does not borrow its state. A changed repetition boundary or missing local
fails admission. Try recording the per-row draft example.
Nested row bodies can read enclosing row fields through their lexical binder.
For example, a control under FOR batch IN order.batches can use label order.id.
The compiler identifies the owning row boundary; repeated child keys under other
parents do not change that binding. Computed model-row expressions remain
native Grid expressions, with their enclosing parameters retained. Private row
sources keep their existing restriction to stored fields or explicit JavaScript
glue for computation.
The Checks panel offers Run beside each CHECK and Rerun failed checks after a failure. Run checks runs all checks in source order; an individual run creates only its selected copy. Results appear as each copy finishes and is released, alongside the current check and progress. Retrying a check clears its old result while retaining the others. Results describe separate runs against copied model values, so they do not certify later input changes.
Cancel checks stops further steps and remaining checks, then releases the current copy. If copy creation is in progress, cancellation waits for its identifier so that the copy can be released. Completed results remain visible; unfinished checks receive no pass or failure. A cleanup failure appears with recovery guidance. Changing source, model, partition, dependencies or host support clears the displayed results and ignores late replies. Select a result to open its failing step or CHECK declaration in Code.
Try the report button example:
run either CHECK alone, then run both. Each resets the count in its own copy.
To try failure recovery, change the first expectation from 2 to 3, run both,
and use Rerun failed checks. Correct the expectation in Code and run again;
source edits clear the previous results.
Capture retains literal types, structured JSON and exact numeric strings, using
literal JSON and TYPE_TAG annotations where needed. Default choices become
reset steps. Local records are not interpreted as model input envelopes. Save
one scope at a time, with up to 256 values and 64 KiB of combined capture fields.
For larger sets, select fewer values or download a preparation file. Omitted
inputs, page, clock and external state are not captured. A name shadowed by a
local or a duplicate CHECK name is refused rather than silently changing meaning.
Capture requires editable source and host authoring support; source, model,
partition or copy changes cancel pending edits.
For the report-counter example, open each click runs the report as a setup. The counter starts at zero; the two presses and expectation have not run. Click Run report in the preview to try the interaction, then reset the copy to start at zero again.
Use a saved input scenario reuses scenarios saved under Saved scenarios in I/O. Choose Refresh scenarios, select one from this model's Files, and choose Stage scenario inputs. You can also open a scenario JSON file. Its source text and source identity must match the private copy. The preparation opens for review: saved overrides retain their value types, and inputs absent from the scenario are marked Restore default. Apply or discard existing staged edits before loading another scenario.
Applying uses the private copy's current revisions and ordinary validated input
writes. Model rules run normally; the connected model is unchanged. Scenario
files are limited to 2 MiB, and preparation supports up to 256 public inputs
with 64 KiB per saved value. Native resource references and unknown or ambiguous
inputs are refused. This imports model inputs only: local state, page, clock,
external data, runtime and package versions are not restored or verified. Use
Start from a CHECK setup for source-authored fixtures. For the canonical Order View, save an I/O
scenario for qty, change its value, then stage the saved scenario in a copy and
review the recalculated total.
Advance rule time steps the private native rule clock by 1 to 60,000 milliseconds. It runs due scheduled rules, debounce and held outcomes through the same evaluator and refreshes the preview. Rule time stays paused between operations; a step is one scheduler operation, not a replay of every elapsed tick. JavaScript timers, animations and date functions continue to use real time. An uncertain time-step reply requires Reset copy before continuing. Older hosts without a reported rule clock keep input experimentation available.
For a starting exercise, open the canonical Order View, choose Experiment in a
copy, stage a different qty, and apply it. Check the updated total, then
restore the input's default. Reset the copy to discard all experimental changes.
The copy uses the same native evaluator and the private CHECK session boundary.
It has no live jobs, connectors, external page fetches, source writes, or navigation
to another View. Clipboard and download actions are refused. It does not run a
background scheduler: delayed native outcomes advance only with explicit rule-time steps.
Values tagged button, including computed and named cells, can trigger their
normal WHEN rules in the copy and in CHECK runs. Each acknowledged click has a
distinct touch value even while rule time is paused. These are button operations;
other computed values remain read-only. An uncertain touch acknowledgement
requires Reset copy. The computed-button example
shows a report counter with a two-click CHECK. In the live workbench, named button
cells use the same input-override path as cell-address button targets.
Trusted scripts and imported components retain their usual browser permissions;
model-copy isolation is not JavaScript sandboxing. A host that lacks private
session support reports that limit before experimentation when it advertises its
tools. Older hosts can report an error with Reset and Inspect as recovery choices;
the experiment never switches to live writes. Copies expire after 15 idle minutes.
Availability and recovery shows the connected host's authoring tools, dependency resolutions observed during analysis, the View's required capabilities, and the workspace's actual custom-UI trust policy. A resolved import does not prove that its runtime assets, credentials or model actions are ready. Unsupported advertised tools are disabled; an older host without this report retains its existing compiler-provided source controls and is identified as unreported. Trust restrictions disable live interaction, private experimentation and checks that would require authored code. They do not prevent editing the source or inspecting installed library locks.
Choose Check library locks to inspect the exact libraries recorded in this View's compiler manifest. This checks installed lock metadata and retained contract source without loading executable runtime assets. Preview loading separately verifies those assets. A missing runtime pin, changed contract or missing source remains an error; a newer installed version is not substituted. Results belong to the current model, partition and draft, and are discarded when those change.
Review model dependencies opens the existing dependency tools; Review Connections opens the service connection workspace. After repairing an import or reconnecting a host, Refresh availability repeats the current draft's analysis across open Code panes. Package environment changes also refresh that analysis. Reload preview retries the renderer and its library loading, discards preview form drafts and navigation, and returns to Inspect; it does not save source or reverse acknowledged model writes. To see the pure-data case, open the order example and inspect its manifest before adding imported components. Publishing a sandboxed app and changing deployment trust remain separate, explicit operations.
Failed checks and private-copy preparation now show recovery controls beside the host message. Open related source opens the failed CHECK step or its setup; a failure before an individual check opens the View declaration. Open Dependencies, Open Connections and Refresh host availability use the existing workspaces and source analysis. Structured authentication and permission refusals explain when to reconnect or review access with the deployment owner; error wording alone is not interpreted as an authorization decision.
For a private-copy failure, Start a fresh copy repeats admission and any selected CHECK setup; Return to Inspect leaves experimentation. These controls remain above the preview and usable even when a failed setup disables its controls. They never enable live interaction or replay an uncertain model action. The report counter example includes a CHECK setup that restores its input before clicking the computed button.
Check outcomes describe the last run. Starting another run removes the old result; a failed attempt cannot leave an earlier pass showing. Changing model, partition, source, host capability or the observed package environment clears results and cancels pending runs. A changed model, partition or environment also returns the preview to Inspect. Later input edits do not continuously rerun or revalidate checks. Deployment-specific credential discovery remains with the connected host's administration tools.
Preview failures collects errors observed while this preview runs: script or locked-library startup, SCRIPT callbacks, imported property preparation and rendering, embedded surface rendering, and providers around the View. Select Open related source to reach a compiler-reported declaring line; a failure without an exact location opens the View declaration. Imported internals remain in their library source. Bounded argument descriptions can help reproduce a callback failure. Views with sensitive locals redact both messages and arguments.
Each Code editor, model, partition, source and environment has its own report. Private drafts can contribute diagnostics from their own copy; the last accepted preview cannot place runtime markers on a newer draft. Reset, reload, leaving a copy or changing context discards that attempt's report, and late failures cannot enter the replacement attempt. At most 64 distinct failures are retained per attempt. These are observed failures, not a promise that every JavaScript task succeeded: exceptions outside the renderer and its wrapped callbacks still use the browser's debugging tools. Native component inputs or source changes can retry a failed render; merely displaying its error does not retry it.
These edits preserve surrounding source, comments, and embedded payloads. The compiler checks the candidate using the model's imports before returning an edit; stale source, unsupported fields, and invalid candidates are refused. Scope-changing structural edits, validation for indirect/local/cell-address bindings and library definitions remain authored in Code. Imported expansions stay at their caller's use line, and embedded modules are opaque outline leaves. Generated surface slots remain read-only.
See Code authoring for editing and inspecting source-declared interfaces.
Builder mode over View source
Code's Builder mode arranges a View by direct manipulation, and every gesture still ends as ordinary Grid source. Each gesture sends one operation for the draft the compiler last analyzed; the compiler writes the candidate source, analyzes it again and refuses anything it would not accept, with its own reason. An accepted gesture is one editor Undo step, and saving in Code applies it to the model as usual. Comments attached to a moved or wrapped element travel with it, and embedded JavaScript and CSS payloads keep their exact text. Dragging, moving a selection and Wrap in are offered while Builder mode is on, the connected host applies View transactions and the analysis matches the current draft; otherwise the outline keeps its static controls. Property fields need a host that applies source edits, as they do in Code mode.
| Gesture | What it writes |
|---|---|
| Drag an outline row | One move of that element's block. The top quarter of the target row places it before, the bottom quarter after, and the middle inside a container. A drop into the dragged block itself, or one that leaves it where it is, is refused; Escape cancels the drag. |
| Drag a choice from Add element or page | One insertion at the highlighted place, written exactly as the insertion editor writes it. |
| Drag a region onto a cell of a named-region layout | The region assignment in the ARRANGE matrix; the region's content stays where it is. |
| Select several rows (Shift-click, or Command/Ctrl-click), then drag them or choose Move to | One move of the whole selection, landing together in outline order as a single source edit. A selected element inside another selected element travels with its parent. An element that owns definitions or page content moves on its own, after a review. |
| Wrap in a Section, Form or named region layout | A new container line above the selected run of consecutive siblings, with the run indented beneath it: "New section", Form, or ARRANGE [main] with a main: label. A page cannot be wrapped. |
| Drop on a page row | The block moves into that page, after its last line and in the page's own spelling (flat or indented). A block that reads a local of its current page cannot leave it. |
| Subject in a placed line's properties | Only the subject name of show x, edit x, Kind x or a bare x. The label, phrases and guards stay. The compiler resolves the new name and refuses a target the line cannot write or an ambiguous name. When the new subject would lower the line to a different kind of element, it asks you to edit the line in Code instead. |
| Page title and Page path in a page's properties | The AS "Title" and AT "path" clauses of the PAGE line, adding the clause when the page uses its default. CHECK steps that select the page by title follow the new title; navigation by page name is unchanged. A page with parameters keeps one :parameter per parameter in its path. |
| Theme in the View's properties | The surface's THEME line only (see below). |
| Shell drawer in Add element or page | A DRAWER left or DRAWER right block before the first PAGE. Deleting the drawer's outline item removes it with its lines. Drawer title in its properties rewrites the header's title. |
Wrap in runs as a single edit. Wrapping in a Section changes no scope and applies directly. A Form wrap is always reviewed first, because a Form stages its fields' edits and makes the arrows inside it submit them; the review shows the new enclosing block before Apply. Wrapping elements that own definitions is reviewed the same way.
input Quantity = 2
input DeliveryNote = ""
STATE submitted = FALSE
SURFACE Order
# Wrap in → Form: both fields now stage their edits until the arrow submits.
Form
Quantity AS "How many?"
DeliveryNote AS "Delivery note"
"Save order" -> submitted = TRUE
ENDThe theme editor offers accent presets and a custom colour, and choices for
density, corner radius, font, colour mode and content width. It changes only
the tokens you edit and keeps the rest of the line's text and order. New tokens
join the last top-level THEME line; when the surface has none, the editor adds
one at its top level. Not set (kit default) removes a token, and removing
the last token removes the line. A token whose value is a Grid expression is
shown read-only, for example "Accent follows BrandColor"; edit it in Code. The
compiler checks each value as it checks authored THEME: an invalid value is
refused with GRID_VIEW_THEME, and low contrast is reported as the
GRID_VIEW_CONTRAST warning. The editor never writes STYLE.
total IS currency = 1250
SURFACE Desk
# The theme editor added density and font to the existing THEME line.
THEME accent :teal density :compact font :serif
Metric total
ENDRenderer style layers
Source Views apply first-party element defaults in grid-view-base, imported
library styles in grid-view-library, kit styles in grid-view-kit, and author
STYLE in grid-view-author. The frontend CSS transform is restricted to the
three existing first-party App/input sheets. It preserves their ordinary App
selectors outside .grid-view and places copies inside the View base layer;
third-party and project styles are not transformed.
This has a measured frontend download cost. On the implementation source, those three sheets increased from 610,056 to 660,212 minified bytes (+50,156), and from 91,864 to 95,329 gzip bytes (+3,465), using esbuild CSS minification and Node gzip on their combined contents. The unminified increase was 54,979 bytes. These are source-sheet measurements, not a full product bundle or a browser-latency result. Ordinary App style matching also gains an exclusion selector; that rendering cost is not represented by the byte measurements. The transform runs at frontend compilation, creates no browser worker, observer, or polling loop, and adds no work to model evaluation or resolve.
Live View nodes retain their existing renderer hooks and add stable app-*
semantic classes. Framework archives can select Plain, DaisyUI, or Tailwind.
For an external kit, the integrating project passes its compiled stylesheet
text as kitCss and includes the exported view-kit.js in class scanning.
The adapter applies this stylesheet inside its shadow root, in the kit layer;
root variables and nested rules remain scoped to the mounted View. The author
layer follows the kit. Stylesheets are supplied by the host project, never
fetched or installed implicitly.
The shared App renderer adds one optional callback check at each converted class site. An ordinary App returns the original class string and does not allocate a class map or cache. A selected View allocates one mapping cache per kit identity and expands each distinct class string once. The shared renderer module alone grows from 54,690 to 56,820 minified JavaScript bytes (+2,130), and from 15,488 to 15,866 gzip bytes (+378), using esbuild ES2022/automatic-JSX module transformation. This is not a full bundle measurement. Callback latency has not yet been measured in a browser. This UI adaptation creates no model-evaluation or resolve work.
In Code, the source outline follows the compiler's declaration tree. Expand pages and containers with the arrow keys, then select an item to reveal its exact source. For an accepted View, Inspect reveals the clicked element without executing its action. Source selection and live interaction are unavailable while the preview belongs to older source. The outline remains a source projection; its property controls apply compiler-checked edits to the same source editor.
Follow a displayed value to its calculation
In Inspect, select a rendered element and choose a value under Model values for this element. Code opens its live value inspector. Why this value? requests a bounded runtime trace; each dependency offers source navigation and further value inspection. Selection alone does not load values or request a trace.
For compiler-generated values, Code shows the View name, authored expression and source line. Go to definition returns to that line, including from a projected View declaration. A reused expression may lead to its first recorded source site. Code withholds locations when the source is stale, the compiler has no exact location, or the metadata is ambiguous. Save or revert the draft and refresh source analysis before trying again.
These values belong to the accepted model. Unsaved Form fields, browser-local values and repeated row fields remain in their preview. Entering live interaction or a private copy clears the selected-element list; private-copy values are not presented as live-model provenance. Choosing a container with no direct reads does not select all of its children.
Try the Order example: select the
total in Inspect, open its value and choose Why this value? to follow qty and
price. To try an inline calculation, add Metric qty * price AS "Calculated total"
inside the View, save, and inspect it. Its generated value should show the authored
expression and line; use Go to definition to return to that line. Full
native-host acceptance and native/CLI diagnostic lineage remain under
development.
Recover a failed model value
A failed model read in a mounted Code preview now appears under Preview failures and in Code's Problems list. Open related source uses the compiler's expression span when one is available; otherwise it opens the declaring element or View. Known generated names in error details are shown as authored expressions and source lines. Unknown generated names do not acquire a guessed location. Native error objects display their message or error code, rather than an object placeholder.
For accepted live-model previews, Inspect failed value opens the same value inspector and explicit Why this value? trace. The report never retries a write or requests provenance automatically. Private copies retain their own source links and reset controls, with no shortcut into live-model value inspection. Reports describe observations in the current preview attempt; reloading or starting a fresh copy clears them. A report may remain after an input recovers.
To try this with the Order example,
add Metric qty / (qty - 2) AS "Review calculation" inside the View and save.
The default quantity exercises division by zero. Open the reported expression,
inspect the failed value, then repair the expression and save again. The preview
only observes values it already has; it does not load every model calculation to
search for errors. Ordinary text and record values are not rewritten as messages.
Framework exports and static capture
Views export through the same renderer into React, Vue, Svelte, and Solid. The Vue/Svelte/Solid artifacts contain a lifecycle adapter and a closed copy of the Grid renderer; the integrating app supplies its existing Grid transport and viewer services. Input edits require acknowledged atomic commits. A host that cannot acknowledge a write cannot silently report it as saved. Native React library and embedded native surface nodes retain explicit target diagnostics on non-React exports.
React’s ui.GridView and exported Vue, Svelte and Solid components accept
onReady and onUpdate callbacks.
onReady gives your app the mounted View handle, and receives null when it is
removed. Before changing your router URL, await handle.navigate(path) and move
the URL only when it returns true. The View asks about unsaved Forms and waits
for page inputs to be accepted. Cancellation or validation failure keeps the
current page and draft. Failures appear beside the View.
You can also pass a path prop. onUpdate reports { accepted, path, message? };
on refusal, path is the retained route so your app can restore its URL. Changing
the source or model connection also asks before replacing a dirty Form. Updating
kit styles, or echoing the already accepted route, keeps the same draft. A newer
update or unmount cancels pending page checks; a late reply cannot write through
the retired attempt. Writes already accepted by the host are not undone.
Before removing the component, call handle.canLeave() and keep it mounted if
the answer is false. handle.hasUnsavedChanges() supplies close/update status.
Hosts with an existing navigation owner may pass app.registerNavigationGuard
instead and consult that registration before removal. Framework cleanup and
handle.destroy() always release resources; they cannot veto a route that has
already removed the component. The Vue/Svelte/Solid README includes this contract.
React receives its host registration through the Component’s binding connection;
a direct GridViewComponent integration supplies it through bindings.
React unmount owns cleanup, so its handle exposes navigation and status without a
destroy method. Changes to compiled source, SCRIPT source, model connection or
partition retain the current React View until its dirty Form can be left. Native
React elements keep their existing renderer and library support. A refused
replacement cannot expand the retained View’s write permissions; reductions
still apply immediately. Equivalent document props and host callback updates
keep the current Form.
A home page with parameters requires an explicit concrete initial path.
Try exporting or ejecting the model field validation example:
change Reference without saving, then request removal through the handle's guard.
Cancel and confirm that Reference is still editable with its draft intact. With a
paged View, try a refused page parameter and then an accepted one before updating
your router URL. In a React Component, pass onReady to ui.GridView, keep its
handle in a ref, and check handle.canLeave() before changing the state that
removes it. Cancel once to retain the draft, then allow removal and confirm that
onReady receives null.
Static HTML capture records the currently rendered page and local draft values. It does not execute new model operations, and its controls are inert. It is not a full paged-table export: rows outside the current rendered page are not added. The snapshot says that it contains only displayed rows. Printing includes all already loaded array rows; a resident table prints its current page with a partial-data notice.
Running CHECK outside the Code layer
The maintained CLI operation runs the same accessible-control driver and view renderer as the Code layer. It requires an already running model host, an already built matching view runtime, and explicitly installed Playwright with Chromium. It never starts a Build or installs browser dependencies. The reusable local helper belongs to the Testing domain:
node tools/testing/run-view-checks.mjs --runtime dist/frontend/grid-view-runtime --project . --source model.grid --surface Desk --model MODEL_ID --api-host http://127.0.0.1:3000--token-env NAME can select an existing API bearer-token environment variable.
The token stays in the Node process. A temporary loopback server admits only
source analysis, private CHECK sessions it created, and reads of pinned library
assets for the selected model. The browser cannot use it for live-model writes
or arbitrary host requests. Every check still executes in the host's private
native calculation session. Completion and cancellation retire those sessions; an
unreachable host retains its existing expiry fallback. A deployment that
selects lifecycle-v3 durable receipts refuses CHECK session and library
requests before reading their bodies, because starting a session loads the
live model; run checks against an ordinary deployment.
The helper verifies selected runtime bytes before starting Chromium. Its JSON report carries check names, outcomes, and source locations. Embedded JavaScript is typechecked before interaction checks, using the Code layer's compiler-owned virtual documents and the project's explicitly installed TypeScript 5.x. Unknown names, malformed JavaScript, and incompatible expression result types map back to their original Grid source. Typechecking never evaluates author scripts or loads their imports. A failed typecheck or CHECK returns an unsuccessful exit status; a source without checks or JavaScript reports that no checks ran. This is an explicit Testing operation, never a condition for Build, Release, Deploy, or Verify.
Adjacent JavaScript tests run through the sibling Testing helper,
npm run test:view:javascript -- --source model.grid --source-sha256 <sha>(tools/testing/run-view-javascript-tests.mjs). It discovers immediate sibling
*.test.js, *.test.mjs, *.test.cjs and matching *.spec.* files beside
the model source and runs them as ordinary node:test tests with installed
Node 22 or later, under one bounded deadline. Author tests use ordinary local
Node authority. The helper clears its API credential variables before running
them, refuses to start if the pinned source changed, and reports source or
test-file drift. Missing, skipped, passed and failed JavaScript tests remain
distinct in its report. The independent CLI's gridctl surface check Desk --model MODEL_ID --source model.grid --runtime dist/frontend/grid-view-runtime --project ., which combines both phases in one report, exists only on the
CLI's codex/0.66.8 line; the CLI's main offers gridctl surface explain.
The runtime manifest records a dependency closure for each entry. Published views copy the publication closure; framework exports add their mount adapter; CHECK selects its own closure. Optional check-driver JavaScript is not included in ordinary published View artifacts.
Theme tokens and responsive hides
THEME, once per surface at its top level or directly in a PAGE, sets the
theme's fields as data: accent, density :compact | :comfortable, radius,
font, mode :light | :dark | :system and contentWidth. A value may be a
literal or a Grid expression; an expression is a model read like any other,
delivered as the value of the matching CSS custom property, so theming follows
the model with no CSS written. Beyond tokens, write real CSS in STYLE.
Builder mode's theme editor writes this THEME line, never STYLE; a STYLE
rule that assigns --accent, --surface-accent, --radius, --gap, --font
or --content-width overrides the token and is reported as
GRID_VIEW_THEME_OVERRIDE.
BrandColor = color"#2F6F9F"
total IS currency = 1250
SURFACE Desk
# Tokens are data; the accent follows the model value.
THEME accent BrandColor density :compact radius :soft mode :system
Metric total
ENDhide on is a universal phrase on an element line, a text line, a button or a
bare placement. Its targets are :mobile, :tablet, :desktop and :print;
any other target is GRID_ELEMENT_ARGUMENTS. A FOR, show or edit line
does not take it: hide the element line instead (Metric total hide on :mobile), or guard the line by comparing viewer.width to a literal.
total IS currency = 1250
notes = "Quarterly figures, reviewed by finance."
SURFACE Desk
Metric total hide on :mobile
Text notes hide on :print
ENDView theme CSS properties
The View root exposes --accent and --surface-accent for the theme accent,
--radius for the selected corner radius, --gap for density spacing,
--font for the selected font, and --content-width for the content width.
Mode supplies --bg-surface, --app-fg, --app-muted, --border-default, and
--border-subtle. Imported components inherit these properties. Use them in
STYLE with var(--accent), for example; Code offers the same property names.
mode :system follows the viewer's light/dark preference. contentWidth :reading limits the view to 72rem; :full uses the available width.
Responsive hide phrases use the view's available container width: mobile up to
640px, tablet from 641px to 1024px, and desktop above 1024px. Universal classes
survive document loading and reach imported components as className.
Default-mode Views retain an outer layout/paint boundary that author root styles
cannot remove. Print mode lifts that boundary for pagination.
Declared VALIDATE IN lists and CHOICE members supply selection options.
Model-dependent numeric bounds remain reactive. Literal validation on locals
also supplies options and bounds; small bounded integer ranges infer a slider,
including inside a Form. A placement before the first PAGE is shared by all
pages; a top-level placement after a PAGE belongs to that page.
When a Form has unsaved fields, changing the host's page, View, workbook tab, layer, model or folder asks before leaving. Cancel keeps the current route and draft. Browser Back and Forward use the same protection; for routes created by Grid, canceling restores the history position so you can retry the same move. The desktop close prompt and update protection also include these drafts. A private Code copy registers its Form draft with the workbench without receiving live model write access. An approved prompt alone does not save or clear fields; they remain until the navigation succeeds or the View closes.
Published Views use the same outer-app protection, including Home, Edit model, browser Back/Forward and desktop close. Their own page buttons ask through the host, so canceling keeps the Form inside its sandbox. The View sends only whether it has unsaved fields; it does not send the draft's values for this protection. Newly published Views require an updated Grid host and show an update message when that host support is missing. If a page's input validation refuses a move, the old Form remains protected.
Try the model field validation example: change Reference without saving, then choose Code or open the model browser. Cancel and continue editing the same draft. Save the Form before leaving to avoid the prompt. After publishing this example on an updated Grid host, repeat the same canceled move from its app page. Explicit Reload preview and Reset copy retain their documented discard behavior. Hosts embedding a View outside the workbench must supply their own navigation protection; the View still protects its own page actions and browser unload.
Model VALIDATE LIKE, ILIKE, STARTS WITH, ENDS WITH and CONTAINS
clauses supply control feedback, as does equality on text and Boolean inputs.
The author's MESSAGE appears beside an invalid draft. The draft is retained,
and a Form's Save action stays unavailable until its fields satisfy these rules.
Changing a referenced input refreshes both the field message and the Save action;
this does not modify the committed value.
A direct model operand, including a plain alias, reads that input's staged value inside the same Form. On hosts with native validation, Check values checks the complete proposed batch without writing it and displays returned field messages. Save checks again before committing. Computed operands are evaluated by the native host; the browser does not evaluate their expressions against unsaved inputs. Native input validation remains authoritative when saving: the host checks the complete proposed input batch, so a computed operand sees every submitted change and any restored defaults. It rejects a failing batch before writing any member. This requires the updated native host. The detached native check uses resident data; a missing/error operand or a dependency requiring new external work refuses the batch without launching that work. Models whose native capabilities cannot be cloned also refuse the check. Wider capability coverage remains unfinished. Native field messages also cover numeric and structured comparisons, including exact numbers and typed records; there is no separate browser implementation of those comparisons. Hosts without native validation retain limited local feedback and cannot offer Check values.
Open examples/canonical/grid-view-model-text-validation.grid. Change the
reference to NEW-101, then change its prefix to NEW-: the Form clears the
message and saves both inputs together. Select Reference in Code's outline and
edit Input validation to change the owning rule and its message. The checked
source change shares Code Undo and applies everywhere the input is used. These
are input contracts; separate record/table field declarations remain unfinished.
Use Link "Documentation" to "https://example.com/docs" for a captioned
external link; the destination can also be a model binding. SCRIPT exports must
not shadow a binding or element. Import a library element with an explicit
alias when its exported name matches a standard element.
Universal token scales are checked on literals at compile time and on bound/glue
values when they arrive. tone accepts :neutral, :info, :success,
:warning, or :danger; align accepts :start, :center, :end, :stretch,
:left, :right, or :justify. gap is a number from 0 through 64 pixels;
span is a positive integer. Width tokens remain :narrow, :half, :wide,
and :full; imported element contracts with a numeric width keep that
numeric setting. Invalid dynamic tokens show a property error instead of being
coerced into an unrelated style.
Computed drawings in Views
Grid 0.67.0 includes an initial drawing workspace. Large-drawing latency targets and broader host, image/export and resource-retention coverage remain follow-up work for 0.67.1. See the release notes.
A Drawing is a coordinate viewport in an ordinary View. Geometry and appearance
use the same native Grid expressions as the rest of the model. A Graphic embeds
an existing DRAW_* result, numeric pixel array, native image, or inert SVG image.
Use Label for vector text and Overlay for ordinary controls and View layout.
input center = [180, 140]
input radius = 40
input caption = "Station"
SURFACE Diagram AS "Diagram"
Drawing size [640, 360] label "Station diagram"
Circle at center radius radius
fill MAKE_COLOR(47, 111, 237)
stroke "#173875"
stroke width 2
Label caption at [24, 28] font size 18
Handle center label "Station position"
Overlay at [400, 24] size [216, 180]
Stack gap 12
TextInput caption label "Caption"
NumberInput radius label "Radius"
ENDSee the complete example.
Positions, sizes, and radii pairs are ordinary two-number arrays. X increases to
the right, Y downwards. Group translate [x, y] rotate degrees scale [sx, sy]
applies scale, clockwise rotation, then translation. A Label position is its
text baseline. A Drawing defaults to [512, 512]; Code insertion writes
size [640, 360] explicitly. fit :contain centers and preserves aspect ratio;
fit :actual uses one CSS pixel per logical unit. The viewport clips all layers.
Graphics follow source order, overlays sit above graphics, and handles sit above
both. HTML controls do not interleave with individual strokes.
The standard drawing elements are Drawing, Group, Graphic, Line,
Polyline, Rect, Circle, Ellipse, Arc, Path, Label, Overlay, and
Handle. Their Properties and insertion forms come from the standard component
catalog. Specific geometry phrases take precedence over presentation phrases:
Drawing size [640, 360] accepts a pair, while text size tokens retain their
existing meaning. Use ordinary FOR with stable keys, conditions, reusable
ELEMENT definitions, and native calculations. Shapes and groups support
on click -> ...; keyboard activation invokes that same action.
A Handle edits one explicitly writable coordinate pair atomically. A stored
record field such as Handle Station.position is also supported when the
compiler proves its writable owner and pair shape. The guarded transaction
preserves sibling fields and checks the revision of the owning input. Dynamic
indexing and computed inverses are not writable handle targets. commit :live is the default; commit :release sends the final
position when released. axis :x or axis :y restricts motion. A positive step
snaps positions; without it, pointer movement is continuous and arrow keys move
one logical unit. Enter opens numeric coordinates. Updates use guarded native
writes with revision receipts, one write in flight and one latest pending value.
Handles keep their on-screen size when the drawing is zoomed or fitted.
Conflicts stop the gesture and retain its draft. Escape cancels unsent movement
and pending input validation; it does not reverse a committed write.
Revert gesture restores the initial position only if its revision guard still
matches. Within a Form, the handle stages and previews until Save. A successful
Save or Discard clears the previous gesture feedback. Hosts without
the required guarded-write capability show the handle as unavailable.
In Code, Draw adds selection, movement, resizing, group rotation, pan, zoom, and fit to the View preview. Shift-click selects multiple authored objects. The insertion panel and Properties retain ordinary source expressions. A literal can be moved visually; an input requires an explicit Handle or control; a derived expression stays intact. Add transform wraps computed geometry in a Group. Repeated instances edit their common template. Selecting an imported drawing identifies its caller placement and library owner. Its arguments remain editable in the caller; Add transform wraps that placement without changing the library or replacing its formulas. Completed source gestures use checked compiler edits and one Code Undo step. Live model gestures are separate from source Undo. Ungroup distributes literal transforms into individual placement Groups, preserving computed child geometry. A shared computed transform or behavior keeps its Group and receives an explanation instead of an inferred rewrite.
Artwork SVG exports the resolved graphics layer; interactive overlays are
excluded. View print/snapshot opens Print Studio with the saved View selected
and includes the complete composition and ordinary controls. It does not export
private experimental input values; save or inspect those separately.
Neither writes model inputs. Native raster exports through DRAW_RENDER and
DRAW_RENDER_RGB explicitly reject text until native text rasterization exists.
Paths support absolute M, L, C, and Z, not general SVG path syntax.
Graphic grayscale values use range :unit by default. Choose :byte for
0–255 values or :auto for legacy automatic normalization. Existing Canvas
configurations keep automatic normalization when no range is specified. SVG text
is displayed as an inert image document and is never inserted as application HTML.
A Drawing admits at most 10,000 expanded graphic primitives and handles, 256 Overlay roots, and 64 nested Groups. Invalid or oversized updates retain the last valid artwork and show a source-linked error. The native scene and raster limits still apply. These limits are admission rules, not performance claims.
Tree hierarchies (0.68.0)
Tree presents model records with globally unique text or finite numeric keys.
key, parent, and label take ordinary expressions in its row scope. A blank
parent denotes a root. Roots and siblings retain source order; missing parents,
cycles, duplicate keys, more than 10,000 occurrences or more than 64 levels
refuse the complete update. A previous valid hierarchy stays visible and read-only.
items = [
{ id: "office", parent: BLANK, name: "Office" },
{ id: "desk", parent: "office", name: "Desk" }
]
SURFACE Outline
local selected = BLANK
local expanded = []
Tree items key node.id parent node.parent label node.name
selected selected
expanded expanded
row(node)
Text node.name
ENDWithout a template, the label is displayed; without a label expression, model
rows display their key. row(node) receives the original row fields, including
native tagged values. Arrow keys navigate and expand/collapse; Home/End move to
the first/last visible item; Enter/Space select. Unbound selection and expansion
are temporary state for the mounted View. Explicit input/local bindings follow
guarded writes and Form staging. Selection and expansion may also bind to
stored fields of input or local records, such as selected state.selected and
expanded state.expanded. The compiler requires an existing stored field;
computed or dynamic fields are refused. Changes preserve sibling fields and use
the owning input's revision. Empty text remains a valid Tree key. Removed rows
and replaced native occurrences retire incompatible temporary selection and
expansion; a rejected draft cannot be applied to a replacement occurrence.
Scoped row locals survive collapse and are released when their occurrence is
removed or their Tree unmounts. Pending validation is canceled if the addressed
row disappears; write permission is checked again before native dispatch.
The 0.68.0 local native-host record
covers model rows, syntax/pattern/term inspection, keyboard controls, keyed state,
Form/private staging and invalid-to-valid recovery. Native input updates may use
ordinary JSON records; selection/expansion preserve the returned native carriers.
Its browser/profile and remaining assessment limits are recorded separately.
Native syntax trees, patterns and terms use bounded inspection records with
owner/occurrence identities, kind, sort, label, symbol, constructor,
terminal, and childField. Equal shared subtrees at different occurrence paths
remain distinct. Other tree-family authority values expose a summary; displaying
one never executes it. Browser-local computed row relationships receive a staging
diagnostic. Legacy App export targets report Tree as unsupported.
Opening a declared View in Builder routes to its Grid source. Existing Builder-authored App documents retain their current editor and storage. Code's Builder mode uses the same source buffer, outline, Properties, Draw preview and source Undo history as Code mode. Reopening the workspace retains the selected authored node for the same source draft. Replacing the source clears that retained selection so it cannot target a different declaration. It does not save a second graphics or UI document. Multi-operation edits validate the original source hash and all node identities together, and refuse overlapping changes as one operation. Drawing gestures retain their proposal when source analysis, editing permission, or the selected source node becomes unavailable; a late reply cannot apply the canceled proposal.
Whole-record View edits and Form checks/commits preserve existing nested native
scalar tags, including mixed currencies, percentages, dates and exact strings.
Untouched complex/error fields survive sibling edits; those fields remain
read-only in structured editing except for explicit clearing to blank.
Preparation happens at the write boundary. Unsupported native carriers or lossy
field-type changes are refused with guidance to use Code; untagged arithmetic
keeps its existing meaning. Numeric and string refinements keep scalar equality with their payloads:
12USD = 12 and url"https://example.test/a" = "https://example.test/a"
are true. Numeric-looking text remains distinct from numbers, and symbols retain
their identity. Filtering retains the selected records' tags. Offset-bearing
datetimes keep their exact timestamp and temporal carrier inside records and
saved input values; they are not converted to another timezone.
The semantic values example
shows mixed currencies, URLs and a native filter. Focused native coverage audits
numeric, string, structured and spatial literal families; complete View export
and interaction acceptance remains separately tracked.
The independent CLI provides gridctl surface explain <model-id> <surface>
(and active-model shorthand). It asks the model-aware native compiler for source
ownership, presentation, capabilities and diagnostics, with the usual human and
JSON output modes. It does not write inputs or source.
See the 0.67.1 completion record for the remaining interaction, export, performance and product-journey work.
Complete View printing in 0.68.0
Complete View output includes overlay values and available native semantic labels,
while artwork SVG excludes interactive overlays. Printing hides the sibling Code
workspace. Contained drawings fit the actual page width; fit "actual" retains
logical size. Export does not save private drafts or write model inputs.
Inspect saved native workbenches
A View can embed an existing Train job, Evaluate run, Process definition, Ledger accounts/journal pair, or Symbolic worksheet using literal owner identities:
SURFACE ReviewDesk
Train saved job "job-1"
Evaluate saved run "run-1"
Process "Review"
Ledger accounts "Accounts" journal "Journal"
Symbolic symbols ["Expression"]
ENDThese embeds inspect their native owners. Opening or printing them starts no
training, evaluation, process execution, posting, or algebra action. Missing
saved identities remain explicit. Dynamic identities and operation callbacks
are refused. The enclosing View does not pass model-writing capabilities to
these inspection nodes. Standalone workbenches remain the place to perform
operations. Framework exports retain explicit unsupported-native-surface
errors; an inspection embed does not export its native engine to a browser.
See examples/canonical/view-saved-workbenches.grid.
Assistants and Process view access
A view with supported actions offers Let assistants act in this view in its ask box. Access starts disabled. Turning it on permits an assistant to use the open view's supported actions and selections for the current tab session. Actions retain their input validation, availability requirements, and history attribution. Browser agents use WebMCP where the browser provides it; actions that require a person's confirmation are not offered through that interface.
A Process shares a model view with a role using SHARE VIEW <view> WITH <role>.
Add TO ACT to permit supported actions as well as reading. Process Start
lets you select views within those source-defined permissions and choose
Read or Act for a view that permits acting; Read is the default.
UNSHARE VIEW <view> WITH <role> withdraws both kinds of access.
The backend must select the hosted view endpoint with GRID_VIEW_TOOLS_HTTP=1
to start a Process with view grants. Grants last for that run, and actions that
need a person's confirmation are not offered to roles. An interrupted command
is retained as interrupted and is never automatically resent.
Fixed-vocabulary name conflicts in reusable definitions are checked even without
a placement. For example, ELEMENT Conflicted(Stack) with a bare Stack body
is ambiguous between the parameter and the standard element. Write show Stack
to select the value, or rename the parameter. This check does not instantiate or
execute the unused definition. Binding-dependent body checks still occur at
placement.