Repository navigation
LOTTO v1.24.2 — Custom appearances on the iPadOS 15 support floor
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 atvar()substitution.@supportsis 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