Files
initiative/specs/036-bottombar-overhaul/research.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

4.0 KiB

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.