Files
initiative/specs/036-bottombar-overhaul/tasks.md
Lukas b6e052f198 Overhaul bottom bar: batch add, custom fields, stat block viewer
Unify the action bar into a single search input with inline bestiary
dropdown. Clicking a dropdown entry queues it with +/- count controls
and a confirm button; Enter or confirm adds N copies to combat.

When no bestiary match exists, optional Init/AC/MaxHP fields appear
for custom creatures. The eye icon opens a separate search dropdown
to preview stat blocks without leaving the add flow.

Fix batch-add bug where only the last creature got a creatureId by
using store.save() instead of setEncounter() in addFromBestiary.
Prevent dropdown buttons from stealing input focus so Enter confirms
the queued batch.

Remove the now-redundant BestiarySearch component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:27:06 +01:00

11 KiB

Tasks: Bottom Bar Overhaul

Input: Design documents from /specs/036-bottombar-overhaul/ Prerequisites: plan.md (required), spec.md (required), research.md, data-model.md, quickstart.md

Tests: Not explicitly requested in the feature specification. Tests omitted.

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 (Unified Search Flow)

Purpose: Remove the dual-mode search (form + BestiarySearch overlay) and establish a single unified input that always shows inline bestiary suggestions. This unblocks all user stories.

  • T001 Refactor ActionBar to remove searchOpen state and the BestiarySearch toggle in apps/web/src/components/action-bar.tsx — the component should always render the form with inline suggestions dropdown (the existing suggestions + handleNameChange flow). Remove the conditional branch that renders <BestiarySearch>. Remove the Search icon button that toggled searchOpen. Keep the bulk import button unchanged.
  • T002 Update ActionBarProps interface in apps/web/src/components/action-bar.tsx — the onSelectCreature behavior changes: clicking a dropdown entry no longer immediately adds. For now, keep the existing click-to-add behavior working (it will be replaced in US1). Ensure the inline dropdown still appears when typing 2+ characters and keyboard navigation (ArrowUp/Down/Enter/Escape) still works.
  • T003 Update placeholder text on the name input from "Combatant name" to "Search creatures to add..." in apps/web/src/components/action-bar.tsx (FR-001).

Checkpoint: Action bar shows a single input field with inline bestiary dropdown. No more toggling between form and search modes. Existing add-on-click still works.


Phase 2: User Story 1 - Batch Add Predefined Creatures (Priority: P1) 🎯 MVP

Goal: Click a bestiary dropdown entry to queue it with a count badge; click again to increment; confirm to add N copies to combat.

Independent Test: Search for a creature, click entry multiple times to increment count, confirm — N copies appear in combat with auto-numbered names.

Implementation for User Story 1

  • T004 [US1] Add QueuedCreature state to ActionBar in apps/web/src/components/action-bar.tsx — add state const [queued, setQueued] = useState<{ result: SearchResult; count: number } | null>(null). When a dropdown entry is clicked: if same creature (match by source + name), increment count; if different creature, replace with count 1. Reset queued state when query changes such that the queued creature is no longer in the results list.
  • T005 [US1] Render count badge and confirm button on the queued dropdown row in apps/web/src/components/action-bar.tsx — in the suggestions <ul>, for the queued entry's row, show a count badge (e.g., a small rounded pill with the number) and a confirm button (e.g., a check icon or "Add" text button). Non-queued rows remain clickable as normal (first click queues them). Style the queued row distinctly (e.g., highlighted background).
  • T006 [US1] Implement batch confirm logic in apps/web/src/components/action-bar.tsx — when confirm button is clicked or Enter is pressed while a creature is queued: call onAddFromBestiary(queued.result) in a loop queued.count times, then reset queued state, clear the input, and close the dropdown. Update handleKeyDown so Enter with a queued creature triggers confirm instead of the default form submit.
  • T007 [US1] Handle edge cases for queue state in apps/web/src/components/action-bar.tsx — Escape clears queued state and closes dropdown. When query changes, check if the queued creature's source:name is still in the current suggestions array; if not, clear the queue.

Checkpoint: Batch add flow fully works. Search → click entry (count badge appears) → click again (count increments) → confirm (N copies added). Auto-numbering handled by existing addFromBestiary.


Phase 3: User Story 2 - Custom Creature with Optional Fields (Priority: P2)

Goal: When the typed name has no bestiary match, show optional initiative/AC/max HP fields so custom creatures can be added with pre-filled stats.

Independent Test: Type a name with no bestiary match, fill in optional fields, submit — creature appears in combat with provided values.

Implementation for User Story 2

  • T008 [US2] Extend onAddCombatant callback signature in apps/web/src/components/action-bar.tsx — change ActionBarProps.onAddCombatant from (name: string) => void to (name: string, opts?: { initiative?: number; ac?: number; maxHp?: number }) => void. Update handleAdd to collect and pass the optional fields.
  • T009 [US2] Extend addCombatant in apps/web/src/hooks/use-encounter.ts to accept and apply optional fields — after the domain addCombatant call creates the combatant, patch initiative, ac, maxHp, and currentHp (set to maxHp when provided) on the new combatant, following the same post-creation patching pattern used by addFromBestiary.
  • T010 [US2] Update App.tsx to pass the extended onAddCombatant through to ActionBar in apps/web/src/App.tsx — ensure the callback signature matches the new optional fields parameter.
  • T011 [US2] Render optional fields UI in apps/web/src/components/action-bar.tsx — when query.length >= 2 and suggestions.length === 0 (no bestiary match, using the same 2-char threshold as bestiary search per FR-002), show three small labeled input fields below the name input: "Initiative" (number), "AC" (number), "Max HP" (number). Use <Input type="number"> or text inputs with numeric parsing. Add local state for the three field values (strings, parsed to numbers on submit). Clear field state when bestiary results appear or after submit.
  • T012 [US2] Implement numeric parsing and submit for custom fields in apps/web/src/components/action-bar.tsx — in handleAdd, parse each field value with Number() or parseInt; if result is NaN or empty string, omit that field. Pass valid values to onAddCombatant(name, { initiative, ac, maxHp }). Reset all field inputs after submit.

