chore: more feedback fixes (#129)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m5s
CI / Lint, Build & Test (push) Successful in 1m9s

## Summary

- Fix `NaN` displayed in Sync New Content / Force Unlock notifications by guarding against undefined counts
- Poll server for exploration claimability before showing Collect button to prevent client/server desync
- Return authoritative materials list from craft API to prevent client desync causing false affordability
- Add test coverage for `sync-new-content` and `explore/claimable` endpoints

Closes #125
Closes #127
Closes #128

## Test plan

- [ ] Trigger a sync with new content and verify the notification shows a real count instead of `NaN`
- [ ] Start an exploration, wait for it to complete, and verify the Collect button only appears after the server confirms claimable
- [ ] Attempt to craft a recipe and verify the material counts in the UI update to match the server's authoritative values

 This issue was created with help from Hikari~ 🌸

Reviewed-on: #129
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #129.
This commit is contained in:
2026-03-24 13:20:37 -07:00
committed by Naomi Carrigan
parent 790d35420f
commit 6e573bea14
11 changed files with 484 additions and 49 deletions
+40 -36
View File
@@ -13,18 +13,22 @@ import { ConfirmationModal } from "../ui/confirmationModal.js";
type ActiveModal = "force-unlocks" | "hard-reset" | "sync-new-content" | null;
interface SyncNewContentResult {
achievementsAdded: number;
adventurersAdded: number;
bossesAdded: number;
bossRewardsPatched: number;
equipmentAdded: number;
explorationAreasAdded: number;
questRewardsPatched: number;
questsAdded: number;
upgradesAdded: number;
zonesAdded: number;
achievementsAdded: number | undefined;
adventurersAdded: number | undefined;
bossesAdded: number | undefined;
bossRewardsPatched: number | undefined;
equipmentAdded: number | undefined;
explorationAreasAdded: number | undefined;
questRewardsPatched: number | undefined;
questsAdded: number | undefined;
upgradesAdded: number | undefined;
zonesAdded: number | undefined;
}
const safeNumber = (value: number | undefined): number => {
return value ?? 0;
};
/**
* Builds a human-readable summary of what the sync-new-content operation added.
* @param result - The counts returned by the operation.
@@ -32,16 +36,16 @@ interface SyncNewContentResult {
*/
const buildSyncNewContentMessage = (result: SyncNewContentResult): string => {
const entries: Array<[ number, string ]> = [
[ result.zonesAdded, "zone(s)" ],
[ result.questsAdded, "quest(s)" ],
[ result.questRewardsPatched, "quest reward(s) patched" ],
[ result.bossesAdded, "boss(es)" ],
[ result.bossRewardsPatched, "boss reward(s) patched" ],
[ result.explorationAreasAdded, "exploration area(s)" ],
[ result.adventurersAdded, "adventurer tier(s)" ],
[ result.upgradesAdded, "upgrade(s)" ],
[ result.equipmentAdded, "equipment item(s)" ],
[ result.achievementsAdded, "achievement(s)" ],
[ safeNumber(result.zonesAdded), "zone(s)" ],
[ safeNumber(result.questsAdded), "quest(s)" ],
[ safeNumber(result.questRewardsPatched), "quest reward(s) patched" ],
[ safeNumber(result.bossesAdded), "boss(es)" ],
[ safeNumber(result.bossRewardsPatched), "boss reward(s) patched" ],
[ safeNumber(result.explorationAreasAdded), "exploration area(s)" ],
[ safeNumber(result.adventurersAdded), "adventurer tier(s)" ],
[ safeNumber(result.upgradesAdded), "upgrade(s)" ],
[ safeNumber(result.equipmentAdded), "equipment item(s)" ],
[ safeNumber(result.achievementsAdded), "achievement(s)" ],
];
const parts = entries.
filter(([ count ]) => {
@@ -60,14 +64,14 @@ const buildSyncNewContentMessage = (result: SyncNewContentResult): string => {
};
interface ForceUnlocksResult {
adventurersUnlocked: number;
bossesUnlocked: number;
equipmentUnlocked: number;
explorationUnlocked: number;
questsUnlocked: number;
storyUnlocked: number;
upgradesUnlocked: number;
zonesUnlocked: number;
adventurersUnlocked: number | undefined;
bossesUnlocked: number | undefined;
equipmentUnlocked: number | undefined;
explorationUnlocked: number | undefined;
questsUnlocked: number | undefined;
storyUnlocked: number | undefined;
upgradesUnlocked: number | undefined;
zonesUnlocked: number | undefined;
}
/**
@@ -77,14 +81,14 @@ interface ForceUnlocksResult {
*/
const buildForceUnlocksMessage = (result: ForceUnlocksResult): string => {
const entries: Array<[ number, string ]> = [
[ result.zonesUnlocked, "zone(s)" ],
[ result.questsUnlocked, "quest(s)" ],
[ result.bossesUnlocked, "boss(es)" ],
[ result.explorationUnlocked, "exploration area(s)" ],
[ result.adventurersUnlocked, "adventurer tier(s)" ],
[ result.upgradesUnlocked, "upgrade(s)" ],
[ result.equipmentUnlocked, "equipment item(s)" ],
[ result.storyUnlocked, "story chapter(s)" ],
[ safeNumber(result.zonesUnlocked), "zone(s)" ],
[ safeNumber(result.questsUnlocked), "quest(s)" ],
[ safeNumber(result.bossesUnlocked), "boss(es)" ],
[ safeNumber(result.explorationUnlocked), "exploration area(s)" ],
[ safeNumber(result.adventurersUnlocked), "adventurer tier(s)" ],
[ safeNumber(result.upgradesUnlocked), "upgrade(s)" ],
[ safeNumber(result.equipmentUnlocked), "equipment item(s)" ],
[ safeNumber(result.storyUnlocked), "story chapter(s)" ],
];
const parts = entries.
filter(([ count ]) => {