Repository navigation
fix(components): pin source(none) on the root stylesheet entry so prose stops compiling into the published sheet - #9579
Merged
Conversation
…root stylesheet entry
`packages/components/src/index.css` opened with a bare `@import 'tailwindcss';`,
the only one of this repo's four stylesheet entries without a `source(none)`
pin. Automatic source detection was therefore on, rooted at the process CWD, and
it compiled every class-shaped token in this package's prose into the published
`dist/index.css`.
Two consequences, both measured on this branch:
* The artifact depended on where the build was launched from -- compiling with
the CWD at this package and compiling from the repo root produced different
stylesheets from the same commit.
* `pnpm changeset:version` writes changeset bodies into `CHANGELOG.md`, a
scanned file, so a changeset that merely quotes a class name promoted it to a
real utility here. The plugin builders subtract every rule this sheet ships,
so that class then vanished from THEIR published sheets while their
components still emitted it.
The entry keeps the FULL `@import 'tailwindcss'` -- this is the root sheet and
preflight, the `@theme` block and the base layer are its to emit; it gains only
`source(none)`, so the declared `@source` lines are the whole input set.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeoGH6X7MtfbjcRDscyXi1
…claims the pin turned false The `@source` pin for this package's stylesheet entry carried a rationale that this branch falsifies: it said the entry opens with a bare `@import 'tailwindcss'` so automatic source detection is ON and rooted at the process CWD. It is off now. The docblock states the current mechanism instead, including the part the old text got backwards -- `base` governs only the automatic root, never the `@source` lines, which resolve against the entry stylesheet's own directory. The new test is the durable half. It compiles the published sheet and asserts `.flex-shrink-0` -- a deprecated Tailwind v3 alias no shipped source emits, which was in the published bundle solely because the changelog entry announcing its deprecation names it -- is absent, behind a lit control asserting `CHANGELOG.md` still names it. Reverting the entry to its pre-fix bare import turns this test red; it passes on the fix. `changeset-release.yml`'s design note claimed the version step cannot move a source byte, which is what made this defect invisible from the release lane: a generated CHANGELOG is a stylesheet source input. One sentence, next to the claim it qualifies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeoGH6X7MtfbjcRDscyXi1
…t-inputs ledger The pin added alongside the `source(none)` fix reads this package's `CHANGELOG.md` as a lit control, which makes it a candidate the markdown-input scanner requires a human verdict on. Recorded with what it reads (`packages/components/CHANGELOG.md`) and what the scanner offered that it does not (the root `CHANGELOG.md`, which the bare-name resolution also reaches). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeoGH6X7MtfbjcRDscyXi1
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
|
1 similar comment
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-justin
marked this pull request as ready for review
September 16, 2026 00:25
os-justin
enabled auto-merge
September 16, 2026 00:25
This was referenced Sep 16, 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.
Fixes #9569
packages/components/src/index.cssopened with a bare@import 'tailwindcss';— the only one of this repo's four stylesheet entries without asource(none)pin. It now reads@import 'tailwindcss' source(none);, keeping the FULL Tailwind import because this is the root sheet: preflight, the@themeblock and the base layer still emit exactly as before.The card's mechanism sentence is falsified, and the PM had already measured it
The card asks for "the
source(none)pin its three siblings carry (@import 'tailwindcss' source(none);… mirroringfields)". The siblings carry no such thing.packages/fields/src/index.cssstates verbatim "This file has no@import 'tailwindcss'on purpose"; whatfields,plugin-gridandplugin-kanbancarry is@referenceon components' entry plus a utilities-only@import 'tailwindcss/utilities.css' layer(utilities) source(none);. Mirroring them literally would have dropped preflight and the shared theme from the only sheet that ships them. The PM seat flagged this before dispatch and the landed spelling is the one it proposed; I verified it is legal Tailwind v4 and that preflight and the theme survive it.Three things measured rather than assumed
1. The
@sourcelines actually fire. This was the highest-risk failure mode: once detection is off, those lines are the only thing keeping the sheet non-empty, and a near-empty sheet builds just as green as a correct one.packages/runner/src/index.cssrecords five@sourcelines that were each wrong by onesrcsegment and contributed nothing. Two legs, both restoring fromHEADwith the restore proven by blob hash:@source '../src/**/*.{ts,tsx}'collapses the compilation from 158,345 bytes / 1289 classes to 9,694 bytes / 4 classes. The line is load-bearing.@sourcepaths to absolute and moving the compile base to the package root produces a byte-identical sheet. So the relative spelling lands exactly onpackages/components/src, andbasegoverns only the automatic root, never the@sourcelines.2. Detection roots at the PROCESS CWD, not at
packages/components/. The card says the latter;fieldsandrunnersay the former, and they are right. Same commit, same command: 1312 classes / 161,225 bytes with the cwd at the package, 3303 classes / 439,726 bytes from the repo root. After the pin the compiled sheet is byte-identical from either directory, which it was not before.3. Class census re-derived, not quoted. 1312 → 1289, so exactly 23 leave, and each one traces to a prose file: 12 to
CHANGELOG.md, 3 toREADME.md, 2 toREADME_SHADCN_SYNC.md, 4 to the JSON examples in the renderer docs, 1 toshadcn-components.json, andinvertto an ordinary English word inside a comment intsconfig.test.json. Zero of the 23 is emitted by any shipped source underpackages/components/src, checked with a matcher that respects Tailwind candidate boundaries — a substring grep scoresdisabled:cursor-not-allowedas a hit forcursor-not-allowed, which is a different candidate producing a different rule — and run with three lit positive controls (bg-background,text-muted-foreground,w-fullare found in 25, 59 and 59 shipped files).MUST_SURVIVE, both legs, on both kinds of injection
Reproducing what
changeset:versiondoes — appending the changeset body line topackages/components/CHANGELOG.md— then running each plugin's own builder withwrite: falseagainst the components sheet compiled from the entry as it stands on each leg:text-muted-foreground/60(not in MUST_SURVIVE)text-muted-foreground/60bg-card/60(in MUST_SURVIVE)The subtraction removed 1 utility(ies) that only this build can produce: .bg-card/60bg-card/60All six MUST_SURVIVE entries pass for
plugin-kanban,plugin-gridandfieldson the AFTER leg.Beyond the card: this is already breaking three published packages today
Running the same harness with a non-Tailwind injection isolates the fix from the injection, and the plugin sheets still change — the prose contamination is stripping classes from sibling published artifacts on
mainright now, with nochangeset:versionneeded. Restored by this PR:@object-ui/plugin-kanban—h-[600px],sm:px-6,sm:py-4@object-ui/plugin-grid—bg-blue-50,cursor-not-allowed@object-ui/fields—bg-blue-50,bg-blue-500,bg-gray-50,text-whiteEach is emitted by that package's own shipped source (
KanbanImpl.tsxspellspx-4 sm:px-6,FormulaBar.tsxspellscursor-not-allowed,ImageLightbox.tsxspellstext-white), and each was being subtracted out because components' prose-polluted sheet happened to contain it. That is why the changeset names four packages, not one.Deviations from the card, all measurement-driven
minor, notpatch. Removing utilities from a published stylesheet contracts a published artifact. This repo forbidsmajoroutside the@objectstackmajor-sync release and ships its own breaking changes asminorwith the break spelled out, which is what the changeset body does.@sourcepin test needed repairing, and gained a leg. Its docblock asserted the entry "opens with a bare@import 'tailwindcss', so automatic source detection is ON" — a claim this branch falsifies, and it also hadbasebackwards. Corrected, and a second test now pins the prose door shut: it asserts.flex-shrink-0is absent from the compiled sheet behind a lit control assertingCHANGELOG.mdstill names it. Reverting the entry to its bare import turns that test red (vitest exit 1); it passes on the fix.scripts/markdown-test-inputs.mjsneeds a verdict for it. Adjudicated with what it reads and what the scanner offered that it does not.flex-shrink-0is worth a second look: it is a deprecated Tailwind v3 alias this repo deliberately migrated away from, and it was shipping in the published bundle solely because the changelog entry announcing that migration names it. Same forw-[--sidebar-width]and theorigin-[--radix-pair — the changelog entry recording the move to v4 syntax quotes the old spelling, and that quote recreated the v3 classes.changeset-release.ymlOne sentence, next to the claim it qualifies.
THE VERSION STEP CANNOT MOVE A SOURCE BYTEis the design note that made this defect invisible from the release lane: a generatedCHANGELOG.mdis a Tailwind source input, which is exactly how a changeset body reacheddist/index.css.Gates
Derived by hand from this repo's own
package.jsonand.github/workflows/, ⛔ not from the dispatch prompt.scripts/pm/dispatch-gates.mjslives only inobjectstackand refuses to answer for this repo — asserted with--repo objectstack-ai/objectui, exit 2: "Gate families are derived from the workflows and check scripts of the tree this process runs in."check-changeset-presencecheck-changeset-no-majorcheck-changeset-fixedcheck-changeset-claims(report-only)check-control-bytescheck-new-cross-file-line-citationscheck-lint-coverageeslint --no-inline-configon the changed filescheck-governed-queue-guard --self-test@object-ui/componentstype-checkvitest run packages/components/vitest run scripts/__tests__NOT MEASURED, with reasons, ⛔ not read as green:
check-governed-queue-guard(exit 1, needsGITHUB_EVENT_PATH; it refuses to exit 0 when it cannot look),check-required-check-set(exit 2, HTTP 401 on the branch-rules API),check-merge-queue-head(exit 2,GITHUB_REPOSITORYunset). All three are CI-credentialed patrols, and none reads a path in this diff.This diff touches no governed surface — classified against the guard's own
GOVERNED_SURFACESexport with three lit controls (AGENTS.md,docs/adr/…,skills/…all classify correctly).Acceptance notes
objectui#8455coverspackages/runner/src/index.css, which carries the same bare import: its census names that row explicitly as published, and its shape C is a repo-wide gate over exactly this. It genuinely covers it, so this PR does not widen to runner and no duplicate card was filed. Its shape A is this card, and it predicted the removal at 23 rules including.flex-shrink-0— which is what was measured here.objectui#9569's own body citesplugin-published-stylesheet.test.ts:196/:252andKanbanImpl.tsx:492, cross-file line addresses of the form this repo's#11bans. An issue body is not a carrier that rule names, and nothing re-reads it. Successor: none.packages/components/src/index.cssspells its scan@source '../src/**'where the three siblings spell'./**'. Both resolve to the same directory, proven byte-identically above, so this is cosmetic drift, not a defect. Successor: whoever implementsobjectui#8455's repo-wide gate.🤖 Generated with Claude Code
https://claude.ai/code/session_01XeoGH6X7MtfbjcRDscyXi1
Generated by Claude Code