Skip to content

feat(desktop): flapping bee on the setup loading screen#1631

Merged
wesbillman merged 8 commits into
mainfrom
feat/bee-loading-icon
Jul 8, 2026
Merged

feat(desktop): flapping bee on the setup loading screen#1631
wesbillman merged 8 commits into
mainfrom
feat/bee-loading-icon

Conversation

@klopez4212

@klopez4212 klopez4212 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What

Puts an animated, wing-flapping Buzz bee on the desktop cold-boot loading gate, over the restored theme-adaptive grainient background — replacing the flat "Setting up your workspace…" text that #1570 left as a static black screen.

  • Grainient restored: brings back the pre-fix(desktop): render black static boot screen #1570 ThemeGrainientBackground (orbiting radial gradient driven by theme vars, with a prefers-reduced-motion fallback). The bee is the hero; the "Setting up your workspace…" caption stays sr-only.
  • Flapping bee: FlappingBee — the same BuzzMark silhouette, with its wings beating on a continuous CSS loop.
  • Boot-flash fix (index.html): paint the pre-React window in the cached theme background instead of black, so light themes don't flash black on launch.

Why the wings are HTML-level SVG layers

WebKit (WKWebView) paints SVG children on the main thread, so animating a transform on a wing <circle> freezes the flap while boot work hogs the thread — exactly when the gate is on screen, so the bee looked static in the real app. Each wing is now its own HTML-level <svg> layer animated via CSS transform; HTML transforms run on the compositor (Core Animation) and keep flapping regardless of main-thread load. Static CSS masks reproduce the slot cutouts so the layered build stays pixel-identical to the masked single-SVG mark (verified 37–39 dB PSNR at rest/mid/tuck).

Testing

  • just ci green; pre-push clippy + tests green.
  • Verified the flap runs in both Chromium and WebKit (the app's engine) with the built CSS — frames distinct, wings visibly beating.
  • Updated the first-run onboarding gate e2e to assert the grainient + flapping-bee treatment (grainient orbit running, wings running, currentColor, no SMIL).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5034f6dea0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/src/app/App.tsx Outdated
klopez4212 pushed a commit that referenced this pull request Jul 8, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2db7ab98b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/src/app/App.tsx Outdated
klopez4212 pushed a commit that referenced this pull request Jul 8, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af55ac28f5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/src/shared/styles/globals/animations.css
klopez4212 added a commit that referenced this pull request Jul 8, 2026
@klopez4212

Copy link
Copy Markdown
Contributor Author

🤖 Refresh-freeze fix: the wing flap now runs on the compositor.

Reproduced the "bee frozen on refresh" report: WebKit paints SVG children on the main thread, and on a warm refresh boot work (bundle eval + first React render of the app tree) hogs that thread for most of the window the gate is visible — a transform animation on the wing <circle>s never advances, so the bee flashed frozen mid-tuck. (Verified in a WebKit harness: under a blocked main thread the whole SVG freezes for 600–800ms while compositor-driven HTML transforms keep animating.)

FlappingBee is now three stacked layers — two HTML-level wing <svg>s under the masked body SVG — and the flap animates the wing elements' CSS transforms in percentages. HTML-level transforms run on the compositor (Core Animation in WKWebView), so the wings keep beating while the main thread is busy. The slot cutouts still punch through the wings via static evenodd alpha masks on the non-animated wrappers; screenshot-diffed against the old single-SVG mark at rest/mid/tuck — pixel-equivalent (37–39 dB PSNR, anti-aliasing only). Start pose moved from full tuck (-0.14s) to genuine mid-stroke (-0.07s) so even a one-frame flash reads as caught mid-beat rather than squished.

Also repaired the setup-gate e2e test, which still asserted the #1570 black static gate this branch replaced (it was failing on this branch before this commit) — it now asserts the grainient wash + flapping-bee treatment, including that the flap uses zero <animate> elements and is running.

Setup gate — light (GitHub Light)

01-gate-light

Setup gate — dark (Catppuccin Macchiato)

02-gate-dark

Wings open

03-wings-open

Wings tucked

04-wings-tucked

@klopez4212 klopez4212 force-pushed the feat/bee-loading-icon branch from edf4a52 to 355dfc2 Compare July 8, 2026 14:29
@klopez4212 klopez4212 changed the title feat(desktop): animated bee on loading gates feat(desktop): flapping bee on the setup loading screen Jul 8, 2026
npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 and others added 5 commits July 8, 2026 15:30
Replace the static Buzz mark and spinner on the setup splash and
workspace-switch gate with the looping FuzzyLogo (fuzzy texture,
theme-adaptive tint). A static BuzzMark renders underneath so the box
paints instantly on hard reload instead of flashing blank.

Loops continuously via loopRestSeconds=0 — the prior rest window left the
mark visually static for ~63% of each cycle, which read as 'not animated'.

Signed-off-by: Fizz <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
… hero

Restore the pre-#1570 theme-adaptive grainient loading gate, with the
animated Buzz bee as the visual hero replacing the old "Setting up your
workspace" text (the caption stays as sr-only for screen readers).

- Restore ThemeGrainientBackground and the grainient/shell CSS (orbiting
  radial grainient driven by --chart-* / --primary theme vars, plus the
  dark-mode alpha overrides and reduced-motion fallback) that #1570 stripped.
- Rewire AppLoadingGate to the .buzz-setup-loading-shell + grainient +
  BeeLoader (relative z-10) structure. Drop the text-only rules
  (buzz-setup-loading-text, buzz-setup-character-rise) since the bee replaces
  the text.
- index.html: paint the pre-React window in the cached theme --background
  (buzz-theme-cache) instead of always black, killing the black flash on
  light themes; falls back to black on the first-ever launch.

Scoped to the cold-boot AppLoadingGate only; the quiet WorkspaceSwitchGate
keeps the bee on a plain themed background.

Signed-off-by: Fizz <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Keep the setup loading gate's bee exactly as before (identical BuzzMark
geometry) and just add motion: a new FlappingBee component tags the two
wing lobes with bee-wing classes so the ported wing-flap keyframes beat
them on a continuous infinite loop. Reduced-motion falls back to the
static silhouette. Also fixes a biome useTemplate lint in index.html's
cached-theme boot script.

Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
…'t caught at rest

