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>
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
ActionBarto removesearchOpenstate and theBestiarySearchtoggle inapps/web/src/components/action-bar.tsx— the component should always render the form with inline suggestions dropdown (the existingsuggestions+handleNameChangeflow). Remove the conditional branch that renders<BestiarySearch>. Remove theSearchicon button that toggledsearchOpen. Keep the bulk import button unchanged. - T002 Update
ActionBarPropsinterface inapps/web/src/components/action-bar.tsx— theonSelectCreaturebehavior 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
QueuedCreaturestate toActionBarinapps/web/src/components/action-bar.tsx— add stateconst [queued, setQueued] = useState<{ result: SearchResult; count: number } | null>(null). When a dropdown entry is clicked: if same creature (match bysource+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: callonAddFromBestiary(queued.result)in a loopqueued.counttimes, then reset queued state, clear the input, and close the dropdown. UpdatehandleKeyDownso 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'ssource:nameis still in the currentsuggestionsarray; 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
onAddCombatantcallback signature inapps/web/src/components/action-bar.tsx— changeActionBarProps.onAddCombatantfrom(name: string) => voidto(name: string, opts?: { initiative?: number; ac?: number; maxHp?: number }) => void. UpdatehandleAddto collect and pass the optional fields. - T009 [US2] Extend
addCombatantinapps/web/src/hooks/use-encounter.tsto accept and apply optional fields — after the domainaddCombatantcall creates the combatant, patchinitiative,ac,maxHp, andcurrentHp(set tomaxHpwhen provided) on the new combatant, following the same post-creation patching pattern used byaddFromBestiary. - T010 [US2] Update
App.tsxto pass the extendedonAddCombatantthrough toActionBarinapps/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— whenquery.length >= 2andsuggestions.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— inhandleAdd, parse each field value withNumber()orparseInt; if result isNaNor empty string, omit that field. Pass valid values toonAddCombatant(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
onViewStatBlockprop toActionBarPropsinapps/web/src/components/action-bar.tsx— addonViewStatBlock?: (result: SearchResult) => voidto the props interface. Add a button (using theSearchorEyeicon from Lucide) next to the input that is enabled only whensuggestionIndex >= 0(a creature is highlighted in the dropdown). On click, callonViewStatBlock(suggestions[suggestionIndex]). - T014 [US3] Wire
onViewStatBlockcallback inapps/web/src/App.tsx— create a handler that takes aSearchResult, derives theCreatureIdusing the same${source}:${slug}pattern fromaddFromBestiaryinuse-encounter.ts, and opens the stat block panel by setting the browse creature ID state. Pass this handler toActionBar.
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.tsxif no longer imported anywhere — verify with a grep forbestiary-searchorBestiarySearchacross the codebase. Remove the file and any unused imports. - T016 [P] Run
pnpm checkto 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)
- Complete Phase 1: Foundational (unified search flow)
- Complete Phase 2: User Story 1 (batch add)
- STOP and VALIDATE: Test batch add independently
- This alone delivers the highest-value improvement
Incremental Delivery
- Phase 1 (Foundational) → Unified input field works
-
- US1 (Batch Add) → MVP! Test independently
-
- US2 (Custom Fields) → Test independently
-
- US3 (Stat Block Viewer) → Test independently
- 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
BestiarySearchcomponent becomes dead code after Phase 1 — removed in Phase 5. - Batch add relies on calling existing
addFromBestiaryN times in a loop — auto-numbering is handled byresolveCreatureNamein 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.