Skip to content

Authoring Pages Programmatically

Hand-writing PageSpec JSON is fine for fixtures; programs should use the fluent builders — authoring (Go) and the same API in @plexusone/uiforge-spec (TypeScript). Both produce plain PageSpecs, set apiVersion/kind automatically, and report every construction problem at Build() time. Registry validation stays a separate, explicit step.

Go

import (
    "github.com/plexusone/uiforge/authoring"
    "github.com/plexusone/uiforge/registry"
    "github.com/plexusone/uiforge/uispec"
)

page, err := authoring.NewPage("sales", "Sales Dashboard").
    Profile(uispec.ProfileDashboard).
    Context("customerId", "cust-42").
    Grid(12, "16px").
    Theme("brand", map[string]string{"primary": "#0f766e"}).
    Add(
        authoring.Component("revenue", "analytics.metric").
            At(0, 0).Span(3, 1).
            Prop("title", "Revenue").
            Bind("primary", "sales-data", "totalRevenue",
                authoring.Param("customerId", "${context.customerId}")).
            Default("primary", 0),
        authoring.Component("note", "core.text").
            Prop("content", "Filtered").
            VisibleWhen("${state.filters.active}"),
    ).
    OnEvent("revenue", "click",
        authoring.SetState("revenue", "filters.month", "${event.label}"),
        authoring.Refresh("revenue"),
    ).
    Build()
if err != nil { ... }

r, _ := registry.NewWithBuiltins()
if err := r.ValidatePage(page); err != nil { ... }

Layout shortcuts: Grid, Stack, AppShell(regions...); anything else via Layout(uispec.LayoutSpec{...}). Bindings: Bind (connector), BindState(path, default), BindStatic(value), Default. Containers compose with Child(...); navigation with Navigation(type, NavItem(...)). The builder pre-checks what it can (theme tokens against the design-token contract, required ids, a layout) — everything component-contract-shaped is the registry's job.

TypeScript

The identical API, camelCased, from @plexusone/uiforge-spec:

import { newPage, component, setState, refresh } from '@plexusone/uiforge-spec'

const page = newPage('sales', 'Sales Dashboard')
  .profile('dashboard')
  .grid(12, '16px')
  .theme('brand', { primary: '#0f766e' })
  .add(
    component('revenue', 'analytics.metric')
      .at(0, 0).span(3, 1)
      .prop('title', 'Revenue')
      .bind('primary', 'sales-data', 'totalRevenue', { customerId: '${context.customerId}' })
      .default('primary', 0),
  )
  .onEvent('revenue', 'click', setState('revenue', 'filters.month', '${event.label}'), refresh('revenue'))
  .build() // throws AuthoringError listing every problem

build() returns a deep clone, so the builder can keep evolving without mutating what you already shipped. This is the API AI agents and visual tooling should target: emit builder calls or the resulting JSON — never renderer code.