From a43a3df669eb857eddc93533c6bece2b28795291 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:26:27 -0700 Subject: [PATCH 1/2] test(v2): one bounded-scope reader for the invariant suite, and the numbers its comment claims (TASK-203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The suite carried three hand-rolled copies of the same brace walk (`teamPhoneBlock`, `mediaBlockContaining`, `mediaAt`) — three copies is how the fourth drifts. They now route through one `blockAt` plus one `blockContaining`, and `ruleBody` takes `{ within }`, so a scoped read names the sheet it read and the scope it read in instead of passing a truncated string as `css`. The comment above `reducedMotionBlock` also claimed the read it replaced "runs to EOF — 800 lines past the block it meant". Measured at main `16917532`: `split('@media (prefers-reduced-motion')[1]` is 865 lines / 33,883 characters from just after the first at-rule (531) to just BEFORE the second (1395) — it does not reach EOF, and any rule in that span could answer a read aimed at the block. v2-landing.css carries five such at-rules; `grep -c` reports six because the prose comment at 1356 mentions it. Arms (base: 205 tests, 205 passed; unchanged after the refactor): - delete the phone rule for `.v2-team__grid`: 3 failed on the new helpers and 3 failed on main's, same titles — the consolidation preserves verdicts. - move `.v2-landing__trusted-set[aria-hidden='true']` from inside the first reduce block to just past its close: the bounded read reds (1 of 205) while the old slice still contains it and stays green. `tsc --noEmit` exit 0; both touched suites 208/208. --- .../v2/__tests__/v2-layout-invariants.test.ts | 140 +++++++++--------- 1 file changed, 67 insertions(+), 73 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 7ed4da432..a617c7e9e 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -23,64 +23,63 @@ import path from 'path'; const read = (rel: string): string => fs.readFileSync(path.join(__dirname, rel), 'utf8'); -// Grab the body of the first ` { ... }` block. Selectors here have no -// nested braces, so a naive slice to the next `}` is sufficient. -// The team's phone block: the `@media (max-width: 760px)` block that carries -// `.v2-team__grid`, wherever it sits in the sheet — not the last one. -const teamPhoneBlock = (css: string): string => { - const marker = '@media (max-width: 760px)'; - let from = 0; - for (;;) { - const at = css.indexOf(marker, from); - if (at < 0) return ''; - // Walk to the block's own closing brace so a base rule after the block - // can never be read as part of it. - const open = css.indexOf('{', at); - let depth = 0; - let end = open; - for (let i = open; i < css.length; i += 1) { - if (css[i] === '{') depth += 1; - if (css[i] === '}') { depth -= 1; if (depth === 0) { end = i; break; } } - } - const block = css.slice(at, end + 1); - if (block.includes('.v2-team__grid')) return block; - from = end + 1; +// The block that opens at the first `{` at or after `from`, brace-matched to +// its own closing brace — or '' when nothing opens there. This is THE walk: +// three hand-rolled copies of it is how the fourth one drifts, and a copy that +// stops at the next `}` reads a base rule after the block as part of it. +const blockAt = (css: string, from: number): string => { + const open = css.indexOf('{', from); + if (open === -1) return ''; + let depth = 0; + for (let i = open; i < css.length; i += 1) { + if (css[i] === '{') depth += 1; + if (css[i] === '}') { depth -= 1; if (depth === 0) return css.slice(from, i + 1); } } + return ''; }; +// The first block introduced by `marker` whose OWN text carries `needle`. A +// scope with a start and no end is not a scope, so both ends come from +// `blockAt` and a later block can never answer for this one. +const blockContaining = (css: string, marker: string, needle: string): string => { + for (let at = css.indexOf(marker); at !== -1; at = css.indexOf(marker, at + 1)) { + const block = blockAt(css, at); + if (block.includes(needle)) return block; + } + return ''; +}; + +// The team's phone block: the `@media (max-width: 760px)` block that carries +// `.v2-team__grid`, wherever it sits in the sheet — not the last one. +const teamPhoneBlock = (css: string): string => + blockContaining(css, '@media (max-width: 760px)', '.v2-team__grid'); + // The `@media (max-width: 760px)` block that carries `selector`, wherever it // sits in the sheet. A phone override is indented inside a media query, so // `ruleBody` (which prefers a line-start selector) silently returns the desktop // rule instead — and a guard reading the wrong rule is green while the phone // layout is broken (TASK-140). -const mediaBlockContaining = (css: string, selector: string): string => { - const marker = '@media (max-width: 760px)'; - let from = 0; - for (;;) { - const at = css.indexOf(marker, from); - if (at < 0) return ''; - const open = css.indexOf('{', at); - let depth = 0; - let end = open; - for (let i = open; i < css.length; i += 1) { - if (css[i] === '{') depth += 1; - if (css[i] === '}') { depth -= 1; if (depth === 0) { end = i; break; } } - } - const block = css.slice(at, end + 1); - if (block.includes(selector)) return block; - from = end + 1; - } -}; - -const ruleBody = (css: string, selector: string): string => { +const mediaBlockContaining = (css: string, selector: string): string => + blockContaining(css, '@media (max-width: 760px)', selector); + +// `within` bounds the read to a slice already taken out of the sheet (an +// at-rule's block, a media query's body). Pass it rather than passing the slice +// as `css`: the call then names the sheet it read AND the scope it read in, so a +// scoped read cannot be mistaken for a whole-sheet one. +const ruleBody = ( + css: string, + selector: string, + { within }: { within?: string } = {}, +): string => { + const scope = within ?? css; // Prefer a selector at the start of a CSS line. A descendant selector can // contain the same text (`.parent .target {`) and is not the rule being // pinned. - const lineStart = css.indexOf(`\n${selector} {`); - const start = lineStart === -1 ? css.indexOf(`${selector} {`) : lineStart + 1; + const lineStart = scope.indexOf(`\n${selector} {`); + const start = lineStart === -1 ? scope.indexOf(`${selector} {`) : lineStart + 1; if (start === -1) return ''; - const end = css.indexOf('}', start); - return end === -1 ? '' : css.slice(start, end); + const end = scope.indexOf('}', start); + return end === -1 ? '' : scope.slice(start, end); }; // TASK-129 supersedes a large sidebar block in place. The current artboard @@ -113,32 +112,27 @@ const selectorRuleBody = (css: string, selector: string): string => { const mediaAt = (css: string, atRule: string): string => { const at = css.indexOf(atRule); if (at < 0) return ''; - const open = css.indexOf('{', at); - let depth = 0; - let end = open; - for (let i = open; i < css.length; i += 1) { - if (css[i] === '{') depth += 1; - if (css[i] === '}') { depth -= 1; if (depth === 0) { end = i; break; } } - } - return css.slice(at, end + 1); + return blockAt(css, at); }; // The `@media (prefers-reduced-motion: reduce)` block that carries `needle`, not -// the first such block in the sheet. There are six of them in v2-landing.css, -// and a slice from the first `prefers-reduced-motion` runs to EOF — 800 lines -// past the block it meant, widening as the file grows — so any later block could -// satisfy a read aimed at one of them. sprint-review measured the consequence on -// #2019: the fallback's own `flex-wrap: wrap` could be deleted outright and the -// guard stayed green, because `.v2-landing__footer-cols` declares the same -// property 300 lines below. A scope with a start and no end is not a scope. -const reducedMotionBlock = (css: string, needle: string): string => { - const marker = '@media (prefers-reduced-motion'; - for (let at = css.indexOf(marker); at !== -1; at = css.indexOf(marker, at + 1)) { - const block = mediaAt(css.slice(at), marker); - if (block.includes(needle)) return block; - } - return ''; -}; +// the first such block in the sheet. v2-landing.css carries FIVE of these +// at-rules (531 / 1395 / 1421 / 1447 / 1517); a plain `grep -c +// prefers-reduced-motion` reports six because the prose comment at 1356 mentions +// it. The read this replaced took `split('@media (prefers-reduced-motion')[1]` — +// from just after the first at-rule to just BEFORE the second one: 865 lines and +// 33,883 characters, opening inside the block it meant and closing at an +// unrelated at-rule 831 lines past that block's own `}` at 564. Any rule in that +// span could satisfy a read aimed at this block. Measured at main `16917532`: +// move `.v2-landing__trusted-set[aria-hidden='true']` from inside the first +// block to just past its close and the bounded read reds (1 failure of 205) +// while the old slice still contains it and stays green. sprint-review measured +// the consequence on #2019 — the fallback's own `flex-wrap: wrap` could be +// deleted outright and the guard stayed green, because +// `.v2-landing__footer-cols` declares the same property further down. A scope +// with a start and no end is not a scope. +const reducedMotionBlock = (css: string, needle: string): string => + blockContaining(css, '@media (prefers-reduced-motion', needle); // Body of a rule that IS the whole selector — anchored at the start of a line, so // it cannot match a rule where this selector is the TAIL of a longer one. @@ -829,7 +823,7 @@ describe('v2 layout invariants (CSS rule presence)', () => { // at ≤760 — the same guarantee (a column never wider than its container) // expressed on the new grid. expect(ruleBody(v2, '.v2-team__grid')).toContain('repeat(3, minmax(0, 1fr))'); - expect(ruleBody(teamPhoneBlock(v2), '.v2-team__grid')).toContain('repeat(1, minmax(0, 1fr))'); + expect(ruleBody(v2, '.v2-team__grid', { within: teamPhoneBlock(v2) })).toContain('repeat(1, minmax(0, 1fr))'); }); test('the agent profile page overrides the app-shell overflow too (sibling invariant)', () => { @@ -1149,8 +1143,8 @@ describe('v2 layout invariants (CSS rule presence)', () => { // Read the rule inside the team's phone block — a `[\s\S]*?` regex across // the sheet passed with the rule deleted (sprint-review at 58fb4147). const teamPhone = teamPhoneBlock(v2); - expect(ruleBody(teamPhone, '.v2-team__heading')).toContain('flex-direction: column'); - expect(ruleBody(teamPhone, '.v2-team__grid')).toContain('repeat(1, minmax(0, 1fr))'); + expect(ruleBody(v2, '.v2-team__heading', { within: teamPhone })).toContain('flex-direction: column'); + expect(ruleBody(v2, '.v2-team__grid', { within: teamPhone })).toContain('repeat(1, minmax(0, 1fr))'); // The retired surfaces: feature rows, standard cards with icon buttons, quiet rows, green dot. expect(v2).not.toContain('.v2-team-feature '); expect(v2).not.toContain('.v2-team-quiet'); @@ -1919,7 +1913,7 @@ describe('v2 layout invariants (CSS rule presence)', () => { it('team card on a phone: one column, so the name column never one-chars (spec §5, #568 class, re-pinned on direction C)', () => { // The featured row is gone; the same guarantee on the card grid is one // column at ≤760 and a card head whose name column can shrink. - expect(ruleBody(teamPhoneBlock(v2), '.v2-team__grid')).toContain('repeat(1, minmax(0, 1fr))'); + expect(ruleBody(v2, '.v2-team__grid', { within: teamPhoneBlock(v2) })).toContain('repeat(1, minmax(0, 1fr))'); expect(ruleBody(v2, '.v2-team-card__title')).toContain('min-width: 0'); }); From ded68a31e2aa77c51b9fdf20d2ec9f61babd3ba2 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:32:43 -0700 Subject: [PATCH 2/2] test(v2): three brace-walk copies collapsed into blockAt, plus { within } on ruleBody (TASK-203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test-only; one file, +60/-76, based on main 16917532. Three helpers — teamPhoneBlock, mediaBlockContaining and mediaAt — each carried their own copy of the brace walk (three `depth -= 1` markers, measured at base). They now call one `blockAt(css, from)` plus one `blockContaining(css, marker, needle)`. The file keeps a second brace walk, `parseCascade`'s local `walk` (:3229) — it parses the cascade into rules rather than reading a single block, and collapsing it is a different job. So the claim here is three copies into one primitive, not one walk in the file. `ruleBody(css, selector, { within })`: a scoped read names the sheet it read AND the scope it read in, instead of passing a truncated string as `css` where a whole-sheet read looks identical at the call site. Four call sites converted. Deleted in this file: the paragraph above `reducedMotionBlock` claiming the read "this replaced" took `split('@media (prefers-reduced-motion')[1]` and ran "to EOF — 800 lines", plus its second copy at what is now line 1417, directly above the call site this change touches. That history, stated correctly — an earlier version of this message got it wrong in the other direction, and the correction belongs here rather than only in a comment on the PR: at #2019's pre-fix heads (c5c2c38a / 8a692533) the read WAS `landing.slice(landing.indexOf('prefers-reduced-motion'))`, a one-arg slice to EOF over a 1308-line v2-landing.css whose first reduce block closes at 509 — 799 lines past the close. Introduced by c5edaf1c (#714), replaced by 404a8f30 (#2019's own merge), which is where the brace-matched `reducedMotionBlock` arrives. So "800 lines" was true when written; what went stale was its SUBJECT, replaced under it inside the very function that replaced it. Deleting the sentence was right; calling it "fiction about the code" was the wrong reason, and is retracted. Decisive control, the same mutation both ways — satisfier moved from inside the first reduce block to just past its close: base helpers + mutation: 1 failed / 205 (landing trusted-by marquee falls back to a static wrap under reduced motion) new helpers + mutation: 1 failed / 205 (same title) The bound was already there; this change adds none. `tsc --noEmit` exit 0; 205/205. --- .../v2/__tests__/v2-layout-invariants.test.ts | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index a617c7e9e..e7cd1a1ef 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -116,21 +116,12 @@ const mediaAt = (css: string, atRule: string): string => { }; // The `@media (prefers-reduced-motion: reduce)` block that carries `needle`, not -// the first such block in the sheet. v2-landing.css carries FIVE of these -// at-rules (531 / 1395 / 1421 / 1447 / 1517); a plain `grep -c -// prefers-reduced-motion` reports six because the prose comment at 1356 mentions -// it. The read this replaced took `split('@media (prefers-reduced-motion')[1]` — -// from just after the first at-rule to just BEFORE the second one: 865 lines and -// 33,883 characters, opening inside the block it meant and closing at an -// unrelated at-rule 831 lines past that block's own `}` at 564. Any rule in that -// span could satisfy a read aimed at this block. Measured at main `16917532`: -// move `.v2-landing__trusted-set[aria-hidden='true']` from inside the first -// block to just past its close and the bounded read reds (1 failure of 205) -// while the old slice still contains it and stays green. sprint-review measured -// the consequence on #2019 — the fallback's own `flex-wrap: wrap` could be -// deleted outright and the guard stayed green, because -// `.v2-landing__footer-cols` declares the same property further down. A scope -// with a start and no end is not a scope. +// the first such block in the sheet: v2-landing.css carries FIVE of them +// (531 / 1395 / 1421 / 1447 / 1517) and a plain `grep -c prefers-reduced-motion` +// reports six because the prose comment at 1356 mentions it. Each occurrence is +// brace-matched, not sliced to the next `@media` — which block a slice lands in +// depends on the order the file happens to put them in, so an assertion can end +// up made about the wrong block and still read as a pass. const reducedMotionBlock = (css: string, needle: string): string => blockContaining(css, '@media (prefers-reduced-motion', needle); @@ -1413,9 +1404,8 @@ describe('v2 layout invariants (CSS rule presence)', () => { // prefers-reduced-motion users get the old wrapping strip: animation off, // wrap on, duplicate set hidden (it exists only for the seamless loop). // Read from the fallback's OWN block, brace-matched: the base marquee rule is - // above it, so a whole-sheet first-match reader returns that body instead, - // and a slice from the first `prefers-reduced-motion` runs to EOF and lets a - // later block answer for this one. Assert the scope is non-empty first — a + // above it, so a whole-sheet first-match reader returns that body instead. + // Assert the scope is non-empty first — a // scope that can silently be empty reads as a pass everywhere it is used. const reduced = reducedMotionBlock(landing, '.v2-landing__trusted-track'); expect(reduced).not.toBe('');