Checkpoint: Custom creatures can be added with optional pre-filled initiative, AC, and max HP. Fields are clearly labeled and only appear when no bestiary match exists.


Phase 4: User Story 3 - Stat Block Viewer from Dropdown (Priority: P3)

Goal: A button in the action bar opens the stat block panel for the currently focused/highlighted creature in the dropdown.

Independent Test: Search for a creature, highlight an entry, click the stat block view button — stat block panel opens showing that creature's data.

Implementation for User Story 3

  • T013 [US3] Add onViewStatBlock prop to ActionBarProps in apps/web/src/components/action-bar.tsx — add onViewStatBlock?: (result: SearchResult) => void to the props interface. Add a button (using the Search or Eye icon from Lucide) next to the input that is enabled only when suggestionIndex >= 0 (a creature is highlighted in the dropdown). On click, call onViewStatBlock(suggestions[suggestionIndex]).
  • T014 [US3] Wire onViewStatBlock callback in apps/web/src/App.tsx — create a handler that takes a SearchResult, derives the CreatureId using the same ${source}:${slug} pattern from addFromBestiary in use-encounter.ts, and opens the stat block panel by setting the browse creature ID state. Pass this handler to ActionBar.

Checkpoint: Stat block preview works from the search dropdown. The search icon button now opens the stat block for the highlighted creature instead of toggling a separate search mode.


Phase 5: Polish & Cross-Cutting Concerns

Purpose: Cleanup, remove dead code, ensure quality gates pass.

  • T015 [P] Remove apps/web/src/components/bestiary-search.tsx if no longer imported anywhere — verify with a grep for bestiary-search or BestiarySearch across the codebase. Remove the file and any unused imports.
  • T016 [P] Run pnpm check to verify all quality gates pass (Knip unused code, Biome lint/format, typecheck, tests, jscpd).
  • T017 Update CLAUDE.md active technologies section for this feature branch in /Users/lukas.richter/projects/initiative/CLAUDE.md.
  • T018 Update README.md if it documents the add-creature workflow — batch-add and custom creature fields are new user-facing capabilities (constitution: README MUST be updated when user-facing capabilities change).

Dependencies & Execution Order

Phase Dependencies

  • Foundational (Phase 1): No dependencies — start immediately. BLOCKS all user stories.
  • US1 (Phase 2): Depends on Phase 1 completion.
  • US2 (Phase 3): Depends on Phase 1 completion. Independent of US1.
  • US3 (Phase 4): Depends on Phase 1 completion. Independent of US1 and US2.
  • Polish (Phase 5): Depends on all user stories being complete.

User Story Dependencies

  • US1 (P1): Can start after Phase 1. No cross-story dependencies.
  • US2 (P2): Can start after Phase 1. No cross-story dependencies (different code paths — bestiary match vs. no match).
  • US3 (P3): Can start after Phase 1. No cross-story dependencies (separate button + callback).

Within Each User Story

  • Tasks are ordered sequentially within each story (state → UI → logic → edge cases).
  • US2 T008-T010 can be parallelized (props change, hook change, App.tsx wiring).

Parallel Opportunities

  • After Phase 1: US1, US2, and US3 can all proceed in parallel (different interaction paths, different code concerns).
  • Within US2: T008, T009, T010 touch different files and can run in parallel.
  • Within Phase 5: T015 and T016 can run in parallel.

Parallel Example: After Phase 1

# All three user stories can start simultaneously:
Stream A (US1): T004 → T005 → T006 → T007
Stream B (US2): T008 + T009 + T010 (parallel) → T011 → T012
Stream C (US3): T013 → T014

Implementation Strategy

MVP First (User Story 1 Only)

  1. Complete Phase 1: Foundational (unified search flow)
  2. Complete Phase 2: User Story 1 (batch add)
  3. STOP and VALIDATE: Test batch add independently
  4. This alone delivers the highest-value improvement

Incremental Delivery

  1. Phase 1 (Foundational) → Unified input field works
    • US1 (Batch Add) → MVP! Test independently
    • US2 (Custom Fields) → Test independently
    • US3 (Stat Block Viewer) → Test independently
  2. Phase 5 (Polish) → Clean up, quality gates

Notes

  • All changes are adapter-layer only (apps/web). No domain or application package changes except extending the hook callback in T009.
  • The BestiarySearch component becomes dead code after Phase 1 — removed in Phase 5.
  • Batch add relies on calling existing addFromBestiary N times in a loop — auto-numbering is handled by resolveCreatureName in the hook.
  • Custom creature optional fields use post-creation patching, same pattern as bestiary creatures.
  • Queue state (QueuedCreature) and custom field state (CustomCreatureFields) are ephemeral React state — no persistence.