Conversation
Eight prompts spelled out the same shell: a question, a muted consequence line, and a ButtonsWrapper holding a secondary Cancel and a confirm. Six were inline, two were already components around the same shape. ConfirmDialog holds it once. Cancel stays first in the DOM, so the focus trap still lands on it. The Manage Backups delete prompt moves from NormalButton to FormButton, which is what the other seven already used.
The language picker, the UI scale picker and the ModDB count picker each spelled out the same Listbox scaffold: a trigger reading off the picked option, an AnimatePresence, a motion list and a styled option row. SelectMenu grows a hint for the muted note beside a label, a listSize for the one list long enough to need a scrollbar, and a title for a row whose description doubles as its tooltip. Its trigger now renders from a find instead of a filtered map, so picking an option no longer replaces the button node and a stored value outside the options still leaves a usable control.
It had one caller left once the picker moved onto SelectMenu.
This PR does not touch ActivityCenter.tsx or its test, but CI on ubuntu still caught this test racing AnimatePresence's exit animation: the region only leaves the DOM once the 200ms exit finishes, and under CI load that outran even the 5s waitFor timeout (same root cause as #504, already fixed the same way on the unrelated PR #509). Popover.Panel's own Escape handler closes and returns focus synchronously; assert on aria-expanded and focus, which is what Escape actually controls, instead of waiting on the animation's timing.
Like the Escape case, this test is untouched by this PR but still caught a CI load flake: useManageInstalledMods's rescan effect occasionally fires an extra, idempotent time once a switch releases _updatingMods, on top of the one it is expected to. Nothing about that changes what lands on the folder or what the player sees, so pin the meaningful event sequence (the scan the switch itself does, both writes, the renames) and only require every event after it to be another harmless rescan, instead of an exact count that races the same load-dependent timing. Root-causing that extra rescan is tracked separately; it is not part of this refactor's renderer shells.
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.
Three of the five findings in #489 already landed on dev while the audit was being written, so this pass delivers the two that were left: the confirm dialog shell (item 2) and the rest of the select menu fold (item 3).
What changes
Item 2: one
ConfirmDialoginstead of eight copies of the same prompt (src/renderer/src/components/ui/ConfirmDialog.tsx, 65 lines).Six inline prompts and two wrapper components each spelled out the same block: a question, a
text-zinc-400consequence line, and aButtonsWrapper className="text-base" bgDark={false} equalWidth flushholding a secondary Cancel withPiXCircleDuotoneand a confirm. All eight now call the shell:ManageInstallationBackups.tsxrestore, deleteListInstallations.tsxdeleteListVersions.tsxuninstall/unlink, restore vanilla, version in useManageInstallationServers.tsxremove serverDeleteModDialog.tsx,RemoveServerModsDialog.tsxThe shell owns no copy but the Cancel button.
question,consequence,confirmLabelandconfirmIconare the caller's, because most of them read off what the player picked (ListVersionspicks all four offversionToDelete?.linked).confirmVariantdefaults to destructive and Restore Vanilla keeps its primary. Two content slots keep every prompt's DOM order exactly as it was:childrenbetween the question and the consequence (the mod name list, the truncated-scan note, the in-use warning box),beforeActionsjust above the buttons (the "delete data" checkbox).ListVersions.tsx's "VS Version in use" prompt was not on the audit's list of six. It is a seventh copy of the same shape in a file whose other two just moved, and it needed no extra prop, so it went with them. The rename dialog in that file stays onPopupDialogPanel: it is a form, not a confirm.Item 3: the last three hand-written select menus move onto
SelectMenu.LanguagesMenu.tsx(86 lines to 29),UIScaleandModDbCountPickerinConfigPage.tsx(52 and 41 lines to one call each) each carried their own Listbox,AnimatePresence,motion.uland option row.SelectMenugrew three optional props to take them:hinton an option for the muted note beside a label (a locale's translator, the default scale),listSizefor the one panel long enough to need a scrollbar, andtitlefor the ModDB row whose description doubles as its tooltip.ModDbCountPickerwas a fifth copy the audit did not list; it is in the same file asUIScaleand fits with no prop of its own beyond that tooltip.Its trigger now renders from
options.findinstead ofoptions.filter().map(). That idiom rebuilt the button node whenever the value changed, which takes the node out from under the focus Headless UI hands back after a pick, and rendered no trigger at all for a stored value outside the options.git diff --shortstat origin/dev...HEAD: 10 files changed, 262 insertions, 332 deletions.What stays
NormalButton/FormButton/LinkButton/FormLinkButton) already landed in8e4f6be9on 15 September.ButtonandButtonLinkhold the body, the four presets set the defaults, the rest slot andFormButtonActionare intact.TagsFilterandVersionsFilteronto one generic) already landed in10179be1:components/ui/MultiSelectFilter.tsxexists and both filters are 33 and 32 lines.4a104e59:InstalledModsSelectFilter.tsxandInstalledTagsFilter.tsxboth importMENU_TRIGGER_STYLESandMENU_OPTION_STYLES.InstalledModsSelectFilterdoes not become a preset overSelectMenu. Its trigger shows a placeholder intext-zinc-400when nothing is picked and carries atitle, and its first row is a clear option that is not one of the options. Folding it in costs three override props on the generic to save about twenty lines, which is the trade the audit warns against forInstalledTagsFilter.InstalledTagsFilterstays out for the reasons Fold the repeated renderer shells into one component each #489 gives.LaunchBackupPromptstays out, as the audit's own correction says.aria-busy/aria-pressed/ariaExpanded, the focus-visible outline, the contrast floorstests/text-contrast.test.tsmeasures (it reads the shared constants and the files it names, none of which this pass moves), the hexagonal split, the path policy, the IPC validation and the mutation-tested guards.Behaviour
No test was added, changed or deleted. The pinning tests pass as they stand:
installationsDelete,installationsRestoreBackup,listVersions,manageInstallationServers,manageModsServerMods,manageMods,launchPlayGame(itsbuttons[0]is Cancel assertion),listModsFilterBar,modsListModsFilterUpdate,modsFilterLookupFailure,configPageModDbCount(which finds the ModDB trigger by its tooltip),selectableItemA11y,actionBusyStateandtext-contrast.Callers checked by grep before each move: every
PopupDialogPanelcall in the four pages, every import of the two dialog components, and everySelectMenucaller. No exported function lost a caller; nothing was deleted that anything still calls.Two changes a reviewer should see rather than take on trust:
NormalButtonwhere the other seven usedFormButton. It now usesFormButtonlike the rest, which addsoverflow-hiddento those two buttons and nothing else.SelectMenuno longer replaces the trigger node, so focus comes back to it. Verified by keyboard on the UI Scale row in the packaged build.Gate, against dev's own figures:
typechecklint:ciformat:checkgit merge-treeagainst every open PR branch: no conflicts.Headless check of the packaged build
npm run build:unpack, launched headless on a throwaway profile (two installations, two game versions, four mods, two backups, two server bookmarks), driven over CDP at 1280x720 and 1024x600. Every dialog and menu below was opened, read, closed with Escape and, where it has one, tabbed through. No renderer error in the log. Language switching still writeslangto localStorage and the UI scale still writesuiScaleanddata-uiscale.childrenslot)hint)Closes #489. Part of #492.