Declared Presentation — `FORMAT`, `VALIDATE`, `PRESENT`, `##` notes
Declared Presentation — FORMAT, VALIDATE, PRESENT, ## notes
A model's meaning lives in its formulas; how a value should look, what writes it accepts, and why it exists are usually scattered across UI state. Declared presentation brings all three next to the cell, in source:
## Quarterly revenue, gross of returns.
input revenue is currency = 100000
FORMAT "$#,##0.00"
VALIDATE BETWEEN 0 AND 10000000 MESSAGE "Revenue must be a positive dollar amount."Record input fields can instead use a standalone VALIDATE FIELD declaration.
It names an exact member path on the owning input; whole-input and field rules
are checked together against the proposed batch. See the
field-rule reference for syntax,
limits and the development verification status. These contracts do not add a
browser evaluator or extend model write ownership to computed fields. Private record locals can
reuse literal rules as described below.
Clauses trail the declaration — inline or on directly following indented lines. They are declarations, not statements: the cell's formula stays exactly what it was.
Layering
Presentation resolves through four layers, weakest to strongest:
tag-implied default a currency tag suggests currency rendering
< tag policy top-level FORMAT <tag> <expr>
< declared per-cell trailing FORMAT/VALIDATE clause, ## note
< override layer rule FORMAT actions + UI/API editsRESET FORMAT x (a rule action) clears the override layer and drops
back to the declared layer. Overridden cells report their provenance
("overridden (declared: …)") so a declared format is never silently
lost.
Doc notes — ##
A ## line documents the next declaration; a trailing ## documents
its own line:
## Discount applied before tax.
## Negotiated annually with the vendor.
discount = 0.15
tax_rate = 0.0875 ## City + state combined.A blank line detaches a ## block, so decorative comment banners stay
inert. Notes ride module metadata into the UI (cell inspection,
tooltips) — they are documentation, not evaluated content.
An intervening ordinary-comment line also detaches the block. Attachment is
currently supported for named, cell and range assignments. Single # comments
must occupy their own line; use /* … */ for trailing comments that should
not become documentation metadata.
FORMAT
Three placements, one vocabulary (spreadsheet format patterns):
price = 19.99 FORMAT "$#,##0.00" /* per-cell declaration */
FORMAT percentage "0.0%" /* tag policy: every percentage-tagged cell */
WHEN audit_mode THEN
FORMAT price "$#,##0.0000" /* dynamic override from a rule */
ENDFormat operands are expressions: FORMAT IF(compact_view, "$#,##0,,\"M\"", "$#,##0.00")
re-derives when its inputs change. Tag policies resolve
most-specific-first through the type lattice, so a USD policy beats a
currency policy beats a number one.
PRESENT
A trailing PRESENT <Kind> chooses the usual Grid View element for a binding:
input qty = 2 VALIDATE 1..10 PRESENT Slider
SURFACE Order
qty
NumberInput qty
show qty
ENDThe first line inside the view uses Slider; the second explicitly uses
NumberInput; show qty remains a read-only presentation. The preference never
changes the cell's formula, type, ownership, or validation. A control preference
applies to editable placements and a display preference applies to read-only
placements. The opposite direction uses its normal presentation.
The priority is an explicit kind on the view line, then the binding's PRESENT,
then the most specific tag policy (PRESENT <tag> AS <Kind>), then the inferred
root default. A preferred kind must accept the binding's subject type. local
declarations can also carry PRESENT. Like FORMAT and VALIDATE, it may appear
inline or on a directly following line, at most once per declaration.
VALIDATE
The clause vocabulary reuses the language's predicate keywords with the subject implicit:
input qty is integer = 1 VALIDATE BETWEEN 1 AND 100
input qty2 is integer = 1 VALIDATE 1..100 /* sugar for BETWEEN */
input margin is percentage = 5pct VALIDATE >= 0
input region = "EMEA" VALIDATE IN ["EMEA", "AMER", "APAC"]
input invoice = "" VALIDATE LIKE "INV-%"
input code = "" VALIDATE STARTS WITH "GRID-"Accepted clause heads: the comparisons (=, <>, <, <=, >,
>=), BETWEEN … AND … (and the lo..hi sugar), IN, LIKE /
ILIKE, STARTS WITH, ENDS WITH, CONTAINS. An optional
MESSAGE "…" supplies the rejection text. Bounds are expressions —
VALIDATE 1..max_qty re-derives when max_qty changes. Any other body,
such as a brace array VALIDATE {2, 3} or a bare VALIDATE 5, is refused
with GRID_PRESENTATION_VALIDATE_CLAUSE; write a list as
VALIDATE IN [2, 3].
The .. range separator does not depend on surrounding spaces or comments.
Imported bounds in VALIDATE fin.low..fin.high bind exactly as in
VALIDATE fin.low .. fin.high; each bound resolves independently in the
current lexical scope.
VALIDATE means one of two things depending on where it sits:
On an input cell: a write contract
Every write surface — the grid UI and the model-binding ABI, so API and connector writes too — checks the incoming value (after type-tag coercion) against the clause. A rejected write returns the violation and leaves the cell unchanged. Native batched writes validate their complete proposed state before committing any member: a clause can depend on another input in the same batch, including a default restored by a clear. The detached check does not launch external work or commit rule events. Unresolved operands and unsupported clone capabilities refuse the batch. The contract gates writes only; changing a bound later does not retro-invalidate a stored value.
Grid View controls show the declared message for text predicates and text/Boolean equality before saving. Forms retain invalid drafts, read staged values for direct input operands, and refresh feedback when those inputs change. Computed operands continue to use committed native results. Numeric/structured equality feedback remains native-owned. See Grid Views for the canonical order-details example and Code's checked validation editor.
On a computed cell: a static assertion
The compiler's interval analysis propagates value ranges through formulas and discharges the assertion at compile time:
input x = 0 VALIDATE 0..1
y = x / (x + 1) VALIDATE 0..1 /* proven — recorded in module metadata */
z = x + 2 VALIDATE 0..1 /* GRID_VALIDATE_UNSATISFIABLE: can never hold */
w = x * 3 VALIDATE 0..1 /* GRID_VALIDATE_UNVERIFIED: not provable */An input's write contract seeds the range other formulas observe —
that is what lets y above be proven outright. Refuted assertions are
warnings by default and hard errors under strict (see
reference §5.4);
unverified ones stay warnings, an honest signal that nothing checks the
assertion at runtime.
Where clauses may not appear
Compound assignments (+=), graph bindings, and table bindings do not
take presentation clauses — declare them where the cell is defined.
See also
- reference.md §5.4 — the strictness umbrella and the static analyses behind it
- cell-metadata.md — the document-state layer these declarations merge under
- assignments.md —
input,default, and type tags
Reusable record contracts
A named contract collects field rules for several model inputs. INCLUDE uses
another contract at the same root; INCLUDE ... AT path prefixes its exact
field paths. Definitions may refer to a later contract. Recursive inclusion is
an error.
RECORD CONTRACT AddressRules
FIELD city <> "" MESSAGE "Enter a city"
END
RECORD CONTRACT OrderRules
FIELD amount > 0 MESSAGE "Enter a positive amount"
INCLUDE AddressRules AT shipping
END
input Order = {amount: 2, shipping: {city: "London"}}
input RepeatOrder = {amount: 3, shipping: {city: "Paris"}}
VALIDATE RECORD Order USING OrderRules
VALIDATE RECORD RepeatOrder USING OrderRulesFIELD accepts the existing validation clauses and optional MESSAGE.
Dot paths and quoted keys in brackets have the same exact-key meaning as
VALIDATE FIELD; numeric and computed keys are rejected. Bounds may read model
values and are checked against the complete proposed input batch. Whole-input
validation and additional standalone field rules still apply.
The compiler expands each use into the existing native record contract. Bound
expressions are admitted once for each used definition; unused definitions do
not create executable bound cells. Reusing
one rule at several paths retains a separate validation identity at every path.
An application requires the same unique local input owner as VALIDATE FIELD;
it does not make a calculated value writable. Empty definitions, missing
includes, cycles and overflow fail admission without truncating the rules.
A source admits at most 256 named contracts and 4,096 model applications. Each contract has at most 256 direct entries and expands to at most 256 field clauses per owner. Paths have at most 32 keys, each at most 512 UTF-8 bytes. Expansion has a shared 4,096-visit and 8 MiB metadata budget and at most 32 nested inclusions. Contract names are case-insensitive; field keys preserve their exact spelling.
VALIDATE ROWS Lines USING LineRules checks each record in a model-input
vector. Use an exact member path such as Order.lines for a nested collection;
quoted keys retain their spelling. Flat, horizontal and vertical vectors admit
at most 65,536 records; empty vectors satisfy the row clauses. Missing members
and malformed collection shapes fail validation. Bounds use the complete
proposed input batch. A rejected edit leaves the input unchanged and reports
the first failing row and exact field path for each clause. The
model row example shares a
live-bound contract between root and nested collections.
Definitions-module imports retain contracts with USE "rules.gs" AS Rules or
USE "rules.gs" EXPOSING (LineRules). Use Rules.LineRules with a namespace.
Selected contracts keep their private includes and transitive definitions.
Bounds close over immutable library values using hygienic compiler substitution;
a same-named caller value cannot change their meaning. LET and lambda bindings
retain their scope, and value substitution leaves builtin call names intact.
Literal library bounds may also be reused by private View records and rows.
Unused imported definitions create no executable bound cells.
Open model-input captures, cell-address/external-qualified captures, cycles, missing includes and collisions fail admission. Importing a model owner with mandatory rules remains refused. Parameterize a dynamic model bound in a caller-owned contract that includes the shared rules. Contract names allow at most 32 namespace segments and 512 bytes when qualified; imported value closure uses the existing syntax budgets plus a shared 4,096-visit/8 MiB module budget. See the imported contract example and its library.
A .grid View library may declare and export RECORD CONTRACT alongside its
Elements. Its private drafts resolve contract names in the defining library,
including nested library dependencies; a caller's same-named contract cannot
replace them. USE "rules.gs" AS Shared inside a View library imports reusable
contracts with their immutable bounds, without exposing module values or
callables. That path resolves relative to the defining library. Imported names
remain private dependencies unless the library declares a public wrapper
contract with INCLUDE Shared.Rule. Callers can apply that exported wrapper to
model RECORD/ROWS owners or their own private drafts. See the
View-library example
and its component library.
Grid source component packages retain contracts used by private RECORD/ROWS owners, including transitive includes and closed imported bounds. Rules receive package-owned names that change with the imported component, so a same-named receiving rule cannot replace them. Re-export keeps the closure once; unused rules are excluded. Exact keys, messages, clause kinds and embedded payloads retain their meaning. Captured model bounds, missing closure and occupied helper names refuse the import. See the packaged source example.
Shared-contract editing in Properties remains under development. The standalone field-rule editor excludes shared definitions, and source rename refuses references whose shared-contract meaning it cannot yet prove. Edit the shared definition in Code to change all its applications.
The reusable record example combines a live bound with repeated nested addresses.
Private record applications
Inside a View or an element, the same application syntax targets a private record local in that lexical scope:
RECORD CONTRACT AddressRules
FIELD city LIKE "_%" MESSAGE "Enter a city"
END
SURFACE Addresses
local Draft = {city: "London"}
VALIDATE RECORD Draft USING AddressRules
edit Draft AS "Address"
ENDPrivate applications accept literal operands, including literal lists. A rule that reads a model binding or calculates a bound is refused; those rules belong on model inputs. Applying a literal contract does not create model bound cells or send the private value to the host. A nested scope cannot add rules to a parent's local, and an imported element cannot capture a receiving model's contract definition.
Applications may precede their local declaration. Repeated components and
repeated-row locals retain separate owners. Whole-local VALIDATE clauses still
apply. Missing members and arrays used as records are refused; quoted keys stay
exact. LIKE "_%" requires nonempty text, including refusing Blank. A simple
<> "" rule alone allows other literal types that differ from empty text.
Rules are checked on direct local writes, action writes and Form submission. An invalid private draft prevents subsequent model writes or actions and stays available for correction. Field errors appear on the matching control; errors without a matching field remain at Form level. Private bounds have a 64 KiB literal-source limit; a View has an 8 MiB expanded-contract budget in addition to its ordinary document limit. Field rule lookup is indexed at View admission.
The private record example uses two independent component instances with the same nested rules.
VALIDATE ROWS Lines USING LineRules applies a contract to every row of a
private list of records (or a one-row or one-column matrix of records), up to
65,536 rows. Each field rule is checked against each row, and a failure names
the row and field path. The other rules above apply unchanged. The
private row contract example
validates two independent draft tables with one nested contract.