Skip to content

fix: one rule for what counts as an installed extension - #119

Merged
RealZST merged 1 commit into
mainfrom
fix/extension-count-consistency
Aug 18, 2026
Merged

fix: one rule for what counts as an installed extension#119
RealZST merged 1 commit into
mainfrom
fix/extension-count-consistency

Conversation

@RealZST

@RealZST RealZST commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Problem

Overview, Extensions and Audit each answered "how many extensions do you have?" differently. On one real install, at the same moment:

Surface Number
Overview 191 extensions
Extensions (Global scope) 206 results
Audit scanned 206 / 216 results

Two independent causes.

1. Audit disagreed with itself. The "scanned" count went through buildGroups(); the row list recomputed extensionGroupKey() 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): two arxiv-search, three frontend-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, or null for "list not fetched yet, don't hide anything".
  • groupHasEnabledAgent() β€” does this row live on at least one of them.
  • groupKeyById() β€” the canonical instance id β†’ groupKey map, 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:

  • Overview's "recently installed" deep link recomputed the group key the same wrong way and landed on nothing.
  • Audit results whose extension no longer exists were counted under their raw ID in All-scopes mode.

Effect

Replayed against the same install (753 extension rows):

Surface Before After
Overview (always All scopes) 191 214
Extensions Β· All scopes 214 214
Extensions Β· Global 206 206
Audit Β· Global 206 / 216 206 / 206
Audit Β· All scopes 214 / 224 214 / 214
  • Audit's two header numbers are now one set counted twice, not two.
  • Overview and Extensions agree in All scopes β€” and not by luck: before this change they only matched because no disabled agent happened to hold an exclusive extension.
  • In a scoped view the two differ by scope alone, which is the intended design (Overview is deliberately scope-independent).
  • Overview moving 191 β†’ 214 is the pre-existing desktop build treating an agent missing from its adapter list as disabled; on this branch it counts normally.

Testing

tsc --noEmit clean, biome clean on touched files, 306 frontend tests pass (10 new, covering the enablement rule, the null "not loaded" case, the badge projection, and the groupKeyById regression that split one extension into two rows).

πŸ€– Generated with Claude Code

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>
@RealZST
RealZST merged commit 69f6776 into main Aug 18, 2026
3 checks passed
@RealZST
RealZST deleted the fix/extension-count-consistency branch August 18, 2026 03:21
@RealZST RealZST mentioned this pull request Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant