11 KiB
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 toapps/web/src/adapters/bestiary-index-adapter.tsthat returns all source codes from the bestiary index'ssourcesobject as astring[] -
T002 Refactor
getDefaultFetchUrlinapps/web/src/adapters/bestiary-index-adapter.tsto accept an optionalbaseUrlparameter:getDefaultFetchUrl(sourceCode: string, baseUrl?: string): string. WhenbaseUrlis 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— theuseBulkImporthook managingBulkImportStatewith astartImport(baseUrl: string, fetchAndCacheSource, isSourceCached, refreshCache)method. State shape:{ status: 'idle' | 'loading' | 'complete' | 'partial-failure', total: number, completed: number, failed: number }wheretotal= ALL sources in the index (including pre-cached),completed= successfully loaded (fetched + pre-cached/skipped),failed= fetch failures. On start: settotaltogetAllSourceCodes().length, immediately count already-cached sources intocompleted, fire remaining fetches concurrently viaPromise.allSettled(), increment completed/failed as each settles, callrefreshCache()once when all settle, transition status tocomplete(failed === 0) orpartial-failure. -
T003a [P] Write tests in
apps/web/src/__tests__/bestiary-index-helpers.test.tsfor:getAllSourceCodes()returns all keys from the index'ssourcesobject;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.tsfor: starts withidlestatus; skips already-cached sources (counts them intocompleted); incrementscompletedon successful fetch; incrementsfailedon rejected fetch; transitions tocompletewhen all succeed; transitions topartial-failurewhen any fetch fails; all-cached edge case immediately transitions tocompletewith 0 fetches; callsrefreshCache()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..." wheretotalSourcesis derived fromgetAllSourceCodes().length), an editable Input pre-filled withhttps://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 providedonStartImport(baseUrl)callback. - T005 [US1] Add Import button (Lucide
Importicon) toapps/web/src/components/action-bar.tsxin the top bar area. Clicking it calls a newonBulkImportcallback prop. - T006 [US1] Add bulk import mode to
apps/web/src/components/stat-block-panel.tsx— when a newbulkImportModeprop is true, renderBulkImportPromptinstead of the normal stat block or source fetch prompt content. Pass throughonStartImportand bulk import state props. - T007 [US1] Wire bulk import in
apps/web/src/App.tsx— addbulkImportModestate, passonBulkImportto ActionBar (sets mode + opens panel), passbulkImportModeanduseBulkImportstate to StatBlockPanel, calluseBulkImport.startImport()withfetchAndCacheSource,isSourceCached, andrefreshCachefromuseBestiary.
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 isloading, 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 receivesBulkImportStateas 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 usingReactDOM.createPortaltodocument.body. Renders at bottom-center with fixed positioning. Shows: message text, optional progress bar, optional dismiss button (X). AcceptsonDismisscallback. 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 isloadingAND the stat block panel is closed (orbulkImportModeis false). Derive toast message fromBulkImportState: "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 iscomplete, show "All sources loaded" success message. When status ispartial-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 iscomplete, show "All sources loaded" toast withautoDismissMs(e.g., 3000ms) and auto-hide viasetTimeout. When status ispartial-failure, show count message with dismiss button, no auto-dismiss. On dismiss, reset bulk import state toidle. - T013 [US4] Add auto-dismiss support to
apps/web/src/components/toast.tsx— accept optionalautoDismissMsprop. When set, start asetTimeouton mount that callsonDismissafter 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.tsxwhile bulk import status isloadingto 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 tocompletestatus 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.tsxfrom 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 T004–T008 (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)
- Complete Phase 1: Foundational helpers + hook
- Complete Phase 2: US1 — Import button, prompt, fetch logic
- Complete Phase 3: US2 — Progress counter + bar in panel
- STOP and VALIDATE: Full import flow works with progress feedback
- This delivers the core user value with tasks T001–T008 (plus T003a, T003b tests)
Incremental Delivery
- Foundational → helpers and hook ready
- US1 + US2 → Full import with progress (MVP!)
- US3 → Toast notification on panel close
- US4 → Completion/failure reporting
- 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