Workbook surfaces

Workbook Surfaces

Workbook Surfaces

Surfaces are workbook tabs that turn model data into a purpose-built interface. A sheet is the most familiar surface: it shows cells. Other surfaces show the same model as a table, map, chart, board, document, app, or custom React view.

The interface-layers development preview also presents these same surfaces in App, with routes to their source in Code and their values in Data. A layer selects how you work with a model; a surface selects a view of its data. Documents and notebooks retain their own editing and execution capabilities. The interface layers guide describes this candidate experience and its current limits.

The Pro Research Workspace is deliberately different: it is one top-level, model-scoped workspace with five research workflows, not another persisted workbook surface tab. Experiments, Molecule, and Document surfaces can open it with bounded context, while the workspace retains one shared model research state. See Research Workspace.

Why Surfaces Exist

Models often need more than a grid. A sales pipeline may want an Airtable-like table and a Monday-style board. A location model may want a map. A forecast may want charts, controls, and a polished dashboard for people who should not have to inspect every formula.

Surfaces let those interfaces live inside the model instead of beside it. The spreadsheet remains the source of truth for formulas, inputs, and calculations; surfaces give that same state a richer user experience.

The UI Model

A workbook has a tab strip. Some tabs are sheets, and some tabs are surfaces. Both are backed by model state.

For a sheet, the tab name is a namespace and the visible items are cell addresses:

Sheet1!A1 = 100
Sheet1!B1 = A1 * 1.2

For a surface, the tab name is also a namespace, but the visible items are named slots:

Map_1!type = "map"
 
Map_1!config = <toml>
  version = 1
  kind = "map"
  title = "Stores"
</toml>
 
Map_1!data = ""

The important slots are:

Slot Purpose
!type Selects the surface family, such as table, map, or component.
!config Stores the surface settings: bindings, layout, field names, titles, and options.
!data Stores surface-owned interactive state, such as board cards, app-builder trees, diagram positions, or layout tiles.
!source Stores editable JSX source for custom or ejected surfaces.

New configurations use explicit language blocks. The standard templates use <toml>…</toml>; <yaml>…</yaml> and <json>…</json> can express the same configuration object. For example, the Map configuration above can also be written as:

Map_1!config = <yaml>
  version: 1
  kind: map
  title: Stores
</yaml>

The language tag identifies the data format. The surface still checks the configuration fields against its own schema. New persisted document seeds use <json>, and authored Component source uses <jsx>. Ordinary labels and empty optional slots remain strings.

Saving a configuration retains its TOML, YAML, or JSON encoding. YAML setting edits preserve comments on unchanged content; renaming and duplicating a surface retain the block format. Older models with plain TOML strings continue to work and use typed TOML on their next configuration save.

For embedded code, <jsx> and <html> preserve source verbatim. JSX braces retain their JavaScript meaning. Quoted markup can instead interpolate Grid expressions, so the formatter keeps that spelling when interpolation is used. See the literal reference for delimiter rules and the distinction between source text and DOM values.

The UI reads those slots and chooses the matching renderer. A surface can be native, custom, or ejected:

Mode Meaning
Native A built-in Grid interface renders the surface from !config and !data.
Authored A Component surface renders author-written JSX from !source.
Ejected A built-in surface generates JSX, stores it in !source, and then renders through the custom UI path.

This is the foundation for Grid's application layer. Built-in surfaces provide familiar app-like views over model data. Custom surfaces use the same tab, binding, and persistence model, so a workbook can grow from spreadsheet to application without moving state into a separate front-end project.

Grid Apps

Grid Apps are the application layer that sits on top of the reactive spreadsheet model. The model remains the state/program; App Mode is the end-user runtime; Dev Mode is the workbench for inspecting and changing the model behind it.

Surfaces can run in two product contexts:

Mode Meaning
Dev Mode The normal Grid workbench: sheets, source editing, inspectors, surface configuration, and model debugging.
App Mode A takeover runtime shell for people using the model as an application.

In the interface-layers candidate, the existing workbench is named Data, Code groups source and surface authoring, and Chat expands the agent conversation. App remains the model's presentation. The old gridMode=dev URL and model.enterDevMode() bridge action continue to return to Data. This terminology change does not replace surface identities or change their model bindings.

App Mode is not a separate model format. The spreadsheet model remains the reactive state engine, while the active Surface or packaged model UI is the presentation layer. A promoted Surface can become an app entry point, navigate to sibling surfaces, and push browser history so Back/Forward behaves like app navigation. A model can also carry a portable ui/ package inside its .gridoc; when present, Grid can render that package as the model-owned app entry instead of the workbench. Packaged apps may declare a homePath in ui/manifest.json so the model's default App Mode launch opens a logical app route such as dist/dashboard while the physical HTML entry document remains dist/index.html. Explicit deep links still use ?gridMode=app&appPath=<path> and win over the declared home route. They may also declare an app block in the same manifest: app name, short name, description, package-relative icon, safe theme hints, and shell chrome. That is the piece that lets a model opened from the Models page behave like a custom application. Generated App Builder packages publish frameless app chrome by default, with a small floating Dev Mode affordance and the keyboard escape chord preserving the path back to the workbench. Generated App Builder packages also write dist/app-meta.json, a package-local metadata sidecar that describes the app as an app: pages, home route, declared model reads and writes, requested connector capabilities, component families, layout primitives, conditions, commands, row-scope usage, and higher-level features such as navigation, responsive layout, record views, filters, charts, feedback, model writes, and the app-framework readiness state. It also records detected app patterns such as routed app, dashboard, records workflow, input workflow, or static screen. The sidecar is not the launch manifest; it is the inspection contract that lets Builder tools, custom UI tooling, audits, and future app catalogs understand the UI framework sitting on top of the reactive model without parsing generated JavaScript. They also include dist/GRID_APP.md, a short package-local handoff guide for authors replacing or extending the generated UI. It lists the app home, pages, read/write model contract, features, connector module, and state helper in the same ui/ package that Grid serves. Its starter snippet is generated from the app's declared reads and writes, so custom UI starts from the actual model contract rather than a blank template. Generated packages also include a custom-ui/ handoff project. Its custom-ui/manifest.json declares the custom project contract, custom-ui/README.md explains the replacement flow, custom-ui/index.html is the replacement entry path, custom-ui/main.jsx is a React entry over the generated React adapter, custom-ui/vue-main.js is a Vue entry over the generated Vue adapter, and custom-ui/grid-app.d.ts re-exports generated app-specific symbol/type helpers. The default app still launches from dist/index.html; the custom project is the explicit handoff folder for replacing or building over that generated entry. Generated packages also include dist/custom-app-starter.js, a runnable plain-JavaScript starter over dist/grid-app.js and the app's declared symbols. It is not the default entry; it is a concrete replacement starting point for authors who want to take over app.js. They also include the stable framework interface modules dist/grid-app.js, dist/grid-react.js, and dist/grid-vue.js, with matching declaration files. grid-app.js exports the generated app contract, symbol constants, write guards, and action helpers such as setAppValue, setAppBatch, appendAppRow, routeApp, runAppJob, and callAppConnector. grid-react.js exports a React useGridApp hook over that contract; grid-vue.js exports the Vue useGridApp composable. The dist/custom-react-starter.jsx and dist/custom-vue-composable.js files are now examples that consume those adapters rather than being the interface custom code has to copy. Generated packages also emit dist/grid-connector.js, an ES module exporting the same grid connector object used by the generated app renderer, plus dist/grid-connector.d.ts with the connector's TypeScript shape. Custom React, Vue, or hand-written UI inside ui/ can import that module instead of re-implementing the grid:model-ui:v1 postMessage protocol, so generated and custom apps sit on the same typed reactive model connector. The module also exports createGridStore(symbols), a framework-neutral reactive store over grid.subscribe. React can adapt it through useSyncExternalStore, Vue can subscribe to it from a composable, and plain JavaScript can listen directly, while all state still lives in the spreadsheet model. For app code that wants fewer moving parts, createGridState(symbols) wraps that store with value, rows, set, setBatch, appendRow, setFormula, location, onLocation, route, runJob, callConnector, dev-mode escape, and lifecycle helpers. The generated App renderer uses that same app-state helper internally, so the visual Builder path and fully custom UI path share the same reactive state adapter, including optimistic local updates for set, setBatch, and appendRow. Generated and starter apps also handle Shift/Cmd/Ctrl+Escape inside the app frame by calling model.enterDevMode(), so takeover UI retains a predictable return path even when keyboard focus is inside custom UI.

When a model has both a packaged ui/ app and a promoted Surface entry, the packaged app is the default model-owned app entry. Explicit Surface URLs still win: ?gridMode=app&surface=Table_1 opens that Surface takeover even when the model also has ui/. If an explicit Surface target is missing, Grid falls back to Dev Mode rather than silently opening the packaged app.

The workbench is always recoverable. Older app shells expose a Dev mode control; the interface-layers candidate exposes Data in its layer selector. Explicit URLs can force ?gridMode=dev, and the escape chord Shift+Esc / Cmd+Esc / Ctrl+Esc returns the user to the workbench. For packaged custom UI, Grid injects the iframe-side escape listener when serving HTML so the shortcut still works even if focus is inside arbitrary app code.

Built-In Surfaces

Built-in surfaces are ready-made interfaces for common model workflows. They are designed for direct manipulation: filter a table, edit a board card, inspect a map layer, shape a dataset, or arrange a dashboard without leaving the workbook.

Current built-in surface families include:

