Skip to content

fix(collections): let Cancel at the game-version prompt end the install - #5

Draft
doodlum wants to merge 2 commits into
masterfrom
fix/collection-game-version-cancel
Draft

doodlum wants to merge 2 commits into
masterfrom
fix/collection-game-version-cancel

Conversation

@doodlum

@doodlum doodlum commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Problem

When a collection revision lists game versions that don't match the installed game, Vortex asks "Game version mismatch" with Cancel and Continue. On master, Cancel doesn't cancel:

  • After Install Now → Cancel: the install dialog stays up. The next driver update, for example an unrelated mod install, or a second Install Now installs the collection anyway, and it never reaches the review screen.
  • After Resume → Cancel: it installs at once.
  • While the prompt is still open: any driver update starts the install before the user has answered, and Continue then never reaches the review.

Independent QA reproduced all three in the app on master 031b81d. It was found while testing #1: on master, the checks that install holds off also stay suppressed for the rest of the session.

InstallDriver.startImpl sets the step to start before the prompt. On Cancel it only marked the install done and returned false. The collections extension's update handler calls continue() whenever it sees the step at start, so the next update began the install.

Change

extensions/collections/util/InstallDriver.ts:

  • Cancel ends the attempt through cancel(), as the install dialog's "Later" does. That returns the driver to idle, closes the dialog and releases the install's hold on the checks.
  • The step still becomes start where it always did. Each start attempt now carries a token, and canContinue() returns false at start until that attempt has finished preparing (revision info fetched, prompt answered). That stops the extension's auto-continue from starting the install early. A newer attempt's token isn't cleared by an older one.
  • After the prompt, an attempt that was paused or cancelled meanwhile ends there, whatever the answer.
  • The extension's update handler moved from collections/index.ts into InstallDriver.ts unchanged, as makeDriverUpdateHandler(api, driver), so the tests run the real handler. index.ts registers it at the same point.

InstallDriver.gameVersion.test.ts (new) has 8 tests through the collection harness and the real handler.

Behaviour changes

  • Cancel at the prompt behaves exactly like "Later".
    • On a resumed incomplete install, it also finishes that install session with a "cancelled" outcome, and emits cancel-dependency-install, which does nothing when nothing is queued.
  • InstallDriver.canContinue() returns false at start while an attempt is preparing.
  • A driver update while the revision info is being fetched no longer starts the install early. On master it could.
  • During the prompt the step is start on every path, as on master. So the install dialog, the finished dialog and the progress display behave as they did before.

Evidence

Head ac1f7d0, on master 031b81d. Tested on Windows 10.

In the app (independent QA, two rounds, production builds, the kit's fake Fallout 4, offline collections with a mismatched game version, a fresh profile per run):

Scenario master this PR
Install Now → Cancel → unrelated install installs the members idle within 0.5 s; nothing installs; checks run again
Unrelated install while the prompt is open, then Continue starts early, then stuck on "installing" nothing starts early; Continue reaches review
Resume from the "incomplete" notification → Cancel installs at once idle; nothing installs later
Collection A reviewed and Done, then B resumed with the prompt open, plus an unrelated install, then Cancel B starts behind the prompt no "installation complete" dialog for B and no installCompleted on it; Cancel returns to idle
Same, then Continue stuck, no review reaches B's review; installCompleted is written only then
Continue reaches review reaches review

Also holding on this PR:

  • a revision with no game versions;
  • Cancel then start again then Continue;
  • pause while the prompt is open, then Continue;
  • a second collection while the prompt is open (refused);
  • a double-click on Install Now (one prompt);
  • clicks and Tab behind the modal.

Combined with #1: the two merge cleanly, and 18 files with 273 tests pass together. In the combined build, Cancel and pause re-run the held-off checks exactly once.

Regression tests: InstallDriver.gameVersion.test.ts. Negative controls:

Control Tests failing
Master logic, with only the handler export 7/8 (Continue passes by design)
Gate removed 4/8
Old Cancel restored 3/8
Old step placement 2/8
Plain boolean instead of a per-attempt token 1/8
Guard removed 1/8

Scoped suite: vitest run src/extensions/collections: 17 files, 266 tests pass. Renderer typecheck and lint pass. oxfmt is clean.

pnpm run verify on ac1f7d0: fails only where master fails: 7 Windows icon-extraction tests (src/index.test.ts) that read notepad.exe on this machine. The formatter left the tree clean.

E2E (packages/e2e, the kit's runner, which fixes the fixture's main-window startup race for the run only): 25 passed and 3 failed (QA-106, QA-113, QA-128), with 38 skipped for lack of Nexus test-account credentials. That is identical to master: no regressions.

CI: all checks pass on this PR. The first run failed in @vortex/main's nativeCrashReporting.test.ts ("recovers stale claims", which backdates a file's mtime). That failure hit all six PRs moved here, in code none of them touches. It passes 3 of 3 locally on upstream master and passed on the rerun.

pr-preflight: 370 lines in 3 files. Its callers and readers warnings were each reviewed:

  • mStarting is read only by canContinue.
  • Every reader of mInstallDone also checks the collection or the step.

Review

  • Round 1 (QA in the app): the fix held, but it found a regression. With the step moved below the prompt, a collection started right after a completed install showed a false "installation complete" dialog and was stamped installCompleted. It also found that the test's copy of the update handler left out the review branch. Both are fixed in ac1f7d0, which restores the step placement, adds the token gate and runs the real handler.
  • Round 2 (QA in the app): round 1's regression no longer reproduces, and every round-1 case holds. It asked for the disclosures above.

Findings by the layer that should have caught them: round 1, 2 author, 2 judgment; round 2, 1 author, 2 judgment.

Not covered

🤖 Generated with Claude Code

doodlum and others added 2 commits September 24, 2026 03:31
When a collection revision lists game versions that do not match the
installed game, the driver asks whether to continue. It had already moved
to its "start" step, and Cancel only returned early, leaving the step and
the collection in place. The collections extension continues any driver
update that finds it on "start", so the next update, a second "Install
Now", or (when resuming) the driver's own update straight after, began the
install anyway. An update while the prompt was still open did the same.

Cancel now cancels the driver, as the install dialog's "Later" does, which
also releases the check suppression the install took. The driver moves to
"start" only once nothing is left to ask, and an attempt that was paused
or cancelled while the prompt was open stops whatever the answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mpt is open

Setting "start" only after the prompt left whatever step the driver was on
before visible while the revision info loaded and the prompt was open. After
a finished install that is "review", so a collection resumed right after one
showed "Collection installation complete" behind the prompt, got an
installCompleted stamp on the next driver update, and could keep that dialog
after Cancel.

Set "start" where it always was, and instead make continue() wait at "start"
until the attempt that set it has prepared the install and the prompt is
answered. That is what stops the extension's update handler beginning the
install early. Each attempt keeps its own mark, so answering a paused
attempt's prompt cannot release a newer one. The mark is set around
startInstall's callers, leaving startInstall itself as it was.

Move the extension's driver update handler into InstallDriver.ts as
makeDriverUpdateHandler, so the tests run the real handler, including its
"review" branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

This PR has been marked as stale due to inactivity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant