TRD — UIForge Technical Design¶
Initiative: INIT-UIFORGE-003 Status: Draft Date: 2026-09-10 Home repo: github.com/plexusone/uiforge
Architecture¶
PageSpec (JSON, apiVersion ui.plexusone.dev/v1)
│
▼
Schema validation schema/ (generated from Go types)
│
▼
Registry validation registry/ (ComponentSpec manifests, profiles)
│
▼
Runtime engines pkg/expression pkg/state pkg/interaction pkg/diff
│
▼
Renderer renderers/react | renderers/lit
│
▼
DOM (shared data-uiforge-* vocabulary)
The key principle: code defines component capabilities; the spec composes and configures them. Renderers never receive arbitrary HTML/CSS from specs — they map semantic component types to implementations.
Repository Structure¶
uiforge/
├── uispec/ # Canonical UISpec Go types — source of truth
├── registry/ # Component registry, ComponentSpec manifests, profiles, page validation
├── pkg/
│ ├── expression/ # ${...} expression evaluation over context maps
│ ├── state/ # Page state store (filters, variables)
│ ├── interaction/ # Event → action engine (drill-down, cross-filter)
│ └── diff/ # PageSpec tree diffing for incremental re-render
├── schema/ # Generated JSON Schemas + go:embed accessors + generator
├── theme/ # DSS → UIForge theme adapter (semantic token bindings)
├── authoring/ # Fluent PageSpec builders (TS counterpart in spec/)
├── spec/ # @plexusone/uiforge-spec — shared TS types + framework-free engines
├── renderers/
│ ├── react/ # @plexusone/uiforge-renderer (React 18/19)
│ └── lit/ # @plexusone/uiforge-renderer-lit (Lit 3 web components)
├── testdata/pagespecs/ # Golden PageSpec fixtures
├── examples/ # Example PageSpec documents
└── docs/specs/ # PRD, TRD, PLAN, ROADMAP
UISpec Type System¶
Go structs in uispec/ are the source of truth; schemas and TS types mirror them.
| File | Types |
|---|---|
page.go |
PageSpec (apiVersion, kind Page, metadata, profile, context, layout, components, interactions, navigation, theme), PageMetadata |
component.go |
ComponentInstance (id, type, version, position, properties, data, children, visibility, slot, style), Position |
layout.go |
LayoutSpec (responsive-grid, stack, split-pane, tabs, application-shell), LayoutConfig, LayoutRegion |
binding.go |
Binding (source, operation, parameters, transform, default) |
interaction.go |
Interaction, InteractionTrigger, InteractionAction |
navigation.go |
NavigationSpec, NavItem |
profile.go |
Experience profile identifiers and constraints |
capability.go |
Capability declarations |
theme.go |
ThemeRef (id, variant, tokens) |
Component Registry¶
registry.ComponentSpec is the machine-readable component contract: properties schema, data inputs, events, actions, layout constraints, capabilities, design-system compliance. registry.NewWithBuiltins() loads the built-in namespaces:
core.*— text, image, button, card, tabs, modal (generic primitives)analytics.*— line-chart, bar-chart, table, metric, gauge, filter (dashboard profile)application.*— input, select, checkbox, form, record-detail, record-list, action-bar, badge (application/portal profiles)assistant.*/agent.*— thread, composer, tool-call, run-status (agent profile)
Registry.ValidatePage(*uispec.PageSpec) checks every component instance against its manifest (unknown types, property validation, profile constraints). Golden fixtures under testdata/pagespecs/ are validated in registry/golden_test.go.
Schema Pipeline¶
Go types → schema/generate/main.go (invopop/jsonschema reflector, //go:build ignore) → schema/{page,component}.schema.json (draft 2020-12, $id under github.com/plexusone/uiforge) → embedded via schema/schema.go. Root tools.go (build tag tools) pins the generator dependency for go mod tidy. Schemas are linted with schemakit lint --property-case camelCase.
Regeneration must be deterministic: go run schema/generate/main.go from the repo root must leave a clean git diff.
Renderer Contracts¶
Both renderers register component implementations keyed by spec type and emit the same DOM vocabulary: data-uiforge-page, data-uiforge-profile, data-uiforge-layout, data-uiforge-cell, data-uiforge-region, data-uiforge-component, data-uiforge-missing, data-uiforge-error; theme tokens surface as --uiforge-* CSS custom properties.
- React (
renderers/react):registerComponent(type, Component)where a component receives{ instance, children };<PageRenderer page={spec}>renders the tree with error boundaries; includes expression/state/interaction/datasource runtimes and assistant components. - Lit (
renderers/lit):registerComponent(type, factory)where a factory maps aComponentInstance(plus an optionalPageContextwith state/engine/dispatch/data and pre-rendered children) to a lit template;<uiforge-page .spec=${spec} .dataSources=${connectors}>renders into shadow DOM. Supports all five layouts, navigation, theme tokens, visibility rules, and the expression/state/interaction engines;core.*,analytics.*, andapplication.*component packs are built in.
Data binding resolution¶
Bindings resolve through a tiered runtime (data.ts, datasource.ts, and the page's binding cache):
staticandstatesources resolve synchronously.- Sources matching a registered
DataSourceConnector({ id, execute(operation, params) → Promise }) resolve asynchronously: the binding isloading(rendered asdata-uiforge-loading), then settles toreadyorerror(data-uiforge-data-error). Bindingparametersare${...}-evaluated against{context, state}beforeexecute;transformexpressions post-process results. Results are cached per component+binding; thecomponent.refreshinteraction action invalidates a component's cache so its bindings re-fetch. - External sources with no registered connector fall back to the binding's
default— pages remain renderable without any data infrastructure.
The full runtime — connector interface, tiered resolution, per-component caching, loading/error markers, and component.refresh invalidation — exists identically in both renderers (<uiforge-page .dataSources> in Lit; the dataSources prop on PageRenderer in React), built on the single shared engines in @plexusone/uiforge-spec.
A fixture-driven conformance suite (conformance.test.ts(x) in each renderer package) renders every golden fixture in both renderers and asserts the shared DOM vocabulary — keep the two test files aligned.
Both renderers depend on @plexusone/uiforge-spec (spec/) — the renderer-independent package holding the UISpec TS types (mirroring the Go source of truth) and the framework-free runtime engines. In the repository the dependency is a file:../../spec link for local development; scripts/npm-publish.sh rewrites it to a semver range at publish time, so the published packages depend on @plexusone/uiforge-spec from the registry. Build order in a checkout: spec/ first, then renderers.
Capability model¶
Capabilities govern what components may do, in three layers:
- Declared — a manifest lists its
capabilities(open vocabulary;data.readandstate.writeare the well-known names UIForge's own machinery understands, defined as constants inuispecand@plexusone/uiforge-spec). - Profile-allowed (validation time) —
ProfileConstraints.AllowedCapabilitiesbounds what components under a profile may declare;ValidatePagerejects pages whose components exceed it. All builtin profiles allowdata.read+state.write; hosts can tighten. - Granted (runtime) — hosts pass a grant set to the renderer (
capabilitiesprop onPageRenderer,.capabilitieson<uiforge-page>). Absent means unrestricted (trusted-native default). When present: connector-backed bindings resolve todata-uiforge-data-errorwithoutdata.read(the connector is never called),writeBindingskips state writes withoutstate.write(events still dispatch), and components can checkctx.hasCapability(name)for their own gates.
Sandboxed execution for untrusted components remains roadmapped (RMI-UIFORGE-124).
Design System Integration¶
UIForge does not define its own design-token model; it consumes SystemSpec: Design System (DSS) documents (systemspec-designsystem, formerly design-system-spec; UIForge imports github.com/plexusone/systemspec-designsystem/sdk/go as of v0.7.0). The integration contract:
- Semantic token vocabulary. Components consume a fixed set of CSS custom properties named
--uiforge-<semantic>after DSS'sValidSemanticsvocabulary (primary, secondary, accent, danger, warning, success, info, neutral, surface, background, text, text-muted, text-inverse, border, focus, disabled, shadow), plus category keysfont-familyandradius. Every component style declares a hard-coded fallback, so unthemed pages render sensibly. themepackage.theme.FromDesignSystem(ds, opts)maps a DSS document's foundations onto that vocabulary (colors by their declaredsemantic, first font family,mdradius) and emits either a scoped stylesheet (Theme.CSS(selector)) or auispec.ThemeReffor embedding in a PageSpec.- White-label / prefix policy. The internal
--uiforge-*prefix anddata-uiforge-*DOM vocabulary are a fixed machine contract (like Lightning's--slds-*or Polaris--p-*) — they are never renamed. Brand adaptability lives at the boundary:Options.SourcePrefixemits bindings that reference the host design system's own variables (--uiforge-primary: var(--plexus-cyan, #06b6d4)), so reliant services keep their own prefix and can restyle at runtime; and theme values apply at each page scope (the<uiforge-page>root or any selector passed toTheme.CSS), so multiple tenants can carry different brands on one page without collisions. - Modes & density.
ThemeRef.modesholds per-mode token overlays, andThemeRef.densitiesholds every declared density's spacing scale keyed by ID (an open set, not a fixed enum) — both derived bytheme.FromDesignSystemWithModesfrom DSS's first-classModes/ColorToken.EffectiveModes()andFoundations.Densities(DSS v0.7.0, systemspec-designsystem#9).variantnames the default mode andThemeRef.densityselects the default density; renderers switch at runtime (modeprop/property,data-uiforge-mode/data-uiforge-densityattributes,CSSWithModesfor stylesheet theming) by looking updensities[density]for--uiforge-density, never touching DSS directly. Validation is structural:theme.densitymust be a key intheme.densities. Scope is limited to the scale factor — DSS's per-tokenspacingOverridesaren't consumed (docs/proposals/dss-density-and-modes.md). - Enforcement. The contract is machine-checked:
uispec.ValidThemeTokenKeysis the canonical vocabulary (its equivalence with DSS'sValidSemanticsis guarded by a theme-package test);registry.Registerrejects manifests whosedesignSystem.tokensfall outside it, andregistry.ValidatePagerejects pages whosetheme.tokensdo.
Versioning¶
- apiVersion (
ui.plexusone.dev/v1) versions the IR contract; additive changes only within a version. - Module/package versions (Go module tags, npm package versions) version implementations.
- Component manifests carry their own
version; pages may pin component versions.
Toolchain Constraints¶
- Go 1.26.6 (
go.mod); shared CI runsGOTOOLCHAIN=localwith a cached 1.26.x toolchain — do not raise the directive past what CI provides. - npm packages build with plain
tsc; tests run under vitest + jsdom;dist/is committed so packages are consumable via git before npm publishing (RMI-UIFORGE-113).