Native component libraries

Pinned React libraries

Pinned React libraries

Grid Views resolve installed React contracts through the model asset libraries/grid-libraries.lock.json. The compiler never installs packages or fetches source. The independent grid-cli repository owns grid library add; Core provides the reusable lock schema, resolver, and runtime artifact admission.

The version 1 lock contains a libraries array. Each record has these fields:

Field Meaning
reference Exact authored request, such as react:react-select or react:@acme/widgets@^1
package Actual npm package name, matching the reference
version Exact resolved semantic version; never a range or tag
integrity Recorded npm tarball SRI, using SHA-256, SHA-384, or SHA-512
exports Unique exported component names admitted by the contract
source {path,size,sha256} for the generated .grid contract
runtime Optional closed runtime manifest, described below
runtimeIntegrity SHA-256 SRI of the canonical runtime manifest; required exactly when runtime is present

Source and runtime paths are exact ASCII paths beneath libraries/ in the model asset sandbox. They use forward slashes; absolute paths, empty or dot segments, traversal, percent escapes, queries, fragments, and backslashes are rejected. The asset store opens files without following symlinks. A byte size and lowercase 64-character SHA-256 digest accompany every file. Hashes describe the retained bytes, independently of the npm tarball pin.

A lock is at most 1 MiB and contains at most 128 libraries. Generated source is at most 2 MiB. An artifact contains at most 256 files, each at most 16 MiB and together at most 64 MiB. Duplicate references, conflicting npm integrity for one exact package version, duplicate exports, and unknown lock fields are rejected.

Editing component options in Code

Select an explicit native component in the Views outline to use its options panel. The compiler supplies properties from the admitted contract, including import aliases and local phrasebooks. Typed values, closed choices, two-way bindings, fixed presets and preset argument lists share Code's checked source edits and undo. Conditional properties describe the required variant selection.

To add a component, use Add element or page in the outline. Imported aliases and local declarations appear even before their first use, with the selected View's phrasebook applied. Enter its Main binding, Main value or declared preset arguments as Grid expressions; use double quotes for text. The compiler checks the library lock and binding at insertion. A refusal leaves the draft unchanged and explains the problem. Insertion adds only the caller's use; install libraries through the existing library tooling. Use Properties afterward to add other settings, events and slots.

The positional subject appears as Main binding for a two-way contract or Main value for a value contract. For Chooser Selected title "Choose", change Main binding to another declared input or local value; the compiler updates only Selected. Add or remove the subject through the same property controls. Value contracts use their declared text, number, Yes/No or choice control, with expression mode where supported. An alias already supplying the same property prevents adding a conflicting subject. Fixed contract settings cannot be overridden. These controls also work in direct native arrow headers.

When the main binding names a model input directly, Input validation links to its owning declaration. Open that declaration to add, update or remove its VALIDATE clause while retaining the View inspector and Code undo. This changes the model input's contract everywhere it is used. View locals, row lenses and indirect bindings continue to use Code for their contracts.

For example, a declared spacing(size) preset takes 4 in its argument field; the compiler writes spacing (4) at the selected use site. Fixed presets take no value. Declared tells events expose a handler source field with their event values listed, such as chosen for on pick tells onPick(chosen). Derived event contracts retain the admitted callback signature after a colon. Code uses it for member checks and completion inside inline JavaScript in the handler. Grid expressions also complete declared record fields, including nested records and array items: payload.customer. and payload.items[1].. Union types offer common fields; nullable records retain their hints. Unknown aliases and complex foreign types remain open. These fields are passed to the handler's glue; they shadow same-named locals, while explicit USING aliases take precedence. Re-import an older locked contract to add its types; untyped event values remain unknown in Code. Raw trailing DOM events are omitted, and rest/overloaded/nonserializable event handlers require an adapter. The day-click exercise shows a typed Date callback. Add, update or remove the handler at the caller use site; guards, keys and nested actions are checked in that event's scope and share editor undo. Aliases for an already-used event remain unavailable. An always setting remains fixed by the contract; named holds slots have Add/Update/Remove slot controls with the scoped values listed. Edit the slot header and its indented content; the compiler checks names in the slot's scope. Unnamed default-body elements appear in Content, without a slot header. Add, update or remove that content with one checked source edit. Interleaved content is gathered at its first location on update; neighboring properties, events and named slots are preserved. A named body and unnamed content cannot overwrite one another through these controls. Reusable definitions stay in Code. Direct native arrow headers expose property controls; their On activation field lists the selected press/click event's values and edits inline actions. Header edits preserve keys, guards and nested/refusal actions. Action requirements separately edits each body-level needs condition and explanation. Additions follow existing checks; removal retains nested action steps and refusal handlers. These requirements can use the selected activation event's values, while component header Availability uses caller values. Refreshing a changed contract cancels pending drafts. Each change shares Code's Undo history. Arrow children are action lines; use the ordinary component block form for named slots or additional events.

