From f024562a7d910ac6943355b1f2cc780cf5a33d6b Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 17 Mar 2026 12:03:34 +0100 Subject: [PATCH] Auto-open stat block panel when adding first bestiary creature When the side panel is in its initial closed state (not user-collapsed), adding a combatant from the bestiary now opens the panel to show its stat block. This makes the panel discoverable without overriding a deliberate collapse. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/App.tsx | 7 +++++-- .../src/hooks/__tests__/use-encounter.test.ts | 16 ++++++++++++---- apps/web/src/hooks/use-encounter.ts | 7 +++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 30fd3c1..bf41c2c 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -128,9 +128,12 @@ export function App() { const handleAddFromBestiary = useCallback( (result: SearchResult) => { - addFromBestiary(result); + const creatureId = addFromBestiary(result); + if (creatureId && sidePanel.panelView.mode === "closed") { + sidePanel.showCreature(creatureId); + } }, - [addFromBestiary], + [addFromBestiary, sidePanel.panelView.mode, sidePanel.showCreature], ); const handleCombatantStatBlock = useCallback( diff --git a/apps/web/src/hooks/__tests__/use-encounter.test.ts b/apps/web/src/hooks/__tests__/use-encounter.test.ts index 1181de0..6db364d 100644 --- a/apps/web/src/hooks/__tests__/use-encounter.test.ts +++ b/apps/web/src/hooks/__tests__/use-encounter.test.ts @@ -137,7 +137,9 @@ describe("useEncounter", () => { type: "humanoid", }; - act(() => result.current.addFromBestiary(entry)); + act(() => { + result.current.addFromBestiary(entry); + }); expect(result.current.hasCreatureCombatants).toBe(true); expect(result.current.canRollAllInitiative).toBe(true); @@ -158,7 +160,9 @@ describe("useEncounter", () => { type: "humanoid", }; - act(() => result.current.addFromBestiary(entry)); + act(() => { + result.current.addFromBestiary(entry); + }); const combatant = result.current.encounter.combatants[0]; expect(combatant.name).toBe("Goblin"); @@ -183,8 +187,12 @@ describe("useEncounter", () => { type: "humanoid", }; - act(() => result.current.addFromBestiary(entry)); - act(() => result.current.addFromBestiary(entry)); + act(() => { + result.current.addFromBestiary(entry); + }); + act(() => { + result.current.addFromBestiary(entry); + }); const names = result.current.encounter.combatants.map((c) => c.name); expect(names).toContain("Goblin 1"); diff --git a/apps/web/src/hooks/use-encounter.ts b/apps/web/src/hooks/use-encounter.ts index 14fa447..cc69412 100644 --- a/apps/web/src/hooks/use-encounter.ts +++ b/apps/web/src/hooks/use-encounter.ts @@ -17,6 +17,7 @@ import type { BestiaryIndexEntry, CombatantId, ConditionId, + CreatureId, DomainEvent, Encounter, PlayerCharacter, @@ -265,7 +266,7 @@ export function useEncounter() { }, [makeStore]); const addFromBestiary = useCallback( - (entry: BestiaryIndexEntry) => { + (entry: BestiaryIndexEntry): CreatureId | null => { const store = makeStore(); const existingNames = store.get().combatants.map((c) => c.name); const { newName, renames } = resolveCreatureName( @@ -284,7 +285,7 @@ export function useEncounter() { // Add combatant with resolved name const id = combatantId(`c-${++nextId.current}`); const addResult = addCombatantUseCase(makeStore(), id, newName); - if (isDomainError(addResult)) return; + if (isDomainError(addResult)) return null; // Set HP const hpResult = setHpUseCase(makeStore(), id, entry.hp); @@ -317,6 +318,8 @@ export function useEncounter() { }); setEvents((prev) => [...prev, ...addResult]); + + return cId; }, [makeStore], );