Skip to content

LOTTO v1.24.2 — Custom appearances on the iPadOS 15 support floor

Choose a tag to compare

@MattGeiger MattGeiger released this 26 Aug 06:49
· 79 commits to main since this release

LOTTO v1.24.2 is a bug-fix release. Custom appearances did not render on the declared iPadOS 15 support floor, and only the two hand-authored built-in brands were exempt.

The defect

oklch() requires Safari 16.4. The declared floor is iPadOS 15, where the deployed iPad mini 4 runs Safari 15.6.

Hand-authored brand stylesheets were safe because they pass through the build, where Lightning CSS downlevels oklch() to sRGB for the browserslist floor — the compiled stylesheet contains no OKLCH at all. Runtime brand themes take a different path: they are derived per request and injected as an inline <style>, so they never reach that pipeline and shipped their OKLCH values verbatim.

Because an invalid value is dropped at computed-value time, the failure was total but selective:

Symptom Mechanism
Card, popover and modal surfaces transparent background-color: var(--card) invalid
Dark outlines around every panel --border invalid, so border-color falls back to currentColor
Toggle switches invisible track and thumb are theme tokens
Modals unreadable surface and backdrop scrim transparent, page content showing through

This affected the white-label feature on precisely the hardware it deploys to. It was not a simulator artifact.

The fix

serializeBrandThemeCss now emits each scope twice — an sRGB baseline, then the OKLCH values inside @supports (color: oklch(0 0 0)). Modern engines take the richer form; iPadOS 15 keeps the baseline. Colours inside gradients and shadows convert in place with alpha preserved. Derived values are unchanged; only serialization differs.

The obvious shorthand does not work here. --card: #fff; --card: oklch(...) fails, because custom properties are not validated at parse time — both declarations are accepted and the later always wins, with the invalidity surfacing only at var() substitution. @supports is the only correct guard.

Also fixed

npm run dev is now usable on the support floor. iOS 15 Safari refuses the Next.js hot-reload WebSocket, and because Next constructs it inside an async appBootstrap, the rejection aborted bootstrap before hydrateRoot — the app rendered but never became interactive. A development-only shim keeps the constructor from throwing. Hot reload is unavailable on that engine; production output is byte-identical.

Verification

Measured rather than inferred: on iPadOS 15.4, CSS.supports("color", "oklch(0.7 0.15 145)") returns false and the value computes to rgba(0, 0, 0, 0). color-mix() is supported there and was not implicated. After the fix, --card, --popover, --primary and --border all resolve to real colours on-device.

780 tests, lint, tsc, a production build and the legacy-bundle scan all pass. The serializer appears in zero client chunks. Confirmed on a simulated iPad mini 4 running iPadOS 15.4 in both the dev server and a production build, and validated on live hardware.

Full detail, including approaches considered and rejected, is in docs/ISSUES.md Issues 42 and 43.

Full changelog: v1.24.1...v1.24.2