Surface What It Is For
Predict Training, registering, and calling a model-backed prediction function.
Dataset Importing, previewing, shaping, and preparing tabular data.
Table Airtable-style browsing and editing over row data.
Map Spatial layers, location data, and geography-heavy models.
Calendar Event and schedule views over date/time rows.
Charts Dense analytical charts for model outputs.
Visuals Presentation-oriented SVG charts and dashboards.
Candlestick Financial OHLC charts with overlays and realtime preview.
Document Narrative, reports, notes, and embedded model context.
Notebook Reactive computational narratives with Grid code, live values, inputs, CLI tools, and provenance.
Board Freeform planning boards with cards, notes, and embeds.
Layout Dense dashboard tiles for values, formulas, and notes.
Kanban Workflow boards backed by rows or surface-owned cards.
Diagram Node-link diagrams for networks, flows, and relationships.
Sketch Parametric 2-D engineering geometry, constraints, dimensions, and CAD export.
Analysis Finite-element fields, convergence, mesh previews, and saved probes.
Requirements ReqIF requirements, traceability, verification records, baselines, and change impact.
Engineering Inspection of normalized IFC, FMU, Gerber, and Excellon artifacts.
Browser Web navigation in an isolated native session with addressable DOM and Web Storage state.
Industrial Asset, telemetry, alarm, work-order, and maintenance supervision.
Sequence Sequence, read, alignment, and sequence-index artifact workflows.
Variant Region-based variant inspection and explicit variant-calling workflows.
Molecule Chemical graphs, structures, fingerprints, docking, and trajectory workflows.
Canvas Computed drawings or numerical pixel images bound to a model value.
Experiments Simulation matrices, run comparison, trajectories, calibration, and evidence.
Discussion Comment, forum, or chat-style views over discussion rows.

These are the surfaces to reach for when the model fits a familiar product shape: tables, boards, dashboards, documents, maps, calendars, and charts.

Predict

The Predict surface owns the lifecycle of a model-backed prediction workflow. It lets a user choose a learner, pick feature data from a range or file, train or register a model artifact, and expose a callable prediction function back to the workbook.

Use it when a spreadsheet model needs a prediction step but the user should not manage training files and function wiring by hand. The surface keeps the model name, learner family, feature source, target column, deployment status, exposed function name, and last metrics together in one tab.

Training requires finite numeric features; missing or categorical feature values must be prepared explicitly. K-Means and PCA use every selected feature column and do not remove a target. A truncated file preview cannot start training: the selected native Dataset/Train development path captures complete input. The quick, balanced and thorough budget profiles default to 10, 60 and 300 seconds; host limits may lower them. The selected Train view reads and shows the resolved host limits.

The development suite separates Train, inference-focused Predict, Evaluate and Process. Those new execution paths are not yet available in the standard build; existing Predict workbooks retain their training interpretation and authored JSX. The explicitly selected development suite has retained-data prediction, snapshot training, mapped few-shot prompt adaptation and case evaluation. It retains row membership, reports known training overlap, and supports bounded pure Grid checks against output_value and case_data. Prompt adaptation does not change model weights. The development Process surface uses the same compiled-plan owner and model/human controls as Agent → Tasks → Processes. Opening the surface performs no execution. Source editing remains in Code; Run and Print expose observation only. The earlier prototype's visual editing, bundled AI workflow adapters and Process-case evaluation are not enabled on this canonical owner yet. Their source is archived for porting. Ordinary saved-result evaluation makes no model calls; fresh inference evaluation supports explicitly selected rubric judges and retained manual ratings. Retained output publication uses the receipt-backed native input owner when that capability is selected. The balanced V2 release and full native/UI and Performance evidence remain unfinished.

XGBoost regression additionally offers Calibrate an automatic upper P90 bound. This selects a seeded held-out calibration, not a full predictive distribution. Automatic mode may still produce a point-only model with an unavailability reason. The deployment view exposes a bound formula only for an admitted exact registry version; bind a worksheet feature range to copy it. Follow Train and Use Calibrated Predictions for data requirements, candidate-versus-pinned versions, external artifacts, and failure handling. Training requests also support required calibration, temporal splits, and explicit probability levels.

Train

Use Train to prepare a model from selected dataset rows, review the chosen learner and resolved time budget, and start training explicitly. Keep the result's exact model version for evaluation before pinning it for prediction. This development surface requires the selected AI suite capability. Opening a tab does not start training; legacy Predict tabs retain their saved behavior.

Evaluate

Use Evaluate to compare retained outputs or explicitly run selected cases. Choose the subject, case snapshot and checks, then inspect outcomes and known training overlap. Saved-result evaluation makes no model calls; fresh inference and rubric judges require explicit selection. Process-case evaluation is not available on the canonical Process owner yet.

Process

Use Process to inspect a compiled team workflow, choose model settings and run limits, and start it explicitly. Replies and human decisions remain associated with their run. Edit source in Code; opening the surface does not execute it. Sequence/diagram visual editing and bundled workflow adapters remain unfinished. See the human review walkthrough.

View

Use View for an interface declared in model source: pages, local state, reusable elements and actions. Edit it in Code and inspect it against the saved model or an explicit private preview. See Grid Views. Train, Evaluate and Process retain their native operation owners and are not admitted as embedded native View controls.

Dataset

The Dataset surface is for bringing tabular data into model shape. It can preview data from model values, files, or configured sources; show schema information; and apply a pipeline of transforms such as filter, select, rename, derive, sort, limit, dedupe, and aggregate.

Use it when raw data needs cleaning before it feeds formulas, tables, charts, or custom apps. Dataset is the surface that turns "we have a CSV or row array" into "we have a model-ready source."

In the preview-and-steps editor, results open in pages of 20 loaded rows. Use First, Previous, Next, Last, or enter a page number and press Enter to move through the result. Escape cancels an unfinished page number. Renaming the source or editing the output target keeps your page; a smaller result moves you to its last available page. For very large results, the first page appears before column type hints finish checking all loaded rows. Those hints still describe the full result once the check completes. CSV and Publish use the full prepared result, regardless of the page on screen. Browser text search covers the visible page. The printed preview retains its existing first-200-row limit. Native preparation has its own result controls.

Table

The Table surface is the Airtable-like record view. It presents rows and fields with density, column, schema, and browsing controls that feel natural for record work rather than cell work.

Use it for operational data: customers, orders, tasks, inventory, leads, assets, or any other row set where users think in records. A Table can bind to a named row source or model-local table handle, infer fields when needed, and provide a familiar tabular interface over the live model.

Map

The Map surface renders spatial data as layers. A layer can bind to rows with latitude/longitude fields or geometry fields, choose visual style, and display labels or identifiers.

Use it for territory plans, store locations, delivery zones, facilities, incidents, assets, or any model where geography is part of the answer. The Map surface gives spatial model state a visual inspection layer rather than forcing users to read coordinate columns.

Calendar

The Calendar surface turns date/time rows into month, week, day, or agenda views. It maps event fields such as start, end, title, and color.

Use it for schedules, launches, staffing, bookings, deadlines, content plans, and project timelines. Calendar is best when the model output is naturally understood as time blocks rather than rows.

When bound to named date rows, the Calendar is editable in place: drag an event to reschedule it, drag its edge to resize it in the week and day time grids (with half-hour snapping), quick-create by clicking an empty slot, or click an event to edit its mapped fields or delete its row after confirmation. Each edit writes back to the bound cells, so the model stays the source of truth.

Charts

When a chart pane is narrow, its configuration editor moves below the plot. Scroll within the chart to reach the editor, or choose Hide editor to focus on the plotted results. This responds to the pane's available width, including space occupied by the source and inspector panes.

If chart initialization, an option update, or a resize throws an error, the chart shows the failed phase, expandable details, and Retry chart rendering. Retry recreates that chart instance. A renderer failure is separate from loading data, an invalid binding, or an empty result. An error inside deferred third-party drawing code may still require reopening the view; this handling does not detect every possible blank canvas.

Select a loaded worksheet rectangle or one named array, then choose Insert → Plot selection, the cell context menu, or Plot selection in the command palette. Review the detected columns and eight-row preview, choose finite numeric X and Y fields, and label the axes. A worksheet header row can supply labels; the saved binding excludes that row. Units are display labels only: convert values in the model when conversion is needed.

Observations creates points; Curve joins points in source order with straight segments. This does not perform a fit. The result is an ordinary Charts tab linked to the original data, with explicit worksheet qualification. The preview accepts at most 16,384 scalar values and 32 columns; unloaded values, errors, numeric text, and missing X/Y observations require correction. A single vector can instead use the existing Named X and Y vectors editor.

For line and area series, Interpolation selects Straight segments or Smoothed curve. TOML uses smooth = false or smooth = true; omitting it preserves existing smoothed charts. Plot selection writes smooth = false. Smoothing is visual interpolation, not an estimated physical model.

Live Charts subscribe to the cells and named arrays actually bound by their series, including qualified Default and quoted worksheet names. Range bindings are limited to 65,536 aggregate cells across the chart; unsafe or larger ranges show a size explanation before subscription or resolution. Reduce the bound range or compute an explicit summary in the model. This chart binding limit is separate from the smaller Plot selection preview limit. Use the exact name for a binding whose data has not loaded yet. Case-insensitive matching of a differently spelled named array uses already-loaded names when the chart opens; reopen or rebind if that name only becomes available later.

The Charts surface is for dense analytical charting over model outputs. It supports familiar series types such as line, bar, area, pie, and scatter, with field mappings for x, y, label, and color.

Use it for exploratory analysis, reports, and dashboards where the important question is "what changed, how much, and compared to what?" Charts is tuned for data-heavy analytical views.

New unbound charts show Choose data to plot. In the editor, Preview example data explicitly opens a labeled demonstration; it does not save sample values into the model. Run and print views do not automatically show examples. Bound charts identify each series' source, distinguish unavailable, empty, calculating, and failed data, and never replace a failed binding with sample data. View plotted data exposes up to the first 100 source rows per series on demand; inspect the bound symbol or range for the complete dataset.

