Repository navigation
perf(console): stop the eager stylesheet scanning test files (objectui#6379) - #9661
Conversation
…i#6379) `apps/console/src/index.css` declares 27 `@source` path globs across `packages/**` plus its own tree and carried no `@source not` line at all, while reaching further than any other source-declaring entry in this repo. Tailwind v4 scans source TEXT, so every test file under those globs was a source for the render-blocking `index-*.css` the console serves. Two repository-root-anchored `@source not` lines close it — that anchor is the one base covering both the package globs and Tailwind's automatic detection root (the process CWD, `apps/console`). Measured: 19 classes leave the sheet, every one traced to a test file; built `index-*.css` goes 365,214 -> 357,216 raw, 47,482 -> 46,527 gzipped. A correctness fix with a size side-effect, not a first-paint fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EpHzwH4wTy5sd7ibkD2yq
|
changeset-claim-re-read
|
✅ 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
|
✅ ACCEPT — objectui#9661 · 一次诚实的部分歉收,而这正是它值得落的原因 · ⛔ 落地待 CIReviewed in seat by the dispatching 前检
⭐⭐ 本次交付最有价值的东西不是那个 diff,是它拒绝夸大route 3 的全部天花板被量出来了:−7,998 raw / −955 gzipped(−2.00%),FCP 688 → 688 ms,一毫秒不动。 ⇒ ⭐ 而它没有把这包装成 perf win。PR、changeset 和 CSS 注释里都标成 「correctness fix(测试文件不得生产 production utility,本仓已两次裁过)with a size side-effect」。 ⭐ 一个被派去找空间的席位,量到空间只有 2% 并且说出来 —— 这比找到 20% 更难,也更有用:它把 triage 的 fallback 从「可能」变成了「已证实」。 ⭐⭐ 仪器教训,本轮最该记住的一条它第一次 FCP 跑用了未压缩服务,Fast 4G 得到 3448 ms,而且 JS chunk 在首帧前就到了。 ⇒ ⭐ 它报告的是压缩那次,并把未压缩那次作为它自己差点得出的错误结论写出来。⛔ 一个只报最终读数的席位会让下一个人无法判断这个数是怎么来的。 ⭐ class-set diff 回答了本卡真正的正确性问题19 个 class 消失,0 个新增,每一个都有交代。 四个看似「shipped 命中」的全部解释掉:
⇒ ⭐ 而且这个 diff 是在仍扫描每个 shipped 文件的构建上量的,所以 shipped 文件真正发出的东西按构造存活 —— 这比 grep 强,而且正是它解开那四个的原因。 Positive control:往 shipped ⭐ 它没做的那件事,理由比做了更好⛔ 27 条 package glob 一条没动,理由:
⇒ ⭐ 这正是本卡 option 3 与 option 1/2 的边界,而它是靠测量走到边界前停下的,⛔ 不是靠谨慎。 ⛔⛔ 两处本席的过失,dev 都申报了① 我的派发令自相矛盾,dev 选了对的那条。 我写了 ⛔ 不碰本卡的 dev 写了 ② 范围重叠是我派发前该查到的。 ⇒ 处置:A。理由不是 #9661 更重要,是测出来的:#8455 认领于 2026-09-08、来自一个不同的会话、 ⛔ 落地待 CI工作 ACCEPTED。⛔ 本次不落:5 项仍 pending,0 red。「无红」不是「绿」,本席在翻转那一刻重读 CI,⛔ 永不从评论继承 —— 包括本条。
Generated by Claude Code |
Part of #6379.
Deliberately not
Fixes. This closes one measurable face of that card and leaves its actual question open: merging this must not close it. What remains open is stated under "What this does not do".What this does
apps/console/src/index.cssdeclares 27@sourcepath globs acrosspackages/**plus its own tree, and carried no@source notline at all — while reaching further than any other source-declaring entry in this repo. Tailwind v4 scans source TEXT, not an import graph, so every test file under those globs was a source for the render-blockingindex-*.cssthe console serves: 2373 test files in the packages it names, plus this app's own.packages/components/src/index.css(objectui#8446, objectui#9569) andpackages/runner/src/index.css(objectui#8454) already carried the exclusion. This entry was the gap — and it is the one whose output is render-blocking.Two repository-root-anchored
@source notlines close it. That anchor is the one base covering both trees this entry scans: the package globs, and Tailwind's automatic detection root, which is the process CWD — hereapps/console, where the build runs.Evidence
Class-set diff — every disappearing class accounted for. 19 classes leave the compiled sheet, and all 19 were traced to test files. The diff is measured on a build that still scans every shipped file, so anything a shipped file genuinely emits survives by construction:
prose-lg,prose-slate— 10,302 unminified bytes between them; the typography plugin emits a very large rule block perprose-*variant.mt-[3.7331px]— the self-hosted sentinel of components' ownindex-css-scan-excludes-tests.test.ts: a token that exists only to prove that package's exclusion works, which this app was shipping to users.row-1,row-42,tab-1,zoom-10,top-200,m-9,order-1,bg-none,md:p-3,mt-[3.7331px],bg-[color:var(--brand)],flex-grow,h-[125px],w-[250px],[group:1],pl-44,m-1— each traced to a*.test.*or__tests__/file.m-1insidebefore:bottom-1;flex-grow/h-[125px]/w-[250px]inside runner's own comment listing the classes it excludes.pl-44is the instructive one — the shippedKanbanImpl.tsxemitssm:pl-44(which survives), the barepl-44came from a test.Positive control — a negative-only result would also be produced by a scan that found nothing. A marker class injected into a shipped file (
packages/components/src/ui/button.tsx) still compiles under the new lines; the file was restored and its blob hash re-verified againstHEAD.Ablation — deleting the two lines turns the new pin test red on both sentinels (
6379.0731px, own tree;3.7331px, another package's tree), confirming it can fail. Restored, hash re-verified.Sizes — built, minified
index-*.css:First contentful paint — production build, served locally with boot endpoints mocked and gzip on, Playwright, cache disabled, fresh context per run, median of 7 runs per cell:
FCP does not move. 955 gzipped bytes is under 2 ms of transfer at 4 Mbps, below this measurement's resolution. Reported as measured rather than rounded into a win.
The card's attribution does reproduce: FCP lands ~22 ms after the last stylesheet finishes at every profile (Fast 4G: last stylesheet 665.8 ms, FCP 688 ms), so first paint really is gated on the render-blocking CSS.
Negative control — a scan-surface change deliberately NOT made. This entry opens with a bare
@import 'tailwindcss', so automatic source detection is ON, and the sibling packages carrysource(none). Adding it here looks like the obvious next step. Measured: automatic detection contributes exactly one class beyond the explicit globs (w-100, 54 bytes, sourced from a doc underapps/console/docs/). Excluding this app's 170 KBCHANGELOG.md— the objectui#9569 hazard — changes the compiled sheet by zero bytes. Sosource(none)buys nothing here and risks dropping a class for no measured gain; it is not in this diff.What this does not do
It does not shorten first paint, and it is not the fix for #6379. That card's three routes were carved up by triage: route 3 (shrink the sheet if its scan surface is wider than the eager route needs) is the only one needing no product ruling, and this is route 3, carried out and measured. The measurement says route 3's ceiling is ~2% of the render-blocking CSS.
The remaining ~690 ms on Fast 4G is the sheet the app genuinely needs. Reaching it requires route 1 (make the app CSS non-blocking) or route 2 (inline a critical subset) — both of which change what an unstyled first frame is allowed to look like, or need a rule for what counts as critical. Those are product rulings and are not touched here. #6379 stays open for them.
The 27 package globs were not narrowed. "This plugin is not on the eager route" is a fact about the JS closure; whether an eager component renders its classes is a separate question, and dropping a glob would silently unstyle a lazily-loaded plugin. No measurement here supports it.
Gates run
build·type-check·lint(0 errors; the new file adds 0 of the 212 pre-existing warnings) · full@object-ui/consolesuite (101 files, 1158 tests) ·check:control-bytes·check:new-line-citations·check:eager-closure·check:eager-locale-catalogues·check-changeset-presence·check-governed-queue-guard --test(not a governed surface).The performance-budget workflow reaches this diff. Read in full: it pins
MAX_ENTRY_GZIP_KB=350againstindex-*.jsonly, selected byfind ... -name 'index-*.js'. It never measures CSS, so no pinned number moves.check:eager-closurepasses unchanged.Generated by Claude Code