fix(796): keep the QuickFiler folder drop-down open and commit row selection before cancel - #807
Merged
drmoisan merged 15 commits intoSep 7, 2026
Merged
Conversation
Preparation-mode orchestration for issue #796: the QuickFiler item-view folder drop-down flashes open and closes immediately on both the arrow click and the Down key, and a row click closes the list without selecting the row. Delivers the promoted record, issue.md, spec.md, the research artifact, the human-exception runbook, the preflight delta artifacts, and a preflight-cleared 10-phase, 83-task atomic plan. No source file is touched; atomic execution is out of scope for this run. AC6 is carried as a hard ordering constraint on the plan rather than as a criterion satisfied somewhere in it. Phase 1 contains only the instrumentation of the two named close sites, Phase 2 is a manual observation gate, Phase 3 records which of the three candidate close paths was confirmed as the first cause, and no behavioural-fix phase precedes that decision. The issue marks the Win32 activation ordering as inferred rather than confirmed, and the plan specifies no fix that presupposes it. Confirming the runtime ordering needs a live Outlook process and human gestures, which the unit-test policy forbids. That is recorded as a permitted human-interaction exception with a runbook, and every acceptance criterion retains at least one automatable managed-seam assertion so none depends on manual verification alone. Three preflight rounds resolved 12 blocking defects and 10 observations across three planner revision passes and one orchestrator scope adjudication. The most consequential: the plan's line-count idiom omitted blank lines and would have halted Phase 0 on a false diagnosis; a proposed interface member would have broken a compiled hand-written implementor outside the write set; and the per-item diagnostic would have dereferenced a reference the existing code guards, turning a silent no-op into a logged error and making Phase 1 behavioural. The write set is sixteen concrete paths confined to QuickFiler and QuickFiler.Test. QuickFiler/Resources/FolderBreadcrumb.html is shared with a concurrently prepared sibling item and the contention is recorded rather than avoided by dropping the file. Refs #796 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MS8fb2wX1Sx1bG67LAr41e
…-dropdown-closes-on-open-796
…tations origin/main advanced by two sibling merges of this parallel run after the plan cleared preflight. Merging it in invalidated citations the plan relies on, so the plan is reconciled against the post-merge tree before execution. Base anchor. Four acceptance gates (P1-T14, P7-T4, P9-T7, P9-T10) hardcoded the pre-merge base c431dc3. Anchored there they enumerate the 146 files the sibling merges brought in and bill them to this item, so every scope-boundary gate would fail on work this item did not do and the P9-T7 changed-code coverage denominator would be computed over sibling-authored lines. All four now anchor on the merge commit c7ae69f. The cut point is retained on the Branch metadata line, labelled as history rather than as the anchor. The anchor was tested rather than read: diffing at c7ae69f lists nothing, diffing at c431dc3 lists roughly 150 entries. Citations the merge moved. The merge inserted three compile entries, at post-merge line 336 of the QuickFiler project file and lines 66 and 148 of the test project file, so the induced shift is positional rather than uniform. Six citations were corrected accordingly, and the rationale now states the positional rule instead of a uniform one-line rule that would mislead a later re-derivation by one line. Counts the merge invalidated. A line-number sweep does not cover counts. The partial-part count for QfcItemController was eleven and is now twelve, the merge having added the breadcrumb-wiring part, which declares only EnsureBreadcrumbPipeline and so collides with no member Phase 1 adds. Pre-existing inaccuracies found while sweeping and corrected in passing: the claim that every project declares EnsureNuGetPackageBuildImports (seventeen of eighteen do); an unsupported count of stalling shell-icon test classes, removed rather than replaced because the membership is not established; the claim that the repository has no root build-property file (it has both Directory.Build.props and Directory.Build.targets, neither setting a nullable property, so the instruction not to pass /p:Nullable=enable is unaffected); the claim that every non-implementor reference to IQfcFormViewer is a mock or a comment (there are production consumers, though the single-implementor conclusion holds); and the target framework, which is 4.8.1 rather than 4.8. No task was added, removed, or renumbered. The write set remains sixteen paths, phase headings and LF endings are unchanged, and the plan validator passes with no warnings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…g sites Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
P1-T15 commits the instrumentation, but its own evidence artifact records the SHA of that commit and so cannot be contained by it. The task's command sequence carries no staging span, so the artifact and the P1-T14 and P1-T15 checklist transitions were left staged. This commit lands them. No source file is touched. Both paths lie inside the feature folder and within the declared write set, and the Phase 1 source footprint is unchanged at the eight permitted paths. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-dropdown-closes-on-open-796
… the plan after merging main The maintainer executed the Phase 2 runbook against branch head ec674e0 and reported the result. Candidate 1, the form-deactivation cancel in QfcFormController.ParkFocusAndCancelSelectors, is confirmed as the first cause on all three gestures; candidate 2 is refuted on all three; candidate 3 was not instrumented and the observed ordering leaves no room for it. This discharges human-interaction requirement HI-796-1 and replaces the INFERRED activation ordering with a measurement, which is what AC6 requires. origin/main advanced by one merge while this item was halted at that gate, so it was merged in at d78ae7f. The 59 incoming files touch nothing under QuickFiler or QuickFiler.Test, and the merged tree rebuilds at 0 warnings and 0 errors. The plan's diff gates are re-anchored, deliberately non-uniformly: P7-T4 and P9-T10 move to d78ae7f because at the old anchor they would enumerate the merge-borne files their own acceptance forbids, while P9-T7 is retained at c7ae69f because it is scoped to a directory the merge did not touch and moving it would drop the Phase 1 instrumentation out of the denominator it requires. Line citations that the executed Phase 1 had moved are repaired and re-expressed against member names rather than line numbers. Phase 4 gains a task repairing a doc comment Phase 1 stranded. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e Phase 3 decision record Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… seam (AC2) Declares IsDeactivationSelfInflictedByOwnPopup on IQfcFormViewer with false meaning GENUINE, implements it in QfcFormViewer from explicitly registered popup state rather than Form.ActiveForm (which the manual observation measured running inverted), wires the registration beside the may-take-focus assignment in ItemViewer.Breadcrumb.cs, and gates the per-item cancel loop on it. Focus parking stays unconditional per the AC2-PARK-FOCUS-SUPPRESSED: NO decision. Also repairs the stranded ParkFocusAndCancelSelectors doc comment and the refuted ActiveFormNull sentence introduced in Phase 1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… flight (AC3) Adds an internal IsCommitPending latch on BreadcrumbDropDownHost.Open.cs, cleared as each fresh native show begins and set when an explicit-commit close reaches the host. FinishClose now cancels only when the reason is Uncommitted and no commit is in flight, per the AC3-ENFORCEMENT-SITE: HOST decision. The open coordinator and FolderBreadcrumb.html are untouched, the latter per AC3-HTML-POINTERDOWN: NOT REQUIRED. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…it opened (AC4) Adds the SearchOwnedDismissalLatch to QfcItemController.EventHandlers.cs: a lifetime provenance flag set by the two search-driven open sites and consulted by TextBoxSearch_Leave, which now dismisses only a popup the search box opened. A mouse-driven open never sets it, so the mouse path needs no edit. The one-shot issue #680 handoff latch keeps its own lifetime and meaning unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ion guard GestureOpen_ResolvesOpenAndLeavesHostOpenWithoutClose asserts at the managed seam that the gesture open resolves, the host stays open, the session reports the selector open and no Close reaches the host. RowSetRefreshWhileOpen_NeverClosesHost pins the issue #438 AC-3 contract across two row-set replacements while open, observing the session-preserving replacement path this item deliberately leaves out of its diff. Records the P7-T4 AC5 exclusion gate and a blocking finding: one pre-existing test outside the write set now pins the behaviour AC4 removes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-dropdown-closes-on-open-796
…ase 9 QA gates QfcItemController.SearchDismissalTests.cs held a pre-existing issue #680 test asserting that a search-box leave dismisses any open drop-down. AC4 deliberately narrows that to popups the search box itself opened, so the test is re-pinned to the new contract rather than left asserting behaviour this item removed. The file entered the write set at that point, making it the seventeenth path. Records the Phase 9 QA loop: csharpier format and check clean, the analyzer and nullable rebuilds at 0 warnings and 0 errors against a 0/0 baseline with 36 Csc invocations each, 1380 of 1380 QuickFiler.Test tests passing against a 1370 baseline with all four expect-fail inventory tests now green, and the coverage delta at 97.5610 percent changed-code coverage with the document ratio rising from 24.1387 to 24.1857 percent. Every write-set .cs and .html file is at or under the 500-line ceiling, the largest being 496. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Feature review returned zero blocking findings. It evaluated AC3 as PARTIAL: the commit-before-cancel clause is delivered and pinned in both polarities, but the clause asserting that a mouse click on a row selects that row has no post-fix observation behind it. The row-activation path was deliberately left unchanged, and the close-ordering decision record states expressly that it establishes only that the evidence does not support the page change, not that the change is unnecessary, and that the question reopens if a row click still fails to select. AC3 is therefore unchecked in spec.md and issue.md. The criterion text is unchanged. This is a verification gap rather than a code defect, and the reviewer recorded it as non-blocking. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
5 tasks
drmoisan
added a commit
that referenced
this pull request
Sep 7, 2026
… issue #808 Files the non-blocking findings from PR #807's code review as one tracked bug in the same three files: the AC2 guard also gating the #791 Cancel teardown (CR-3), the commit-pending latch never cleared on consumption (CR-2), a stale FinishClose comment (CR-1), a dead accessor (CR-4), and the untested AC2 producer (CR-7). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
5 tasks
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.
Summary
Why
In the QuickFiler item view the folder drop-down flashed open and closed on every gesture. Typing did keep the list open, but clicking a row closed it without selecting; only Up and Down changed the selection.
The drop-down is not a ComboBox. It is a WebView2 breadcrumb page hosted by
ItemViewer, plus aToolStripDropDownpopup hosting a second WebView2. That structure is what makes the defect possible: aToolStripDropDownis not aForm, so giving focus to the popup's WebView2 deactivates the owning QuickFiler form, and the existing deactivate handler treats every deactivation as a reason to cancel the pending selector session.The specification imposed an ordering constraint: the first implementation step had to be instrumentation, not a fix, so that the choice among three candidate close paths was made from evidence rather than from an assumption about Win32 activation ordering. That constraint was honoured. The instrumentation landed first, the reproduction was run against it, and the ordering was read off the log. Candidate 1 was confirmed and candidates 2 and 3 refuted on all three gestures.
What Changed
Core behaviour
QuickFiler/Controllers/QfcFormController.Deactivate.cs— gates the cancel loop on a self-inflicted-deactivation seam, so a deactivation caused by this add-in's own popup no longer cancels the selector session. A deactivation caused by any other window still does, preserving the issue 677 contract.QuickFiler/Viewers/BreadcrumbDropDownHost.cs—FinishCloseconsults a pending-commit latch, so an uncommitted-reason close does not cancel while a commit is in flight.QuickFiler/Controllers/QfcItemController.EventHandlers.cs— the search-leave dismissal latch is now also set on the mouse open path.QuickFiler/Viewers/QfcFormViewer.cs,QuickFiler/Interfaces/IQfcFormViewer.cs— the intent member the deactivate handler reads, implemented at the one site that reads non-injectable activation state.QuickFiler/Viewers/BreadcrumbDropDownHost.Open.cs,QuickFiler/Viewers/ItemViewer.Breadcrumb.cs— open-path and wiring changes supporting the above.Diagnostics
QuickFiler/Viewers/BreadcrumbDropDownHost.Diagnostics.cs— new partial part carrying the AC6 instrumentation.OnDropDownClosedwas relocated into it rather than duplicated, because the host file sits close to the repository's 500-line ceiling and declares no logger.Tests
QuickFiler.Test/Viewers/BreadcrumbDropDownCloseOrderingTests.cs,QuickFiler.Test/Controllers/QfcItemController.SearchLeaveLatchTests.cs.QuickFiler.Test/Controllers/QfcFormControllerDeactivateTests.cs,QuickFiler.Test/Viewers/BreadcrumbPendingOpenCloseTests.cs,QuickFiler.Test/Controllers/QfcItemController.SearchDismissalTests.cs.One pre-existing test was deliberately re-pinned rather than weakened.
TextBoxSearchLeave_WhileDropDownOpen_RoutesExactlyOneCloseIntentcame from the issue 680 fix and asserted that a search-box leave dismisses an open drop-down regardless of which gesture opened it. That is precisely the state AC4 requires to stop being dismissed. Its method name and itsTimes.Once()assertion are retained and one Arrange line establishing a search-driven open was added; the case it no longer covers is covered by a new test assertingTimes.Never(). The class still declares six test methods.Architecture / How It Fits Together
The selector session spans three components.
ItemViewerhosts the collapsed breadcrumb;BreadcrumbDropDownHostowns the popup and its open/close lifetime;QfcFormControllerowns form-level deactivation. The defect lived in the seam between the third and the second: deactivation is a form-level event, but whether it should cancel depends on popup-level state.The fix adds that missing information flow.
QfcFormViewerexposes whether the current deactivation was caused by this add-in's own popup;QfcFormController.Deactivateconsults it before cancelling; andBreadcrumbDropDownHostseparately tracks whether a commit is in flight so that close ordering cannot lose a selection. No public API changed.Verification
Completed
All commands were run against the branch head in the item worktree.
dotnet tool run csharpier check .— exit 0 over 1608 files, empty unformatted list.msbuild TaskMaster.sln /t:Rebuild ... /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true— exit 0, 0 warnings, 0 errors, against a baseline of 0 and 0. Non-vacuity established by 36Csctask invocations and 36csc.exetool invocations with both touched assemblies rebuilt after the recorded run start.msbuild TaskMaster.sln /t:Rebuild ... /p:TreatWarningsAsErrors=true— exit 0, 0 warnings, 0 errors.QuickFiler.Testassembly, Total 1380, Passed 1380, Failed 0, against a baseline of Total 1370 with an empty failure set.UtilitiesCS/,UtilitiesCS.Test/,.claude/,.codex/,.agents/,config/or.github/, andTaskMaster.slnuntouched.AC6 required a runtime ordering observation that needs a live Outlook process, a real WebView2 surface, a real
ToolStripDropDownand human gestures. Repository unit-test policy forbids external processes and shown windows, so no automated test can produce it. It was handled as an approved human-interaction exception with a runbook and a recorded observation artifact, following the precedent of issues 400 and 438. Every acceptance criterion also retains at least one automatable managed-seam assertion.AC3 is partially delivered
AC3 has two clauses. The second, that the selection is committed before any auto-close cancel runs, is delivered and pinned by tests in both polarities.
The first, that a mouse click on a row selects that row, has no post-fix observation behind it. The row-activation path was deliberately left unchanged, because the evidence did not support changing it, and the decision record states expressly that this establishes only that the evidence does not support the page change and not that the change is unnecessary. The claim that closing the deactivation-cancel path also removes the click-without-select symptom is a causal argument, and it has not been confirmed against a build carrying the fix.
AC3 is therefore left unchecked in
spec.mdandissue.md. This is a verification gap rather than a known defect, and feature review classified it as non-blocking. Closing it requires either one post-fix observation of the click gesture or a maintainer's acceptance of the causal argument.Recommended
Backward Compatibility / Migration Notes
No breaking changes. No public API was changed, no file was renamed or removed, and no dependency was added. The issue 677 contract that a genuine deactivation cancels the selector session is preserved and pinned by tests in both polarities. The issue 438 regression guard on row-set refreshes is retained.
Risks and Mitigations
Review Guide
Suggested order:
QuickFiler/Controllers/QfcFormController.Deactivate.cs— the AC2 seam, the substance of the fix.QuickFiler/Viewers/BreadcrumbDropDownHost.cs— the AC3 commit-before-cancel ordering.QuickFiler/Controllers/QfcItemController.EventHandlers.cs— the AC4 latch.QuickFiler.Test/Controllers/QfcItemController.SearchDismissalTests.cs— the deliberate re-pinning, worth reading against the Test Strategy section ofspec.md.QuickFiler/Viewers/BreadcrumbDropDownHost.Diagnostics.csis largely a relocation ofOnDropDownClosedand reads as new code in the diff without being new behaviour.The documentation footprint is large relative to the code footprint. The feature folder carries the plan, specification, research, runbook, decision record and the per-task evidence artifacts. None of it is product code.
Follow-ups
SearchOwnsDropDownDismissalinQfcItemController.EventHandlers.cs. The underlying field is live and is the AC4 latch; only this exposing property has no reader.BreadcrumbDropDownHost.csstating that only the focus step is gated, which the new guard placement has falsified.GitHub Auto-close