In the chart editor, Data source → Named X and Y vectors pairs existing numeric vectors without creating model formulas. Select an X and a Y for each series; add another series to use the same X with another Y. Row and column vectors are accepted. Both must contain finite numbers, have equal lengths, and contain at most 65,536 points. Matrices, blanks, and numeric text produce an explanation rather than a truncated or coerced plot. Numeric X coordinates retain their spacing. Source captions and the data table identify both names.

The optional TOML keys are x_bind and y_bind, replacing bind for that series. For example:

[[charts]]
type = "line"
x_bind = "Rates"
y_bind = "OutflowPressures"
label = "Outflow"

Do not combine a table/range bind with vector bindings in one series. The editor writes ordinary surface configuration. Ejecting to a Component retains both references, shape checks, and numeric X spacing. Reducing large vectors remains an explicit calculation in the model. Chart axis titles use bounded horizontal space above and below the plot in narrow and print layouts.

Visuals

The Visuals surface is the presentation-oriented sibling of Charts. It focuses on polished SVG visuals, palettes, legends, grid options, grouped bars, curves, donuts, and hover behavior.

Use it when the model needs a chart that feels ready to show: executive dashboards, narrative reports, embedded presentations, and cleaner visual summary tabs. Finished visuals can be exported as SVG or PNG for use outside the workbook. Export work runs only after the user chooses a format. SVG serializes the live presentation tree with explicit pixel dimensions; PNG rasterizes that tree at 2× on a white background. After either download, the surface shows the exact output digest and byte count plus format-specific preserved, transformed, approximated, omitted, and boundary claims. Neither format is represented as a model snapshot, accessible-data substitute, archival research package, or verified evidence artifact.

Candlestick

The Candlestick surface renders OHLC financial data with optional volume, overlays, indicators, and a live demo feed when no data source is bound.

Use it for market data, trading models, price simulations, and time-series analysis where open, high, low, close, and volume matter. It keeps chart settings, bound fields, realtime behavior, and overlays in one surface.

Document

The Document surface is for narrative around model state. It can show markdown or rich document content, bind to a live string body, and carry report-style themes.

Use it for memos, model explanations, operating notes, investment writeups, policy summaries, launch docs, and reports that need to sit next to the live calculation. Document makes the workbook readable to people who need context before cells.

In the App layer, Document keeps its editing tools and can receive an editable Chat excerpt through Keep in model. Its Insert menu can add links to other model views. App’s Brief starter creates a normal rich Document with chosen live references and ranges; these additions use the existing document editor, revision checks, recovery and Undo.

Import Word converts a local .docx file into editable document content, appending it without replacing existing writing. Headings, lists, tables, links, named styles, paragraph formatting, and supported embedded images are retained. Empty documents adopt imported page settings; appending keeps destination settings. Word pagination may differ. Imports can be canceled and undone. See the document editing guide for limits and compatibility.

Documents also provide named styles, paragraph formatting, Find/Replace, Heading 1–6 navigation, counts, zoom, and image width controls. Page setup controls exported paper, orientation, margins, headers, footers, and page numbers. Publish creates Word or PDF files and an exact PDF preview, freezing live values as text and tables. Unresolved values and incoming conflicts need attention before export. Unequal table widths, merged cells, repeating headers, numbering, and explicit page breaks survive supported Word round trips. Desktop exports use a native Save dialog; browser versions download files. The editor is a continuous writing view. Located notes identify conversion simplifications. Review adds anchored comments, replies, resolution, and tracked text edits with individual or bulk accept/reject and Undo. Discussion and supported text revisions survive reopening and Word exchange. PDF publishes proposed text without review markup. Formatting and structural changes are not tracked; reviewer names are labels, not authenticated collaboration identities. See the document editing guide, publishing walkthrough, and review walkthrough.

Deck

Create a Deck to present model values alongside slide text, tables, and charts. Browse slides in App, choose Edit slides to author them, and use Present for an audience view. Live references stay connected to the model; exported slides capture the values at export time. See the Deck guide for presentation controls, export options, and their limitations.

Notebook

The Notebook surface is a computational narrative over the resident Grid model. Its ordered blocks mix markdown, Grid formula assignments, live values and ranges, scenario inputs, WHY provenance traces, and commands from the interactive Grid CLI. Formula blocks do not start a second notebook kernel: submitting one edits the model source through the normal formula-write path, and its output then participates in the same reactive calculation graph as the rest of the workbook.

Use it for exploratory modeling, scenario labs, reproducible investigations, model validation, and handoffs where the reasoning matters alongside the answer. Live value and range blocks always resolve from model state. Command and provenance output can be persisted with the notebook so a printed or shared copy retains the evidence that produced the conclusion. Each execution stores a bounded receipt with its principal, status, duration, model revision, timestamp, and truncation state. Captured output is marked stale after the resident model advances. The backend reparses every request and enforces an effect allowlist: model-changing, administrative, raw-RPC, source-replacement, temporary-model evaluation, and streaming CLI commands are blocked inside notebook command blocks. Writes use typed input and formula blocks instead.

Formula writes are acknowledged and compare-and-swap guarded by the model source hash. A formula block durably owns its output symbol; another block or an existing source definition cannot be overwritten without an explicit Take over symbol action. Formula execution uses a dedicated Notebook route: the server derives the Notebook, block, and authenticated principal identities from the route and session, then injects ownership into the internal model-runtime request. The generic batch editor rejects client-supplied Notebook ownership metadata. Each formula attempt carries a persisted retry identity and returns the same principal-bound execution receipt when an ambiguous network retry reaches the same backend. Reusing that identity with different content is rejected as a conflict. Notebook document saves use the data symbol's request revision, serialize through a single queue, and preserve local edits when a remote revision wins. After a connection or server failure, Retry save resubmits the open draft with the same revision check. It does not override a conflicting revision; Load remote version is available when a newer notebook has arrived. Keep the notebook open until its status returns to saved. Production WHY runs are refused unless the runtime temporal ledger is enabled on durable storage.

Notebook is a dedicated surface rather than a mode inside Document because it owns execution and write semantics. It still follows the same surface lifecycle: native blocks live in Notebook_1!data, and Eject to code creates editable JSX. In the ejected snapshot, model reads and input controls remain live while native CLI and WHY results are labelled snapshots.

App’s Exploration starter creates a normal Notebook from selected inputs and results. Edit notebook exposes its writing and execution tools. Chat excerpts are appended as narrative and optional live-value blocks, never executable code. In a long notebook (80 or more blocks), the first block opens ready to edit and the rest remain readable. Click a block's content or use its Edit action to bring its editor and block controls into focus. The action appears on hover, keyboard focus, and touch. Shorter notebooks show their editors directly. Editing one block leaves unchanged blocks steady. Block ordering, keyboard movement, draft saving, and conflict handling retain their usual behavior. Initial opening of very large notebooks may still take longer than editing an open one.

Board

The Board surface is a freeform canvas for planning. It can hold text blocks, cards, embeds, and positioned items stored in the surface's data slot. It can also project bound card rows as read-only cards.

Use it for brainstorming, planning, model walkthroughs, lightweight dashboards, and mixed visual work where a grid is too rigid. Board is spatial and flexible: place the pieces where they help the story.

Layout

The Layout surface is a dense dashboard grid. It arranges value tiles and text tiles into a fixed set of rows and columns, with each block bound to a literal, a formula reference, or a note.

Use it for operating dashboards, KPI panels, trading-terminal-style views, scenario summaries, and compact executive screens. Layout is more structured than Board and more dashboard-like than Sheet.

New layouts start empty. In App, View actions → Edit layout reveals Add value, Add note, Undo, and Redo. A value can contain literal text or a reference such as =Forecast; the latter follows the model and its display format. Notes remain authored text. Older layouts retain their existing seeds and saved blocks.

Drag a block header to move it or its corner to resize. With a block focused, Alt+Arrow moves it and Alt+Shift+Arrow resizes it; the focused resize handle also accepts Arrow keys. Overlaps are rejected, Escape cancels a drag, and a full canvas asks you to make space. Adding a block focuses its title. Delete removes the focused block, and Undo restores it and its selection.

Done waits for the existing save path and returns focus to the selected block. Failed writes retain the draft; concurrent changes offer explicit recovery. App reading mode hides arrangement controls, keeps live values inspectable, and stacks blocks in row/column order on narrow windows. Arrangement keeps the authored grid and allows horizontal scrolling. Data retains its dense operational presentation.

Kanban

The Kanban surface is a workflow board. It can own cards directly in !data, or project cards from bound rows grouped by a status field. Columns carry titles, colors, and optional work-in-progress limits.

Use it for tasks, sales stages, recruiting pipelines, editorial workflows, incident response, model review queues, and any process that moves through states.

When cards are projected from bound rows, moving a card between columns writes the new status back to the model. Cards can be dragged with the pointer or moved by keyboard for accessibility, and bound cards can be edited through a modal. As with every surface, the bound cells remain the durable record.

Diagram

The Diagram surface is a node-link canvas. It can own editable nodes and edges or project them from bound node and edge row sources.

Use it for dependency maps, system diagrams, org relationships, process flows, network analysis, scenario graphs, and any model where relationships are easier to see as connected nodes than as rows.

Sketch

The Sketch surface is a parametric two-dimensional engineering canvas. It owns stable line, circle, and arc entities plus their geometric and dimensional constraints in !data. Dimensions can be driven by live model symbols, so a sheet edit regenerates the geometry; editing a bound input dimension in the inspector writes through the normal model input path.

Use it for profiles, plates, layouts, mechanisms, tolerance studies, design tables, manufacturing outlines, and spatial engineering models where geometry must stay connected to calculations. The native inspector reports degrees of freedom, constraint residuals, bounds, path length, and areas, and exports SVG or DXF as advisory browser previews, not manufacturing files. Named design configurations overlay unbound dimensions and expose an explicit native comparison of measured outputs; model-bound dimensions keep external ownership and cannot be shadowed by a configuration.

