Part of #10206
The gap
The react tier is real, shipped, and gated:
packages/spec/src/ui/react-blocks.ts — the curated block index (ADR-0081), with the overlay-shadow ledger and the record:* exclusion ledger;@object-ui/react-runtime in the objectui repo — Sucrase transpile + eval, the trusted execution tier;os validate step 3d (validateReactPageProps) parses each react page's real JSX and checks block usage against the generated contract;docs/audits/2026-06-react-tier-authoring-dogfood.md proves the authoring loop end-to-end on examples/app-showcase/src/pages/renewals-pipeline.page.ts.
On the docs site, a developer can find only:
| where | what |
|---|
references/ui/page.mdx L87 / L89 | two generated schema rows describing kind and source |
ui/pages.mdx L147 | one bullet, phrased negatively: record:* blocks are rejected on a react page |
deployment/validating-metadata.mdx §10b and neighbours | how it fails validation |
And critically, the Page Properties table in content/docs/ui/pages.mdx lists neither kind nor source (it stops at name/label/description/type/object/template/regions/variables/isDefault/assignedProfiles). A developer reading the Page guide end to end concludes that regions + components is the only way to author a page. kind:'html' — the constrained, parsed-never-executed tier served by @objectstack/sdui-parser — is equally invisible.
The material exists, in two places a customer does not read: objectui/content/docs/guide/react-pages.md (a different docs site; @object-ui/react-runtime's README links it by a relative path that does not resolve from this repo) and skills/objectstack-ui/references/react-blocks.md (generated, AI-facing).
File surface
content/docs/ui/pages.mdx — editcontent/docs/ui/react-pages.mdx — newcontent/docs/ui/meta.json — one added entry
⛔ Do not touch any other file under content/docs/ui/ — sibling cards in this epic own reports.mdx, theming.mdx and doc-pages.mdx, and this card holds the serial position on ui/meta.json.
What to write
1. ui/pages.mdx
- Add
kind and source rows to the Page Properties table. Take the wording from the schema, not from this issue: read PageSchema in packages/spec/src/ui/page.zod.ts and mirror what references/ui/page.mdx L87/L89 already publishes. - Add an Authoring modes section placed before
## Regions, contrasting the three tiers in one table — full / slotted (structured authoring, the existing content), html (constrained JSX/HTML + Tailwind, parsed and compiled at save time, never executed — the tier for untrusted authors; jsx is the deprecated alias), react (real React executed at render, the trusted tier, gated by a host capability that defaults ON and is disabled server-side with OS_PAGE_REACT=off). - Say plainly that
source is authoritative over regions when both are present. - Cross-link the new page and
deployment/validating-metadata.mdx.
2. ui/react-pages.mdx (new)
objectui/content/docs/guide/react-pages.md is the starting draft — but port it, do not copy it. Every claim must be re-verified against this repo's sources (packages/spec/src/ui/react-blocks.ts, page.zod.ts, the os validate implementation) and every cross-link must resolve inside this site. Cover:
- Choosing between
react and html — the security boundary stated once and plainly: react source is new Function(...)-evaluated with full access to the page's React tree, no sandbox, first-party trust only; html is parsed into a schema tree and never executed. - The security gate — the host capability and the
OS_PAGE_REACT=off server-side kill switch. - What is in scope — how the runtime builds the injected scope, and that blocks are referenced by their PascalCase tag. Link the generated contract (
skills/objectstack-ui/references/react-blocks.md) as the authority for per-block props rather than restating prop tables that would drift. - Blocks take flat props, and the
<Block> escape hatch. - Live data —
const adapter = useAdapter(); adapter.find/findOne/create/update. - Styling (ADR-0065) — a page's source is runtime metadata, so the console's build-time Tailwind never scans it and utility class names silently produce no CSS. Inline
style={{}} with hsl(var(--token)) theme colors; use <ObjectForm formType='drawer'|'modal'> for overlays instead of hand-rolling a backdrop. This is the single highest-value paragraph on the page — an author who misses it produces a page that renders unstyled with no error. - Accepted source shapes and what happens when something throws.
- Page state — the parent record on a react page is ordinary React state.
- The
record:* exclusion — why the record-context family is not in the react tier (they read a shared record context a type:'record' page mounts once; a react page mounts none), what os validate reports (react-block-needs-record-context), and what to write instead. REACT_RECORD_BLOCK_ALTERNATIVES in react-blocks.ts is the authority for the alternatives. - How you check your work —
os validate step 3d, with the real error shapes (missing required prop, did-you-mean on a typo'd callback) taken from docs/audits/2026-06-react-tier-authoring-dogfood.md.
3. ui/meta.json
Add react-pages immediately after pages.
⛔ Non-goals
- Do not restate per-block prop tables in prose. They are generated from
REACT_BLOCKS by packages/spec/scripts/build-react-blocks-contract.ts precisely so they cannot drift; a hand-copied table in content/docs/** is a new drift surface and will be rejected in review. - Do not touch
packages/spec/**, skills/**, or the objectui repo. - If porting turns up a claim in the objectui guide that this repo's sources contradict, report it — do not silently write the version you believe. That is a finding worth filing, not a paragraph to smooth over.
Gates (derived at dispatch time via node scripts/pm/dispatch-gates.mjs, not recalled)
check:doc-anchors, check:docs-audit-scope, check:docs-redirects, check:published-readme-links, check:role-word, check:cross-package-test-inputs, and the spec liveness set (check:empty-state, check:liveness, check:strictness-ledger, check:variant-docs). Re-derive against your actual diff. Note that content/docs/** carries an {/* os:check */} convention for verified code samples — follow what ui/pages.mdx already does for its regions example.
Acceptance
- A developer who opens
/docs/ui/pages learns that three authoring modes exist and can reach the react guide in one click. - The Page Properties table is complete against
PageSchema. - Every code sample on the new page is valid against the real contract, and the styling rule is stated where an author will hit it.
- Named gates green.
Part of #10206
The gap
The react tier is real, shipped, and gated:
packages/spec/src/ui/react-blocks.ts— the curated block index (ADR-0081), with the overlay-shadow ledger and therecord:*exclusion ledger;@object-ui/react-runtimein the objectui repo — Sucrase transpile + eval, the trusted execution tier;os validatestep 3d (validateReactPageProps) parses each react page's real JSX and checks block usage against the generated contract;docs/audits/2026-06-react-tier-authoring-dogfood.mdproves the authoring loop end-to-end onexamples/app-showcase/src/pages/renewals-pipeline.page.ts.On the docs site, a developer can find only:
references/ui/page.mdxL87 / L89kindandsourceui/pages.mdxL147record:*blocks are rejected on a react pagedeployment/validating-metadata.mdx§10b and neighboursAnd critically, the Page Properties table in
content/docs/ui/pages.mdxlists neitherkindnorsource(it stops atname/label/description/type/object/template/regions/variables/isDefault/assignedProfiles). A developer reading the Page guide end to end concludes thatregions+componentsis the only way to author a page.kind:'html'— the constrained, parsed-never-executed tier served by@objectstack/sdui-parser— is equally invisible.The material exists, in two places a customer does not read:
objectui/content/docs/guide/react-pages.md(a different docs site;@object-ui/react-runtime's README links it by a relative path that does not resolve from this repo) andskills/objectstack-ui/references/react-blocks.md(generated, AI-facing).File surface
content/docs/ui/pages.mdx— editcontent/docs/ui/react-pages.mdx— newcontent/docs/ui/meta.json— one added entry⛔ Do not touch any other file under
content/docs/ui/— sibling cards in this epic ownreports.mdx,theming.mdxanddoc-pages.mdx, and this card holds the serial position onui/meta.json.What to write
1.
ui/pages.mdxkindandsourcerows to the Page Properties table. Take the wording from the schema, not from this issue: readPageSchemainpackages/spec/src/ui/page.zod.tsand mirror whatreferences/ui/page.mdxL87/L89 already publishes.## Regions, contrasting the three tiers in one table —full/slotted(structured authoring, the existing content),html(constrained JSX/HTML + Tailwind, parsed and compiled at save time, never executed — the tier for untrusted authors;jsxis the deprecated alias),react(real React executed at render, the trusted tier, gated by a host capability that defaults ON and is disabled server-side withOS_PAGE_REACT=off).sourceis authoritative overregionswhen both are present.deployment/validating-metadata.mdx.2.
ui/react-pages.mdx(new)objectui/content/docs/guide/react-pages.mdis the starting draft — but port it, do not copy it. Every claim must be re-verified against this repo's sources (packages/spec/src/ui/react-blocks.ts,page.zod.ts, theos validateimplementation) and every cross-link must resolve inside this site. Cover:reactandhtml— the security boundary stated once and plainly: react source isnew Function(...)-evaluated with full access to the page's React tree, no sandbox, first-party trust only;htmlis parsed into a schema tree and never executed.OS_PAGE_REACT=offserver-side kill switch.skills/objectstack-ui/references/react-blocks.md) as the authority for per-block props rather than restating prop tables that would drift.<Block>escape hatch.const adapter = useAdapter(); adapter.find/findOne/create/update.style={{}}withhsl(var(--token))theme colors; use<ObjectForm formType='drawer'|'modal'>for overlays instead of hand-rolling a backdrop. This is the single highest-value paragraph on the page — an author who misses it produces a page that renders unstyled with no error.record:*exclusion — why the record-context family is not in the react tier (they read a shared record context atype:'record'page mounts once; a react page mounts none), whatos validatereports (react-block-needs-record-context), and what to write instead.REACT_RECORD_BLOCK_ALTERNATIVESinreact-blocks.tsis the authority for the alternatives.os validatestep 3d, with the real error shapes (missing required prop, did-you-mean on a typo'd callback) taken fromdocs/audits/2026-06-react-tier-authoring-dogfood.md.3.
ui/meta.jsonAdd
react-pagesimmediately afterpages.⛔ Non-goals
REACT_BLOCKSbypackages/spec/scripts/build-react-blocks-contract.tsprecisely so they cannot drift; a hand-copied table incontent/docs/**is a new drift surface and will be rejected in review.packages/spec/**,skills/**, or the objectui repo.Gates (derived at dispatch time via
node scripts/pm/dispatch-gates.mjs, not recalled)check:doc-anchors,check:docs-audit-scope,check:docs-redirects,check:published-readme-links,check:role-word,check:cross-package-test-inputs, and the spec liveness set (check:empty-state,check:liveness,check:strictness-ledger,check:variant-docs). Re-derive against your actual diff. Note thatcontent/docs/**carries an{/* os:check */}convention for verified code samples — follow whatui/pages.mdxalready does for itsregionsexample.Acceptance
/docs/ui/pageslearns that three authoring modes exist and can reach the react guide in one click.PageSchema.