Named events also have requirements sections below component options. Open one to edit individual checks with the values supplied by that event; selecting the handler in the outline opens the same controls. Open handler in Code reveals its source. Other events, slots, bindings and handler refusal actions stay intact. The complete handler source field remains available for other action changes. An event's requirement drafts are independent of its neighbors and are cancelled when its source, contract or editing support changes.

Property values, event actions and slot content are resolved where the component is used. They retain the caller's imported aliases, local elements, named actions and scoped slot values. A library's private helper names do not become caller names or replace a caller action. Contract presets, converters and styles retain their defining library's origin, including its own JavaScript exports.

The inspector neither changes the imported contract nor installs, trusts or executes a library. See the Views authoring guide for the editing workflow and remaining limits.

Runtime artifact

runtime has exactly format, entry, and files:

{
  "format": "grid-view-library.v1",
  "entry": "libraries/widgets@1.2.3/runtime/index.js",
  "files": [
    {"path": "libraries/widgets@1.2.3/runtime/index.js", "size": 1234, "sha256": "<64 lowercase hex characters>"}
  ]
}

Every file is inside the entry's directory, and the entry occurs exactly once in the file list. The entry is prebundled ESM whose default export is a factory: factory({react,reactDom,reactDomClient,jsxRuntime,jsxDevRuntime,assetUrl}) returns {components,providers?}. This gives the artifact Grid's existing React instances. Each optional provider is {component,props?} defined in the pinned entry bytes. The artifact owns its imported code and resources; a document cannot supply an arbitrary module URL.

The canonical runtime bytes are UTF-8 JSON with no whitespace. Keys have this exact order: format, entry, files; files are sorted by their ASCII path, and each file's keys are path, size, sha256. This is the result of:

