mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 KiB
13 KiB
v1.4.15 — Empty-states audit
Date: 2026-05-09
Owner: Stage C5 work-stream (parallel with C1 + C4).
Primitive: src/components/ui/empty-state.tsx (already shipped earlier
in v1.4.15; sweep below tracks adoption).
The audit walks every list / table / tile surface a brand-new user hits, classifies the empty path, and records the fix. "Useful" means icon + title + description + (where it makes sense) a primary action, not a single grey sentence floating in a card.
Findings
| Route / surface | Component | State before | What was wrong | What changed |
|---|---|---|---|---|
/admin/users |
UserManagementSection |
Loading-only — when users resolved to [] the table simply rendered an empty <tbody> with the header still visible. |
Empty case never specified; on a fresh deployment with one admin self-row it never reproduced for the live tenant, but the admin/user filter did regularly. |
New <EmptyState> mounts inside the card when filteredUsers.length === 0, with Users icon, filter-aware copy, and a "Reset filter" CTA when a filter is active. |
/admin/backups |
BackupsSection |
Plain <p>No backups recorded yet.</p>. |
No icon, no CTA, no signal that the "Run backup now" button at the top is what to click. | Replaced with <EmptyState> (Database icon) that includes a primary "Backup now" button — duplicates the header CTA but inside the empty card so the new admin doesn't need to look up. |
/admin/login-overview |
LoginOverviewSection |
Plain <p>No entries found.</p> (filter-dependent). |
When "Failed only" is selected on a healthy installation the user sees nothing — looked like a bug. | <EmptyState> with ScrollText icon, filter-aware description, and a "Show all events" CTA when the failed-only filter is active. |
/admin/api-tokens |
ApiTokenOverviewSection |
Plain <p>No API tokens found.</p>. |
Doesn't tell admins that tokens are issued at login from native clients — they wonder if they need to create them somewhere. | <EmptyState> with Key icon and a description pointing to native sign-in / Settings → Account API tokens. |
/admin/feedback |
FeedbackInboxSection |
Plain <p>No feedback in this category.</p> per tab. |
OK as text but the tab strip already shows 0 badges; the inner panel deserves the same compactness as other admin sections. |
<EmptyState size="compact"> with Inbox icon. Per-tab description. |
/admin overview |
RecentAuditPreview |
Plain <p>No recent audit entries.</p>. |
Compact but icon-less — visually inconsistent with the audit-table fixes. | <EmptyState size="compact" variant="plain"> with ScrollText icon. |
/measurements |
MeasurementList |
<div class="border-dashed">{t("measurements.noMeasurements")}</div>. |
No CTA and the top-level "Add measurement" button is the only way out. | <EmptyState> with Activity icon, filter-aware description, "Add your first measurement" CTA. |
/mood |
MoodList |
<div class="border-dashed">{t("mood.noEntries")}</div>. |
No CTA. | <EmptyState> with Smile icon, filter-aware description, "Log your first mood" CTA. |
/medications |
MedicationsPage |
Custom inline icon + button block. | Already friendly but didn't share the new primitive — diverging styles. | Refactored to <EmptyState> with Pill icon, "Add first medication" CTA. Visual parity with the rest. |
/insights (top-level) |
InsightsPage |
Plain <div class="text-muted-foreground py-20 text-center">{t("common.noData")}</div> when data is undefined. |
Looked like a hung page. | <EmptyState> with TrendingUp icon, "Log a measurement" CTA → /measurements. |
/insights (BMI no-height) |
Inline <p> |
"no data" when user.heightCm == null. |
The user might wonder why the BMI section is empty. | <EmptyState size="compact" variant="plain"> with Ruler icon, copy points to /settings/account and a "Set height" CTA. |
/achievements |
AchievementsPage (no-grouped state) |
Plain <p>No achievements unlocked yet.</p> inside a card. |
Icon-less. The card after summary tiles needs the same visual cue we use elsewhere. | <EmptyState> with Trophy icon — keeps the same noneUnlockedYet string to avoid C4 i18n churn. |
| Dashboard tile-strip / charts | DashboardPage |
Tile strip + chart row both hidden when every metric has zero data; only <GettingStartedChecklist> plus the welcome banner show. The strip itself stays a 0-px row. |
Brand-new users see a near-blank page below the welcome banner — feels broken even though the checklist is technically present. | New <EmptyState> block rendered when trendCards.length === 0 && charts.length === 0. Routed to "Add your first measurement" via the existing quick-entry dialog (no nav). Does not race the checklist (which has its own self-gating). |
Out of scope (deferred)
/insightscorrelation-card empty branches (scatterData.length < 5) already use a custom in-card empty surface (icon + 2 lines) that's tighter than the generic primitive — refactor would only churn classes, no UX win. Leave for v1.4.16 if C4 i18n sweep wants the consistency./admin/danger-zoneand/admin/servicesare not lists — irrelevant./admin/system-statusshows operational data not user records — empty paths handled by per-card "—" + status text./admin/remindersis a settings form, not a list.- Dashboard
/insightsBMI<p>{t("common.noData")}</p>for a user with height but no weight is intentionally minimal — the chart itself shows the empty axis. - Tab strip
0badges on/admin/feedbackuse the same<EmptyState size="compact">per panel; not duplicating in this audit.
Tests added
src/app/__tests__/measurement-list-empty.test.tsx— confirms<EmptyState>renders with the "Log your first measurement" CTA on a fresh account (mocked TanStack Query result of{ measurements: [], meta: { total: 0 }}).src/components/admin/__tests__/users-section-empty.test.tsx— confirms the user-management empty state renders the filter-aware description and a "Reset filter" action when the admin filter is active.src/components/admin/__tests__/backups-section-empty.test.tsx— confirms the backups empty state surfaces the secondary "Backup now" CTA inside the empty card.- Existing
src/components/ui/__tests__/empty-state.test.tsxcovers the primitive (4 variants).
Counts
- Empty-states added or upgraded: 13.
- admin sub-routes: 6 (users, backups, login-overview, api-tokens, feedback, overview-preview).
- feature lists: 4 (measurements, mood, medications, achievements).
- insights: 2 (top-level no-data, BMI no-height).
- dashboard: 1 (tile-strip + charts both empty).
- i18n keys added: 18 in EN + DE under
emptyStates.*(1),measurements.empty.*(4),mood.empty.*(4),medications.empty.*(3),admin.section.users.empty.*(3),admin.section.backups.empty.*(2),admin.section.login-overview.empty.*(3),admin.section.api-tokens.empty.*(2),admin.section.feedback.empty.*(2),admin.overview.auditEmpty*(1 reuses existing key + new description),insights.empty.*(3),dashboard.empty.*(3),achievements.empty.*(reusesnoneUnlockedYet). - No new dependencies.
Verification
pnpm testafter each commit (target: 0 regressions).pnpm typecheckafter each commit.pnpm lintafter each commit.
Reference
Stage C5 work order: .planning/STATE.md § Stage C5.
Primitive intro PR: see git log for feat(ui): generic EmptyState primitive (already on main before this stage started — section 1 of
the C5 brief satisfied without a new commit).