When the active solid result supplies mass properties, enter a target such as MassSnapshot under Publish mass properties to cell, then choose Publish properties. Query the saved result with ordinary formulas:

solid_volume = CAD_VOLUME(MassSnapshot)
solid_area = CAD_SURFACE_AREA(MassSnapshot)
solid_center = CAD_CENTER_OF_MASS(MassSnapshot)

This publishes a persisted, revision-pinned cad_mass_properties_snapshot; it does not keep a process-local solid handle or automatically republish on later Sketch edits. After changing the design, inspect the newly computed properties and publish again deliberately. CAD_MASS_PROPERTIES validates a snapshot, and the query functions accept an optional expected source-revision string when the consuming calculation must require an exact revision. Volume and area carry dimensions; center of mass is a dimensional three-coordinate vector. Unavailable or uncertifiable area/center results return #N/A, not a fabricated zero.

Experimental source-owned CAD editor

The source pane's CAD tab is separate from the Sketch canvas. It provides an explicitly selected, experimental workflow for a reusable millimeter rectangular pad: author an imported part, edit its depth input or width argument, regenerate and save, reopen its saved checkpoint, measure, and download revision-matched STEP. This is not general programmable CAD support or a claim of manufacturing validation.

Load saved source deliberately. Use the library editor for the reusable part, then Regenerate and save source for the root model. Measurements and STEP downloads use the confirmed saved revision and are unavailable while drafts are unsaved. A conflicting or unconfirmed edit requires reloading rather than assuming the save succeeded. After a restart, explicitly reopen the saved checkpoint; an already-live reopen refusal is not a successful reopen.

The checkpoint is not a new portable Grid-document archive. Refreshing a pinned import is not available in this editor. Its drafts are local: switching models or closing the workspace does not preserve them. Copy unfinished work before leaving. Source-view tab changes ask before discarding a draft or active operation. Other geometry operations retain their individual support limits.

Analysis

Use Analysis to inspect finite-element regions already authored in model source. Add the surface, choose Resolve regions, then select a region, field, and component. Inspect convergence/status and units before interpreting the color plot; the field table and mesh are bounded previews, not proof that every entity is visible.

Add a probe by entity index, or select a displayed node for a node field. Region, field/component selection, and bounded probes are saved with the tab. Change mesh, materials, loads, and boundary conditions in model source, then resolve again. The surface does not introduce a separate finite-element solver or make an unconverged result converged by displaying it.

Requirements

Open a saved model, add Requirements, and Import ReqIF from a bounded ReqIF 1.2/1.2.1 document or a single-document ReqIFZ archive. Use Requirements, Traceability, and Verification views to inspect paged objects, typed attributes, hierarchy, links, and conflicting evidence. Select a requirement to record verification with its method, observation time, and evidence references when write access permits. Export ReqIF exports the normalized artifact, not arbitrary embedded archive content.

Under Baselines & change impact, name and publish a baseline of the current artifact. After importing or creating the changed state, enter the exact before and after artifact IDs and choose Publish change impact. Inspect changed identifiers and suspect links; retain the resulting artifact references for review. Baselines and structural comparisons do not confer approval, signoff, promotion, or release status. Those are separate authorized workflows.

The formal Requirements signoff workflow, when installed and configured, requires the exact baseline and complete matching impact report, reviewer submission, then a different approver's reason and trusted signature. An incomplete/truncated comparison cannot be used as complete signoff evidence. The resulting workflow record remains tied to those artifacts; subsequent changes do not inherit approval.

Engineering

Use Engineering to inspect imported engineering files without executing them. Choose the IFC, FMU, or PCB view, select the corresponding file, and import or inspect it. The tab retains the artifact reference and selected view, so reopening it reads the same normalized artifact rather than reparsing an untracked browser file.

IFC inspection exposes normalized building entities; FMU inspection exposes model metadata and variables without loading its executable code; PCB inspection handles the supported Gerber/Excellon input. Browser import ceilings are 256 MiB for IFC, 64 MiB for FMU, and 32 MiB for PCB, with bounded previews. This surface is not an IFC geometry editor, FMU simulation runner, electrical design-rule checker, router, CAM tool, or fabrication/machine controller.

Browser

Use Browser in Grid Desktop to open an HTTP or HTTPS page in an isolated native session. Enter a URL in the location bar, browse normally, then use the automation panel when model or agent work needs page state. A surface address such as dom:main h1 reads the first matching element, dom:all:a[href] returns a bounded list, and storage:local:theme reads one local-storage value for the current origin. page:url, page:title, page:html, and page:text address main-frame state.

Programmatic callers use the same surface variable to navigate, query, click, fill, focus, and read or update local/session storage. Prefer stable CSS selectors and semantic attributes over layout coordinates. DOM and storage results are snapshots at call time; read again after navigation or page script mutates the document.

Programmatic automation uses the same surface-scoped handle as the toolbar. It supports redirect- and hash-route-aware goto, settled back/forward/reload, bounded load/selector waits, CSS query/count, visibility and computed-style inspection, click/fill/focus/hover/keyboard, checkbox and select controls, scrolling, history, reload, storage mutation, and explicit session close. Element results include control state, visibility, and viewport geometry; no coordinate synthesis or arbitrary page-script evaluation is required. Selector actions wait up to five seconds for the element to become attached and actionable instead of silently returning false while a page is still rendering; callers can override that bound with timeoutMs, up to 30 seconds. DOM, page, computed-style, storage, and formula-snapshot results are byte-budgeted inside the page before crossing the native bridge; large queryAll results return the bounded document-order prefix rather than first allocating an unbounded payload. Direct page:html reads share the formula snapshot's live-control capture and password redaction. Selecting a missing option—or multiple distinct values on a single-select control—is an explicit error, not a silent empty selection.

State-changing calls on one Browser session are deliberately serial: await one navigation, wait, element action, or storage mutation before starting the next. An overlapping stateful call is rejected clearly instead of racing the live page, and the native host does not retain an unbounded queue of pending automation. Immediate queries remain point-in-time reads and can run alongside revision polling and window layout.

Cells read the Browser's captured developer-panel state through its data slot. For example, BROWSER_READ(Browser_1!data, "dom:main h1") returns the first matching DOM node, DOM_TEXT(...) extracts its text, BROWSER_QUERY_ALL(Browser_1!data, "a[href]") returns matching links, and BROWSER_STORAGE(Browser_1!data, "local", "theme") reads an exact storage key. Completed navigation and automation publish a bounded snapshot; use Capture for cells for an immediate manual refresh. While the Browser is visible, page-script DOM changes, form interaction, and same-document history changes are detected and republished after a short debounce. Web Storage is included—and its mutations are observed—in that persistent snapshot only when [browser] formula_storage = true; live storage remains queryable through the surface handle without enabling it.

The session is currently incognito and lasts only for the desktop process. It does not share Grid's signed-in WebView profile, and browsing state is not persisted as a browser profile. Up to eight Browser sessions retain their in-memory state while hidden. Opening another releases the least-recently-used hidden session unless it is running automation; revisiting a released surface starts a fresh isolated session at its configured URL, while the last captured cell snapshot remains available until the new session publishes a replacement. If the surface is still open in the workbench, it shows Reopen fresh session as soon as the native host releases its hidden WebView, rather than leaving its controls bound to a stale session. Deleting a Browser surface closes its session immediately, cancelling any call still using that deleted page. The workbook receives only the bounded formula snapshot (URL, title, ready state, and bounded HTML/text, plus explicitly admitted Web Storage). Password-control values are redacted from snapshots and DOM-query results. Cookies—including HttpOnly cookies—and history are intentionally excluded. Direct, redirected, clicked, and popup navigation all share the same credential-free HTTP(S) and normalized-URL size policy. Browser surfaces cannot run in the web client or print output; those contexts show a capability or static-content notice instead of silently substituting an iframe.

The explicit live QA harness in tools/qa/browser-surface/ can drive an already-built Desktop executable against a deterministic loopback page and preserve a no-overwrite result bound to that executable's SHA-256. Its checks cover page and CSS addresses, password redaction, DOM mutation, forms, local and session storage, captured HTML, history, frame boundaries, popup containment, the eight-session resident limit, least-recently-used eviction, and isolated recovery. No completed packaged-product run is recorded in this working tree; new product bytes require their own driven qualification.

Industrial

Bind the surface's assets, telemetry, alarms, work orders, and maintenance collections to normalized model rows or tables. Start with Overview, select an asset or alarm for detail, and use Refresh endpoint status to inspect declared connector health. Missing or unavailable status is not evidence that equipment is healthy.

Supervisory actions require a configured action and an explicitly authorized host bridge. The default workbook host does not grant those action bridges. When one is available, select its target, click the action, review its confirmation, then choose Confirm manual action. Refresh, telemetry changes, and timers never trigger it. Adding this surface does not install a control loop or grant industrial write authority.

Sequence

Use Sequence for resident references, reads, alignments, and related artifacts. Import browser text formats FASTA, FASTQ, SAM, or Matrix Market, or select a supported file already in the model's Files area. Browser imports are bounded to 60 MiB. Binary BAM and H5AD use the linked-asset path, not the text upload path; backend support is still required.

Select the imported artifact, inspect its identity/summary, then choose an operation available for that artifact kind. Examples include a sequence sketch, mapping index, reading-frame translation, open-reading-frame search, and alignment. Mapping reads requires the reference and its index as explicit dependencies. Follow the job status and select the resulting artifact; importing a file alone does not run every analysis.

Variant

