# 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 - [ ] 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[]` - [ ] T002 Add `getBulkFetchUrl(baseUrl: string, sourceCode: string)` helper to `apps/web/src/adapters/bestiary-index-adapter.ts` that constructs `{baseUrl}bestiary-{sourceCode.toLowerCase()}.json` (ensure trailing slash normalization on baseUrl) - [ ] T003 Create `apps/web/src/hooks/use-bulk-import.ts` — the `useBulkImport` hook managing `BulkImportState` (status, total, completed, failed) with a `startImport(baseUrl: string, fetchAndCacheSource, isSourceCached, refreshCache)` method that: filters out already-cached sources via `isSourceCached`, fires all remaining fetches concurrently via `Promise.allSettled()`, increments completed/failed counters as each settles, calls `refreshCache()` once when all settle, and transitions status to `complete` or `partial-failure` **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 - [ ] T004 [US1] Create `apps/web/src/components/bulk-import-prompt.tsx` — component showing descriptive text ("Load stat block data for all 102 sources at once. This will download approximately 12.5 MB..."), 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 or import is active). On click, calls a provided `onStartImport(baseUrl)` callback. - [ ] 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. - [ ] 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. - [ ] 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 - [ ] 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 (`
` 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 - [ ] 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). - [ ] 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 - [ ] 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 + completed} sources ({failed} failed)" message. Include a "Done" button to reset bulk import mode. - [ ] 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`. - [ ] 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 - [ ] T014 Disable Import button in `apps/web/src/components/action-bar.tsx` while bulk import status is `loading` to prevent double-trigger - [ ] 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 - [ ] 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) - T009 (toast component) can run in parallel with T004–T008 (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 10 tasks (T001–T008) ### 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) - No test tasks included — not explicitly requested in spec