fix: one rule for what counts as an installed extension - #119
Merged
Conversation
Overview, Extensions and Audit each answered "how many extensions do you have?" differently β 191 / 206 / 206 / 216 on the same machine. Two independent causes: - Audit grouped its rows by recomputing extensionGroupKey() per instance, which skips the sibling merge buildGroups() applies. A sourceless copy and its URL-carrying twin rendered as two rows for one extension, so the header reported more results than extensions scanned (10 split rows on a real install). - Overview dropped rows living only on switched-off agents; Extensions did not look at agent enablement at all β even though its own agent dropdown has always listed enabled agents only, so those rows could not be filtered to, yet still padded the total. Route every count through enabledAgentSet() + groupHasEnabledAgent(), and give Audit the canonical id -> groupKey map via groupKeyById(). The enablement rule is group-level on purpose: a skill installed on both Claude and a disabled Windsurf stays, with every instance intact, so deleting it still cleans up the Windsurf copy on disk. The badge column and the detail panel's AGENTS chips take the same projection; PATHS and the delete dialog deliberately do not, so deleting a row can never quietly remove a file the user was never shown. Fixed in passing: Overview's "recently installed" deep link recomputed the group key the same wrong way and landed on nothing, and audit results whose extension no longer exists were counted under their raw ID in All-scopes mode. Result: Overview and Extensions agree in All scopes, Audit's two header numbers are one set counted twice, and a scoped view differs from Overview only by scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merged
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.
Problem
Overview, Extensions and Audit each answered "how many extensions do you have?" differently. On one real install, at the same moment:
Two independent causes.
1. Audit disagreed with itself. The "scanned" count went through
buildGroups(); the row list recomputedextensionGroupKey()per instance. Those are not the same thing βbuildGroups()has a pre-pass that folds a sourceless copy into its same-scope, URL-carrying sibling. Skipping it splits those rows in two, so one extension rendered as two audit rows (10 of them on the install above): twoarxiv-search, threefrontend-design, and so on.2. Overview and Extensions disagreed about what exists. Overview dropped rows belonging only to switched-off agents; the Extensions list never looked at agent enablement. The Extensions agent dropdown has always listed enabled agents only β so those rows could not even be filtered to, yet still padded the total.
Change
One rule, defined once, used by all three surfaces:
enabledAgentSet()β the agents a count should include, ornullfor "list not fetched yet, don't hide anything".groupHasEnabledAgent()β does this row live on at least one of them.groupKeyById()β the canonicalinstance id β groupKeymap, for anything that starts from a bare extension ID.The enablement rule is group-level on purpose: a skill installed on both Claude and a disabled Windsurf stays, with every instance intact, so deleting it still cleans up the Windsurf copy on disk. Only a row that lives nowhere enabled drops out.
The agent-badge column and the detail panel's AGENTS chips take the same projection, so a disabled agent isn't simultaneously absent (its exclusive rows are gone) and present (its badge on surviving rows). PATHS and the delete dialog deliberately do not β they answer "what is on disk", so deleting a row can never quietly remove a file the user was never shown.
Fixed in passing, both the same root cause:
Effect
Replayed against the same install (753 extension rows):
Testing
tsc --noEmitclean, biome clean on touched files, 306 frontend tests pass (10 new, covering the enablement rule, the null "not loaded" case, the badge projection, and thegroupKeyByIdregression that split one extension into two rows).π€ Generated with Claude Code