JSON.stringify({
  format: runtime.format,
  entry: runtime.entry,
  files: [...runtime.files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
    .map(({path, size, sha256}) => ({path, size, sha256}))
})

runtimeIntegrity is sha256- followed by the base64 SHA-256 digest of those bytes. The compiler retains this pin in both its native node and library manifest. A compiler-only lock may omit the runtime, but it cannot render an imported component until a runtime artifact is prepared and the source is recompiled to admit its pin.

Resolver and host requests

AssetModuleResolver reads ordinary .gs and .grid modules through the model asset sandbox. Its first selected react: request loads and checks the lock once for that compilation. Resolving react: source verifies the generated contract's exact byte size and SHA-256 before returning text to the single compiler. There is no package fallback, environment discovery, installation, or network request.

The model-scoped RPCs are inspectViewLibrary(modelId, pin) and readViewLibraryAsset(modelId, {...pin, path}). Both require the complete compiled pin: {reference,package,version,integrity,runtimeIntegrity}. Inspect returns the current record only if those pins match and its contract bytes still match. Read accepts only a file in that pinned runtime and returns {path,size,sha256,contentsBase64} after verifying the bytes. A changed lock or artifact is refused with contract drift; the browser must not substitute newly changed code under an older compiled identity. HTTP ingress uses the same handlers under the existing model access checks. Ordinary hosts list both methods in /readyz supported_methods. A deployment that selects lifecycle-v3 durable receipts refuses them before reading the request body, so the app cannot load a view's React libraries from it.

Ordinary module resolution leaves the library cache empty: no lock read, hash, serialization, package load, extra worker, or network request. The cache field is paid at resolver construction, a model-admission boundary. Selected React imports pay bounded lock parsing and runtime-manifest hashing once per compilation; generated source is checked when imported. Runtime requests pay bounded manifest checks and file hashing at their explicit request boundary. No cell, operation, or resolve kernel gains a library policy branch.

Preparing an installed package for rendering

Core's local helper prepares browser bytes from an explicitly selected installed Node project and an existing generated contract/lock:

node scripts/prepare-view-library-runtime.mjs \
  --assets /path/to/model/assets \
  --project /path/to/installed/node/project \
  --reference 'react:react-day-picker@10.0' \
  --styles '["react-day-picker/style.css"]'

The helper does not install packages, contact a registry, change dependency locks, or parse Grid source. It checks the selected installed package name and exact version, bundles its browser code with React externalized to Grid's existing instances, and retains CSS and resource files. --providers accepts a JSON object mapping exported provider names to their literal props. Providers wrap the view once; library styles are scoped to that library's rendered nodes.

Runtime bytes are written to immutable content-addressed paths. The existing library lock receives the new closed manifest and its digest only after all bytes are retained. Recompile the Grid source to admit that artifact. A concurrent lock edit refuses the lock replacement and retains the new artifact for inspection. Browser-incompatible resources or bundle warnings require an explicit adapter rather than a partially working package.

The browser compares all five compiled pins with the host response, checks the canonical manifest digest, and verifies each file before loading the factory. The workbench imports verified bytes only under its trusted-code policy. A published app copies those exact files into its own sandbox and uses content-addressed module URLs. Neither target imports a URL supplied by the view document. Each library shares the existing React runtime and is released when its final open view closes. Ordinary views with no imported library do not load the library adapter or fetch any library assets.

This helper is an artifact-preparation API for the independent CLI's library workflow; it does not register a grid library add command in Core.

Source-authored view libraries

A .grid asset can share elements, named actions, and presentation policies:

# ui/labels.grid
ELEMENT Label(value)
  show value
END
 
STYLE <css>.label { font-weight: 600; }</css>
USE "ui/labels.grid" EXPOSING (Label AS Summary)
 
SURFACE Desk
  Summary "Ready"
END

USE "ui/labels.grid" AS Labels exposes Labels.Label instead. A library's relative imports resolve from its own directory. Only names declared by that library are exports; imported helpers remain private unless wrapped by a new element or named action. An element may call a named action supplied by its using surface. The compiler reports a missing action rather than generating a callback that does nothing.

A view library accepts ELEMENT, named actions, PRESENT, USE, and at most one SCRIPT and one STYLE. It cannot declare model bindings or a SURFACE. Importing the library therefore adds no model statements. Its JavaScript stays in a separate module, with exports visible only to expressions authored in that library. Local names and element parameters cross into glue through explicit compiler-generated aliases. Its stylesheet is scoped to the library's rendered subtrees beneath the surface's stylesheet layer. Unused libraries do not emit script modules or stylesheets.

Source imports are bounded to 64 levels, 256 files, 2 MiB per file, and 32 MiB per compilation. A library SCRIPT or STYLE is at most 256 KiB. Cycles, ambiguous aliases, missing exports, invalid library declarations, and changed React contracts produce compile diagnostics. Generated React contracts use the same import machinery; EXPOSING (Select AS Chooser) keeps the pinned native export Select while exposing Chooser to the model.

Imported source is retained separately from the caller. During view compilation, a bounded temporary source pool lets the existing expression parser lower every expression into the same arena. Ordinary diagnostics point to the caller's USE, while view source spans retain definitionSource: {reference, span} for the original library location. Caller arguments keep their own source locations. No JavaScript or second Grid evaluator interprets model expressions.

The import source pool, lexical scope maps, and shared import budget are allocated only after a view library import is selected. Models with no such import retain empty metadata collections; they do not read a library lock or allocate the source pool. All additional work occurs during compilation and view admission, with no changes to per-cell evaluation, scheduling, or resolve kernels.

Deriving a contract from installed declarations

derive-view-library.mjs is the selected authoring operation behind the independent CLI's library add. It reads the installed package's TypeScript 5 declarations and its exact npm lock identity; it does not install dependencies, contact a registry, execute package JavaScript, or parse Grid formulas.

node scripts/derive-view-library.mjs \
  --assets /path/to/model/assets \
  --project /path/to/installed/node/project \
  --reference 'react:widgets@2.1' \
  --exports Picker

The authoring project needs TypeScript 5 and, for runtime preparation, esbuild. The request may use the installed exact version or a matching major/minor prefix; the lock always records the exact installed version. npm projects use the matching package-lock.json package-manager integrity. Other package managers can supply their existing SRI with --integrity; this pin identifies the selected dependency, while generated source and browser bytes receive separate digests of the bytes actually retained.

A default component export is named Default in the generated contract; use EXPOSING (Default AS Chooser) to give it a local name. The prepared browser factory preserves that exact mapping.

Each prop appears in the coverage report as mapped, deliberately dropped, or requiring an adapter, with the derivation rule and reason. Controlled pairs, serializable event arguments, slots, string subjects, questions, flags, enums, and ordinary data props produce Grid declarations. DOM event objects, refs, implementation styling, unresolved types, scoped render slots and unresolved discriminated variants receive explicit coverage outcomes. A required unsupported prop marks the component tier 2. Hook-only packages require an adapter. The report is part of authoring feedback; it is not an assertion that every third-party component works without an adapter.

Contracts and coverage reports use immutable content-addressed asset paths. Re-importing a changed contract retains the old source for comparison and clears any previous runtime admission. Runtime preparation follows separately, then the model is recompiled to bind its new source and runtime pins.