diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index ef5c039..cb4927c 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -203,6 +203,15 @@ export function App() { const playerCharacterRef = useRef(null); const actionBarAnim = useActionBarAnimation(encounter.combatants.length); + // Auto-update stat block panel when the active combatant changes + const activeCreatureId = + encounter.combatants[encounter.activeIndex]?.creatureId; + useEffect(() => { + if (activeCreatureId && sidePanel.panelView.mode === "creature") { + sidePanel.showCreature(activeCreatureId); + } + }, [activeCreatureId, sidePanel.panelView.mode, sidePanel.showCreature]); + // Auto-scroll to the active combatant when the turn changes const activeRowRef = useRef(null); useEffect(() => { @@ -282,6 +291,9 @@ export function App() { ? () => handleCombatantStatBlock(c.creatureId as string) : undefined } + isStatBlockOpen={ + c.creatureId === sidePanel.selectedCreatureId + } onRollInitiative={ c.creatureId ? handleRollInitiative : undefined } diff --git a/apps/web/src/components/combatant-row.tsx b/apps/web/src/components/combatant-row.tsx index 6b0bda7..7ea7ef6 100644 --- a/apps/web/src/components/combatant-row.tsx +++ b/apps/web/src/components/combatant-row.tsx @@ -4,7 +4,7 @@ import { deriveHpStatus, type PlayerIcon, } from "@initiative/domain"; -import { BookOpen, Brain, X } from "lucide-react"; +import { Book, BookOpen, Brain, X } from "lucide-react"; import { type Ref, useCallback, useEffect, useRef, useState } from "react"; import { cn } from "../lib/utils"; import { AcShield } from "./ac-shield"; @@ -41,6 +41,7 @@ interface CombatantRowProps { onToggleCondition: (id: CombatantId, conditionId: ConditionId) => void; onToggleConcentration: (id: CombatantId) => void; onShowStatBlock?: () => void; + isStatBlockOpen?: boolean; onRollInitiative?: (id: CombatantId) => void; } @@ -396,6 +397,7 @@ export function CombatantRow({ onToggleCondition, onToggleConcentration, onShowStatBlock, + isStatBlockOpen, onRollInitiative, }: CombatantRowProps & { ref?: Ref }) { const { id, name, initiative, maxHp, currentHp } = combatant; @@ -480,9 +482,9 @@ export function CombatantRow({ onClick={onShowStatBlock} title="View stat block" aria-label="View stat block" - className="shrink-0 text-muted-foreground transition-colors hover:text-hover-neutral" + className="shrink-0 text-foreground transition-colors hover:text-hover-neutral" > - + {isStatBlockOpen ? : } )} {!!combatant.icon && @@ -495,7 +497,7 @@ export function CombatantRow({ ]; return PcIcon ? ( diff --git a/specs/004-bestiary/spec.md b/specs/004-bestiary/spec.md index 000bc99..50927cb 100644 --- a/specs/004-bestiary/spec.md +++ b/specs/004-bestiary/spec.md @@ -264,7 +264,7 @@ As a DM with a creature pinned, I want to collapse the right (browse) panel inde ### Edge Cases - User pins a creature and then that creature is removed from the encounter: the pinned panel continues displaying the creature's stat block (data is already loaded). -- Active combatant changes while panel is open or collapsed: advancing turns does not auto-show or update the stat block panel. The panel only changes when the user explicitly clicks a book icon. If the panel is collapsed, it stays collapsed. +- Active combatant changes while panel is open: if the new active combatant has a creature, the panel auto-updates to show that creature's stat block. If the new active combatant has no creature, the panel remains on the previous creature. If the panel is collapsed, it stays collapsed. If the panel is closed, it stays closed. - Viewport resized from wide to narrow while a creature is pinned: the pinned (left) panel is hidden and the pin button disappears; resizing back to wide restores the pinned panel. - User is in bulk import mode and tries to collapse: the collapse/expand behavior applies to the bulk import panel identically. - Panel showing a source fetch prompt: the pin button is hidden.