♻️ refactor(frontend): split HealthDashboard into smart wrapper + dumb View for state-based stories #33

Merged
arcodange merged 1 commits from feat/healthdashboard-stateful-stories into main 2026-05-03 17:55:48 +02:00
Owner

Summary

Follow-up to PR #30 (Storybook) and PR #32 (Playwright fix). Splits HealthDashboard.vue into:

  1. HealthDashboardView.vue (new, dumb / pure / presentational) — accepts data, pending, error as props
  2. HealthDashboard.vue (refactored into a smart wrapper) — does the useFetch('/api/healthz') and forwards state to View

Why

PR #30 left a documentation note on HealthDashboard.stories.ts saying state-based stories needed a future prop refactor. PR #32 added a regression test mocking /api/healthz to 502, but the component itself still couldn't be Storybook-previewed in Loading/Error states because the data fetching was hardcoded internally.

This splits per SRP (data layer ↔ presentation layer), enabling:

  • 4 Storybook stories on the View (Healthy / Loading / ErrorState / HealthyHighUptime)
  • Unit-testing each rendering branch without mocking the Nuxt fetch layer
  • Reusing the View in different contexts (e.g. embedded in admin dashboards, status pages)

What's preserved

  • pages/index.vue still imports <HealthDashboard /> (unchanged).
  • All existing data-testid attributes preserved (health-dashboard, health-info, health-status).
  • Playwright tests from PR #32 continue to pass without modification.

What's added

  • New testids on the View for fine-grained targeting: health-loading, health-error.
  • shims-vue.d.ts: Vue 3 file module declaration with permissive DefineComponent<any,any,any> typing. Required because the strict TS + Storybook combo otherwise generates false-positive TS2353 errors on prop typing through .vue imports.

Architecture notes (per code-reviewer skill SOLID/DDD section)

  • SRP : data fetching and rendering separated into 2 files
  • OCP : the View can be reused with different data sources (mock, real, polled, websocket)
  • DDD modular : the wrapper is the "adapter" boundary between Nuxt's runtime and the pure UI; matches the Hexagonal architecture pattern
  • Cognitive load : each file < 30 lines, naming explicit (HealthDashboard = composite, HealthDashboardView = view-only)

Test plan

  • npx tsc --noEmit passes
  • CI passes
  • Storybook shows 4 stories on HealthDashboardView (manual)
  • Playwright e2e (existing) still passes (manual)

🤖 Co-Authored-By: Claude Opus 4.7 (1M context)

## Summary Follow-up to PR #30 (Storybook) and PR #32 (Playwright fix). Splits `HealthDashboard.vue` into: 1. **`HealthDashboardView.vue`** (new, **dumb / pure / presentational**) — accepts `data`, `pending`, `error` as props 2. **`HealthDashboard.vue`** (refactored into a **smart wrapper**) — does the `useFetch('/api/healthz')` and forwards state to View ## Why PR #30 left a documentation note on `HealthDashboard.stories.ts` saying state-based stories needed a future prop refactor. PR #32 added a regression test mocking /api/healthz to 502, but the component itself still couldn't be Storybook-previewed in Loading/Error states because the data fetching was hardcoded internally. This splits per **SRP** (data layer ↔ presentation layer), enabling: - 4 Storybook stories on the View (Healthy / Loading / ErrorState / HealthyHighUptime) - Unit-testing each rendering branch without mocking the Nuxt fetch layer - Reusing the View in different contexts (e.g. embedded in admin dashboards, status pages) ## What's preserved - `pages/index.vue` still imports `<HealthDashboard />` (unchanged). - All existing `data-testid` attributes preserved (`health-dashboard`, `health-info`, `health-status`). - Playwright tests from PR #32 continue to pass without modification. ## What's added - New testids on the View for fine-grained targeting: `health-loading`, `health-error`. - `shims-vue.d.ts`: Vue 3 file module declaration with permissive `DefineComponent<any,any,any>` typing. Required because the strict TS + Storybook combo otherwise generates false-positive TS2353 errors on prop typing through .vue imports. ## Architecture notes (per code-reviewer skill SOLID/DDD section) - **SRP** : data fetching and rendering separated into 2 files - **OCP** : the View can be reused with different data sources (mock, real, polled, websocket) - **DDD modular** : the wrapper is the "adapter" boundary between Nuxt's runtime and the pure UI; matches the Hexagonal architecture pattern - **Cognitive load** : each file < 30 lines, naming explicit (`HealthDashboard` = composite, `HealthDashboardView` = view-only) ## Test plan - [x] `npx tsc --noEmit` passes - [ ] CI passes - [ ] Storybook shows 4 stories on HealthDashboardView (manual) - [ ] Playwright e2e (existing) still passes (manual) 🤖 Co-Authored-By: Claude Opus 4.7 (1M context)
arcodange added 1 commit 2026-05-03 17:55:32 +02:00
User feedback (PR #32 commit, T13 follow-up): HealthDashboard.stories.ts could not
demonstrate Loading or Error states because the component used useFetch internally
and didn't accept props. Same limitation made unit-testing the rendering branches
impossible without mocking the Nuxt fetch layer.

Split into 2 files (SRP / DDD modular per code-reviewer skill):

- HealthDashboardView.vue (NEW): pure presentational, accepts data/pending/error
  as props. Adds explicit data-testid="health-loading" + "health-error" so e2e and
  unit tests can target each branch.
- HealthDashboard.vue (REFACTORED): now a thin smart wrapper that calls
  useFetch('/api/healthz') and forwards data/pending/error to HealthDashboardView.

Stories:
- HealthDashboardView.stories.ts (NEW): 4 stories — Healthy, Loading, ErrorState,
  HealthyHighUptime. Reviewers can now see all branches without running the backend.
- HealthDashboard.stories.ts: still has the Default story for the wrapper (smoke).

Tooling:
- shims-vue.d.ts: Vue file module declaration with permissive any-typing for the
  DefineComponent. Required because Vue 3 strict TS + Storybook propagates prop
  types poorly through .vue imports otherwise (false-positive TS2353 errors).

Backwards compatibility:
- pages/index.vue still imports <HealthDashboard /> (unchanged).
- All existing data-testid attributes preserved (health-dashboard, health-info,
  health-status). The new health-loading and health-error testids are additive.
- The Playwright tests from PR #32 continue to pass without modification.

🤖 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arcodange merged commit 7c3617c9d7 into main 2026-05-03 17:55:48 +02:00
arcodange deleted branch feat/healthdashboard-stateful-stories 2026-05-03 17:55:49 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: arcodange/dance-lessons-coach#33