Skip to main content

Pages

ui/pages.yaml declares the pages of your solution UI. A page binds a title, a layout and an ordered list of widget placements. The portal's layout engine and widget registry render it — there is no page-level code.

Page definitions​

ui/pages.yaml
- id: dashboard
title: Dashboard
layout: dashboard-grid
widgets:
- { widget: active_orders, span: 3 }
- { widget: revenue_chart, span: 9 }
- { widget: order_table, span: 12 }

- id: orders
title: Orders
layout: split-grid
widgets:
- { widget: order_table, span: 8 }
- { widget: orders_timeline, span: 4 }
FieldTypeRequiredDescription
idstringYesPage id; referenced by navigation entries and routes.
titlestringYesPage header and tab label.
layoutstring or objectYesLayout preset id from layouts.yaml, or an inline layout.
widgetslistYesOrdered widget placements (at least one).

Widget placements​

FieldTypeRequiredDescription
widgetstringYesWidget id declared in widgets.yaml.
spannumberNoGrid columns occupied (1–12). Defaults to full width.

Placements render in declaration order, wrapping left-to-right, top-to-bottom. restaurant-pro's dashboard places the active_orders metric (3 columns) beside the revenue_chart line chart (9 columns), with the full-width order_table underneath.

Referencing layouts​

Prefer named presets from layouts.yaml:

layout: dashboard-grid

Inline layouts are accepted for one-off pages:

layout:
type: grid
columns: 12

Both forms enforce the same rules: the layout type must be grid, and columns must be between 1 and 12.

Validation and clamping​

  • At publish time: unknown widget ids, spans larger than the layout's column count, non-grid layouts and out-of-range column counts are rejected. See Validation.
  • At render time: the layout engine defensively clamps spans into range and coerces shapes, so a degraded definition renders as a scoped error card instead of crashing the portal.

Responsive behavior​

On narrow screens every placement stacks into a single column in declaration order; at ≥ 1024 px the grid uses the declared column count. Design dashboards so the stacked order still tells a story (headline metrics first, details last).

Data wiring​

Pages never fetch data. Every widget references a source from sources.yaml, and each fetch is capability-gated: a widget whose capability is not granted never fires a request. The page itself renders immediately with loading states.

Host pages (CRM and Automation)​

Marketplace Apps can open platform hosts instead of a widget layout. From restaurant-pro-runtime / real-estate-runtime:

- id: contacts
title: Contacts
host: contacts
- id: automations
title: Automations
host: automations
hostSurface
contactsPerson CRM (not an app-local contacts table)
automationsCRM Automation (e.g. reservation.created → Send WhatsApp)

These are not package folders. See Events.

Guidelines​

  • One page per operational question ("what's happening now?", "what did we sell?") rather than per entity.
  • Keep dashboards under 8 placements; split detail views into separate pages.
  • Reuse widgets across pages — restaurant-pro-runtime uses the same reservations_table on Today and Reservations with different spans.
  • Every page referenced by navigation.yaml must exist; every page should be reachable from navigation.

Restaurant Pro Runtime page list​

PageLayoutPlacements
Todaydashboard-gridintro + reservations table
Reservationssplit-gridform + table
Tables / Menusplit-gridentity tables
ContactshostPerson CRM
AutomationshostCRM Automation

The full definitions are in the restaurant-pro-runtime example. SDK takeaway pages remain in restaurant-pro.