From 3ef2370a34fe1a357ec41ea2783b59230077e612 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sat, 14 Mar 2026 11:13:23 +0100 Subject: [PATCH] Replace panel mode booleans with discriminated union Three mutually exclusive state variables (selectedCreatureId, bulkImportMode, sourceManagerMode) replaced with a single PanelView union type, eliminating impossible states and boolean-clearing boilerplate in handlers. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/App.tsx | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 68a978d..1f5bb40 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -27,6 +27,12 @@ import { useBulkImport } from "./hooks/use-bulk-import"; import { useEncounter } from "./hooks/use-encounter"; import { usePlayerCharacters } from "./hooks/use-player-characters"; +type PanelView = + | { mode: "closed" } + | { mode: "creature"; creatureId: CreatureId } + | { mode: "bulk-import" } + | { mode: "source-manager" }; + function rollDice(): number { return Math.floor(Math.random() * 20) + 1; } @@ -116,10 +122,11 @@ export function App() { const [rollSkippedCount, setRollSkippedCount] = useState(0); - const [selectedCreatureId, setSelectedCreatureId] = - useState(null); - const [bulkImportMode, setBulkImportMode] = useState(false); - const [sourceManagerMode, setSourceManagerMode] = useState(false); + const [panelView, setPanelView] = useState({ mode: "closed" }); + const selectedCreatureId = + panelView.mode === "creature" ? panelView.creatureId : null; + const bulkImportMode = panelView.mode === "bulk-import"; + const sourceManagerMode = panelView.mode === "source-manager"; const [isRightPanelFolded, setIsRightPanelFolded] = useState(false); const [pinnedCreatureId, setPinnedCreatureId] = useState( null, @@ -151,9 +158,7 @@ export function App() { ); const handleCombatantStatBlock = useCallback((creatureId: string) => { - setSelectedCreatureId(creatureId as CreatureId); - setBulkImportMode(false); - setSourceManagerMode(false); + setPanelView({ mode: "creature", creatureId: creatureId as CreatureId }); setIsRightPanelFolded(false); }, []); @@ -177,23 +182,17 @@ export function App() { .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, ""); const cId = `${result.source.toLowerCase()}:${slug}` as CreatureId; - setSelectedCreatureId(cId); - setBulkImportMode(false); - setSourceManagerMode(false); + setPanelView({ mode: "creature", creatureId: cId }); setIsRightPanelFolded(false); }, []); const handleBulkImport = useCallback(() => { - setBulkImportMode(true); - setSourceManagerMode(false); - setSelectedCreatureId(null); + setPanelView({ mode: "bulk-import" }); setIsRightPanelFolded(false); }, []); const handleOpenSourceManager = useCallback(() => { - setSourceManagerMode(true); - setBulkImportMode(false); - setSelectedCreatureId(null); + setPanelView({ mode: "source-manager" }); setIsRightPanelFolded(false); }, []); @@ -210,14 +209,12 @@ export function App() { ); const handleBulkImportDone = useCallback(() => { - setBulkImportMode(false); + setPanelView({ mode: "closed" }); bulkImport.reset(); }, [bulkImport.reset]); const handleDismissBrowsePanel = useCallback(() => { - setSelectedCreatureId(null); - setBulkImportMode(false); - setSourceManagerMode(false); + setPanelView({ mode: "closed" }); }, []); const handleToggleFold = useCallback(() => { @@ -258,9 +255,10 @@ export function App() { if (!window.matchMedia("(min-width: 1024px)").matches) return; const active = encounter.combatants[encounter.activeIndex]; if (!active?.creatureId || !isLoaded) return; - setSelectedCreatureId(active.creatureId as CreatureId); - setBulkImportMode(false); - setSourceManagerMode(false); + setPanelView({ + mode: "creature", + creatureId: active.creatureId as CreatureId, + }); }, [encounter.activeIndex, encounter.combatants, isLoaded]); const isEmpty = encounter.combatants.length === 0;