Fix learnset levels and scroll-position loss on detail visits

MovesList: a move can have several version_group_details for one game —
one per learn method, plus a Gen 1/2 data artifact that repeats a
level-up move at level 1 next to its real level (e.g. Gyarados / Dragon
Rage in Red-Blue: level 1, level 25 and a machine entry). The old
`.find()` grabbed the first, so Dragon Rage showed "Lv 1". Now collect
every matching detail, group by method, and keep the highest level per
method — a move can also legitimately land in both "By level up" and
"By TM / HM".

PokemonDetail: the recently-viewed write used the function form of
ui.set(), which *replaces* state instead of merging. Every Pokémon visit
was wiping feedScroll (so Back returned to the top of the dex) along with
the saved sort / filters / tab. Spread `...s`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
This commit is contained in:
chris 2026-08-28 21:42:42 -04:00
parent 344e641475
commit 99d472f469
2 changed files with 28 additions and 10 deletions

View File

@ -86,18 +86,33 @@ export function MovesList(pokemonMoves, { versionGroupKey, gen = 9, genOfVg = ()
const byMethod = new Map();
for (const entry of pokemonMoves) {
const detail = entry.version_group_details.find(
const details = entry.version_group_details.filter(
(d) => d.version_group.name === versionGroupKey,
);
if (!detail) continue;
const method = detail.move_learn_method.name;
if (!byMethod.has(method)) byMethod.set(method, []);
byMethod.get(method).push({
id: idFromUrl(entry.move.url),
name: entry.move.name.replace(/-/g, ' '),
level: detail.level_learned_at,
data: null,
});
if (!details.length) continue;
// One move can list several details for a single game: one per learn
// method, plus Gen 1/2 data artifacts that repeat a level-up move at
// level 1 (with a non-null `order`) alongside its real learn level.
// Collapse to one entry per method, keeping the highest level.
const perMethod = new Map();
for (const d of details) {
const method = d.move_learn_method.name;
const prev = perMethod.get(method);
if (!prev || (d.level_learned_at || 0) > (prev.level_learned_at || 0)) {
perMethod.set(method, d);
}
}
for (const [method, d] of perMethod) {
if (!byMethod.has(method)) byMethod.set(method, []);
byMethod.get(method).push({
id: idFromUrl(entry.move.url),
name: entry.move.name.replace(/-/g, ' '),
level: d.level_learned_at,
data: null,
});
}
}
if (byMethod.size === 0) {

View File

@ -121,7 +121,10 @@ export async function PokemonDetail(nationalId) {
}
// Recently-viewed history for the feed strip (most recent first, capped).
// Spread the previous state — the function form of set() replaces rather
// than merges, so dropping `...s` here would wipe feedScroll, sort, etc.
ui.set((s) => ({
...s,
recent: [nationalId, ...(s.recent || []).filter((x) => x !== nationalId)].slice(0, 12),
}));