Skip to content

fluid-loading: dot matrix loaders everywhere#48

Merged
x0ba merged 1 commit into
stagingfrom
fluid-loading/dotmatrix-loaders
May 28, 2026
Merged

fluid-loading: dot matrix loaders everywhere#48
x0ba merged 1 commit into
stagingfrom
fluid-loading/dotmatrix-loaders

Conversation

@x0ba
Copy link
Copy Markdown
Owner

@x0ba x0ba commented May 28, 2026

Stack Context

Improves perceived performance and visual consistency by using the dot matrix animation everywhere the app would otherwise show a blank screen, skeleton, or generic spinner.

Why?

Loading states were inconsistent — pulse skeletons, CSS spinners, and empty content during navigation. The dot matrix component gives a single branded loader for route transitions, auth, bootstrap, and in-flight actions.

What

  • Add shared DotMatrixLoader with page, content, panel, and inline variants
  • Wire route navigation loaders via SvelteKit navigating state (root + app shell)
  • Add bootstrap dot grid in app.html until the app hydrates
  • Replace auth skeletons/spinners and run-page action spinners with dot matrix

Test plan

  • Hard refresh — bootstrap loader fades once app mounts
  • Navigate Runs → Run detail — sidebar stays, content area shows dot matrix
  • Navigate landing → Sign in — full-page dot matrix during Clerk load
  • Start run / Connect ChatGPT / Evaluate run — inline dot matrix in buttons and panels

Replace spinners and skeleton placeholders with the shared dot matrix component during navigation, auth, bootstrap, and async actions.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flightlog Ready Ready Preview, Comment May 28, 2026 7:04pm

@x0ba x0ba changed the title Use dot matrix loader for all loading states. fluid-loading: dot matrix loaders everywhere May 28, 2026
@x0ba x0ba marked this pull request as ready for review May 28, 2026 19:02
Copy link
Copy Markdown
Owner Author

x0ba commented May 28, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

Greptile Summary

This PR introduces a unified DotMatrixLoader component system and wires it everywhere the app previously showed inconsistent loading states (pulse skeletons, CSS spinners, blank screens).

  • Adds a self-contained dot-matrix UI library (dotmatrix-core.ts, dotmatrix-base.svelte, dotm-square-4.svelte, dotmatrix-hooks.svelte.ts, dotmatrix-loader.css) with four display variants (page, content, panel, inline) and full reduced-motion support.
  • Replaces auth skeleton/spinner components and in-flight action spinners with the new DotMatrixLoader inline variant; adds a route-navigation loader that distinguishes within-app transitions (overlay) from cross-boundary transitions (full-page), driven by SvelteKit's navigating rune.
  • Ships a static HTML boot loader in app.html that fades out once app-ready is added to <html> by the root layout $effect, covering the SSR → hydration gap.

Confidence Score: 4/5

Safe to merge — changes are purely visual and additive; no data paths, auth logic, or API contracts are touched.

The loading-state system is well-structured and the reduced-motion, SSR, and browser-guard cases are all handled. The only items worth a second look are a dotStyle variable that shadows an imported function of the same name (cosmetic, no runtime impact) and the aria-hidden wrappers on the navigation loader overlays that silence the inner role="status" live regions for screen readers.

src/lib/components/ui/dotmatrix/dotmatrix-base.svelte (variable shadowing) and src/lib/components/route-navigation-loader.svelte (aria-hidden suppressing live regions)

Important Files Changed

