fix(quickfiler): resolve seven QfcCollectionController defects and remove twelve unreachable members - #636
Merged
drmoisan merged 28 commits intoAug 26, 2026
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
…icated drain sites
Captures the atomic-executor evidence written between 11:31 and 11:41 that was left uncommitted when the host crashed, plus the spec and plan status updates recorded alongside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ration' into bug/qfc-collection-controller-defects-468
…ection-controller-defects-468
…rals for AC-27 through AC-29
drmoisan
merged commit Aug 26, 2026
808bf46
into
epic/quickfiler-bug-family-integration
5 checks passed
drmoisan
added a commit
that referenced
this pull request
Aug 26, 2026
Projects the epic checkpoint after feature 468 merged as PR #636. Four of twelve features are now on the integration branch: 484 (#619), 446 (#625), 498 (#626) and 468 (#636), whose merge is the current tip 808bf46. Records that 468 landed on a second pass. It was halted earlier in this session at 120 of 180 plan tasks with 14 of 29 acceptance criteria unchecked and no feature review; it was re-delegated to resume at P13-T4 and is now 180 of 180 tasks and 28 of 29 criteria with three audit artifacts carrying zero blocking findings. AC-28 remains unchecked by design, because an integration-branch merge cannot close the seven referenced issues. Also records the repository-wide line-coverage shortfall against the rules-file floor and the absence of feature-review artifacts for 498. Co-Authored-By: Claude Opus 5 (1M context) <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.
Fix seven QuickFiler collection-controller defects and remove twelve unreachable members
Summary
RemoveSpecificControlGroupAsync(Bug: qfc-collectioncontroller-removespecificcontrolgroup-counter-leak #286), which previously left the guard elevated after a throw and blocked all subsequent group removals for the life of the controller.QfcCollectionController(Bug: qfc-collection-controller-unreachable-load-paths #468) after proving no caller exists, including no reflective caller.SetVisualDigits.Subjectread after a failed move.Why
Seven issues filed against
QuickFiler/Controllers/QfcCollectionController.csshared one root cause pattern: the class had accumulated logic that could not be exercised in isolation, so defects in it were found by reading rather than by testing. Two of the seven are latent rather than observed, and this PR states that plainly rather than overclaiming impact:Addpairs occur in the same method body strictly before theirWhenAll; no other member adds to the bag; and each of the three production construction sites (QfcFormController.Actions.cs:49,:83,:139) creates a fresh controller that is awaited. The fix closes the window for a future caller. It does not repair an observed production failure, and nothing here should be read as fixing an intermittent hang.QfcFormControlleris the only production implementation of the parent role, so the runtime downcast could not throw today. The value of the change is that a second implementation cannot reintroduce the hazard.The remaining five are real, reachable defects.
What Changed
Core logic (2 production files)
QuickFiler/Controllers/QfcCollectionController.cs(+493 / -405) andQuickFiler/Interfaces/IQfcCollectionController.cs(+13 / -0, XML documentation only).Dead-code removal (#468). Twelve unreachable members were deleted. Absence of any caller was verified three ways: the solution compiles without them; a reflective-caller search covered 398 build-input files and all 42
GetMethod(call sites; and the full suite stayed green across the removal commit. This removal is not offered as a coverage improvement —QfcCollectionControllercarries[ExcludeFromCodeCoverage]at line 21, so every line of the type sits outside both the numerator and the denominator of the coverage metric and removing lines from it cannot move any coverage number in either direction. Removing that attribute was explicitly out of scope.Interface retype (#474 defect 1).
QuickFiler.Controllers.IQfcFormControllerderives fromIFilerFormController(QuickFiler/Controllers/IQfcFormController.cs:13) and is a strict superset of it. There were no parallel interfaces to consolidate. The fix is therefore a narrow field and constructor-parameter retype from the base interface to the derived one, which is why the diff for this defect is small. The call site now binds toIQfcFormController.SkipGroupAsync()at compile time instead of downcasting at runtime.Three extracted seams, each landed in its own commit with the suite passed count identical before and after:
DrainBackgroundLoadingTasksAsync— behaviour-preserving extraction of two byte-identical drain sites, then replaced with anInterlocked.Exchangeatomic-swap drain loop.TryGetMoveReadiness(out string notifications)with an injectable_notifyNotReadydelegate — separates the readiness predicate from its modal notification, so readiness became assertable without presenting a dialog.MessageBox.Shownow appears exactly once in the file, inside the delegate's default.ShrinkByRows— isolates the height arithmetic corrected for Bug: qfc-collection-eliminate-space-sign-error #471.Tests (5 new files, 2 changed, 1 csproj)
QfcCollectionControllerDefects468Tests.cs,...Defects468MoveTests.cs,...Defects468ConversationTests.cs,...TestSupport.cs, andQfcCollectionControllerLayout.StaTests.cs.QfcCollectionControllerTests.csandQfcCollectionControllerDarkModeTests.csreceived small changes;QuickFiler.Test.csprojgained five consecutiveCompile Includeentries. All tests use MSTest, Moq and FluentAssertions, create no temporary file, and require no live Outlook.Documentation and evidence
The full evidence tree under
docs/features/active/qfc-collection-controller-defects-468/evidence/, plus the feature audit, policy audit and code review.Architecture / How It Fits Together
QfcCollectionControllerowns a collection of item groups and mediates betweenQfcFormController(its parent) and the per-item controllers. Three of the seven issues came from that mediation layer reading a collection by index that carried no ordering guarantee, or subscripting with a sentinel-1returned by a lookup that found nothing.The shape of every fix in this PR is the same: extract the decision from the presentation or the framework dependency, assert the decision directly, and leave the wiring untouched. That is what makes the defects testable without a live Outlook process or a shown WinForms form.
QfcCollectionController.csis not split into partial classes here; that decomposition is tracked separately by #623.Verification
Completed
Final QA loop, single clean pass, no restart, no file rewritten:
dotnet tool run csharpier format <10 owned paths>dotnet tool run csharpier check .msbuild /t:Rebuild "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=trueCoreCompile, 0Skipping target "CoreCompile", 0 analyzer diagnosticsmsbuild /t:Rebuild "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=trueCS86xxdotnet-coveragewrapping vstest with/InIsolation)/t:Rebuildis load-bearing: a warm/t:Buildreturns exit 0 having skippedCoreCompileon every project, so the analyzer and nullable gates would pass while compiling nothing. The zero skip count is recorded as the non-vacuity proof for both gates.Repository line coverage moved from 84.7703% to 84.9435% and branch coverage from 78.6876% to 78.9377% — both up. No part of that delta is attributable to this feature's tests, for the
[ExcludeFromCodeCoverage]reason given above. Changed-line coverage is therefore undefined rather than unmeasured: zero changed production lines lie in the coverage denominator.Per-defect evidence
Because a coverage delta cannot serve as evidence for an excluded type, verification is cited as named tests:
RemoveSpecificControlGroupAsync_ThrowAtFirstStatement_RestoresReentrancyCounter,RemoveSpecificControlGroupAsync_ThrowLaterInBody_RestoresReentrancyCounterGetMoveDiagnostics_WithNullItemController_ReturnsUnknownLineWithoutThrowingGetMoveDiagnostics_WithOneGroup_ReturnsExactlyOneLine,GetMoveDiagnostics_WithThreeGroups_ReturnsThreeLinesAndNoNullsItemGroupsToMoveFieldDeclaresAnOrderedContract,TryGetItemGroupByIndexResolvesInsertionOrderAfterMutationMoveEmailsAsync_WithNullStack_BehavesIdenticallyToAnEmptyStackPromoteFirstChild_WithNoMatchingChild_ReturnsMinusOneWithoutSubscripting,ToggleGroupConv_WithNoMatchingOriginal_DoesNotSubscriptWithMinusOneResolveConversationInsertions_ExcludesBaseEntryAndOrdersBySentOnDescending,ReconcileInsertionCount_AboveReservation_ReturnsInsertionsCountAndWarnsOnce,ReconcileInsertionCount_EqualToReservation_ReturnsInsertionsCountAndDoesNotWarn,ReconcileInsertionCount_BelowReservation_ReturnsInsertionsCountAndWarnsOnce,EnumerateConversationMembers_WithNoInsertions_DoesNotThrowSetVisualDigits_WithNullItemController_SkipsTheGroupWithoutThrowingShrinkByRows_WithPositiveRemovalCount_ReducesHeight,ShrinkByRows_WithNegativeRemovalCount_IncreasesHeight,EliminateSpaceForItems_ReducesMinimumHeightByTemplateHeightTimesRemovalCount,MakeSpaceThenEliminateSpace_IsMinimumHeightNeutralDrainBackgroundLoadingTasksAsync_AwaitsATaskAddedDuringTheDrainWindowMoveEmailsAsync_WhenMoveIsCancelled_PropagatesOperationCanceledException,MoveEmailsAsync_AfterFirstFailure_DoesNotReadSubjectASecondTime,MoveEmailsAsync_WithNullGroupFromIndexLookup_DoesNotThrowParentFieldAndConstructorParameterAreTypedIQfcFormControllerTryGetMoveReadiness_WithUnassignedDestination_ReturnsFalseAndProducesNotificationText,TryGetMoveReadiness_WithAllDestinationsAssigned_ReturnsTrueAndEmptyNotificationRecommended
Backward Compatibility / Migration Notes
IQfcCollectionControllermember set is unchanged.TryGetMoveReadinessis deliberatelyinternaland was not added to the interface. The move-emails member keeps itsstackMovedItemsparameter for source compatibility; it is now documented and consumed rather than silently ignored, and its removal is deferred to Refactor: Remove thestackMovedItemsparameter fromMoveEmailsAsync#629.packages.configentry and no csprojPackageReferencewas added.Risks and Mitigations
GetMethod(call sites; the residual repository-wide sweep is filed as #635TaskContinuationOptions.ExecuteSynchronouslyso the late add lands deterministically inside the drain window; noThread.Sleep,Task.Delay, or wall-clock wait appears in any new testQfcCollectionController.csgrew by 88 lines, to 2,437Rollback is a single revert of the merge commit; no data migration or schema change is involved.
Review Guide
QuickFiler/Controllers/QfcCollectionController.cs— the substance. Read the three seam commits first (refactor(473),refactor(474),refactor(471)); each is behaviour-preserving and isolated, which makes the following fix commits small.QuickFiler/Interfaces/IQfcCollectionController.cs— XML documentation only, no signature change.docs/features/active/qfc-collection-controller-defects-468/— evidence and audits; skim unless a specific claim above needs sourcing.evidence/baseline/coverage-baseline.cobertura.xmlandevidence/qa-gates/coverage-final.cobertura.xml; they are generated coverage reports retained as evidence.Commits are ordered to match the planned fix order, with the dead-code removal isolated in its own commit.
Follow-ups
Nine follow-up candidates were identified and all nine now own an open issue:
QfcCollectionController.csbelow the 500-line cap (baseline needs updating to 2,437)stackMovedItemsparameter fromMoveEmailsAsync#629 — remove thestackMovedItemsparameter entirelyReadyForMovepresentation to the caller #630 — relocate theReadyForMovepresentation to the callerIFilerFormControllerandIQfcFormController#631 — consolidateIFilerFormControllerandIQfcFormControllerQuickFiler.Interfaces.IQfcFormController#632 — remove the orphanQuickFiler.Interfaces.IQfcFormControllerKbdActions(IEnumerable<UClass>)with the duplicate check bothAddoverloads performTwo acceptance criteria remain open by design: AC-28 (issue closure) cannot be satisfied at this merge, because this PR targets the epic integration branch and GitHub registers closing references only for pull requests targeting the default branch. The seven issues below close when the integration branch merges to the default branch.
GitHub Auto-close