Use Variant for a bounded genomic region and variant artifacts. Import VCF text (up to 60 MiB through the browser), or use supported linked VCF/BCF data. Select the exact artifact and set the contig and start/end coordinates for the region view. A bounded result page does not represent the entire cohort.

Call variants from alignments is an explicit operation: supply the reference-sequence and read-alignment artifacts, run it, and inspect the resulting artifact and job outcome. A VCF import does not infer missing reference provenance, run a caller automatically, or establish clinical interpretation.

Molecule

Use Molecule to inspect chemical graphs, structures, and trajectories. Import SMILES, SDF, MOL2, PDB, or mmCIF text, or select a supported model asset; browser text imports are bounded to 60 MiB. Select the artifact and, where applicable, the structure model or trajectory frame.

Operations depend on artifact kind: molecule sets offer fingerprints, conformers, and ligand docking; structures offer dynamics preparation or receptor docking; fingerprint sets offer comparison/screening; prepared dynamics systems can run dynamics; trajectories can be analyzed. Choose the required receptor, ligand, or comparison artifact explicitly and follow the job result. Availability depends on the installed scientific capability; a viewer or artifact row alone does not establish backend availability or scientific validity. Research Workspace is a separate context-aware workspace, not an automatic side effect of these operations.

Molecule sets and structures also offer Calculate calibrated properties. Choose the RDKit descriptor set, Crippen log P, or fragment polar surface area explicitly. The interface checks property-adapter registration on selection; the submitted job confirms method support and runtime permission. Saved results show units, missing descriptors, warnings, backend/version, and execution provenance, with 50 molecules per page and full JSON download. Reopening results does not rerun the adapter. See the computational chemistry guide.

Canvas

Use Canvas to display a computed drawing or image, rather than manually edit a Sketch. Set source.bind to a cell holding a DRAW_* drawing, a supported DRAW_TO_SVG result, or numerical pixels produced by DRAW_RENDER/IMAGE_*. The view refreshes when the bound value changes.

For numerical pixels, pixels.mode = "gray" reads grayscale intensities and "rgb" reads packed RGB colors. layout.fit chooses "contain" or "actual"; show_checkerboard controls the background. Nothing to render means the binding is missing or its value is not a supported drawing/pixel shape. Correct the producing formula or binding instead of pasting arbitrary HTML into the canvas.

Experiments

Use Experiments to compare simulation candidates without editing the live model's inputs for every trial. In Design, name the experiment, select Time simulation, Discrete event, or Spatial agents, choose outputs, and supply the relevant step/integrator or region settings. Time-simulation sweeps use a full-factorial product of explicit values or linear ranges and scenarios; other design-of-experiments algorithms are not implied. Review the expanded matrix size before running it.

In Runs & comparison, follow the queue, inspect failures and returned summaries, compare runs without rerunning them, and Inspect trajectory for verified replay of a completed, successful time-simulation row. Active/failed rows, discrete-event/spatial-agent rows, and older records without complete replay evidence do not support this view. Publish evidence creates a pinned results artifact; it does not approve or release a model. Graduate creates a model branch from a selected run using the explicit target model ID, not a software release or silent replacement of the current model.

In Calibration, select a loss symbol, bounded parameters, tolerance, and maximum iterations. Run calibration returns the final optimum and counts. Run & publish evidence deliberately reruns the optimization and retains bounded, hash-complete evaluation evidence; it does not merely attach evidence to the prior result. The current synchronous calibration operation exposes no iteration progress, cancellation, or residual vectors. This simulation-fitting workflow is distinct from Predict's calibrated upper-P90 model bounds. See simulation authoring for model and replay semantics.

Discussion

The Discussion surface renders chat or forum-style conversation from bound rows or local preview messages. It maps author, body, timestamp, thread, and reply fields when present.

Use it for model review, operational notes, comment streams, decision logs, and collaborative context. Discussion keeps conversation close to the model state it is about.

Custom UI Surfaces

Custom surfaces are for model-specific applications. They let a workbook carry its own UI layer: forms, dashboards, workflow tools, calculators, decision screens, or embedded mini-apps that read from and write to the model.

There are two custom UI paths:

Surface What It Gives You
App A structured UI builder for composing model-bound screens from reusable blocks.
Component An authored React/JSX surface for custom interfaces written directly in the model.

App surfaces are the lower-code path. They are useful when you want to assemble a screen from standard UI blocks, bind those blocks to model values, and keep the interface editable by model authors.

Component surfaces are the code-first path. They are useful when the surface needs custom layout, custom interaction, or React ecosystem components. A Component can read live model values, render them through React, and write back to allowed model symbols.

Custom UI availability is a deployment trust decision expressed as one setting: VITE_GRID_CUSTOM_UI_TRUST = off | trusted | sandboxed-only. off disables every custom-UI runtime (including packaged app takeover); trusted enables them all — the single-author / trusted-team posture; sandboxed-only disables in-process JSX evaluation (Component surfaces and ejected built-ins) while keeping the App builder's no-eval interpreter and sandboxed packaged apps — the multi-tenant posture. When the policy is unset, the legacy VITE_GRID_COMPONENT_SURFACE / VITE_GRID_APP_SURFACE flags keep their existing behavior. Public embeds never evaluate in-process JSX regardless of the policy. Disabled runtimes keep their surface data; the tabs render an explanatory notice instead.

Packaged desktop builds default this policy to sandboxed-only. That makes the no-eval App builder and Publish Grid App available in the desktop product without trusting authored Component JSX. A desktop product build may still set an explicit supported policy when its deployment boundary calls for off or trusted; invalid values fail the build instead of silently disabling the builder through the legacy fallback.

One model-binding ABI

Every custom-UI runtime speaks the same verb set — the grid:model-ui:v1 model-binding ABI: read verbs (model.symbols, model.range, model.subscribe), write verbs (model.setInput, model.setBatchInput, model.setFormula), route/context verbs (app.location, app.setPath, app.setContext), and command verbs (job.run, connector.call). There are two transports: packaged apps speak it over postMessage through the host shell, and Component surfaces bind it in-process over the live symbol store. The generated package client derives its method table from the shared ABI module at publish time, and a conformance suite drives the packaged client and the reference client through identical wire sequences, so the runtimes cannot drift apart.

Grid Views also uses app.formatValues for bounded presentation of supplied scalar values with the native TEXT formatter. It accepts no model id, source or formula, reads no model state and grants no write capability. The same operation serves the workbench and private copies. Older hosts return a concrete unavailable-format notice; the renderer retains the original values for actions and numeric sorting.

In the App layer, model.setContext?.({ symbol, title?, description? }) in Component JSX, or await grid.setContext(...) in a packaged app, selects a model value in Grid's Context panel. null clears it. The host resolves the live value; the contribution cannot provide values or callbacks. Symbol, title, and description limits are 256, 160, and 1,200 characters respectively. Packaged contributions require model.read; an unavailable host returns an explicit connector error. Selection is scoped to the current model, view, and app route.

Capabilities are one schema everywhere: verb scopes (model.read, model.write, app.route, job.run, connector.call) plus per-symbol bindings.reads / bindings.writes. A Component surface declares them in its !config; a published package carries them in ui/manifest.json; the App builder's capability review renders them before publish. Writes are enforced at every execution point with the same guard: the Component runtime blocks undeclared writes in-process, and the packaged-app host shell rejects writes outside the manifest allowlist (write_denied) even when the coarse model.write capability was granted. The command verbs are capability-gated end to end; hosts that expose no job runner or connector bridge answer method_unavailable rather than pretending.

App

An App surface is a structured builder for custom model UI. It stores a tree of UI blocks in !data, mirrors declared reads and writes into !config, and can run in a preview mode against live model bindings. The App document is now an app-level structure, not only one component tree: it can contain pages, each with a title, package-relative path, and root component tree. The active page is mirrored into the legacy root field so older single-page App surfaces still render and publish.

Large App outlines use a scrolling viewport, with keyboard and range selection across the full expanded tree. The canvas and Preview continue to include every component. Initial loading avoids replaying an already loaded document; recovered drafts, later model updates and Undo/Redo retain their normal behavior.

Use App when the user wants a custom screen but still benefits from a visual composition model: metrics, inputs, cards, rows, lists, buttons, and repeated sections. The builder is direct-manipulation: drag blocks into place, multi-select and move or resize them, drop across pages, and grab and position blocks from the keyboard. App is also a stepping stone to code; when the user needs full custom behavior, an App can be ejected into a Component. The layout system is now part of that app model rather than incidental CSS. Alongside Stack, Row, Card, and Form, builders can use Section, Responsive grid, and Sidebar layout primitives. Sections provide page-level regions with title, subtitle, spacing, padding, and surface treatment; grids provide responsive dashboard/form/card regions with explicit or auto-fit columns; sidebar layouts provide a two-region workspace that collapses for narrower app frames. Layout presets in the palette seed dashboard grids, form sections, and record workspaces, and the same nodes render in builder preview, ejected JSX, and packaged ui/ apps. When published as a model-owned UI package, App buttons can also act as route buttons: action = "route" treats the button value as a package-relative appPath and updates the takeover URL without declaring a model write. The builder exposes the App document's pages as route targets so authors can wire navigation by choosing a page path instead of memorizing package URLs. Route buttons can optionally write a selected-state value before navigating. Inside a Repeater, that write can come from row scope (@order.id, @order.#index), which gives App Builder the standard master-detail app pattern: choose a record, commit that choice to the model, then open a detail page that reacts to the same state cell. Submit buttons are the batch-command sibling of route buttons. A submit action can write several model symbols at once, can take values from the current Repeater row (Target = @order.amount), and can optionally route after the writes. That gives visual App Builder screens a reusable command layer for review, approve, apply, save, and wizard-style workflows without moving state out of the spreadsheet model. Alerts are the feedback primitive for that same workflow layer. An Alert can bind its title or message to a model cell and use normal visibleWhen conditions, so validation errors, save results, approval status, or warnings can be calculated in the spreadsheet layer and shown in the app shell. The same condition system gates actions: disabledWhen can disable inputs, buttons, submits, or whole containers from model state or row scope, and that behavior is preserved in preview, ejected code, and packaged model UI. Published App packages declare a homePath from the selected home page, so opening the model in App Mode starts at a stable app route rather than the generated dist/index.html document. The generated package listens to Grid's app.location connector events and renders the page whose path matches the outer app route, while all reads/writes still flow through the model binding contract. The builder preview uses the same page-path rules, so route buttons can be tested before publishing. Ejecting an App to a Component also preserves the page table with a small generated router; the ejected code can navigate locally and still call the host model.setPath API when available.

