fix(skills): prevent duplicate imports when import button is double-clicked#2211
Merged
Merged
Conversation
…licked Closes farion1231#2139 Two related defects let the installed-skills count balloon when users tap the import confirm button multiple times — either deliberately or because the button is still clickable while a slow import is in flight: - The confirm button only disabled itself while `selected.size === 0`, so it stayed clickable during a pending mutation. Each extra click triggered another `importFromApps` mutation. - `useImportSkillsFromApps` appended the server response to the installed cache without deduping, so re-firing the mutation stacked the same skills into the list again. Disable the confirm (and cancel) buttons while the mutation is pending — matching the `isRestoring` / `isDeleting` pattern already used by `RestoreSkillsDialog` — and merge success payloads by `InstalledSkill.id` so repeated results overwrite rather than accumulate. The merge is extracted as a pure `mergeImportedSkills` reducer to make the behaviour unit-testable and to short-circuit on an empty payload, returning the existing reference so React Query does not notify subscribers about a no-op cache update.
farion1231
approved these changes
Apr 23, 2026
farion1231
left a comment
Owner
There was a problem hiding this comment.
Thank you for your contribution!
ManLOK-Chu
pushed a commit
to ManLOK-Chu/cc-switch
that referenced
this pull request
May 11, 2026
…licked (farion1231#2211) Closes farion1231#2139 Two related defects let the installed-skills count balloon when users tap the import confirm button multiple times — either deliberately or because the button is still clickable while a slow import is in flight: - The confirm button only disabled itself while `selected.size === 0`, so it stayed clickable during a pending mutation. Each extra click triggered another `importFromApps` mutation. - `useImportSkillsFromApps` appended the server response to the installed cache without deduping, so re-firing the mutation stacked the same skills into the list again. Disable the confirm (and cancel) buttons while the mutation is pending — matching the `isRestoring` / `isDeleting` pattern already used by `RestoreSkillsDialog` — and merge success payloads by `InstalledSkill.id` so repeated results overwrite rather than accumulate. The merge is extracted as a pure `mergeImportedSkills` reducer to make the behaviour unit-testable and to short-circuit on an empty payload, returning the existing reference so React Query does not notify subscribers about a no-op cache update.
dfbb
pushed a commit
to dfbb/cc-switch
that referenced
this pull request
May 20, 2026
…licked (farion1231#2211) Closes farion1231#2139 Two related defects let the installed-skills count balloon when users tap the import confirm button multiple times — either deliberately or because the button is still clickable while a slow import is in flight: - The confirm button only disabled itself while `selected.size === 0`, so it stayed clickable during a pending mutation. Each extra click triggered another `importFromApps` mutation. - `useImportSkillsFromApps` appended the server response to the installed cache without deduping, so re-firing the mutation stacked the same skills into the list again. Disable the confirm (and cancel) buttons while the mutation is pending — matching the `isRestoring` / `isDeleting` pattern already used by `RestoreSkillsDialog` — and merge success payloads by `InstalledSkill.id` so repeated results overwrite rather than accumulate. The merge is extracted as a pure `mergeImportedSkills` reducer to make the behaviour unit-testable and to short-circuit on an empty payload, returning the existing reference so React Query does not notify subscribers about a no-op cache update.
delta-lo
pushed a commit
to delta-lo/cc-switch-mod
that referenced
this pull request
May 31, 2026
…licked (farion1231#2211) Closes farion1231#2139 Two related defects let the installed-skills count balloon when users tap the import confirm button multiple times — either deliberately or because the button is still clickable while a slow import is in flight: - The confirm button only disabled itself while `selected.size === 0`, so it stayed clickable during a pending mutation. Each extra click triggered another `importFromApps` mutation. - `useImportSkillsFromApps` appended the server response to the installed cache without deduping, so re-firing the mutation stacked the same skills into the list again. Disable the confirm (and cancel) buttons while the mutation is pending — matching the `isRestoring` / `isDeleting` pattern already used by `RestoreSkillsDialog` — and merge success payloads by `InstalledSkill.id` so repeated results overwrite rather than accumulate. The merge is extracted as a pure `mergeImportedSkills` reducer to make the behaviour unit-testable and to short-circuit on an empty payload, returning the existing reference so React Query does not notify subscribers about a no-op cache update.
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.
Summary / 概述
When a user taps the "Import" confirm button multiple times in the Skills import dialog — either deliberately or because the UI lags while importing many skills — the installed-skills count balloons to a multiple of the real count.
Two related defects combined to produce this:
selected.size === 0, so every extra tap fired anotherimportFromAppsmutation.useImportSkillsFromAppsappended results to the installed cache without deduping. Re-firing the mutation stacked the same skills into the list.Changes
ImportSkillsDialoggains anisImportingprop; the Import and Cancel buttons are disabled while the mutation is pending. This follows theisRestoring/isDeletingpattern already used byRestoreSkillsDialogin the same file.mergeImportedSkills(existing, imported)in a newsrc/hooks/useSkills.helpers.tsso it can be unit-tested without React / React Query setup. It dedupes byInstalledSkill.id(newer entries replace stale ones) and short-circuits to the original reference whenimportedis empty so React Query does not notify subscribers about a no-op update.Scope
Only
useImportSkillsFromAppsis touched here.useInstallSkillanduseInstallSkillsFromZipalso perform naive cache appends but are not reachable from a dialog double-click in the same way, and changing them is out of scope for this fix — happy to open a follow-up if desired.Related Issue / 关联 Issue
Fixes #2139
Screenshots / 截图
N/A — the behaviour change is functional. Reproduction steps and root cause are documented in the linked issue; after the fix the confirm button goes disabled on first click and the installed count matches the imported count regardless of how many times the button is tapped.
Checklist / 检查清单