Summary
Two pages show plain text "Loading…" instead of skeletons, and the empty-state implementation is duplicated across the app. This is the "same state, two different renderings" inconsistency class.
Evidence
- Loading:
web/src/routes/admin/settings/+page.svelte:284 ({#if loading}Loading…{:else}…{/if}), web/src/routes/admin/calendar/+page.svelte:342 (<div class="calendar-loading">Loading calendar…</div>).
- Empty states: shared
EmptyState.svelte (shadcn Empty + icon, used by schedules + analytics) vs ad-hoc .empty-state div in lib/components/DataTable.svelte:112-118 (no icon, no action slot).
- Loading rows that are fine (keep as reference): accounts, comments, mentions, dashboard, analytics, media (Skeleton), DataTable (skeleton rows), schedules (skeleton cards).
Impact
- Layout shift + inconsistent look during loads; empty tables look different from empty card grids.
Fix
- Settings: skeleton the health stat + schedule count while loading.
- Calendar: skeleton grid cells while loading.
- DataTable empty branch renders the shared
EmptyState component (supports title/desc/action).
Acceptance criteria
Source: Titen UX audit 2026-09-20 (finding M3).
Summary
Two pages show plain text "Loading…" instead of skeletons, and the empty-state implementation is duplicated across the app. This is the "same state, two different renderings" inconsistency class.
Evidence
web/src/routes/admin/settings/+page.svelte:284({#if loading}Loading…{:else}…{/if}),web/src/routes/admin/calendar/+page.svelte:342(<div class="calendar-loading">Loading calendar…</div>).EmptyState.svelte(shadcnEmpty+ icon, used by schedules + analytics) vs ad-hoc.empty-statediv inlib/components/DataTable.svelte:112-118(no icon, no action slot).Impact
Fix
EmptyStatecomponent (supports title/desc/action).Acceptance criteria
EmptyStatecomponent.Source: Titen UX audit 2026-09-20 (finding M3).