This makes App Builder the visual framework layer for Grid Apps: pages define navigation, layout primitives define responsive regions, bindings define reactive state edges, actions define writes or route changes, and publishing turns the result into the model-owned ui/ package. Component surfaces remain the escape hatch for custom code, but they sit on the same connector and app-mode runtime. The Builder left rail includes an App framework panel generated from that same contract, so authors can see the app home, pages, model reads and writes, commands, row scope, conditions, feedback, and record/chart/filter features while they build instead of discovering those relationships only after publish. That panel now treats App Builder as a small application framework map: App shell, Reactive state, Actions, Data views, and Custom UI each carry a status, signal, and recommended action from the same metadata written to the package. Empty or partial layers can launch the existing shell/form/records starters directly, keeping the authoring conversation centered on the app being assembled rather than only on the URL that will launch it. The same metadata now rolls those layers into an App readiness strip (draft, forming, usable, or publishable) with a ready-layer count and a single next action. That gives builders a product-level path through app composition instead of asking them to infer intent from component counts alone. It also records a layout summary: section count, responsive grid count, sidebar layout count, card/form count, responsive-region count, and max explicit grid columns. Generated GRID_APP.md includes the same summary so a custom React, Vue, or plain JavaScript handoff can see whether it is replacing a dashboard grid, a form flow, a record workspace, or a basic stack. The framework panel also includes a capability review before publish. It summarizes the app's current risk level and lists every scope the generated app does or does not use: model reads, model writes, app route changes, job runs, and connector calls. Read-only apps show as low risk, model writes show as medium risk, and write-then-route workflows are highlighted as higher risk. Inactive job and connector scopes are shown explicitly so builders can see that a packaged app is not silently running jobs or calling external connectors. The palette now starts with inferred App patterns from the live model: Model app, Dashboard, Records workflow, and Input workflow. These apply the same model-aware starter logic as the framework map, using row arrays, selection/search state, scalar inputs, and feedback cells to assemble higher-level app flows instead of isolated widgets. The starter palette also exposes named app templates: CRM, Approval workflow, Forecast dashboard, Intake form, Operational queue, and Scenario planner. Each template binds live row sources, selected/search state, scalar assumptions, status/approval cells, and feedback cells when the model exposes them; when a symbol is missing, the template keeps the region as an unbound App component so the builder can wire it later instead of failing generation. The palette also exposes layout presets that do not require a model symbol: Dashboard grid, Form sections, and Record workspace. They are reusable container patterns rather than generated launch plumbing, so authors can first shape the app screen and then bind model state into it. Actions are now described as first-class action plans in the Builder contract, not just as incidental button props. Button plans can set, increment, decrement, write formulas, append a row by replacing array/table state, validate against a model condition, branch to a success route, write feedback, or declare planned job/connector calls for publish review. Submit buttons are batch transactions: their write lines run together, can resolve row-scope values, can route after success, and can write feedback. Generated dist/app-meta.json records an actions summary plus per-action plans with kind, reads, writes, validation, route-after-success, feedback target, capability scopes, support status, risk, and evidence. The framework panel and GRID_APP.md render the same Action Builder section so custom UI handoff can preserve action intent instead of rediscovering behavior from generated code. The adjacent Appearance panel persists app-level mode, accent, density, and radius on the App document. Builder preview, read-only run mode, generated ui/ packages, and ejected Component JSX all consume that same theme contract, so app styling is a first-class framework layer instead of loose CSS drift. The App state panel is now a semantic binding browser over the current reactive model contract. For every declared read/write symbol it shows access (Read, Write, or Read/write), binding role (table schema, writable app state, action target, or derived value), live kind/preview, source lineage (Named state or sheet/table source), row schema, a small sample row when the model exposes one, validation hints, and suggested UI uses such as Table, Cards, Chart, Number input, Toggle, Alert, or Metric. Rows can jump back to the source model symbol through the existing selection bridge, making the spreadsheet state visible while the app is being assembled. Generated dist/app-meta.json also writes a static stateBindings map with access, role, component lineage, and suggested uses. GRID_APP.md includes the same Semantic Bindings section so a custom UI handoff can preserve the model-state intent even when it no longer has the live workbench samples. The same handoff guide includes Capability Review from dist/app-meta.json, including scope evidence, so custom UI authors can see what the Builder app was allowed to read, write, route, run, or call before replacing the generated entry point. The same panel surfaces the custom UI handoff files: GRID_APP.md, the custom-ui/ project folder, custom-ui/manifest.json, custom-ui/README.md, custom-ui/index.html, custom-ui/main.jsx, custom-ui/vue-main.js, custom-ui/grid-app.d.ts, grid-app.js, grid-app.d.ts, grid-react.js, grid-react.d.ts, grid-vue.js, grid-vue.d.ts, custom-app-starter.js, custom-react-starter.jsx, custom-vue-composable.js, grid-connector.js, grid-connector.d.ts, and the createGridState helper. The Navigation block is the first page-aware component in that framework: it renders the App document's pages as tabs, pills, or a list, marks the active page, and routes through the same app-path connector used by route buttons. The builder also offers an App Shell starter from the palette and Pages panel: it creates a routed Dashboard, Records, and Inputs page set from the model's live symbols, with Navigation already placed on each page and responsive sections/grids seeded for dashboards, forms, and records. When the model also has obvious selected-row state for the row source, the shell adds a Record detail page and seeds row cards with Open buttons that write the selected row before routing. Records workflows use the Sidebar layout when selection/detail state is available, keeping filters/detail beside the main table on wide frames and stacking naturally on smaller frames. When the model exposes obvious error, warning, status, result, or validation message cells, generated starters add Alerts and keep those cells out of editable input forms. It is meant to turn a blank App surface into a working multi-page application skeleton in one move. The Bar chart block gives dashboards a first visual primitive over row data: it binds to a row source, infers label/value columns by default, and can be inserted directly from the From model palette for a selected range. Tables can participate in the same app-state loop: a Table may bind a selected value to a model cell and optionally name the row column to write when the user clicks a row. The selected value is read back to highlight the active row, and published/ejected apps preserve that read/write contract. Tables may also bind filter to a text-like model cell; the table filters rows from that value while preserving source-row identity for selection. Authors can pair a Text input and Table on the same search cell, so search is still model state rather than private component state. The Detail block completes the basic master-detail pattern: it reads the same row source and selected value, finds the active row, and renders selected-row fields without requiring an author to build helper formulas first. The Records starter uses this convention when the model already exposes an obvious selection symbol such as SelectedOrder: it creates a selectable table and selected-record detail instead of leaving the author to wire the record workflow by hand. When it also sees an obvious search symbol such as SearchOrders, it adds the search input and binds the table filter automatically. The same workflow is available from the From model palette: choosing Records view on a row source inserts the searchable, selectable, master-detail block for that specific data source. Row sources can also become end-user card lists. Choosing Cards on a row source inserts a Repeater bound to that source; when the builder can see a sample row it seeds a card template with row-scope bindings such as @order.id and @order.amount, and otherwise falls back to automatic labelled cards at runtime. When the model exposes a matching search state such as SearchOrders, the generated Cards workflow includes a search input and binds the Repeater's filter slot to the same model cell. Scalar model state has a matching workflow primitive. Choosing Input form from a number, text, or boolean symbol inserts a Form whose children are model-bound inputs for the detected editable scalar state. Obvious record workflow state, such as SearchOrders and SelectedOrder, stays with the Records view instead of being pulled into the generic Inputs screen. Boolean state can also become reactive structure: choosing Conditional section on a boolean symbol inserts a Card whose visibility is driven by that model cell. This makes show/hide behavior another declarative edge in the App document rather than private component state. Text state can become a view state machine. Choosing Mode switcher on a string-like symbol such as Scenario, Status, or Mode inserts a bound segmented control plus conditional Cards for the inferred options, so an author can build scenario, status, or tab-style app flows from one model cell.

Component

A Component surface is authored JSX inside the workbook. It renders through React and receives a curated model API for reading and writing bound model state.

Use Component when the interface needs code-level control: conditional layouts, custom interactions, richer components, specialized visualizations, or a UI pattern from the React ecosystem. Component surfaces should declare the symbols they read and the symbols they are allowed to write.

One Surface Framework

Built-in and custom surfaces use the same model-facing shape. A surface is a namespace with named slots:

Map_1!type = "map"
 
Map_1!config = <toml>
  version = 1
  kind = "map"
  title = "Stores"
</toml>
 
Map_1!data = ""

The !type slot chooses the surface family. The !config slot stores the surface settings. The !data slot stores surface-owned state such as board cards, layout tiles, app-builder trees, or diagram positions. Some surfaces also use !source for JSX source.

This shared shape is what lets Grid unify built-in product surfaces with custom UI work:

  • A built-in Table, Chart, Map, or Document can start with a native Grid UI.
  • Ejectable built-ins can generate JSX source for customization.
  • A Component can use the same model bindings as an ejected built-in surface.
  • An App can be assembled visually, previewed, and ejected into a Component when the interface needs code-level control.

