Files
initiative/specs/030-bulk-import-sources/tasks.md

176 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks: Bulk Import All Sources
**Input**: Design documents from `/specs/030-bulk-import-sources/`
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
---
## Phase 1: Foundational (Blocking Prerequisites)
**Purpose**: Adapter helpers and core hook that all user stories depend on
- [x] T001 Add `getAllSourceCodes()` helper to `apps/web/src/adapters/bestiary-index-adapter.ts` that returns all source codes from the bestiary index's `sources` object as a `string[]`
- [x] T002 Refactor `getDefaultFetchUrl` in `apps/web/src/adapters/bestiary-index-adapter.ts` to accept an optional `baseUrl` parameter: `getDefaultFetchUrl(sourceCode: string, baseUrl?: string): string`. When `baseUrl` is provided, construct `{baseUrl}bestiary-{sourceCode.toLowerCase()}.json` (with trailing-slash normalization). When omitted, use the existing hardcoded default. Update existing call sites (no behavior change for current callers).
- [x] T003 Create `apps/web/src/hooks/use-bulk-import.ts` — the `useBulkImport` hook managing `BulkImportState` with a `startImport(baseUrl: string, fetchAndCacheSource, isSourceCached, refreshCache)` method. State shape: `{ status: 'idle' | 'loading' | 'complete' | 'partial-failure', total: number, completed: number, failed: number }` where `total` = ALL sources in the index (including pre-cached), `completed` = successfully loaded (fetched + pre-cached/skipped), `failed` = fetch failures. On start: set `total` to `getAllSourceCodes().length`, immediately count already-cached sources into `completed`, fire remaining fetches concurrently via `Promise.allSettled()`, increment completed/failed as each settles, call `refreshCache()` once when all settle, transition status to `complete` (failed === 0) or `partial-failure`.
- [x] T003a [P] Write tests in `apps/web/src/__tests__/bestiary-index-helpers.test.ts` for: `getAllSourceCodes()` returns all keys from the index's `sources` object; `getDefaultFetchUrl(sourceCode, baseUrl)` constructs correct URL with trailing-slash normalization and lowercases the source code in the filename.
- [x] T003b [P] Write tests in `apps/web/src/__tests__/use-bulk-import.test.ts` for: starts with `idle` status; skips already-cached sources (counts them into `completed`); increments `completed` on successful fetch; increments `failed` on rejected fetch; transitions to `complete` when all succeed; transitions to `partial-failure` when any fetch fails; all-cached edge case immediately transitions to `complete` with 0 fetches; calls `refreshCache()` exactly once when all settle.
**Checkpoint**: Foundation ready — user story implementation can begin
---
## Phase 2: User Story 1 — Bulk Load All Sources (Priority: P1)
**Goal**: User clicks an Import button in the top bar, sees a bulk import prompt in the side panel with editable base URL, and can load all sources with one click.
**Independent Test**: Click Import button → side panel opens with prompt → click "Load All" → all uncached sources are fetched, normalized, and cached in IndexedDB.
### Implementation for User Story 1
- [x] T004 [US1] Create `apps/web/src/components/bulk-import-prompt.tsx` — component showing descriptive text ("Load stat block data for all {totalSources} sources at once. This will download approximately 12.5 MB..." where `totalSources` is derived from `getAllSourceCodes().length`), an editable Input pre-filled with `https://raw.githubusercontent.com/5etools-mirror-3/5etools-src/main/data/bestiary/`, and a "Load All" Button (disabled when URL is empty/whitespace-only or import is active). On click, calls a provided `onStartImport(baseUrl)` callback.
- [x] T005 [US1] Add Import button (Lucide `Import` icon) to `apps/web/src/components/action-bar.tsx` in the top bar area. Clicking it calls a new `onBulkImport` callback prop.
- [x] T006 [US1] Add bulk import mode to `apps/web/src/components/stat-block-panel.tsx` — when a new `bulkImportMode` prop is true, render `BulkImportPrompt` instead of the normal stat block or source fetch prompt content. Pass through `onStartImport` and bulk import state props.
- [x] T007 [US1] Wire bulk import in `apps/web/src/App.tsx` — add `bulkImportMode` state, pass `onBulkImport` to ActionBar (sets mode + opens panel), pass `bulkImportMode` and `useBulkImport` state to StatBlockPanel, call `useBulkImport.startImport()` with `fetchAndCacheSource`, `isSourceCached`, and `refreshCache` from `useBestiary`.
**Checkpoint**: User Story 1 fully functional — Import button opens panel, "Load All" fetches all sources, cached sources skipped
---
## Phase 3: User Story 2 — Progress Feedback in Side Panel (Priority: P1)
**Goal**: During bulk import, the side panel shows a text counter ("Loading sources... 34/102") and a progress bar that updates in real time.
**Independent Test**: Start bulk import → observe counter and progress bar updating as each source completes.
### Implementation for User Story 2
- [x] T008 [US2] Add progress display to `apps/web/src/components/bulk-import-prompt.tsx` — when import status is `loading`, replace the "Load All" button area with a text counter ("Loading sources... {completed}/{total}") and a Tailwind-styled progress bar (`<div>` with percentage width based on `(completed + failed) / total`). The component receives `BulkImportState` as a prop.
**Checkpoint**: User Stories 1 AND 2 work together — full import flow with live progress
---
## Phase 4: User Story 3 — Toast Notification on Panel Close (Priority: P2)
**Goal**: If the user closes the side panel during an active import, a toast notification appears at bottom-center showing progress counter and bar.
**Independent Test**: Start bulk import → close side panel → toast appears with progress → toast updates as sources complete.
### Implementation for User Story 3
- [x] T009 [P] [US3] Create `apps/web/src/components/toast.tsx` — lightweight toast component using `ReactDOM.createPortal` to `document.body`. Renders at bottom-center with fixed positioning. Shows: message text, optional progress bar, optional dismiss button (X). Accepts `onDismiss` callback. Styled with Tailwind (dark background, rounded, shadow, z-50).
- [x] T010 [US3] Wire toast visibility in `apps/web/src/App.tsx` — show the toast when bulk import status is `loading` AND the stat block panel is closed (or `bulkImportMode` is false). Derive toast message from `BulkImportState`: "Loading sources... {completed}/{total}" with progress value `(completed + failed) / total`. Hide toast when panel reopens in bulk import mode.
**Checkpoint**: User Story 3 functional — closing panel during import shows toast with progress
---
## Phase 5: User Story 4 — Completion and Failure Reporting (Priority: P2)
**Goal**: On completion, show "All sources loaded" (auto-dismiss) or "Loaded X/Y sources (Z failed)" (persistent until dismissed).
**Independent Test**: Complete a successful import → see success message auto-dismiss. Simulate failures → see partial-failure message persist until dismissed.
### Implementation for User Story 4
- [x] T011 [US4] Add completion states to `apps/web/src/components/bulk-import-prompt.tsx` — when status is `complete`, show "All sources loaded" success message. When status is `partial-failure`, show "Loaded {completed}/{total} sources ({failed} failed)" message. Include a "Done" button to reset bulk import mode.
- [x] T012 [US4] Add completion behavior to toast in `apps/web/src/App.tsx` — when status is `complete`, show "All sources loaded" toast with `autoDismissMs` (e.g., 3000ms) and auto-hide via `setTimeout`. When status is `partial-failure`, show count message with dismiss button, no auto-dismiss. On dismiss, reset bulk import state to `idle`.
- [x] T013 [US4] Add auto-dismiss support to `apps/web/src/components/toast.tsx` — accept optional `autoDismissMs` prop. When set, start a `setTimeout` on mount that calls `onDismiss` after the delay. Clear timeout on unmount.
**Checkpoint**: All user stories complete — full flow with progress, toast, and completion reporting
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Edge cases and cleanup
- [x] T014 Disable Import button in `apps/web/src/components/action-bar.tsx` while bulk import status is `loading` to prevent double-trigger
- [x] T015 Handle all-cached edge case in `apps/web/src/hooks/use-bulk-import.ts` — if all sources are already cached (0 to fetch), immediately transition to `complete` status without firing any fetches
- [x] T016 Run `pnpm check` (knip + format + lint + typecheck + test) and fix any issues
---
## Dependencies & Execution Order
### Phase Dependencies
- **Foundational (Phase 1)**: No dependencies — can start immediately
- **US1 (Phase 2)**: Depends on Phase 1 completion
- **US2 (Phase 3)**: Depends on Phase 2 (extends `bulk-import-prompt.tsx` from US1)
- **US3 (Phase 4)**: Depends on Phase 1 (uses `BulkImportState`); toast component (T009) can be built in parallel with US1/US2
- **US4 (Phase 5)**: Depends on Phase 2 and Phase 4 (extends both panel and toast)
- **Polish (Phase 6)**: Depends on all story phases complete
### User Story Dependencies
- **US1 (P1)**: Depends only on Foundational
- **US2 (P1)**: Depends on US1 (extends same component)
- **US3 (P2)**: Toast component (T009) is independent; wiring (T010) depends on US1
- **US4 (P2)**: Depends on US1 (panel completion) and US3 (toast completion behavior)
### Parallel Opportunities
- T001 and T002 can run in parallel (different functions, same file)
- T003a and T003b can run in parallel with each other and with T003 (different files)
- T009 (toast component) can run in parallel with T004T008 (different files)
- T011 and T013 can run in parallel (different files)
---
## Parallel Example: Phase 1
```bash
# These foundational tasks modify the same file, so run T001+T002 together then T003:
Task: T001 + T002 "Add getAllSourceCodes and getBulkFetchUrl helpers"
Task: T003 "Create useBulkImport hook" (independent file)
```
## Parallel Example: US1 + Toast
```bash
# Toast component can be built while US1 is in progress:
Task: T004 "Create bulk-import-prompt.tsx" (US1)
Task: T009 "Create toast.tsx" (US3) — runs in parallel, different file
```
---
## Implementation Strategy
### MVP First (User Stories 1 + 2)
1. Complete Phase 1: Foundational helpers + hook
2. Complete Phase 2: US1 — Import button, prompt, fetch logic
3. Complete Phase 3: US2 — Progress counter + bar in panel
4. **STOP and VALIDATE**: Full import flow works with progress feedback
5. This delivers the core user value with tasks T001T008 (plus T003a, T003b tests)
### Incremental Delivery
1. Foundational → helpers and hook ready
2. US1 + US2 → Full import with progress (MVP!)
3. US3 → Toast notification on panel close
4. US4 → Completion/failure reporting
5. Polish → Edge cases and merge gate
---
## Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story
- US1 and US2 share `bulk-import-prompt.tsx` — US2 extends the component from US1
- US3's toast component (T009) is fully independent and can be built any time
- US4 adds completion behavior to both panel (from US1) and toast (from US3)
- Test tasks (T003a, T003b) cover foundational helpers and hook logic