Repository navigation
chromecast receiver css legacy
The Cast Android TV receiver runs on the Cast engine's Chromium 92 (CrKey UA, verified on a real device). The engine silently drops any CSS feature it does not implement — no error, no warning, the declaration just disappears. A layout built on a dropped declaration collapses.
Three Chromium-92 floors bite this app:
| Feature | Chrome | Effect when dropped |
|---|---|---|
@layer blocks |
99 | Tailwind v4 emits every rule inside @layer — the receiver renders with no styling at all
|
Container queries (cqw, container-type) |
105 | The TV timer surface sizes its padding, gaps, clock type and buttons in cqw — every size vanishes, the timer has no alignment
|
Individual transform properties (translate/rotate/scale) |
104 | Tailwind v4 emits transforms as individual properties — offset and scaled elements lose their transform |
Dynamic viewport units (dvh, svh) |
108 | Full-height panes collapse |
apps/playground/src/build/legacyReceiverCss.ts lowers the built receiver
stylesheet, in order:
-
postcss
- unwrap
@layerblocks in document order (single cascade order, so the semantics survive), - drop
@propertyregistrations, - drop
:has()rules (the engine drops the whole rule anyway, including the other selectors in its list), - drop
container-type/container-name(inert without container queries), - rewrite container-query units → viewport units (
cqw→vw,cqh→vh), - rewrite dynamic viewport units →
vh/vw, - fold the individual transform properties into the
transformshorthand, in the order the spec applies them (translate → rotate → scale, then the shorthand).
- unwrap
-
lightningcss at chrome 87 — lowers
oklch/labcolors to hex/rgb fallbacks, keeping modern values behind@supportsupgrades.
Only declaration values are rewritten. Selectors keep their spelling:
Tailwind escapes units into class names (.h-\[100dvh\]), and those class
names have to keep matching the markup.
The receiver's timer surface is full-bleed (h-screen w-screen), so its
container is the viewport and 1cqw ≈ 1vw. A container-query unit resolves
against the container's content box (inside the surface's own padding)
while vw resolves against the viewport, so the legacy sizes land ~9% larger
on a 1280px screen. The layout is correct and nothing overflows; the scale is
a shade off the design.
bun run check:receiver-css # checks the built receiver stylesheet
bun scripts/check-receiver-css-compat.cjs <file> # checks a specific stylesheetThe checker parses the built stylesheet and fails when it finds a feature
Chromium 92 lacks. Support data comes from caniuse-lite, so the floor is
not hand-maintained. It runs in CI right after the receiver build
(.github/workflows/_verify.yml).
- Rebuild the receiver — the artifact the TV loads is the built one:
bun x vite build --config apps/playground/vite.receiver.config.ts --outDir dist - Run the checker against
apps/playground/dist/assets/receiver-*.css. - If the checker is clean but the layout is still off, the source is using a
modern unit that resolves to something the engine ignores — add it to
UNIT_REWRITESinapps/playground/src/build/legacyReceiverCss.tsand cover it inlegacyReceiverCss.test.ts.