Nested CSS rules starting with an element name are silently dropped on the phone #154

Closed
opened 2026-08-20 04:04:44 +00:00 by logan · 2 comments
Collaborator

Report

The device renders in Chrome 113 (CLAUDE.md, "the phone is a
Chrome 113 WebView"), which predates relaxed CSS nesting (Chrome 120).
So a nested rule whose selector begins with a bare identifier is not a
parse error anyone would notice — it is silently dropped, on the
phone and only on the phone.

Three were live in frontend/index.css, all inside .bottom-bar:

.bottom-bar {
    #track-info { p {  } }   /* the metadata's ellipsis */
    now-playing { overflow: hidden; }
    audio-player { margin: 0.5em 1em; }
}

The first is the one that matters: it is the text-overflow: ellipsis
on the bottom bar's track title and artist, so on the device that
text has never truncated
. Same class of fault as the now-playing
marquee whose text-overflow sat on the wrong box and had therefore
never produced an ellipsis in any mode — invisible to every assertion,
visible in a screenshot.

Those three are fixed in the PR for #23/#42, which rewrites that block
anyway. This issue is about the class, not those three.

Findings

  • The fix is one character: & p, & now-playing, & audio-player.
    The & form is valid in both the old and the new syntax, so there is
    no reason for any nested rule in this repo to omit it.
  • A rule inside @media is not nested. @media … { bottom-nav { … } }
    is a top-level rule and is fine — worth stating, because a naive
    regex over the file flags those and they are the majority of the
    matches.
  • A sweep of frontend/src/**/*.ts for the same shape inside css`` literals returned only false positives (try {, return {`) with the
    crude parser I used; a real one needs to know it is inside a tagged
    template and inside a declaration block.
  • No tier can see this. The component tier, the e2e tier and
    make ui-visual all run a current Chromium, where the rule applies
    normally. Only the device disagrees, and only in a screenshot.

Direction

Extend make css-check, which already exists for a trap of exactly
this shape (a backtick that ends a css literal early — silent, and
diagnosed a long way from the cause). It should fail on a nested rule
whose selector starts with an identifier, naming the file and line, and
say the fix is a leading &.

Two things to get right in the check: it must not flag rules directly
inside an at-rule (@media, @supports, @container), and it should
cover both frontend/*.css and the css tagged templates in
components, since the shadow-root stylesheets have the same constraint.

An alternative worth weighing first: a build-time transform (Lightning
CSS or PostCSS with a target of Chrome 113) would downlevel the
nesting rather than reject it, which fixes the class permanently and
covers whatever else the target browser lacks. That is a larger change
and adds a dependency; the check is the cheap version and matches how
this repo has handled the other silent-on-one-platform traps.

**Report** The device renders in **Chrome 113** (`CLAUDE.md`, "the phone is a Chrome 113 WebView"), which predates relaxed CSS nesting (Chrome 120). So a nested rule whose selector begins with a bare identifier is not a parse error anyone would notice — it is **silently dropped**, on the phone and only on the phone. Three were live in `frontend/index.css`, all inside `.bottom-bar`: ```css .bottom-bar { #track-info { p { … } } /* the metadata's ellipsis */ now-playing { overflow: hidden; } audio-player { margin: 0.5em 1em; } } ``` The first is the one that matters: it is the `text-overflow: ellipsis` on the bottom bar's track title and artist, so **on the device that text has never truncated**. Same class of fault as the `now-playing` marquee whose `text-overflow` sat on the wrong box and had therefore never produced an ellipsis in any mode — invisible to every assertion, visible in a screenshot. Those three are fixed in the PR for #23/#42, which rewrites that block anyway. This issue is about the **class**, not those three. **Findings** - The fix is one character: `& p`, `& now-playing`, `& audio-player`. The `&` form is valid in both the old and the new syntax, so there is no reason for any nested rule in this repo to omit it. - **A rule inside `@media` is not nested.** `@media … { bottom-nav { … } }` is a top-level rule and is fine — worth stating, because a naive regex over the file flags those and they are the majority of the matches. - A sweep of `frontend/src/**/*.ts` for the same shape inside `css`` literals returned only false positives (`try {`, `return {`) with the crude parser I used; a real one needs to know it is inside a tagged template and inside a declaration block. - No tier can see this. The component tier, the e2e tier and `make ui-visual` all run a current Chromium, where the rule applies normally. Only the device disagrees, and only in a screenshot. **Direction** Extend `make css-check`, which already exists for a trap of exactly this shape (a backtick that ends a `css` literal early — silent, and diagnosed a long way from the cause). It should fail on a nested rule whose selector starts with an identifier, naming the file and line, and say the fix is a leading `&`. Two things to get right in the check: it must not flag rules directly inside an at-rule (`@media`, `@supports`, `@container`), and it should cover both `frontend/*.css` and the `css` tagged templates in components, since the shadow-root stylesheets have the same constraint. An alternative worth weighing first: a build-time transform (Lightning CSS or PostCSS with a target of Chrome 113) would *downlevel* the nesting rather than reject it, which fixes the class permanently and covers whatever else the target browser lacks. That is a larger change and adds a dependency; the check is the cheap version and matches how this repo has handled the other silent-on-one-platform traps.
logan self-assigned this 2026-08-21 09:36:19 +00:00
logan added the
Status
In Progress
label 2026-08-21 09:36:19 +00:00
Author
Collaborator

Taking this on branch fix/154-nested-css-check.

Approach is the Direction's cheap version: a second check under
make css-check rather than a build-time downlevel, matching how the
other silent-on-one-platform traps here are handled.

It covers frontend/index.css and the css literals in
frontend/src/**/*.ts alike, and the detection walks blocks rather
than matching lines, so both of the 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, which leaves @media (…) { bottom-nav { … } } at
the top level alone and still flags one inside an at-rule that is
itself inside a rule. Strings and comments are read through, so a brace
in a url() is not a block.

The three live cases went with #23/#42, so the tree is clean and the
check would pass over an empty glob just as happily — hence a guard for
that and unit tests for the semantics, since no rendered tier can see
this class at all.

Taking this on branch `fix/154-nested-css-check`. Approach is the Direction's cheap version: a second check under `make css-check` rather than a build-time downlevel, matching how the other silent-on-one-platform traps here are handled. It covers `frontend/index.css` and the `css` literals in `frontend/src/**/*.ts` alike, and the detection walks blocks rather than matching lines, so both of the 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, which leaves `@media (…) { bottom-nav { … } }` at the top level alone and still flags one inside an at-rule that is itself inside a rule. Strings and comments are read through, so a brace in a `url()` is not a block. The three live cases went with #23/#42, so the tree is clean and the check would pass over an empty glob just as happily — hence a guard for that and unit tests for the semantics, since no rendered tier can see this class at all.
Author
Collaborator

PR #180 is open and CI is green (check and e2e, the latter over both
Chromium and WebKit).

make css-check carries a second script,
frontend/scripts/check-css-nesting.mjs, over index.css and the css
literals alike, plus a lefthook pre-commit entry beside the existing
css-literals one. Both of the things this issue asks it to get right
fall out of one rule — a rule is nested when a style rule is somewhere
above it — so a rule directly inside a top-level @media is not
flagged and one inside an at-rule that is itself inside a rule is.

Watched failing before believing it: the three historical &s removed
from index.css's bottom-bar block report exactly lines 282, 289 and
293, and a span { … } injected into page-header's literal reports
its own line through the literal path.

The alternative this issue asks to weigh first — a build-time downlevel
targeting Chrome 113 — is deliberately not taken, and the trade is
written into CLAUDE.md rather than left in this thread.

PR #180 is open and CI is green (`check` and `e2e`, the latter over both Chromium and WebKit). `make css-check` carries a second script, `frontend/scripts/check-css-nesting.mjs`, over `index.css` and the `css` literals alike, plus a lefthook pre-commit entry beside the existing `css-literals` one. Both of the things this issue asks it to get right fall out of one rule — a rule is nested when a *style* rule is somewhere above it — so a rule directly inside a top-level `@media` is not flagged and one inside an at-rule that is itself inside a rule is. Watched failing before believing it: the three historical `&`s removed from `index.css`'s bottom-bar block report exactly lines 282, 289 and 293, and a `span { … }` injected into `page-header`'s literal reports its own line through the literal path. The alternative this issue asks to weigh first — a build-time downlevel targeting Chrome 113 — is deliberately not taken, and the trade is written into CLAUDE.md rather than left in this thread.
logan closed this issue 2026-08-21 15:59:25 +00:00
gitea-actions bot removed the
Status
In Progress
label 2026-08-21 15:59:38 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#154