diff --git a/.changeset/page-source-tailwind-prose-retraction-5469.md b/.changeset/page-source-tailwind-prose-retraction-5469.md new file mode 100644 index 0000000000..f125f38941 --- /dev/null +++ b/.changeset/page-source-tailwind-prose-retraction-5469.md @@ -0,0 +1,51 @@ +--- +'@object-ui/react-runtime': patch +'@object-ui/sdui-parser': patch +'@object-ui/components': patch +--- + +Documentation no longer teaches the "JSX/HTML + Tailwind" framing for a page's +`source`, which ADR-0080's own 2026-06-30 header amendment (under ADR-0065, +Accepted) retracted. objectui#5461 corrected three sites; a multiline census +found eight more, in three spellings a line-oriented grep could not see. + +A page's `source` is *runtime metadata*. The console's Tailwind is compiled at +build time by scanning the console's own `src`, and there is no safelist, so it +never sees your page: an authored utility class produces CSS only by coincidence +(when objectui already ships that exact class) and otherwise produces nothing, +with no error anywhere. That is the ADR-0065 "works only by coincidence" failure +mode, and it is how a modal's `bg-black/50` backdrop reached production fully +transparent. `os validate` reports it as `page-source-className-tailwind`, a +warning on kinds `html`, `react` and `jsx`, shipped in `@objectstack/lint@11.5.0`. + +The tiers themselves are unchanged and every load-bearing claim survives — +parse-never-execute, the untrusted-author safety argument for `html`, and the +deprecated `'jsx'` alias. Only the styling primitive is corrected, to the wording +`content/docs/guide/react-pages.md` §Styling already uses: + +| `kind` | Style with | +|---|---| +| `"html"` | The blocks' own structured props (`` `` ``, `` `` ``) plus a JSON `style` object. | +| `"react"` | Inline `style` objects. | + +Colors on both tiers come from the theme as `hsl(var(--token))`. + +Why each package has an entry — each was measured against its built artefact, not +assumed: + +- **`@object-ui/react-runtime`**: `README.md` is published to npm (npm includes + `README.md` in the tarball regardless of `files`). Its "no sandbox" callout is + the paragraph that routes untrusted-author work to the `html` tier, and it + carried the retracted framing line-wrapped across `:17-18`. It also gains the + §Styling section it was missing — the absence is why the framing survived here. +- **`@object-ui/sdui-parser`**: the corrected header of `src/types.ts` projects + verbatim into the published `dist/types.d.ts`. +- **`@object-ui/components`**: the corrected header of + `src/renderers/basic/html-elements.tsx` projects verbatim into the published + `dist/renderers/basic/html-elements.d.ts`. The `kind === 'html'` dispatch-arm + comment in `src/renderers/layout/page.tsx` does **not** project (it is inside a + function body) and is included here only because the same package already owes + an entry. + +No behaviour change: this is prose only. `CHANGELOG.md` occurrences are +deliberately untouched — immutable release history. diff --git a/content/docs/components/basic/div.mdx b/content/docs/components/basic/div.mdx index 6b177b59b4..efe1beaa3e 100644 --- a/content/docs/components/basic/div.mdx +++ b/content/docs/components/basic/div.mdx @@ -17,12 +17,17 @@ description: "Generic container element - use Shadcn components instead" The Div component is a generic container element. **It is now deprecated in favor of semantic Shadcn-based components** that provide better accessibility, consistency, and design system integration. **Scope of this deprecation: JSON-authored pages.** A `kind:'html'` page is written as -constrained JSX/Tailwind text that the engine compiles (parses, never executes) into +constrained JSX text that the engine compiles (parses, never executes) into nodes, tag name straight through — there, the plain box tag is part of that tier's own vocabulary and stays fully supported, because no other spelling of it exists for an author to migrate to. The dev-build deprecation notice is therefore reported for JSON-authored nodes only, and never for what the html tier compiled from its own source. +Styling on that tier is the blocks' own structured props (``, +``) plus a JSON `style` object — **not** Tailwind utility classes, which +produce no CSS at all when authored in page `source`. `os validate` reports that as +`page-source-className-tailwind`. See the React Pages guide, §Styling. + ## Migration Guide Instead of using `div`, use these Shadcn alternatives: diff --git a/content/docs/components/basic/span.mdx b/content/docs/components/basic/span.mdx index cf6a16199b..e9d449a328 100644 --- a/content/docs/components/basic/span.mdx +++ b/content/docs/components/basic/span.mdx @@ -19,12 +19,17 @@ import { DemoGrid } from '@/app/components/ComponentDemo'; The Span component is an inline container. **It is now deprecated in favor of semantic Shadcn-based components** that provide better accessibility and design system integration. **Scope of this deprecation: JSON-authored pages.** A `kind:'html'` page is written as -constrained JSX/Tailwind text that the engine compiles (parses, never executes) into +constrained JSX text that the engine compiles (parses, never executes) into nodes, tag name straight through — there, the plain inline tag is part of that tier's own vocabulary and stays fully supported, because no other spelling of it exists for an author to migrate to. The dev-build deprecation notice is therefore reported for JSON-authored nodes only, and never for what the html tier compiled from its own source. +Styling on that tier is the blocks' own structured props (``, +``) plus a JSON `style` object — **not** Tailwind utility classes, which +produce no CSS at all when authored in page `source`. `os validate` reports that as +`page-source-className-tailwind`. See the React Pages guide, §Styling. + ## Migration Guide Instead of using `span`, use these Shadcn alternatives: diff --git a/packages/components/src/__tests__/div-deprecation-provenance.test.tsx b/packages/components/src/__tests__/div-deprecation-provenance.test.tsx index 09cfe964fb..6f015e9fb3 100644 --- a/packages/components/src/__tests__/div-deprecation-provenance.test.tsx +++ b/packages/components/src/__tests__/div-deprecation-provenance.test.tsx @@ -9,7 +9,7 @@ /** * The `div` deprecation notice is scoped BY PROVENANCE (objectui#4000). * - * A `kind:'html'` page is authored as constrained JSX/Tailwind text that our own + * A `kind:'html'` page is authored as constrained JSX text that our own * parser compiles (never executes) into SDUI nodes, tag name straight through. * So an author writing the plain box tag in that tier gets a node the DEPRECATED * renderer serves — and the notice fired at them, recommending replacements that diff --git a/packages/components/src/__tests__/span-deprecation-provenance.test.tsx b/packages/components/src/__tests__/span-deprecation-provenance.test.tsx index d1ed7f423c..5d8df8b0aa 100644 --- a/packages/components/src/__tests__/span-deprecation-provenance.test.tsx +++ b/packages/components/src/__tests__/span-deprecation-provenance.test.tsx @@ -10,7 +10,7 @@ * The `span` deprecation notice is scoped BY PROVENANCE (objectui#4917, * applying the ruling made for `div` in objectui#4000). * - * A `kind:'html'` page is authored as constrained JSX/Tailwind text that our own + * A `kind:'html'` page is authored as constrained JSX text that our own * parser compiles (never executes) into SDUI nodes, tag name straight through. * So an author writing the plain inline tag in that tier gets a node the * DEPRECATED renderer serves — and the notice fired at them, recommending diff --git a/packages/components/src/renderers/basic/html-elements.tsx b/packages/components/src/renderers/basic/html-elements.tsx index bc57c508a7..5149d3dc4d 100644 --- a/packages/components/src/renderers/basic/html-elements.tsx +++ b/packages/components/src/renderers/basic/html-elements.tsx @@ -7,7 +7,7 @@ * * Safe native HTML element passthrough renderers (ADR: kind:'html'). * - * A `kind:'html'` page is authored as a constrained JSX/Tailwind string that is + * A `kind:'html'` page is authored as a constrained JSX string that is * PARSED (never executed) into the SDUI tree. For that tier to live up to its * name, the everyday HTML tags an author reaches for — headings, paragraphs, * lists, links, images, emphasis — must each resolve to a renderer (otherwise diff --git a/packages/components/src/renderers/layout/page.tsx b/packages/components/src/renderers/layout/page.tsx index eae4f5eed1..0c14df03c0 100644 --- a/packages/components/src/renderers/layout/page.tsx +++ b/packages/components/src/renderers/layout/page.tsx @@ -538,9 +538,15 @@ export const PageRenderer: React.FC<{ if (kind === 'react') { return ; } - // `kind:'html'` (formerly 'jsx') — author-written constrained JSX/HTML+Tailwind - // compiled (parsed, never executed) to a SchemaNode tree and rendered. The - // legacy 'jsx' value is still accepted as a deprecated alias. + // `kind:'html'` (formerly 'jsx') — author-written constrained JSX compiled + // (parsed, never executed) to a SchemaNode tree and rendered. The legacy + // 'jsx' value is still accepted as a deprecated alias. + // Styling on this tier is the blocks' own structured props + // (``, ``) plus a JSON `style` object — + // NOT Tailwind utility classes. `source` is runtime metadata, so the build's + // Tailwind scan never sees it and an authored class produces no CSS and no + // error anywhere; `os validate` reports `page-source-className-tailwind`. + // (ADR-0065; ADR-0080's 2026-06-30 amendment.) if (kind === 'html' || kind === 'jsx') { const src = (schema as { source?: string }).source ?? ''; const { tree, diagnostics } = compile(src, getJsxManifest()); diff --git a/packages/react-runtime/README.md b/packages/react-runtime/README.md index 0d5903e59d..705061bb09 100644 --- a/packages/react-runtime/README.md +++ b/packages/react-runtime/README.md @@ -14,8 +14,8 @@ lazy-load it behind a capability flag. > and everything in scope. It is not isolated, not restricted, and not > validated. Only run source you would run as first-party code. > -> For untrusted authors use **`kind:'html'`** instead: constrained JSX/HTML + -> Tailwind, parsed into a schema tree and never executed. See +> For untrusted authors use **`kind:'html'`** instead: constrained JSX, parsed +> into a schema tree and never executed. See > [React pages](../../content/docs/guide/react-pages.md). ## Installation @@ -86,6 +86,23 @@ that reads `scope.import`: Anything not provided there throws `Module not found`. +## Styling + +**Do not author Tailwind utility classes in page `source`** — on either tier. A +page's `source` is *runtime metadata*: the console's Tailwind is compiled at +build time by scanning the console's own `src`, and there is no safelist, so it +never sees your page. An authored utility class produces CSS only by coincidence +(when objectui already ships that exact class) and otherwise produces nothing, +with no error anywhere. `os validate` reports it as +`page-source-className-tailwind`, a warning on both tiers. (ADR-0065; ADR-0080's +2026-06-30 amendment.) + +Style a `kind:'react'` page with inline `style` objects, and a `kind:'html'` page +with the blocks' own structured props (``, ``) +plus a JSON `style` object. Colors on both tiers come from the theme as +`hsl(var(--token))`, so a page follows light/dark and whatever theme the +deployment installs. + ## Stable scope identity matters Every evaluation produces a **new** component function — a new element *type* — diff --git a/packages/sdui-parser/src/types.ts b/packages/sdui-parser/src/types.ts index 0f83034f73..78c9bac5d4 100644 --- a/packages/sdui-parser/src/types.ts +++ b/packages/sdui-parser/src/types.ts @@ -2,7 +2,7 @@ * ObjectUI — SDUI JSX-source parser (ADR-0080) * * Types shared by the constrained JSX-source compiler. The parser turns a - * constrained JSX/HTML+Tailwind *text* into the existing SDUI `SchemaNode` + * constrained JSX *text* into the existing SDUI `SchemaNode` * tree. It PARSES — it never executes. No `import`, no `eval`, no JS. */