Filename Overview
src/app.html Adds inline boot-loader overlay with 5×5 dot grid; fades out when app-ready class is added. 25 spans match the 25 nth-child CSS rules. Reduced-motion handled. No issues found.
src/lib/components/route-navigation-loader.svelte Navigation-scoped loader using $app/state navigating rune. Logic correctly partitions app vs root scopes across all existing routes. Both overlay wrappers carry aria-hidden="true", silencing the inner role="status" live region for AT.
src/lib/components/ui/dotmatrix/dotmatrix-base.svelte Core rendering component; imports dotStyle function then declares a local const dotStyle: DotStyle inside the $derived.by() callback, shadowing the import in that scope. Works at runtime but is confusing and may trigger lint warnings.
src/lib/components/ui/dotmatrix/dotmatrix-core.ts Pure math utilities and pattern-index builders; all boundary cases handled. rowWaveNormFromIndex correctly uses the real max (29, not 24) because col 2 is deliberately visited twice.
src/lib/components/dotmatrix-loader.svelte Clean 4-variant wrapper; presets are as const so TypeScript enforces valid keys. ariaLabel defaults to label when omitted. No issues.
src/routes/(app)/runs/[id]/+page.svelte Replaces LoaderCircle spinners with inline DotMatrixLoader. All action buttons (Evaluate, Approve, Reject) have aria-busy set. No issues.
src/routes/(app)/runs/+page.svelte Start-run and Connect-ChatGPT buttons updated with inline loaders and aria-busy. Waiting-for-authorization text gains an inline loader. No issues.
src/routes/+layout.svelte Adds app-ready class to <html> via $effect (browser-only) to fade out the boot loader, and mounts the root-scoped navigation loader. Correct and minimal.
src/routes/layout.css Adds --color-dot-on CSS variable in both :root (raw --primary) and dark-mode alias block (--color-primary). The app is dark-only so the second definition is always active; no conflict.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Page Load] --> B[app.html boot-loader visible]
    B --> C{SvelteKit hydrates?}
    C -- yes --> D[root +layout $effect adds app-ready]
    D --> E[boot-loader CSS transition: opacity 0 / visibility hidden]

    F[User clicks link] --> G{navigating.to set?}
    G -- no --> H[No loader]
    G -- yes --> I{isAppPath from AND to?}
    I -- both app --> J[RouteNavigationLoader scope=app\ncontent overlay over sidebar]
    I -- at least one non-app --> K[RouteNavigationLoader scope=root\nfull-page overlay]
    J --> L[Navigation complete - loader unmounts]
    K --> L

    M[Action button click] --> N{loading state true?}
    N -- yes --> O[DotMatrixLoader variant=inline inside button]
    N -- no --> P[Icon rendered]
Loading

Fix All in Cursor Fix All in Codex

Reviews (1): Last reviewed commit: "Use dot matrix loader for all loading st..." | Re-trigger Greptile

Comment on lines +26 to +35
<div
class="absolute inset-0 z-10 flex items-center justify-center bg-background/78 backdrop-blur-[1px]"
aria-hidden="true"
>
<DotMatrixLoader variant="content" />
</div>
{:else}
<div class="fixed inset-0 z-50 bg-background" aria-hidden="true">
<DotMatrixLoader variant="page" />
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Navigation loader live region silenced by aria-hidden

Both overlay wrappers carry aria-hidden="true", which hides the inner DotMatrixBase's role="status" / aria-live="polite" from assistive technology. Screen readers will not announce the loading state on navigation. SvelteKit does announce page changes via the route announcer, but that fires after navigation completes — there is no in-progress "Loading…" callout for AT users. Consider moving aria-hidden to the decorative dot grid only, and letting the role="status" container remain accessible, or adding a visually-hidden live region outside the aria-hidden wrapper.

Fix in Cursor Fix in Codex

Comment on lines +162 to +193
const dotStyle: DotStyle = {
width: dotSize,
height: dotSize,
'--dmx-distance': distance,
'--dmx-row': row,
'--dmx-col': col,
'--dmx-x': `${deltaX}px`,
'--dmx-y': `${deltaY}px`,
'--dmx-angle': angle,
'--dmx-radius': radiusNormalizedValue,
'--dmx-manhattan': manhattan,
...stylePatch,
...(!isActive
? {
opacity: 0,
visibility: 'hidden',
pointerEvents: 'none',
animation: 'none'
}
: {})
};

return {
index,
class: cn(
'dmx-dot',
!isActive && 'dmx-inactive',
isBloomDot && 'dmx-bloom-dot',
dotClassName,
animationState.className
),
style: dotStyle
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 dotStyle variable shadows the imported function

The local const dotStyle: DotStyle = { … } declared inside the $derived.by() loop shares its name with the dotStyle function imported from ./dotmatrix-core.js. The scopes are different so the runtime result is correct — dot.style holds the object, and the template calls the imported function — but the collision makes the code hard to follow and will trigger no-shadow lint warnings. Renaming the local variable to something like dotCellStyle would remove the ambiguity.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Codex

@x0ba x0ba merged commit 8e2687c into staging May 28, 2026
5 checks passed
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.

1 participant