ListVersions sorts with semver.rcompare on version strings that nothing validates: the value comes from the game's -v probe (detect.ts trims stdout) and normalizeGameVersion only clamps it to 128 characters. rcompare throws on "1.20" or "Vintage Story 1.21.0", and since the sort runs at render, the versions page white-screens with the bad entry already persisted in config, so the UI cannot recover by itself.
Pre-existing, but the linked-versions feature (#120, PR #138) makes it likelier: registering not-yet-released builds is its stated use case, and unreleased builds are exactly where a nonstandard -v output shows up. Surfaced while reviewing #138.
Fix direction: validate at the boundary (refuse or coerce at registration time with a named verdict), and make the sort tolerant (semver.valid guard with a fallback comparator) so one bad entry can never take the page down.
ListVersions sorts with semver.rcompare on version strings that nothing validates: the value comes from the game's -v probe (detect.ts trims stdout) and normalizeGameVersion only clamps it to 128 characters. rcompare throws on "1.20" or "Vintage Story 1.21.0", and since the sort runs at render, the versions page white-screens with the bad entry already persisted in config, so the UI cannot recover by itself.
Pre-existing, but the linked-versions feature (#120, PR #138) makes it likelier: registering not-yet-released builds is its stated use case, and unreleased builds are exactly where a nonstandard -v output shows up. Surfaced while reviewing #138.
Fix direction: validate at the boundary (refuse or coerce at registration time with a named verdict), and make the sort tolerant (semver.valid guard with a fallback comparator) so one bad entry can never take the page down.