system.cssthe engine GitHub

View transitions & the FOUC ledger

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= (the av() helper, wired via view.AssetVer); versioned URLs cache immutable, 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 .hud silently sharing vt-hud with the fab cluster — armed only when an unseen toast painted it, then every navigation hard-cut. Overlays whose content differs per page get view-transition-name: none, never a lifted group.

  • Custom VT animations silently wipe plus-lighter (gf-140). The UA applies mix-blend-mode: plus-lighter through its OWN animation — override animation-name on ::view-transition-old/new and 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 restore isolation: isolate on the image-pair, plus-lighter on 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 inline display: none; (datastar overwrites el.style.display on evaluation). Same law for data-attr state: BAKE the landed value server-side (the locked form now lands data-ui-state="locked" beside its data-attr binding). Elements inside a closed <dialog> are exempt — they never paint. display in 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-flex lived 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's color-scheme arrives 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), and staticfs must keep max-age=0, stale-while-revalidate so 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.