Skip to content

feat: skeleton loading UIs to replace text-based loading states (closes #198)#321

Merged
mmogr merged 8 commits intomainfrom
feat/skeleton-loading-ui-198
Mar 7, 2026
Merged

feat: skeleton loading UIs to replace text-based loading states (closes #198)#321
mmogr merged 8 commits intomainfrom
feat/skeleton-loading-ui-198

Conversation

@mmogr
Copy link
Owner

@mmogr mmogr commented Mar 7, 2026

Closes #198

Summary

Replaces all text-based loading states ("Loading models...", "Loading conversations…", etc.) with polished shimmer skeleton UIs that mirror the real content layout, eliminating layout shift and making the app feel faster.

This PR covers Phase 1 (foundation) and Phase 2 (co-located templates). Phase 3 (wiring the skeletons into the live loading states) follows in a separate PR once this is reviewed.


Changes

Phase 1 — Foundation

src/styles/tailwind.css

  • Added @keyframes shimmer: background-position sweep (-200% 0200% 0) with linear easing for a smooth, continuous wave (not ease-in-out, which causes jarring acceleration)
  • Registered --animate-shimmer: shimmer 1.5s linear infinite in @theme inline
  • All colours use var(--color-surface-elevated) / var(--color-surface-hover) — zero hardcoded hex

src/components/primitives/Skeleton.tsx (new)

  • Props: variant (text | rect | circle), width, height, className, count
  • aria-hidden="true" on every root element — screen readers skip purely visual placeholders
  • Shimmer via animate-shimmer class + style={{ background: gradient, backgroundSize: '200% 100%' }}
  • count > 1 wraps N copies in the existing Stack primitive
  • Exported from primitives/index.ts

Phase 2 — Co-located Skeleton Templates

Each template lives beside the feature it mirrors (no central skeletons/ directory). All use Stack/Row primitives — no raw flex div soup.

File Mirrors Details
src/components/ModelLibraryPanel/ModelListSkeleton.tsx ModelsListContent rows 6 rows: py-md px-base border-b + name line (55%) + 3 badge rects
src/components/ConversationListPanel/ConversationListSkeleton.tsx Conversation list items 5 items: border rounded-base + title (70%) + timestamp (35%)
src/pages/ChatPageSkeleton.tsx ChatPage two-column layout Left 30% (header approx + ConversationListSkeleton) + right 70% (4 alternating message rects)
src/components/HuggingFaceBrowser/ModelCardSkeleton.tsx HF ModelCard rounded-xl card: name + id line + param badge + stats
src/components/ModelInspectorPanel/ModelInspectorSkeleton.tsx ModelInspectorPanel Header (name + 3 circle buttons) + 4 metadata rows + 3 tag pills + 2 action rects

Design decisions

  • linear easing: ease-in-out makes the wave visibly speed up/slow down on each loop — linear gives the continuous, even flow of a real shimmer
  • Co-location over centralisation: each skeleton template lives in the same directory as the feature it belongs to, keeping the codebase feature-isolated and DRY
  • aria-hidden="true": skeletons are purely visual; parent containers will use aria-busy in Phase 3 if needed
  • Skeleton in primitives/ (not ui/): it is a layout-level primitive, consistent with Card, Stack, Row

Verification

tsc --noEmit exits clean (EXIT:0) with all 7 commits.

mmogr added 7 commits March 8, 2026 00:26
Add @Keyframes shimmer using a background-position sweep (from -200% to 200%)
with linear easing for a smooth, continuous wave effect.
Register --animate-shimmer: shimmer 1.5s linear infinite in @theme inline.
All gradient colours reference var(--color-surface-*) tokens — no hardcoded hex.
Reusable shimmer placeholder primitive with:
- variant: 'text' | 'rect' | 'circle' (controls shape and defaults)
- width / height: CSS value overrides
- count: renders N stacked items via the Stack primitive
- aria-hidden on every root element (purely visual, screen-reader silent)
- Shimmer applied via animate-shimmer class + inline gradient style using
  var(--color-surface-elevated/hover) tokens for full dark-theme compatibility
Co-located skeleton template matching the ModelsListContent row layout:
6 rows of py-md px-base border-b with a name line and three badge rects.
Replaces 'Loading models...' text in Phase 3.
Co-located skeleton template matching the conversation list item layout:
5 items of border border-border rounded-base with a title line (70%)
and a relative-timestamp line (35%, 0.75em).
Replaces 'Loading conversations...' text in Phase 3.
Co-located skeleton template matching the ModelCard layout:
rounded-xl card with model name, id line, param badge, and download/like stats.
Used in Phase 3 to replace the hardcoded animate-spin spinner during HF search.
Co-located skeleton template matching the inspector panel layout:
- Header bar: model name rect + three circle icon-button placeholders
- Metadata section: section heading + 4 label:value rows
- Tags section: section heading + 3 rounded-full tag pill placeholders
- Actions row: two button-height rects
Shown in Phase 3 when loading and no model is selected yet (initial fetch).
Co-located skeleton template matching the ChatPage two-column layout:
- Left panel (30%): header approximation (tabs + model name + search) + ConversationListSkeleton
- Right panel (70%): 4 alternating message rects (self-start / self-end)
Used in Phase 3 as the Suspense fallback while the ChatPage JS chunk loads.
@mmogr mmogr added type: feature New functionality or enhancement component: frontend React/TypeScript UI ux User experience improvements priority: medium Should be done soon size: m 4-8 hours (half to full day) status: needs-review Waiting for PR review labels Mar 7, 2026
@mmogr mmogr self-assigned this Mar 7, 2026
@mmogr mmogr enabled auto-merge (squash) March 7, 2026 15:00
@mmogr mmogr merged commit f82a6d8 into main Mar 7, 2026
9 checks passed
@mmogr mmogr deleted the feat/skeleton-loading-ui-198 branch March 7, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: frontend React/TypeScript UI priority: medium Should be done soon size: m 4-8 hours (half to full day) status: needs-review Waiting for PR review type: feature New functionality or enhancement ux User experience improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add skeleton loading UIs to replace text-based loading states

1 participant