fix(quickfiler): stop folder-search keystrokes from stealing focus (#438) - #514
Merged
Merged
Conversation
Add the promoted potential-bug entries backing issues #438, #439, and #440, each grounded in a read of the current sources rather than the reported symptom alone. - #438 quickfiler-search-keystroke-focus-steal: TextBoxSearch_TextChanged reopens the drop-down on every keystroke, and the open path schedules _focusPending, so focus leaves the search textbox mid-typing. - #439 efcviewer-missing-lineage-and-segment-navigation: presented rows are archive-root-relative stems while ResolveLeafKeyAsync matches full Outlook folder paths, so the ancestor chain never resolves and BreadcrumbRowBuilder takes its single-segment fallback; non-leaf segment clicks also do not select or expand the ancestor. - #440 breadcrumb-left-right-arrow-parent-child-navigation: both surfaces implement display-collapse / leaf-expand rather than parent-select / expand-children, and the requested contract conflicts with an issue #400 acceptance criterion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bvWJ7sjfLsHB922U5YsUR
) Typing in the QuickFiler folder-search textbox lost keyboard focus after one to two characters, so the rest of the query never reached SearchText and the collapsed surface jumped to a mid-search folder. TextBoxSearch_TextChanged ran on every keystroke and issued ClearFolderItems + SetFolderItems + SetFolderSelectedIndex(1) + SetFolderDroppedDown(true). That composition stole focus twice per keystroke -- the leading Clear cancelled the open selector session, whose close focused the collapsed anchor, and the trailing open focused the popup -- and committed a mid-search selection that Escape could not undo in the controller cache. The handler now issues one additive intent, IItemViewer.PresentFolderSearchResults. Behind it the coordinator layer replaces rows while preserving the open session (reusing ReconcileRowsReplaced), opens the selector only when closed, and highlights the first selectable row pending-only. An additive IBreadcrumbDropDownHost.OpenAsync overload carries an explicit takeFocus intent that BreadcrumbDropDownOpenCoordinator latches for search-originated opens; the host and open lifetime skip both focus steps when it is false. Contract changes are additive only. The existing 3-parameter OpenAsync delegates with takeFocus: true, so every explicit gesture -- Down arrow, mouse toggle, JumpToFolderDropDown -- keeps its exact focus-on-open semantics. This is the sanctioned, gesture-scoped qualification of #400 AC-13 recorded in the spec. Toolchain (in order, all exit 0): csharpier 1.2.6 format/check; analyzer msbuild, 0 errors; nullable warnings-as-errors msbuild, 0 errors; coverage-enabled vstest, 6348/6348 passing. Repository line coverage 0.858261 -> 0.858665, branch 0.792082 -> 0.792502. Every new or changed member is at or above 95.24% line coverage. The EfcViewer search path has zero diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hecklist Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Record cycle-1 policy-audit, code-review, feature-audit, and remediation-inputs artifacts - Log one blocking finding: 50% new-file branch coverage on BreadcrumbItemViewerLifecycleCoordinator.Search.cs against the 75% floor, with all 14 gating acceptance criteria passing - Update feature-review agent memory with cycle-1 findings Refs: #438 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Add remediation plan for the single blocking review finding: 50% branch coverage on BreadcrumbItemViewerLifecycleCoordinator.Search.cs - Scope the Phase 0 clean-tree check to *.cs/*.csproj so review-cycle documentation does not trigger a false-positive halt - Correct the coverage-gate XPath to the backslash filename form used by the Cobertura artifact, and require a node count of exactly 1 before evaluating the branch-rate threshold so an empty selection cannot pass vacuously Refs: #438 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Promote the CSharpier documentation defect to #509: CLAUDE.md and .claude/rules/csharp.md document v0 syntax that cannot work against the pinned CSharpier 1.2.6 - Promote WinFormsPumpHost test flakiness to #511: a real Application.Run message pump makes affected suites load-dependent and shows a window - Record the CS2002 duplicate compile entry, then close its issue #510 as a duplicate of the pre-existing open #394 and move the fresh evidence there All three are pre-existing or tooling defects outside the #438 minimal-fix boundary. Refs: #438, #509, #511, #394 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ewerLifecycleCoordinator - Add two MSTest cases exercising the null open-coordinator and null bridge-coordinator arms, raising branch coverage for BreadcrumbItemViewerLifecycleCoordinator.Search.cs from 50% (2/4) to 100% (4/4); no production code changed - Record remediation evidence (baseline/final Cobertura reports, toolchain pass logs, remediation plan update) and a correction note disclosing estimated evidence timestamps and a non-reproducible repo-wide coverage baseline, leaving plan task P2-T7 unchecked Refs: #438 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Record zero-blocking cycle-2 verdict; verify cycle-1 branch coverage finding resolved at 100% on BreadcrumbItemViewerLifecycleCoordinator.Search.cs - Adjudicate repo-wide coverage floor miss and estimated-timestamp disclosure as non-blocking, with variance isolated to three untouched legacy files - Promote PR-context collector misclassification defect to issue #513 - Update feature-review agent memory with coverage-constant nondeterminism note Refs: #438, #513 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5 tasks
The CI gate for PR #514 failed on TimeoutAfter_GenericTask_ShouldPropagateFaultedSourceException_WhenSourceFaultsLater, which races a real 100ms wall-clock deadline against exception propagation. The test is untouched by this branch and passes locally on the same commit, confirming a load-dependent race rather than a regression. Real wall-clock waits in test code are banned by .claude/rules/general-unit-test.md, so this is a genuine test defect rather than tolerable flakiness. Same root-cause family as #511. Refs: #438, #516, #511 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Main advanced with the #503 merge (aca69af) while this branch was in review, leaving PR #514 CONFLICTING. GitHub cannot compute a merge commit for a conflicting PR, so no pull_request workflow run was created for the branch head and the S9 CI gate had no checks to observe. Conflicts were confined to append-only .claude/agent-memory files where both branches added entries; resolved by keeping both sides: - task-researcher/MEMORY.md: kept the #438 and #503 index lines - feature-review/project_pr-context-summary-misclassifies-cs.md: kept the shared history plus both the #503 and #438 recurrence records No production or test file conflicted; the C# diff is unchanged. Refs: #438 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Suggested title
fix(quickfiler): stop folder-search keystrokes from stealing focus (#438)
Summary
IItemViewermember and one newIBreadcrumbDropDownHostoverload. No existing signature is removed or altered, and every existing caller keeps its exact semantics.JumpToFolderDropDown); only search-driven opens become non-focusing. This is a deliberate, documented qualification of an issue Bug: quickfiler-folder-selector-dropdown #400 acceptance criterion — see Backward Compatibility.Why
Folder search is the primary way to file an item to a folder that is not among the suggestions. Because focus left the textbox after one to two characters, the search box was effectively unusable for any multi-character query, and the view jumped to whichever folder matched the truncated string.
Root cause, confirmed against source rather than assumed:
BreadcrumbDropDownOpenLifetime.FocusCurrentSurfacecalls_host.FocusPending()on a fresh open, andBreadcrumbDropDownHost.OpenAsyncschedules_focusPendingon a re-issued open. Opening the drop-down focuses the popup by design.ClearFolderItems()cancelled the open selector session;BreadcrumbDropDownHost.FinishCloseunconditionally invokes_focusAnchor. This mechanism was not identified in the original issue, and any fix suppressing only_focusPendingwould still have lost focus here.SetFolderSelectedIndex(1)on each keystroke mutated the model selection (what the collapsed surface andGetSelectedFolder()report) and raisedSelectionChanged, so the controller cached a mid-search folder. BecauseCancelSelectorraises noSelectionChanged, Escape left that stale value behind.The defect was the composition of "refresh on every keystroke" with "opening the drop-down takes focus" — neither part is wrong on its own.
What Changed
Core fix
TextBoxSearch_TextChangedreduces toFindFolderplus one call:Sequencing moves into the coordinator layer that owns the posted FIFO operation queue, where it can be ordered deterministically.
IItemViewer.PresentFolderSearchResults(string[])— the single controller-facing search intent.BreadcrumbBridgeCoordinator.PresentSearchResults— composite: replace rows preserving the session, open the selector if closed, then apply a pending-only highlight. Reuses the existingReconcileRowsReplacedprimitive.FolderBreadcrumbBridgeRouter.SearchPresentation.cs) — a refresh no longer closes and re-opens the popup, which removes the close-side_focusAnchorsteal and the per-keystroke popup churn.HighlightRow(BreadcrumbSelectionSession.Highlight.cs) — highlights without committing, so Escape restores the identity committed before the search began.takeFocusintent through the open pipeline — additiveIBreadcrumbDropDownHost.OpenAsync(anchor, workingArea, size, bool takeFocus). The existing 3-parameter overload delegates withtakeFocus: true, and default opens continue to route through it, so every existing caller and test is untouched.Tests
QfcItemController.SearchFocusRegressionTests.cs,BreadcrumbDropDownSearchIntegrationTests.cs(+.Part2),BreadcrumbDropDownOpenCoordinatorTests.Part3.cs,BreadcrumbSelectionSessionHighlightTests.cs,FolderBreadcrumbBridgeRouterReplaceItemsTests.cs.TextBoxSearch_TextChanged_UsesInjectedFolderSearchHandler_PopulatesAndSelectsFolder, which pinned the defective composition. Its durable protections are re-asserted against the new intent, with added negative assertions.IBreadcrumbDropDownHost); one gained apartialkeyword. No test method was added, removed, weakened, or altered by those edits.Docs and evidence
Feature folder under
docs/features/active/2026-08-07-quickfiler-search-keystroke-focus-steal-438/: spec, research, atomic plan, remediation plan, two review cycles, a human-verification runbook, and the full evidence tree.Architecture / How It Fits Together
The latch is deterministic because
SetDroppedDown-posted work andHandleSelectorOpenStateChanged-posted work execute FIFO on the sameBreadcrumbPopupUiOperationsqueue.Explicit-gesture paths are unchanged and still call the 3-parameter
OpenAsync, which defaults totakeFocus: true.Verification
Completed
evidence/regression-testing/.formatthencheck(1501 files, 0 violations); .NET analyzers (0 errors); nullable withTreatWarningsAsErrors(0 errors).BreadcrumbDropDownIntegrationTests.csis byte-unmodified; no file exceeds the 500-line ceiling.Recommended
pwsh -NoProfile -Command "& ./.dotnet-sdk/dotnet.exe tool run csharpier check . ; exit $LASTEXITCODE"pwsh -NoProfile -Command "& msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true ; exit $LASTEXITCODE"pwsh -NoProfile -Command "& msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:Nullable=enable /p:TreatWarningsAsErrors=true ; exit $LASTEXITCODE"pwsh -NoProfile -Command "& ./scripts/vscode/Invoke-MSTestWithCoverage.ps1 -SearchRoot . ; exit $LASTEXITCODE"docs/features/active/2026-08-07-quickfiler-search-keystroke-focus-steal-438/runbooks/verify-search-focus-retention.runbook.mdBackward Compatibility / Migration Notes
spec.md, not an undisclosed regression — every other mapped Bug: quickfiler-folder-selector-dropdown #400 criterion is preserved and its suites pass unmodified.Risks and Mitigations
ToolStripDropDown.AutoClosebehavior while typing is not unit-observable. Mitigation: a human-verification runbook covers the residual check; it is explicitly not a merge gate. All managed focus transfers are delegate invocations and are asserted at existing harnesses.Review Guide
QuickFiler/Controllers/QfcItemController.EventHandlers.cs— the actual fix, ~16 lines.QuickFiler/Viewers/BreadcrumbBridgeCoordinator.Search.cs— the composite and its ordering.UtilitiesCS/.../FolderBreadcrumbBridgeRouter.SearchPresentation.csandBreadcrumbSelectionSession.Highlight.cs— the host-neutral transitions.QuickFiler/Viewers/BreadcrumbDropDownOpenCoordinator.csandIBreadcrumbDropDownHost.cs— thetakeFocuslatch and overload; confirm default opens still use the 3-parameter path.QuickFiler.Test/Controllers/QfcItemController.SearchFocusRegressionTests.cs— the primary regression.Follow-ups
Filed during this work, all out of scope here:
CLAUDE.mdand.claude/rules/csharp.mddocument a CSharpier command (csharpier .) that cannot work against the pinned 1.2.6, which requiresformat/checksubcommands.WinFormsPumpHosttests run a real message pump: load-flaky, show a window, and make repo-wide coverage measurement non-reproducible across runs.PercentageFormatterTests.cscompile entry; re-confirmed with current evidence.Deferred, not addressed here:
QfcItemController.EventHandlers.csremains below the per-file coverage floor. This is pre-existing: every changed line is covered and the uncovered-line set is identical before and after. Awaiting a maintainer disposition — either a follow-up issue for the untested WinForms theme/menu handlers, or a recorded exemption.evidence/other/timestamp-and-coverage-floor-correction.2026-08-08T19-25Z.md.GitHub Auto-close