Conversation
expectedGameExecutables returned the file names that gameExecutableCandidates then mapped to launch modes, so one table was spelled twice and a test existed only to assert the two agreed. Fold the names into the candidate table and have the install check read fileName off it.
hasActiveInstalledModFilters restated the three-axis OR that the count function below it already sums. Ask the count instead, so a fourth axis cannot land in one and not the other.
Both options bags were filled by the test file alone: every production call site passed none. Drop WhatsNewLimits and SelectReleasesOptions and read the constants directly. The truncation and block-cap branches keep their coverage against the real 2000-character and 120-block caps instead of a cheap override, and the block cap gains its exact-fit boundary case.
parseWhatsNewVersion, compareIdentifiers and compareWhatsNewVersions spelled out semver precedence over 65 lines: dotted release parts, prerelease split, numeric identifiers as numbers, numeric before alphanumeric, a release above its own prerelease. semver is a production dependency that detect.ts, compatibility.ts and health.ts already read versions with, so ask it instead, behind the valid guard gameVersionOrder.ts ships for the same problem. A tag semver refuses sorts below every tag it accepts, so it stays out of the dialog's window. That is stricter than the coerced parse rather than the same ranking: Number(part) || 0 read the leading run of a tag as a number, so 1.8 ranked between 1.9.0 and 1.7.0 and 2024-06-01 ranked above every 1.x tag, where both now sort last. A tag opening with a word coerced to 0 and already sorted there. Every tag this project has published is valid semver, so no published release moves.
No code change: the shorter names and the dropped parameters left three paragraphs wrapping mid-sentence.
The valid guard's three fallback lines had no test: coverage named 291 to 293 of whatsNew.ts as the only uncovered lines in the file. Two cases now hold them. A tag shaped v1.8 stays outside the dialog's window instead of falling between 1.9.0 and 1.7.0 where the coerced parse used to rank it, and a list holding several tags semver refuses renders them in the same order whichever order GitHub answered in. The doc comment said what the comparator does but not what it stopped doing. It now names the shapes that move and says nothing upstream constrains a tag: netHandlers.ts keeps any non-empty tag_name of up to 128 characters.
Pixnop
force-pushed
the
refactor/485-domain-reuse
branch
from
September 20, 2026 11:19
d418ed8 to
cfe8505
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The four findings of #485, in the order the issue suggests. Refactor only: no behaviour change intended anywhere, and no test was relaxed to make a change pass.
What changes
One executable table in the versions domain.
src/domain/versions/gameExecutable.tsgoes from 77 lines to 62.expectedGameExecutables(os)returned the file names thatgameExecutableCandidates(os)then mapped to launch modes, so one table was spelled twice and a test existed only to assert the two agreed with each other. The candidate list is the table now, andgameLandedinsrc/domain/versions/install.tsreads.fileNameoff it, which was the only caller outside the pair.tests/domain/versions/gameExecutable.test.tsgoes from 64 lines to 35.docs/architecture.mdanddocs/vintage-story-quirks.mdboth named the deleted function and were updated.One definition of an active mod filter. In
src/domain/mods/installedFilters.ts,hasActiveInstalledModFiltersrestated the three-axis OR thatcountActiveInstalledModFilterssums right below it. It asks the count now (> 0), so a fourth axis cannot land in one and not the other. Two lines, one duplicated definition.The What's new caps read straight from the module.
WhatsNewLimitsandSelectReleasesOptionswere filled by the test file alone: all three production call sites, insrc/renderer/src/features/info/hooks/useWhatsNew.tsat lines 55, 99 and 142, passed no options at all. Both interfaces are gone, with the three parameters that took them.DEFAULT_WHATS_NEW_LIMITSwent with them: nothing outside the module imported it, and with no override left the bag was two numbers, so it isMAX_BLOCKSandMAX_BLOCK_LENGTHas plain module constants.capBlockTextlost its length parameter for the same reason, one caller and one value. About 15 lines.semver orders the releases.
parseWhatsNewVersion,compareIdentifiersandcompareWhatsNewVersionsspelled out semver precedence over 65 lines: dotted release parts, prerelease split, numeric identifiers sorting as numbers, numeric before alphanumeric, a plain release outranking its own prerelease.semveris a production dependency thatsrc/domain/versions/detect.ts,src/domain/mods/compatibility.tsandsrc/domain/mods/health.tsalready read versions with in this same layer, so the three functions are one 8-line comparator:semver.comparebehind asemver.validguard, the shapesrc/renderer/src/utils/gameVersionOrder.tsships for the same problem.whatsNew.tsgoes from 402 lines to 359.Reviewer's call on that last one, and the reason it is listed last here too: PR #442 recorded a deliberate decision to hand-roll this rather than take the dependency, comparing it to
betaUpdates.tsreading a prerelease with a string check. That decision is recent. If it still stands, drop the last commit (Order What's new releases with semver) and the other three are independent of it.What stays
Nothing was skipped outright, but two halves of the issue's own suggestions were not taken.
The issue proposes moving
compareGameVersionsDescout of the renderer and into the domain so both call sites share one comparator. Not done: the two want opposite directions, and deriving one from the other by swapping the arguments also swaps the both-unparseable tie-break, whichgameVersionOrder.tsdocuments as alphabetical so the game version list renders the same way every time. Negating that is a behaviour change in the renderer for no line saved. The two comparators now have the same shape and the domain one says where the other lives.The issue's own "not recommended" note on finding 4, un-exporting
matchesInstalledModFilters, is not done either, for the reason it gives: routing its twelve direct test call sites throughfilterInstalledModscosts more code than it saves.Untouched, as the issue's out-of-scope line requires: the hexagonal split,
src/ipc/pathPolicy.ts, the IPC validation at the boundary, the mutation-tested guards and the accessibility wiring.tests/security-boundaries.test.ts,tests/log-provenance.test.ts,tests/text-contrast.test.tsandtests/i18n/i18n-parity.test.tsare unchanged and pass.One follow-up for someone else: #457 cites
expectedGameExecutablesby name and by line range as existing infrastructure for its planned feature. It wantsgameExecutableCandidates(os).map((candidate) => candidate.fileName)now, or the candidates directly.Behaviour
Every caller was grepped before each cut.
expectedGameExecutableshad one caller outside its own pair (install.ts:101) and two docs references.WhatsNewLimits,SelectReleasesOptions,maxReleasesandDEFAULT_WHATS_NEW_LIMITShad no production reader left after the parameters went, whichgrepoversrc,testsanddocsconfirms returns nothing.Five tests were deleted, all in
tests/domain/versions/gameExecutable.test.tsand all because the function they tested is gone:gameExecutableCandidatescases assert, which are exactdeepEqualon the whole list.deepEqualpins the same order.No other test was touched to make a change pass. The seven
whatsNewassertions that used the option overrides now reach the same branches through the real caps: a body over 2000 characters for the word-boundary cut, 3000xwith no space to back up to, 2000 emoji for the surrogate-pair cut, and 200 bullets for the block cap. The block cap gained its exact-fit boundary case (120 blocks, no ellipsis block), which the cheap override never reached. The two cases that testedmaxReleasesitself went away with it.On the semver swap:
semver.validrefuses a tag the old parser coerced throughNumber(part) || 0, and that is stricter rather than the same ranking. An earlier revision of this section claimed the two put a refused tag in the same place. They do not, and the correction is below.Every tag this project has published parses once
stripVersionPrefixhas run, so no published release moves: running both comparators over all 41 published tags, every ordered pair agrees. What moves is a tag semver refuses that the old parse could still read a number out of.1.8ranked between1.9.0and1.7.0and now sorts below both.2024-06-01ranked above every 1.x tag and now sorts below them too. A tag opening with a word,nightlyand the like, coerced to 0 and already sorted last, so those are unchanged, and two of them against each other are alphabetical now rather than the old dead heat.That fallback is reachable on data fetched from GitHub, because nothing upstream constrains the shape of a tag:
src/ipc/handlers/netHandlers.ts:281keeps any non-emptytag_nameof up to 128 characters. It is pinned by two tests now, listed in Check below.tests/domain/appUpdate/whatsNew.test.ts:356(beta.10 after beta.9, not equal to it) and:364(a release outranking its own prerelease) pass unchanged, as do all tests undertests/domain/appUpdate/.Gate, against
origin/devat the same commit:typechecklint:ciformat:checkEvery denominator shrank, which is the point.
git merge-treeagainst all nine open branches reports no conflict, #514 included, which is the only one that also touchessrc/domain/versions/install.ts.Check
One finding from review, applied in
Pin where a tag semver refuses lands in the What's new order.The semver fallback reorders a tag semver refuses, the opposite of what this body claimed, and nothing tested it. Correct on both counts, and the claim was the worse half: the coerced parse read the leading run of a tag as a number, so it ranked
1.8among the tags semver accepts rather than below them. Sortingv1.9.0,v1.8andv1.7.0ondevlists all three; before this fix it droppedv1.8out of the list, and out of the dialog's window, with no test to notice.src/domain/appUpdate/whatsNew.ts:286says the same thing at the comparator, which described what it does but not what it stopped doing, and records thatnetHandlers.tsleaves the tag shape unconstrained.tests/domain/appUpdate/whatsNew.test.ts:390pins the exclusion: a release taggedv1.8stays outside the window between1.7.0and1.9.0instead of falling inside it.tests/domain/appUpdate/whatsNew.test.ts:446pins the order among refused tags, asserted from both input orders so it holds whichever way GitHub answers. Letters only, no punctuation whose collation could differ between the Linux and Windows runners.The ranking itself stands rather than being restored.
semver.coercewas the obvious way to take the old lenience back and it is far too greedy to use here: it readsuntagged-abc123as123.0.0andbuild 42as42.0.0, which would rank a junk tag above every real release and pull it into the dialog. The old parse put those at 0 and so does the guard. Between guessing and refusing, refusing is the one that cannot show the wrong release notes.src/domain/appUpdate/whatsNew.tsis back to 100% statements and 100% lines, from 97.39% lines on the previous revision, where the three uncovered lines in the file were exactly this fallback.Closes #485. Part of #492.