Filed from the framework repo (objectstack-ai/objectstack) while porting
content/docs/guide/react-pages.md into the customer docs site as
content/docs/ui/react-pages.mdx — framework issue
#10210, PR
#10281.
The port was explicitly instructed to re-verify every claim against the framework's
sources rather than copy, and to report contradictions rather than silently pick a
side. Four came up. All were measured against objectstack@502dc6fe7; none was carried
into the new page.
This guide is not dead material — @object-ui/react-runtime's README links it — so the
divergence is live.
1. Tailwind styling — the guide teaches the primitive an ADR amendment retracted
The guide's headline react example styles with Tailwind:
<divclassName="grid grid-cols-2 gap-4">
and the "What is in scope" section instructs:
Layout containers are deliberately not injected. In react mode you compose layout
with real HTML and Tailwind [...] use <div className="flex gap-4">.
objectstack/docs/adr/0080-ai-authored-ui-jsx-source.md carries an explicit header
amendment:
Amendment (2026-06-30 — ADR-0065 styling correction). The "HTML + Tailwind"
framing for page source is superseded on styling. A page's source is runtime
metadata, so the console's build-time Tailwind never scans it — authored utility
classNames silently produce no CSS (the exact failure ADR-0065 was written to
prevent; the Task Desk modal's bg-black/50 backdrop rendered transparent). [...]
Do not author Tailwind classes in page source.
ADR-0065 gives the mechanism: the console's CSS scans only apps/console/src, there is
no safelist, so a class in page metadata renders only if it coincidentally appears in
objectui's own source.
Corroborating, in the framework repo:
- the generated react contract (
skills/objectstack-ui/references/react-blocks.md):
"Do NOT use Tailwind className in page source." packages/lint/src/validate-responsive-styles.ts raises style-classname-tailwind:
"these are not compiled from metadata and will silently do nothing."- all three real
kind:'react' pages in examples/app-showcase contain zero
className and style exclusively with inline style objects and
hsl(var(--token)) colors.
This is the highest-cost item: a reader following the guide gets a page that renders
with correct structure, correct data, and no styling, and nothing reports anything.
The half of that paragraph that is still correct — layout containers are not injected
on the react tier — is confirmed by buildComponentScope in
packages/components/src/renderers/layout/react-page.tsx (if (!tag || cfg.isContainer) continue;). Only the "so use Tailwind" conclusion needs replacing.
2. record:* blocks are offered as an in-scope example, and the publish gate rejects them
The guide's scope table says:
The block tags come from the curated public contract (PUBLIC_BLOCKS), converted
from kebab-case to PascalCase: object-grid → <ObjectGrid>, record:details →
<RecordDetails>.
The tag mapping is accurate — buildComponentScope really does inject every public
non-container block, record:* included, so <RecordDetails> resolves at runtime. But
the framework withdrew the family from the tier (REACT_RECORD_BLOCK_ALTERNATIVES and
the ledger above it in packages/spec/src/ui/react-blocks.ts), and os validate now
fails the build on it. Measured against the real CLI:
✗ Author-time rules failed (1 issue)
• page "showcase_renewals_pipeline" › RecordHighlights: RecordHighlights renders
"record:highlights", which reads its record from the record context a record page
mounts — a kind:'react' page never mounts one, so the block renders empty no matter
how it is bound (its objectName/recordId are not read by the renderer).
rule: react-block-needs-record-context
So the guide's illustrative example is the one tag class that cannot ship. Suggest
keeping the mapping rule and changing the example, plus a line noting the exclusion.
3. The Live data sample passes an option key the adapter does not read
adapter.find('showcase_project',{filters: [['status','=','open']]})filters is not a QueryParams key. packages/types/src/data.ts declares $select,
$filter, $orderby, $skip, $top, $expand, $search, $count, and
ObjectStackAdapter.find reads $filter (array form via translateFilterArray). An
unprefixed filters reaches no branch and is dropped, so the sample returns the
object's records unfiltered, with no error.
The framework's own react page uses the working spelling:
{ $filter: ['account', '=', sel] }.
4. The block inventory does not match the published contract
The guide lists the injected data blocks as <ObjectGrid>, <ListView>,
<ObjectForm>, <ObjectKanban>, <ObjectChart>, <ObjectMetric>, <Markdown>, and
demonstrates <ObjectGrid objectName pageSize={25}> and <Block type="object-tree">.
Two different things are being conflated, and both are true separately:
- the runtime scope is everything public and non-container, which is roughly the
guide's list; - the authored contract — what has published props, is checked by
os validate, and
is what an author (human or AI) should write against — is
REACT_BLOCKS: ObjectForm, ListView, ObjectChart, Block. The generated
contract's own footer routes the rest through the escape hatch: "Kanban/calendar/
gantt/timeline/map of an object = <ListView navigation={…} /> with the matching
visualization, or <Block type="object-kanban" …/>."
pageSize is also not a published prop on any of them (pagination is).
Suggest stating the two-tier distinction explicitly rather than listing tags, and
linking the generated contract as the prop authority.
What the port did
Resolved every point toward the framework's sources, and said so in the PR body rather
than quietly choosing. No file in this repo was touched — the framework card's scope
forbids it. The three claims the guide gets right and that were carried over, for the
record: the new Function / no-sandbox security boundary, the react-pages capability
plus OS_PAGE_REACT=off kill switch, and the implicit-export default source-shape
rule (verified against normalizeCode in packages/react-runtime/src/index.tsx).
Filed from the framework repo (objectstack-ai/objectstack) while porting
content/docs/guide/react-pages.mdinto the customer docs site ascontent/docs/ui/react-pages.mdx— framework issue#10210, PR
#10281.
The port was explicitly instructed to re-verify every claim against the framework's
sources rather than copy, and to report contradictions rather than silently pick a
side. Four came up. All were measured against
objectstack@502dc6fe7; none was carriedinto the new page.
This guide is not dead material —
@object-ui/react-runtime's README links it — so thedivergence is live.
1. Tailwind styling — the guide teaches the primitive an ADR amendment retracted
The guide's headline react example styles with Tailwind:
and the "What is in scope" section instructs:
objectstack/docs/adr/0080-ai-authored-ui-jsx-source.mdcarries an explicit headeramendment:
ADR-0065 gives the mechanism: the console's CSS scans only
apps/console/src, there isno safelist, so a class in page metadata renders only if it coincidentally appears in
objectui's own source.
Corroborating, in the framework repo:
skills/objectstack-ui/references/react-blocks.md):"Do NOT use Tailwind className in page source."
packages/lint/src/validate-responsive-styles.tsraisesstyle-classname-tailwind:"these are not compiled from metadata and will silently do nothing."
kind:'react'pages inexamples/app-showcasecontain zeroclassNameand style exclusively with inlinestyleobjects andhsl(var(--token))colors.This is the highest-cost item: a reader following the guide gets a page that renders
with correct structure, correct data, and no styling, and nothing reports anything.
The half of that paragraph that is still correct — layout containers are not injected
on the react tier — is confirmed by
buildComponentScopeinpackages/components/src/renderers/layout/react-page.tsx(if (!tag || cfg.isContainer) continue;). Only the "so use Tailwind" conclusion needs replacing.2.
record:*blocks are offered as an in-scope example, and the publish gate rejects themThe guide's scope table says:
The tag mapping is accurate —
buildComponentScopereally does inject every publicnon-container block,
record:*included, so<RecordDetails>resolves at runtime. Butthe framework withdrew the family from the tier (
REACT_RECORD_BLOCK_ALTERNATIVESandthe ledger above it in
packages/spec/src/ui/react-blocks.ts), andos validatenowfails the build on it. Measured against the real CLI:
So the guide's illustrative example is the one tag class that cannot ship. Suggest
keeping the mapping rule and changing the example, plus a line noting the exclusion.
3. The
Live datasample passes an option key the adapter does not readfiltersis not aQueryParamskey.packages/types/src/data.tsdeclares$select,$filter,$orderby,$skip,$top,$expand,$search,$count, andObjectStackAdapter.findreads$filter(array form viatranslateFilterArray). Anunprefixed
filtersreaches no branch and is dropped, so the sample returns theobject's records unfiltered, with no error.
The framework's own react page uses the working spelling:
{ $filter: ['account', '=', sel] }.4. The block inventory does not match the published contract
The guide lists the injected data blocks as
<ObjectGrid>,<ListView>,<ObjectForm>,<ObjectKanban>,<ObjectChart>,<ObjectMetric>,<Markdown>, anddemonstrates
<ObjectGrid objectName pageSize={25}>and<Block type="object-tree">.Two different things are being conflated, and both are true separately:
guide's list;
os validate, andis what an author (human or AI) should write against — is
REACT_BLOCKS:ObjectForm,ListView,ObjectChart,Block. The generatedcontract's own footer routes the rest through the escape hatch: "Kanban/calendar/
gantt/timeline/map of an object =
<ListView navigation={…} />with the matchingvisualization, or
<Block type="object-kanban" …/>."pageSizeis also not a published prop on any of them (paginationis).Suggest stating the two-tier distinction explicitly rather than listing tags, and
linking the generated contract as the prop authority.
What the port did
Resolved every point toward the framework's sources, and said so in the PR body rather
than quietly choosing. No file in this repo was touched — the framework card's scope
forbids it. The three claims the guide gets right and that were carried over, for the
record: the
new Function/ no-sandbox security boundary, thereact-pagescapabilityplus
OS_PAGE_REACT=offkill switch, and the implicit-export defaultsource-shaperule (verified against
normalizeCodeinpackages/react-runtime/src/index.tsx).