On a warm Cmd+R refresh the cold-boot loading gate can be very short-lived,
so the wing-flap was often caught at its 0% rest pose (no visible motion).
Start the flap with a negative animation-delay (-0.14s) so the very first
painted frame is already mid-beat — the bee never appears frozen at rest.

Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
…sh doesn't freeze the bee

On a warm refresh the bee flashed frozen: WebKit paints SVG *children* on
the main thread, and boot work (bundle eval, first React render of the app
tree) hogs that thread for most of the window the loading gate is visible,
so a transform animation on the wing <circle>s never advances. Rebuild
FlappingBee as layered HTML-level <svg> wings under the masked body SVG and
animate the wing elements' CSS transforms in percentages — HTML-level
transforms run on the compositor (Core Animation in WKWebView) and keep
flapping while the main thread is blocked.

The slot cutouts still punch through the wings via static CSS masks on the
non-animated wing wrappers (evenodd alpha masks — CSS mask-image uses the
alpha channel, not luminance), verified pixel-equivalent to the single-SVG
mark at rest/mid/tuck phases (37-39dB PSNR, anti-aliasing only). Start pose
moves from full tuck (-0.14s) to genuine mid-stroke (-0.07s) so a one-frame
flash reads as caught mid-beat rather than squished.

Also repair the setup-gate e2e assertions, which still tested the #1570
black static gate this branch replaced (pre-existing failure): assert the
grainient wash + flapping bee treatment instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: klopez4212 <klopez4212@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 355dfc2427

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/index.html Outdated
…e on cold boot

Give the animated FuzzyLogo layer in BeeLoader an important full width/height
so it overrides the buzz-logo--compact 1.5rem rule and fills the static
BuzzMark instead of rendering at 24px.

On first-ever launch (no theme cache) seed the dark class synchronously in the
boot script so the setup gate paints from the dark :root vars, matching the
default dark houston theme ThemeProvider applies moments later — no light
Catppuccin-Latte flash between the black boot background and the themed gate.

Addresses review comments on PR #1631.

Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
@klopez4212 klopez4212 force-pushed the feat/bee-loading-icon branch from 355dfc2 to c225b7e Compare July 8, 2026 14:41
…ld boot

Cold boot resolves the workspace in well under 100ms — traced in the real
staging app: the loading gate mounted at t=91ms and unmounted at t=150ms,
before the hidden Tauri window (visible:false until show()) ever put a
frame on screen. The wing animation was healthy the whole time; there was
just nothing on screen long enough to see. Refresh only looked different
because the window is already visible, so the flash is catchable.

Keep the gate up as a fixed overlay above the already-mounted app for a
minimum visible duration (1.2s + 200ms fade), then unmount it. Boot work
and data loading proceed beneath the overlay, so time-to-interactive is
unchanged — only the reveal waits. Workspace switches and first-run
completion keep their quiet gates; boots slower than the hold behave
exactly as before.

E2E runs skip the hold (it would slow every spec's boot and block pointer
actionability); specs can opt back in via __BUZZ_E2E__.bootSplashHoldMs,
which the new boot-splash.spec.ts uses to assert the overlay holds with a
running wing flap, doesn't delay the app beneath, and dismisses cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@klopez4212

