extensions_ui: Apply category filter to dev extensions (#48954)

chenwuji2000-cyber , Claude , and Marshall Bowers created

Following up on #49183 which fixed the category filter for remote
extensions.

This PR applies the same category filter logic to **dev extensions**.
Previously, dev extensions were always shown regardless of the selected
category filter (e.g., "Themes", "Languages").

Changes:
- Add `filtered_dev_extension_indices` to track which dev extensions
match the active `provides_filter`
- Add `dev_extension_matches_provides()` helper to map
`ExtensionManifest` fields to `ExtensionProvides` variants
- Update `render_extensions()` and list count to use filtered dev
extension indices

Release Notes:

- Fixed extension category filter not applying to dev extensions in the
extensions panel.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>

Change summary

crates/extensions_ui/src/extensions_ui.rs | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)

Detailed changes

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -320,6 +320,7 @@ pub struct ExtensionsPage {
     remote_extension_entries: Vec<ExtensionMetadata>,
     dev_extension_entries: Vec<Arc<ExtensionManifest>>,
     filtered_remote_extension_indices: Vec<usize>,
+    filtered_dev_extension_indices: Vec<usize>,
     query_editor: Entity<Editor>,
     query_contains_error: bool,
     provides_filter: Option<ExtensionProvides>,
@@ -381,6 +382,7 @@ impl ExtensionsPage {
                 filter: ExtensionFilter::All,
                 dev_extension_entries: Vec::new(),
                 filtered_remote_extension_indices: Vec::new(),
+                filtered_dev_extension_indices: Vec::new(),
                 remote_extension_entries: Vec::new(),
                 query_contains_error: false,
                 provides_filter,
@@ -493,6 +495,19 @@ impl ExtensionsPage {
                 })
                 .map(|(ix, _)| ix),
         );
+
+        self.filtered_dev_extension_indices.clear();
+        self.filtered_dev_extension_indices.extend(
+            self.dev_extension_entries
+                .iter()
+                .enumerate()
+                .filter(|(_, manifest)| match self.provides_filter {
+                    Some(provides) => manifest.provides().contains(&provides),
+                    None => true,
+                })
+                .map(|(ix, _)| ix),
+        );
+
         cx.notify();
     }
 
@@ -601,14 +616,15 @@ impl ExtensionsPage {
         cx: &mut Context<Self>,
     ) -> Vec<ExtensionCard> {
         let dev_extension_entries_len = if self.filter.include_dev_extensions() {
-            self.dev_extension_entries.len()
+            self.filtered_dev_extension_indices.len()
         } else {
             0
         };
         range
             .map(|ix| {
                 if ix < dev_extension_entries_len {
-                    let extension = &self.dev_extension_entries[ix];
+                    let dev_ix = self.filtered_dev_extension_indices[ix];
+                    let extension = &self.dev_extension_entries[dev_ix];
                     self.render_dev_extension(extension, cx)
                 } else {
                     let extension_ix =
@@ -1821,7 +1837,7 @@ impl Render for ExtensionsPage {
             .child(v_flex().px_4().size_full().overflow_y_hidden().map(|this| {
                 let mut count = self.filtered_remote_extension_indices.len();
                 if self.filter.include_dev_extensions() {
-                    count += self.dev_extension_entries.len();
+                    count += self.filtered_dev_extension_indices.len();
                 }
 
                 if count == 0 {