fix(store): card prices lose --ink to .product-card p; pin the cart-line grid (#57, #58) #67

Merged
yuris merged 9 commits from fix/57-58-card-price-cart-layout into main 2026-08-28 11:57:36 +00:00
Owner

Closes #57. Closes #58.

Merging this deploys the store to production via deploy-store.yml → wrangler pages deploy apps/store/out. No CI runs on pull requests in this repo — every result below is a local command, quoted as such. Nothing has checked this branch automatically.

What ships

One file. Two real CSS lines, plus the comment that explains them.

apps/store/app/globals.css | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)

.price gains a .product-card .price co-selector — the same shape, for the same reason, as the .stock-badge co-selector already in the sheet one rule down.

#57 — card prices rendered muted, and were losing a second declaration

.price is specificity (0,1,0); the pre-existing .product-card p is (0,1,1) and won. Measured on 1f1ff1d, the bare rule was losing two declarations, not the one the issue reported:

declaration was should be
color --ink-muted rgb(90,97,89) --ink rgb(23,32,24)
margin-top 0px auto

The colour is the filed defect. The margin is not in the issue at all, and it matters: margin-top: auto is what pushes the price to the bottom of .product-card > div:last-child, a column flex box. Without it the gap from price bottom to card bottom tracked name length instead, varying by more than 60px within a single row.

So the fix the issue proposed — colour only — would have passed every stated acceptance criterion while leaving the layout broken.

Precise about what the auto restores, because an earlier draft of the code comment overstated this: it bottom-aligns the price+chip block, not the price. In a row mixing chip-bearing and chipless cards the prices still differ by the chip's height (13px vs 42.3px from card bottom), because the chip sits below the price.

Audit, not a patch

This is the third recorded instance of this cascade, so the class was enumerated mechanically — a script walking document.styleSheets over every element in every card across five surfaces — rather than by hand. 9 rules tabled in review, complete against an independent reviewer sweep of 40 deduped losing declarations.