Copy link
Copy Markdown
Contributor Author

🤖 Cold-boot follow-up: traced the real staging app and found the gate only lived 59ms on cold boot (mounted t=91ms, unmounted t=150ms) — the wing animation was healthy, but the hidden Tauri window (visible:false until show()) takes longer than that to put its first frame on screen, so the bee was never visible. Refresh only looked different because the window is already up.

b3df8898 adds a minimum boot-splash hold: once the app is ready, the gate stays up as a fixed overlay above the already-mounted app for 1.2s (+200ms fade), then unmounts. Boot work and data loading proceed beneath it — time-to-interactive is unchanged, only the reveal waits. Workspace switches and first-run completion keep their quiet gates; boots slower than the hold behave exactly as before.

E2E runs skip the hold by default so spec boots stay fast and pointer actionability is unaffected; the new boot-splash.spec.ts opts back in via __BUZZ_E2E__.bootSplashHoldMs and asserts the overlay holds with a running flap, doesn't delay the app beneath, and dismisses cleanly.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3df88985b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/index.html
Comment on lines +41 to +42
bg = parsed.vars["--background"];
if (bg) document.documentElement.style.backgroundColor = `hsl(${bg})`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply cached CSS vars before painting the splash

When the cached theme is anything other than the built-in Catppuccin light/dark vars, this only copies --background into html.style.backgroundColor and then adds a light/dark class. Fresh evidence beyond the prior thread is that ThemeProvider.applyCachedVars() reapplies the entire cached vars map (desktop/src/shared/theme/ThemeProvider.tsx:224-228), while body and the new loading gate read var(--background), --chart-*, etc. from CSS variables before React mounts; with a cached Houston/Buzz/custom theme the pre-React paint still uses the static Catppuccin variables and can flash the wrong themed splash until the bundle runs. Seed the cached vars on documentElement.style here before adding the class.

Useful? React with 👍 / 👎.

… survives cold boot

A WKWebView born in a hidden Tauri window (visible:false until
getCurrentWindow().show()) can lose the race between the window appearing
and WebKit being told the view is visible. When that happens — some boots,
not others, and inherited by every reload in that webview — the page's
rendering scheduler wedges permanently: requestAnimationFrame fires exactly
once and never again, CSS/compositor animations never advance, and even
timer-driven DOM mutations only reach the screen when something external
(user input) forces a flush, while visibilityState still reports "visible".
Traced in the real staging app across many boots; no in-page implementation
of the splash bee (CSS animation, rAF loop, timer + style writes, timer +
SVG attribute mutation) could animate in that state, because every
scheduling and flush primitive was dead.

Boot the window visible instead. The hidden-window dance existed to hide an
unstyled first paint, but index.html now paints the pre-React window in the
cached theme background (with dark seeding on first launch), so there is
nothing to hide — and a window that is never hidden cannot lose the
visibility race. The committed splash implementation (compositor-driven
wing flap + minimum-visible splash hold) then works on every boot and
reload. getCurrentWindow().show() stays as a harmless no-op safety.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@klopez4212 klopez4212 requested a review from wesbillman July 8, 2026 16:23
@klopez4212

Copy link
Copy Markdown
Contributor Author

🤖 Cold-boot freeze: actual root cause found and fixed (528c2111).

The frozen-bee reports traced to a race, not to any animation implementation: a WKWebView born in a hidden Tauri window (visible:false until show()) can miss the notification that the view became visible. When that happens — some boots, not others, inherited by every Cmd+R in that webview — the page's rendering scheduler wedges permanently: rAF fires exactly once and never again, CSS/compositor animations never advance, and timer-driven DOM writes only reach the screen when user input forces a flush, while visibilityState still lies "visible". We verified in the running app that no in-page implementation (CSS animation, rAF loop, timer + style writes, timer + SVG attribute mutation) can animate in that state.

Fix: boot the window visible. The hidden-window dance existed to hide an unstyled first paint, but this branch already paints the pre-React window in the cached theme background via the index.html boot script (with dark seeding on first launch), so there's nothing left to hide — and a window that is never hidden can't lose the race. With that, the committed splash implementation (compositor wing flap + minimum-visible splash hold) works on every cold boot and reload; confirmed across repeated boots on staging.

Worth noting for other features: in a wedged instance this race silently kills rAF and all CSS animation app-wide, so the visible:true change likely fixes more than the bee. The underlying wry/tauri race deserves an upstream report.

@wesbillman wesbillman merged commit c145037 into main Jul 8, 2026
25 checks passed
@wesbillman wesbillman deleted the feat/bee-loading-icon branch July 8, 2026 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants