fix: force sync before boss fight and surface challenge errors to UI (#64)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m2s
CI / Lint, Build & Test (push) Successful in 1m8s

Closes #50

## Summary
- Calls `forceSync()` before every boss challenge so the server always fights against the player's live state (equipped items, upgrades, etc.) rather than a potentially stale snapshot
- Adds a `bossError` state that captures and displays error messages from failed manual boss challenges in the boss panel, matching the existing `autoBossError` display pattern

 This PR was created with help from Hikari~ 🌸

Reviewed-on: #64
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #64.
This commit is contained in:
2026-03-18 11:26:12 -07:00
committed by Naomi Carrigan
parent 9e5b8ed972
commit 219d299e9f
2 changed files with 31 additions and 2 deletions
@@ -235,6 +235,7 @@ const BossPanel = (): JSX.Element => {
toggleAutoBoss, toggleAutoBoss,
autoBossLastResult, autoBossLastResult,
autoBossError, autoBossError,
bossError,
} = useGame(); } = useGame();
const [ challengingBossId, setChallengingBossId ] = useState<string | null>( const [ challengingBossId, setChallengingBossId ] = useState<string | null>(
null, null,
@@ -362,6 +363,13 @@ const BossPanel = (): JSX.Element => {
</div> </div>
</div> </div>
{bossError === null
? null
: <p className="auto-boss-error">
{"⚠️ "}
{bossError}
</p>
}
{autoBossError === null {autoBossError === null
? null ? null
: <p className="auto-boss-error"> : <p className="auto-boss-error">
+23 -2
View File
@@ -557,6 +557,12 @@ interface GameContextValue {
* when no error). Cleared automatically when the player re-enables auto-boss. * when no error). Cleared automatically when the player re-enables auto-boss.
*/ */
autoBossError: string | null; 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 { export interface BattleResult {
@@ -606,6 +612,7 @@ export const GameProvider = ({
at: number; at: number;
} | null>(null); } | null>(null);
const [ autoBossError, setAutoBossError ] = useState<string | null>(null); const [ autoBossError, setAutoBossError ] = useState<string | null>(null);
const [ bossError, setBossError ] = useState<string | null>(null);
const syncErrorTimerReference = useRef<ReturnType<typeof setTimeout> | null>( const syncErrorTimerReference = useRef<ReturnType<typeof setTimeout> | null>(
null, null,
); );
@@ -1867,6 +1874,14 @@ export const GameProvider = ({
return; 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 { try {
const result = await challengeBossApi({ bossId }); const result = await challengeBossApi({ bossId });
setState((previous) => { setState((previous) => {
@@ -1878,9 +1893,13 @@ export const GameProvider = ({
setBattleResult({ bossName: boss.name, result: result }); setBattleResult({ bossName: boss.name, result: result });
} catch (error_: unknown) { } catch (error_: unknown) {
logError("challenge_boss", error_); 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(() => { const dismissOfflineGold = useCallback(() => {
setOfflineGold(0); setOfflineGold(0);
@@ -2023,6 +2042,7 @@ export const GameProvider = ({
autoBossError, autoBossError,
autoBossLastResult, autoBossLastResult,
battleResult, battleResult,
bossError,
buyAdventurer, buyAdventurer,
buyEchoUpgrade, buyEchoUpgrade,
buyEquipment, buyEquipment,
@@ -2091,6 +2111,7 @@ export const GameProvider = ({
autoBossError, autoBossError,
autoBossLastResult, autoBossLastResult,
battleResult, battleResult,
bossError,
completedQuestToasts, completedQuestToasts,
failedQuestToasts, failedQuestToasts,
formatNumber, formatNumber,