ux(loading): make every loading state match the content it replaces - #447
ux(loading): make every loading state match the content it replaces#447yhabib wants to merge 1 commit into
Conversation
The app had six ways to show a load. Three of them showed generic grey bars that did not match the page behind them. Replace them with one set of content shaped shells, move the anti flicker delay into CSS so every skeleton gets it, and route the raw spinner icons through the shared `Spinner`.
|
✅ No security or compliance issues detected. Reviewed everything up to 134b698. Security Overview
Detected Code Changes
|
📊 Build Bundle StatsThe latest build generated the following assets: |
There was a problem hiding this comment.
Pull request overview
This PR standardizes loading/processing UI across the governance app frontend by replacing ad-hoc skeletons and loader icons with route-aligned skeleton screens, shared list/card/row skeleton primitives, and shared spinner components—while also improving accessibility (single loading announcement per region) and reducing flicker via a delayed skeleton reveal in CSS.
Changes:
- Introduces route-shaped skeleton screens (and shared skeleton primitives) and wires them as
pendingComponent/loadingComponentacross major authenticated routes and dialogs. - Moves the anti-flicker delay from React logic into the
.skeletonCSS utility; removesuseDelayedFlagandMultipleSkeletons, and updatesQueryStatesdefaults accordingly. - Replaces various
Loader/Loader2usages with the sharedSpinnerand consolidates mutation “processing” UI intoProcessingSpinner; adds reduced-motion behavior for skeleton pulsing.
Reviewed changes
Copilot reviewed 58 out of 58 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/governance-app-frontend/src/routes/_auth/voting/proposals/$id/index.tsx | Uses proposal-detail skeletons for route pending + query loading state. |
| src/governance-app-frontend/src/routes/_auth/voting/index.tsx | Uses voting and proposal-list skeletons for route pending + list loading/infinite-scroll loading. |
| src/governance-app-frontend/src/routes/_auth/settings/index.tsx | Adds a route-level settings pending skeleton. |
| src/governance-app-frontend/src/routes/_auth/neurons/index.tsx | Adds a route-level neurons pending skeleton and query loading skeleton. |
| src/governance-app-frontend/src/routes/_auth/dashboard/index.tsx | Adds a route-level dashboard pending skeleton. |
| src/governance-app-frontend/src/routes/_auth/accounts/index.tsx | Adds a route-level accounts pending skeleton. |
| src/governance-app-frontend/src/features/voting/components/VotingOverviewSimple.tsx | Updates loading UI to use shared skeleton screen + page header skeleton. |
| src/governance-app-frontend/src/features/voting/components/VotingOverviewAdvanced.tsx | Updates overview loading UI to use shared skeleton screen + page header skeleton; refines row skeleton sizing. |
| src/governance-app-frontend/src/features/voting/components/SimpleFollowingModal.tsx | Replaces inline picker-row skeletons with shared SkeletonPickerRows. |
| src/governance-app-frontend/src/features/voting/components/KnownNeuronCard.tsx | Replaces Loader2 with shared Spinner for loading state. |
| src/governance-app-frontend/src/features/voting/components/FolloweePicker.tsx | Replaces inline picker-row skeletons with shared SkeletonPickerRows. |
| src/governance-app-frontend/src/features/voting/components/FollowedNeuronCard.tsx | Adjusts skeleton width for loading label. |
| src/governance-app-frontend/src/features/voting/components/AdvancedFollowingModal.tsx | Replaces inline topic-row skeletons with SkeletonTopicRows and swaps Loader2 for Spinner in the action button. |
| src/governance-app-frontend/src/features/transactions/components/TransactionListDialog.tsx | Replaces generic skeletons with a transaction-list-shaped skeleton component. |
| src/governance-app-frontend/src/features/stakes/components/summaryCards/EngagementCard.tsx | Adjusts skeleton sizing/spacing to match final content height more closely. |
| src/governance-app-frontend/src/features/stakes/components/summaryCards/EarningsCard.tsx | Adjusts skeleton height to better match loaded typography. |
| src/governance-app-frontend/src/features/stakes/components/summaryCards/CapitalCard.tsx | Prevents hiding already-available data; shows only USD line as skeleton while ticker loads. |
| src/governance-app-frontend/src/features/stakes/components/stakingWizard/StakingWizardStepConfirmation.tsx | Replaces custom animated loader with shared ProcessingSpinner. |
| src/governance-app-frontend/src/features/stakes/components/StakedCard.tsx | Adjusts skeleton heights to reduce layout shift. |
| src/governance-app-frontend/src/features/stakes/components/neuronDetail/NeuronDetailMaturityModeView.tsx | Replaces Loader2 with shared Spinner during mutation pending state. |
| src/governance-app-frontend/src/features/stakes/components/neuronDetail/NeuronDetailIncreaseStakeView.tsx | Replaces Loader2 with shared Spinner during mutation pending state. |
| src/governance-app-frontend/src/features/stakes/components/neuronDetail/NeuronDetailIncreaseDelayView.tsx | Replaces Loader2 with shared Spinner during mutation pending state. |
| src/governance-app-frontend/src/features/stakes/components/neuronDetail/NeuronDetailDissolveView.tsx | Replaces Loader2 with shared Spinner during mutation pending state. |
| src/governance-app-frontend/src/features/stakes/components/FollowingStatusAlert.tsx | Replaces Loader2 with shared Spinner during mutation pending state. |
| src/governance-app-frontend/src/features/proposals/components/ProposalListItem.tsx | Replaces Loader2 with shared Spinner for in-progress vote actions. |
| src/governance-app-frontend/src/features/proposals/components/ProposalDetailsVoting.tsx | Replaces Loader2 with shared Spinner for in-progress vote actions. |
| src/governance-app-frontend/src/features/dashboard/components/TotalStakedCard.tsx | Keeps card height stable by separating skeleton/value from the caption line. |
| src/governance-app-frontend/src/features/dashboard/components/IcpPriceCard.tsx | Keeps card height stable while ticker change row loads; refines skeleton placement. |
| src/governance-app-frontend/src/features/dashboard/components/DailyRewardsCard.tsx | Keeps card height stable while last reward row loads. |
| src/governance-app-frontend/src/features/addressBook/components/AddressBookModal.tsx | Uses shared address-book row skeletons for the loading state. |
| src/governance-app-frontend/src/features/accounts/components/RecentTransactions.tsx | Uses shared compact transaction-row skeletons for loading. |
| src/governance-app-frontend/src/features/accounts/components/AccountsTotalCard.tsx | Adjusts skeleton heights to better match loaded typography. |
| src/governance-app-frontend/src/features/accounts/components/AccountsListItem.tsx | Adjusts skeleton height for balance subtitle line. |
| src/governance-app-frontend/src/features/accounts/components/AccountsList.tsx | Switches to shared account-card skeletons and wraps in SkeletonScreen. |
| src/governance-app-frontend/src/features/accounts/components/AccountsCard.tsx | Tweaks skeleton sizing/consistency for account summary card. |
| src/governance-app-frontend/src/features/account/components/AccountCard.tsx | Adjusts skeleton height for balance subtitle line. |
| src/governance-app-frontend/src/common/hooks/useDelayedFlag.ts | Removes hook (anti-flicker now handled via CSS delayed skeleton reveal). |
| src/governance-app-frontend/src/common/constants/extra.ts | Removes LOADING_INDICATOR_DELAY constant. |
| src/governance-app-frontend/src/common/components/skeletons/VotingSkeleton.tsx | Adds route-shaped voting skeleton composition. |
| src/governance-app-frontend/src/common/components/skeletons/SkeletonTransactionList.tsx | Adds list + compact-row transaction skeletons. |
| src/governance-app-frontend/src/common/components/skeletons/SkeletonText.tsx | Adds reusable paragraph-like skeleton text blocks. |
| src/governance-app-frontend/src/common/components/skeletons/SkeletonStatCard.tsx | Adds reusable stat-card skeleton used by dashboard/neurons. |
| src/governance-app-frontend/src/common/components/skeletons/SkeletonScreen.tsx | Adds a single “loading” status region wrapper for skeleton groups. |
| src/governance-app-frontend/src/common/components/skeletons/SkeletonPageHeader.tsx | Adds page-header skeleton matching PageHeader layout. |
| src/governance-app-frontend/src/common/components/skeletons/SkeletonListRows.tsx | Adds reusable skeleton row sets for address book, pickers, and topic rows. |
| src/governance-app-frontend/src/common/components/skeletons/SkeletonAccountCard.tsx | Adds account-card-shaped skeleton to match account list items. |
| src/governance-app-frontend/src/common/components/skeletons/SettingsSkeleton.tsx | Adds route-shaped settings skeleton composition. |
| src/governance-app-frontend/src/common/components/skeletons/ProposalListSkeleton.tsx | Adds proposal-list skeleton that matches proposal card layout. |
| src/governance-app-frontend/src/common/components/skeletons/ProposalDetailSkeleton.tsx | Adds proposal-detail skeleton composition (page + content variants). |
| src/governance-app-frontend/src/common/components/skeletons/NeuronsSkeleton.tsx | Adds neurons page + content skeleton compositions. |
| src/governance-app-frontend/src/common/components/skeletons/DashboardSkeleton.tsx | Adds dashboard page skeleton composition. |
| src/governance-app-frontend/src/common/components/skeletons/AccountsSkeleton.tsx | Adds accounts page skeleton composition. |
| src/governance-app-frontend/src/common/components/Skeleton.tsx | Updates skeleton bars to use .skeleton styling and marks them aria-hidden. |
| src/governance-app-frontend/src/common/components/QueryStates.tsx | Removes delayed-flag logic; relies on CSS-delayed skeleton reveal and updates default loading fallback. |
| src/governance-app-frontend/src/common/components/ProcessingSpinner.tsx | Introduces shared mutation “processing” spinner halo component. |
| src/governance-app-frontend/src/common/components/MutationDialog.tsx | Replaces dialog-specific animated spinner with shared ProcessingSpinner. |
| src/governance-app-frontend/src/common/components/MultipleSkeletons.tsx | Removes old generic multi-bar skeleton helper. |
| src/governance-app-frontend/src/app/styles/main.css | Adds skeleton color tokens and delayed reveal/pulse animations; disables pulse under reduced motion. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| <div | ||
| data-slot="skeleton" | ||
| className={cn('animate-pulse rounded-md bg-accent dark:bg-muted-foreground/20', className)} | ||
| aria-hidden={true} | ||
| className={cn('skeleton rounded-md', className)} | ||
| {...props} | ||
| /> |
| if (q.isLoading) { | ||
| // Hold the frame empty until the delay elapses rather than flashing a skeleton. | ||
| return showLoading ? loadingComponent || <MultipleSkeletons count={3} /> : null; | ||
| // The skeleton holds the space at once and reveals itself after a short | ||
| // delay, so a fast query needs no hold-back here. See `.skeleton` in | ||
| // `main.css`. Pass a `loadingComponent` built from `Skeleton` to keep that. | ||
| return loadingComponent || <SkeletonText lines={3} />; | ||
| } |
Motivation
The app had six ways to show a load. Three of them showed the same three grey bars whatever was behind them: the
/votingroute, the proposal page, and the transaction list./dashboard,/neurons,/accountsand/settingshad no pending state at all, so they showed a blank frame. The anti flicker delay only ran insideQueryStates, so the 28 files with inline skeletons flashed on every fast query.Changes
skeletons/set of content shaped shells, one per route plus the list, card, and row shapes they share, and wired them aspendingComponenton all six routes..skeletonclass, so every skeleton holds its space from the first frame and reveals itself late. RemoveduseDelayedFlagandMultipleSkeletons.CapitalCard,IcpPriceCard,TotalStakedCard,DailyRewardsCard, and the account and stakes cards.aria-hiddenand added onerole="status"per region, so a screen reader hears "loading" once instead of nothing.Loader2andLoadericons to the sharedSpinner, and the two processing halos to oneProcessingSpinner.animate-pulseunderprefers-reduced-motion, and left spinners turning.