Files
initiative/specs/030-bulk-import-sources/tasks.md
Lukas c323adc343 Add spec, plan, and tasks for 030-bulk-import-sources feature
Defines the "Bulk Import All Sources" feature for the on-demand bestiary
system: one-click loading of all ~104 bestiary sources with concurrent
fetching, progress feedback, toast notifications, and completion reporting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 22:46:24 +01:00

10 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 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 (<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 + 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 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 10 tasks (T001T008)

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