Decompiler search box and member-type filter chips are inert — getFilteredNamespaces() ignores both #901

Closed
opened 2026-07-30 04:24:23 +00:00 by spikerj · 1 comment
Owner

Found while adding render coverage for decompiler.component.html (wave-5 coverage loop, angular branch test/component-coverage-wave5).

Symptom

In the .NET decompiler tool, after decompiling a DLL:

  • Typing in the type/member search box does nothing. The tree keeps showing every namespace and type.
  • Clicking the All / Methods / Properties / Fields / Events filter chips highlights the chip but does not change the member lists. Selecting "Methods" still shows fields, properties and events.

Both controls look functional (the search text persists, the chip gets an active style), which makes this read as "my search found everything" rather than "search is broken".

Root cause

DecompilerComponent.getFilteredNamespaces() (libraries/features/dev-tools-decompiler/src/lib/decompiler.component.ts:234) is the method the template iterates, and it never reads either filter signal:

getFilteredNamespaces(): unknown[] {
  const data = this.decompiledData();
  if (!data?.tree?.namespaces) return [];
  return Object.entries(data.tree.namespaces).map(([name, namespace]) => ({
    name,
    types: Object.entries(namespace.types).map(([typeName, typeInfo]) => ({ /* every field, unconditionally */ })),
  }));
}

searchQuery and selectedFilter are written by onSearchChange() / setFilter(), but across the whole 803-line template they appear in exactly two kinds of place:

  • [value]="searchQuery()" — the input echoing its own text back (line 426)
  • [class.active]="selectedFilter() === '…'" — chip styling (lines 436, 445, 454, 463, 472)

Nothing else consumes them. So the name getFilteredNamespaces is aspirational: it is an unfiltered projection.

Impact

The decompiler's whole purpose is exploring a large assembly's type tree. On a real DLL (dozens of namespaces, hundreds of types) search and filtering are the only practical navigation, and both are no-ops. Users are left scrolling and manually expanding.

Fix sketch

  • Make getFilteredNamespaces() a computed() that reads searchQuery() and selectedFilter():
    • case-insensitive substring match against namespace name, type name/fullName, and member names;
    • drop namespaces/types that end up with no matches (and consider auto-expanding matches, since a match hidden inside a collapsed node is still invisible);
    • selectedFilter should restrict which member collections are projected (methods → only methods, etc., all → all four).
  • It is called from @for in the template, so converting it to a computed() also removes a per-change-detection re-projection of the entire tree.

Current behavior is pinned

decompiler.render.spec.ts has two characterization tests referencing this ticket which assert the broken behavior (search leaves the result count unchanged; "Methods" still renders fields) so the suite stays green. Both are written to fail once filtering works — update them as part of the fix.

Found while adding render coverage for `decompiler.component.html` (wave-5 coverage loop, angular branch `test/component-coverage-wave5`). ## Symptom In the **.NET decompiler** tool, after decompiling a DLL: - Typing in the type/member **search box** does nothing. The tree keeps showing every namespace and type. - Clicking the **All / Methods / Properties / Fields / Events** filter chips highlights the chip but does not change the member lists. Selecting "Methods" still shows fields, properties and events. Both controls look functional (the search text persists, the chip gets an active style), which makes this read as "my search found everything" rather than "search is broken". ## Root cause `DecompilerComponent.getFilteredNamespaces()` (`libraries/features/dev-tools-decompiler/src/lib/decompiler.component.ts:234`) is the method the template iterates, and it never reads either filter signal: ```ts getFilteredNamespaces(): unknown[] { const data = this.decompiledData(); if (!data?.tree?.namespaces) return []; return Object.entries(data.tree.namespaces).map(([name, namespace]) => ({ name, types: Object.entries(namespace.types).map(([typeName, typeInfo]) => ({ /* every field, unconditionally */ })), })); } ``` `searchQuery` and `selectedFilter` are written by `onSearchChange()` / `setFilter()`, but across the whole 803-line template they appear in exactly two kinds of place: - `[value]="searchQuery()"` — the input echoing its own text back (line 426) - `[class.active]="selectedFilter() === '…'"` — chip styling (lines 436, 445, 454, 463, 472) Nothing else consumes them. So the name `getFilteredNamespaces` is aspirational: it is an unfiltered projection. ## Impact The decompiler's whole purpose is exploring a large assembly's type tree. On a real DLL (dozens of namespaces, hundreds of types) search and filtering are the only practical navigation, and both are no-ops. Users are left scrolling and manually expanding. ## Fix sketch - Make `getFilteredNamespaces()` a `computed()` that reads `searchQuery()` and `selectedFilter()`: - case-insensitive substring match against namespace name, type name/fullName, and member names; - drop namespaces/types that end up with no matches (and consider auto-expanding matches, since a match hidden inside a collapsed node is still invisible); - `selectedFilter` should restrict which member collections are projected (`methods` → only `methods`, etc., `all` → all four). - It is called from `@for` in the template, so converting it to a `computed()` also removes a per-change-detection re-projection of the entire tree. ## Current behavior is pinned `decompiler.render.spec.ts` has two characterization tests referencing this ticket which assert the *broken* behavior (search leaves the result count unchanged; "Methods" still renders fields) so the suite stays green. **Both are written to fail once filtering works — update them as part of the fix.**
Author
Owner

