The SDUI authoring preview stops teaching Tailwind in page source; the two plumbing previews declare the exception - #5662
Merged
Conversation
…age source `apps/console/src`'s three ADR-0080 browser preview harnesses authored Tailwind classNames inside real page `source` strings. Page `source` is RUNTIME metadata: the console's Tailwind is compiled at BUILD time by scanning the console's own `src` (`@source '../src/**'` in `index.css`) with no safelist, so a utility class authored in real page metadata produces no CSS and no error anywhere — the ADR-0065 "works only by coincidence" failure, recorded as ADR-0080's 2026-06-30 amendment. These harnesses are the one place in the repo where the rule is violated AND STILL LOOKS RIGHT, because each harness file is itself inside the scanned `src`. Split by what each harness claims to be: - `sdui-tiers-preview.tsx` labels itself "Browser preview for the two AI-authoring tiers", so it is the one making an authoring claim. Its two sources are rewritten to each tier's real primitive — the html tier with `<flex>`'s structured props plus JSON `style` objects, the react tier with inline `style` objects, colours as `hsl(var(--token))`. It now demonstrates what authors are told to write, exercises the token path in a browser, and follows light/dark instead of pinning a fixed slate/indigo palette. - `sdui-jsx-preview.tsx` and `sdui-workbench-preview.tsx` are renderer-plumbing previews. They keep their Tailwind and declare the exception in their headers, naming the build-scan coincidence and pointing at the authoring example. `__tests__/sdui-preview-page-source-styling.test.ts` pins both halves against the shipped rule itself (`validatePageSourceStyling` / `page-source-className-tailwind` from `@objectstack/lint`, the same finding `os validate` gives an author) rather than a local re-implementation: zero findings for the authoring harness, expected findings WITH the header note for the other two, and no third path for a new harness. Part of #5470 Co-authored-by: Claude <noreply@anthropic.com>
…de:fs
The console app's tsconfig is browser-only (`lib: ES2020, DOM`, `types` without
`node`), so a `node:fs` import passes under Vitest and fails `tsc` in the
console's own build — the trap `insecure-origin-crypto.placement.test.ts`
already records. `import.meta.glob('../*-preview.tsx', { query: '?raw' })` reads
the same bytes and is a better enumeration besides: Vite expands it against the
real directory at transform time, so a NEW preview harness is judged without
anyone remembering to list it.
Co-authored-by: Claude <noreply@anthropic.com>
…zero-width `<flex>`'s `align` defaults to `start` (`items-start`), which makes a child with no intrinsic width collapse. Measured in a real browser: the rewritten html source's `<hr>` resolved to `hidden` — Playwright reported `123 x locator resolved to hidden <hr data-obj-type="hr"/>` — while its computed border was correct all along, so nothing static could have caught it. `align="stretch"` is the canonical structured prop for it and keeps the fix on this tier's own primitive. Re-measured: `alignItems: stretch`, and the `<hr>`, `<blockquote>` and `<img>` all report 688px inside a 688px column, matching what the pre-change page rendered. Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-sales
marked this pull request as ready for review
August 22, 2026 07:30
This was referenced Aug 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #5470
Triage settled the remedy fork on the card itself (2026-08-21): (a) for
sdui-tiers-preview.tsx, (b) for the other two. This implements that split. The onejudgement triage left open — whether the rewrite costs visual fidelity the manual dogfood
loop uses — is answered below with a measured before/after, not an opinion: it costs
none, and it gains dark mode.
Part of, notFixes, because the card's headline numberis wrong (see The counts) and the maintainer may want the corrected figure recorded
before it closes.
The defect
A page's
sourceis runtime metadata. The console's Tailwind is compiled at buildtime by scanning the console's own
src—apps/console/src/index.cssline 12:— with no safelist. So a utility class authored in real page metadata produces no CSS and
no error anywhere. That is the ADR-0065 "works only by coincidence" failure, recorded as
ADR-0080's 2026-06-30 amendment, and
os validatereports it aspage-source-className-tailwind.The three harnesses are the one place in the repo where the rule is violated and still
looks right, because each harness file is itself inside that scanned
src— their ownheaders said as much. Lift one of their source strings into a real page and every class
evaporates silently.
The counts — the card's
79is a whole-filegrep -cRe-measured on the current tip. The three files are byte-identical to the card's
measurement commit (blob hashes at
9bd753682and at branch point7e811687amatch, andno commit touched them in between), so the difference is method, not drift:
className=inside pagesourcesdui-jsx-preview.tsxsdui-tiers-preview.tsxsdui-workbench-preview.tsxThe card's numbers reproduce exactly as:
— which counts matching lines, not occurrences, and reads the whole file, so it
includes the harnesses' own console JSX (legitimate Tailwind, outside any page source). The
middle column counts occurrences strictly inside the page-
sourcetemplate literals; theright column is what
validatePageSourceStylingitself reports, and it agrees. The cardundercounts the violation.
What changed
(a)
sdui-tiers-preview.tsx— rewritten to each tier's real primitive. It is the filethat labels itself "Browser preview for the two AI-authoring tiers", so it is the one
making an authoring claim. Its two sources now carry zero
className:kind:'html'— layout from the blocks' own structured props, everything else a JSONstyleobject:Quoted keys and quoted values are load-bearing: the html tier materializes a braced
attribute with
JSON.parseand keeps anything else as a deferred{ $expr }, soJS-object syntax parses without complaint and renders nothing. A test covers exactly
that.
kind:'react'— inlinestyleobjects, real JS so shared style objects are just consts.Colours on both tiers as
hsl(var(--token)).(b)
sdui-jsx-preview.tsxandsdui-workbench-preview.tsx— kept, and declared. Theyare renderer-plumbing previews (does the tier compile and mount the registered blocks; do
the real
ListView/ObjectFormblocks interact inside an executed react page). Eachheader now carries an
ADR-0080 EXCEPTIONnote naming the build-scan coincidence, sayingthe source is not a pattern to copy, and pointing at the authoring example.
A gate that can see the exception.
__tests__/sdui-preview-page-source-styling.test.tsruns the shipped rule —
validatePageSourceStylingfrom@objectstack/lint, the samefinding an author gets from
os validate— not a local re-implementation, because a copyof a rule cannot disagree with itself. It pins: zero findings for the authoring harness;
expected findings with the header note for the other two; and no third path, over an
import.meta.glob('../*-preview.tsx')enumeration so a new harness is judged withoutanyone remembering to list it.
Verification
The gate is measured capable of failing. Three mutation legs, each confirmed on disk
(anchor counts before/after — an editor's exit code proves nothing) and each restored under
a
trap, restoration verified by blob hash:className="p-4"to the rewritten html sourceADR-0080 EXCEPTIONline fromsdui-jsx-preview.tsxLeg 3's message is the trap stated by the tool itself:
Each leg was re-measured against the final tree after the test's read path changed. The
in-test control specimen is committed too: the rule must fire on a dirty specimen and stay
silent on a clean one, so a green run means clean, not rule inert.
Rendered in a real browser — Chromium (Playwright) against
vite devservingsdui-tiers-preview.html, not merely asserted:display: flex,flex-direction: column,gap: 24pxstylematerializes: 12 styled nodes; the list →list-style-type: disc; padding-left: 24px; the blockquote → token background, 4px left border, 16px paddingClicked 1 times; sort by name → first rowAcme(wasUmbrellaby amount); 5 rows,Total: $465,000.dark—h1
rgb(2,8,23)→rgb(248,250,252), bodyrgb(246,246,249)→rgb(14,14,16),blockquote
rgb(237,237,242)→rgb(39,39,43). The old sources hard-coded slate/indigoand did not move.
One real regression, caught only by the browser and fixed. The
flexblock'saligndefaults to
start(items-start), so the horizontal rule — no intrinsic width —collapsed. Playwright reported:
…while its computed border was correct all along, so nothing static could have caught it.
align="stretch"is the canonical structured prop for it; re-measured, the rule, theblockquote and the image all report 688px in a 688px column, matching the pre-change page.
Fidelity, measured against the base. The pre-change file was checked out at
7e811687a(26classNames — the pristine count re-asserted), re-rendered, and compared.Same structure, same content, same richness; every difference is the palette moving from
fixed slate/indigo to theme tokens. Two things that look like losses are not from this
change and are identical in both renders: the
placehold.cobanner fails to load (thesandbox proxy blocks outbound;
ERR_TUNNEL_CONNECTION_FAILED, one occurrence in eachversion), and the space between a text run and an adjacent inline element is dropped —
A <strong>x</strong> pagerenders asAxpage— filed as #5661, asdui-parserdefectwith its own fork. The other console error is a
/favicon.ico404.Gates
Run at the final commit
70fc8b9a6, clean tree, each exit code captured before anypipe and each verdict quoted from the gate's own output:
ESLint — a declared narrowing, not a skipped run.
eslint apps/consolewas run ratherthan the repo-wide
eslint .: 168 files enumerated by ESLint itself, count read from--format json, 0 errors (200 warnings, all pre-existing@typescript-eslint/no-explicit-anyon the harnesses'as anyschema casts — same countand kind as before). The narrowing is sound because the repo's
eslint.config.jsdeclaresno
parserOptions.projectand noprojectService, i.e. no type-aware linting, so norule's verdict on a file can depend on another file's contents — and every file this branch
touches is under
apps/console(plus a changeset.mdand the lockfile, neither of whichfiles: ['**/*.{ts,tsx}']matches). CI runs the full farm regardless.Notes
apps/console's viteconfig declares no
build.rollupOptions.input— resolved config, measured:inputunset→ vite's default single
index.htmlentry — so the threesdui-*-preview.htmlentriesand the modules behind them are dev-server-only and never enter
dist/.@object-ui/console'sfilesomitssrc, and itsexportsmap has exactly one entry(
.→./plugin.js, built fromtsconfig.plugin.jsonincludingplugin.tsalone,which imports nothing from
src/). Re-verified this round; the shape has not moved.@objectstack/lintis added toapps/console'sdevDependencies(^17.0.0, matching@object-ui/app-shell) because the test imports the real rule. It is not in the workspaceroot's devDependencies, so
check-phantom-dependencieswould have called it a phantom.packages/components/src/renderers/**andthe
packages/typesdeclaration sites. Noted while reading, not fixed:packages/components/src/renderers/layout/page.tsx:541still carries the retracted"HTML+Tailwind" framing in a comment — that is The ADR-0080 page-source styling retraction reached 3 of 11 prose sites — 8 more still teach "JSX/HTML + Tailwind", in two spellings #5461's grep cannot see #5469's surface, not this one's.
Generated by Claude Code