> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 0110 — The experience service is server-driven UI at level L2: trees of registered coded components, no code over the wire

> Accepted, amended by 0139, 0154 · 2026-10-19

> [!NOTE]
>
> Amended by [0154](/adr/0154-tenant-code-runs-only-in-a-sandboxed-code-step), decision 4: tenant
> code runs only in the sandbox.

> [!NOTE]
>
> Amended by [0139](/adr/0139-actions-declare-their-user-experience): actions declare their user
> experience.

## Context

[0011](/adr/0011-rejected-and-deferred-technologies) rejects "a runtime schema engine, server-driven UI, JSONB-for-everything"
in favour of coded resources. Dashboards, page builders and tenant-customised screens need layouts that change without a
deploy. The owner's experience-service architecture document already makes that service the server-driven UI service, using
versioned declarative schemas: component type, props, bindings, actions and permissions, with no code over the wire. Levels
of server-driven UI were weighed; level L2 is a tree of registered, coded components with typed props. Higher levels ship
logic or markup and are what 0011 rightly rejects.

## Decision

1. **The experience service serves UI documents at L2**: a tree of registered coded components, each named `key@major`, with
   Zod-validated props.
2. **Data binds to `@Query` action keys**; the client calls them batched through `experience.query.batch`.
3. **Events bind to `@Action` keys or to a closed set of UI verbs** (navigate, open, close, refresh and the like).
4. **No code, markup, CSS or head content travels in a document**, ever.
5. **Three parts of the owner's document are replaced**: hand-written YAML registered at startup becomes gen manifests
   published by CI ([0109](/adr/0109-registry-manifests-published-by-ci)); NestJS and an API gateway become an Encore service
   through gen; the Cloudflare edge becomes an optional cache only.
6. **Every document is validated server-side** against the registry before it is stored or served: unknown component,
   unknown query or action key, bad props or a missing permission is refused.

## Consequences

* Tenants and agents can change layouts without a deploy, and every change is still checked against coded components and actions.
* The 0011 rejection of a runtime schema engine and of JSONB-for-everything stands; only data that references registered code is allowed.
* The validator and the component registry are security-critical code.

## Enforcement

Planned: the document validator in `@stackra/experience` with a test per refusal case; a lint rule that no renderer evaluates
a string as code or HTML. Nothing checks this today.

## Supersedes and sources

Supersedes: the "A runtime schema engine, server-driven UI, JSONB-for-everything" row of
[0011](/adr/0011-rejected-and-deferred-technologies). The row is rewritten to reject a runtime schema engine,
JSONB-for-everything and tenant data that carries code, markup or logic, and to allow data that only references registered
code: L2 UI documents (here), builder-UI agents ([0100](/adr/0100-ai-model-and-ownership) decision 8), tenant automations
([0122](/adr/0122-workflow-studio-three-layers)) and bounded custom fields ([0116](/adr/0116-content-types-and-custom-fields)). Sources: owner decisions in chat,
2026-10-19 (Round 11 X1, X3). Informational: `text/experience-service-architecture-decision.md`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.