From 498c97582dfad3dd6e3164bc7e4fc877ab31b1b5 Mon Sep 17 00:00:00 2001 From: Hikari Date: Wed, 18 Mar 2026 11:19:37 -0700 Subject: [PATCH] fix: force sync before boss fight and surface challenge errors to UI Closes #50 --- apps/web/src/components/game/bossPanel.tsx | 8 +++++++ apps/web/src/context/gameContext.tsx | 25 ++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/game/bossPanel.tsx b/apps/web/src/components/game/bossPanel.tsx index 0985962..4b04ff1 100644 --- a/apps/web/src/components/game/bossPanel.tsx +++ b/apps/web/src/components/game/bossPanel.tsx @@ -235,6 +235,7 @@ const BossPanel = (): JSX.Element => { toggleAutoBoss, autoBossLastResult, autoBossError, + bossError, } = useGame(); const [ challengingBossId, setChallengingBossId ] = useState( null, @@ -362,6 +363,13 @@ const BossPanel = (): JSX.Element => { + {bossError === null + ? null + :

+ {"⚠️ "} + {bossError} +

+ } {autoBossError === null ? null :

diff --git a/apps/web/src/context/gameContext.tsx b/apps/web/src/context/gameContext.tsx index 0bf3fc3..0f146a8 100644 --- a/apps/web/src/context/gameContext.tsx +++ b/apps/web/src/context/gameContext.tsx @@ -557,6 +557,12 @@ interface GameContextValue { * when no error). Cleared automatically when the player re-enables auto-boss. */ autoBossError: string | null; + + /** + * Error message from the most recent manual boss challenge (null when no + * error). Cleared automatically when a new challenge is initiated. + */ + bossError: string | null; } export interface BattleResult { @@ -606,6 +612,7 @@ export const GameProvider = ({ at: number; } | null>(null); const [ autoBossError, setAutoBossError ] = useState(null); + const [ bossError, setBossError ] = useState(null); const syncErrorTimerReference = useRef | null>( null, ); @@ -1867,6 +1874,14 @@ export const GameProvider = ({ return; } + setBossError(null); + + /* + * Flush any pending state (e.g. newly equipped items) to the server before + * the fight so the server-side calculation uses the player's live stats. + */ + await forceSync(); + try { const result = await challengeBossApi({ bossId }); setState((previous) => { @@ -1878,9 +1893,13 @@ export const GameProvider = ({ setBattleResult({ bossName: boss.name, result: result }); } catch (error_: unknown) { logError("challenge_boss", error_); - // Silently ignore — server errors shouldn't crash the UI + setBossError( + error_ instanceof Error + ? error_.message + : "Failed to challenge boss", + ); } - }, []); + }, [ forceSync ]); const dismissOfflineGold = useCallback(() => { setOfflineGold(0); @@ -2023,6 +2042,7 @@ export const GameProvider = ({ autoBossError, autoBossLastResult, battleResult, + bossError, buyAdventurer, buyEchoUpgrade, buyEquipment, @@ -2091,6 +2111,7 @@ export const GameProvider = ({ autoBossError, autoBossLastResult, battleResult, + bossError, completedQuestToasts, failedQuestToasts, formatNumber,