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

11 KiB
Raw Blame History

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 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).

  • 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.

  • 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.

  • 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

  • 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.
  • 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 (<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

  • 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} 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)
  • 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

# 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

# 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