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>
157 lines
11 KiB
Markdown
157 lines
11 KiB
Markdown
# 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.
|
|
|
|
- [x] 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.
|
|
- [x] 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.
|
|
- [x] 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
|
|
|
|
- [x] 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.
|
|
- [x] 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).
|
|
- [x] 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.
|
|
- [x] 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
|
|
|
|
- [x] 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.
|
|
- [x] 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`.
|
|
- [x] 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.
|
|
- [x] 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.
|
|
- [x] 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
|
|
|
|
- [x] 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])`.
|
|
- [x] 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.
|
|
|
|
- [x] 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.
|
|
- [x] T016 [P] Run `pnpm check` to verify all quality gates pass (Knip unused code, Biome lint/format, typecheck, tests, jscpd).
|
|
- [x] T017 Update CLAUDE.md active technologies section for this feature branch in `/Users/lukas.richter/projects/initiative/CLAUDE.md`.
|
|
- [x] 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
|
|
|
|
```text
|
|
# 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
|
|
2. + US1 (Batch Add) → MVP! Test independently
|
|
3. + US2 (Custom Fields) → Test independently
|
|
4. + US3 (Stat Block Viewer) → Test independently
|
|
5. 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.
|