fix(vs2026): filter now prunes non-matching subtrees deterministically#17
Merged
Conversation
When a user expanded a pipeline or template after the filter scan had finished, LoadPipelineAnalysisAsync / LoadTemplateAnalysisAsync built fresh child nodes that defaulted to IsVisibleUnderFilter=true — leaving non-matching sibling templates and the whole Scripts group visible under matched pipelines (bug reported against 0.3.0). BuildAnalysisChildren now runs ApplyVisibilityRecursive on the parent after ReplaceList when a filter is active, so newly materialised subtrees prune correctly. InfoNodes (loading, warning, no-templates) are pinned visible so the parent's decision still surfaces its status text, but they do not on their own force a non-matching parent open. Restores VS Code parity (plan 001 section 2).
…hlight
Follow-up to plan 001. Keeps matches-only pruning as the default; adds a per-pipeline context-menu action to temporarily unprune a single pipeline's subtree, and independently adds substring highlighting in visible matched labels. No global setting: the right view depends on the moment ('find' vs 'explore'), not on a persistent preference.
…plates Two follow-up gaps discovered after commit 5850e94 (which reapplied filter visibility on lazy-load but only covered one case). 1) Race between scan and lazy-load. MarkTemplateOrScriptMatch silently no-op'd whenever the target pipeline's Templates/Scripts group was not yet materialised (children still [Loading InfoNode]), which is the case for pipelines the user hadn't expanded before typing the filter. When the user then expanded that pipeline after the scan finished, BuildAnalysisChildren materialised the groups and ApplyVisibilityRecursive hid every non-directly-matching template — so a pipeline matched via a nested template ended up either invisible or (worse) collapsed with no expandable content. The scan now records deferred (pipeline, GroupKind) marks in _deferredGroupMarks regardless of materialisation state, and BuildAnalysisChildren applies them to newly-materialised GroupNodes before running ApplyVisibilityRecursive. Non-deterministic behaviour reported after reset+re-apply of the same filter is explained by this race and eliminated. 2) Intermediate templates with only nested matches. ScanTemplatesRecursivelyAsync only tracked a scalar anyNestedMatch — nothing recorded WHICH intermediate template on the path to the match should be surfaced. For a pipeline whose Templates group contained e.g. deployment.yml -> build.yml, only build.yml (buried inside deployment.yml) matched the term 'build.yml'; the intermediate deployment.yml basename did not, so ApplyVisibilityRecursive hid deployment.yml, leaving a 'Templates 7' group that expanded to nothing. The scan is now a proper DFS (ScanTemplateLevelAsync helper) that returns true when the subtree contains a match; every intermediate template on such a path is added to _templatesWithDescendantMatch (keyed by repo-absolute resolved path). ApplyVisibilityRecursive treats a TemplateNode whose ResolvedPath sits in that set as self-matched, so it surfaces the full path from the pipeline down to the deepest match — matching the VS Code client. Both tracking sets are cleared in ClearFilterInternal and at the top of RunFilterScanAsync alongside _matchedNodes / _ancestorNodes.
…te UI race Symptoms: the `Scripts` group of a matched pipeline could stay visible under an active filter even though `ApplyVisibilityRecursive` correctly set `IsVisibleUnderFilter=false` on it. Which pipeline was affected varied between runs. Root cause: `BuildAnalysisChildren` used to push the newly-materialised children to the parent via `ReplaceList` first (initial snapshot with the default `IsVisibleUnderFilter=true`), then call `ApplyVisibilityRecursive` — firing `PropertyChanged(false)` on nodes that had just been added. The follow-up property change on a freshly-added item is dropped by the WPF-side binding on the Remote UI client, so the group remained visible. Fix: apply visibility on the local `children` list before handing it to `ReplaceList`. The initial snapshot sent to the client already carries the correct `IsVisibleUnderFilter` for every new node — no post-add property change needed, no race.
Roll the three filter-pruning fixes on this branch (5850e94, 008613f, 57d4d51) into a 0.3.1 patch release of the VS 2026 client. - `source.extension.vsixmanifest` Identity/@Version 0.3.0 -> 0.3.1 (csproj propagates it to the assembly Version). - CHANGELOG: converted [Unreleased] into [0.3.1] - 2026-07-02 and added a fresh empty [Unreleased] section on top. No code change here.
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.
fix(vs2026): filter now prunes non-matching subtrees deterministically
Three follow-up fixes on top of the plan 001 filter, plus one draft planning
document.
Symptoms
Under an active filter (e.g.
build.yml) the VS 2026 tree could show:scan (
Templates 7with 7 visible items, only 4 actually matched);Templatesgroup still shown as empty because the match lived severallevels deep inside a nested template chain;
Scriptsgroup visible under a matched pipeline even when no script insideit matched — the offending pipeline varied between runs.
Fixes (in order applied)
5850e94—BuildAnalysisChildrennow re-applies the active filter when apipeline / template is lazily expanded after the scan, so newly materialised
children collapse away exactly like they would if they had existed at scan
time.
008613f— the scan records deferred group-visibility marks (Templates /Scripts) so the visibility of a group node can be reconstructed after it is
materialised, and DFS-walks nested template chains to record every
intermediate template on a path to a nested match. Intermediate templates
surface even before the user drills into them.
57d4d51— apply visibility on the localchildrenlist beforeReplaceList(parent.Children, children), not after. The old order pushedthe new children with the default
IsVisibleUnderFilter=true, then firedPropertyChanged(false)on freshly-added items — that late property changewas dropped by the WPF-side binding on the Remote UI client, leaving the
Scriptsgroup stuck visible. The initial snapshot now carries the correctvalue and no post-add property change is required.
Combined, the tree under an active filter matches the VS Code client and
stays deterministic regardless of when (or whether) the user expands each
pipeline.
Other commits
ee6c91e— draftplan 003scoping the next filter improvement(show pipeline in context + highlight). Documentation only, no code
change. Can be dropped from this PR if you'd rather keep the fix isolated —
say the word and I'll rebase it out.
Files touched
src/vs2026/ViewModels/PipelinesViewModel.cs— filter algorithm + lazy-loadreapply + pre-
ReplaceListvisibility.src/vs2026/CHANGELOG.md— rolled the fixes into[0.3.1].src/vs2026/source.extension.vsixmanifest—Identity/@Version0.3.0→0.3.1(csproj propagates it to the assemblyVersion)..specify/plans/003-*— draft plan (seeee6c91e).How to verify
src/vs2026/bin/Debug/net10.0-windows/PipelinesExplorer.VisualStudio.vsix.scripts and nested
template:references.build.ymlin the filter box. Wait for the debounce.Scriptsgroup with no script whosebasename contains
build.ymlshould collapse away; everyTemplatesgroup should show only the templates on a path to a match.
Reference log (log level
Info,%LocalAppData%\PipelinesExplorer\logs) canbe re-instrumented locally by reverting commit
57d4d51up to before thefinal cleanup — the intermediate diagnostic logs (
[FILTER] group-created,apply group=Scripts …,group-visibility-changed) were used to prove therace, then removed.
Release policy
Per
.github/instructions/project.instructions.md, this touches user-facingcode under
src/vs2026/ViewModels/. The VS 2026 client is bumped to0.3.1in this PR (
4af7760). The VS Code client is untouched.