+ );
+}
diff --git a/docs/agents/plans/2026-03-13-declutter-action-bars.md b/docs/agents/plans/2026-03-13-declutter-action-bars.md
new file mode 100644
index 0000000..0b6a57d
--- /dev/null
+++ b/docs/agents/plans/2026-03-13-declutter-action-bars.md
@@ -0,0 +1,536 @@
+---
+date: "2026-03-13T14:58:42.882813+00:00"
+git_commit: 75778884bd1be7d135b2f5ea9b8a8e77a0149f7b
+branch: main
+topic: "Declutter Action Bars"
+tags: [plan, turn-navigation, action-bar, overflow-menu, ux]
+status: draft
+---
+
+# Declutter Action Bars — Implementation Plan
+
+## Overview
+
+Reorganize buttons across the top bar (TurnNavigation) and bottom bar (ActionBar) to reduce visual clutter and improve UX. Each bar gets a clear purpose: the top bar is for turn navigation + encounter lifecycle, the bottom bar is for adding combatants + setup actions.
+
+## Current State Analysis
+
+**Top bar** (`turn-navigation.tsx`) has 5 buttons + center info:
+```
+[ Prev ] | [ R1 Dwarf ] | [ D20 Library Trash ] [ Next ]
+```
+The D20 (roll all initiative) and Library (manage sources) buttons are unrelated to turn navigation — they're setup/utility actions that add noise.
+
+**Bottom bar** (`action-bar.tsx`) has an input, Add button, and 3 icon buttons:
+```
+[ + Add combatants... ] [ Add ] [ Users Eye Import ]
+```
+The icon cluster (Users, Eye, Import) is cryptic — three ghost icon buttons with no labels, requiring hover to discover purpose. The Eye button opens a separate search dropdown for browsing stat blocks, which duplicates the existing search input.
+
+### Key Discoveries:
+- `rollAllInitiativeUseCase` (`packages/application/src/roll-all-initiative-use-case.ts`) applies to combatants with `creatureId` AND no `initiative` set — this defines the conditional visibility logic
+- `Combatant.initiative` is `number | undefined` and `Combatant.creatureId` is `CreatureId | undefined` (`packages/domain/src/types.ts`)
+- No existing dropdown/menu UI component — the overflow menu needs a new component
+- Lucide provides `EllipsisVertical` for the kebab menu trigger
+- The stat block viewer already has its own search input, results list, and keyboard navigation (`action-bar.tsx:65-236`) — in browse mode, we reuse the main input for this instead
+
+## Desired End State
+
+### UI Mockups
+
+**Top bar (after):**
+```
+[ Prev ] [ R1 Dwarf ] [ Trash ] [ Next ]
+```
+4 elements. Clean, focused on turn flow + encounter lifecycle.
+
+**Bottom bar — add mode (default):**
+```
+[ + Add combatants... 👁 ] [ Add ] [ D20? ] [ ⋮ ]
+```
+The Eye icon sits inside/beside the input as a toggle. D20 appears conditionally. Kebab menu holds infrequent actions.
+
+**Bottom bar — browse mode (Eye toggled on):**
+```
+[ 🔍 Search stat blocks... 👁 ] [ ⋮ ]
+```
+The input switches purpose: placeholder changes, typing searches stat blocks instead of adding combatants. The Add button and D20 hide (irrelevant in browse mode). Eye icon stays as the toggle to switch back. Selecting a result opens the stat block panel and exits browse mode.
+
+**Overflow menu (⋮ clicked):**
+```
+┌──────────────────────┐
+│ 👥 Player Characters │
+│ 📚 Manage Sources │
+│ 📥 Bulk Import │
+└──────────────────────┘
+```
+Labeled items with icons — discoverable without hover.
+
+### Key Discoveries:
+- `sourceManagerOpen` state lives in App.tsx:116 — the overflow menu's "Manage Sources" item needs the same toggle callback
+- The stat block viewer state (viewerOpen, viewerQuery, viewerResults, viewerIndex) in action-bar.tsx:66-71 gets replaced by a `browseMode` boolean that repurposes the main input
+- The viewer's separate input, dropdown, and keyboard handling (action-bar.tsx:188-248) can be removed — browse mode reuses the existing input and suggestion dropdown infrastructure
+
+## What We're NOT Doing
+
+- Changing domain logic or use cases
+- Modifying ConfirmButton behavior
+- Changing the stat block panel itself
+- Altering animation logic (useActionBarAnimation)
+- Modifying combatant row buttons
+- Changing how SourceManager works (just moving where the trigger lives)
+
+## Implementation Approach
+
+Four phases, each independently testable. Phase 1 simplifies the top bar (pure removal). Phase 2 adds the overflow menu component. Phase 3 reworks the ActionBar (browse toggle + conditional D20 + overflow integration). Phase 4 wires everything together in App.tsx.
+
+---
+
+## Phase 1: Simplify TurnNavigation
+
+### Overview
+Strip TurnNavigation down to just turn controls + clear encounter. Remove Roll All Initiative and Manage Sources buttons and their associated props.
+
+### Changes Required:
+
+#### [x] 1. Update TurnNavigation component
+**File**: `apps/web/src/components/turn-navigation.tsx`
+**Changes**:
+- Remove `onRollAllInitiative` and `onOpenSourceManager` from props interface
+- Remove the D20 button (lines 53-62)
+- Remove the Library button (lines 63-72)
+- Remove the inner `gap-0` div wrapper (lines 52, 80) since only the ConfirmButton remains
+- Remove unused imports: `Library` from lucide-react, `D20Icon`
+- Adjust layout: ConfirmButton + Next button grouped with `gap-3`
+
+Result:
+```tsx
+interface TurnNavigationProps {
+ encounter: Encounter;
+ onAdvanceTurn: () => void;
+ onRetreatTurn: () => void;
+ onClearEncounter: () => void;
+}
+
+// Layout becomes:
+// [ Prev ] | [ R1 Name ] | [ Trash ] [ Next ]
+```
+
+#### [x] 2. Update TurnNavigation usage in App.tsx
+**File**: `apps/web/src/App.tsx`
+**Changes**:
+- Remove `onRollAllInitiative` and `onOpenSourceManager` props from the `` call (lines 256-257)
+
+### Success Criteria:
+
+#### Automated Verification:
+- [x] `pnpm check` passes (typecheck catches removed props, lint catches unused imports)
+
+#### Manual Verification:
+- [ ] Top bar shows only: Prev, round badge + name, trash, Next
+- [ ] Prev/Next/Clear buttons still work as before
+- [ ] Top bar animation (slide in/out) unchanged
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Phase 2: Create Overflow Menu Component
+
+### Overview
+Build a reusable overflow menu (kebab menu) component with click-outside and Escape handling, following the same patterns as ConfirmButton and the existing viewer dropdown.
+
+### Changes Required:
+
+#### [x] 1. Create OverflowMenu component
+**File**: `apps/web/src/components/ui/overflow-menu.tsx` (new file)
+**Changes**: Create a dropdown menu triggered by an EllipsisVertical icon button. Features:
+- Toggle open/close on button click
+- Close on click outside (document mousedown listener, same pattern as confirm-button.tsx:44-67)
+- Close on Escape key
+- Renders above the trigger (bottom-full positioning, same as action-bar suggestion dropdown)
+- Each item: icon + label, full-width clickable row
+- Clicking an item calls its action and closes the menu
+
+```tsx
+import { EllipsisVertical } from "lucide-react";
+import { type ReactNode, useEffect, useRef, useState } from "react";
+import { Button } from "./button";
+
+export interface OverflowMenuItem {
+ readonly icon: ReactNode;
+ readonly label: string;
+ readonly onClick: () => void;
+ readonly disabled?: boolean;
+}
+
+interface OverflowMenuProps {
+ readonly items: readonly OverflowMenuItem[];
+}
+
+export function OverflowMenu({ items }: OverflowMenuProps) {
+ const [open, setOpen] = useState(false);
+ const ref = useRef(null);
+
+ useEffect(() => {
+ if (!open) return;
+ function handleMouseDown(e: MouseEvent) {
+ if (ref.current && !ref.current.contains(e.target as Node)) {
+ setOpen(false);
+ }
+ }
+ function handleKeyDown(e: KeyboardEvent) {
+ if (e.key === "Escape") setOpen(false);
+ }
+ document.addEventListener("mousedown", handleMouseDown);
+ document.addEventListener("keydown", handleKeyDown);
+ return () => {
+ document.removeEventListener("mousedown", handleMouseDown);
+ document.removeEventListener("keydown", handleKeyDown);
+ };
+ }, [open]);
+
+ return (
+
+ );
+}
+```
+
+### Success Criteria:
+
+#### Automated Verification:
+- [x] `pnpm check` passes (new file compiles, no unused exports yet — will be used in phase 3)
+
+#### Manual Verification:
+- [ ] N/A — component not yet wired into the UI
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Phase 3: Rework ActionBar
+
+### Overview
+Replace the icon button cluster with: (1) an Eye toggle on the input that switches between add mode and browse mode, (2) a conditional Roll All Initiative button, and (3) the overflow menu for infrequent actions.
+
+### Changes Required:
+
+#### [x] 1. Update ActionBarProps
+**File**: `apps/web/src/components/action-bar.tsx`
+**Changes**: Add new props, keep existing ones needed for overflow menu items:
+```tsx
+interface ActionBarProps {
+ // ... existing props stay ...
+ onRollAllInitiative?: () => void; // new — moved from top bar
+ showRollAllInitiative?: boolean; // new — conditional visibility
+ onOpenSourceManager?: () => void; // new — moved from top bar
+}
+```
+
+#### [x] 2. Add browse mode state
+**File**: `apps/web/src/components/action-bar.tsx`
+**Changes**: Replace the separate viewer state (viewerOpen, viewerQuery, viewerResults, viewerIndex, viewerRef, viewerInputRef — lines 66-71) with a single `browseMode` boolean:
+
+```tsx
+const [browseMode, setBrowseMode] = useState(false);
+```
+
+Remove all viewer-specific state variables and handlers:
+- `viewerOpen`, `viewerQuery`, `viewerResults`, `viewerIndex` (lines 66-69)
+- `viewerRef`, `viewerInputRef` (lines 70-71)
+- `openViewer`, `closeViewer` (lines 189-202)
+- `handleViewerQueryChange`, `handleViewerSelect`, `handleViewerKeyDown` (lines 204-236)
+- The viewer click-outside effect (lines 239-248)
+
+#### [x] 3. Rework the input area with Eye toggle
+**File**: `apps/web/src/components/action-bar.tsx`
+**Changes**: Add an Eye icon button inside the input wrapper that toggles browse mode. When browse mode is active:
+- Placeholder changes to "Search stat blocks..."
+- Typing calls `bestiarySearch` but selecting a result calls `onViewStatBlock` instead of queuing/adding
+- The suggestion dropdown shows results but clicking opens stat block panel instead of adding
+- Add button and custom fields (Init/AC/MaxHP) are hidden
+- D20 button is hidden
+
+When toggling browse mode off, clear the input and suggestions.
+
+The Eye icon sits to the right of the input inside the `relative flex-1` wrapper:
+```tsx
+
+```
+
+Import `cn` from `../../lib/utils` (already used by other components).
+
+#### [x] 4. Update suggestion dropdown for browse mode
+**File**: `apps/web/src/components/action-bar.tsx`
+**Changes**: In browse mode, the suggestion dropdown behaves differently:
+- No "Add as custom" row at the top
+- No player character matches section
+- No queuing (plus/minus/confirm) — clicking a result calls `onViewStatBlock` and exits browse mode
+- Keyboard Enter on a highlighted result calls `onViewStatBlock` and exits browse mode
+
+Add a `handleBrowseKeyDown` handler:
+```tsx
+const handleBrowseKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Escape") {
+ setBrowseMode(false);
+ setNameInput("");
+ setSuggestions([]);
+ setSuggestionIndex(-1);
+ return;
+ }
+ if (suggestions.length === 0) return;
+ if (e.key === "ArrowDown") {
+ e.preventDefault();
+ setSuggestionIndex((i) => (i < suggestions.length - 1 ? i + 1 : 0));
+ } else if (e.key === "ArrowUp") {
+ e.preventDefault();
+ setSuggestionIndex((i) => (i > 0 ? i - 1 : suggestions.length - 1));
+ } else if (e.key === "Enter" && suggestionIndex >= 0) {
+ e.preventDefault();
+ onViewStatBlock?.(suggestions[suggestionIndex]);
+ setBrowseMode(false);
+ setNameInput("");
+ setSuggestions([]);
+ setSuggestionIndex(-1);
+ }
+};
+```
+
+In the suggestion dropdown JSX, conditionally render based on `browseMode`:
+- Browse mode: simple list of creature results, click → `onViewStatBlock` + exit browse mode
+- Add mode: existing behavior (custom row, PC matches, queuing)
+
+#### [x] 5. Replace icon button cluster with D20 + overflow menu
+**File**: `apps/web/src/components/action-bar.tsx`
+**Changes**: Replace the `div.flex.items-center.gap-0` block (lines 443-529) containing Users, Eye, and Import buttons with:
+
+```tsx
+{!browseMode && (
+ <>
+
+ Add
+
+ {showRollAllInitiative && onRollAllInitiative && (
+
+
+
+ )}
+ >
+)}
+
+```
+
+Build the `overflowItems` array from props:
+```tsx
+const overflowItems: OverflowMenuItem[] = [];
+if (onManagePlayers) {
+ overflowItems.push({
+ icon: ,
+ label: "Player Characters",
+ onClick: onManagePlayers,
+ });
+}
+if (onOpenSourceManager) {
+ overflowItems.push({
+ icon: ,
+ label: "Manage Sources",
+ onClick: onOpenSourceManager,
+ });
+}
+if (bestiaryLoaded && onBulkImport) {
+ overflowItems.push({
+ icon: ,
+ label: "Bulk Import",
+ onClick: onBulkImport,
+ disabled: bulkImportDisabled,
+ });
+}
+```
+
+#### [x] 6. Clean up imports
+**File**: `apps/web/src/components/action-bar.tsx`
+**Changes**:
+- Add imports: `D20Icon`, `OverflowMenu` + `OverflowMenuItem`, `Library` from lucide-react, `cn` from utils
+- Remove imports that are no longer needed after removing the standalone viewer: check which of `Eye`, `Import`, `Users` are still used (Eye stays for the toggle, Users and Import stay for overflow item icons, Library is new)
+- The `Check`, `Minus`, `Plus` imports stay (used in queuing UI)
+
+### Success Criteria:
+
+#### Automated Verification:
+- [x] `pnpm check` passes
+
+#### Manual Verification:
+- [ ] Bottom bar shows: input with Eye toggle, Add button, (conditional D20), kebab menu
+- [ ] Eye toggle switches input between "add" and "browse" modes
+- [ ] In browse mode: typing shows bestiary results, clicking one opens stat block panel, exits browse mode
+- [ ] In browse mode: Add button and D20 are hidden, overflow menu stays visible
+- [ ] In add mode: existing behavior works (search, queue, custom fields, PC matches)
+- [ ] Overflow menu opens/closes on click, closes on Escape and click-outside
+- [ ] Overflow menu items (Player Characters, Manage Sources, Bulk Import) trigger correct actions
+- [ ] D20 button appears only when bestiary combatants lack initiative, disappears when all have values
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Phase 4: Wire Up App.tsx
+
+### Overview
+Pass the new props to ActionBar — roll all initiative handler, conditional visibility flag, and source manager toggle. Remove the now-unused `onOpenSourceManager` callback from the TurnNavigation call (already removed in Phase 1) and ensure sourceManagerOpen toggle is routed through the overflow menu.
+
+### Changes Required:
+
+#### [x] 1. Compute showRollAllInitiative flag
+**File**: `apps/web/src/App.tsx`
+**Changes**: Add a derived boolean that checks if any combatant with a `creatureId` lacks an `initiative` value:
+
+```tsx
+const showRollAllInitiative = encounter.combatants.some(
+ (c) => c.creatureId != null && c.initiative == null,
+);
+```
+
+Place this near `const isEmpty = ...` (line 241).
+
+#### [x] 2. Pass new props to both ActionBar instances
+**File**: `apps/web/src/App.tsx`
+**Changes**: Add to both `` calls (empty state at ~line 269 and populated state at ~line 328):
+
+```tsx
+ setSourceManagerOpen((o) => !o)}
+/>
+```
+
+#### [x] 3. Remove stale code
+**File**: `apps/web/src/App.tsx`
+**Changes**:
+- The `onRollAllInitiative` and `onOpenSourceManager` props were already removed from `` in Phase 1 — verify no references remain
+- Verify `sourceManagerOpen` state and the `` rendering block (lines 287-291) still work correctly — the SourceManager inline panel is still toggled by the same state, just from a different trigger location
+
+### Success Criteria:
+
+#### Automated Verification:
+- [x] `pnpm check` passes
+
+#### Manual Verification:
+- [ ] Top bar: only Prev, round badge + name, trash, Next — no D20 or Library buttons
+- [ ] Bottom bar: input with Eye toggle, Add, conditional D20, overflow menu
+- [ ] Roll All Initiative (D20 in bottom bar): visible when bestiary creatures lack initiative, hidden after rolling
+- [ ] Overflow → Player Characters: opens player management modal
+- [ ] Overflow → Manage Sources: toggles source manager panel (same as before, just different trigger)
+- [ ] Overflow → Bulk Import: opens bulk import mode
+- [ ] Browse mode (Eye toggle): search stat blocks without adding, selecting opens panel
+- [ ] Clear encounter (top bar trash): still works with two-click confirmation
+- [ ] All animations (bar transitions) unchanged
+- [ ] Empty state: ActionBar centered with all functionality accessible
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Testing Strategy
+
+### Unit Tests:
+- No domain/application changes — existing tests should pass unchanged
+- `pnpm check` covers typecheck + lint + existing test suite
+
+### Manual Testing Steps:
+1. Start with empty encounter — verify ActionBar is centered with Eye toggle and overflow menu
+2. Add a bestiary creature — verify D20 appears in bottom bar, top bar slides in with just 4 elements
+3. Click D20 → initiative rolls → D20 disappears from bottom bar
+4. Toggle Eye → input switches to browse mode → search and select → stat block opens → exits browse mode
+5. Open overflow menu → click each item → verify correct modal/panel opens
+6. Click trash in top bar → confirm → encounter clears, back to empty state
+7. Add custom creature (no creatureId) → D20 should not appear (no bestiary creatures)
+8. Add mix of custom + bestiary creatures → D20 visible → roll all → D20 hidden
+
+## Performance Considerations
+
+None — this is a pure UI reorganization with no new data fetching, state management changes, or rendering overhead. The `showRollAllInitiative` computation is a simple `.some()` over the combatant array, which is negligible.
+
+## References
+
+- Research: `docs/agents/research/2026-03-13-action-bars-and-buttons.md`
+- Top bar: `apps/web/src/components/turn-navigation.tsx`
+- Bottom bar: `apps/web/src/components/action-bar.tsx`
+- App layout: `apps/web/src/App.tsx`
+- Button: `apps/web/src/components/ui/button.tsx`
+- ConfirmButton: `apps/web/src/components/ui/confirm-button.tsx`
+- Roll all use case: `packages/application/src/roll-all-initiative-use-case.ts`
+- Combatant type: `packages/domain/src/types.ts`
diff --git a/docs/agents/research/2026-03-13-action-bars-and-buttons.md b/docs/agents/research/2026-03-13-action-bars-and-buttons.md
new file mode 100644
index 0000000..5d7f05c
--- /dev/null
+++ b/docs/agents/research/2026-03-13-action-bars-and-buttons.md
@@ -0,0 +1,176 @@
+---
+date: "2026-03-13T14:39:15.661886+00:00"
+git_commit: 75778884bd1be7d135b2f5ea9b8a8e77a0149f7b
+branch: main
+topic: "Action Bars Setup — Top Bar and Bottom Bar Buttons"
+tags: [research, codebase, action-bar, turn-navigation, layout, buttons]
+status: complete
+---
+
+# Research: Action Bars Setup — Top Bar and Bottom Bar Buttons
+
+## Research Question
+
+How are the top and bottom action bars set up, what buttons do they contain, and how are their actions wired?
+
+## Summary
+
+The application has two primary bar components that frame the encounter tracker UI:
+
+1. **Top bar** — `TurnNavigation` (`turn-navigation.tsx`) — turn controls, round/combatant display, and encounter-wide actions.
+2. **Bottom bar** — `ActionBar` (`action-bar.tsx`) — combatant input, bestiary search, stat block browsing, bulk import, and player character management.
+
+Both bars share the same visual container styling (`flex items-center gap-3 rounded-md border border-border bg-card px-4 py-3`). They are laid out in `App.tsx` within a flex column, with a scrollable combatant list between them. When the encounter is empty, only the ActionBar is shown (centered in the viewport); the TurnNavigation appears with an animation when the first combatant is added.
+
+## Detailed Findings
+
+### Layout Structure (`App.tsx:243-344`)
+
+The bars live inside a `max-w-2xl` centered column:
+
+```
+┌──────────────────────────────────┐
+│ TurnNavigation (pt-8, shrink-0) │ ← top bar, conditionally shown
+├──────────────────────────────────┤
+│ SourceManager (optional inline) │ ← toggled by Library button in top bar
+├──────────────────────────────────┤
+│ Combatant list (flex-1, │ ← scrollable
+│ overflow-y-auto) │
+├──────────────────────────────────┤
+│ ActionBar (pb-8, shrink-0) │ ← bottom bar
+└──────────────────────────────────┘
+```
+
+**Empty state**: When `encounter.combatants.length === 0`, the top bar is hidden and the ActionBar is vertically centered in a `flex items-center justify-center` wrapper with `pb-[15%]` offset. It receives `autoFocus` in this state.
+
+**Animation** (`useActionBarAnimation`, `App.tsx:30-66`): Manages transitions between empty and populated states:
+- Empty → populated: ActionBar plays `animate-settle-to-bottom`, TurnNavigation plays `animate-slide-down-in`.
+- Populated → empty: ActionBar plays `animate-rise-to-center`, TurnNavigation plays `animate-slide-up-out` (with `absolute` positioning during exit).
+
+The `showTopBar` flag is `true` when either combatants exist or the top bar exit animation is still running.
+
+### Top Bar — TurnNavigation (`turn-navigation.tsx`)
+
+**Props interface** (`turn-navigation.tsx:7-14`):
+- `encounter: Encounter` — full encounter state
+- `onAdvanceTurn`, `onRetreatTurn` — turn navigation callbacks
+- `onClearEncounter` — destructive clear with confirmation
+- `onRollAllInitiative` — rolls initiative for all combatants
+- `onOpenSourceManager` — toggles source manager panel
+
+**Layout**: Left–Center–Right structure:
+
+```
+[ ◀ Prev ] | [ R1 Active Combatant Name ] | [ 🎲 📚 🗑 ] [ Next ▶ ]
+```
+
+**Buttons (left to right)**:
+
+| # | Icon | Component | Variant | Action | Disabled when |
+|---|------|-----------|---------|--------|---------------|
+| 1 | `StepBack` | `Button` | default | `onRetreatTurn` | No combatants OR at round 1 index 0 |
+| 2 | `D20Icon` | `Button` | ghost | `onRollAllInitiative` | Never |
+| 3 | `Library` | `Button` | ghost | `onOpenSourceManager` | Never |
+| 4 | `Trash2` | `ConfirmButton` | — | `onClearEncounter` | No combatants |
+| 5 | `StepForward` | `Button` | default | `onAdvanceTurn` | No combatants |
+
+**Center section** (`turn-navigation.tsx:40-49`): Displays a round badge (`R{n}` in a `rounded-full bg-muted` span) and the active combatant's name (truncated). Falls back to "No combatants" in muted text.
+
+**Button grouping**: Buttons 2-4 are grouped in a `gap-0` div (tight spacing), while button 5 (Next) is separated by the outer `gap-3`.
+
+**Wiring in App.tsx** (`App.tsx:251-258`):
+- `onAdvanceTurn` → `advanceTurn` from `useEncounter()`
+- `onRetreatTurn` → `retreatTurn` from `useEncounter()`
+- `onClearEncounter` → `clearEncounter` from `useEncounter()`
+- `onRollAllInitiative` → `handleRollAllInitiative` → calls `rollAllInitiativeUseCase(makeStore(), rollDice, getCreature)`
+- `onOpenSourceManager` → toggles `sourceManagerOpen` state
+
+### Bottom Bar — ActionBar (`action-bar.tsx`)
+
+**Props interface** (`action-bar.tsx:20-36`):
+- `onAddCombatant` — adds custom combatant with optional init/AC/maxHP
+- `onAddFromBestiary` — adds creature from search result
+- `bestiarySearch` — search function returning `SearchResult[]`
+- `bestiaryLoaded` — whether bestiary index is loaded
+- `onViewStatBlock` — opens stat block panel for a creature
+- `onBulkImport` — triggers bulk source import mode
+- `bulkImportDisabled` — disables import button during loading
+- `inputRef` — external ref to the name input
+- `playerCharacters` — list of player characters for quick-add
+- `onAddFromPlayerCharacter` — adds a player character to encounter
+- `onManagePlayers` — opens player management modal
+- `autoFocus` — auto-focuses input (used in empty state)
+
+**Layout**: Form with input, contextual fields, submit button, and action icons:
+
+```
+[ + Add combatants... ] [ Init ] [ AC ] [ MaxHP ] [ Add ] [ 👥 👁 📥 ]
+```
+
+The Init/AC/MaxHP fields only appear when the input has 2+ characters and no bestiary suggestions are showing.
+
+**Buttons (left to right)**:
+
+| # | Icon | Component | Variant | Action | Condition |
+|---|------|-----------|---------|--------|-----------|
+| 1 | — | `Button` | sm | Form submit → `handleAdd` | Always shown |
+| 2 | `Users` | `Button` | ghost | `onManagePlayers` | Only if `onManagePlayers` provided |
+| 3 | `Eye` | `Button` | ghost | Toggle stat block viewer dropdown | Only if `bestiaryLoaded && onViewStatBlock` |
+| 4 | `Import` | `Button` | ghost | `onBulkImport` | Only if `bestiaryLoaded && onBulkImport` |
+
+**Button grouping**: Buttons 2-4 are grouped in a `gap-0` div, mirroring the top bar's icon button grouping.
+
+**Suggestion dropdown** (`action-bar.tsx:267-410`): Opens above the input when 2+ chars are typed and results exist. Contains:
+- A "Add as custom" escape row at the top (with `Esc` keyboard hint)
+- **Players section**: Lists matching player characters with colored icons; clicking adds them directly via `onAddFromPlayerCharacter`
+- **Bestiary section**: Lists search results; clicking queues a creature. Queued creatures show:
+ - `Minus` button — decrements count (removes queue at 0)
+ - Count badge — current queued count
+ - `Plus` button — increments count
+ - `Check` button — confirms and adds all queued copies
+
+**Stat block viewer dropdown** (`action-bar.tsx:470-513`): A separate search dropdown anchored to the Eye button. Has its own input, search results, and keyboard navigation. Selecting a result calls `onViewStatBlock`.
+
+**Keyboard handling** (`action-bar.tsx:168-186`):
+- Arrow Up/Down — navigate suggestion list
+- Enter — queue selected suggestion or confirm queued batch
+- Escape — clear suggestions and queue
+
+**Wiring in App.tsx** (`App.tsx:269-282` and `328-340`):
+- `onAddCombatant` → `addCombatant` from `useEncounter()`
+- `onAddFromBestiary` → `handleAddFromBestiary` → `addFromBestiary` from `useEncounter()`
+- `bestiarySearch` → `search` from `useBestiary()`
+- `onViewStatBlock` → `handleViewStatBlock` → constructs `CreatureId` and sets `selectedCreatureId`
+- `onBulkImport` → `handleBulkImport` → sets `bulkImportMode` and clears selection
+- `onAddFromPlayerCharacter` → `addFromPlayerCharacter` from `useEncounter()`
+- `onManagePlayers` → opens `managementOpen` state (shows `PlayerManagement` modal)
+
+### Shared UI Primitives
+
+**`Button`** (`ui/button.tsx`): CVA-based component with variants (`default`, `outline`, `ghost`) and sizes (`default`, `sm`, `icon`). Both bars use `size="icon"` with `variant="ghost"` for their icon button clusters, and `size="icon"` with default variant for the primary navigation buttons (Prev/Next in top bar).
+
+**`ConfirmButton`** (`ui/confirm-button.tsx`): Two-click destructive action button. First click shows a red pulsing confirmation state with a Check icon; second click fires `onConfirm`. Auto-reverts after 5 seconds. Supports Escape and click-outside cancellation. Used for Clear Encounter in the top bar.
+
+### Hover Color Convention
+
+Both bars use consistent hover color classes on their ghost icon buttons:
+- `hover:text-hover-action` — used on the D20 (roll initiative) button, suggesting an action/accent color
+- `hover:text-hover-neutral` — used on Library, Users, Eye, Import buttons, suggesting a neutral/informational color
+
+## Code References
+
+- `apps/web/src/components/turn-navigation.tsx` — Top bar component (93 lines)
+- `apps/web/src/components/action-bar.tsx` — Bottom bar component (533 lines)
+- `apps/web/src/App.tsx:30-66` — `useActionBarAnimation` hook for bar transitions
+- `apps/web/src/App.tsx:243-344` — Layout structure with both bars
+- `apps/web/src/components/ui/button.tsx` — Shared Button component
+- `apps/web/src/components/ui/confirm-button.tsx` — Two-step confirmation button
+- `apps/web/src/components/d20-icon.tsx` — Custom D20 dice SVG icon
+
+## Architecture Documentation
+
+The bars follow the app's adapter-layer convention: they are pure presentational React components that receive all behavior via callback props. No business logic lives in either bar — they delegate to handlers defined in `App.tsx`, which in turn call use-case functions from the application layer or manipulate local UI state.
+
+Both bars are rendered twice in `App.tsx` (once in the empty-state branch, once in the populated branch) rather than being conditionally repositioned, which simplifies the animation logic.
+
+The `ActionBar` is the more complex of the two, managing multiple pieces of local state (input value, suggestions, queued creatures, custom fields, stat block viewer) while `TurnNavigation` is fully stateless — all its data comes from the `encounter` prop.