|
| 1 | +# Overview Empty-State Reuse Implementation Plan |
| 2 | + |
| 3 | +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| 4 | +
|
| 5 | +**Goal:** Replace the Overview panel's bare `<div class="stats-empty">No collections</div>` with the shared `CollectionEmptyState` component, so an empty org shows the same "No collections yet" + Create-collection card as the collection view. |
| 6 | + |
| 7 | +**Architecture:** `OverviewPanel` already renders its empty branch exactly when `collections.value.length === 0`, which is the condition `CollectionEmptyState` handles. Reuse the component directly, passing the `connected` signal's value. |
| 8 | + |
| 9 | +**Tech Stack:** Preact + @preact/signals, Vitest (jsdom). |
| 10 | + |
| 11 | +## Global Constraints |
| 12 | + |
| 13 | +- Reuse `CollectionEmptyState` as-is; do NOT modify it. |
| 14 | +- The empty branch fires only when `totalCount === collections.value.length === 0`, so `CollectionEmptyState`'s `collections.length > 0` ("Select a collection") branch is unreachable here — correct by construction. |
| 15 | +- No new chrome.storage keys, no migration. The populated Overview (table, charts, sorting, totals) is untouched. |
| 16 | +- Leave the `.stats-empty` CSS rule in place (still used by `StatsPanel.jsx:180-181`). |
| 17 | +- No git commits in this run (owner rule) — stay on `master`, leave changes uncommitted; the final step is a test/build gate. The per-task diff is a working-tree diff scoped to the task's files. |
| 18 | +- Tests: `// @vitest-environment jsdom`, `h(Component, null)` + preact `render`, `vi.mock`. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +### Task 1: Reuse `CollectionEmptyState` in the Overview |
| 23 | + |
| 24 | +**Files:** |
| 25 | +- Modify: `src/mdh/components/OverviewPanel.jsx` (store import + component import + empty-branch swap) |
| 26 | +- Test: `tests/mdh-overview-empty.test.js` (new) |
| 27 | + |
| 28 | +**Interfaces:** |
| 29 | +- Consumes: `CollectionEmptyState({ connected })` (default export of `src/mdh/components/CollectionEmptyState.jsx`, shipped in `ae66019`); the `connected` signal from `src/mdh/store.js`. |
| 30 | +- Produces: no new exports. |
| 31 | + |
| 32 | +- [ ] **Step 1: Write the failing test** |
| 33 | + |
| 34 | +Create `tests/mdh-overview-empty.test.js`. An empty Overview is inert (the initial-load effect calls `streamStats([])` → zero workers → no API calls; the live-poll effect early-returns on empty `cols`, so no timers/listeners; `OverviewPanel` uses no `chrome.*`). Mock `api.js` defensively and mock `Sidebar.jsx`'s `showCreateModal` (imported transitively by `CollectionEmptyState`), mirroring `tests/mdh-empty-state.test.js`. |
| 35 | + |
| 36 | +```javascript |
| 37 | +// @vitest-environment jsdom |
| 38 | +import { describe, it, expect, beforeEach, vi } from 'vitest'; |
| 39 | +import { h, render } from 'preact'; |
| 40 | + |
| 41 | +vi.mock('../src/mdh/api.js', () => ({ aggregate: vi.fn() })); |
| 42 | +vi.mock('../src/mdh/components/Sidebar.jsx', () => ({ showCreateModal: vi.fn() })); |
| 43 | + |
| 44 | +import OverviewPanel from '../src/mdh/components/OverviewPanel.jsx'; |
| 45 | +import { collections, connected, loading, error } from '../src/mdh/store.js'; |
| 46 | + |
| 47 | +function mount() { |
| 48 | + const root = document.createElement('div'); |
| 49 | + render(h(OverviewPanel, null), root); |
| 50 | + return root; |
| 51 | +} |
| 52 | + |
| 53 | +describe('OverviewPanel empty state', () => { |
| 54 | + beforeEach(() => { |
| 55 | + collections.value = []; |
| 56 | + connected.value = true; |
| 57 | + loading.value = false; |
| 58 | + error.value = null; |
| 59 | + }); |
| 60 | + |
| 61 | + it('reuses the shared no-collections empty state instead of bare text', () => { |
| 62 | + const root = mount(); |
| 63 | + expect(root.textContent).toContain('No collections yet'); |
| 64 | + expect(root.querySelector('button.btn-success')).toBeTruthy(); |
| 65 | + // the old bare "No collections" .stats-empty div is gone |
| 66 | + expect(root.querySelector('.stats-empty')).toBeNull(); |
| 67 | + // no stats table when empty |
| 68 | + expect(root.querySelector('table.stats-table')).toBeNull(); |
| 69 | + }); |
| 70 | +}); |
| 71 | +``` |
| 72 | + |
| 73 | +- [ ] **Step 2: Run the test to verify it fails** |
| 74 | + |
| 75 | +Run: `npx vitest run tests/mdh-overview-empty.test.js` |
| 76 | +Expected: FAIL — the panel still renders `<div class="stats-empty">No collections</div>`, so `.stats-empty` is present, "No collections yet" is absent, and there is no `button.btn-success`. |
| 77 | + |
| 78 | +- [ ] **Step 3: Import the store signal and the component** |
| 79 | + |
| 80 | +In `src/mdh/components/OverviewPanel.jsx`: |
| 81 | + |
| 82 | +(a) Add `connected` to the store import (line 3), changing: |
| 83 | + |
| 84 | +```jsx |
| 85 | +import { collections, selectedCollection, activeView } from '../store.js'; |
| 86 | +``` |
| 87 | + |
| 88 | +to: |
| 89 | + |
| 90 | +```jsx |
| 91 | +import { collections, selectedCollection, activeView, connected } from '../store.js'; |
| 92 | +``` |
| 93 | + |
| 94 | +(b) Add the component import after the existing `OverviewCharts` import (line 8): |
| 95 | + |
| 96 | +```jsx |
| 97 | +import CollectionEmptyState from './CollectionEmptyState.jsx'; |
| 98 | +``` |
| 99 | + |
| 100 | +- [ ] **Step 4: Swap the empty branch** |
| 101 | + |
| 102 | +In `src/mdh/components/OverviewPanel.jsx`, replace (lines 308-310): |
| 103 | + |
| 104 | +```jsx |
| 105 | + {totalCount === 0 ? ( |
| 106 | + <div class="stats-empty">No collections</div> |
| 107 | + ) : ( |
| 108 | +``` |
| 109 | +
|
| 110 | +with: |
| 111 | +
|
| 112 | +```jsx |
| 113 | + {totalCount === 0 ? ( |
| 114 | + <CollectionEmptyState connected={connected.value} /> |
| 115 | + ) : ( |
| 116 | +``` |
| 117 | +
|
| 118 | +(Leave the rest of the ternary — the `<table>` branch — unchanged.) |
| 119 | +
|
| 120 | +- [ ] **Step 5: Run the test to verify it passes** |
| 121 | +
|
| 122 | +Run: `npx vitest run tests/mdh-overview-empty.test.js` |
| 123 | +Expected: PASS (1 test). |
| 124 | +
|
| 125 | +- [ ] **Step 6: Run the full suite and rebuild** |
| 126 | +
|
| 127 | +Run: `npm test` |
| 128 | +Expected: all tests green (new file + no regressions). |
| 129 | +
|
| 130 | +Run: `npm run build` |
| 131 | +Expected: clean build into `dist/` (console.js emits with no errors). |
| 132 | +
|
| 133 | +- [ ] **Step 7: Manual verification + handoff (no commit)** |
| 134 | +
|
| 135 | +Reload the extension, open the Console → Dataset Management on an org with **no** collections, click **Overview**: it shows the "No collections yet" card + Create-collection button (was: bare "No collections"). On an org **with** collections the Overview table renders as before. Leave uncommitted on `master`. |
| 136 | +
|
| 137 | +--- |
| 138 | +
|
| 139 | +## Self-Review |
| 140 | +
|
| 141 | +**Spec coverage:** |
| 142 | +- Import `connected` + `CollectionEmptyState`, swap the empty branch (spec §Design) → Task 1 Steps 3-4. ✓ |
| 143 | +- Reuse component unchanged; unreachable "Select a collection" branch (spec §facts 1-2) → Task 1 Step 4 + Global Constraints. ✓ |
| 144 | +- Layout fit via `.stats-scroll` flex column (spec §fact 3) → relied on, no code needed. ✓ |
| 145 | +- Inert empty Overview / no chrome / no timers (spec §fact 4) → Task 1 Step 1 test rationale. ✓ |
| 146 | +- `.stats-empty` left in place (spec §Design, still used by StatsPanel) → Global Constraints; no CSS change in the task. ✓ |
| 147 | +- Test asserts card + button, absence of `.stats-empty` and table (spec §Testing) → Task 1 Step 1. ✓ |
| 148 | +- Backward compat (no storage, populated Overview untouched) → Global Constraints; only the empty branch changes. ✓ |
| 149 | +
|
| 150 | +**Placeholder scan:** No TBD/TODO; every code step shows complete code; every command has an expected result. ✓ |
| 151 | +
|
| 152 | +**Type consistency:** `CollectionEmptyState({ connected })` consumed with `connected={connected.value}` (a boolean/null from the signal), matching its prop contract. Store signals `collections`/`connected`/`loading`/`error` match `src/mdh/store.js` exports. ✓ |
0 commit comments