feat(ui): add RozeniteLoader component - #446
Merged
Merged
Conversation
Add a dithered gem loading spinner masked to the Rozenite logo silhouette, for on-brand loading states in DevTools plugin panels. Includes a Storybook story with Controls for cols, period, and noise.
V3RON
added a commit
that referenced
this pull request
Aug 23, 2026
## Description
Covers the standalone app's window with a startup splash —
`RozeniteLoader` centered on the app's own `--background` — for as long
as the device handshake is in flight, then fades it off once the shell
mounts.
- New `Splash` component in `@rozenite/app` owning the whole
show/fade/unmount sequence; `App` only tells it whether the shell is up
yet.
- Replaces the bare "Connecting to device…" empty state that used to
fill that time.
- The splash stays up *behind* the pre-connection status dialogs ("Can't
reach the dev server", "Rozenite isn't set up in this app"), which now
sit on it as their backdrop instead of on an empty window.
- Speeds up `RozeniteLoader` itself: the default `period` drops from
3600ms to 2000ms per loop (~24 fps of visible dither flips with the
default `frames`, up from ~13). Callers can pass `period` to keep the
old pace.
## Related Issue
<!--- This project only accepts pull requests related to open issues -->
No tracking issue — this is a follow-up to #446 (`RozeniteLoader`),
which this puts to use in the standalone app.
## Context
- **Fade, then unmount.** The splash renders at `opacity-100`,
transitions to `opacity-0`, and unmounts a full transition later, so it
animates off rather than popping. Under `prefers-reduced-motion` the
transition is dropped and it simply disappears at the end of that
window.
- **Minimum visible time (450 ms).** A device on the same machine can
connect in a couple of hundred milliseconds; without a floor, the splash
blinks in and straight back out, which reads as a glitch. The cost is a
short hold on the fastest connections.
- **`z-40`, under `Dialog`'s `z-50`.** That's what lets the dialogs
render over the splash — and keeps a dialog that comes up mid-fade from
being covered by the tail of the animation.
- **`aria-hidden`.** The splash is a purely visual cover; the footer's
status badge and the dialogs stay in the accessibility tree underneath,
so the loader must not duplicate them as a second status.
- **Never comes back.** Visibility is driven off the existing sticky
`shellMounted` latch, so a mid-session disconnect shows its dialog over
the still-mounted shell exactly as before, never over a re-shown splash.
- The pre-shell branch now renders only `WindowDragHandle`, which sits
above the splash so the Electron window can still be moved while it's
up.
- **Loader speed.** `period` is the only knob for it, and lower is
faster; 2000ms keeps the sprite-strip cache and per-frame `drawImage`
playback unchanged, so this costs nothing at runtime. It's a behavior
change for anyone already using the component at its default, hence its
own version plan.
## Testing
- `pnpm --filter @rozenite/app test` — 74 tests passed, including three
new `App.test.tsx` cases: the splash covers the connecting state, fades
(`opacity-0`) and then unmounts once the shell mounts, and stays opaque
behind a `rozeniteMissing` dialog.
- `pnpm checks:affected` — typecheck, lint, and format clean across 102
tasks.
- `pnpm test:affected` — 63 tasks passing.
Note: the last two ran against an earlier revision, before the splash
was switched to stay up behind the status dialogs and before the loader
default changed; the app tests above are from the final state. Not
verified manually against a real device.
---------
Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds
RozeniteLoaderto@rozenite/ui: a dithered gem loading spinner masked to the Rozenite logo silhouette, for on-brand loading states in DevTools plugin panels.cacheis on (default forsize <= 120), so steady-state playback is a singledrawImageper frame.prefers-reduced-motion.@rozenite/uialongside itsRozeniteLoaderPropstype.Related Issue
No issue was provided for this change; happy to open/link one if required before merge.
Context
The component and its docblock comments were trimmed to keep only the parts that explain non-obvious behavior (the perf model, caching, and reduced-motion handling) and drop restatements of what the code already makes clear.
Added a Storybook
Playgroundstory withcols,period, andnoiseexposed as ranged Controls so the dithering resolution, loop speed, and grain can be tuned live, plusDefault/Large/Pausedstories covering common usage.Added a changeset (
@rozenite/ui: minor) since this adds new publishable behavior.Testing
Automated:
git fetch origin mainpnpm checks:affected(lint + typecheck) — passedpnpm test:affected— passedManual: reviewed the story visually against the component's stated behaviors (grid mask, dithering, sizes, pause state).
Generated by Claude Code