archon-async-loading
Display driver — activated when editing UI components.
Standardized async data loading: skeleton screens, 3-state display, error retry, viewport lazy loading.
Skeleton Screens
- Every async section MUST show a skeleton placeholder while loading
- Skeleton height/width should approximate real content dimensions to minimize CLS
- Use a shared
<Skeleton />component — do not inline shimmer styles
Three-State Value Display
Values fed by async data MUST handle 3 states:
| State | Display |
|---|---|
loading | <Skeleton /> (matches value dimensions) |
error | "—" or error indicator |
resolved | Formatted value |
Create a utility like kpiVal(value, isLoading, isError) to enforce consistency.
Error + Retry
Each independent data section MUST handle errors independently:
- Display the error inline within the failed section, not as a full-page error
- Provide a retry button that calls
refetch()or re-invokes the request - Other sections continue loading/displaying normally
Viewport-Triggered Loading
Off-screen sections SHOULD defer API requests until scrolled into view:
- Use
IntersectionObserver(or auseInViewhook) withrootMarginof~200px - Once loaded, data stays cached — no re-request on scroll-out/scroll-in
- Combine with query
skipparameter:skip: !inView
Examples
Correct: independent section with 3-state display
tsx
function RevenueCard() {
const { ref, inView } = useInView({ rootMargin: "200px" });
const { data, isLoading, isError, refetch } = useGetRevenue({ skip: !inView });
return (
<Card ref={ref}>
{isLoading ? <Skeleton width={120} height={32} /> :
isError ? <ErrorRetry onRetry={refetch} /> :
<span>{formatCurrency(data.total)}</span>}
</Card>
);
}Correct: page with independent error boundaries
tsx
function Dashboard() {
return (
<Page>
<RevenueCard /> {/* fails independently */}
<UsersCard /> {/* fails independently */}
<OrdersChart /> {/* fails independently */}
</Page>
);
}Prohibitions
- ❌ Single API failure crashes the entire page — wrap each section with its own
isError/refetchhandling - ❌ Showing
0or"No data"while actually still loading — use skeleton instead - ❌ Firing all API calls on mount via
useEffectregardless of scroll position — useskip: !inViewfor off-screen sections
Battle-tested Prohibitions
Project-specific prohibitions from production incidents. Format: ❌ <pattern> — <fix> [INCIDENT]: <what broke>
(empty — populated via Stage 3.6 → proposed-rules.md → user approval)