The issue. The device renders in Chrome 113, which predates relaxed
CSS nesting (Chrome 120), so a nested rule whose selector starts with an
element name is not a parse error anyone would notice — it is silently
dropped, on the phone and only on the phone. Three were live in index.css, and the one that mattered was the text-overflow: ellipsis
on the bottom bar's title and artist, which had therefore never
truncated on the device. Those three went with #23/#42; #154 is about
the class.
What changed.make css-check runs a second script, frontend/scripts/check-css-nesting.mjs, over index.css and the css
literals in src/**/*.ts alike — a shadow-root stylesheet is parsed by
the same engine. It names the file, the line and the fix, which is a
leading &: valid in both syntaxes, so no nested rule here has a reason
to omit it. It is a lefthook pre-commit entry as well, beside the
existing css-literals one, and CI already runs make css-check.
The detection (css-nesting.mjs) walks blocks rather than matching
lines, and both things the issue asks it to get right fall out of one
rule — a rule is nested when a style rule is somewhere above it, not
when its immediate parent is a block:
@media (…) { bottom-nav { … } } at the top level is an ordinary rule
and is not flagged. That is the majority of what a regex over the file
would report.
The same rule inside .bar { @media (…) { … } }is nested and is
flagged, at-rule in between or not.
Strings and comments are read through, so a brace in a url() is not a
block. The literal scanner the two checks now share is lifted into css-literals.mjs unchanged, except that a ${} substitution is
blanked keeping its newlines so a line number survives it.
The tree has no violation left, so the check would pass just as happily
over an empty glob — it refuses one, in the spirit of icon-language.test.ts's first assertion.
Verification tiers run.
make css-check — clean, and watched failing. Removing the three &s from index.css's bottom-bar block (the historical cases, lines
282/289/293) reports exactly those three lines with the right
selectors; a span { … } injected into page-header's css literal
is reported at its own line, which exercises the literal path and the
line mapping through it. Both reverts left the tree clean.
make ui-test — 991 passed, 7 of them the new frontend/test/utils/css-nesting.test.ts, which pins the at-rule
distinction in both directions plus the string/comment handling.
npx tsc --noEmit — clean. make skill-check — clean.
The lefthook pre-commit and pre-push hooks ran on the commit and the
push, including the new css-nesting entry.
Deliberately not done.
The alternative the issue asks to weigh first — a build-time downlevel
(Lightning CSS or PostCSS targeting Chrome 113), which would fix the
class permanently rather than reject it — is not taken. It is a
dependency and a build step against twenty lines, and the issue's own
Direction says the check is the version that matches how this repo has
handled the other silent-on-one-platform traps. It is written down in CLAUDE.md as the expensive answer, so the trade survives this PR.
No device tier: none is attached, and none is needed to check a rule
the check enforces about source text.
The sweep covers frontend/index.css and css literals. A .css
file added elsewhere under frontend/ would not be swept.
CLAUDE.md gains a paragraph under the Chrome 113 section and the yellowjacket-dev skill's make css-check bullet gains the second
check, since that file is the imperative half.
**The issue.** The device renders in Chrome 113, which predates relaxed
CSS nesting (Chrome 120), so a nested rule whose selector starts with an
element name is not a parse error anyone would notice — it is silently
dropped, on the phone and only on the phone. Three were live in
`index.css`, and the one that mattered was the `text-overflow: ellipsis`
on the bottom bar's title and artist, which had therefore never
truncated on the device. Those three went with #23/#42; #154 is about
the **class**.
**What changed.** `make css-check` runs a second script,
`frontend/scripts/check-css-nesting.mjs`, over `index.css` and the `css`
literals in `src/**/*.ts` alike — a shadow-root stylesheet is parsed by
the same engine. It names the file, the line and the fix, which is a
leading `&`: valid in both syntaxes, so no nested rule here has a reason
to omit it. It is a lefthook pre-commit entry as well, beside the
existing `css-literals` one, and CI already runs `make css-check`.
The detection (`css-nesting.mjs`) walks blocks rather than matching
lines, and both things the issue asks it to get right fall out of one
rule — a rule is nested when a **style** rule is somewhere above it, not
when its immediate parent is a block:
- `@media (…) { bottom-nav { … } }` at the top level is an ordinary rule
and is not flagged. That is the majority of what a regex over the file
would report.
- The same rule inside `.bar { @media (…) { … } }` **is** nested and is
flagged, at-rule in between or not.
Strings and comments are read through, so a brace in a `url()` is not a
block. The literal scanner the two checks now share is lifted into
`css-literals.mjs` unchanged, except that a `${}` substitution is
blanked keeping its newlines so a line number survives it.
The tree has no violation left, so the check would pass just as happily
over an empty glob — it refuses one, in the spirit of
`icon-language.test.ts`'s first assertion.
**Verification tiers run.**
- `make css-check` — clean, and **watched failing**. Removing the three
`&`s from `index.css`'s bottom-bar block (the historical cases, lines
282/289/293) reports exactly those three lines with the right
selectors; a `span { … }` injected into `page-header`'s `css` literal
is reported at its own line, which exercises the literal path and the
line mapping through it. Both reverts left the tree clean.
- `make ui-test` — **991 passed**, 7 of them the new
`frontend/test/utils/css-nesting.test.ts`, which pins the at-rule
distinction in both directions plus the string/comment handling.
- `npx tsc --noEmit` — clean. `make skill-check` — clean.
- The lefthook pre-commit and pre-push hooks ran on the commit and the
push, including the new `css-nesting` entry.
**Deliberately not done.**
- The alternative the issue asks to weigh first — a build-time downlevel
(Lightning CSS or PostCSS targeting Chrome 113), which would fix the
class permanently rather than reject it — is not taken. It is a
dependency and a build step against twenty lines, and the issue's own
Direction says the check is the version that matches how this repo has
handled the other silent-on-one-platform traps. It is written down in
`CLAUDE.md` as the expensive answer, so the trade survives this PR.
- No device tier: none is attached, and none is needed to check a rule
the check enforces about source text.
- The sweep covers `frontend/index.css` and `css` literals. A `.css`
file added elsewhere under `frontend/` would not be swept.
`CLAUDE.md` gains a paragraph under the Chrome 113 section and the
`yellowjacket-dev` skill's `make css-check` bullet gains the second
check, since that file is the imperative half.
Closes #154
CI is green on 4bf2414: check success (run 478) and e2e success, the latter over both Chromium and WebKit. make css-check runs the new script in that first job, so the check is gating from this commit on.
CI is green on `4bf2414`: **`check` success** (run 478) and **`e2e` success**, the latter over both Chromium and WebKit. `make css-check` runs the new script in that first job, so the check is gating from this commit on.
The device renders in Chrome 113, which predates relaxed CSS nesting, so
a nested rule whose selector starts with an element name is not a parse
error anyone would notice -- the rule simply does not exist, there and
nowhere else. Three were live in `index.css`, and the one that mattered
was the `text-overflow: ellipsis` on the bottom bar's title and artist,
which had therefore never truncated on the device. No tier here can see
the class at all: the component tier, the e2e tier and `make ui-visual`
all run a current engine, where the rule applies normally.
So `make css-check` carries a second script. It reads `index.css` and
the `css` literals in `src/**/*.ts` alike, since a shadow-root
stylesheet is parsed by the same engine, and it names the file, the line
and the fix -- a leading `&`, which is valid in both syntaxes.
The detection walks blocks rather than matching lines, and both things
it has to get right fall out of one rule: a rule is nested when a
*style* rule is somewhere above it, not when its immediate parent is a
block. That leaves `@media (...) { bottom-nav { ... } }` at the top
level alone, which is the majority of what a regex over the file would
report, and still flags the same rule inside an at-rule that is itself
inside a style rule. Strings and comments are read through, so a brace
in a `url()` is not a block.
The tree has no violation left, so the check would pass just as happily
over an empty glob: it refuses one, and `test/utils/css-nesting.test.ts`
pins the semantics that make the sweep mean something. The literal
scanner the two checks share is lifted into `css-literals.mjs`
unchanged, except that a `${}` substitution is now blanked keeping its
newlines so a line number survives it.
Closes#154
The hook fires on frontend/**/*.{ts,css} while the script read one hardcoded path, so a second stylesheet would have been silently unswept while the hook still went green over it. There is only index.css today, which is exactly when this is cheap to fix. Watched catching a planted nested rule in a second file.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The issue. The device renders in Chrome 113, which predates relaxed
CSS nesting (Chrome 120), so a nested rule whose selector starts with an
element name is not a parse error anyone would notice — it is silently
dropped, on the phone and only on the phone. Three were live in
index.css, and the one that mattered was thetext-overflow: ellipsison the bottom bar's title and artist, which had therefore never
truncated on the device. Those three went with #23/#42; #154 is about
the class.
What changed.
make css-checkruns a second script,frontend/scripts/check-css-nesting.mjs, overindex.cssand thecssliterals in
src/**/*.tsalike — a shadow-root stylesheet is parsed bythe same engine. It names the file, the line and the fix, which is a
leading
&: valid in both syntaxes, so no nested rule here has a reasonto omit it. It is a lefthook pre-commit entry as well, beside the
existing
css-literalsone, and CI already runsmake css-check.The detection (
css-nesting.mjs) walks blocks rather than matchinglines, and both things the issue asks it to get right fall out of one
rule — a rule is nested when a style rule is somewhere above it, not
when its immediate parent is a block:
@media (…) { bottom-nav { … } }at the top level is an ordinary ruleand is not flagged. That is the majority of what a regex over the file
would report.
.bar { @media (…) { … } }is nested and isflagged, at-rule in between or not.
Strings and comments are read through, so a brace in a
url()is not ablock. The literal scanner the two checks now share is lifted into
css-literals.mjsunchanged, except that a${}substitution isblanked keeping its newlines so a line number survives it.
The tree has no violation left, so the check would pass just as happily
over an empty glob — it refuses one, in the spirit of
icon-language.test.ts's first assertion.Verification tiers run.
make css-check— clean, and watched failing. Removing the three&s fromindex.css's bottom-bar block (the historical cases, lines282/289/293) reports exactly those three lines with the right
selectors; a
span { … }injected intopage-header'scssliteralis reported at its own line, which exercises the literal path and the
line mapping through it. Both reverts left the tree clean.
make ui-test— 991 passed, 7 of them the newfrontend/test/utils/css-nesting.test.ts, which pins the at-ruledistinction in both directions plus the string/comment handling.
npx tsc --noEmit— clean.make skill-check— clean.push, including the new
css-nestingentry.Deliberately not done.
(Lightning CSS or PostCSS targeting Chrome 113), which would fix the
class permanently rather than reject it — is not taken. It is a
dependency and a build step against twenty lines, and the issue's own
Direction says the check is the version that matches how this repo has
handled the other silent-on-one-platform traps. It is written down in
CLAUDE.mdas the expensive answer, so the trade survives this PR.the check enforces about source text.
frontend/index.cssandcssliterals. A.cssfile added elsewhere under
frontend/would not be swept.CLAUDE.mdgains a paragraph under the Chrome 113 section and theyellowjacket-devskill'smake css-checkbullet gains the secondcheck, since that file is the imperative half.
Closes #154
CI is green on
4bf2414:checksuccess (run 478) ande2esuccess, the latter over both Chromium and WebKit.make css-checkruns the new script in that first job, so the check is gating from this commit on.4bf2414653tocee322dc31The device renders in Chrome 113, which predates relaxed CSS nesting, so a nested rule whose selector starts with an element name is not a parse error anyone would notice -- the rule simply does not exist, there and nowhere else. Three were live in `index.css`, and the one that mattered was the `text-overflow: ellipsis` on the bottom bar's title and artist, which had therefore never truncated on the device. No tier here can see the class at all: the component tier, the e2e tier and `make ui-visual` all run a current engine, where the rule applies normally. So `make css-check` carries a second script. It reads `index.css` and the `css` literals in `src/**/*.ts` alike, since a shadow-root stylesheet is parsed by the same engine, and it names the file, the line and the fix -- a leading `&`, which is valid in both syntaxes. The detection walks blocks rather than matching lines, and both things it has to get right fall out of one rule: a rule is nested when a *style* rule is somewhere above it, not when its immediate parent is a block. That leaves `@media (...) { bottom-nav { ... } }` at the top level alone, which is the majority of what a regex over the file would report, and still flags the same rule inside an at-rule that is itself inside a style rule. Strings and comments are read through, so a brace in a `url()` is not a block. The tree has no violation left, so the check would pass just as happily over an empty glob: it refuses one, and `test/utils/css-nesting.test.ts` pins the semantics that make the sweep mean something. The literal scanner the two checks share is lifted into `css-literals.mjs` unchanged, except that a `${}` substitution is now blanked keeping its newlines so a line number survives it. Closes #154The hook fires on frontend/**/*.{ts,css} while the script read one hardcoded path, so a second stylesheet would have been silently unswept while the hook still went green over it. There is only index.css today, which is exactly when this is cheap to fix. Watched catching a planted nested rule in a second file.19d91639a4to11ba7b3180