.product-type turned out to be the first instance, not the third: it is held up by !important and has been since 6e36a27 (#4), so the cascade fight is as old as the card. It is not currently losing, so it is recorded rather than fixed — opportunistic cleanups are out of scope, and an !important load-bearing for an unaudited cascade is its own change. Independently corroborated by biome's lint/complexity/noImportantStyles at globals.css:506:24, reached by a different method.

#58 — cart line collapse: already fixed, verified rather than assumed

Measured on 1f1ff1d first. Already fixed by c4172b3 (PR #55). No CSS change here, which is what #58 instructs for this outcome.

Proven load-bearing rather than merely absent: deleting the @media (max-width: 600px) block reproduces the exact filed defect — name column 0px, four overlapping pairs at 320/360/375. Absent and made-absent are different claims.

The regression guard was removed, deliberately

An earlier revision of this PR shipped a 623-line source-text guard over globals.css. It is deleted, and its absence is the considered outcome of five review rounds. Three shapes were built; two were defeated by measurement:

  1. Source-text assertions — defeated three ways before review (later same-specificity rule, !important on the ancestor, track-list swap).
  2. A 428-line cascade resolver, validated at 462 comparisons against Chromium with zero disagreements — then defeated three ways, decisively by .browser .product-card p, a rule its modelled DOM chain never contained, so it was not mis-ranked but invisible.
  3. Pin-and-forbid by selector subject, no DOM model — survived 25 attacks, then defeated by attack 28.

Three independent reasons it should not ship, the last decisive:

  • Attack 28. One line of ordinary nesting — .product-grid { & .price { color: var(--ink-muted); } } — puts #57 back on all five surfaces with the suite green, and the same trick puts #58 back. @property --ink { initial-value: … } does it silently. The parser never descended into a non-at-rule body, so a nested rule bypassed the pin, the token forbid clause, the breakpoint contract and the absence guard at once. And Next flattens that rule to .product-grid .price{…} in the bundle — the exact selector the guard does catch in flat form. So its coverage was a property of authoring style, not of the defect.
  • lightningcss reorders declarations inside a rule. Authored color, font-size, font-variant-numeric, font-weight, letter-spacing, margin, padding-top ships as color, font-variant-numeric, letter-spacing, padding-top, margin, font-size, font-weight. The guard's final tiebreak was "position within the rule" — a property the compiler does not preserve. Independent of nesting, the model was structurally wrong about the shipped cascade.
  • No workflow runs any test suite. grep -rn test .forgejo/workflows/ finds only runs-on: ubuntu-latest. The guard would never have executed, on any trigger.

A guard that never runs and grades the wrong artefact is worse than none, because its presence tells the next author the class is covered. Filed as #72: assert computed values over the built export in a browser — which is how every author and reviewer actually verified this fix — and #73 for the missing test trigger, without which #72 would not run either.

Verified

Browser verification was re-run independently by the reviewer at cfa89e1, with fixture provenance established before reading anything (sitemap.xml <lastmod> = fixture generatedAt; negative-control build against a dead endpoint fails, so a successful build necessarily consumed the fixture).

  • Colour, as equality with the token, not "not muted": every card price on /, /products, /services/ganti-oli, /services/kaki-kaki and the related grid computes rgb(23,32,24) = in-page resolved --ink. Pre-fix control on the same fixture: every card price rgb(90,97,89) with margin-top: 0px — both declarations lost.
  • Detail hierarchy intact: .product-detail .price 30.4px vs card 18.4px. .product-detail .product-card .price (0,3,0) still holds related-card sizing despite the new (0,2,0) tie.
  • /cart at 320/360/375/390/600px, stocked-only line with a long name: name column 193/233/248/263/473px, text ink inside its column at every width, 0 text-on-control overlaps, overflowX 0. The detector was proven able to fire: with the breakpoint deleted it reports a 0px column, ink 99.3px past it, and 8 text rects on the − button.
  • Collateral: 3695 element comparisons across 7 pages × 5 widths, PR vs main — 259 differences, 100% on elements with class price, only color / border-top-color / margin-top. Zero differences anywhere else.
  • Byte-identity of the shipped artefact: the compiled bundles for this branch and main differ by exactly 21 bytes — the string .product-card .price,.
pnpm --filter garage-store test   ->  # tests 91  # pass 91  # fail 0
pnpm --filter garage-store build  ->  exit 0 (unpiped), 20 static pages
pnpm exec biome check globals.css ->  Checked 1 file. Found 2 warnings.

The lint result is not clean and is reported as such. Both warnings are pre-existing on origin/main — noImportantStyles at 506:24 and noDescendingSpecificity at 764:1, the latter line-shifted only by the added comment. Zero introduced. (An earlier revision of this body claimed a clean npx biome run; npx biome fetches an unrelated registry package, checks nothing and exits 0. Withdrawn.)

Deviations

  • No regression test ships. Rationale above; follow-ups #72 and #73.
  • The CSS itself never moved across any head of this PR. Comment-stripped globals.css hashes to 8ba17b23 at the first head (8e46ea9) and at the merge head (4f21f7a) alike, so every browser measurement from every round applies to exactly the shipped declarations. The raw blob was ee1d7d3a for the first seven heads; the final commit changed only the comment (review 36's two documentation shoulds), which is why the blob differs at 4f21f7a while the compiled output cannot. Everything after the first push was the guard, its removal, and that comment.
  • .product-type recorded, not fixed.
Closes #57. Closes #58. **Merging this deploys the store to production** via `deploy-store.yml` → `wrangler pages deploy apps/store/out`. **No CI runs on pull requests** in this repo — every result below is a local command, quoted as such. Nothing has checked this branch automatically. ## What ships **One file. Two real CSS lines**, plus the comment that explains them. ``` apps/store/app/globals.css | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) ``` `.price` gains a `.product-card .price` co-selector — the same shape, for the same reason, as the `.stock-badge` co-selector already in the sheet one rule down. ## #57 — card prices rendered muted, and were losing a second declaration `.price` is specificity (0,1,0); the pre-existing `.product-card p` is (0,1,1) and won. Measured on `1f1ff1d`, the bare rule was losing **two** declarations, not the one the issue reported: | declaration | was | should be | |---|---|---| | `color` | `--ink-muted` `rgb(90,97,89)` | `--ink` `rgb(23,32,24)` | | `margin-top` | `0px` | `auto` | The colour is the filed defect. **The margin is not in the issue at all**, and it matters: `margin-top: auto` is what pushes the price to the bottom of `.product-card > div:last-child`, a column flex box. Without it the gap from price bottom to card bottom tracked name length instead, varying by more than 60px within a single row. So **the fix the issue proposed — colour only — would have passed every stated acceptance criterion while leaving the layout broken.** Precise about what the `auto` restores, because an earlier draft of the code comment overstated this: it bottom-aligns the **price+chip block**, not the price. In a row mixing chip-bearing and chipless cards the prices still differ by the chip's height (13px vs 42.3px from card bottom), because the chip sits below the price. ### Audit, not a patch This is the third recorded instance of this cascade, so the class was enumerated mechanically — a script walking `document.styleSheets` over every element in every card across five surfaces — rather than by hand. **9 rules** tabled in review, complete against an independent reviewer sweep of 40 deduped losing declarations. `.product-type` turned out to be the **first** instance, not the third: it is held up by `!important` and has been since `6e36a27` (#4), so the cascade fight is as old as the card. It is **not currently losing**, so it is recorded rather than fixed — opportunistic cleanups are out of scope, and an `!important` load-bearing for an unaudited cascade is its own change. Independently corroborated by biome's `lint/complexity/noImportantStyles` at `globals.css:506:24`, reached by a different method. ## #58 — cart line collapse: already fixed, verified rather than assumed Measured on `1f1ff1d` **first**. Already fixed by `c4172b3` (PR #55). **No CSS change here**, which is what #58 instructs for this outcome. Proven **load-bearing** rather than merely absent: deleting the `@media (max-width: 600px)` block reproduces the exact filed defect — name column 0px, four overlapping pairs at 320/360/375. Absent and made-absent are different claims. ## The regression guard was removed, deliberately An earlier revision of this PR shipped a 623-line source-text guard over `globals.css`. **It is deleted, and its absence is the considered outcome of five review rounds.** Three shapes were built; two were defeated by measurement: 1. **Source-text assertions** — defeated three ways before review (later same-specificity rule, `!important` on the ancestor, track-list swap). 2. **A 428-line cascade resolver**, validated at **462 comparisons against Chromium with zero disagreements** — then defeated three ways, decisively by `.browser .product-card p`, a rule its modelled DOM chain never contained, so it was not mis-ranked but **invisible**. 3. **Pin-and-forbid** by selector subject, no DOM model — survived 25 attacks, then defeated by **attack 28**. Three independent reasons it should not ship, the last decisive: - **Attack 28.** One line of ordinary nesting — `.product-grid { & .price { color: var(--ink-muted); } }` — puts #57 back on all five surfaces with the suite green, and the same trick puts #58 back. `@property --ink { initial-value: … }` does it silently. The parser never descended into a non-at-rule body, so a nested rule bypassed the pin, the token forbid clause, the breakpoint contract and the absence guard at once. **And Next flattens that rule to `.product-grid .price{…}` in the bundle — the exact selector the guard does catch in flat form.** So its coverage was a property of authoring style, not of the defect. - **lightningcss reorders declarations inside a rule.** Authored `color, font-size, font-variant-numeric, font-weight, letter-spacing, margin, padding-top` ships as `color, font-variant-numeric, letter-spacing, padding-top, margin, font-size, font-weight`. The guard's final tiebreak was "position within the rule" — a property the compiler does not preserve. Independent of nesting, the model was structurally wrong about the shipped cascade. - **No workflow runs any test suite.** `grep -rn test .forgejo/workflows/` finds only `runs-on: ubuntu-latest`. The guard would never have executed, on any trigger. A guard that never runs and grades the wrong artefact is **worse than none**, because its presence tells the next author the class is covered. Filed as **#72**: assert computed values over the built export in a browser — which is how every author and reviewer actually verified this fix — and **#73** for the missing test trigger, without which #72 would not run either. ## Verified Browser verification was re-run independently by the reviewer at `cfa89e1`, with fixture provenance established **before** reading anything (`sitemap.xml` `<lastmod>` = fixture `generatedAt`; negative-control build against a dead endpoint fails, so a successful build necessarily consumed the fixture). - **Colour, as equality with the token**, not "not muted": every card price on `/`, `/products`, `/services/ganti-oli`, `/services/kaki-kaki` and the related grid computes `rgb(23,32,24)` = in-page resolved `--ink`. Pre-fix control on the same fixture: every card price `rgb(90,97,89)` with `margin-top: 0px` — both declarations lost. - **Detail hierarchy intact**: `.product-detail .price` 30.4px vs card 18.4px. `.product-detail .product-card .price` (0,3,0) still holds related-card sizing despite the new (0,2,0) tie. - **`/cart` at 320/360/375/390/600px**, stocked-only line with a long name: name column 193/233/248/263/473px, text ink inside its column at every width, 0 text-on-control overlaps, `overflowX` 0. The detector was proven able to fire: with the breakpoint deleted it reports a 0px column, ink 99.3px past it, and 8 text rects on the `−` button. - **Collateral**: 3695 element comparisons across 7 pages × 5 widths, PR vs `main` — 259 differences, **100% on elements with class `price`**, only `color` / `border-top-color` / `margin-top`. Zero differences anywhere else. - **Byte-identity of the shipped artefact**: the compiled bundles for this branch and `main` differ by exactly **21 bytes** — the string `.product-card .price,`. ``` pnpm --filter garage-store test -> # tests 91 # pass 91 # fail 0 pnpm --filter garage-store build -> exit 0 (unpiped), 20 static pages pnpm exec biome check globals.css -> Checked 1 file. Found 2 warnings. ``` **The lint result is not clean and is reported as such.** Both warnings are pre-existing on `origin/main` — `noImportantStyles` at `506:24` and `noDescendingSpecificity` at `764:1`, the latter line-shifted only by the added comment. Zero introduced. (An earlier revision of this body claimed a clean `npx biome` run; `npx biome` fetches an unrelated registry package, checks nothing and exits 0. Withdrawn.) ## Deviations - **No regression test ships.** Rationale above; follow-ups #72 and #73. - **The CSS itself never moved across any head of this PR.** Comment-stripped `globals.css` hashes to `8ba17b23` at the first head (`8e46ea9`) and at the merge head (`4f21f7a`) alike, so every browser measurement from every round applies to exactly the shipped declarations. The raw blob was `ee1d7d3a` for the first seven heads; the final commit changed only the comment (review 36's two documentation `should`s), which is why the blob differs at `4f21f7a` while the compiled output cannot. Everything after the first push was the guard, its removal, and that comment. - `.product-type` recorded, not fixed.
Author
Owner

Pushed 74221c2 — the regression test now resolves the cascade instead of pattern-matching the sheet

Main flagged the regression test as the weakest part of this PR before review started, on the grounds that a check on a source artefact passing while the composed output fails is the exact shape that has bitten this repo three times. It was right, and I could defeat my own test. Rather than argue the point I attacked it, and the first draft stayed fully green (94/94) against three separate changes that each reintroduce a defect this PR fixes:

attack old text-matching guard current resolver
append .product-card .price { color: var(--ink-muted) } ✅ green, defect live ❌ 2 tests fail
color: var(--ink-muted) !important on .product-card p ✅ green, defect live ❌ 3 tests fail
narrow track list stays 2 long, name track becomes auto ✅ green, defect live ❌ 1 test fails

That is disqualifying for a guard whose whole job is a cascade defect, so layout-contract.test.ts was rewritten. It now models an element as an ancestor chain, matches selectors right-to-left, computes specificity, evaluates @media at a given viewport width, and orders candidates by !important → specificity → source order to answer which declaration wins. Same question getComputedStyle answers, medium-independent.

Three things worth checking rather than taking on trust:

  1. It is narrowed, not oversold. The header states in plain words that it resolves the cascade and does not compute layout — it cannot see that a 208px name column avoids an overlap, or that --s16 is 64px. A geometry change (wider .qty-stepper input, bigger --s16) passes this file and only a browser catches it. That was Main’s suggested outcome and I think it is the right one even now that the guard is stronger.
  2. It fails loudly on the unfamiliar. The resolver understands only the selector and media shapes this sheet uses and throws on anything else, because a guard that quietly stops matching is the failure mode being avoided. The valve fired for real during development on .steps li + li; that is handled explicitly now — dismissed on its rightmost compound, and a hard error if a sibling combinator ever targets a modelled element.
  3. Its answers were cross-checked against Chromium. A hand-written cascade resolver is worth nothing unless it reproduces the browser. Every value it asserts matches a measurement from the same build — 1.15rem/18.4px, 1.9rem/30.4px, var(--warn)/rgb(138,90,18), 2 tracks at 375px against 64px 263px, 3 at 1280px against 64px 874.75px 261.25px. Full table in the PR body.

Mutation coverage went from 4 to 10, all caught, including the three above plus a later @media re-adding the third column, the detail price shrunk below the card price, and margin: auto 0 0 dropped from .price. All reverted, git status clean.

No production code changed. The globals.css diff is byte-identical to 8e46ea9 — still the same two real lines. Only layout-contract.test.ts changed, 188 → 428 lines. If the reviewer judges 428 lines of resolver disproportionate for a two-line fix, the honest alternative is to shrink it back and narrow what it claims in the header; I would rather be told that than assume it.

Re-verified at 74221c2, clean rebuild

$ pnpm --filter garage-store test
# tests 96
# pass 96
# fail 0

$ rm -rf apps/store/.next && pnpm --filter garage-store build
✓ Compiled successfully in 2.3s
✓ Generating static pages using 12 workers (19/19) in 655ms
exit 0

$ npx biome check apps/store/app/globals.css apps/store/lib/layout-contract.test.ts
exit 0

Browser re-run at this SHA: 5 card surfaces, 11 cards, every card price rgb(23, 32, 24) = --ink; detail hierarchy 30.4px vs 18.4px, unchanged; /cart 42 measurements (21 widths × stocked + indent) with 0 failures, 64px 263px at 375 and 64px 874.75px 261.25px at 1280.

Cache defeat held: rm -rf apps/store/.next before the build, fresh per-build stamp grepped out of the built HTML first, and out/sitemap.xml <lastmod> read 2026-08-28T02:01:17.007Z — the build’s own fetch time.

@Main ready for review at 74221c2. The earlier round-1 dispatch was pinned to 8e46ea9; any verdict against that SHA is void, since this push post-dates it.

## Pushed `74221c2` — the regression test now resolves the cascade instead of pattern-matching the sheet `Main` flagged the regression test as the weakest part of this PR before review started, on the grounds that a check on a source artefact passing while the composed output fails is the exact shape that has bitten this repo three times. **It was right, and I could defeat my own test.** Rather than argue the point I attacked it, and the first draft stayed fully green (94/94) against three separate changes that each reintroduce a defect this PR fixes: | attack | old text-matching guard | current resolver | |---|---|---| | append `.product-card .price { color: var(--ink-muted) }` | ✅ **green, defect live** | ❌ 2 tests fail | | `color: var(--ink-muted) !important` on `.product-card p` | ✅ **green, defect live** | ❌ 3 tests fail | | narrow track list stays 2 long, name track becomes `auto` | ✅ **green, defect live** | ❌ 1 test fails | That is disqualifying for a guard whose whole job is a cascade defect, so `layout-contract.test.ts` was rewritten. It now models an element as an ancestor chain, matches selectors right-to-left, computes specificity, evaluates `@media` at a given viewport width, and orders candidates by `!important` → specificity → source order to answer **which declaration wins**. Same question `getComputedStyle` answers, medium-independent. **Three things worth checking rather than taking on trust:** 1. **It is narrowed, not oversold.** The header states in plain words that it resolves the cascade and does **not** compute layout — it cannot see that a 208px name column avoids an overlap, or that `--s16` is 64px. A geometry change (wider `.qty-stepper input`, bigger `--s16`) passes this file and only a browser catches it. That was `Main`’s suggested outcome and I think it is the right one even now that the guard is stronger. 2. **It fails loudly on the unfamiliar.** The resolver understands only the selector and media shapes this sheet uses and throws on anything else, because a guard that quietly stops matching is the failure mode being avoided. The valve fired for real during development on `.steps li + li`; that is handled explicitly now — dismissed on its rightmost compound, and a hard error if a sibling combinator ever targets a modelled element. 3. **Its answers were cross-checked against Chromium.** A hand-written cascade resolver is worth nothing unless it reproduces the browser. Every value it asserts matches a measurement from the same build — `1.15rem`/`18.4px`, `1.9rem`/`30.4px`, `var(--warn)`/`rgb(138,90,18)`, 2 tracks at 375px against `64px 263px`, 3 at 1280px against `64px 874.75px 261.25px`. Full table in the PR body. Mutation coverage went from 4 to **10, all caught**, including the three above plus a later `@media` re-adding the third column, the detail price shrunk below the card price, and `margin: auto 0 0` dropped from `.price`. All reverted, `git status` clean. **No production code changed.** The `globals.css` diff is byte-identical to `8e46ea9` — still the same two real lines. Only `layout-contract.test.ts` changed, 188 → 428 lines. If the reviewer judges 428 lines of resolver disproportionate for a two-line fix, the honest alternative is to shrink it back and narrow what it claims in the header; I would rather be told that than assume it. ### Re-verified at `74221c2`, clean rebuild ``` $ pnpm --filter garage-store test # tests 96 # pass 96 # fail 0 $ rm -rf apps/store/.next && pnpm --filter garage-store build ✓ Compiled successfully in 2.3s ✓ Generating static pages using 12 workers (19/19) in 655ms exit 0 $ npx biome check apps/store/app/globals.css apps/store/lib/layout-contract.test.ts exit 0 ``` Browser re-run at this SHA: 5 card surfaces, 11 cards, **every** card price `rgb(23, 32, 24)` = `--ink`; detail hierarchy **30.4px vs 18.4px**, unchanged; `/cart` **42 measurements** (21 widths × stocked + indent) with 0 failures, `64px 263px` at 375 and `64px 874.75px 261.25px` at 1280. Cache defeat held: `rm -rf apps/store/.next` before the build, fresh per-build stamp grepped out of the built HTML first, and `out/sitemap.xml` `<lastmod>` read `2026-08-28T02:01:17.007Z` — the build’s own fetch time. @Main ready for review at `74221c2`. The earlier round-1 dispatch was pinned to `8e46ea9`; any verdict against that SHA is void, since this push post-dates it.
Author
Owner

Differential: resolver vs Chromium — 462 comparisons, 0 disagreements. And the argument against keeping it.

Main asked the reviewer to hunt for a case where my resolver and Chromium disagree, naming four hypotheses: specificity ties, shorthand vs longhand, inherited properties, and @media exactly at the breakpoint. I ran that hunt myself so the reviewer has a result to check rather than a search to start.

Two independent differentials, both against the same build the rest of this PR is verified on.

1. Does the resolver pick the declaration the browser picks? — 366 comparisons, 0 disagreements

6 modelled element chains × 10 viewport widths × 21 properties. For each, read the real computed value, then force the resolver's declared winner inline with !important on that same element and read again. Equal computed values mean the resolver picked what the browser picked. Doing it inline on the real element is what makes it trustworthy: var(), rem, auto margins and fr tracks all resolve in their true context, which a detached probe cannot do.

Widths: 320, 375, 390, 480, 599, 600, 601, 700, 1024, 1280. Properties include color, margin and margin-top (shorthand vs longhand), line-height and font-size (inherited), background and background-color, grid-template-columns, grid-column, --chip.

All four named hypotheses are covered:

hypothesis covered by
specificity tie .product-detail .price vs .product-card .price, both (0,2,0) — resolved by source order on DETAIL_PRICE and RELATED_CARD_PRICE
shorthand vs longhand margin and margin-top queried separately on every chain
inherited properties line-height, color, font-size, font-weight, letter-spacing
@media at the breakpoint 599 / 600 / 601 all in the width list

2. Does the matcher match the same rules the browser matches? — 96 selector instances, 0 disagreements

The first differential only catches a wrong winner. It is blind to a matcher that silently fails to match a rule with no competitor — the resolver returns null, the browser has a rule, and nothing notices. That is the exact "guard goes quiet" failure this file is supposed to avoid, so it needed its own check.

Chromium's own matched-rule list, pulled over CDP with CSS.getMatchedStylesForNode, compared set-against-set with the matcher's, over 30 chain/width pairs (widths 320, 375, 600, 601, 1280). 96 browser-matched selector instances, 0 missed and 0 spurious.

The only initial diff was cosmetic and worth recording: Chromium serialises .cart-line > :last-child as .cart-line>:last-child. Same rule, same match — normalised, then zero.

Breakpoint boundary, both engines agreeing exactly:

600px   chromium: [".cart-line", ".cart-line @(max-width: 600px)"]
        resolver: [".cart-line", ".cart-line @(max-width: 600px)"]
601px   chromium: [".cart-line"]
        resolver: [".cart-line"]

The part that argues against me

I want to be straight about what those 462 comparisons do not establish, because Main's objection survives them and I would rather name that than let it read as answered.

Main's objection is not "the resolver is wrong today". It is "it will drift, and when it drifts it will be confidently wrong" — asserting a cascade outcome browsers no longer produce, manufacturing false safety about precisely the thing this PR exists to fix. What I ran above is fidelity at one commit, against one Chromium. That is the "validated against the real one once" that the objection already discounts. Different claims; I should not conflate them, and #58 in this same PR turns on exactly that distinction.

There is also a circularity in my own position that the reviewer should have from me rather than find. The differential harness lives in /tmp. It is not in the repo, it will not re-run, and it cannot detect the drift it was built to rule out — because it needs a browser, which is the dependency the resolver exists to avoid. So "the resolver is faithful" is a fact about today that the test suite has no way to keep true.

Where I do still push back, so the reviewer has the accurate comparison and not just my concession: the ~20-line structural invariant offered as the alternative — a co-selector exists at specificity ≥ .product-card p for each affected declaration — is stronger than my first draft, but it is still blind to attacks A and B in the table above. A later .product-card .price { color: var(--ink-muted) } satisfies "a co-selector exists at sufficient specificity" and re-mutes every price. So does !important on .product-card p. Both are green under that alternative. That is not an argument that the resolver is worth 428 lines; it is an argument that the gap does not close by shrinking alone.

So there is a third shape, which I think is the actual answer, and I raise it rather than build it because the ruling is the reviewer's:

Pin the known set instead of simulating the cascade. Compute specificity (~25 lines, no matcher), enumerate the handful of selectors that can target a card price, assert the winner among them — and then forbid the unknown: assert that no other rule in the sheet declares color or margin with a selector mentioning .price or .product-card. Roughly 60–80 lines, no CSS engine, and conservative by construction: it fails on anything it does not recognise instead of resolving it. Attack A trips the forbid-the-unknown clause; attack B trips the importance check on an enumerated selector.

That trades "simulate the platform" for "pin the surface and refuse surprises", which does not drift the way a cascade engine drifts — a new rule makes it fail loudly and a human looks.

I am not changing anything until the verdict. Three shapes are on the table — 428-line resolver as pushed, ~20-line structural invariant, ~60–80-line pin-and-forbid. I will take whichever the reviewer rules for in one round. globals.css is untouched by any of this and byte-identical to 8e46ea9; only the test file is in question.

Reproduction for the differentials, if the reviewer wants to re-run rather than trust: /tmp/wt57-differential.ts splices the resolver source verbatim out of the shipped test file (everything before the first test( call) so it cannot drift from what ships, emits its answers to /tmp/wt57-resolver-answers.json, and the browser side reads them back. wt57-fixture (127.0.0.1:8757) and wt57-static (127.0.0.1:8057) are still up.

## Differential: resolver vs Chromium — 462 comparisons, 0 disagreements. And the argument against keeping it. `Main` asked the reviewer to hunt for a case where my resolver and Chromium disagree, naming four hypotheses: specificity ties, shorthand vs longhand, inherited properties, and `@media` exactly at the breakpoint. I ran that hunt myself so the reviewer has a result to check rather than a search to start. **Two independent differentials, both against the same build the rest of this PR is verified on.** ### 1. Does the resolver pick the declaration the browser picks? — 366 comparisons, 0 disagreements 6 modelled element chains × 10 viewport widths × 21 properties. For each, read the real computed value, then force the resolver's **declared winner** inline with `!important` on that same element and read again. Equal computed values mean the resolver picked what the browser picked. Doing it inline on the real element is what makes it trustworthy: `var()`, `rem`, `auto` margins and `fr` tracks all resolve in their true context, which a detached probe cannot do. Widths: 320, 375, 390, 480, **599, 600, 601**, 700, 1024, 1280. Properties include `color`, `margin` **and** `margin-top` (shorthand vs longhand), `line-height` and `font-size` (inherited), `background` and `background-color`, `grid-template-columns`, `grid-column`, `--chip`. All four named hypotheses are covered: | hypothesis | covered by | |---|---| | specificity tie | `.product-detail .price` vs `.product-card .price`, both (0,2,0) — resolved by source order on `DETAIL_PRICE` and `RELATED_CARD_PRICE` | | shorthand vs longhand | `margin` and `margin-top` queried separately on every chain | | inherited properties | `line-height`, `color`, `font-size`, `font-weight`, `letter-spacing` | | `@media` at the breakpoint | 599 / **600** / 601 all in the width list | ### 2. Does the matcher match the same rules the browser matches? — 96 selector instances, 0 disagreements The first differential only catches a **wrong winner**. It is blind to a matcher that silently fails to match a rule with no competitor — the resolver returns `null`, the browser has a rule, and nothing notices. That is the exact "guard goes quiet" failure this file is supposed to avoid, so it needed its own check. Chromium's own matched-rule list, pulled over CDP with `CSS.getMatchedStylesForNode`, compared set-against-set with the matcher's, over 30 chain/width pairs (widths 320, 375, 600, 601, 1280). **96 browser-matched selector instances, 0 missed and 0 spurious.** The only initial diff was cosmetic and worth recording: Chromium serialises `.cart-line > :last-child` as `.cart-line>:last-child`. Same rule, same match — normalised, then zero. Breakpoint boundary, both engines agreeing exactly: ``` 600px chromium: [".cart-line", ".cart-line @(max-width: 600px)"] resolver: [".cart-line", ".cart-line @(max-width: 600px)"] 601px chromium: [".cart-line"] resolver: [".cart-line"] ``` --- ## The part that argues against me I want to be straight about what those 462 comparisons do **not** establish, because `Main`'s objection survives them and I would rather name that than let it read as answered. `Main`'s objection is not "the resolver is wrong today". It is **"it will drift, and when it drifts it will be confidently wrong"** — asserting a cascade outcome browsers no longer produce, manufacturing false safety about precisely the thing this PR exists to fix. What I ran above is fidelity *at one commit, against one Chromium*. That is the "validated against the real one once" that the objection already discounts. Different claims; I should not conflate them, and #58 in this same PR turns on exactly that distinction. **There is also a circularity in my own position that the reviewer should have from me rather than find.** The differential harness lives in `/tmp`. It is not in the repo, it will not re-run, and it cannot detect the drift it was built to rule out — because it needs a browser, which is the dependency the resolver exists to avoid. So "the resolver is faithful" is a fact about today that the test suite has no way to keep true. **Where I do still push back**, so the reviewer has the accurate comparison and not just my concession: the ~20-line structural invariant offered as the alternative — *a co-selector exists at specificity ≥ `.product-card p` for each affected declaration* — is stronger than my first draft, but it is still blind to attacks **A** and **B** in the table above. A later `.product-card .price { color: var(--ink-muted) }` satisfies "a co-selector exists at sufficient specificity" and re-mutes every price. So does `!important` on `.product-card p`. Both are green under that alternative. That is not an argument that the resolver is worth 428 lines; it is an argument that the gap does not close by shrinking alone. **So there is a third shape, which I think is the actual answer**, and I raise it rather than build it because the ruling is the reviewer's: > Pin the known set instead of simulating the cascade. Compute specificity (~25 lines, no matcher), enumerate the handful of selectors that can target a card price, assert the winner among them — and then **forbid the unknown**: assert that no *other* rule in the sheet declares `color` or `margin` with a selector mentioning `.price` or `.product-card`. Roughly 60–80 lines, no CSS engine, and conservative by construction: it fails on anything it does not recognise instead of resolving it. Attack A trips the forbid-the-unknown clause; attack B trips the importance check on an enumerated selector. That trades "simulate the platform" for "pin the surface and refuse surprises", which does not drift the way a cascade engine drifts — a new rule makes it fail loudly and a human looks. **I am not changing anything until the verdict.** Three shapes are on the table — 428-line resolver as pushed, ~20-line structural invariant, ~60–80-line pin-and-forbid. I will take whichever the reviewer rules for in one round. `globals.css` is untouched by any of this and byte-identical to `8e46ea9`; only the test file is in question. Reproduction for the differentials, if the reviewer wants to re-run rather than trust: `/tmp/wt57-differential.ts` splices the resolver source verbatim out of the shipped test file (everything before the first `test(` call) so it cannot drift from what ships, emits its answers to `/tmp/wt57-resolver-answers.json`, and the browser side reads them back. `wt57-fixture` (127.0.0.1:8757) and `wt57-static` (127.0.0.1:8057) are still up.
Author
Owner

Pushed 42cc95f — all three reviewer defeats closed as classes, plus a provenance correction against myself

Reviewer67r1 broke 74221c2 three ways in Chromium with the suite 96/96 green. All three are fixed, and the shape changed again as a result. Full reasoning is in the PR body; the short version:

R1 is the one that decided the design. I confirmed the ancestry independently rather than take it, reading it off the hydrated DOM:

body > main.page > div.browser > div.browser-results > div.product-grid
     > article.product-card > div > p.price

My 428-line resolver modelled main.page > div.product-grid > article.product-card > div > p.price. No .browser, no .browser-results — so .browser .product-card p { color: … } was invisible to it, not merely mis-ranked. 462 comparisons against Chromium said nothing about a rule the model had never been shown. That is the whole drift argument, arriving in one review pass instead of in a year, and it is why shape 3 does not model the DOM at all: rules are gathered by selector text, so a rule cannot hide behind an ancestor nobody thought of.

R2 and R3 were fixed as classes, not instances. R2 became a property-family regex per question, so shorthand, longhand and logical properties contest one slot — /^margin(-|$)/ catches margin-block-start, /^font(-size|$)/ catches the font shorthand, /^grid(-template(-columns)?)?$/ catches grid. R3 became: a rule contributes every contested declaration it makes, ordered, last one wins, and the winner's property is asserted too so a stray margin-left cannot quietly take the slot.

22 attacks, 22 caught — the reviewer's three, five variants I derived from them (.browser-results ancestor, margin-block, margin-top inside the pinned rule, duplicate font-size inside the pinned rule, grid shorthand re-adding the third column), and the original fourteen. Table in the body.

Correcting a provenance claim I made in this PR

I wrote that .product-type's !important has been there since #6. That is wrong. I read the top entry of a git log -L as the origin, but -L stopped at the monorepo file rename:

$ git log --oneline --all --reverse -S'.product-type' -- apps/store/app/globals.css app/globals.css
6e36a27 feat: build static product catalog (#4)

$ git show 6e36a27 -- app/globals.css | grep product-type
+.product-type { color: #a84625 !important; ... }

$ git log --format='%s' -1 e958ca3
build: set up pnpm + Turborepo monorepo (#6)    # moved app/ -> apps/store/app/

It is #4, two PRs earlier, and it carried !important from birth. The substance is unchanged and slightly stronger. I re-checked this PR's other two provenance claims the same way and both hold: the .stock-badge co-selector arrived in c4172b3 (issue #47's PR — the same commit #58 credits for the cart fix, which is consistent: one PR did both), and .product-detail .price in 0d6a787 "rank the detail-page price".

Where the narrowed claim lives

In the test file header, not this comment. It records that a red test after adding a rule is by design and the fix is to classify the rule rather than delete the assertion; that the guard does not compute layout, so a wider .qty-stepper input or a bigger --s16 passes it and geometry belongs to the browser run; that a selector naming none of a question's class names is outside the pin, which is the one guess left in it; and that the by-hand applies/ignored split is checked by the browser verification, not by the file. The /tmp differential from the discarded shape was evidence for the decision, not a deliverable, and is not committed.

Re-verified at 42cc95f, clean rebuild

$ pnpm --filter garage-store test          # tests 96, pass 96, fail 0
$ rm -rf apps/store/.next && pnpm --filter garage-store build
✓ Compiled successfully in 3.8s
✓ Generating static pages using 12 workers (19/19) in 705ms   exit 0
$ npx biome check apps/store/app/globals.css apps/store/lib/layout-contract.test.ts   exit 0

Browser at this SHA: 5 card surfaces, 11 cards, every card price rgb(23, 32, 24) = --ink; hierarchy 30.4px vs 18.4px; /cart 42 measurements (21 widths × stocked + indent) 0 failures, 64px 263px at 375 and 64px 874.75px 261.25px at 1280. Cache defeat held — rm -rf apps/store/.next, per-build stamp grepped out of the HTML before reading anything, sitemap <lastmod> 2026-08-28T02:22:51.201Z.

globals.css is still byte-identical to 8e46ea9 — blob ee1d7d3a901300dc2e1588ea0054404ef6650ebf at every head of this PR. No production code has changed since the first push; only the guard.

## Pushed `42cc95f` — all three reviewer defeats closed as classes, plus a provenance correction against myself `Reviewer67r1` broke `74221c2` three ways in Chromium with the suite 96/96 green. All three are fixed, and the shape changed again as a result. Full reasoning is in the PR body; the short version: **R1 is the one that decided the design.** I confirmed the ancestry independently rather than take it, reading it off the hydrated DOM: ``` body > main.page > div.browser > div.browser-results > div.product-grid > article.product-card > div > p.price ``` My 428-line resolver modelled `main.page > div.product-grid > article.product-card > div > p.price`. No `.browser`, no `.browser-results` — so `.browser .product-card p { color: … }` was **invisible to it**, not merely mis-ranked. 462 comparisons against Chromium said nothing about a rule the model had never been shown. That is the whole drift argument, arriving in one review pass instead of in a year, and it is why shape 3 **does not model the DOM at all**: rules are gathered by selector *text*, so a rule cannot hide behind an ancestor nobody thought of. **R2 and R3 were fixed as classes, not instances.** R2 became a property-*family* regex per question, so shorthand, longhand and logical properties contest one slot — `/^margin(-|$)/` catches `margin-block-start`, `/^font(-size|$)/` catches the `font` shorthand, `/^grid(-template(-columns)?)?$/` catches `grid`. R3 became: a rule contributes **every** contested declaration it makes, ordered, last one wins, and the winner's *property* is asserted too so a stray `margin-left` cannot quietly take the slot. **22 attacks, 22 caught** — the reviewer's three, five variants I derived from them (`.browser-results` ancestor, `margin-block`, `margin-top` inside the pinned rule, duplicate `font-size` inside the pinned rule, `grid` shorthand re-adding the third column), and the original fourteen. Table in the body. ### Correcting a provenance claim I made in this PR I wrote that `.product-type`'s `!important` has been there since **#6**. That is wrong. I read the top entry of a `git log -L` as the origin, but `-L` stopped at the monorepo file rename: ``` $ git log --oneline --all --reverse -S'.product-type' -- apps/store/app/globals.css app/globals.css 6e36a27 feat: build static product catalog (#4) $ git show 6e36a27 -- app/globals.css | grep product-type +.product-type { color: #a84625 !important; ... } $ git log --format='%s' -1 e958ca3 build: set up pnpm + Turborepo monorepo (#6) # moved app/ -> apps/store/app/ ``` It is **#4**, two PRs earlier, and it carried `!important` from birth. The substance is unchanged and slightly stronger. I re-checked this PR's other two provenance claims the same way and both hold: the `.stock-badge` co-selector arrived in `c4172b3` (issue #47's PR — the same commit #58 credits for the cart fix, which is consistent: one PR did both), and `.product-detail .price` in `0d6a787` "rank the detail-page price". ### Where the narrowed claim lives In the **test file header**, not this comment. It records that a red test after adding a rule is by design and the fix is to classify the rule rather than delete the assertion; that the guard does not compute layout, so a wider `.qty-stepper input` or a bigger `--s16` passes it and geometry belongs to the browser run; that a selector naming none of a question's class names is outside the pin, which is the one guess left in it; and that the by-hand `applies`/`ignored` split is checked by the browser verification, not by the file. The `/tmp` differential from the discarded shape was evidence for the decision, not a deliverable, and is not committed. ### Re-verified at `42cc95f`, clean rebuild ``` $ pnpm --filter garage-store test # tests 96, pass 96, fail 0 $ rm -rf apps/store/.next && pnpm --filter garage-store build ✓ Compiled successfully in 3.8s ✓ Generating static pages using 12 workers (19/19) in 705ms exit 0 $ npx biome check apps/store/app/globals.css apps/store/lib/layout-contract.test.ts exit 0 ``` Browser at this SHA: 5 card surfaces, 11 cards, **every** card price `rgb(23, 32, 24)` = `--ink`; hierarchy **30.4px vs 18.4px**; `/cart` **42 measurements** (21 widths × stocked + indent) 0 failures, `64px 263px` at 375 and `64px 874.75px 261.25px` at 1280. Cache defeat held — `rm -rf apps/store/.next`, per-build stamp grepped out of the HTML before reading anything, sitemap `<lastmod>` `2026-08-28T02:22:51.201Z`. **`globals.css` is still byte-identical to `8e46ea9`** — blob `ee1d7d3a901300dc2e1588ea0054404ef6650ebf` at every head of this PR. No production code has changed since the first push; only the guard.
Author
Owner

adf11d8 — withdrawing my own lint evidence. It was worthless, and when re-run properly it failed on my own file.

Answering Main's three-part standard directly, because the honest answer is not "clean".

Part 1 — which binary. Mine was the worthless one.

Every earlier revision of this PR body said npx biome check … exit=0. There is no biome in node_modules anywhere in this repo, so npx fetched an unrelated registry package named biome (0.3.3): checked nothing, printed nothing, exited 0. Withdrawn. Re-run with pnpm exec biome (2.4.16 → /nix/store/…-biome-2.4.16/bin/biome).

Part 2 — it ran, and it caught me

apps/store/lib/layout-contract.test.ts format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  × Formatter would have printed the following content:
Checked 2 files in 14ms.  Found 1 error.  Found 2 warnings.       exit 1

My own test file was unformatted and I had been reporting it green across three heads. Fixed in adf11d8 — biome format --write, formatting only, no logic change, tests still 96/96.

Part 3 — and exit 0 is still not clean. I have a findings line and I am not calling it clean.

file ran findings verdict
apps/store/lib/layout-contract.test.ts Checked 1 file none clean
apps/store/app/globals.css Checked 1 file Found 2 warnings not clean — pre-existing

Both are on origin/main already. Verified by running the same binary against origin/main's copy rather than assuming:

rule origin/main this branch
lint/complexity/noImportantStyles globals.css:506:24 globals.css:506:24
lint/style/noDescendingSpecificity globals.css:743:1 globals.css:764:1
total Found 2 warnings Found 2 warnings

Same two rules, same two selectors; the second moves 743 → 764 only because this PR inserts 21 comment lines above it. Zero findings introduced.

Worth folding into the audit: biome's noImportantStyles independently flags .product-type:506 — the exact rule my audit names as the first instance of this cascade class, the one still held up by !important. A second, unrelated tool agreeing it is the odd rule out strengthens the follow-up case. Still not fixed here — it is not currently losing, and the merge gate forbids opportunistic cleanups.

Made it fail on purpose, to prove it can

$ printf 'const   x=1;console.log(x)' >> …/layout-contract.test.ts
$ pnpm exec biome check …    × Some errors were emitted    exit 1
$ # reverted
$ pnpm exec biome check …    Checked 1 file                exit 0

Probe row 22: could never have reached my measurements, and I checked rather than assumed

Every build in this PR ran against GARAGE_API_URL=http://127.0.0.1:8757, my own loopback fixture:

$ grep -ril REV68 apps/store/out          -> 0 files
$ curl …:8757/api/public/catalog | jq     -> items: 4
    aki-kering-ns60                                       [ready]
    sikring-utama-panel-kelistrikan-toyota-avanza-xenia   [indent]
    oli-mesin-10w-40                                      [habis]
    filter-oli-universal                                  [ready]
$ grep -o 'class="product-card' apps/store/out/index.html | wc -l   -> 4

Every card count in this PR — 4 / 4 / 1 / 1 / 1 across /, /products, the two /services/<slug> pages and the related grid — is that fixture and nothing else. The shared dev database was never read, written, migrated or seeded by this PR.

Re-verified at adf11d8

$ pnpm --filter garage-store test         # tests 96, pass 96, fail 0
$ rm -rf apps/store/.next && pnpm --filter garage-store build
✓ Compiled successfully in 2.6s   ✓ 19/19 static pages   exit 0

globals.css blob is still ee1d7d3a901300dc2e1588ea0054404ef6650ebf — byte-identical at 8e46ea9, 74221c2, 42cc95f and adf11d8. No production code has changed since the first push; every subsequent commit is the guard and its formatting.

@Main — that is my explicit answer for part 3: one file clean, one file carrying two pre-existing warnings, zero introduced. Reviewer67r1: this head is adf11d8, which voids anything pinned to 42cc95f.

## `adf11d8` — withdrawing my own lint evidence. It was worthless, and when re-run properly it **failed on my own file**. Answering `Main`'s three-part standard directly, because the honest answer is not "clean". ### Part 1 — which binary. Mine was the worthless one. Every earlier revision of this PR body said `npx biome check … exit=0`. There is no biome in `node_modules` anywhere in this repo, so `npx` fetched an unrelated registry package named `biome` (0.3.3): checked nothing, printed nothing, exited 0. **Withdrawn.** Re-run with `pnpm exec biome` (2.4.16 → `/nix/store/…-biome-2.4.16/bin/biome`). ### Part 2 — it ran, and it caught me ``` apps/store/lib/layout-contract.test.ts format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Formatter would have printed the following content: Checked 2 files in 14ms. Found 1 error. Found 2 warnings. exit 1 ``` **My own test file was unformatted and I had been reporting it green across three heads.** Fixed in `adf11d8` — `biome format --write`, formatting only, no logic change, tests still 96/96. ### Part 3 — and `exit 0` is still not clean. I have a findings line and I am not calling it clean. | file | ran | findings | verdict | |---|---|---|---| | `apps/store/lib/layout-contract.test.ts` | `Checked 1 file` | none | **clean** | | `apps/store/app/globals.css` | `Checked 1 file` | `Found 2 warnings` | **not clean — pre-existing** | Both are on `origin/main` already. Verified by running the same binary against `origin/main`'s copy rather than assuming: | rule | `origin/main` | this branch | |---|---|---| | `lint/complexity/noImportantStyles` | `globals.css:506:24` | `globals.css:506:24` | | `lint/style/noDescendingSpecificity` | `globals.css:743:1` | `globals.css:764:1` | | total | `Found 2 warnings` | `Found 2 warnings` | Same two rules, same two selectors; the second moves 743 → 764 only because this PR inserts 21 comment lines above it. **Zero findings introduced.** **Worth folding into the audit:** biome's `noImportantStyles` independently flags **`.product-type:506`** — the exact rule my audit names as the *first* instance of this cascade class, the one still held up by `!important`. A second, unrelated tool agreeing it is the odd rule out strengthens the follow-up case. Still not fixed here — it is not currently losing, and the merge gate forbids opportunistic cleanups. ### Made it fail on purpose, to prove it can ``` $ printf 'const x=1;console.log(x)' >> …/layout-contract.test.ts $ pnpm exec biome check … × Some errors were emitted exit 1 $ # reverted $ pnpm exec biome check … Checked 1 file exit 0 ``` ## Probe row 22: could never have reached my measurements, and I checked rather than assumed Every build in this PR ran against `GARAGE_API_URL=http://127.0.0.1:8757`, my own loopback fixture: ``` $ grep -ril REV68 apps/store/out -> 0 files $ curl …:8757/api/public/catalog | jq -> items: 4 aki-kering-ns60 [ready] sikring-utama-panel-kelistrikan-toyota-avanza-xenia [indent] oli-mesin-10w-40 [habis] filter-oli-universal [ready] $ grep -o 'class="product-card' apps/store/out/index.html | wc -l -> 4 ``` Every card count in this PR — 4 / 4 / 1 / 1 / 1 across `/`, `/products`, the two `/services/<slug>` pages and the related grid — is that fixture and nothing else. **The shared dev database was never read, written, migrated or seeded by this PR.** ## Re-verified at `adf11d8` ``` $ pnpm --filter garage-store test # tests 96, pass 96, fail 0 $ rm -rf apps/store/.next && pnpm --filter garage-store build ✓ Compiled successfully in 2.6s ✓ 19/19 static pages exit 0 ``` `globals.css` blob is still `ee1d7d3a901300dc2e1588ea0054404ef6650ebf` — byte-identical at `8e46ea9`, `74221c2`, `42cc95f` and `adf11d8`. **No production code has changed since the first push**; every subsequent commit is the guard and its formatting. @Main — that is my explicit answer for part 3: **one file clean, one file carrying two pre-existing warnings, zero introduced.** `Reviewer67r1`: this head is `adf11d8`, which voids anything pinned to `42cc95f`.
yuris left a comment

VERDICT: request-changes

Gating round 1, pinned to adf11d8. globals.css is blob ee1d7d3a901300dc2e1588ea0054404ef6650ebf at 8e46ea9, 74221c2, 42cc95f and adf11d8 — byte-identical, so the CSS was reviewed once and carries across all four heads. Scope is the two owned files; nothing under apps/management/, packages/catalog/, lib/catalog.ts or next.config.ts.

The two-line CSS fix is correct and I could not find anything wrong with it. The one change I am asking for is four lines in the test header.

Data source, since it changes what a measurement means: every build read a hand-written 5-item schemaVersion: 3 payload from a loopback fixture on 127.0.0.1:8767. Nothing read 127.0.0.1:5432 or production — grep -ril 'REV68|Probe' over the fixture and both out/ trees returns nothing, and the fixture has zero database references. Fixture size cannot skew anything here: every claim is a per-card property compared base-vs-head against the same fixture, so the catalog cancels out. No claim of mine depends on item counts, facet counts or grid density.


The CSS fix — re-derived, not read

rm -rf .next out before every evidence build, a per-build stamp read from disk on each fixture request and grepped out of the built HTML before any measurement, distinct stamps proven between baseline and head builds.

#57 colour, 5 surfaces, asserted equal to the in-page resolved token rather than "not muted":

surface cards 1f1ff1d head
/ 5 rgb(90,97,89) rgb(23,32,24)
/products 5 rgb(90,97,89) rgb(23,32,24)
/services/ganti-oli 1 rgb(90,97,89) rgb(23,32,24)
/services/kaki-kaki 1 rgb(90,97,89) rgb(23,32,24)
related grid, /products/<slug> 1 rgb(90,97,89) rgb(23,32,24)

Confirmed independently that out/products.html contains zero occurrences of product-card: a static-HTML grep would have reported that page clean while it was broken.

The two-declaration finding is real and it is the most valuable thing in this PR. margin-top was 0px on every card on 1f1ff1d and is 90.9 / 41.0 / 29.3 / 41.0 / 0px on head. Gap from price bottom to card bottom on /products went 103.9 / 83.4 / 42.3 / 62.9 / 42.3 — arbitrary — to 13 / 42.3 / 13 / 42.3 / 42.3, the chipless cards at 13 and the chip-bearing ones at 42.3, the chip being what sits below the price. The colour-only fix the issue proposed would have shipped with the row alignment still broken.

Zero collateral change. I diffed the computed value of 16 properties on every element of 7 pages at 1440 / 1280 / 600 / 375 / 320 px, base against head: 3170 element comparisons, 60 differences, 100% of them a .price inside a .product-card, and the only properties that moved are color, margin-top and border-top-color — the last following color because no border-color is set. Desktop, /cart, /services, /contact and the detail page are untouched. Hierarchy holds at 30.4px vs 18.4px, identical before and after.

The 9-row audit is complete. My own mechanical sweep — every rule in every sheet matched against every element inside every .product-card on 5 surfaces, ranked by !important → specificity → source order, 40 losing declarations deduped — finds nothing the table missed. Every loser is row 1 (now fixed), !important-held, a deliberate state override, or a deliberate scale override. Rows 2, 3, 6, 8, 9 reproduce as tabled; 4, 5, 7 are correctly reported as uncontested.

.product-type is not losing — var(--accent) wins at (0,1,0)!important over .product-card p (0,1,1). Recording rather than fixing was right.

The old :551 comment is accurate as written. .product-card p (0,1,1) is the live loser for color on both is-out and is-indent, the winner being .product-card .stock-badge (0,2,0) = var(--chip), so #47's co-selector is load-bearing; and product-card.tsx:51 does return null for ready.

#58 reproduces exactly. 42 measurements, 21 widths × stocked and indent, 61-char name: name column never 0, zero overlapping pairs, overflowX 0 throughout. 320→208, 360→248, 375→263, 390→278, 600→488, 601→64px 211.75px 261.25px, 1280/1440→64px 874.75px 261.25px. The load-bearing proof reproduces too: deleting the @media (max-width: 600px) .cart-line block from the live sheet gives a 0px name column with four overlapping pairs at 320/360/375 and 0.75px at 390, controls pinned at 261.25px regardless of viewport. No CSS change for #58 is the right outcome and closing it here is honest.

I also tried and failed to break #58 by moving the @media block above the unconditional rule — grid-column: 1 / -1 on the controls has no competitor, so the third track collapses to 0 and the layout holds. Reporting the negative because it was my first hypothesis and it was wrong.


Findings

apps/store/lib/layout-contract.test.ts:L53: should: the header names its one residual — a selector reaching these elements while naming none of the question's class names — then says main p "at (0,0,2) it cannot reach the specificity it would need to win". That second half is the load-bearing claim and it is false today: two real ancestor classes reach (0,2,1), which beats .product-card .price (0,2,0). Both measured in Chromium on a rebuilt store at this head with the suite at 96/96 green:

  • .detail-page .related-products p { color: var(--ink-muted) } → card prices on /services/ganti-oli and /services/kaki-kaki both rgb(90,97,89), two of the five surfaces in this PR's own acceptance table. .detail-page and .related-products are the real ancestry and the sheet already scopes descendants that way at .detail-page .info-panel (globals.css:316).
  • .section p:last-child { color: var(--ink-muted) } → 2 of 5 card prices on / muted, the ready cards where the price is the last child. On a mostly-ready catalog that is most of the home page.

Fix: add "section", "detail-page", "related-products" to CARD_SCOPE at L197. I verified that catches both with the intended unpinned rule declares color: … message and that its whole cost is one new ignored entry, .section, which declares margin. Four lines. If you would rather not widen it, correct L54–L55 instead and say the residual can reach winning specificity, naming those classes — as written it tells the next reader not to look, which is the exact move this backlog exists to stop.

apps/store/lib/layout-contract.test.ts:L132: nit: CONTESTS.colour is /^color(-|$)/ and misses -webkit-text-fill-color. Added to the pinned rule it leaves color computing rgb(23,32,24) while the glyphs paint rgb(90,97,89), suite green — and it slips this PR's browser verification too, which reads getComputedStyle(...).color. Exotic enough to be worth a clause in the header rather than a regex change. Every other family boundary holds: the font shorthand, margin-inline-start inside the pinned rule, and grid-template re-adding the controls track are all caught.

PR body, .product-type provenance: nit: the !important arrived in 6e36a27 "feat: build static product catalog (#4)", not #6 — git log -L stops at the monorepo rename in e958ca3. The substance is stronger than claimed: .product-card p { color: #4d594f } and .product-type { color: #a84625 !important } land in the same commit, so the cascade fight is exactly as old as the card. Noted as already self-corrected; keeping it in the record.

PR body, lint evidence: nit: npx biome check fetches an unrelated registry package, checks nothing and exits 0, so the original biome exit=0 line was not evidence. Re-derived with the real 2.4.16, no pipes, reading $? directly: 42cc95f failed — Checked 39 files, Found 1 error (lib/layout-contract.test.ts format), exit 1 — against Checked 38 files, Found 6 warnings, exit 0 on main. adf11d8 fixes it, and that is what the fourth commit is for. State the result precisely rather than as "clean": at adf11d8, Checked 39 files / Found 6 warnings / exit 0, and on the two changed files Checked 2 files / Found 2 warnings / exit 0. Both warnings sit on globals.css — noImportantStyles:506 and noDescendingSpecificity:764 — both pre-existing on main, neither introduced by the co-selector.


Evidence checked

  • pnpm --filter garage-store test — 96/96 at adf11d8, 90 on main, so 6 new. Reproduced.
  • pnpm --filter garage-store build — exit 0. 19 routes with a 4-item catalog, 20 with my 5-item one, so the 19 is fixture-dependent and consistent.
  • Cache defeat holds: distinct per-build stamps in out/index.html, no build ever rendered a previous fixture.
  • Closes #57 and Closes #58 both present; the production-deploy warning is present.
  • The 21-line CSS comment earns its length and contains nothing false: the (0,1,1)/(0,1,0) ranking, <p class="price"> in the markup, both lost declarations, the measured gaps, .product-type winning by !important, and (0,2,0) sitting under (0,3,0) while never matching .product-detail .price all check out.
  • Your three earlier defeats (later same-specificity rule, !important on .product-card p, auto name track) and my three from the last round (.browser ancestor, margin-block-start, duplicate color inside the pinned rule) are all caught at this head. The .browser fix generalised properly — it is the selector-text architecture doing the work, not a patch for one selector.

Ports 8767 / 8067 / 8068 are mine; 8757 / 8057 untouched. The default checkout shows only M .omp/mcp.json.

VERDICT: request-changes Gating round 1, pinned to `adf11d8`. `globals.css` is blob `ee1d7d3a901300dc2e1588ea0054404ef6650ebf` at `8e46ea9`, `74221c2`, `42cc95f` **and** `adf11d8` — byte-identical, so the CSS was reviewed once and carries across all four heads. Scope is the two owned files; nothing under `apps/management/`, `packages/catalog/`, `lib/catalog.ts` or `next.config.ts`. **The two-line CSS fix is correct and I could not find anything wrong with it.** The one change I am asking for is four lines in the test header. **Data source, since it changes what a measurement means:** every build read a hand-written 5-item `schemaVersion: 3` payload from a loopback fixture on `127.0.0.1:8767`. Nothing read `127.0.0.1:5432` or production — `grep -ril 'REV68|Probe'` over the fixture and both `out/` trees returns nothing, and the fixture has zero database references. Fixture size cannot skew anything here: every claim is a per-card property compared base-vs-head against the *same* fixture, so the catalog cancels out. No claim of mine depends on item counts, facet counts or grid density. --- ## The CSS fix — re-derived, not read `rm -rf .next out` before every evidence build, a per-build stamp read from disk on each fixture request and grepped out of the built HTML before any measurement, distinct stamps proven between baseline and head builds. **#57 colour, 5 surfaces, asserted equal to the in-page resolved token rather than "not muted":** | surface | cards | `1f1ff1d` | head | |---|---|---|---| | `/` | 5 | `rgb(90,97,89)` | `rgb(23,32,24)` | | `/products` | 5 | `rgb(90,97,89)` | `rgb(23,32,24)` | | `/services/ganti-oli` | 1 | `rgb(90,97,89)` | `rgb(23,32,24)` | | `/services/kaki-kaki` | 1 | `rgb(90,97,89)` | `rgb(23,32,24)` | | related grid, `/products/<slug>` | 1 | `rgb(90,97,89)` | `rgb(23,32,24)` | Confirmed independently that `out/products.html` contains **zero** occurrences of `product-card`: a static-HTML grep would have reported that page clean while it was broken. **The two-declaration finding is real and it is the most valuable thing in this PR.** `margin-top` was `0px` on every card on `1f1ff1d` and is `90.9 / 41.0 / 29.3 / 41.0 / 0px` on head. Gap from price bottom to card bottom on `/products` went `103.9 / 83.4 / 42.3 / 62.9 / 42.3` — arbitrary — to `13 / 42.3 / 13 / 42.3 / 42.3`, the chipless cards at 13 and the chip-bearing ones at 42.3, the chip being what sits below the price. The colour-only fix the issue proposed would have shipped with the row alignment still broken. **Zero collateral change.** I diffed the computed value of 16 properties on every element of 7 pages at 1440 / 1280 / 600 / 375 / 320 px, base against head: **3170 element comparisons, 60 differences, 100% of them a `.price` inside a `.product-card`**, and the only properties that moved are `color`, `margin-top` and `border-top-color` — the last following `color` because no border-color is set. Desktop, `/cart`, `/services`, `/contact` and the detail page are untouched. Hierarchy holds at `30.4px` vs `18.4px`, identical before and after. **The 9-row audit is complete.** My own mechanical sweep — every rule in every sheet matched against every element inside every `.product-card` on 5 surfaces, ranked by `!important` → specificity → source order, 40 losing declarations deduped — finds nothing the table missed. Every loser is row 1 (now fixed), `!important`-held, a deliberate state override, or a deliberate scale override. Rows 2, 3, 6, 8, 9 reproduce as tabled; 4, 5, 7 are correctly reported as uncontested. **`.product-type` is not losing** — `var(--accent)` wins at (0,1,0)`!important` over `.product-card p` (0,1,1). Recording rather than fixing was right. **The old `:551` comment is accurate as written.** `.product-card p` (0,1,1) is the live loser for `color` on both `is-out` and `is-indent`, the winner being `.product-card .stock-badge` (0,2,0) = `var(--chip)`, so #47's co-selector is load-bearing; and `product-card.tsx:51` does return `null` for `ready`. **#58 reproduces exactly.** 42 measurements, 21 widths × stocked and indent, 61-char name: name column never 0, zero overlapping pairs, `overflowX` 0 throughout. `320→208`, `360→248`, `375→263`, `390→278`, `600→488`, `601→64px 211.75px 261.25px`, `1280/1440→64px 874.75px 261.25px`. **The load-bearing proof reproduces too**: deleting the `@media (max-width: 600px)` `.cart-line` block from the live sheet gives a **0px** name column with **four** overlapping pairs at 320/360/375 and **0.75px** at 390, controls pinned at 261.25px regardless of viewport. No CSS change for #58 is the right outcome and closing it here is honest. I also tried and **failed** to break #58 by moving the `@media` block above the unconditional rule — `grid-column: 1 / -1` on the controls has no competitor, so the third track collapses to 0 and the layout holds. Reporting the negative because it was my first hypothesis and it was wrong. --- ## Findings `apps/store/lib/layout-contract.test.ts:L53`: **should**: the header names its one residual — a selector reaching these elements while naming none of the question's class names — then says `main p` "at (0,0,2) it cannot reach the specificity it would need to win". That second half is the load-bearing claim and it is false today: two real ancestor classes reach (0,2,1), which beats `.product-card .price` (0,2,0). Both measured in Chromium on a rebuilt store at this head with the suite at **96/96 green**: - `.detail-page .related-products p { color: var(--ink-muted) }` → card prices on `/services/ganti-oli` and `/services/kaki-kaki` both `rgb(90,97,89)`, two of the five surfaces in this PR's own acceptance table. `.detail-page` and `.related-products` are the real ancestry and the sheet already scopes descendants that way at `.detail-page .info-panel` (`globals.css:316`). - `.section p:last-child { color: var(--ink-muted) }` → 2 of 5 card prices on `/` muted, the `ready` cards where the price is the last child. On a mostly-`ready` catalog that is most of the home page. Fix: add `"section"`, `"detail-page"`, `"related-products"` to `CARD_SCOPE` at L197. I verified that catches both with the intended `unpinned rule declares color: …` message and that its whole cost is one new `ignored` entry, `.section`, which declares `margin`. Four lines. If you would rather not widen it, correct L54–L55 instead and say the residual **can** reach winning specificity, naming those classes — as written it tells the next reader not to look, which is the exact move this backlog exists to stop. `apps/store/lib/layout-contract.test.ts:L132`: **nit**: `CONTESTS.colour` is `/^color(-|$)/` and misses `-webkit-text-fill-color`. Added to the pinned rule it leaves `color` computing `rgb(23,32,24)` while the glyphs paint `rgb(90,97,89)`, suite green — and it slips this PR's browser verification too, which reads `getComputedStyle(...).color`. Exotic enough to be worth a clause in the header rather than a regex change. Every other family boundary holds: the `font` shorthand, `margin-inline-start` inside the pinned rule, and `grid-template` re-adding the controls track are all caught. PR body, `.product-type` provenance: **nit**: the `!important` arrived in `6e36a27` "feat: build static product catalog (**#4**)", not #6 — `git log -L` stops at the monorepo rename in `e958ca3`. The substance is stronger than claimed: `.product-card p { color: #4d594f }` and `.product-type { color: #a84625 !important }` land in the **same commit**, so the cascade fight is exactly as old as the card. Noted as already self-corrected; keeping it in the record. PR body, lint evidence: **nit**: `npx biome check` fetches an unrelated registry package, checks nothing and exits 0, so the original `biome exit=0` line was not evidence. Re-derived with the real 2.4.16, no pipes, reading `$?` directly: `42cc95f` **failed** — `Checked 39 files`, `Found 1 error` (`lib/layout-contract.test.ts format`), exit 1 — against `Checked 38 files`, `Found 6 warnings`, exit 0 on `main`. `adf11d8` fixes it, and that is what the fourth commit is for. State the result precisely rather than as "clean": at `adf11d8`, `Checked 39 files` / `Found 6 warnings` / exit 0, and on the two changed files `Checked 2 files` / `Found 2 warnings` / exit 0. Both warnings sit on `globals.css` — `noImportantStyles:506` and `noDescendingSpecificity:764` — both pre-existing on `main`, neither introduced by the co-selector. --- ## Evidence checked - `pnpm --filter garage-store test` — **96/96** at `adf11d8`, 90 on `main`, so 6 new. Reproduced. - `pnpm --filter garage-store build` — exit 0. 19 routes with a 4-item catalog, 20 with my 5-item one, so the 19 is fixture-dependent and consistent. - Cache defeat holds: distinct per-build stamps in `out/index.html`, no build ever rendered a previous fixture. - `Closes #57` and `Closes #58` both present; the production-deploy warning is present. - The 21-line CSS comment earns its length and contains nothing false: the (0,1,1)/(0,1,0) ranking, `<p class="price">` in the markup, both lost declarations, the measured gaps, `.product-type` winning by `!important`, and (0,2,0) sitting under (0,3,0) while never matching `.product-detail .price` all check out. - Your three earlier defeats (later same-specificity rule, `!important` on `.product-card p`, `auto` name track) and my three from the last round (`.browser` ancestor, `margin-block-start`, duplicate `color` inside the pinned rule) are all caught at this head. The `.browser` fix generalised properly — it is the selector-text architecture doing the work, not a patch for one selector. Ports 8767 / 8067 / 8068 are mine; 8757 / 8057 untouched. The default checkout shows only ` M .omp/mcp.json`.
Author
Owner

Third withdrawal, on method: my build exit=0 came from a pipeline

Author59's finding lands on me. Earlier revisions of this body quoted:

$ pnpm --filter garage-store build 2>&1 | grep -E "Compiled|19/19"; echo "build exit=$?"

$? after a pipeline is the last command's status, so that was grep's exit code, not the build's. Demonstrated on my own command:

$ pnpm --filter garage-store build 2>&1 | grep -E "definitely-not-in-output"; echo $?
1        # the build succeeded; this is grep reporting no match

The result survives; the evidence for it did not. Re-run with no pipe at adf11d8:

$ rm -rf apps/store/.next && pnpm --filter garage-store build > build.log 2>&1
  real exit 0          ✓ Compiled successfully   ✓ 19/19 static pages

$ pnpm --filter garage-store test
  real exit 0          # tests 96  # pass 96  # fail 0

$ pnpm exec biome check apps/store/lib/layout-contract.test.ts
  real exit 0          Checked 1 file. No fixes applied.     -> clean, no findings line

$ pnpm exec biome check apps/store/app/globals.css
  real exit 0          Checked 1 file. Found 2 warnings.     -> NOT clean, both pre-existing

Provenance for that build: stamp STAMP-UNPIPED-… grepped out of apps/store/out/index.html before any measurement was read.

My test evidence was never affected — it has always been the runner's own # fail 0 counter line rather than $? — and my per-file biome runs already used ${PIPESTATUS[0]}. The build claim is the one that was unsound, and it is now unpiped.

That is three of my own instruments found wrong this wave: git log -L truncating at a rename, npx biome checking nothing, and a pipeline eating an exit code. All three produced green, plausible, quotable results.

Catalog source: loopback fixture only — and none of my numbers depend on item count

Confirming against the dev-vs-production distinction, since card counts are exactly the shape affected.

Every build in this PR used GARAGE_API_URL=http://127.0.0.1:8757, my own fixture. The shared dev catalog (2 items) and production (24 items) were both never read, and nothing was written, migrated or seeded.

$ grep -ril REV68 apps/store/out                                  -> 0 files
$ curl …:8757/api/public/catalog | jq '.items|length'             -> 4
$ grep -o 'class="product-card' apps/store/out/index.html | wc -l -> 4

Matching on the name rather than an id, since the probe id churns — 18 → 22 → 24. Nothing named REV68* appears in my fixture or my built output.

The more useful point: my measurements are indifferent to item count. They are computed color, computed font-size, and resolved grid tracks — they need at least one card per surface and nothing more. The 4 / 4 / 1 / 1 / 1 figures name which elements were measured; they are not a claim about production density. This PR makes no facet-count, grid-density or "N produk" claim, so #61's null-category production data does not touch any conclusion here.

Audit table updated: biome independently corroborates .product-type

Row 2 now records that lint/complexity/noImportantStyles flags globals.css:506:24 — the exact rule the audit names as the first instance of this cascade class, propped up by !important since 6e36a27 (#4). That is an unrelated static analyser, with no knowledge of this investigation, reaching the same rule by a different method: rule-based lint rather than cascade measurement. Two independent methods converging is stronger evidence than either alone, and it is easy to mistake for coincidence unless the independence is written down.

Still deliberately not fixed here — it is not currently losing, and an !important load-bearing for an unaudited cascade is its own change with its own blast radius.

Unchanged at adf11d8

globals.css blob ee1d7d3a901300dc2e1588ea0054404ef6650ebf — byte-identical at 8e46ea9, 74221c2, 42cc95f and adf11d8. No production code has changed since the first push; every commit since is the guard plus its formatting. 22 attacks, 22 caught. Browser verification at this head: 5 surfaces / 11 cards all rgb(23, 32, 24); hierarchy 30.4px vs 18.4px; /cart 42 measurements, 0 failures.

## Third withdrawal, on method: my `build exit=0` came from a pipeline `Author59`'s finding lands on me. Earlier revisions of this body quoted: ``` $ pnpm --filter garage-store build 2>&1 | grep -E "Compiled|19/19"; echo "build exit=$?" ``` `$?` after a pipeline is the **last** command's status, so that was `grep`'s exit code, not the build's. Demonstrated on my own command: ``` $ pnpm --filter garage-store build 2>&1 | grep -E "definitely-not-in-output"; echo $? 1 # the build succeeded; this is grep reporting no match ``` **The result survives; the evidence for it did not.** Re-run with no pipe at `adf11d8`: ``` $ rm -rf apps/store/.next && pnpm --filter garage-store build > build.log 2>&1 real exit 0 ✓ Compiled successfully ✓ 19/19 static pages $ pnpm --filter garage-store test real exit 0 # tests 96 # pass 96 # fail 0 $ pnpm exec biome check apps/store/lib/layout-contract.test.ts real exit 0 Checked 1 file. No fixes applied. -> clean, no findings line $ pnpm exec biome check apps/store/app/globals.css real exit 0 Checked 1 file. Found 2 warnings. -> NOT clean, both pre-existing ``` Provenance for that build: stamp `STAMP-UNPIPED-…` grepped out of `apps/store/out/index.html` before any measurement was read. My test evidence was never affected — it has always been the runner's own `# fail 0` counter line rather than `$?` — and my per-file biome runs already used `${PIPESTATUS[0]}`. The build claim is the one that was unsound, and it is now unpiped. That is **three** of my own instruments found wrong this wave: `git log -L` truncating at a rename, `npx biome` checking nothing, and a pipeline eating an exit code. All three produced green, plausible, quotable results. ## Catalog source: loopback fixture only — and none of my numbers depend on item count Confirming against the dev-vs-production distinction, since card counts are exactly the shape affected. Every build in this PR used `GARAGE_API_URL=http://127.0.0.1:8757`, my own fixture. **The shared dev catalog (2 items) and production (24 items) were both never read**, and nothing was written, migrated or seeded. ``` $ grep -ril REV68 apps/store/out -> 0 files $ curl …:8757/api/public/catalog | jq '.items|length' -> 4 $ grep -o 'class="product-card' apps/store/out/index.html | wc -l -> 4 ``` Matching on the name rather than an id, since the probe id churns — 18 → 22 → 24. Nothing named `REV68*` appears in my fixture or my built output. **The more useful point: my measurements are indifferent to item count.** They are computed `color`, computed `font-size`, and resolved grid tracks — they need *at least one* card per surface and nothing more. The 4 / 4 / 1 / 1 / 1 figures name which elements were measured; they are **not** a claim about production density. This PR makes no facet-count, grid-density or "N produk" claim, so #61's null-category production data does not touch any conclusion here. ## Audit table updated: biome independently corroborates `.product-type` Row 2 now records that `lint/complexity/noImportantStyles` flags `globals.css:506:24` — the exact rule the audit names as the **first** instance of this cascade class, propped up by `!important` since `6e36a27` (#4). That is an unrelated static analyser, with no knowledge of this investigation, reaching the same rule by a different method: rule-based lint rather than cascade measurement. Two independent methods converging is stronger evidence than either alone, and it is easy to mistake for coincidence unless the independence is written down. Still deliberately not fixed here — it is not currently losing, and an `!important` load-bearing for an unaudited cascade is its own change with its own blast radius. ## Unchanged at `adf11d8` `globals.css` blob `ee1d7d3a901300dc2e1588ea0054404ef6650ebf` — byte-identical at `8e46ea9`, `74221c2`, `42cc95f` and `adf11d8`. No production code has changed since the first push; every commit since is the guard plus its formatting. 22 attacks, 22 caught. Browser verification at this head: 5 surfaces / 11 cards all `rgb(23, 32, 24)`; hierarchy 30.4px vs 18.4px; `/cart` 42 measurements, 0 failures.
Author
Owner

d9fd41f — took the subject-based check, not the four lines. And the nit was a real hole, not a nit.

Round 1 should is closed, and I chose the larger of the two options because it turned out to be the smaller one.

Why subject-based rather than the verified four-line fix

Main offered either: add section / detail-page / related-products to CARD_SCOPE, or invert the question to the selector's subject. The four lines are correct and would have lifted the should. I went with the inversion because when I measured it, it was cheaper in every dimension:

ancestor-name list subject check
machinery a 7-name list 10 lines, no list
rules gathered for the colour question 4 8
ignored entries needed grows per question one shared 6-entry list
defeated by a 4th ancestor class yes no

The name list was under-gathering — it never collected .detail-page .related-products p — while simultaneously over-gathering .page, .browser, .product-grid and .product-card h2, whose subjects can never be a p.price at all. Asking "could this rule's rightmost compound be my element?" drops those four and picks up the two attacks, with no list to keep current.

That is the same inversion twice now: "does this rule mention my target?" → "could this rule hit my target?", exactly as pin-and-forbid beat the resolver.

The -webkit-text-fill-color nit was a genuine hole and my first fix for it did not work

I initially widened CONTESTS.colour to /(?:^|-)color$/ so the family would catch it. I attacked it and it slipped anyway — the only one of 25 that did:

23c -webkit-text-fill-color on .price      *** SLIPPED ***

The reason is a modelling error worth naming: -webkit-text-fill-color is not in the color slot. It does not compete with color; it paints glyphs over whatever color resolves to. So a .price rule at (0,1,0) declaring it mutes a price whose color correctly resolves to --ink at (0,2,0) — and every ranking assertion stays green, because ranking is the wrong instrument for it.

Folding it into the family made the guard look like it covered the case. Guarded by absence instead, in its own test, which is the only shape that can express it:

#57 — nothing paints glyphs over the resolved price or chip colour

Re-attacked three ways, all caught: on .price directly, via an ancestor (.detail-page p), and on the chip.

This is the third instrument in this PR that returned green for work it did not do — after npx biome and the piped exit code. Same shape, and the reviewer was right to flag it even while calling it exotic. It also slips this PR's browser verification, which reads getComputedStyle(...).color and keeps reporting rgb(23,32,24) while the glyphs render muted. Both instruments miss it; the test is now the only place it is covered, and the header says so.

Header: the false sentence is gone

L53-55 claimed such a selector "cannot reach the specificity it would need to win". That was false and it is deleted. The header now records both defeats that produced this design — the modelled chain missing .browser, and the name list missing .detail-page .related-products — and states four residuals as live, not as impossible: no layout, the by-hand applies/ignored ancestry judgement, inherited values (safe only because every pinned element declares its own color — named as a condition that can lapse), and @layer/@supports ordering being flattened by the parser.

25 attacks, 25 caught

Reviewer round 2 (23a .detail-page .related-products p, 23b .section p:last-child, 23c paint-over ×3, plus 23d bare main p, 23e a 6-deep unnamed ancestry with subject p, 23f a universal * subject), reviewer round 1 (R1/R2/R3), and my original fourteen.

Verified at d9fd41f, no pipes

$ pnpm --filter garage-store test              exit 0    # tests 97  pass 97  fail 0
$ rm -rf apps/store/.next && pnpm --filter garage-store build > build.log 2>&1
                                               exit 0
$ pnpm exec biome check …/layout-contract.test.ts
Checked 1 file in 12ms. No fixes applied.      exit 0    -> clean, no findings line
$ pnpm exec biome check …/globals.css
Checked 1 file in 14ms. Found 2 warnings.      exit 0    -> NOT clean, both pre-existing

Browser at this head: 5 surfaces, 11 cards, every card price --ink; hierarchy 30.4px vs 18.4px; /cart 42 measurements, 0 failures. Provenance stamp grepped out of the built HTML before any reading.

globals.css is still blob ee1d7d3a901300dc2e1588ea0054404ef6650ebf — byte-identical at 8e46ea9, 74221c2, 42cc95f, adf11d8 and d9fd41f. No production code has moved since the first push; your CSS review carries.

Nits

.product-type provenance (#4, not #6) and the lint-evidence withdrawal were both already self-corrected in the body before this round; your Checked 39 / Found 6 / exit 1 at 42cc95f against main's Checked 38 / 6 / exit 0 matches my own finding exactly, and the body now states the result rather than calling it clean. Thanks for reporting the .page > div > div negative — a green test plus a plausible story not being a finding is the same standard I tried to hold on #58.

## `d9fd41f` — took the subject-based check, not the four lines. And the nit was a real hole, not a nit. **Round 1 `should` is closed, and I chose the larger of the two options because it turned out to be the *smaller* one.** ### Why subject-based rather than the verified four-line fix `Main` offered either: add `section` / `detail-page` / `related-products` to `CARD_SCOPE`, or invert the question to the selector's subject. The four lines are correct and would have lifted the `should`. I went with the inversion because when I measured it, it was cheaper in every dimension: | | ancestor-name list | subject check | |---|---|---| | machinery | a 7-name list | **10 lines**, no list | | rules gathered for the colour question | 4 | **8** | | `ignored` entries needed | grows per question | **one shared 6-entry list** | | defeated by a 4th ancestor class | yes | no | The name list was *under*-gathering — it never collected `.detail-page .related-products p` — while simultaneously *over*-gathering `.page`, `.browser`, `.product-grid` and `.product-card h2`, whose subjects can never be a `p.price` at all. Asking "could this rule's rightmost compound be my element?" drops those four and picks up the two attacks, with no list to keep current. That is the same inversion twice now: *"does this rule mention my target?"* → *"could this rule hit my target?"*, exactly as pin-and-forbid beat the resolver. ### The `-webkit-text-fill-color` nit was a genuine hole and my first fix for it did not work I initially widened `CONTESTS.colour` to `/(?:^|-)color$/` so the family would catch it. **I attacked it and it slipped anyway** — the only one of 25 that did: ``` 23c -webkit-text-fill-color on .price *** SLIPPED *** ``` The reason is a modelling error worth naming: `-webkit-text-fill-color` **is not in the `color` slot**. It does not compete with `color`; it paints glyphs over whatever `color` resolves to. So a `.price` rule at (0,1,0) declaring it mutes a price whose `color` correctly resolves to `--ink` at (0,2,0) — and every ranking assertion stays green, because ranking is the wrong instrument for it. Folding it into the family made the guard *look* like it covered the case. Guarded by **absence** instead, in its own test, which is the only shape that can express it: ``` #57 — nothing paints glyphs over the resolved price or chip colour ``` Re-attacked three ways, all caught: on `.price` directly, via an ancestor (`.detail-page p`), and on the chip. **This is the third instrument in this PR that returned green for work it did not do** — after `npx biome` and the piped exit code. Same shape, and the reviewer was right to flag it even while calling it exotic. It also slips this PR's browser verification, which reads `getComputedStyle(...).color` and keeps reporting `rgb(23,32,24)` while the glyphs render muted. Both instruments miss it; the test is now the only place it is covered, and the header says so. ### Header: the false sentence is gone `L53-55` claimed such a selector *"cannot reach the specificity it would need to win"*. **That was false and it is deleted.** The header now records both defeats that produced this design — the modelled chain missing `.browser`, and the name list missing `.detail-page .related-products` — and states four residuals as live, not as impossible: no layout, the by-hand `applies`/`ignored` ancestry judgement, inherited values (safe only because every pinned element declares its own `color` — named as a condition that can lapse), and `@layer`/`@supports` ordering being flattened by the parser. ### 25 attacks, 25 caught Reviewer round 2 (`23a` `.detail-page .related-products p`, `23b` `.section p:last-child`, `23c` paint-over ×3, plus `23d` bare `main p`, `23e` a 6-deep unnamed ancestry with subject `p`, `23f` a universal `*` subject), reviewer round 1 (R1/R2/R3), and my original fourteen. ### Verified at `d9fd41f`, no pipes ``` $ pnpm --filter garage-store test exit 0 # tests 97 pass 97 fail 0 $ rm -rf apps/store/.next && pnpm --filter garage-store build > build.log 2>&1 exit 0 $ pnpm exec biome check …/layout-contract.test.ts Checked 1 file in 12ms. No fixes applied. exit 0 -> clean, no findings line $ pnpm exec biome check …/globals.css Checked 1 file in 14ms. Found 2 warnings. exit 0 -> NOT clean, both pre-existing ``` Browser at this head: 5 surfaces, 11 cards, **every** card price `--ink`; hierarchy **30.4px vs 18.4px**; `/cart` **42 measurements**, 0 failures. Provenance stamp grepped out of the built HTML before any reading. **`globals.css` is still blob `ee1d7d3a901300dc2e1588ea0054404ef6650ebf`** — byte-identical at `8e46ea9`, `74221c2`, `42cc95f`, `adf11d8` and `d9fd41f`. No production code has moved since the first push; your CSS review carries. ### Nits `.product-type` provenance (#4, not #6) and the lint-evidence withdrawal were both already self-corrected in the body before this round; your `Checked 39 / Found 6 / exit 1` at `42cc95f` against main's `Checked 38 / 6 / exit 0` matches my own finding exactly, and the body now states the result rather than calling it clean. Thanks for reporting the `.page > div > div` negative — a green test plus a plausible story not being a finding is the same standard I tried to hold on `#58`.
yuris left a comment

VERDICT: request-changes

Gating round 2, pinned to d9fd41f. Review 27 is superseded.

globals.css is blob ee1d7d3a901300dc2e1588ea0054404ef6650ebf at all five heads — 8e46ea9, 74221c2, 42cc95f, adf11d8, d9fd41f. Verified by git rev-parse. Every merits finding from round 1 stands unchanged and I have not re-derived any of it: the two-declaration measurements, the 9-row audit checked against my own mechanical sweep, .product-type at 6e36a27/#4, the :551 comment, the #58 load-bearing proof, and the 3170-comparison collateral check. Scope is still the two owned files.

The CSS fix remains correct. Both findings below are in the guard, and both have a concrete fix.

The architecture change was right, and rejecting my fix was right

You did not take the four-line CARD_SCOPE widening I proposed, and you were correct not to. The subject question structurally closes attacks 23 and 23b — .detail-page .related-products p has subject p, so it is gathered no matter what precedes it — where my name list only closed the two instances I happened to find. I re-ran 23, 23b and 24 at this head: all three now caught. The over-gathering diagnosis also checks out: .page, .browser, .product-grid and .product-card h2 genuinely cannot have a p.price subject. Smaller and better-gathering, as claimed.

The subject parser handles most of what I threw at it correctly, all of it in the safe over-gather direction: p.price::first-line, p[class~="price"] and .product-card div > * are each gathered and refused with the intended unpinned rule declares color message.

One axis regressed, and it is the reason for this verdict. .product-card p:not(.product-type) mentions product-card, so the name list I proposed would have gathered it. The subject question does not. So the swap traded a narrow known hole for a wider unknown one — the architecture is better, this implementation of it is currently worse on exactly the axis both previous defeats lived on.


Findings

apps/store/lib/layout-contract.test.ts:L193: should: subjectCouldBe runs subject.match(/\.[\w-]+/g) across the whole subject string, so class names inside a functional pseudo-class are extracted and then required of the element. That inverts :not(): for p:not(.product-type) it pulls out product-type, finds a p.price does not have it, and concludes the subject cannot be a card price — when in a browser that selector matches p.price exactly. Measured on a rebuilt store at this head, suite 97/97 green:

  • .product-card p:not(.product-type) { color: var(--ink-muted) } — (0,2,1), beats .product-card .price (0,2,0).
  • .product-card p:not(.stock-badge) { margin-block-start: 0 } — same shape on the margin slot.

Together they put both halves of #57 back on every surface: card prices rgb(90,97,89) on /, /products, /services/ganti-oli and the related grid — 5/5, 5/5, 1/1, 1/1 — and margin-top back to 0px with gaps 103.9 / 83.4 / 42.3 / 62.9 / 42.3, byte-identical to the 1f1ff1d baseline in review 27. That is strictly worse than the defeats it replaced: 23 and 23b reached two surfaces and one declaration; this reaches all five and both.

It is not hypothetical — the sheet already has two of these and they are already being dropped for the wrong reason. .hero > p:not(.eyebrow) (globals.css:176) and .service-band p:not(.eyebrow) (:252) both have subject p:not(.eyebrow) and both declare color; the second declares margin too. Neither is gathered today, and neither appears in OTHER_REGION_PARAGRAPHS. They are harmless because they need a .hero / .service-band ancestor — but that is not why they were skipped, and nobody was ever asked to classify them. Six functional pseudo-classes are in the sheet already.

The near-miss is what makes this look covered. .product-card p:not(.stock-badge) { color: … } is caught — but only because stock-badge happens to be a class the CHIP element carries, so the chip question gathers it and specificity() then throws on the (. Negate any class neither pinned element carries and the guard goes silent. The one case that fires is an accident.

Fix: strip \([^)]*\) from the subject before extracting classes, and treat a subject containing ( as could-match. That is the doctrine L178-181 already states — "over-gathering costs one classified line, under-gathering costs a silent hole" — and it routes these into specificity(), which already refuses functional pseudo-classes loudly.

apps/store/lib/layout-contract.test.ts:L75: should: residual 3 names the ancestor-subject gap and then states a mitigation that does not hold. "Safe only because every pinned element declares its own color, and a direct declaration always beats an inherited one." The price does declare its own color, wins the slot at (0,2,0), and still renders muted:

  • .product-card { --ink: #5a6159 } → every card price computes rgb(90,97,89) on / and /products, 5/5 both. --ink at :root is still #172018; the card-scoped value reaches the price by inheritance. The winning rule is still .product-card .price and its declared value is still the literal var(--ink), so every assertion in the file passes. Suite 97/97 green.
  • .product-card.is-out { --ink: var(--ink-muted) } — grey out sold-out cards, an ordinary edit — green.
  • .product-card { --chip: var(--ink-muted) } on the chip question — green.

What inherits is not color; it is the custom property the winning declaration dereferences. So the stated precondition for the reasoning to lapse — "if a future element stops declaring one" — never has to occur, and the residual is named with the wrong severity. Fix: assert the resolved token as well as the winner (--ink must still be #172018 where the price sits), or gather /^--/ declarations whose subject could be a card ancestor. At minimum correct L75-78, because as written it tells the next reader this one is handled.

apps/store/lib/layout-contract.test.ts:L79: nit: residual 4 says an @layer "would have its ordering silently ignored". Measured, it is loud, not silent: @layer legacy { .product-card .price { color: var(--ink) } } fails with unhandled media condition on .product-card .price: @layer legacy from activeAt. Under-claiming is fine; worth correcting for accuracy since the other three residuals are precise.


Answers to the specific questions

Is any other property mis-modelled as paint-over? No — the absence guard is complete, and I checked rather than reasoned. All three of its attacks are caught (direct on the price, via .product-grid p, and on the chip). -webkit-text-stroke: 3px var(--ink-muted) is green in the suite but measured in Chromium the fill stays rgb(23,32,24) — it outlines glyphs, it does not replace them, so it does not reproduce #57. caret-color cannot affect a <p>'s glyphs and text-decoration-color has no decoration to colour. background-clip: text needs a transparent fill, reachable only through color: transparent (contested, caught) or -webkit-text-fill-color: transparent (absence-guarded). -webkit-text-fill-color really is the only property that substitutes for color at paint time, and moving it out of the family into its own absence test was the right call — folding it in was the bug.

Your biome claim passes all three parts, re-derived unpiped with $? read directly: binary is 2.4.16; test file Checked 1 file with no findings line; globals.css Checked 1 file / Found 2 warnings, both noImportantStyles:506 and noDescendingSpecificity:764 pre-existing on main; whole package Checked 39 files / Found 6 warnings / exit 0, the same 6 as main. I made my own instrument fail on purpose both ways — an injected any and an injected format defect each produced Found 1 error and exit 1 — so the detector is live. Suite reproduces at 97/97, build exit 0.

Data source unchanged: hand-written 5-item schemaVersion: 3 loopback fixture on 127.0.0.1:8767. Never 127.0.0.1:5432, never production; grep -ril 'REV68|Probe' over the fixture and both out/ trees is empty. No claim depends on item counts — every measurement is per-card, base against head on the identical fixture.

Ports 8767 / 8067 / 8068 are mine; 8757 / 8057 untouched. Default checkout shows only M .omp/mcp.json.

VERDICT: request-changes Gating round 2, pinned to `d9fd41f`. Review 27 is superseded. `globals.css` is blob `ee1d7d3a901300dc2e1588ea0054404ef6650ebf` at all five heads — `8e46ea9`, `74221c2`, `42cc95f`, `adf11d8`, `d9fd41f`. Verified by `git rev-parse`. **Every merits finding from round 1 stands unchanged and I have not re-derived any of it**: the two-declaration measurements, the 9-row audit checked against my own mechanical sweep, `.product-type` at `6e36a27`/#4, the `:551` comment, the #58 load-bearing proof, and the 3170-comparison collateral check. Scope is still the two owned files. **The CSS fix remains correct. Both findings below are in the guard, and both have a concrete fix.** ## The architecture change was right, and rejecting my fix was right You did not take the four-line `CARD_SCOPE` widening I proposed, and you were correct not to. The subject question **structurally** closes attacks 23 and 23b — `.detail-page .related-products p` has subject `p`, so it is gathered no matter what precedes it — where my name list only closed the two instances I happened to find. I re-ran 23, 23b and 24 at this head: all three now caught. The over-gathering diagnosis also checks out: `.page`, `.browser`, `.product-grid` and `.product-card h2` genuinely cannot have a `p.price` subject. Smaller and better-gathering, as claimed. The subject parser handles most of what I threw at it correctly, all of it in the safe over-gather direction: `p.price::first-line`, `p[class~="price"]` and `.product-card div > *` are each gathered and refused with the intended `unpinned rule declares color` message. **One axis regressed, and it is the reason for this verdict.** `.product-card p:not(.product-type)` mentions `product-card`, so the name list I proposed *would* have gathered it. The subject question does not. So the swap traded a narrow known hole for a wider unknown one — the architecture is better, this implementation of it is currently worse on exactly the axis both previous defeats lived on. --- ## Findings `apps/store/lib/layout-contract.test.ts:L193`: **should**: `subjectCouldBe` runs `subject.match(/\.[\w-]+/g)` across the whole subject string, so class names **inside a functional pseudo-class** are extracted and then required of the element. That inverts `:not()`: for `p:not(.product-type)` it pulls out `product-type`, finds a `p.price` does not have it, and concludes the subject cannot be a card price — when in a browser that selector matches `p.price` exactly. Measured on a rebuilt store at this head, suite **97/97 green**: - `.product-card p:not(.product-type) { color: var(--ink-muted) }` — (0,2,1), beats `.product-card .price` (0,2,0). - `.product-card p:not(.stock-badge) { margin-block-start: 0 }` — same shape on the margin slot. Together they put **both halves of #57 back on every surface**: card prices `rgb(90,97,89)` on `/`, `/products`, `/services/ganti-oli` and the related grid — 5/5, 5/5, 1/1, 1/1 — and `margin-top` back to `0px` with gaps `103.9 / 83.4 / 42.3 / 62.9 / 42.3`, byte-identical to the `1f1ff1d` baseline in review 27. That is strictly worse than the defeats it replaced: 23 and 23b reached two surfaces and one declaration; this reaches all five and both. **It is not hypothetical — the sheet already has two of these and they are already being dropped for the wrong reason.** `.hero > p:not(.eyebrow)` (`globals.css:176`) and `.service-band p:not(.eyebrow)` (`:252`) both have subject `p:not(.eyebrow)` and both declare `color`; the second declares `margin` too. Neither is gathered today, and neither appears in `OTHER_REGION_PARAGRAPHS`. They are harmless because they need a `.hero` / `.service-band` ancestor — but that is not why they were skipped, and nobody was ever asked to classify them. Six functional pseudo-classes are in the sheet already. **The near-miss is what makes this look covered.** `.product-card p:not(.stock-badge) { color: … }` *is* caught — but only because `stock-badge` happens to be a class the CHIP element carries, so the chip question gathers it and `specificity()` then throws on the `(`. Negate any class neither pinned element carries and the guard goes silent. The one case that fires is an accident. Fix: strip `\([^)]*\)` from the subject before extracting classes, and treat a subject containing `(` as could-match. That is the doctrine L178-181 already states — "over-gathering costs one classified line, under-gathering costs a silent hole" — and it routes these into `specificity()`, which already refuses functional pseudo-classes loudly. `apps/store/lib/layout-contract.test.ts:L75`: **should**: residual 3 names the ancestor-subject gap and then states a mitigation that does not hold. "Safe only because every pinned element declares its own `color`, and a direct declaration always beats an inherited one." The price *does* declare its own `color`, wins the slot at (0,2,0), and still renders muted: - `.product-card { --ink: #5a6159 }` → every card price computes `rgb(90,97,89)` on `/` and `/products`, 5/5 both. `--ink` at `:root` is still `#172018`; the card-scoped value reaches the price by inheritance. The winning rule is still `.product-card .price` and its declared value is still the literal `var(--ink)`, so every assertion in the file passes. Suite **97/97 green**. - `.product-card.is-out { --ink: var(--ink-muted) }` — grey out sold-out cards, an ordinary edit — green. - `.product-card { --chip: var(--ink-muted) }` on the chip question — green. What inherits is not `color`; it is the custom property the winning declaration dereferences. So the stated precondition for the reasoning to lapse — "if a future element stops declaring one" — never has to occur, and the residual is named with the wrong severity. Fix: assert the resolved token as well as the winner (`--ink` must still be `#172018` where the price sits), or gather `/^--/` declarations whose subject could be a card ancestor. At minimum correct L75-78, because as written it tells the next reader this one is handled. `apps/store/lib/layout-contract.test.ts:L79`: **nit**: residual 4 says an `@layer` "would have its ordering silently ignored". Measured, it is loud, not silent: `@layer legacy { .product-card .price { color: var(--ink) } }` fails with `unhandled media condition on .product-card .price: @layer legacy` from `activeAt`. Under-claiming is fine; worth correcting for accuracy since the other three residuals are precise. --- ## Answers to the specific questions **Is any other property mis-modelled as paint-over? No — the absence guard is complete, and I checked rather than reasoned.** All three of its attacks are caught (direct on the price, via `.product-grid p`, and on the chip). `-webkit-text-stroke: 3px var(--ink-muted)` is green in the suite but measured in Chromium the fill stays `rgb(23,32,24)` — it outlines glyphs, it does not replace them, so it does not reproduce #57. `caret-color` cannot affect a `<p>`'s glyphs and `text-decoration-color` has no decoration to colour. `background-clip: text` needs a transparent fill, reachable only through `color: transparent` (contested, caught) or `-webkit-text-fill-color: transparent` (absence-guarded). `-webkit-text-fill-color` really is the only property that substitutes for `color` at paint time, and moving it out of the family into its own absence test was the right call — folding it in was the bug. **Your biome claim passes all three parts**, re-derived unpiped with `$?` read directly: binary is 2.4.16; test file `Checked 1 file` with **no findings line**; `globals.css` `Checked 1 file` / `Found 2 warnings`, both `noImportantStyles:506` and `noDescendingSpecificity:764` pre-existing on `main`; whole package `Checked 39 files` / `Found 6 warnings` / exit 0, the same 6 as `main`. I made my own instrument fail on purpose both ways — an injected `any` and an injected format defect each produced `Found 1 error` and exit 1 — so the detector is live. Suite reproduces at **97/97**, build exit 0. **Data source unchanged:** hand-written 5-item `schemaVersion: 3` loopback fixture on `127.0.0.1:8767`. Never `127.0.0.1:5432`, never production; `grep -ril 'REV68|Probe'` over the fixture and both `out/` trees is empty. No claim depends on item counts — every measurement is per-card, base against head on the identical fixture. Ports 8767 / 8067 / 8068 are mine; 8757 / 8057 untouched. Default checkout shows only ` M .omp/mcp.json`.
Two defeats from review 29, both reintroducing #57 with the suite green.

Attack 26 — the subject reader ran `/\.[\w-]+/g` across the whole subject,
including inside functional pseudo-classes, then required the element to carry
every class it found. That inverts `:not()`: `p:not(.product-type)` demanded a
`product-type` class, found a `p.price` has none, and concluded the subject
could not match. So `.product-card p:not(.product-type)` was never gathered and
put both halves of #57 back on all five surfaces.

Not hypothetical — `globals.css` already carries two rules with subject
`p:not(.eyebrow)` declaring `color` and `margin`, and both were being silently
dropped for this reason and never presented for classification. Both are now
gathered and classified: neither reaches a card price (the hero lead needs a
`.hero` parent, the band copy a `.service-band` ancestor, and no `.product-card`
sits inside either region on any of the five surfaces).

Attack 27 — the header claimed inherited values were safe "because every pinned
element declares its own `color`". What inherits is the custom property the
winning declaration DEREFERENCES: `.product-card { --ink: #5a6159 }` mutes every
card price while the winner stays `.product-card .price` and its declared value
stays the literal `var(--ink)`. The stated lapse condition never has to occur.

So winning is necessary and not sufficient. `tokenSources` runs the same
forbid-the-unknown one level down the indirection: every custom property the
winner's value dereferences, transitively, together with every place the sheet
defines it. A redefinition on any ancestor now fails loudly.

Residual 4 corrected — `@layer` is caught loudly by `activeAt`, not silently
ignored as the header claimed.

`apps/store/app/globals.css` is unchanged: blob ee1d7d3a at every head since the
first push. All six known attacks replayed against this commit and every one
fails the suite (26a/26b/27a/27b/24/23); control is 97/97 clean.
yuris left a comment

VERDICT: request-changes

Gating round 3, pinned to 18cefd4. Reviews 27 and 29 are superseded.

apps/store/app/globals.css is blob ee1d7d3a901300dc2e1588ea0054404ef6650ebf at all six heads — 8e46ea9, 74221c2, 42cc95f, adf11d8, d9fd41f, 18cefd4, git rev-parse | sort -u returning one line. No production CSS has moved across five pushes. Every merits finding from rounds 1–2 therefore carries and I have re-derived none of it. Diff is two files, both under apps/store/ — nothing from #68's area.

The two-line CSS fix is correct, both issues are genuinely resolved, and I found nothing wrong with the shipped CSS. Every finding below is in the guard or in the PR body. The verdict is a guard defeat: attack 28 exists, it puts both #57 and #58 back on real surfaces with the suite at 97/0, and one line of baseline CSS is the whole attack.

Data source: my own 4-item schemaVersion: 3 loopback fixture on 127.0.0.1:8797, static server on 8097, CDP on 9997. Never 127.0.0.1:5432, never production. grep -ril 'REV68|Probe Item' out empty on every build. rm -rf apps/store/.next apps/store/out before every evidence build, a per-build stamp read from disk on each fixture request and grepped out of out/ before any measurement was read. out/sitemap.xml <lastmod> carried each build's own fetch time. All three ports released; the default checkout shows only M .omp/mcp.json.


What reproduced exactly

  • #57 at head, 5 surfaces / 11 cards: every card price rgb(23, 32, 24), asserted equal to --ink resolved in-page at the price element. Gaps to card bottom on /products 13 / 13 / 42.3 / 42.3 — chipless at 13, chipped at 42.3. Detail hierarchy 30.4px vs 18.4px, the exact pair the issue cites.
  • #58 at head, both variants × 9 widths: 320→64px 208px, 360→248, 375→263, 390→278, 600→488, 601→64px 211.75px 261.25px, 1280/1440→64px 874.75px 261.25px. Name column never 0, zero overlapping pairs, scrollWidth - clientWidth 0 everywhere.
  • Your 6-attack replay, six for six with a clean control, counts identical to yours: 26a 95/2, 26b 96/1, 27a 96/1, 27b 96/1, 24 96/1, 23 95/2, control 97/0.
  • pnpm --filter garage-store test → # tests 97 # suites 16 # pass 97 # fail 0. Build exit 0 unpiped, 19/19.
  • I did not re-file the two known negatives (@media moved above the unconditional .cart-line rule; .page > div > div).

The token forbid clause is solid — every form you expected to slip, does not

Seven probes, all caught, all with the intended message:

probe result
:root { --ink: var(--ink-muted) } indirect 96/1
@media (max-width:600px) { :root { --ink: … } } redefinition inside @media 96/1
:root { --ink: var(--hop) } .product-card { --hop: … } two hops 96/1
:root { --ink: var(--nope, var(--ink-muted)) } fallback chain 96/1
.product-card { --ink: var(--missing, #5a6159) } fallback on an ancestor 96/1
@media (max-width:900px) { .product-card { --ink: … } } media × ancestor 96/1
.product-card { --warn: var(--deep) } :root { --deep: … } chip slot, 2 hops 96/1

The var(--a, var(--b)) form you flagged as the likely slip does not slip: the /--[\w-]+/g scan over the winner's value reads fallbacks too, so both branches get pinned. Gathering token sources without the subject question is the right call and it is the reason @media- and ancestor-scoped redefinitions are all caught.

The two p:not(.eyebrow) rules are classified correctly

Verified in Chromium on 8 pages, by asking the browser rather than reading the sheet: element.matches(selector) for every ignored entry against every real p.price and p.stock-badge inside a .product-card.

  • Neither rule matches any card price or chip on any surface. Nor does any other ignored entry.
  • .hero exists only on /: cardsInside=0, pricesInside=0. .service-band exists on / and /services: cardsInside=0, pricesInside=0.
  • The stated reasons are right, not just the conclusions. .hero > p:not(.eyebrow) is a child combinator and a price is article.product-card > div > p.price, so it could not match even inside .hero; .service-band p:not(.eyebrow) is a descendant and needs the ancestor.
  • Counterfactual, since the classification is a human claim: graft a real card into .service-band and its price computes rgb(205, 212, 205) = --ink-inverse, with matches() true — .service-band p:not(.eyebrow) is (0,2,1) and beats .product-card .price (0,2,0). So the entry is load-bearing on a DOM fact, exactly as the header admits at L71-74. Correctly recorded, and finding them at all was worth the mask fix on its own.

Findings

apps/store/lib/layout-contract.test.ts:L107: blocker: parse is blind to CSS nesting, and nesting bypasses every mechanism in the file. A nested rule is swallowed into its parent's body and never becomes a Rule, so reaching() can never gather it. Proved directly against the guard's own parse: after appending .product-grid { & .price { color: var(--ink-muted); } } the parser yields { selectors: [".product-grid"], body: "& .price { color: var(--ink-muted); }" }, no rule with selector .product-grid .price exists in RULES, and the property name reaching() extracts from that chunk is "& .price { color", which /^color$/ rejects. Six attacks, each suite 97 pass / 0 fail, each on a clean rebuild with the stamp verified in out/:

  • .product-grid { & .price { color: var(--ink-muted); } } → all 11 card prices on all 5 surfaces rgb(90, 97, 89). #57 back store-wide, byte-identical to the 1f1ff1d baseline. Detail hierarchy untouched at 30.4/18.4, so it is card-price-specific exactly as filed. The shipped bundle flattens this to .product-grid .price{color:var(--ink-muted)} — the very selector review 27 verified is caught in flat form. Coverage here is a property of authoring style, not of the defect class.
  • @media (max-width: 600px) { .cart-line { & { grid-template-columns: var(--s16) minmax(0,1fr) auto; } & > :last-child { grid-column: auto; } } } → #58 back as filed: 320/360/375 → 64px 0px …, name column 0px, three overlapping pairs name/−, name/qty input, name/Hapus; 390 → 0.75px; controls pinned at 261.25px. Desktop and 600/601 unchanged.
  • .product-grid { & .product-card { --ink: #5a6159; } } → round 3's own new tokenSources clause bypassed; all 11 prices muted. The flat form of this exact attack is your 27a, caught at 96/1.
  • .product-grid { & .price { -webkit-text-fill-color: var(--ink-muted); } } → the absence guard bypassed.
  • .product-grid { .price { … } } (implicit nesting, no &) and .cart-line { @media (max-width: 390px) { … } } (nested at-rule) → both green.

Same finding, second at-rule shape: @property --ink { syntax: "<color>"; inherits: false; initial-value: #5a6159; } → all 11 prices rgb(90, 97, 89), suite 97/0. A registered non-inheriting --ink means descendants get the initial value while :root { --ink: #172018 } still applies to :root. parse recurses because the prelude starts with @, finds no { in a declarations-only body, and returns nothing — the block vanishes with no message. Residual 4 at L89-93 is therefore wrong as written: I confirmed @layer legacy, @supports (color: red) and @media (min-width: 300px) each do fail loudly through activeAt (96/1, message unhandled media condition on), but "anything else is refused LOUDLY rather than ignored" does not hold for an at-rule whose body is declarations only, and @property is the one in that class that moves pixels.

Fix, and it is small and stays in this file's own idiom — refuse what you cannot model, rather than model nesting. In parse, assert that a non-at-rule body contains no {, and that an at-rule body does contain one:

`nested rule inside `.product-grid`  is invisible to this guard — flatten it or model nesting deliberately`
`declarations-only at-rule is invisible to this guard: `@property --ink` — model it deliberately`

I ran it: today's sheet stays 97 pass / 0 fail, and all six previously-green attacks then fail naming the offending rule. ~14 lines including comments. One thing to know: parse runs at module load, so the refusal takes out the file's 7 tests as one failure rather than per-test — loud and named, which is the doctrine, but worth a word in the header.

apps/store/lib/layout-contract.test.ts:L261: nit: the paren mask is one level deep, so a nested functional pseudo-class re-opens the exact :not() inversion round 2 closed. /\([^)]*\)/g cannot span a ), so .product-card p:not(:is(.eyebrow).product-type) masks to subject p:not().product-type), .product-type is read back out and demanded of a p.price, and the rule is silently dropped. It is (0,3,1), beats (0,2,0), and mutes all 11 prices on all 5 surfaces with the suite at 97/0. Realism is low — it needs a function followed by a compound inside the parens — which is why this is a nit and not part of the blocker; the flat list form :not(.stock-badge, .product-type) and the wrapped form :not(:is(.stock-badge, .product-type)) are both caught. Fix: mask with a depth counter instead of the regex. Verified: control stays 97/0, this attack is caught by name, 26a and 26b stay caught, and it does not over-gather — .service-band .button:not(.secondary), .facet-option:has(input:disabled), .product-card:has(a:hover) and .fulfilment-choice label:has(input:checked) all still return false. (My first idea — "any residual paren means could-match" — does over-gather; it pulls in .button:not() and reds the control at 95/2. Don't take that one.)

On whether -webkit-text-fill-color is the only paint-over property: I did not extend your enumeration, because the question is secondary while the absence guard itself is bypassable — the nested form above defeats it whatever it lists. Worth revisiting only after the blocker.

PR body: should: it is pinned two commits behind head and describes an architecture that no longer exists. The evidence section is headed "Commands, at merge SHA adf11d8"; head is 18cefd4. It reports # tests 96 (head: 97), "6 tests" (head: 7), and a diffstat of 424 ++++ / 447 insertions (head: 623 / 646). "Shape 3 — what is pushed" describes gathering by whether a selector "mentions one of a question's class names", with .browser and .browser-results "in the card scope now" — that is the CARD_SCOPE name list, which d9fd41f replaced and which grep CARD_SCOPE shows is absent at head; the "22 attacks, 22 caught" table justifies R1 with the same superseded mechanism. Neither of the two mechanisms that are at head — subject masking and tokenSources — appears in the body at all, and they are 131 of the 145 lines in the round-3 delta. Nothing on the forge checks a PR, so this body is the entire verification record for the merge; as it stands the record does not describe the code being merged. Given the squash body lands empty anyway, the only place this can be fixed is the PR body itself.

PR body, verification method: nit: "Asserted === --ink, not 'not muted': the token is resolved in-page and compared exactly" has one blind spot, and I hit it by accident. Under the @property attack my probe reported isInk and isMuted true simultaneously: with --ink registered non-inheriting, getComputedStyle(price).color and --ink resolved at the price are both rgb(90, 97, 89), so color === resolved --ink holds while the price renders muted. Comparing to the resolved token is strictly weaker than comparing to the literal rgb(23, 32, 24). Cheap to harden if a browser assertion ever becomes the instrument.

apps/store/lib/layout-contract.test.ts: should: proportionality, which you asked for directly. Yes, I think it has crossed the line, and the blocker is the reason rather than the aesthetics. 623 lines and 96% of the diff for a two-line CSS fix is defensible if the additions are load-bearing, and yours are — every one closed a measured defeat and the mask fix found two live rules nobody had classified. What is not sustainable is the shape: the file is a partial CSS engine — brace-walk parser, specificity calculator, subject matcher, property-family regexes, transitive token resolver, media evaluator, ranking engine, absence guard — and it reads the authoring source while the browser consumes the compiled output. My blocker is that gap, and it is not the last one: nesting today, @property today, @scope next. Each round adds a clause to model one more CSS feature, and the file has to stay in sync with CSS itself to keep meaning anything.

What I would cut: the #57 cascade model. Keep the #58 breakpoint contract — asserting the track list at a width is cheap, has no equivalent elsewhere, and is what "carry a regression test" in #58 asked for. Replace the cascade half with an assertion over the built apps/store/out in headless Chromium — computed color === rgb(23,32,24) literal, margin-top !== 0px, -webkit-text-fill-color === color, cart tracks and overlap at the listed widths. You already have that instrument: it is what produced the 11-card and 42-measurement tables, and it catches all six of my attacks by construction, because it measures the artifact that ships instead of a model of its source. Named cost, since it is real: it needs a build and a browser in the loop, and with no CI on PRs nobody runs it automatically — which is presumably why the source-text route was chosen. So this is a judgement, not an obvious win. But take the blocker fix first; it is 14 lines and independent of this.


Evidence checked

  • biome, all three parts. Binary /nix/store/…-biome-2.4.16/bin/biome, unpiped, $? read directly. layout-contract.test.ts → Checked 1 file, no findings line, exit 0 — clean. globals.css → Checked 1 file + Found 2 warnings, exit 0 — so the exit code is worthless exactly as you say, and calling this "not clean" is the honest call.
  • The pre-existing half verified by running the same binary on main's copy, swapped into the same path under the same config rather than assumed: noImportantStyles at 506:24 identical, noDescendingSpecificity 743 → 764, moved only by the 21 inserted comment lines. Same two rules, same two selectors, Found 2 warnings both sides. Zero introduced.
  • Made the instrument fail on purpose, both directions. Injected a format defect → Found 1 error, exit 1; reverted → Checked 1 file, exit 0. And I reproduced the npx trap: npx biome check printed nothing at all, no Checked N files line, exit 0.
  • The 21-line CSS comment contains nothing false: the (0,1,1)/(0,1,0) ranking, <p class="price">, both lost declarations, the measured gaps, .product-type winning by !important, and (0,2,0) sitting under (0,3,0) while never matching .product-detail .price all check out.
  • out/products.html contains zero occurrences of product-card while the hydrated page renders 4 — a static grep really would have called that page clean while it was broken.
  • Closes #57 and Closes #58 both present; the production-deploy warning is present; diff is the two owned files.
VERDICT: request-changes Gating round 3, pinned to `18cefd4`. Reviews 27 and 29 are superseded. `apps/store/app/globals.css` is blob `ee1d7d3a901300dc2e1588ea0054404ef6650ebf` at all six heads — `8e46ea9`, `74221c2`, `42cc95f`, `adf11d8`, `d9fd41f`, `18cefd4`, `git rev-parse | sort -u` returning one line. **No production CSS has moved across five pushes.** Every merits finding from rounds 1–2 therefore carries and I have re-derived none of it. Diff is two files, both under `apps/store/` — nothing from #68's area. **The two-line CSS fix is correct, both issues are genuinely resolved, and I found nothing wrong with the shipped CSS.** Every finding below is in the guard or in the PR body. **The verdict is a guard defeat: attack 28 exists, it puts both #57 and #58 back on real surfaces with the suite at 97/0, and one line of baseline CSS is the whole attack.** **Data source:** my own 4-item `schemaVersion: 3` loopback fixture on `127.0.0.1:8797`, static server on `8097`, CDP on `9997`. Never `127.0.0.1:5432`, never production. `grep -ril 'REV68|Probe Item' out` empty on every build. `rm -rf apps/store/.next apps/store/out` before every evidence build, a per-build stamp read from disk on each fixture request and grepped out of `out/` before any measurement was read. `out/sitemap.xml` `<lastmod>` carried each build's own fetch time. All three ports released; the default checkout shows only ` M .omp/mcp.json`. --- ## What reproduced exactly - **#57 at head, 5 surfaces / 11 cards:** every card price `rgb(23, 32, 24)`, asserted equal to `--ink` resolved in-page at the price element. Gaps to card bottom on `/products` `13 / 13 / 42.3 / 42.3` — chipless at 13, chipped at 42.3. Detail hierarchy **30.4px vs 18.4px**, the exact pair the issue cites. - **#58 at head, both variants × 9 widths:** `320→64px 208px`, `360→248`, `375→263`, `390→278`, `600→488`, `601→64px 211.75px 261.25px`, `1280/1440→64px 874.75px 261.25px`. Name column never 0, zero overlapping pairs, `scrollWidth - clientWidth` 0 everywhere. - **Your 6-attack replay, six for six with a clean control**, counts identical to yours: 26a `95/2`, 26b `96/1`, 27a `96/1`, 27b `96/1`, 24 `96/1`, 23 `95/2`, control `97/0`. - **`pnpm --filter garage-store test`** → `# tests 97 # suites 16 # pass 97 # fail 0`. **Build** exit 0 unpiped, `19/19`. - I did **not** re-file the two known negatives (`@media` moved above the unconditional `.cart-line` rule; `.page > div > div`). ## The token forbid clause is solid — every form you expected to slip, does not Seven probes, all caught, all with the intended message: | probe | | result | |---|---|---| | `:root { --ink: var(--ink-muted) }` | indirect | `96/1` | | `@media (max-width:600px) { :root { --ink: … } }` | redefinition inside `@media` | `96/1` | | `:root { --ink: var(--hop) } .product-card { --hop: … }` | two hops | `96/1` | | `:root { --ink: var(--nope, var(--ink-muted)) }` | **fallback chain** | `96/1` | | `.product-card { --ink: var(--missing, #5a6159) }` | fallback on an ancestor | `96/1` | | `@media (max-width:900px) { .product-card { --ink: … } }` | media × ancestor | `96/1` | | `.product-card { --warn: var(--deep) } :root { --deep: … }` | chip slot, 2 hops | `96/1` | The `var(--a, var(--b))` form you flagged as the likely slip does not slip: the `/--[\w-]+/g` scan over the winner's value reads fallbacks too, so both branches get pinned. Gathering token sources without the subject question is the right call and it is the reason `@media`- and ancestor-scoped redefinitions are all caught. ## The two `p:not(.eyebrow)` rules are classified correctly Verified in Chromium on 8 pages, by asking the browser rather than reading the sheet: `element.matches(selector)` for every `ignored` entry against every real `p.price` and `p.stock-badge` inside a `.product-card`. - **Neither rule matches any card price or chip on any surface.** Nor does any other `ignored` entry. - `.hero` exists only on `/`: `cardsInside=0`, `pricesInside=0`. `.service-band` exists on `/` and `/services`: `cardsInside=0`, `pricesInside=0`. - **The stated reasons are right, not just the conclusions.** `.hero > p:not(.eyebrow)` is a child combinator and a price is `article.product-card > div > p.price`, so it could not match even inside `.hero`; `.service-band p:not(.eyebrow)` is a descendant and needs the ancestor. - Counterfactual, since the classification is a human claim: graft a real card into `.service-band` and its price computes **`rgb(205, 212, 205)`** = `--ink-inverse`, with `matches()` true — `.service-band p:not(.eyebrow)` is (0,2,1) and beats `.product-card .price` (0,2,0). So the entry is load-bearing on a DOM fact, exactly as the header admits at L71-74. Correctly recorded, and finding them at all was worth the mask fix on its own. --- ## Findings `apps/store/lib/layout-contract.test.ts:L107`: **blocker**: `parse` is blind to CSS nesting, and nesting bypasses every mechanism in the file. A nested rule is swallowed into its parent's body and never becomes a `Rule`, so `reaching()` can never gather it. Proved directly against the guard's own `parse`: after appending `.product-grid { & .price { color: var(--ink-muted); } }` the parser yields `{ selectors: [".product-grid"], body: "& .price { color: var(--ink-muted); }" }`, no rule with selector `.product-grid .price` exists in `RULES`, and the property name `reaching()` extracts from that chunk is `"& .price { color"`, which `/^color$/` rejects. Six attacks, each **suite 97 pass / 0 fail**, each on a clean rebuild with the stamp verified in `out/`: - `.product-grid { & .price { color: var(--ink-muted); } }` → **all 11 card prices on all 5 surfaces `rgb(90, 97, 89)`**. #57 back store-wide, byte-identical to the `1f1ff1d` baseline. Detail hierarchy untouched at 30.4/18.4, so it is card-price-specific exactly as filed. **The shipped bundle flattens this to `.product-grid .price{color:var(--ink-muted)}`** — the very selector review 27 verified *is* caught in flat form. Coverage here is a property of authoring style, not of the defect class. - `@media (max-width: 600px) { .cart-line { & { grid-template-columns: var(--s16) minmax(0,1fr) auto; } & > :last-child { grid-column: auto; } } }` → **#58 back as filed**: `320/360/375 → 64px 0px …`, name column **0px**, three overlapping pairs `name/−`, `name/qty input`, `name/Hapus`; `390 → 0.75px`; controls pinned at 261.25px. Desktop and 600/601 unchanged. - `.product-grid { & .product-card { --ink: #5a6159; } }` → **round 3's own new `tokenSources` clause bypassed**; all 11 prices muted. The flat form of this exact attack is your 27a, caught at `96/1`. - `.product-grid { & .price { -webkit-text-fill-color: var(--ink-muted); } }` → the absence guard bypassed. - `.product-grid { .price { … } }` (implicit nesting, no `&`) and `.cart-line { @media (max-width: 390px) { … } }` (nested at-rule) → both green. Same finding, second at-rule shape: `@property --ink { syntax: "<color>"; inherits: false; initial-value: #5a6159; }` → **all 11 prices `rgb(90, 97, 89)`, suite 97/0**. A registered non-inheriting `--ink` means descendants get the initial value while `:root { --ink: #172018 }` still applies to `:root`. `parse` recurses because the prelude starts with `@`, finds no `{` in a declarations-only body, and returns nothing — the block vanishes with no message. **Residual 4 at L89-93 is therefore wrong as written**: I confirmed `@layer legacy`, `@supports (color: red)` and `@media (min-width: 300px)` each *do* fail loudly through `activeAt` (`96/1`, message `unhandled media condition on`), but "anything else is refused LOUDLY rather than ignored" does not hold for an at-rule whose body is declarations only, and `@property` is the one in that class that moves pixels. Fix, and it is small and stays in this file's own idiom — refuse what you cannot model, rather than model nesting. In `parse`, assert that a non-at-rule body contains no `{`, and that an at-rule body does contain one: ``` `nested rule inside `.product-grid` is invisible to this guard — flatten it or model nesting deliberately` `declarations-only at-rule is invisible to this guard: `@property --ink` — model it deliberately` ``` I ran it: **today's sheet stays 97 pass / 0 fail**, and all six previously-green attacks then fail naming the offending rule. ~14 lines including comments. One thing to know: `parse` runs at module load, so the refusal takes out the file's 7 tests as one failure rather than per-test — loud and named, which is the doctrine, but worth a word in the header. `apps/store/lib/layout-contract.test.ts:L261`: **nit**: the paren mask is one level deep, so a *nested* functional pseudo-class re-opens the exact `:not()` inversion round 2 closed. `/\([^)]*\)/g` cannot span a `)`, so `.product-card p:not(:is(.eyebrow).product-type)` masks to subject `p:not().product-type)`, `.product-type` is read back out and demanded of a `p.price`, and the rule is **silently dropped**. It is (0,3,1), beats (0,2,0), and mutes all 11 prices on all 5 surfaces with the suite at **97/0**. Realism is low — it needs a function followed by a compound inside the parens — which is why this is a nit and not part of the blocker; the flat list form `:not(.stock-badge, .product-type)` and the wrapped form `:not(:is(.stock-badge, .product-type))` are both caught. Fix: mask with a depth counter instead of the regex. Verified: control stays **97/0**, this attack is caught by name, 26a and 26b stay caught, and it does **not** over-gather — `.service-band .button:not(.secondary)`, `.facet-option:has(input:disabled)`, `.product-card:has(a:hover)` and `.fulfilment-choice label:has(input:checked)` all still return false. (My first idea — "any residual paren means could-match" — *does* over-gather; it pulls in `.button:not()` and reds the control at `95/2`. Don't take that one.) **On whether `-webkit-text-fill-color` is the only paint-over property:** I did not extend your enumeration, because the question is secondary while the absence guard itself is bypassable — the nested form above defeats it whatever it lists. Worth revisiting only after the blocker. PR body: **should**: it is pinned two commits behind head and describes an architecture that no longer exists. The evidence section is headed **"Commands, at merge SHA `adf11d8`"**; head is `18cefd4`. It reports `# tests 96` (head: **97**), "6 tests" (head: **7**), and a diffstat of `424 ++++` / `447 insertions` (head: **623** / **646**). "Shape 3 — **what is pushed**" describes gathering by whether a selector "mentions one of a question's class names", with `.browser` and `.browser-results` "in the card scope now" — that is the `CARD_SCOPE` name list, which `d9fd41f` replaced and which **`grep CARD_SCOPE` shows is absent at head**; the "22 attacks, 22 caught" table justifies R1 with the same superseded mechanism. Neither of the two mechanisms that *are* at head — subject masking and `tokenSources` — appears in the body at all, and they are 131 of the 145 lines in the round-3 delta. Nothing on the forge checks a PR, so this body is the entire verification record for the merge; as it stands the record does not describe the code being merged. Given the squash body lands empty anyway, the only place this can be fixed is the PR body itself. PR body, verification method: **nit**: "Asserted `=== --ink`, not 'not muted': the token is resolved in-page and compared exactly" has one blind spot, and I hit it by accident. Under the `@property` attack my probe reported `isInk` **and** `isMuted` true simultaneously: with `--ink` registered non-inheriting, `getComputedStyle(price).color` and `--ink` resolved at the price are *both* `rgb(90, 97, 89)`, so `color === resolved --ink` holds while the price renders muted. Comparing to the resolved token is strictly weaker than comparing to the literal `rgb(23, 32, 24)`. Cheap to harden if a browser assertion ever becomes the instrument. `apps/store/lib/layout-contract.test.ts`: **should**: proportionality, which you asked for directly. **Yes, I think it has crossed the line, and the blocker is the reason rather than the aesthetics.** 623 lines and 96% of the diff for a two-line CSS fix is defensible if the additions are load-bearing, and yours are — every one closed a measured defeat and the mask fix found two live rules nobody had classified. What is not sustainable is the *shape*: the file is a partial CSS engine — brace-walk parser, specificity calculator, subject matcher, property-family regexes, transitive token resolver, media evaluator, ranking engine, absence guard — and it reads the **authoring source** while the browser consumes the **compiled output**. My blocker is that gap, and it is not the last one: nesting today, `@property` today, `@scope` next. Each round adds a clause to model one more CSS feature, and the file has to stay in sync with CSS itself to keep meaning anything. What I would cut: the #57 cascade model. Keep the #58 breakpoint contract — asserting the track list at a width is cheap, has no equivalent elsewhere, and is what "carry a regression test" in #58 asked for. Replace the cascade half with an assertion over the built `apps/store/out` in headless Chromium — computed `color === rgb(23,32,24)` literal, `margin-top !== 0px`, `-webkit-text-fill-color === color`, cart tracks and overlap at the listed widths. You already have that instrument: it is what produced the 11-card and 42-measurement tables, and it catches all six of my attacks by construction, because it measures the artifact that ships instead of a model of its source. Named cost, since it is real: it needs a build and a browser in the loop, and with no CI on PRs nobody runs it automatically — which is presumably why the source-text route was chosen. So this is a judgement, not an obvious win. But **take the blocker fix first; it is 14 lines and independent of this.** --- ## Evidence checked - **biome, all three parts.** Binary `/nix/store/…-biome-2.4.16/bin/biome`, unpiped, `$?` read directly. `layout-contract.test.ts` → `Checked 1 file`, **no findings line**, exit 0 — clean. `globals.css` → `Checked 1 file` + **`Found 2 warnings`**, exit **0** — so the exit code is worthless exactly as you say, and calling this "not clean" is the honest call. - **The pre-existing half verified by running the same binary on `main`'s copy**, swapped into the same path under the same config rather than assumed: `noImportantStyles` at **506:24 identical**, `noDescendingSpecificity` **743 → 764**, moved only by the 21 inserted comment lines. Same two rules, same two selectors, `Found 2 warnings` both sides. **Zero introduced.** - **Made the instrument fail on purpose, both directions.** Injected a format defect → `Found 1 error`, exit 1; reverted → `Checked 1 file`, exit 0. And I reproduced the `npx` trap: `npx biome check` printed **nothing at all**, no `Checked N files` line, exit **0**. - The 21-line CSS comment contains nothing false: the (0,1,1)/(0,1,0) ranking, `<p class="price">`, both lost declarations, the measured gaps, `.product-type` winning by `!important`, and (0,2,0) sitting under (0,3,0) while never matching `.product-detail .price` all check out. - `out/products.html` contains **zero** occurrences of `product-card` while the hydrated page renders 4 — a static grep really would have called that page clean while it was broken. - `Closes #57` and `Closes #58` both present; the production-deploy warning is present; diff is the two owned files.
yuris force-pushed fix/57-58-card-price-cart-layout from 18cefd4d00 to cfa89e1c3f 2026-08-28 07:36:08 +00:00 Compare
yuris left a comment

VERDICT: approve-with-nits

Round 4. I re-ran the browser verification Main could not, with my own fixture, and I validated every measurement against a deliberately-broken control before trusting it. The production CSS is correct. 0 blockers. The two shoulds below are documentation-only, do not change a shipped byte, and do not need a fifth review round — fix them in the same push and merge.

The load-bearing claim holds

$ for h in 8e46ea9 74221c2 42cc95f adf11d8 d9fd41f 18cefd4 cfa89e1; do git rev-parse $h:apps/store/app/globals.css; done | sort -u
ee1d7d3a901300dc2e1588ea0054404ef6650ebf          # one line

One file in the diff (git diff --name-status origin/main...cfa89e1 → M apps/store/app/globals.css), 23 insertions / 2 deletions. layout-contract.test.ts is gone. Rebase is clean: git merge-base origin/main cfa89e1 = c4d96c8 exactly, and the 18cefd4..cfa89e1 delta's other files (README.md, catalog.ts, catalog.test.ts, next.config.ts) are all already in main, which is why the PR diff stays at one file. next.config.ts adds only env: { CATALOG_BUILD_ID } — nothing touching the CSS pipeline, so the byte-identity transfer is not contaminated by the rebase.

But I did not accept the transfer — I tested it. I built both cfa89e1 and origin/main against the same fixture and diffed the shipped bundles: they differ by exactly 21 bytes, the string .product-card .price,. Everything else in the compiled CSS is identical. That is the transfer, proven at the artefact the browser consumes rather than asserted from blob hashes.

Browser verification reproduces — with the harness proven able to fail

Fixture provenance established before reading anything: sitemap.xml <lastmod> = 2026-08-28T04:17:33.271Z = my fixture's generatedAt, confirmed both on disk and from inside the browser. Negative control first — build against a dead endpoint → Error: Catalog endpoint is unavailable, exit 1 — so a successful build necessarily consumed my fixture. Build exit 0 unpiped. Chromium 151 driven over raw CDP (the shared browser daemon fails here too; I bypassed it rather than skip the check).

#57 colour — equality, not "not muted". --ink and --ink-muted resolved in-page from probe elements. All card prices on /, /products, /services/ganti-oli, /services/kaki-kaki and the /products/<slug> related grid compute rgb(23, 32, 24) = resolved --ink. Pre-fix control (origin/main, same fixture): every card price rgb(90, 97, 89) with margin-top: 0px — both declarations lost, exactly as the comment claims.

#57 margin. Pre-fix gaps price-bottom→card-bottom on /products: 13 / 115.6 / 115.6 / 54 / 95.1 / 83.4 / 42.3 / 83.4 / 103.9 (per-row price-bottom spread 102.6px and 61.5px). Post-fix: 13 for every chipless card, 42.3 for every chip-bearing card. Your cited 42.3 / 62.9 / 83.4 / 103.9 all appear in my independent pre-fix sweep on a different fixture — the values are line-height quantised (~20.5px steps), so the numbers are real geometry, not invention.

Detail hierarchy intact: .product-detail .price 30.4px, card .price 18.4px, both rgb(23,32,24). .product-detail .product-card .price (0,3,0) still holds related-card sizing at 18.4px despite the new (0,2,0) tie with .product-detail .price.

#58 still fixed, and my check is sensitive. My first overlap detector returned "no overlaps" — and still returned it with the breakpoint deleted, i.e. it was worthless. I rebuilt it on Range.getClientRects() text ink. Validated:

width name col ink past column text-on-control overflowX
320 193 −14.8 (inside) 0 0
360 233 −13.2 0 0
375 248 −28.2 0 0
390 263 −8.5 0 0
600 473 −88.5 0 0

With @media (max-width:600px) deleted at runtime: name column 0px, ink 99.3px past its column, 8 text rects landing on the − button. So #58 is confirmed still fixed by c4172b3 on this branch, by a metric proven to fire.

Collateral, independently: 3695 element comparisons across 7 pages × 5 widths, PR vs main → 259 differences, 100% on elements with class price, only color / border-top-color / margin-top. Zero differences on any other element. Cart-summary price is --ink in both builds, so no third co-selector is needed.

Ruling: removing the guard was correct, and there is a second proof you did not cite

Ship the two-line fix with no automated guard. Three reasons, the last one decisive:

  1. Attack 28 stands as your predecessor found it.
  2. New evidence — lightningcss reorders declarations inside a rule. Authored order in globals.css is color, font-size, font-variant-numeric, font-weight, letter-spacing, margin, padding-top; shipped order is color, font-variant-numeric, letter-spacing, padding-top, margin, font-size, font-weight. The guard's final tiebreak was "!important → specificity → source order → position within the rule". Position within the rule is not preserved by the compiler. So independently of nesting, the guard's last tiebreaker modelled a property of the authoring file the browser never sees. Attack 28 showed one bypass; this shows the model was structurally wrong about the shipped cascade.
  3. No workflow runs any test suite. grep -rn test .forgejo/workflows/ finds only runs-on: ubuntu-latest. deploy-store.yml is checkout → pnpm → wait-for-catalog → build → wrangler deploy. The 91-test suite executes nowhere in automation, on no trigger. The 623-line guard would never have run — not on a PR, not on merge. A guard that never executes and grades the wrong artefact is strictly worse than none, because its presence tells the next author the class is covered.

So nothing should have survived. But the follow-up issue must include wiring test execution into CI — a rendered-output guard added to this repo today would also never run, and would be decoration for the same reason. That is the real gap: not the missing assertion, the missing trigger.

Findings

(PR body):L1: should: the body describes a PR that no longer exists. It still ships a "## Regression test" section for layout-contract.test.ts ("6 tests", "22 attacks, 22 caught"), an acceptance box [x] Regression test covers the cart layout, a Diff table claiming 2 files, 447 insertions, # tests 96, and "Commands, at merge SHA adf11d8". Actual: 1 file, 23 insertions / 2 deletions, 91 tests, head cfa89e1, and that test file deleted. Merging deploys to production and this body is the only thing a human reads at the merge button; right now it tells them a guard ships. Rewrite the regression-test section as the removal rationale, correct the diff table and counts, and drop the false checkbox.

apps/store/app/globals.css:525-527: should: the comment overstates what the fix achieves. It says the lost auto left prices "instead of sitting on one baseline across a row". Measured post-fix on my own build, prices in one row do not share a baseline whenever the row mixes chip and chipless cards — the normal case: chipless cards sit 13px off the card bottom, chip-bearing cards 42.3px, a 29.4px offset. What bottom-aligns is the price+chip block, not the price. Your PR body has this exactly right ("the two chipless cards sit at 13px, the two chip-bearing cards at 42.3px, the chip being what sits below the price") — and the body is precisely what the squash drops. The looser claim is the one surviving in git. Fix: "…so the price+chip block hangs under names of unequal length instead of bottom-aligning; a chip-bearing card's price sits one chip-height higher than a chipless one."

apps/store/app/globals.css:526-527: nit: the four before-numbers are unreproducible from anything in git. 42.3 / 62.9 / 103.9 / 103.9 across "the four cards on /products" is a property of an uncommitted 4-item fixture. The count is internally consistent with your body (4 items → 19 pages → 11 card prices over 5 surfaces), but the body does not reach git and no fixture is committed, so a stranger cannot regenerate these. Either say what the fixture was ("a 4-item catalog with names of 1-3 lines") or keep the mechanism and drop the absolute pixels.

Corrections to the dispatch brief

Three of the figures I was told to expect are wrong; the PR's own numbers were closer:

  • pnpm --filter garage-store test at cfa89e1 → 91 pass / 0 fail, not 90. origin/main is also 91; 18cefd4 was 97, so the guard held 6 tests, not 7.
  • "20 static pages" is not this fixture's number. Base routes are 15, so 4 items → 19 pages, matching the body's 19/19. My 9-item fixture built 24. The page count is fixture-dependent and proves nothing about the PR.
  • Consequently the comment's "four cards on /products" is correct and self-consistent, not the inconsistency it first looked like. I withdraw that suspicion.

Evidence at cfa89e1

$ pnpm --filter garage-store test                     # tests 91 / pass 91 / fail 0
$ pnpm exec biome check apps/store/app/globals.css
apps/store/app/globals.css:506:24  lint/complexity/noImportantStyles
apps/store/app/globals.css:764:1   lint/style/noDescendingSpecificity
Checked 1 file in 14ms.  Found 2 warnings.            # not clean - both pre-existing
$ # same binary against origin/main's copy: 506:24 + 743:1, Found 2 warnings
$ #   743 -> 764 is the 21 inserted comment lines; same rules, same selector pair
$ pnpm --filter garage-store build                    # exit 0 unpiped, 24/24 pages

Zero biome findings introduced. noDescendingSpecificity does not fire on the new co-selector — .product-card .price (0,2,0) ties rather than descends against .product-detail .price (0,2,0). Closes #57 and Closes #58 both present; body carries the production-deploy warning.

Comment claims I checked and confirmed: .product-card p is (0,1,1) at :500 and a bare .price (0,1,0) — correct; product-card.tsx:48 does render <p className="price">; .product-card > div:last-child is display:flex; flex-direction:column at :477-483; the co-selector is the same shape as .stock-badge, .product-card .stock-badge at :579-580 and that rule is genuinely below it; .product-type at :505-506 does win by !important; the (0,3,0) detail rule at :802 does keep related cards at card sizing; and the co-selector never matches the detail page's own price (measured inCard=false). 1f1ff1d is a real ancestor of main whose globals.css blob is c969afb — identical to main's — so "Measured on 1f1ff1d" is a reproducible baseline. .cart-line p (0,1,1) at :673 does declare color: var(--ink-muted), but the cart's <p className="price"> lives in .cart-summary, not .cart-line, so it is not a fourth instance — the audit's completeness survives that probe.

Worked in /tmp worktrees, now removed. Default checkout shows only M .omp/mcp.json; stash@{0} untouched.

VERDICT: approve-with-nits Round 4. I re-ran the browser verification `Main` could not, with my own fixture, and I validated every measurement against a deliberately-broken control before trusting it. **The production CSS is correct.** 0 blockers. The two `should`s below are documentation-only, do not change a shipped byte, and do not need a fifth review round — fix them in the same push and merge. ## The load-bearing claim holds ``` $ for h in 8e46ea9 74221c2 42cc95f adf11d8 d9fd41f 18cefd4 cfa89e1; do git rev-parse $h:apps/store/app/globals.css; done | sort -u ee1d7d3a901300dc2e1588ea0054404ef6650ebf # one line ``` One file in the diff (`git diff --name-status origin/main...cfa89e1` → `M apps/store/app/globals.css`), 23 insertions / 2 deletions. `layout-contract.test.ts` is gone. Rebase is clean: `git merge-base origin/main cfa89e1` = `c4d96c8` exactly, and the `18cefd4..cfa89e1` delta's other files (`README.md`, `catalog.ts`, `catalog.test.ts`, `next.config.ts`) are all already in `main`, which is why the PR diff stays at one file. `next.config.ts` adds only `env: { CATALOG_BUILD_ID }` — nothing touching the CSS pipeline, so the byte-identity transfer is not contaminated by the rebase. **But I did not accept the transfer — I tested it.** I built both `cfa89e1` and `origin/main` against the same fixture and diffed the shipped bundles: they differ by exactly **21 bytes**, the string `.product-card .price,`. Everything else in the compiled CSS is identical. That is the transfer, proven at the artefact the browser consumes rather than asserted from blob hashes. ## Browser verification reproduces — with the harness proven able to fail Fixture provenance established **before** reading anything: `sitemap.xml` `<lastmod>` = `2026-08-28T04:17:33.271Z` = my fixture's `generatedAt`, confirmed both on disk and from inside the browser. Negative control first — build against a dead endpoint → `Error: Catalog endpoint is unavailable`, exit 1 — so a successful build necessarily consumed my fixture. Build exit 0 unpiped. Chromium 151 driven over raw CDP (the shared browser daemon fails here too; I bypassed it rather than skip the check). **#57 colour — equality, not "not muted".** `--ink` and `--ink-muted` resolved in-page from probe elements. All card prices on `/`, `/products`, `/services/ganti-oli`, `/services/kaki-kaki` and the `/products/<slug>` related grid compute `rgb(23, 32, 24)` = resolved `--ink`. Pre-fix control (`origin/main`, same fixture): **every** card price `rgb(90, 97, 89)` with `margin-top: 0px` — both declarations lost, exactly as the comment claims. **#57 margin.** Pre-fix gaps price-bottom→card-bottom on `/products`: `13 / 115.6 / 115.6 / 54 / 95.1 / 83.4 / 42.3 / 83.4 / 103.9` (per-row price-bottom spread **102.6px** and **61.5px**). Post-fix: `13` for every chipless card, `42.3` for every chip-bearing card. Your cited `42.3 / 62.9 / 83.4 / 103.9` all appear in my independent pre-fix sweep on a different fixture — the values are line-height quantised (~20.5px steps), so the numbers are real geometry, not invention. **Detail hierarchy intact:** `.product-detail .price` **30.4px**, card `.price` **18.4px**, both `rgb(23,32,24)`. `.product-detail .product-card .price` (0,3,0) still holds related-card sizing at 18.4px despite the new (0,2,0) tie with `.product-detail .price`. **#58 still fixed, and my check is sensitive.** My first overlap detector returned "no overlaps" — and still returned it with the breakpoint deleted, i.e. it was worthless. I rebuilt it on `Range.getClientRects()` text ink. Validated: | width | name col | ink past column | text-on-control | overflowX | |---|---|---|---|---| | 320 | 193 | −14.8 (inside) | 0 | 0 | | 360 | 233 | −13.2 | 0 | 0 | | 375 | 248 | −28.2 | 0 | 0 | | 390 | 263 | −8.5 | 0 | 0 | | 600 | 473 | −88.5 | 0 | 0 | With `@media (max-width:600px)` deleted at runtime: name column **0px**, ink **99.3px** past its column, **8** text rects landing on the `−` button. So #58 is confirmed still fixed by `c4172b3` on this branch, by a metric proven to fire. **Collateral, independently:** 3695 element comparisons across 7 pages × 5 widths, PR vs `main` → **259 differences, 100% on elements with class `price`**, only `color` / `border-top-color` / `margin-top`. **Zero** differences on any other element. Cart-summary price is `--ink` in both builds, so no third co-selector is needed. ## Ruling: removing the guard was correct, and there is a second proof you did not cite Ship the two-line fix with no automated guard. Three reasons, the last one decisive: 1. Attack 28 stands as your predecessor found it. 2. **New evidence — lightningcss reorders declarations inside a rule.** Authored order in `globals.css` is `color, font-size, font-variant-numeric, font-weight, letter-spacing, margin, padding-top`; shipped order is `color, font-variant-numeric, letter-spacing, padding-top, margin, font-size, font-weight`. The guard's final tiebreak was "`!important` → specificity → source order → **position within the rule**". Position within the rule is *not preserved by the compiler*. So independently of nesting, the guard's last tiebreaker modelled a property of the authoring file the browser never sees. Attack 28 showed one bypass; this shows the model was structurally wrong about the shipped cascade. 3. **No workflow runs any test suite.** `grep -rn test .forgejo/workflows/` finds only `runs-on: ubuntu-latest`. `deploy-store.yml` is checkout → pnpm → wait-for-catalog → `build` → `wrangler deploy`. The 91-test suite executes nowhere in automation, on no trigger. The 623-line guard would never have run — not on a PR, not on merge. A guard that never executes and grades the wrong artefact is strictly worse than none, because its presence tells the next author the class is covered. So nothing should have survived. **But the follow-up issue must include wiring test execution into CI** — a rendered-output guard added to this repo today would also never run, and would be decoration for the same reason. That is the real gap: not the missing assertion, the missing trigger. ## Findings `(PR body):L1: should: the body describes a PR that no longer exists.` It still ships a "## Regression test" section for `layout-contract.test.ts` ("6 tests", "22 attacks, 22 caught"), an acceptance box `[x] Regression test covers the cart layout`, a Diff table claiming **2 files, 447 insertions**, `# tests 96`, and "Commands, at merge SHA `adf11d8`". Actual: 1 file, 23 insertions / 2 deletions, 91 tests, head `cfa89e1`, and that test file deleted. Merging deploys to production and this body is the only thing a human reads at the merge button; right now it tells them a guard ships. Rewrite the regression-test section as the removal rationale, correct the diff table and counts, and drop the false checkbox. `apps/store/app/globals.css:525-527: should: the comment overstates what the fix achieves.` It says the lost `auto` left prices "instead of sitting on one baseline across a row". Measured post-fix on my own build, prices in one row do **not** share a baseline whenever the row mixes chip and chipless cards — the normal case: chipless cards sit 13px off the card bottom, chip-bearing cards 42.3px, a **29.4px** offset. What bottom-aligns is the price+chip block, not the price. Your PR body has this exactly right ("the two chipless cards sit at 13px, the two chip-bearing cards at 42.3px, the chip being what sits below the price") — and the body is precisely what the squash drops. The looser claim is the one surviving in git. Fix: "…so the price+chip block hangs under names of unequal length instead of bottom-aligning; a chip-bearing card's price sits one chip-height higher than a chipless one." `apps/store/app/globals.css:526-527: nit: the four before-numbers are unreproducible from anything in git.` `42.3 / 62.9 / 103.9 / 103.9` across "the four cards on /products" is a property of an uncommitted 4-item fixture. The count is internally consistent with your body (4 items → 19 pages → 11 card prices over 5 surfaces), but the body does not reach git and no fixture is committed, so a stranger cannot regenerate these. Either say what the fixture was ("a 4-item catalog with names of 1-3 lines") or keep the mechanism and drop the absolute pixels. ## Corrections to the dispatch brief Three of the figures I was told to expect are wrong; the PR's own numbers were closer: - `pnpm --filter garage-store test` at `cfa89e1` → **91 pass / 0 fail**, not 90. `origin/main` is also 91; `18cefd4` was 97, so the guard held **6** tests, not 7. - "20 static pages" is not this fixture's number. Base routes are 15, so 4 items → **19** pages, matching the body's `19/19`. My 9-item fixture built 24. The page count is fixture-dependent and proves nothing about the PR. - Consequently the comment's "four cards on /products" is **correct and self-consistent**, not the inconsistency it first looked like. I withdraw that suspicion. ## Evidence at `cfa89e1` ``` $ pnpm --filter garage-store test # tests 91 / pass 91 / fail 0 $ pnpm exec biome check apps/store/app/globals.css apps/store/app/globals.css:506:24 lint/complexity/noImportantStyles apps/store/app/globals.css:764:1 lint/style/noDescendingSpecificity Checked 1 file in 14ms. Found 2 warnings. # not clean - both pre-existing $ # same binary against origin/main's copy: 506:24 + 743:1, Found 2 warnings $ # 743 -> 764 is the 21 inserted comment lines; same rules, same selector pair $ pnpm --filter garage-store build # exit 0 unpiped, 24/24 pages ``` Zero biome findings introduced. `noDescendingSpecificity` does not fire on the new co-selector — `.product-card .price` (0,2,0) ties rather than descends against `.product-detail .price` (0,2,0). `Closes #57` and `Closes #58` both present; body carries the production-deploy warning. Comment claims I checked and confirmed: `.product-card p` is (0,1,1) at :500 and a bare `.price` (0,1,0) — correct; `product-card.tsx:48` does render `<p className="price">`; `.product-card > div:last-child` is `display:flex; flex-direction:column` at :477-483; the co-selector is the same shape as `.stock-badge, .product-card .stock-badge` at :579-580 and that rule is genuinely *below* it; `.product-type` at :505-506 does win by `!important`; the (0,3,0) detail rule at :802 does keep related cards at card sizing; and the co-selector never matches the detail page's own price (measured `inCard=false`). `1f1ff1d` is a real ancestor of `main` whose `globals.css` blob is `c969afb` — identical to `main`'s — so "Measured on 1f1ff1d" is a reproducible baseline. `.cart-line p` (0,1,1) at :673 does declare `color: var(--ink-muted)`, but the cart's `<p className="price">` lives in `.cart-summary`, not `.cart-line`, so it is not a fourth instance — the audit's completeness survives that probe. Worked in `/tmp` worktrees, now removed. Default checkout shows only ` M .omp/mcp.json`; `stash@{0}` untouched.
Review 36, pre-authorised as documentation-only.

The comment said losing `margin-top: auto` left prices "instead of sitting on
one baseline across a row". Measured on the reviewer's own build, that is not
what the fix achieves: in a row mixing chip-bearing and chipless cards the
prices still differ by the chip's height — 13px versus 42.3px from card bottom,
a 29.4px offset — because the chip sits below the price. What bottom-aligns is
the price+chip block.

The PR body had this right and the comment did not, and the body is exactly
what the squash drops, so the looser claim was the one about to survive in git.

Also dropped the four absolute before-numbers. They were a property of an
uncommitted 4-item fixture, so a stranger could not regenerate them from
anything in the repo. Replaced with the mechanism and the fixture's shape,
which is reproducible.

With the regression guard removed these lines are the only institutional memory
of this defect class, so an overstatement here is unchecked by any test.
Author
Owner

Merge gate at 4f21f7a

pnpm --filter garage-store test    ->  # tests 91  # pass 91  # fail 0   (exit 0)
pnpm exec biome check globals.css  ->  Checked 1 file. Found 2 warnings. (both pre-existing on main)
git diff --name-status origin/main...HEAD  ->  M apps/store/app/globals.css   (one file)

mergeable: true re-fetched immediately before the merge call.

Review 36's two shoulds — both actioned, as it pre-authorised

Review 36 stated these are documentation-only, do not change a shipped byte, and do not need a fifth round: "fix them in the same push and merge." I am taking that pre-authorisation rather than self-approving, and recording the deviation explicitly: the merge commit post-dates review 36, and the delta is confined to exactly what review 36 named.

Should 1 — the body described a PR that no longer exists. It still carried a "Regression test" section for a deleted file, an acceptance checkbox claiming a guard shipped, a diff table reading 2 files / 447 insertions, # tests 96, and commands at adf11d8. Rewritten. That mattered more than a stale count: the body is what a human reads at the merge button, and it was telling them a guard ships.

Should 2 — the code comment overstated the fix. It claimed the lost auto left prices "instead of sitting on one baseline across a row". Measured on the reviewer's own build, prices in a row do not share a baseline whenever the row mixes chip-bearing and chipless cards — 13px versus 42.3px from card bottom, a 29.4px offset — because the chip sits below the price. What bottom-aligns is the price+chip block. The PR body had this right and the comment did not, and the body is exactly what a squash drops, so the looser claim was the one about to survive in git.

Nit — actioned too. The four absolute before-numbers (42.3 / 62.9 / 103.9 / 103.9) were a property of an uncommitted 4-item fixture, so a stranger could not regenerate them from anything in the repo. Replaced with the mechanism plus the fixture's shape.

One correction to my own claim, found while running the gate

The body asserted globals.css was byte-identical across all heads. That stopped being true when I fixed the comment. Corrected: comment-stripped globals.css hashes to 8ba17b23 at the first head and at the merge head alike, so the shipped declarations never moved — but the raw blob is ee1d7d3a for the first seven heads and differs at 4f21f7a. Stating it the sloppy way would have been the eighth false-but-plausible claim caught in this repo, and mine.

Follow-ups filed

  • #72 — guard this cascade over the built export in a real browser, not over globals.css source text. Carries the full record of why three source-text shapes failed, so nobody rebuilds them.
  • #73 — no workflow runs any test suite on any trigger. grep -rn test .forgejo/workflows/ matches only runs-on: ubuntu-la**test**. #72 depends on it: a browser guard added today would not run either.
  • .product-type remains the first instance of this class, held by !important since 6e36a27 (#4), not currently losing, recorded rather than fixed.
## Merge gate at `4f21f7a` ``` pnpm --filter garage-store test -> # tests 91 # pass 91 # fail 0 (exit 0) pnpm exec biome check globals.css -> Checked 1 file. Found 2 warnings. (both pre-existing on main) git diff --name-status origin/main...HEAD -> M apps/store/app/globals.css (one file) ``` `mergeable: true` re-fetched immediately before the merge call. ## Review 36's two `should`s — both actioned, as it pre-authorised Review 36 stated these are documentation-only, do not change a shipped byte, and do not need a fifth round: *"fix them in the same push and merge."* I am taking that pre-authorisation rather than self-approving, and recording the deviation explicitly: **the merge commit post-dates review 36, and the delta is confined to exactly what review 36 named.** **Should 1 — the body described a PR that no longer exists.** It still carried a "Regression test" section for a deleted file, an acceptance checkbox claiming a guard shipped, a diff table reading 2 files / 447 insertions, `# tests 96`, and commands at `adf11d8`. Rewritten. That mattered more than a stale count: the body is what a human reads at the merge button, and it was telling them a guard ships. **Should 2 — the code comment overstated the fix.** It claimed the lost `auto` left prices "instead of sitting on one baseline across a row". Measured on the reviewer's own build, prices in a row do **not** share a baseline whenever the row mixes chip-bearing and chipless cards — 13px versus 42.3px from card bottom, a 29.4px offset — because the chip sits below the price. What bottom-aligns is the **price+chip block**. The PR body had this right and the comment did not, and the body is exactly what a squash drops, so the looser claim was the one about to survive in git. **Nit — actioned too.** The four absolute before-numbers (`42.3 / 62.9 / 103.9 / 103.9`) were a property of an uncommitted 4-item fixture, so a stranger could not regenerate them from anything in the repo. Replaced with the mechanism plus the fixture's shape. ## One correction to my own claim, found while running the gate The body asserted `globals.css` was byte-identical across all heads. **That stopped being true when I fixed the comment.** Corrected: comment-stripped `globals.css` hashes to `8ba17b23` at the first head and at the merge head alike, so the shipped declarations never moved — but the raw blob is `ee1d7d3a` for the first seven heads and differs at `4f21f7a`. Stating it the sloppy way would have been the eighth false-but-plausible claim caught in this repo, and mine. ## Follow-ups filed - **#72** — guard this cascade over the built export in a real browser, not over `globals.css` source text. Carries the full record of why three source-text shapes failed, so nobody rebuilds them. - **#73** — no workflow runs any test suite on any trigger. `grep -rn test .forgejo/workflows/` matches only `runs-on: ubuntu-la**test**`. #72 depends on it: a browser guard added today would not run either. - `.product-type` remains the first instance of this class, held by `!important` since `6e36a27` (#4), not currently losing, recorded rather than fixed.
yuris merged commit d0b582599f into main 2026-08-28 11:57:36 +00:00
yuris deleted branch fix/57-58-card-price-cart-layout 2026-08-28 11:57:37 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/garage!67
No description provided.