The result is a gradual path: start with a built-in surface, customize it when the model outgrows the default UI, and keep everything inside the workbook.

Model Bindings

Surfaces bind to model values by name. A Table might bind to Orders; a Chart might bind to MonthlyRevenue; a Map layer might bind to StoreLocations. Bound surfaces update when the model updates, so formulas, connector data, and surface views stay together.

Some surfaces are also editable: moving a Kanban card or dragging a Calendar event writes the change back to the bound cells through the model's normal input path. The model stays the source of truth — the surface is just a faster way to edit it.

Custom surfaces use the same idea. A Component can read a value:

function App() {
  const revenue = model.useValue("Revenue");
  return <ui.Metric label="Revenue" value={revenue} format="currency" />;
}
 
render(<App />);

It can also read rows:

function App() {
  const orders = model.useRows("Orders");
  return <ui.Table columns={orders.columns} rows={orders.rows} empty="No orders yet." />;
}
 
render(<App />);

Writes are explicit. Custom surfaces declare which symbols they can write, and Grid blocks writes outside that allowed set. This keeps a custom UI from accidentally editing unrelated parts of the model.

The custom UI model exposes these common reads and writes:

API Purpose
model.get("Name") Read a scalar once.
model.useValue("Name") Reactively read a scalar and rerender when it changes.
model.useValues(["A", "B"]) Reactively read several symbols.
model.useRows("Orders") Read row data with status, columns, and rows.
model.useRange("SomeRange") Read a range-like binding as rows.
model.set("Name", value) Write a value to an allowed symbol.
model.setFormula("Name", formula) Write a formula to an allowed symbol when formula writes are available.

The injected ui primitives provide standard controls and display pieces so a custom surface can look and behave like the rest of Grid without rebuilding basic widgets every time. Current primitives include ui.Stack, ui.Row, ui.Card, ui.Panel, ui.Toolbar, ui.Text, ui.Badge, ui.StatusBadge, ui.Alert, ui.Button, ui.CommandButton, ui.Metric, ui.Field, ui.FilterBar, ui.TextInput, ui.NumberInput, ui.InlineEditor, ui.Select, ui.Toggle, ui.Slider, ui.Table, ui.DataTable, ui.Cards, ui.EmptyState, ui.Progress, ui.KpiGrid, ui.Tabs, ui.Sparkline, and ui.Chart. The framework views — ui.DataTable and ui.Cards — window large row sets to O(visible) DOM and carry the model-state view conventions (bind filter to a search cell; selected/selectKey/onSelect write the selected row back to a model cell), the same conventions the App builder's Table uses. ui.Chart renders an inline SVG chart with no chart library in the bundle. ui.Alert is the feedback banner: compute validation, results, or status in the spreadsheet layer and show it with <ui.Alert when={errors} tone="danger" message={errors} /> — feedback stays model state, gated like the App builder's visibleWhen. A Component can also use standard JSX elements such as div, section, input, select, table, and svg. Package components from the wider React ecosystem must be made available by the host surface scope before a Component can reference them by name.

Authoring Custom UI

When creating a custom UI, start from the user's workflow rather than from the component tree. Decide:

  1. Which model symbols does the UI read?
  2. Which model symbols may it write?
  3. Which state belongs in formulas, and which state belongs in the surface's !data slot?
  4. Is a built-in surface close enough, or should the UI be an App or Component?

A small Component surface has three parts:

Component_1!type = "component"
 
Component_1!config = <toml>
  version = 1
  kind = "component"
  title = "Revenue Console"
 
  [bindings]
  reads = ["Revenue", "Orders"]
  writes = ["Scenario"]
</toml>
jsx Component_1!source = <jsx>
function App() {
  const revenue = model.useValue("Revenue");
  const orders = model.useRows("Orders");
 
  function applyScenario(next) {
    model.set("Scenario", next);
  }
 
  return (
    <ui.Stack gap={12}>
      <ui.Metric label="Revenue" value={revenue} format="currency" />
      <ui.Row>
        {["bear", "base", "bull"].map((scenario) => (
          <ui.Button key={scenario} onClick={() => applyScenario(scenario)}>
            {scenario}
          </ui.Button>
        ))}
      </ui.Row>
      <ui.Table columns={orders.columns} rows={orders.rows} empty="No orders yet." />
    </ui.Stack>
  );
}
 
render(<App />);
</jsx>

The create menu and Component editor also provide scaffold actions for common custom UI starting points: Dashboard, Records, Controls, Scenario, Review, and Kanban. Creating from a scaffold seeds the new Component surface with both JSX source and config; applying a scaffold inside an existing Component replaces the JSX source and rewrites the config in one step. In both cases, the generated UI and its declared read/write bindings stay together. This is the same packaging an AI agent should produce when it creates a custom UI directly: !type = "component", !config with narrow bindings.reads / bindings.writes, and runnable JSX in !source.

AI Agent Contract For Component Surfaces

When an AI agent creates a custom UI, it should produce a complete Component surface package in one edit:

  1. Keep durable business logic in model formulas, rules, or named data values.
  2. Choose a Component namespace, such as ScenarioConsole.
  3. Write ScenarioConsole!type = "component".
  4. Write ScenarioConsole!config with version = 1, kind = "component", a human title, and [bindings].
  5. Put every symbol read by JSX in bindings.reads.
  6. Put only the symbols the UI may mutate in bindings.writes.
  7. Write runnable JSX in jsx ScenarioConsole!source = <jsx>...</jsx>.
  8. End the source with render(<App />); or an equivalent single render call.

Do not make the Component calculate values that should be shared with sheets, built-in surfaces, or other custom UIs. Put those calculations in the model and read them from the Component. Do not write broad allowlists such as every model symbol; writes are a capability boundary.

The authoring surface statically scans JSX and shows repair actions when a symbol is read but missing from bindings.reads, or written but missing from bindings.writes. Those diagnostics are advisory; runtime write enforcement still happens through the Component model API, so undeclared writes are blocked. The editor also exposes authoring metadata for available model symbols, the supported model.* methods, and the blessed ui.* primitives; agents should treat that metadata as the autocomplete contract for generated Component code.

Deterministic Generation Recipe

Agents should use the same planning rules as the Component editor's recommended starter:

  1. Classify the request from intent words first:
    • scenario / planner / forecast / assumption -> Scenario
    • review / approval / queue / triage -> Review
    • kanban / board / pipeline / stage / status -> Kanban
    • record / search / browser / detail / list -> Records
    • control / form / input / edit / settings -> Controls
    • dashboard / KPI / metric / summary / overview -> Dashboard
  2. If intent is vague, infer from model shape:
    • row data plus scalar metrics -> Dashboard
    • row data only -> Records
    • scalar values only -> Controls
    • status/stage-like row data -> Kanban
    • scenario/target/assumption-like scalar values -> Scenario
  3. Generate the Component scaffold package: !type, !config, and !source.
  4. Run the static binding scan over JSX and compare it to [bindings].
  5. Repair missing reads/writes before presenting the result.
  6. Verify the custom UI in Mock, Empty, Loading, and Error preview modes. A generated Component is not done until those states render cleanly.

The planner is exposed in code as buildComponentGenerationPlan. It returns the chosen scaffold, generated package, reads, writes, validation checks, safety notes, and rationale. Agents should prefer that helper when they are operating inside the Grid UI codebase; otherwise they should follow the recipe above exactly.

The injected model API has this shape:

interface ComponentModelApi {
  get(symbol: string): unknown;
  useValue(symbol: string): unknown;
  useValues(symbols: readonly string[]): Record<string, unknown>;
  useRows(symbol: string): {
    status: "unbound" | "loading" | "error" | "empty" | "bound";
    columns: Array<{ key: string; label: string; type?: string }>;
    rows: Array<Record<string, unknown>>;
    label: string;
    error?: string;
  };
  useRange(symbol: string): Array<Record<string, unknown>>;
  set(symbol: string, value: unknown, typeTag?: string): void;
  setFormula(symbol: string, formula: string): void;
}

The injected ui primitives are React components. They are intentionally small and composable:

ui.Stack;       // vertical layout
ui.Row;         // horizontal layout
ui.Card;        // framed content
ui.Panel;       // titled framed section
ui.Toolbar;     // header/actions row
ui.Text;        // muted/supporting text
ui.Badge;       // status token
ui.StatusBadge; // tonal status token
ui.Button;      // action button
ui.CommandButton; // primary/quiet/danger command
ui.Metric;      // labeled scalar
ui.Field;       // label/value display
ui.FilterBar;   // search/filter row
ui.TextInput;   // string write control
ui.NumberInput; // numeric write control
ui.InlineEditor; // compact editable value
ui.Select;      // option write control
ui.Toggle;      // boolean write control
ui.Slider;      // numeric range control
ui.Table;       // row display
ui.DataTable;   // windowed data view: any row count, filter + selection write-back
ui.Cards;       // windowed card list over rows (renderCard) with filter
ui.StatusBadge; // tonal status token
ui.Alert;       // feedback banner, gated by model state (when)
ui.EmptyState;  // empty/error panel
ui.Progress;    // progress meter
ui.KpiGrid;     // repeated metrics
ui.Tabs;        // segmented sections
ui.Sparkline;   // compact trend chart
ui.Chart;       // dependency-free SVG chart (line/bar/area) over rows

For a model like this:

Revenue is currency = 720000
Scenario = "base"
Orders = [
  { id: "ORD-1", customer: "Acme", amount: 42000 },
  { id: "ORD-2", customer: "Northstar", amount: 88000 }
]

an agent-generated Component should look like this:

ScenarioConsole!type = "component"
 
