View transitions & the FOUC ledger (hard-won — do not relearn)
CROSS-DOCUMENT VT IS OFF (gf-200, per Mike: "more trouble than it's
worth"). Measured 2026-09-04: with @view-transition {navigation: auto} active, EVERY same-origin navigation stalled ~4s —
the old document held open (SSE alive four seconds past the new GET)
waiting for a snapshot that never completed, then the transition timed
out, skipped, and showed the white interstitial; cross-origin
navigations ran in 21ms. All cost, no cross-fade. The opt-in is
commented out in the view-transitions layer with the evidence; the
html[data-vt-ran] pagereveal probe stays armed — any re-enable
experiment starts by uncommenting the opt-in and watching that probe,
and MUST verify the ~4s stall stays gone. The baked meta color-scheme keeps plain document swaps dark-to-dark.
The nav cross-blur lived in @layer view-transitions (the spine's last
layer; the old project tail is retired) as @view-transition { navigation: auto } plus explicit animations. The navigation-flash
bugs run to ground so far; each is a LAW now:
-
Stale-sheet FOUC is dead — assets are VERSIONED (gf-188). The static handler computes a per-file version at boot; layout stamps every static reference
?v=(theav()helper, wired viaview.AssetVer); versioned URLs cacheimmutable, so HTML can never pair with a mismatched sheet no matter the deploy cadence. If a "FOUC after deploy" report ever returns, check av() coverage first — module-internal import specifiers are the one known unversioned crack. -
One name, one rendered box includes OVERLAYS (gf-188). The toasts hud was a second not-
.mobile.hudsilently sharingvt-hudwith the fab cluster — armed only when an unseen toast painted it, then every navigation hard-cut. Overlays whose content differs per page getview-transition-name: none, never a lifted group. -
Custom VT animations silently wipe plus-lighter (gf-140). The UA applies
mix-blend-mode: plus-lighterthrough its OWN animation — overrideanimation-nameon::view-transition-old/newand the blend vanishes: mid-cross the two half-transparent snapshots sum below 100% and the canvas flashes through every navigation. Any custom VT animation must restoreisolation: isolateon the image-pair,plus-lighteron old/new, and LINEAR timing both sides so opacities sum to 1 at every instant. -
In-flow data-show FALSE-at-seed carries inline
display: none;(gf-198 — THE SEED-HIDE LAW). Datastar evaluates after its module loads, AFTER first paint: an in-flow element whose data-show expression is false at land paints VISIBLE for a frame (Set-Up → General landed doubled with a live Save row; every feedback thread landed with its composer open). The floor is the landed HTML itself: seed-hide with inlinedisplay: none;(datastar overwrites el.style.display on evaluation). Same law for data-attr state: BAKE the landed value server-side (the locked form now landsdata-ui-state="locked"beside its data-attr binding). Elements inside a closed<dialog>are exempt — they never paint.displayin this idiom is state, not geometry; the ratchet baselines carry a dated note where it is used. -
Client-state hosts get their display rule in the ENGINE, not a rocket-injected sheet (gf-198). hold-confirm's
display:inline-flexlived only in JS injectCSS — the pre-upgrade frame rendered the host inline and reflowed on connect. Every custom-element host is sized by system.css's:where(host)rules (date-picker, day-picker, combo-box…); a rocket's injected CSS may add BEHAVIOR chrome only, never the host's own box. -
The INTERSTITIAL frame is pre-CSS — bake
meta color-scheme(gf-200). Between two documents the engine paints the window default (WHITE) until the incoming page's color-scheme is known; CSS'scolor-schemearrives too late for that frame. Chrome-the- browser masks the gap with the cross-doc view transition; app-mode windows (chromium --app — Omarchy web apps) don't, so dark-mode users see a white flash on every click. The layout bakes<meta name="color-scheme">from the SERVER-known theme (dark / light / "dark light" for auto) beside the theme-color meta — the blank frame then paints in the right darkness with or without a view transition. -
Every
<script>in the head must exist on disk (gf-47). A parser-blocking 404 stalls first paint AND breaks the VT handoff. A batch commit once scooped an unrelated deletion — verify a commit's file list, and after deleting any static/ file grep layout.templ for its tag. -
One landing = ONE document GET (gf-47). A client-side default-view or remembered-lens re-navigation doubles the load and the cross-fade (reads as page-wide FOUC). Any "apply stored view" logic must compare URLs order-insensitively (page=1 ≡ absent) and use location.replace with a handoff key that skips the second transition.
-
Theme/size/skin seed rides the ROOT element (gf-108), and statics serve stale-while-revalidate (gf-113) — the
<html>data-attrs must be baked server-side (a bundle that evaluates root bindings before the body seed strips the theme for a frame), andstaticfsmust keepmax-age=0, stale-while-revalidateso CSS renders from cache instantly. Check response headers before suspecting the cascade.
When a flash report arrives: FIRST check the console for 404s and count
document GETs per click (network tab), THEN test with --cfg-motion: 0
(a flash that survives motion-0 is a blend/paint bug, not an animation
bug), and only then read the VT rules.