Skip to content

feat(ui): add RozeniteLoader component - #446

Merged
V3RON merged 1 commit into
mainfrom
claude/rozeniteloader-ui-package-1ss522
Aug 21, 2026
Merged

feat(ui): add RozeniteLoader component#446
V3RON merged 1 commit into
mainfrom
claude/rozeniteloader-ui-package-1ss522

Conversation

@V3RON

@V3RON V3RON commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

Adds RozeniteLoader to @rozenite/ui: a dithered gem loading spinner masked to the Rozenite logo silhouette, for on-brand loading states in DevTools plugin panels.

  • Ordered (Bayer 4×4) dithering over an animated light field, deterministic and seamlessly looping.
  • Time is quantized to the dither frame grid so redraws happen well below 60fps, and identical frames are skipped.
  • Pre-renders the loop into an offscreen sprite strip when cache is on (default for size <= 120), so steady-state playback is a single drawImage per frame.
  • Pauses when the tab is hidden or the canvas leaves the viewport, and renders a static frame under prefers-reduced-motion.
  • Exported from @rozenite/ui alongside its RozeniteLoaderProps type.

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 Playground story with cols, period, and noise exposed as ranged Controls so the dithering resolution, loop speed, and grain can be tuned live, plus Default/Large/Paused stories covering common usage.

Added a changeset (@rozenite/ui: minor) since this adds new publishable behavior.

Testing

Automated:

  • git fetch origin main
  • pnpm checks:affected (lint + typecheck) — passed
  • pnpm test:affected — passed

Manual: reviewed the story visually against the component's stated behaviors (grid mask, dithering, sizes, pause state).


Generated by Claude Code

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
V3RON merged commit 40a8ccd into main Aug 21, 2026
4 checks passed
@V3RON
V3RON deleted the claude/rozeniteloader-ui-package-1ss522 branch August 21, 2026 19:35
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>
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