fix(mods): guard readModDetail against a missing releases array - #210
Conversation
The install popup and the installed-mods scan both map or iterate releases directly, so a detail response without the array crashed them instead of reaching their failure states.
Pixnop
left a comment
There was a problem hiding this comment.
Approving. The guard sits at the right layer: readModDetail is the single door every consumer comes through, so one check there covers the install popup and useGetCompleteInstalledMods (which iterates dmod.releases directly and would have crashed the same way), instead of a guard per caller. The comment earns its place by saying why this field gets an explicit check when modid and name do not: the API is not known to always send it.
Both new rows assert the malformed-response shape like their siblings, and the mutation is obvious enough that I did not need to run it: delete the Array.isArray line and both rows return a parsed detail instead of the refusal.
Coordination note, my fault not yours: I had an agent picking up #199 alongside #200 and #201 from the same review batch. I have pulled #199 out of its scope, so the branch coming for those two will not touch moddb.ts. Yours lands first.
Summary
A mod detail response whose
modobject has noreleasesarray crashed the Install Mod popup withTypeError: Cannot read properties of undefined (reading 'map'), becausereadModDetailonly validatedmodidandname. The reader now rejects that shape up front, so the response flows through the existing malformed-response failure path and the popup shows its failure state instead of crashing.The same guard also protects
useGetCompleteInstalledMods, which iteratesdmod.releasesdirectly and would have hit the same crash for installed-mod details missing the field.Two unit tests cover the new cases: a detail with no
releaseskey, and one wherereleasesis not an array. Both assert the malformed-response result like their sibling tests.Type
Checklist
dev, notmain.npm run typecheckpasses.npm run lint:cipasses.npm run format:checkpasses.npm run test:coveragepasses, coverage at or above the floor invitest.config.ts.npm run build:unpackpasses.As with #205,
build:unpackwas left to CI; this touches one guard and two tests. Happy to run it locally if reviewers prefer.Testing
Ran on node v22.22.1 with the repo's TypeScript 5.9.2 (Linux node_modules, so commands ran under WSL):
npm run typecheck: clean across node, web, and tests configs.npm run lint:ci: 0 errors; 15 warnings, all pre-existing exhaustive-deps findings in untouched files.npm run format:check: pass.vitest run tests/domain/mods/moddb.test.ts: 24 passed, including the two new cases.Manual trace: a detail payload without
releasesnow reaches InstallModPopup's failure UI with the reload button, instead of throwing at line 103.Related issues
Fixes #199