Components
Loading States
Peri is local-first and renders instantly, so it has no skeletons. Loading UI exists only where real waits exist: Drive sync, wearable import, and PDF export. Don't fake latency.
The three real waits
Background Drive syncSyncing…
Importing wearable data
Night 14 of 30 · sleep stages
Generating PDF…
| Wait | Surface | Blocking? |
|---|---|---|
| Google Drive sync | sync dot pulse + caption in the top bar | No — background, app fully usable |
| Wearable import | SyncProgressModal with progress feedback | Yes — data integrity during import |
| PDF export | “Generating PDF” interstitial → “Export ready” sheet (file card, share/download, Done) | Yes — short, with a completion artifact |
The no-skeleton rule
Engines compute synchronously from local data — there is nothing to wait for on render. A skeleton would advertise latency the product doesn't have.
If a future surface gains real async
Inherit the rule: show progress only for the actual wait, name what is happening (“Night 14 of 30”), and keep the rest of the app readable. Blocking is reserved for data-integrity operations.
Do
- ·Name the operation and its progress concretely
- ·Keep background syncs in the status dot, out of the way
- ·End blocking waits with a tangible artifact (the PDF card)
Don't
- ·Add skeletons or shimmer to local-first surfaces
- ·Show spinners without labels
- ·Block the dashboard for background syncs
Accessibility
- —Progress modals announce updates via
aria-live; the spinner alone is decorative. - —Blocking modals trap focus and explain why they block.