Fix Safari chrome tint with simulator-verified mechanisms - #67
Conversation
Fourth pass at #60, this time debugged against real iOS Safari in the simulator instead of by inference. Three findings, three mechanisms: - Safari's status-bar strip tints from the body's computed background-color, and a change driven only by a CSS custom property transition never triggers its re-sample — that's the "stuck grey with a hard line" footer. render() now writes the ground color as an inline style, a real mutation Safari notices; the color is invisible on the page (the opaque gradient paints over it). - Safari applies theme-color once around pageshow and can miss the meta swaps made while the page is still loading, leaving the toolbar on the static midnight fallback. start() now re-renders on pageshow (twice, straddling Safari's variable apply point), which also covers back/forward-cache restores. Meta replacement is unconditional so an equal-color repaint still re-asserts the value. - viewport-fit=cover: the sky paints edge-to-edge under the notch and home indicator, with safe-area side padding for landscape; controls already pad by the insets. Also adds ?sky=day|night|auto to pin the mode for one load — the device-testing override used to drive these simulator runs, not persisted so shared links don't overwrite a visitor's choice. Verified on the iOS 26.5 simulator (iPhone 17 Pro): the status strip now tracks auto/day/night switches exactly (rgb(112,140,170) day ↔ rgb(29,42,97) night, sampled), where it previously kept its load-time sample through every switch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Amy could still force the grey footer by clicking the sky button: iOS Safari applies theme-color once per navigation, so an in-page mode switch left the toolbar on the tint it latched at load. Simulator A/B across candidate levers — setAttribute, node replacement, re-insertion, history.replaceState, history.pushState, hash assignment — found exactly one that makes Safari re-run the apply: a same-document navigation. location.replace to a #sky-<color> fragment performs one without adding a history entry, so Back stays clean. render() now issues that nudge when the chrome color actually changes: Apple touch devices only, never on first paint (load applies natively), never at demo frame rate, and never clobbering a fragment the site didn't put there. Simulator-verified across repeated day/night switches: both the status strip and the toolbar now track every change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed a follow-up commit: the grey-footer-by-clicking-the-sky-button case is the toolbar's once-per-navigation theme-color apply. Simulator A/B found the one lever that re-runs it — a same-document navigation — so render() now does a history-free |
Two layers, per the half of #60 each can see: - tests/sky-chrome.spec.ts runs in real WebKit with iPhone emulation (new webkit-iphone Playwright project; CI now installs webkit) and pins every signal the page emits into Safari's chrome: both media-gated theme-color metas rewritten on a mode switch, the inline body background-color Safari samples for its status strip, the history-free #sky- navigation nudge, fragment preservation, and the ?sky= pin. - scripts/check-sky-chrome.mjs drives real simulator Safari through in-page day/night switches via a new dev-only ?skyseq= harness in sky.ts and pixel-samples the actual rendered chrome. It currently FAILS on its in-page-switch legs — deliberately kept red: it reproduces the exact remaining bug (switch modes after load, chrome keeps the old sky) deterministically, where it disproved three more candidate mechanisms tonight (deferred, history-pushing, and doubled hash navigations). The Apple-touch gate for the nudge also accepts 'ontouchstart' — real WebKit under iPhone emulation reports maxTouchPoints 0, and iPads with desktop-mode UA carry the event handler without the touch-point count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Status update, honest version: the in-page switch bug is not fixed yet, but it is now fully reproducible and fenced with tests.
What still holds: load-state chrome is correct (pageshow re-assert), and the status strip tracks the first switch after load. What's left: current simulator Safari appears to honor at most one dynamic chrome update per real navigation, and no re-apply lever found so far gets past that. Next session: iterate new mechanisms directly against the red harness. |
PR #67's Lighthouse run failed a11y (0.94) on both blog pages — not because of that PR: the run happened at 03:16 UTC, San Francisco sunset, and Lighthouse audits the live sky. At dusk the computed ink/card pairing bottoms out at ~6.6:1, and the blog date and description lines carried opacity-70/80, multiplying the effective contrast down to ~4.15:1 — under the 4.5:1 floor for small text. The same failure exists on main; daytime CI runs just never see it. Drop the opacity de-emphasis (size and tracking already carry the hierarchy) and add the ink-on-card pairing to check:contrast with a 4.5:1 floor across every sun altitude (worst case 6.56:1), so this class of regression fails the build deterministically instead of only when CI happens to run at sunset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…trast Fix sunset contrast on blog card text and gate it in check:contrast
Fourth pass at #60 (latest report) — this time debugged against real iOS Safari in the simulator (Xcode + iOS 26.5 runtime, iPhone 17 Pro) instead of by inference, with pixel-sampled before/after evidence.
What the simulator revealed
Your screen recording showed the truth: the bottom toolbar was following the sky (PR #66 worked), but the top status-bar strip stayed stuck, with a hard line. Driving the real site through scripted day/night switches in the simulator and sampling pixels:
background-color— not theme-color, not the page's rendered pixels. And when that color changes only because a CSS custom property (--sky-bottom) transitions, Safari never re-samples: the strip keeps the sky from page load. Sampled proof: strip stayedrgb(112,140,170)(day) through every switch before the fix; after, it tracksrgb(112,140,170)↔rgb(29,42,97)exactly.theme-coloronce, aroundpageshow, and can miss meta swaps made while the page is still loading — which is whensky.tsdoes its first renders. That's the toolbar stuck on the static midnight#141a3d("stuck as dark blue night mode"). It also explains the randomness: back/forward-cache restores skip the load path entirely.The fixes
render()writes the ground color as an inlinebackground-coloron body — a real style mutation Safari notices and re-samples. Invisible on the page itself (the opaque gradient paints over it); it only feeds the chrome.start()re-renders onpageshow(immediately + 1.2s later, straddling Safari's variable apply point), covering both the mid-load miss and bfcache restores. Meta replacement is now unconditional so an equal-color repaint still re-asserts the value.viewport-fit=cover: the sky paints edge-to-edge under the notch and home indicator (safe-area side padding added for landscape; the controls, footer, and lightbox already pad by the insets). Verified the about page's back-link clears the notch.?sky=day|night|autopins the mode for one load (not persisted) — this is what drove the simulator runs, and it makes phone testing much easier for you too.Verified
check:contrastholds AAA; Prettier/ESLint/stylelint clean.Please give the Vercel preview a spin on your phone (both light and dark system appearance) before merging — the simulator runs iOS 26.5 and your Safari may differ:
?sky=night/?sky=dayon the preview URL makes the states easy to force.🤖 Generated with Claude Code