fix(play): lock orientation pre-paint to avoid portrait loading flash
Set the session orientation in an isomorphic layout effect so portrait phones don't flash the landscape loading chrome for a frame before the bootstrap effect runs. State still inits to "landscape" for SSR-safety; the correction now lands before first paint (no-op on landscape devices). Addresses Copilot review on PR #31. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+20
-1
@@ -6,6 +6,7 @@ import {
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
@@ -70,6 +71,13 @@ function detectOrientation(): Orientation {
|
||||
return portrait && coarse ? "portrait" : "landscape";
|
||||
}
|
||||
|
||||
// Runs before the browser paints (so it can correct first-frame state without a
|
||||
// visible flash), but useLayoutEffect warns when called during SSR. PlayInner
|
||||
// only ever renders on the client (/play prerenders the Suspense fallback), yet
|
||||
// fall back to useEffect on the server anyway to keep the warning out.
|
||||
const useIsomorphicLayoutEffect =
|
||||
typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
||||
|
||||
// Cap how long we wait for the browser to download + decode a scene image
|
||||
// before giving up and rendering anyway. Runware's CDN is usually <2s for a
|
||||
// 1792×1024 PNG, but over slow links / VPN / strict corp networks the same
|
||||
@@ -797,6 +805,16 @@ function PlayInner() {
|
||||
};
|
||||
}, [togglePresentation, presentation]);
|
||||
|
||||
// Lock the visible orientation BEFORE the first paint, so portrait phones
|
||||
// never flash the landscape loading chrome. The state inits to "landscape"
|
||||
// for SSR-safety; this corrects it pre-paint (no-op re-render on landscape
|
||||
// devices). Prebaked cards (decision C) stay landscape-baked regardless of
|
||||
// device. The bootstrap effect below re-derives the same value for the
|
||||
// /api/start payload.
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
setOrientation(params.get("card") ? "landscape" : detectOrientation());
|
||||
}, [params]);
|
||||
|
||||
// ── Bootstrap: start session ─────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
if (startedRef.current) return;
|
||||
@@ -848,10 +866,11 @@ function PlayInner() {
|
||||
// Lock orientation for the whole session. Prebaked cards (decision C) are
|
||||
// landscape-baked, so they stay landscape regardless of device; only the
|
||||
// live /api/start path requests a portrait paint when the phone is upright.
|
||||
// The visible state is already set pre-paint by the layout effect above;
|
||||
// here we only need the value for the /api/start payload.
|
||||
const sessionOrientation: Orientation = cardName
|
||||
? "landscape"
|
||||
: detectOrientation();
|
||||
setOrientation(sessionOrientation);
|
||||
if (livePayload) livePayload.orientation = sessionOrientation;
|
||||
|
||||
if (!cardName && !livePayload) {
|
||||
|
||||
Reference in New Issue
Block a user