From 96f273ce2bfabefebd6c2eceaa2021325c1f365b Mon Sep 17 00:00:00 2001 From: goober Date: Fri, 21 Aug 2026 12:30:56 +0300 Subject: [PATCH] fix: reachable undo, nameable groups, and no favicon request Found by running the app in a browser for the first time. - Undo was only reachable from the team tab, and only after a team solve had finished, so an equip made from the character view had no way back. Both views now show it whenever there is something to undo. - The owner-group naming dropdown listed all 23 espers from the game database rather than the characters the capture says you own. Two espers are both published as "Zero", so the list held two identical entries and picking the wrong one misattributed a whole loadout; conversely a character the game database has never heard of had no entry at all and its group could not be named. Options now come from the captured roster, labelled with the esper name and disambiguated by codename when two owned characters share one. - An inline SVG favicon, so the page stops requesting one that does not exist. Inline rather than a file because the single-file build must make no requests at all. Verified headless against the real 819-item export: import writes 819 items, 20 characters and 99 equipment rows to IndexedDB; a solve runs in a real Worker and scores 0.6690 for Zankou, matching the Node bench; equip writes 8 app rows and displaces 7; undo restores all 99 imported rows; state survives a reload. The file:// build renders, imports, solves inline and issues no network request. Co-Authored-By: Claude Opus 5 --- index.html | 1 + src/ui/CharacterView.tsx | 3 +++ src/ui/CharactersTab.tsx | 32 +++++++++++++++++++++++++++++--- src/ui/TeamTab.tsx | 5 ++++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index b0521e7..056c6d1 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + NTE gear optimizer diff --git a/src/ui/CharacterView.tsx b/src/ui/CharacterView.tsx index 12c4089..7a5c175 100644 --- a/src/ui/CharacterView.tsx +++ b/src/ui/CharacterView.tsx @@ -351,6 +351,9 @@ export function CharacterView({ Equip this build )} + {state.data.undo.length > 0 && ( + + )} {running && progress && ( diff --git a/src/ui/CharactersTab.tsx b/src/ui/CharactersTab.tsx index ae3982c..9f5dfb2 100644 --- a/src/ui/CharactersTab.tsx +++ b/src/ui/CharactersTab.tsx @@ -10,6 +10,7 @@ import { esperFor, type LoadedData } from "../state/gamedata.ts"; import { CharacterView } from "./CharacterView.tsx"; import { Icon } from "./Icon.tsx"; import { equipmentOf } from "../db/store.ts"; +import type { CharacterRow } from "../db/schema.ts"; import { hashFor, type Route } from "./router.ts"; import { useAppState, useStore } from "./useStore.ts"; @@ -60,6 +61,31 @@ export function CharactersTab({ ); } +/** + * What to call a character in the naming dropdown. + * + * Two espers are both published as "Zero", so a bare display name can send a + * whole loadout to the wrong character; where a name is shared the codename + * disambiguates it. Seven of the captured roster have no everness record at + * all, so their codename is all there is - which is also why the options come + * from the capture rather than from the esper list: a character the game says + * you own must be nameable even when the game database has never heard of it. + */ +function ownerLabel( + data: LoadedData, + characters: CharacterRow[], + characterId: string, +): string { + const esper = esperFor(data.gamedata, characterId); + if (!esper) return characterId; + const shared = characters.some( + (other) => + other.characterId !== characterId && + esperFor(data.gamedata, other.characterId)?.name === esper.name, + ); + return shared ? `${esper.name} (${characterId})` : esper.name; +} + /** * The owner groups nobody has named. * @@ -97,9 +123,9 @@ function UnidentifiedGroups({ groups, data }: { groups: string[]; data: LoadedDa }} > - {data.gamedata.espers.map((esper) => ( - ))} diff --git a/src/ui/TeamTab.tsx b/src/ui/TeamTab.tsx index 1dbf8b0..46fc333 100644 --- a/src/ui/TeamTab.tsx +++ b/src/ui/TeamTab.tsx @@ -223,6 +223,7 @@ function Result({ data: LoadedData; store: ReturnType; }) { + const state = useAppState(); if (result.infeasible) { return

No conflict-free team could be built from this pool.

; } @@ -243,7 +244,9 @@ function Result({

Equipping writes an “app” row you still have to apply in game.{" "} - + {state.data.undo.length > 0 && ( + + )}

);