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>
This commit is contained in:
48
specs/036-bottombar-overhaul/research.md
Normal file
48
specs/036-bottombar-overhaul/research.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Research: Bottom Bar Overhaul
|
||||
|
||||
## R-001: Batch Add via Existing Domain API
|
||||
|
||||
**Decision**: Call `addFromBestiary(result)` in a loop N times (once per queued count) rather than creating a new batch domain function.
|
||||
|
||||
**Rationale**: The existing `addFromBestiary` in `use-encounter.ts` already handles name auto-numbering (via `resolveCreatureName`), HP/AC assignment, and creatureId derivation. Calling it N times is correct because each call resolves the creature name against the updated combatant list, producing proper sequential numbering (e.g., "Goblin 1", "Goblin 2", "Goblin 3").
|
||||
|
||||
**Alternatives considered**:
|
||||
- New domain-level `addBatchCombatants` function: Rejected because auto-numbering depends on seeing previously-added names, which the loop approach already handles. A batch function would duplicate logic.
|
||||
- Application-layer batch use case: Rejected — no persistence coordination needed; the hook already manages state.
|
||||
|
||||
## R-002: Custom Creature Optional Fields (Initiative, AC, Max HP)
|
||||
|
||||
**Decision**: Extend the existing `onAddCombatant` callback (or create a sibling) to accept optional `{ initiative?: number; ac?: number; maxHp?: number }` alongside the name. The `use-encounter` hook's `addCombatant` flow will be extended to apply these fields to the newly created combatant.
|
||||
|
||||
**Rationale**: The domain `Combatant` type already has `initiative?`, `ac?`, and `maxHp?` fields. The domain `addCombatant` function creates a combatant with just `id` and `name`; the hook currently patches `currentHp`/`maxHp`/`ac` after creation for bestiary creatures. The same pattern works for custom creatures.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Extend domain `addCombatant` to accept optional fields: Rejected — the domain function is minimal by design (pure add with name validation). Post-creation patching is the established pattern.
|
||||
- New domain function `addCustomCombatant`: Rejected — unnecessary complexity for fields that are just optional patches on the existing type.
|
||||
|
||||
## R-003: Stat Block Preview from Dropdown
|
||||
|
||||
**Decision**: Pass a `onViewStatBlock(result: SearchResult)` callback from App.tsx through ActionBar. When triggered, it derives the `CreatureId` from the search result (same `${source}:${slug}` pattern used in `addFromBestiary`) and opens the stat block panel for that creature.
|
||||
|
||||
**Rationale**: The stat block panel infrastructure already exists and accepts a `CreatureId`. The only new wiring is a callback from the dropdown row to the panel opener.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Inline stat block preview in the dropdown: Rejected — the dropdown is compact and the stat block panel already handles full rendering, source fetching, and caching.
|
||||
|
||||
## R-004: Unified Search Flow vs Separate BestiarySearch Component
|
||||
|
||||
**Decision**: Merge the separate `BestiarySearch` component's functionality into the action bar's existing inline search. Remove the toggle between "form mode" and "search mode" (`searchOpen` state). The action bar always shows a single input field that serves both purposes.
|
||||
|
||||
**Rationale**: The current action bar has two modes — a name input with inline suggestions, and a separate full `BestiarySearch` overlay toggled by the search button. The spec calls for a unified flow where the single input field shows bestiary results as you type (already working via `suggestions`), with click-to-queue behavior replacing the immediate add-on-click. The separate `BestiarySearch` component becomes redundant.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Keep both modes: Rejected — the spec explicitly merges the flows (dropdown opens on type, search button becomes stat block viewer).
|
||||
|
||||
## R-005: Queue State Management
|
||||
|
||||
**Decision**: Queue state is local React state in the action bar component: `{ result: SearchResult; count: number } | null`. No hook or context needed.
|
||||
|
||||
**Rationale**: The queue is purely ephemeral and scoped to the action bar's interaction lifecycle. It resets on confirm, escape, or when the queued creature leaves the search results.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Custom hook for queue state: Rejected — the state is simple (one nullable object) and doesn't need to be shared.
|
||||
Reference in New Issue
Block a user