Uh oh!
There was an error while loading. Please reload this page.
catalog: author layout props as props, and rename spacing to gap (#4891, #4890) - #5691
Conversation
…4891, #4890) Two cards, one sweep over the same 39 catalog files. #4891 — 140 nodes across 33 files were already `flex` / `stack` / `container` and still hand-wrote, in `className`, props their own type declares: 231 tokens in all (`flex` 221, `container` 7, `stack` 3, `grid` 0 — re-measured on `origin/main`, reproducing the card's figures exactly). `items-center` becomes `align`, `justify-between` becomes `justify`, `gap-2` becomes `gap`, `flex-wrap` becomes `wrap`, `p-4` becomes a container's `padding`. Breakpoint- prefixed overrides and everything decorative stay in `className`, because the props are not responsive. #4890 — 135 `stack` nodes across 39 files authored `spacing`, a key nothing declares. Producer first: the nodes now author `gap`, carrying the same value. Then the consumer leg goes, so the alias cannot come back through the renderer. Nothing outside those 272 nodes changed: the diff and the per-node list in the PR body are the same set, verified by comparing every node of all 423 catalog files against `HEAD` (0 changed-but-unlisted, 0 listed-but-unchanged). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012u2pRjcqAYtoEjgr3wwhnK
os-sales
commented
Aug 22, 2026
Erratum, mechanical: the body's lint command lost a placeholder to GitHub's body sanitizer. I wrote Re-spelled here, where a comment preserves it. The file list is the 43 changed files of this PR: Reading is unchanged: exit 0, 43 files handed in, 3 inside eslint's population ( Generated by Claude Code |
✅ 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
commented
Aug 22, 2026
ACCEPT — reviewed by the |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#4891
Fixes#4890
Two cards, one sweep, per triage (comment 5335896302): the same 39 catalog files, the same anti-pattern family, and the same ratchet landing spot.
flex/stack/container的节点,仍在 className 里手写自己已声明的 props #4891 — 140 nodes across 33 files were alreadyflex/stack/containerand still hand-wrote, inclassName, props their own type declares. 231 tokens in all.stack渲染器读一个从未声明的spacing键,catalog 里 135 个节点照着它写 #4890 — 135stacknodes across 39 files authoredspacing, a key nothing declares. Producer first, then the consumer leg.Direction was fixed by triage and is followed literally: producer-side rename first, then delete the
(schema as any).spacingconsumer leg.spacingis not legalised intoStackSchema;gapis already there, and a second name for one thing is what leaves AI authors guessing.Re-measured on
origin/mainbefore touching anythingThe counts are inherited from cards written days ago, so they were re-derived rather than trusted. They reproduced exactly — this was the byte-identical outcome, not the drifted one:
3b147a367flex221 /container7 /stack3flex221 /container7 /stack3 (grid0) — 231 totalMeasured by parsing all 423 catalog JSON documents and walking the trees, not by grep — the shapes being counted are multi-line object literals and multi-token class strings, which a line-oriented grep mis-reads in both directions. Three secondary readings that shaped the work:
spacingvalues is in{0, 1, 2, 3, 4, 6}, all stepsstack.tsxactually maps, so the rename is render-neutral for every node."spacing"occurs 135 times in the catalog as raw text and 135 times as a parsed node key — the two agreeing is what rules out aspacinghiding inside a string value.origin/main, of 153 catalogstacknodes, 18 already saidgapand 135 saidspacing; none said neither. So "all 153 declare a gap" is a complete statement of the rename having landed, and it is asserted.What is left in
className, deliberatelyBreakpoint- and state-prefixed tokens (
md:items-start,hover:bg-accent/50) stay, because the props are not responsive and flattening one would silently discard the breakpoint. So does everything decorative (border-b,bg-muted), everything belonging to a different type (max-w-mdon aflex— a flex has nomaxWidthprop, so the class is the only way to say it), and everything describing the node as a flex item rather than as a container (flex-shrink-0,space-x-2).Gap steps outside a renderer's ladder are also not offenders:
flex.tsxhas nogap === 9arm, so moving agap-9out ofclassNamewould delete the spacing rather than declare it. The ratchet only demands extraction where extraction is lossless.The one deliberate rendering change
gapandpaddingrender a mobile-first ladder —gap: 3isgap-2 sm:gap-3, notgap-3. Belowsmthat is genuinely different from the hand-written single value, and it is the correct direction: the ladder is what the prop means, and giving it up is half of what these 140 nodes were giving up. #4003 pinned the same delta for the same reason. Every such case is sampled and pinned on both sides (see Verification).containerpadding gets a second, smaller improvement: a container writingp-4inclassNamerenderedp-4 sm:p-3 md:p-4— the default ladder with only its base step overridden, so padding shrank between 640px and 768px. Aspadding: 4it is the cleanp-2 sm:p-3 md:p-4.Per-node itemisation — #4891, 140 nodes / 33 files / 231 tokens
Grouped by distinct
(type, before, after)conversion; 44 conversions, every one of the 140 nodes listed exactly once under the conversion that produced it.nis the node count per row and the column sums to 140.classNameflexitems-center gap-4 px-4 py-3 border-b text-smalign: "center"gap: 4+className: "px-4 py-3 border-b text-sm"plugin-grid/product-inventory-grid.children[1].children[1]·plugin-grid/product-inventory-grid.children[1].children[2]·plugin-grid/product-inventory-grid.children[1].children[3]·plugin-grid/product-inventory-grid.children[1].children[4]·plugin-grid/team-members-grid.children[1].children[1]·plugin-grid/team-members-grid.children[1].children[2]·plugin-grid/team-members-grid.children[1].children[3]·plugin-view/grid-view-mode.children[1].children[1]·plugin-view/grid-view-mode.children[1].children[2]·plugin-view/grid-view-mode.children[1].children[3]·report/report-breakdown-table.children[1].children[1]·report/report-breakdown-table.children[1].children[2]·report/report-breakdown-table.children[1].children[3]·report/report-breakdown-table.children[1].children[4]flexitems-centeralign: "center"(className dropped)dashboard/dashboard-overview.children[2].children[1].children[0].children[1].children[0]·marketing/pricing-table.children[1].children[0].children[0].children[4].children[0]·marketing/pricing-table.children[1].children[0].children[0].children[4].children[1]·marketing/pricing-table.children[1].children[0].children[0].children[4].children[2]·marketing/pricing-table.children[1].children[1].children[0].children[4].children[0]·marketing/pricing-table.children[1].children[1].children[0].children[4].children[1]·marketing/pricing-table.children[1].children[1].children[0].children[4].children[2]·marketing/pricing-table.children[1].children[1].children[0].children[4].children[3]·marketing/pricing-table.children[1].children[2].children[0].children[4].children[0]·marketing/pricing-table.children[1].children[2].children[0].children[4].children[1]·marketing/pricing-table.children[1].children[2].children[0].children[4].children[2]·marketing/pricing-table.children[1].children[2].children[0].children[4].children[3]flexitems-center gap-2align: "center"gap: 2(className dropped)app/application-header.children[1]·marketing/call-to-action.children[0].children[3].children[0]·marketing/call-to-action.children[0].children[3].children[1]·marketing/call-to-action.children[0].children[3].children[2]·plugin-view/detail-view-mode.children[0].children[0]·plugin-view/detail-view-mode.children[0].children[1]·plugin-view/detail-view-mode.children[0].children[2]·plugin-view/detail-view-mode.children[0].children[3]·report/report-scheduling.header[0]flexitems-center justify-betweenalign: "center"justify: "between"(className dropped)auth/login-simple.children[0].children[2]·blocks-gallery/block-gallery-stats-card.children[0].children[0]·dashboard/dashboard-overview.children[0]·dashboard/recent-activity-card.children[0].children[1].children[4]·ecommerce/shopping-cart.children[0].children[0]·forms/settings-form.children[1].children[0].children[2].children[1]·forms/settings-form.children[1].children[0].children[2].children[2]·report/report-header-with-kpis.children[0]flexjustify-between text-smjustify: "between"+className: "text-sm"ecommerce/order-summary.children[2].children[0].children[1].children[0]·ecommerce/order-summary.children[2].children[0].children[1].children[1]·ecommerce/order-summary.children[2].children[0].children[3].children[0]·ecommerce/order-summary.children[2].children[0].children[3].children[1]·ecommerce/order-summary.children[2].children[0].children[3].children[2]·ecommerce/shopping-cart.children[0].children[4].children[0]·ecommerce/shopping-cart.children[0].children[4].children[1]·ecommerce/shopping-cart.children[0].children[4].children[2]flexitems-center gap-3align: "center"gap: 3(className dropped)app/application-header.children[0]·ecommerce/order-summary.children[1].children[1].children[0].children[1]·marketing/testimonials.children[1].children[0].children[0].children[2]·marketing/testimonials.children[1].children[1].children[0].children[2]·marketing/testimonials.children[1].children[2].children[0].children[2]·plugin-view/detail-view-mode.header[0]flexitems-center gap-2 px-3 py-2 rounded-md hover:bg-accent/50align: "center"gap: 2+className: "px-3 py-2 rounded-md hover:bg-accent/50"app/sidebar-navigation.children[0].children[1]·app/sidebar-navigation.children[0].children[2]·app/sidebar-navigation.children[0].children[5]·app/sidebar-navigation.children[0].children[6]·app/sidebar-navigation.children[0].children[8]flexitems-center justify-between mb-4align: "center"justify: "between"+className: "mb-4"dashboard/recent-activity-card.children[0].children[0]·plugin-grid/product-inventory-grid.children[0]·plugin-grid/team-members-grid.children[0]·plugin-view/grid-view-mode.children[0]·report/report-breakdown-table.children[0]flexitems-center space-x-4align: "center"+className: "space-x-4"dashboard/recent-activity-card.children[0].children[1].children[0].children[0]·dashboard/recent-activity-card.children[0].children[1].children[1].children[0]·dashboard/recent-activity-card.children[0].children[1].children[2].children[0]·dashboard/recent-activity-card.children[0].children[1].children[3].children[0]·dashboard/recent-activity-card.children[0].children[1].children[4].children[0]flexgap-2 flex-wrapgap: 2wrap: true(className dropped)actions/action-button-variants.children[0]·block-schema/block-marketplace-listing.children[1].children[2]·theme/theme-aware-ui-elements.children[0]·theme/theme-aware-ui-elements.children[1]containerp-4padding: 4(className dropped)dashboard/dashboard-overview.children[1].children[0].children[0]·dashboard/dashboard-overview.children[1].children[1].children[0]·dashboard/dashboard-overview.children[1].children[2].children[0]·dashboard/dashboard-overview.children[1].children[3].children[0]flexitems-center justify-between pb-4 border-balign: "center"justify: "between"+className: "pb-4 border-b"dashboard/recent-activity-card.children[0].children[1].children[0]·dashboard/recent-activity-card.children[0].children[1].children[1]·dashboard/recent-activity-card.children[0].children[1].children[2]·dashboard/recent-activity-card.children[0].children[1].children[3]flexitems-center gap-1align: "center"gap: 1(className dropped)ecommerce/product-card.children[1].children[1]·marketing/testimonials.children[1].children[0].children[0].children[0]·marketing/testimonials.children[1].children[1].children[0].children[0]·marketing/testimonials.children[1].children[2].children[0].children[0]flexgap-2gap: 2(className dropped)ecommerce/product-card.children[1].children[3]·plugin-grid/product-inventory-grid.children[0].children[1]·report/report-header-with-kpis.children[0].children[1]·report/report-scheduling.children[0].children[3]flexitems-center gap-4 px-4 py-2 bg-muted text-xs font-medium text-muted-foreground border-balign: "center"gap: 4+className: "px-4 py-2 bg-muted text-xs font-medium text-muted-foreground border-b"plugin-grid/product-inventory-grid.children[1].children[0]·plugin-grid/team-members-grid.children[1].children[0]·plugin-view/grid-view-mode.children[1].children[0]·report/report-breakdown-table.children[1].children[0]flexitems-center gap-4 px-4 py-3 text-smalign: "center"gap: 4+className: "px-4 py-3 text-sm"plugin-grid/product-inventory-grid.children[1].children[5]·plugin-grid/team-members-grid.children[1].children[4]·plugin-view/grid-view-mode.children[1].children[4]·report/report-breakdown-table.children[1].children[5]stackitems-center text-center p-4align: "center"+className: "text-center p-4"block-schema/block-with-variable-overrides-analytics-feature.children[0]·block-schema/block-with-variable-overrides-security-feature.children[0]·block-schema/feature-card-block.children[0]containerp-6padding: 6(className dropped)dashboard/dashboard-overview.children[2].children[0].children[0]·dashboard/dashboard-overview.children[2].children[1].children[0]·dashboard/recent-activity-card.children[0]flexitems-baselinealign: "baseline"(className dropped)marketing/pricing-table.children[1].children[0].children[0].children[1]·marketing/pricing-table.children[1].children[1].children[0].children[1]·marketing/pricing-table.children[1].children[2].children[0].children[1]flexgap-2 items-centergap: 2align: "center"(className dropped)actions/action-toolbar.children[1].children[0]·actions/action-toolbar.children[1].children[1]flexjustify-end gap-2 pt-4 border-t mt-4justify: "end"gap: 2+className: "pt-4 border-t mt-4"actions/action-toolbar.children[2]·plugin-view/detail-view-mode.children[1]flexitems-start space-x-2align: "start"+className: "space-x-2"auth/signup.children[0].children[3]·forms/newsletter-signup.children[0].children[2]flexgap-4gap: 4(className dropped)ecommerce/shopping-cart.children[0].children[2].children[0]·ecommerce/shopping-cart.children[0].children[2].children[2]flexitems-center gap-2 mt-2align: "center"gap: 2+className: "mt-2"ecommerce/shopping-cart.children[0].children[2].children[0].children[1].children[2]·ecommerce/shopping-cart.children[0].children[2].children[2].children[1].children[2]flexjustify-end gap-2 pt-2justify: "end"gap: 2+className: "pt-2"plugin-view/form-view-mode.children[0].children[3]·report/report-scheduling.children[0].children[4]flexitems-center justify-between border-b pb-3 mb-4align: "center"justify: "between"+className: "border-b pb-3 mb-4"actions/action-toolbar.children[0]flexjustify-end gap-2 pt-4justify: "end"gap: 2+className: "pt-4"actions/confirmation-dialog.children[0]flexitems-center justify-between p-3 border rounded-lg bg-backgroundalign: "center"justify: "between"+className: "p-3 border rounded-lg bg-background"app/application-headerflexitems-center gap-2 ml-2 pl-2 border-lalign: "center"gap: 2+className: "ml-2 pl-2 border-l"app/application-header.children[1].children[2]flexitems-center gap-2 px-3 py-2 rounded-md bg-accent text-accent-foregroundalign: "center"gap: 2+className: "px-3 py-2 rounded-md bg-accent text-accent-foreground"app/sidebar-navigation.children[0].children[0]flexitems-center space-x-2align: "center"+className: "space-x-2"auth/login-simple.children[0].children[2].children[0]flexitems-start justify-betweenalign: "start"justify: "between"(className dropped)block-schema/block-marketplace-listing.children[1].children[0]flexitems-center justify-between pt-2 border-talign: "center"justify: "between"+className: "pt-2 border-t"block-schema/block-marketplace-listing.children[1].children[3]flexitems-start gap-3 p-4 border rounded-lg max-w-mdalign: "start"gap: 3+className: "p-4 border rounded-lg max-w-md"blocks-gallery/block-gallery-notification-itemflexh-[300px] items-center justify-center text-muted-foregroundalign: "center"justify: "center"+className: "h-[300px] text-muted-foreground"dashboard/dashboard-overview.children[2].children[0].children[0].children[1]flexjustify-between font-bold text-lgjustify: "between"+className: "font-bold text-lg"ecommerce/order-summary.children[2].children[0].children[3].children[4]flexgap-3gap: 3(className dropped)ecommerce/order-summary.children[3]flexitems-baseline gap-2align: "baseline"gap: 2(className dropped)ecommerce/product-card.children[1].children[2]flexitems-center justify-between mb-6align: "center"justify: "between"+className: "mb-6"ecommerce/product-grid.children[0]flexjustify-between text-lg font-boldjustify: "between"+className: "text-lg font-bold"ecommerce/shopping-cart.children[0].children[4].children[4]flexjustify-end space-x-2justify: "end"+className: "space-x-2"forms/settings-form.children[1].children[0].children[3]flexitems-center justify-center gap-4align: "center"justify: "center"gap: 4(className dropped)marketing/call-to-action.children[0].children[2]flexitems-center justify-center gap-8 text-white/80 text-smalign: "center"justify: "center"gap: 8+className: "text-white/80 text-sm"marketing/call-to-action.children[0].children[3]flexitems-center justify-between mb-1align: "center"justify: "between"+className: "mb-1"marketing/pricing-table.children[1].children[1].children[0].children[0].children[0]Distribution of what was written:
align: "center"×98,gap: 2×34,justify: "between"×33,gap: 4×25,gap: 3×8,justify: "end"×6,wrap: true×4,align: "start"×4,padding: 4×4,gap: 1×4,align: "baseline"×4,padding: 6×3,justify: "center"×3,gap: 8×1 — 231, the token count.65 of the 140 nodes had nothing left to say afterwards and lost their
classNamekey entirely.Per-node itemisation — #4890, 135 nodes / 39 files
Uniform conversion:
spacing: Nbecomesgap: N, same value, on the node itself. Grouped by value; thencolumn sums to 135.spacing: 0gap: 0app/application-header.children[1].children[2].children[1]·plugin-grid/product-inventory-grid.children[1]·plugin-grid/team-members-grid.children[1]·plugin-view/grid-view-mode.children[1]·report/report-breakdown-table.children[1]spacing: 1gap: 1app/sidebar-navigation.children[0]·block-schema/block-marketplace-listing.children[1].children[0].children[0]·blocks-gallery/block-gallery-login-card.header[0]·blocks-gallery/block-gallery-notification-item.children[1]·dashboard/dashboard-overview.children[2].children[1].children[0].children[1].children[0].children[1]·ecommerce/order-summary.children[1].children[0].children[0].children[1]·ecommerce/product-card.children[1].children[0]·ecommerce/shopping-cart.children[0].children[2].children[0].children[1]·ecommerce/shopping-cart.children[0].children[2].children[2].children[1]·forms/settings-form.children[1].children[0].children[2].children[1].children[0]·forms/settings-form.children[1].children[0].children[2].children[2].children[0]·plugin-view/detail-view-mode.header[0].children[1]·report/report-header-with-kpis.children[0].children[0]spacing: 2gap: 2actions/confirmation-dialog.header[0]·auth/forgot-password.header[0]·auth/forgot-password.children[0].children[0]·auth/login-simple.header[0]·auth/login-simple.children[0].children[0]·auth/login-simple.children[0].children[1]·auth/signup.header[0]·auth/signup.children[0].children[0].children[0]·auth/signup.children[0].children[0].children[1]·auth/signup.children[0].children[1]·auth/signup.children[0].children[2]·auth/two-factor.header[0]·auth/two-factor.children[0].children[0]·blocks-gallery/block-gallery-login-card.children[0].children[0]·blocks-gallery/block-gallery-login-card.children[0].children[1]·blocks-gallery/block-gallery-stats-card.children[0]·dashboard/recent-activity-card.children[0].children[1].children[0].children[0].children[1]·dashboard/recent-activity-card.children[0].children[1].children[1].children[0].children[1]·dashboard/recent-activity-card.children[0].children[1].children[2].children[0].children[1]·dashboard/recent-activity-card.children[0].children[1].children[3].children[0].children[1]·dashboard/recent-activity-card.children[0].children[1].children[4].children[0].children[1]·ecommerce/order-summary.children[2].children[0].children[3]·ecommerce/product-grid.children[1].children[0].children[1]·ecommerce/product-grid.children[1].children[1].children[1]·ecommerce/product-grid.children[1].children[2].children[1]·ecommerce/product-grid.children[1].children[3].children[1]·ecommerce/shopping-cart.children[0].children[2].children[0].children[2]·ecommerce/shopping-cart.children[0].children[2].children[2].children[2]·ecommerce/shopping-cart.children[0].children[4]·forms/contact-form.header[0]·forms/contact-form.children[0].children[0].children[0]·forms/contact-form.children[0].children[0].children[1]·forms/contact-form.children[0].children[1]·forms/contact-form.children[0].children[2]·forms/contact-form.children[0].children[3]·forms/newsletter-signup.children[0].children[0]·forms/newsletter-signup.children[0].children[1]·forms/payment-form.children[0].children[0]·forms/payment-form.children[0].children[1].children[0]·forms/payment-form.children[0].children[1].children[1]·forms/payment-form.children[0].children[1].children[2].children[0]·forms/payment-form.children[0].children[1].children[2].children[1]·forms/payment-form.children[0].children[1].children[4]·forms/payment-form.children[0].children[1].children[4].children[1].children[0]·forms/payment-form.children[0].children[1].children[4].children[1].children[1]·forms/settings-form.children[0]·forms/settings-form.children[1].children[0].children[0].children[0]·forms/settings-form.children[1].children[0].children[0].children[1]·forms/settings-form.children[1].children[0].children[0].children[2]·forms/settings-form.children[1].children[0].children[0].children[3]·forms/settings-form.children[1].children[0].children[2].children[0]·forms/settings-form.children[1].children[0].children[2].children[3]·marketing/pricing-table.children[1].children[0].children[0].children[0]·marketing/pricing-table.children[1].children[0].children[0].children[4]·marketing/pricing-table.children[1].children[1].children[0].children[0]·marketing/pricing-table.children[1].children[1].children[0].children[4]·marketing/pricing-table.children[1].children[2].children[0].children[0]·marketing/pricing-table.children[1].children[2].children[0].children[4]·marketing/testimonials.children[1].children[0].children[0].children[2].children[1]·marketing/testimonials.children[1].children[1].children[0].children[2].children[1]·marketing/testimonials.children[1].children[2].children[0].children[2].children[1]·plugin-view/form-view-mode.children[0].children[0].children[0]·plugin-view/form-view-mode.children[0].children[0].children[1]·plugin-view/form-view-mode.children[0].children[1]·plugin-view/form-view-mode.children[0].children[2]·report/report-scheduling.children[0].children[0]·report/report-scheduling.children[0].children[1]·report/report-scheduling.children[0].children[2]spacing: 3gap: 3actions/action-toolbar.children[1]·block-schema/block-marketplace-listing.children[1]·block-schema/block-with-variable-overrides-analytics-feature.children[0]·block-schema/block-with-variable-overrides-security-feature.children[0]·blocks-gallery/block-gallery-login-card.children[0]·ecommerce/order-summary.children[2].children[0].children[1]·ecommerce/product-card.children[1]·marketing/features-grid.children[1].children[0].children[0]·marketing/features-grid.children[1].children[1].children[0]·marketing/features-grid.children[1].children[2].children[0]·marketing/features-grid.children[1].children[3].children[0]·marketing/features-grid.children[1].children[4].children[0]·marketing/features-grid.children[1].children[5].children[0]·plugin-view/detail-view-mode.children[0]spacing: 4gap: 4actions/action-button-variants·auth/forgot-password.children[0]·auth/login-simple.children[0]·auth/signup.children[0]·auth/two-factor.children[0]·block-schema/feature-card-block.children[0]·dashboard/dashboard-overview.children[2].children[1].children[0].children[1]·dashboard/recent-activity-card.children[0].children[1]·ecommerce/order-summary.children[1].children[0].children[0]·ecommerce/order-summary.children[1].children[1].children[0]·ecommerce/order-summary.children[2].children[0]·ecommerce/shopping-cart.children[0].children[2]·forms/contact-form.children[0]·forms/newsletter-signup.children[0]·forms/payment-form.children[0].children[1]·forms/payment-form.children[0].children[1].children[4].children[1]·forms/settings-form.children[1].children[0].children[0]·forms/settings-form.children[1].children[0].children[2]·marketing/pricing-table.children[1].children[0].children[0]·marketing/pricing-table.children[1].children[1].children[0]·marketing/pricing-table.children[1].children[2].children[0]·marketing/testimonials.children[1].children[0].children[0]·marketing/testimonials.children[1].children[1].children[0]·marketing/testimonials.children[1].children[2].children[0]·plugin-view/form-view-mode.children[0]·report/report-header-with-kpis·report/report-scheduling.children[0]·theme/theme-aware-ui-elementsspacing: 6gap: 6dashboard/dashboard-overview·ecommerce/order-summary·ecommerce/shopping-cart.children[0]·forms/payment-form.children[0]·forms/settings-form·forms/settings-form.children[1].children[0]·marketing/call-to-action.children[0]Three nodes appear in both tables (
block-schema/feature-card-block.children[0]and its twoblock-with-variable-overrides-*siblings) — they were carryingspacingand anitems-center. 140 + 135 − 3 = 272 distinct nodes changed.How the table and the diff were checked against each other
The risk a sweep actually carries is a node changed but not listed, or listed but not changed. Both directions were measured rather than eyeballed: every one of the 423 catalog documents was parsed at
HEADand in the working tree, each object reduced to a signature of its scalar keys, and the two walks compared pointer by pointer.Set equality, both directions. Two further post-state checks:
"spacing"now occurs 0 times in the catalog, and each of the 272 rows was re-read from disk and its props and survivingclassNamecompared against what the table claims.The edits are applied by offset into the original bytes, never by re-serialising the JSON — 12 catalog files carry hand-compacted single-line objects that a
JSON.stringify(…, null, 2)round-trip would reflow, which is exactly the "changes outside the listed nodes" this had to avoid.git diffon those 12 shows only the intended lines.Verification
All commands from the repo root, per AGENTS.md §怎么跑测试. Union re-run at the final commit
953c58820(theHEADprinted by the run itself), not at an earlier tree:Exit codes were captured before any pipe, in a wrapper that propagates them, and each line above quotes the tool's own verdict rather than a
$?atailwould have written. Heavy runs went throughscripts/pm/os-verify-lock.sh.Why this set is a provable superset of what the diff can affect, rather than a guess:
@object-ui/example-schema-catalog. Its in-repo dependents areapps/site(apackage.jsondependency; excluded from every root vitest project by config, and owned by CI — noapps/siteorcontent/docspath is touched here) plus four test files, all of which are in the run above alongside the catalog's own nine test files.stack.tsxchange is one expression:schema.gap ?? (schema as any).spacing ?? 2becomesschema.gap ?? 2. For any node withgapdefined the two are identical; with neither key, both give 2. The only input whose output moves is a node withspacingand nogap— and a repo-wide search finds no such node outside the test that authors one deliberately. The observable surface is therefore empty except where it is asserted.Lint, narrowed and declared.
pnpm lint(the whole-treeeslint .) is CI's run, not re-done here. The narrowing is a measurement, with all three legs: the population comes from eslint's own config resolution rather than a guess about which files count; the file count is read from--format json; and the invariance claim is thateslint.config.jsdeclares noparserOptions.project/projectServiceand no rule ineslint-rules/reads another file (noreadFileSync/globSyncanywhere in it), so every rule here is single-file syntactic and this diff cannot move the verdict of a file it did not touch. Reading:The two warnings on
stack.tsxare pre-existing and untouched (react-refresh/only-export-components, and theno-explicit-anyon theforwardRefparameter's index signature at line 20 — the objectui#4422 shape). This diff removes oneanyfrom that file and adds none.Reverse-verification
Three legs, each with its direction predicted before running. Neither leg needs a build: the root vitest config aliases
@object-ui/componentstopackages/components/src(vitest.config.mts:264) and the catalog test importsallExamplesfrom'../src/index.js'(line 54), so there is nodistanywhere in the resolution path and a source mutation takes effect directly. Every leg mutated, proved the mutation on disk, ran, then restored from the commit undertrap … EXIT INT TERM; the restore is verified by sha256, and all three files came back byte-identical.actions/action-toolbar$.children[0]) to its shippedclassNameTests 2 failed | 26 passedspacing: 2back onauth/login-simple$.header[0]spacingban (1 offender) and the value census (a stack with no numericgap)Tests 2 failed | 26 passed, reportingexpected 1 to be +0?? (schema as any).spacinginstack.tsxspacingrenders the DEFAULT gap"; theflex,gapandgap: 0cases unaffectedTests 1 failed | 3 passedLeg B's first attempt was void and is reported rather than quietly retried. The mutation landed, but the on-disk confirmation used
grep -Fwith a multi-line pattern, which grep treats as several patterns and counts matching lines for — so the "this text is gone" probe returned 3 instead of 0 and the guard declared the leg unrun. The reading was discarded and the leg re-run with single-line counting probes ("gap": 2,3 → 2,"spacing": 2,0 → 1). The row above is the second, valid run. The guard firing on a bad probe rather than on a bad mutation is the outcome it exists for; a leg that "looks fine" is the one worth distrusting.Leg C is what makes the new renderer pin non-phantom:
spacing: 6was chosen precisely because 6 is a mapped ladder step, so a still-live alias rendersmd:gap-6and fails loudly instead of coinciding with the default.What is ratcheted, and where
examples/schema-catalog/test/layout-props-conversion.test.tsx— extended, per the card, from "nodivcarries layout intent" to "noflex/stack/container/gridhand-writes its own props". Also carries thespacingban, the value census, and the sampled equivalence (2+ cases each forflex,stack,container;gridhas none because the sweep converted no grid node — its unprefixed tokens re-measured tomb-6andp-4, neither of which a grid declares). Equivalence is asserted on the class token set, not the string: extraction moves a token from the end ofcn()'s arguments to the front,tailwind-mergehas already resolved every same-property conflict by then, and asserting the string would be assertingcn()'s argument order. The post-sweep string is still pinned literally per case.packages/components/src/__tests__/stack-spacing-alias-removed.test.tsx— new; the renderer-side half. An undeclared key is inert,stackandflexnow answer identically for it (the trapstack渲染器读一个从未声明的spacing键,catalog 里 135 个节点照着它写 #4890 named),gapstill drives the ladder, andgap: 0stays reachable.Out of scope, filed separately
flexnodes still hand-write spacing asspace-x-*, the one spelling #4891's sweep deliberately left behind #5690 (new,finding) — nine catalogflexnodes still spell horizontal spacing asspace-x-*. Same reader-facing shape, different mechanism:space-x-Nis a margin on a sibling selector, not the flexbox gap, so it is not a mechanical rename and a conversion changes rendering in ways worth deciding on deliberately. Excluding it is why this card's 221-tokenflexfigure re-measured to the digit.packages/components/src/renderers/**— four layout/basic renderers still carry the unfixed spread #5574 (existing, already open) — theflex/stack/containerrenderers spread their schema keys onto the DOM element, the leak PR fix(components): whitelist ui:grid's DOM passthrough so schema keys stop leaking as attributes #5573 closed forgridviatoDomProps. That issue lists these three as "candidates to measure"; the measurement is now posted there as a comment (662 stray attributes across the catalog,gridreading clean as the control) instead of duplicating the issue. Worth knowing while reviewing this PR: 231 of those attributes are new here, because declared props are what leak.Fenced-out paths were not touched: no
content/docs/**, noapps/site/**, and none of the four paths held by concurrent cards this round.Generated by Claude Code