fix(quickfiler): close five QfcItemController lifecycle and error-handling defects - #619
Merged
drmoisan merged 12 commits intoAug 26, 2026
Conversation
…troller defects Bootstraps the fresh agent worktree (repo-local .NET SDK, NuGet restore, analyzer version back-fill, dotnet tool restore) and records the baseline formatting, analyzer, nullable, test, coverage, file-size, exemption and capacity-budget state for the five QfcItemController defects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
Deletes the unconditional _itemPositionTips.Toggle(false) dispatch that preceded the if (async) branch in ToggleNavigation(bool async), so each branch produces exactly one flip instead of two. Tightens the existing synchronous assertion to Times.Once() and adds an exact-count test for the previously-untested async branch, with a shared executing-viewer arrange helper appended to the test support file. Addresses issue 480. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
Extracts the decision half of the WebResourceRequested handler into internal static TryResolveCidResource, which now guards an unparsable or non-absolute URI, an empty final segment, a null map, a null match, and a null AttachmentData, logging the two diagnosable cases at debug level. The lambda is reduced to a two-statement adapter that reads the attachments through a null-conditional ItemHelper, and its delegate and CoreWebView2 source are captured into private fields for later detach. Addresses issue 485. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
…tion Replaces the swallowing catch in MoveMailAsync with an error log, a notification through the new MoveFailureNotifier seam marshalled on the UI dispatcher, and a wrapped rethrow carrying the subject and destination folder with the original as InnerException. Adds Token.ThrowIfCancellationRequested() as the first body statement of MoveMailAsync (outside the try), FlagAsTaskAsync and EnumerateConversationAsync. Addresses issue 483. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
Preserves the formatting pass the interrupted run produced but did not commit. No semantic change: all three hunks are CSharpier line-break normalizations over the issue #483 MoveMailAsync work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
…n cleanup Issue #484. Cleanup() now disposes _emailIsReadTimer before nulling it and releases _mailActions, and ApplyReadEmailFormat returns early when the state it dereferences has been torn down, because a thread-pool timer callback already in flight when the timer is disposed still executes. Adds three regression tests (timer disposal, callback inertness, adapter rebind) plus two shared arrange helpers. The adapter-rebind test is placed in QfcItemController.ViewerSetupTests.cs under constraint C2 capacity rule 3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
Issue #481. Adds UnwireEvents(), UnwireControlTreeEvents(), UnwireIntentEvents() as exact mirrors of the wiring methods, plus DetachWebResourceRequestedHandler() for the subscription made outside them, and calls UnwireEvents() from Cleanup() before _itemViewer and _kbdHandler are released. A pooled viewer handed back after Cleanup() therefore carries no subscription into the released controller. The unwire methods carry teardown guards the wiring methods do not need, because Cleanup() is reachable from an aborted initialization; the unguarded fail-before run records that those guards are load-bearing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
…vidence Records the changed-file set, interface and forbidden-file stability, the delivered Cleanup() statement order against constraint C4, public-surface stability of the four owned partials, and the post-Cleanup() lifecycle invariant, and checks off the five matching acceptance criteria. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
Records the four-stage final pass: scope-locked csharpier format (0 of 9 files rewritten), repo-wide csharpier check (0 unformatted of 1520), MSBuild analyzer and nullable rebuilds (0 errors each), and vstest with coverage (959 of 959 passed, baseline 938 plus 21). Adds the coverage delta, file-size, test-policy, real-ItemViewer and exemption audits plus the consecutive-pass confirmation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
…r defects Checks off the final 17 spec.md acceptance criteria and records the reconciliation and status summary: 50 of 50 criteria delivered and verified, with no criterion text modified. Two descriptive sub-clause divergences are recorded against their cited evidence: the ViewerSetupTests relocation forced by the constraint C2 capacity rule, and the notifier default delegate whose measured line rate is 100 percent rather than the predicted zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
Records the [P8-T15] evidence: empty git status, a 73-path changed-file set confined to the nine owned files and the feature folder, and the branch commit history since BASE_SHA. Closes out the atomic plan checklist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
…fects Adds the policy-audit, code-review and feature-audit artifacts for the issue #484 branch. Zero blocking findings; verdict is ready to merge. The review records two non-blocking residuals recommended for promotion through the potential-to-issue lifecycle: a TOCTOU race in ApplyReadEmailFormat, and a silent skip when OneDrive is missing in MoveMailAsync. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
drmoisan
added a commit
that referenced
this pull request
Aug 26, 2026
Regenerated projection. Feature 484 (qfc-item-controller-defects) merged into the integration branch as 363bfcd. Records the parallel-removal-gate misfire that blocks worktree cleanup on epic runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mic58ikwEhpXsTnhz9FShE
drmoisan
added a commit
that referenced
this pull request
Aug 26, 2026
Projects the epic checkpoint after the fan-in-only run: 484 (PR #619), 446 (PR #625) and 498 (PR #626) are merged into the integration branch, whose tip is now 8c8f769. Records that 468 is halted rather than merged. Its stale-base merge is pushed and its CI run is green, but its atomic plan is 120 of 180 tasks complete (P13 stops at T3; P14, P15 and P16 never ran) and 14 of 29 acceptance criteria in spec.md are unchecked, so no pull request was opened for it. Also carries forward the child-PR CI trigger gap, the missing feature-review artifacts, and the worktree-removal gate defect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
This was referenced Aug 31, 2026
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(quickfiler): close five QfcItemController lifecycle and error-handling defects
Summary
QfcItemControllerpartial classes: a double toggle inToggleNavigation, a missing event unwiring path, swallowed failures and unobserved cancellation inMoveMailAsync, an undisposed read timer with stale collaborators inCleanup(), and unguarded inputs in theWebResourceRequestedhandler.UnwireEvents()now detaches all 22 subscriptions that the wiring methods attach (16 intent, 6 control-tree) plus theWebResourceRequesteddelegate, closing an event-handler leak that kept controllers alive after pooling.MoveMailAsyncnow propagates failures instead of swallowing them, preservesInnerException, and routes the user-facing notification through an injectable seam so the path is testable headlessly.QuickFiler/Interfacesfiles are untouched.Why
QfcItemControlleris pooled and reused. Three of these defects only surface on the second and later use of a recycled controller, which is why they escaped earlier testing:WireIntentEventsandWireControlTreeEventshad no inverse. EveryCleanup()left 22 live subscriptions attached, so a pooled controller accumulated handlers and kept its previousItemViewerreachable.Cleanup()nulled_emailIsReadTimerwithout disposing it, so an armed timer could fire against a torn-down controller. It also left_mailActionsbound to a released collaborator.ToggleNavigationapplied an unconditional toggle in addition to the branch-specific one, so the asynchronous path toggled twice and landed back on its original state.catchblock logged and returned, so a failed move reported success to the caller and the mail item silently stayed put.WebResourceRequestedlambda parsed its URI and indexed the attachment map without guarding either, so a malformed CID URI threw inside an event handler.What Changed
Core fixes (4 production partials, +219/-16)
QfcItemController.EventWiring.cs— addsUnwireEvents(),UnwireIntentEvents(),UnwireControlTreeEvents(), andDetachWebResourceRequestedHandler(), with early-return guards for a null viewer or keyboard handler.QfcItemController.ViewerSetup.cs—Cleanup()disposes the read timer before nulling it, unwires events before releasing collaborators, and nulls_mailActions;TryResolveCidResourceis extracted as aninternal staticseam withUri.TryCreateand map-lookup guards.QfcItemController.MailActions.cs— the swallowingcatchnow rethrows with the inner exception preserved and notifies through theMoveFailureNotifierseam;Token.ThrowIfCancellationRequested()is the first statement of the three async mail actions, outside thetry, so cancellation cannot be re-wrapped as a move failure.QfcItemController.FocusAndTheme.cs— removes the unconditional toggle;ApplyReadEmailFormatreturns early once torn down.Tests (5 files, +591/-2) — 21 new MSTest cases using Moq and FluentAssertions, covering wire/unwire symmetry by count, teardown statement order, cancellation, error propagation, and CID guard cases.
Documentation — the atomic plan, 50 acceptance criteria, 68 evidence artifacts, and the three review audits.
Architecture / How It Fits Together
Cleanup()is the single teardown entry point and now runs in a fixed order: unwire events, then dispose the timer, then null it, then release collaborators. The order is load-bearing — unwiring after releasing_itemViewerwould lose the handler targets, and nulling the timer before disposing it would orphan an armed callback. Two seams were introduced purely to make host-bound paths testable without a live Outlook or WebView2 runtime:MoveFailureNotifier(defaulting to the modal dialog) and theinternal static TryResolveCidResource.Verification
Completed — full CLAUDE.md toolchain, one consecutive clean pass, zero restarts:
dotnet tool run csharpier format(9 owned files)dotnet tool run csharpier check .msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=truemsbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=truevstest.console.exe QuickFiler.Test.dll /EnableCodeCoverage /InIsolationThe 5 remaining build warnings are pre-existing
System.Reactivepackages.confignotices, unrelated to this change.Coverage: repository line rate 84.775% to 84.8323%; changed-line coverage 126/132 added executable lines (95.5%), the 6 uncovered lines being the two authorized WebView2-runtime carve-outs; all five new production members at 100%.
Recommended — re-run the four toolchain commands above.
Backward Compatibility / Migration Notes
No breaking change. Public surface is unchanged; the new members are
internal. Behavioral changes callers should know about:MoveMailAsyncnow throws where it previously returned normally on failure. This is the intended fix, but any caller relying on silent failure will now observe an exception.ToggleNavigation(async: true)now toggles once rather than twice, so its net effect changes for that path.Risks and Mitigations
QuickFiler/Controllerspartials and their tests; reverting the commits restores prior behavior with no schema or config migration.Review Guide
QfcItemController.ViewerSetup.cs—Cleanup()statement order (highest risk).QfcItemController.EventWiring.cs— wire/unwire symmetry.QfcItemController.MailActions.cs— cancellation placement and rethrow.The 68 evidence files and 3 audits account for most of the line count and can be skimmed.
Follow-ups
Two non-blocking residuals identified in review, recommended for promotion through the potential-to-issue lifecycle:
ApplyReadEmailFormat: the guard re-reads fields after checking them, and parameterlessTimer.Dispose()does not wait for an in-flight callback. A local capture would close it.MoveMailAsyncwhen OneDrive is absent.Also surfaced for maintainer resolution: the repository publishes two different line-coverage floors — 80% in
CLAUDE.mdand 85% in.claude/rules/general-unit-test.md. This branch measures 84.8323%, which clears the first and not the second. The shortfall is pre-existing and repository-wide, and this branch improves it.GitHub Auto-close
None. The base of this pull request is the epic integration branch rather than the default branch, so no auto-close keyword is emitted here. Issue references in scope: #484, #480, #481, #483, #485.