# Plan 21-02 Summary: Skeleton Loading States

## Result: COMPLETE

**Duration:** ~3 min
**Commits:** 2

## What was done

1. **Created reusable Skeleton component** (`packages/web/src/components/Skeleton.tsx`)
   - Simple animated placeholder: `animate-pulse rounded-md bg-muted`
   - Accepts `className` prop merged via `cn()` for caller-controlled sizing
   - Named export for consistency with other components

2. **Dashboard skeleton loading** (`packages/web/src/components/InitiativeList.tsx`)
   - Replaced "Loading initiatives..." text with 3 skeleton cards
   - Each card matches InitiativeCard layout: title bar, status badge, progress bar, phase count

3. **Inbox skeleton loading** (`packages/web/src/routes/inbox.tsx`)
   - Replaced "Loading inbox..." text with skeleton header + 4 skeleton message rows
   - Each row matches MessageCard layout: status circle, name, preview text, timestamp

4. **Detail page skeleton loading** (`packages/web/src/routes/initiatives/$id.tsx`)
   - Initiative loading: header skeleton (back arrow, title, badge) + two-column grid (phase accordions, sidebar panels)
   - Phase loading: 3 skeleton accordion bars replacing "Loading phases..." text

## Decisions

- 21-02: Skeleton component in components/ (not ui/) — it's app-specific, not a shadcn primitive
- 21-02: Inbox skeleton lives in inbox.tsx route (not InboxList component) since InboxList receives pre-loaded data

## Verification

- [x] `npx tsc --noEmit -p packages/web/tsconfig.app.json` — passes (own files clean)
- [x] `npm run --workspace=packages/web build` — succeeds
- [x] No "Loading..." plain text remains in loading paths
- [x] Skeleton component exported and used consistently across all three screens
