diff --git a/desktop/index.html b/desktop/index.html index 31d90dc4d..c219f214d 100644 --- a/desktop/index.html +++ b/desktop/index.html @@ -6,18 +6,48 @@ + diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts index 8ca25b81d..cd881650c 100644 --- a/desktop/playwright.config.ts +++ b/desktop/playwright.config.ts @@ -60,6 +60,7 @@ export default defineConfig({ "**/top-chrome-zoom-clearance.spec.ts", "**/thread-unread.spec.ts", "**/workspace-rail.spec.ts", + "**/boot-splash.spec.ts", "**/thread-reply-anchor-roleplay.spec.ts", "**/threadpane-ultrawide.spec.ts", "**/animated-avatar.spec.ts", diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index c33f3443b..7bcc434f7 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -20,7 +20,7 @@ "width": 800, "height": 600, "maximized": true, - "visible": false, + "visible": true, "backgroundColor": "#000000", "titleBarStyle": "Overlay", "hiddenTitle": true, diff --git a/desktop/src/app/App.tsx b/desktop/src/app/App.tsx index c3624af24..a830cfd2e 100644 --- a/desktop/src/app/App.tsx +++ b/desktop/src/app/App.tsx @@ -12,6 +12,7 @@ import { } from "react"; import { router } from "@/app/router"; +import { ThemeGrainientBackground } from "@/app/ThemeGrainientBackground"; import { useReloadShortcut } from "@/app/useReloadShortcut"; import { useAppOnboardingState } from "@/features/onboarding/hooks"; import { OnboardingSlideTransition } from "@/features/onboarding/ui/OnboardingSlideTransition"; @@ -25,27 +26,108 @@ import { createBuzzQueryClient } from "@/shared/api/queryClient"; import { isSharedIdentity as isSharedIdentityCmd } from "@/shared/api/tauri"; import { listenForDeepLinks } from "@/shared/deep-link"; import { useSystemColorScheme } from "@/shared/theme/useSystemColorScheme"; +import { cn } from "@/shared/lib/cn"; import { Button } from "@/shared/ui/button"; import { BuzzMark } from "@/shared/ui/buzz-logo/BuzzMark"; -import { Spinner } from "@/shared/ui/spinner"; +import { FlappingBee } from "@/shared/ui/buzz-logo/FlappingBee"; +import { FuzzyLogo } from "@/shared/ui/buzz-logo/FuzzyLogo"; import { StartupWindowDragRegion } from "@/shared/ui/StartupWindowDragRegion"; import { StepProgress } from "@/shared/ui/step-progress"; const LOADING_TEXT = "Setting up your workspace..."; -// Cold boot gate: a plain static Buzz mark (#D7D72E) on solid black. No -// animation machinery, no gradient — the mark must paint complete on the very -// first frame, even in webviews that render before scripting or SMIL start. +// Minimum time the cold-boot splash stays on screen. A real boot resolves the +// workspace in well under 100ms, and the hidden Tauri window (visible:false +// until getCurrentWindow().show()) takes longer than that to put its first +// frame on screen — without a hold, the bee is unmounted before it is ever +// visible. The hold runs as an overlay above the already-mounted app, so +// time-to-interactive is unchanged; only the reveal waits. +const BOOT_SPLASH_MIN_VISIBLE_MS = 1_200; +const BOOT_SPLASH_FADE_MS = 200; + +type BootSplashPhase = "holding" | "fading" | "done"; + +// E2E runs skip the hold (it would slow every spec's boot and block pointer +// actionability); a spec can opt back in via __BUZZ_E2E__.bootSplashHoldMs. +function bootSplashHoldMs(): number { + const e2e = ( + window as Window & { + __BUZZ_E2E__?: { bootSplashHoldMs?: number }; + } + ).__BUZZ_E2E__; + if (e2e) { + return e2e.bootSplashHoldMs ?? 0; + } + return BOOT_SPLASH_MIN_VISIBLE_MS; +} + +function useBootSplashHold(): BootSplashPhase { + const [phase, setPhase] = useState(() => + bootSplashHoldMs() > 0 ? "holding" : "done", + ); + + useEffect(() => { + const holdMs = bootSplashHoldMs(); + if (holdMs <= 0) { + return; + } + const fadeTimer = window.setTimeout(() => setPhase("fading"), holdMs); + const doneTimer = window.setTimeout( + () => setPhase("done"), + holdMs + BOOT_SPLASH_FADE_MS, + ); + return () => { + window.clearTimeout(fadeTimer); + window.clearTimeout(doneTimer); + }; + }, []); + + return phase; +} + +// Animated Buzz mark for the loading gates. The static BuzzMark renders in +// normal flow and sizes the box — it's plain SVG (no JS/SMIL), so it paints on +// the very first frame even before scripting starts, avoiding a blank flash on +// hard reload. The animated FuzzyLogo is layered on top and takes over once it +// begins playing. +function BeeLoader({ + ariaLabel, + className, + tintClassName = "text-foreground", +}: { + ariaLabel: string; + className?: string; + tintClassName?: string; +}) { + return ( +
+ + +
+ ); +} + +// Cold boot gate: the theme-adaptive grainient background with a single +// centered Buzz bee flying over it — the same static mark as before, now with +// its wings flapping (ported from the Buzz website's wing-flap). Replaces the +// old "Setting up your workspace" text, which stays as an sr-only caption. function AppLoadingGate() { return (
+ {LOADING_TEXT} - +
); } @@ -69,7 +151,11 @@ function WorkspaceSwitchGate() { Switching workspace… {showSpinner ? ( -