ScenarioConsole!config = <toml>
  version = 1
  kind = "component"
  title = "Scenario Console"
 
  [bindings]
  reads = ["Revenue", "Scenario", "Orders"]
  writes = ["Scenario"]
</toml>
jsx ScenarioConsole!source = <jsx>
function App() {
  const revenue = model.useValue("Revenue");
  const scenario = String(model.useValue("Scenario") ?? "base");
  const orders = model.useRows("Orders");
 
  return (
    <ui.Stack gap={12}>
      <ui.KpiGrid
        items={[
          { label: "Revenue", value: revenue, format: "currency" },
          { label: "Scenario", value: scenario }
        ]}
      />
      <ui.Select
        label="Scenario"
        value={scenario}
        options={["bear", "base", "bull"]}
        onChange={(next) => model.set("Scenario", next)}
      />
      <ui.Table columns={orders.columns} rows={orders.rows} empty="No orders yet." />
    </ui.Stack>
  );
}
 
render(<App />);
</jsx>

Keep calculations in formulas whenever possible. Use the custom UI for interaction, presentation, and workflow. This keeps the model inspectable and lets the same values continue to work in sheets, built-in surfaces, and custom surfaces.

Complete Surface Examples

These examples show the model-source shape a user or AI agent should produce. They are intentionally small, but each one includes both model data and surface slots.

CRM With Table And Kanban

MODEL "Pipeline CRM"
DESCRIPTION "Track accounts as rows, then show them as a table and workflow board."
VERSION "1.0.0"
AUTHOR "AI Agent"
TAGS "surface-template", "crm", "table", "kanban", "surfaces"
 
Accounts = [
  { name: "Acme", owner: "Mina", stage: "lead", value: 42000 },
  { name: "Northstar", owner: "Owen", stage: "qualified", value: 88000 },
  { name: "River Co", owner: "June", stage: "proposal", value: 56000 }
]
 
TotalPipeline IS currency = SUM(MAP(Accounts, row => row.value))
 
# Inspect account fields in a table.
Accounts_Table!type = "table"
 
Accounts_Table!config = <toml>
  version = 1
  kind = "table"
  title = "Accounts"
 
  [columns]
  bind = "Accounts"
  title = "Accounts"
 
  [[columns.fields]]
  key = "name"
  label = "Account"
 
  [[columns.fields]]
  key = "owner"
  label = "Owner"
 
  [[columns.fields]]
  key = "stage"
  label = "Stage"
 
  [[columns.fields]]
  key = "value"
  label = "Value"
</toml>
 
Accounts_Table!data = ""
 
# Group the same accounts by pipeline stage.
Pipeline_Board!type = "kanban"
 
Pipeline_Board!config = <toml>
  version = 1
  kind = "kanban"
  title = "Pipeline"
 
  [layout]
  wip_limit = 8
 
  [[columns]]
  id = "lead"
  title = "Lead"
 
  [[columns]]
  id = "qualified"
  title = "Qualified"
 
  [[columns]]
  id = "proposal"
  title = "Proposal"
 
  [items]
  bind = "Accounts"
  column = "stage"
  title = "name"
  assignee = "owner"
</toml>
 
Pipeline_Board!data = ""
 
END MODEL

Store Map

MODEL "Store Map"
DESCRIPTION "Show store locations on a map."
VERSION "1.0.0"
AUTHOR "AI Agent"
TAGS "surface-template", "map", "locations", "surfaces"
 
Stores = [
  { id: "SFO", name: "San Francisco", latitude: 37.7749, longitude: -122.4194, revenue: 125000 },
  { id: "OAK", name: "Oakland", latitude: 37.8044, longitude: -122.2712, revenue: 92000 },
  { id: "SJC", name: "San Jose", latitude: 37.3382, longitude: -121.8863, revenue: 111000 }
]
 
TotalRevenue IS currency = SUM(MAP(Stores, store => store.revenue))
 
# Plot store locations from the shared model data.
Store_Map!type = "map"
 
Store_Map!config = <toml>
  version = 1
  kind = "map"
  title = "Store Map"
 
  [layout]
  basemap = "streets"
  center = [37.7749, -122.4194]
  zoom = 9
 
  [[layers]]
  name = "Stores"
  bind = "Stores"
  lat = "latitude"
  lng = "longitude"
  label = "name"
  color = "#2563eb"
</toml>
 
Store_Map!data = ""
 
END MODEL

KPI Layout Dashboard

MODEL "Revenue Dashboard"
DESCRIPTION "Summarize a forecast in dashboard tiles."
VERSION "1.0.0"
AUTHOR "AI Agent"
TAGS "surface-template", "dashboard", "layout", "surfaces"
 
MonthlyRevenue = [120000, 135000, 142000, 150000, 168000]
Pipeline = 186000
Forecast IS currency = SUM(MonthlyRevenue#) + Pipeline
AverageMonth IS currency = AVERAGE(MonthlyRevenue#)
 
# Present the model’s revenue summaries as dashboard tiles.
Dashboard!type = "layout"
 
Dashboard!config = <toml>
  version = 1
  kind = "layout"
  title = "Dashboard"
 
  [layout]
  cols = 12
  rows = 6
  show_controls = true
 
  [[tiles.static]]
  title = "Forecast"
  x = 0
  y = 0
  w = 4
  h = 2
  formula = "=Forecast"
 
  [[tiles.static]]
  title = "Average Month"
  x = 4
  y = 0
  w = 4
  h = 2
  formula = "=AverageMonth"
 
  [[tiles.static]]
  title = "Notes"
  x = 0
  y = 2
  w = 12
  h = 4
  body = "Forecast includes five months of actuals plus open pipeline."
</toml>
 
Dashboard!data = ""
 
END MODEL

Custom Scenario Console

MODEL "Scenario Console"
DESCRIPTION "A custom Component surface reads model values and writes the selected scenario."
VERSION "1.0.0"
AUTHOR "AI Agent"
TAGS "surface-template", "custom-ui", "component", "surfaces"
 
Revenue IS currency = 720000
Scenario = "base"
Orders = [
  { id: "ORD-1", customer: "Acme", amount: 42000 },
  { id: "ORD-2", customer: "Northstar", amount: 88000 },
  { id: "ORD-3", customer: "River Co", amount: 56000 }
]
 
# Let readers explore the model through a custom component.
ScenarioConsole!type = "component"
 
ScenarioConsole!config = <toml>
  version = 1
  kind = "component"
  title = "Scenario Console"
 
  [bindings]
  reads = ["Revenue", "Scenario", "Orders"]
  writes = ["Scenario"]
</toml>
jsx ScenarioConsole!source = <jsx>
function App() {
  const revenue = model.useValue("Revenue");
  const scenario = model.useValue("Scenario");
  const orders = model.useRows("Orders");
 
  return (
    <ui.Stack gap={12}>
      <ui.Metric label="Revenue" value={revenue} format="currency" />
      <ui.Select
        label="Scenario"
        value={String(scenario ?? "")}
        options={["bear", "base", "bull"]}
        onChange={(next) => model.set("Scenario", next)}
      />
      <ui.Table columns={orders.columns} rows={orders.rows} empty="No orders yet." />
    </ui.Stack>
  );
}
 
render(<App />);
</jsx>
 
END MODEL

Choosing A Surface

Use a built-in surface when the model fits a known workflow: tables for records, maps for locations, boards for planning, documents for narrative, charts for analysis, and layouts for dashboards.

Use an App surface when you want to compose a model-bound screen without writing React first.

Use a Component surface when you want a custom front end with React-level control over layout, state, components, and interaction.

Use eject when a built-in surface gets you most of the way there but the final experience needs custom behavior. Ejecting keeps the model bindings and produces editable JSX source that can continue inside the workbook.

Current Boundary

Surfaces are a UI layer over model state. They do not replace formulas, rules, or the source editor. The model remains the durable computation layer; surfaces are how people operate, inspect, and present that computation.

That boundary is intentional. It lets Grid be a spreadsheet, a model, and an application shell at once: built-in surfaces provide familiar app-like interfaces, and custom surfaces let teams build the exact UI their model needs.

Deck

Deck provides structured live presentations in App, the shared visual editor and source in Code, model bindings in Data, and narrative work in Chat. Present live or capture values, use a separate audience window, print slides, or export editable PowerPoint. See Deck presentations.

App presentation and authoring

Charts and Visuals use the shared App heading and View actions menu. Edit chart and Edit visualization open their native arrangement tools; Done returns to the view. Visual export review remains available from the menu. Pending chart edits flush when leaving the surface. Data keeps its existing chart toolbars and editor layout.

Shared numeric controls display percentages in percentage points: 6 beside % writes the ratio 0.06. Currency grouping is removed for editing and validated when pasted. Enter and blur save, Escape restores the last accepted value, and failed text remains available for correction. Numeric edits preserve existing currency/unit tags. Controls honor the host’s declared input catalog when available; read-only bindings remain visibly distinct from editable assumptions. Documents and notebooks carry the host’s existing display metadata with their selected live references.

App hosts the same editors and persistence as Data. Its compact frame separates content actions from Customize App, which arranges navigation and optional per-view Next steps. Numeric/text controls commit on Enter or blur, retain failed drafts, and report acknowledged writes. Native Inspect exposes selected-object commands without opening a panel for every selection. Notebook blocks add drag ordering, keyboard movement, and structural Undo/Redo. App Builder, Layout, and Notebook flush pending content on navigation through the existing ordered draft writer and preserve conflicts for explicit recovery. See Interface layers for the authoring boundaries and the complete forecasting example.

Document insertion accepts full command titles such as /heading 2 and /cell value. Keeping a Chat excerpt uses the same Markdown conversion as the document editor, accepts the saved revision before its first write, and retains the excerpt as one undoable content operation. It does not replace an unresolved draft or interpret prose as executable code.