From a31da89df51ad5da59b83c163d88654f680ce8e3 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 18 Oct 2026 16:00:04 -0400 Subject: [PATCH] feat(ui): View Transitions crossfade for library switches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Where the browser supports it (Chrome 111+, Safari 18+, Firefox 139+), switchWithTransition now uses the native View Transitions API: the old shelves are snapshotted, the fetch + swap happen behind the dim, and the two snapshots crossfade — no blank phase, no manual opacity choreography. The dim + spinner is hidden inside the update so it blends away as part of the transition. Browsers without the API keep the existing dim + fade sequence unchanged. Zero dependencies — the crossfade is the browser's own. --- web/src/library-switcher.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/web/src/library-switcher.ts b/web/src/library-switcher.ts index 60e9270e..5eaadcc2 100644 --- a/web/src/library-switcher.ts +++ b/web/src/library-switcher.ts @@ -15,6 +15,33 @@ export async function switchWithTransition( if (!container || !loading) return; + // View Transitions (native, where supported): the browser snapshots the + // old shelves, fetchFn swaps the DOM, and the two snapshots crossfade — + // no blank phase, no manual opacity choreography. The dim + spinner is + // shown before the snapshot and hidden inside the update, so the dim + // blends away as part of the crossfade. Browsers without the API fall + // through to the dim + fade sequence below. + const doc = document as Document & { + startViewTransition?: (updateCallback: () => void | Promise) => { + finished: Promise; + }; + }; + if (doc.startViewTransition) { + try { + loading.classList.remove("hidden"); + const transition = doc.startViewTransition(async () => { + await fetchFn(); + loading.classList.add("hidden"); + }); + await transition.finished; + } catch (error) { + loading.classList.add("hidden"); + showToast("Failed to load library", "error"); + console.error("Switch library error:", error); + } + return; + } + try { container.classList.add( "opacity-0",