Migrated to spikerj/spikersoft-angular#649 as part of the umbrella-tracker breakup.

Verified 2026-08-07 against spikersoft-angular@8e5a404, and live against the deployed main-KZKZUOSX.js bundle on learn.spikersoft.com.

  • Code: Unchanged. libraries/features/dev-tools-decompiler/src/lib/decompiler.component.ts:234-256 — getFilteredNamespaces() reads only this.decompiledData() and returns an unconditional projection of every namespace, type and member collection. searchQuery (:68) and selectedFilter (:69) are written by onSearchChange() (:230-232) and setFilter() (:322) but consumed nowhere else: in the template they appear only at decompiler.component.html:426 ([value]="searchQuery()") and :436,:445,:454,:463,:472 ([class.active]). The @for at :487 iterates the unfiltered projection.
  • Live: Confirmed deployed. learn.spikersoft.com → main-KZKZUOSX.js → chunk-CHLW0UZC2.js ships the minified method verbatim:
    getFilteredNamespaces(){let e=this.decompiledData();return e?.tree?.namespaces?Object.entries(e.tree.namespaces).map(([t,r])=>({name:t,types:Object.entries(r.types).map(([l,x])=>({name:l,fullName:x.fullName,kind:x.kind,accessModifier:x.accessModifier,isStatic:x.isStatic,baseType:x.baseType,interfaces:x.interfaces||[],methods:x.methods||[],properties:x.properties||[],fields:x.fields||[],events:x.events||[]}))})):[]}
    
    No reference to the search or filter signals in the shipped body, and the template still calls getFilteredNamespaces() from its @for. Search and filtering are no-ops in production today.

Status: not done — The whole fix: make it a computed() over searchQuery() + selectedFilter() with case-insensitive matching on namespace/type/member names, prune empty branches, auto-expand matches, and have selectedFilter restrict which member collections are projected. Then flip the two characterization tests in decompiler.render.spec.ts that currently pin the broken behaviour.

Closing here. Work now lives in the repo that holds the fix, so fixes #649 in a PR will auto-close it on merge. The umbrella tracker keeps cross-repo epics only.

— Opus 5 Agent

Migrated to **spikerj/spikersoft-angular#649** as part of the umbrella-tracker breakup. Verified 2026-08-07 against `spikersoft-angular@8e5a404`, and live against the deployed `main-KZKZUOSX.js` bundle on learn.spikersoft.com. - **Code:** Unchanged. `libraries/features/dev-tools-decompiler/src/lib/decompiler.component.ts:234-256` — `getFilteredNamespaces()` reads only `this.decompiledData()` and returns an unconditional projection of every namespace, type and member collection. `searchQuery` (`:68`) and `selectedFilter` (`:69`) are written by `onSearchChange()` (`:230-232`) and `setFilter()` (`:322`) but consumed nowhere else: in the template they appear only at `decompiler.component.html:426` (`[value]="searchQuery()"`) and `:436,:445,:454,:463,:472` (`[class.active]`). The `@for` at `:487` iterates the unfiltered projection. - **Live:** **Confirmed deployed.** `learn.spikersoft.com` → `main-KZKZUOSX.js` → `chunk-CHLW0UZC2.js` ships the minified method verbatim: ```js getFilteredNamespaces(){let e=this.decompiledData();return e?.tree?.namespaces?Object.entries(e.tree.namespaces).map(([t,r])=>({name:t,types:Object.entries(r.types).map(([l,x])=>({name:l,fullName:x.fullName,kind:x.kind,accessModifier:x.accessModifier,isStatic:x.isStatic,baseType:x.baseType,interfaces:x.interfaces||[],methods:x.methods||[],properties:x.properties||[],fields:x.fields||[],events:x.events||[]}))})):[]} ``` No reference to the search or filter signals in the shipped body, and the template still calls `getFilteredNamespaces()` from its `@for`. Search and filtering are no-ops in production today. Status: **not done** — The whole fix: make it a `computed()` over `searchQuery()` + `selectedFilter()` with case-insensitive matching on namespace/type/member names, prune empty branches, auto-expand matches, and have `selectedFilter` restrict which member collections are projected. Then flip the two characterization tests in `decompiler.render.spec.ts` that currently pin the broken behaviour. Closing here. Work now lives in the repo that holds the fix, so `fixes #649` in a PR will auto-close it on merge. The umbrella tracker keeps cross-repo epics only. — Opus 5 Agent
Sign in to join this conversation.