refactor(782): consolidate the PR #778 post-merge review residuals into one delivery - #789
Merged
Merged
Conversation
… folder Promote the consolidated PR #778 post-merge review findings to issue #782, create the active refactor feature folder, and untrack the stale orchestrator checkpoint that had been committed from the #704 recovery worktree (artifacts/ is gitignored; the checkpoint is session-local state). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016RGpFtBp79mAkJp2vGwmqU
Adds the research record for issue #782, which verifies every finding from the three-phase post-merge review of PR #778 against the current tree and reports seven divergences from the requirements source. Adds spec.md (12 acceptance criteria) and user-story.md, required for full-feature work mode, plus a local verbatim copy of the PR #778 body so the finding text is available without network access. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016RGpFtBp79mAkJp2vGwmqU
Adds the 9-phase, 101-task atomic plan for issue #782 after two preflight revision rounds, and amends the spec's AC5 evidence method. AC5 previously named the token GetField("_dispatcher", which matches no single line anywhere in the tree because CSharpier wraps all six acquisition sites. The criterion could not fail. It now names the wrap-tolerant token "_dispatcher" scoped to *.cs files, matching the plan tasks that verify it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016RGpFtBp79mAkJp2vGwmqU
Applies 46 defects reported across four atomic-executor preflight rounds. The plan now clears preflight with PREFLIGHT: ALL CLEAR and CONVERGENCE: NO FURTHER ROUNDS EXPECTED. The substantive corrections were: the thirteen Phase 0 baseline artifacts were never staged, which made both terminal porcelain gates unpassable; the shared test seam used nullable annotations that raise CS8632 in this repository and would have failed the nullable build; a mandated document edit recorded 2026-09-05 as the PR #778 merge date where the merge commit dates to 2026-09-04; three baseline counts were pinned as hard equalities with no record-and-continue escape; and three tasks justified a TestResults removal on the premise that CSharpier would process its output, which is false because CSharpier 1.2.6 honours .gitignore. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016RGpFtBp79mAkJp2vGwmqU
- add a shared dispatcher-not-initialized message and a strict single-read `UiThread.Dispatcher` guard; align the `UiThread` assertion with the new `Init()` wording - route `WpfDispatcherYield` and `ProgressTracker*` through the shared or already-captured dispatcher and remove dead null checks in ribbon command invalidation - capture Phase 0 baseline evidence and update the #782 plan/spec to record corrected baselines and the documented omission of the C03 latch re-arm Refs: #782
Execution reached P1-T9 and could not pass it. The plan's implementation of finding C03, which re-arms the UiThread.Init() single-shot latch so a failed Initialize() can be retried, causes DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue to fail reproducibly. The executor bisected it to the single re-arm line: with it, UtilitiesCS.Test plus TaskMaster.Test returns 5179/5180 with that test failing at a 21-second duration; without it, 5180/5180. The branch base passes 6992/6992 both before and after. The UiSyncContext and AutoScaleFactor getters call Init() lazily when their backing field is null, and Initialize() constructs a SyncContextForm and shows it. Without the re-arm the latch stays set after a first failure and later Init() calls are cheap no-ops; with it, every read of either accessor retries the WinForms construction and throws again, starving the thread pool and defeating a 500 ms CancelAfter. C03 is withdrawn under the omission branch AC2 already carries. The retry semantics will be promoted as a separate follow-up entry, in the same way the issue already carved out the C09 behavioral half. Also folds in four execution-time corrections: the CoreCompile gate now asserts a deterministic per-project sub-count instead of an unstable header equality; two baseline figures are corrected to the executor's measurements; the hook-blocked Remove-Item form is replaced; and the coverage line-counting selection is pinned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
The branch was rebased from a007f72 onto main at 77c6d31 mid-run, which orphaned the pre-782-base tag and staled every Phase 0 baseline. The tag is re-anchored to 736c2cf, the last documentation-only commit before the implementation commit, and verified as an ancestor of HEAD whose source tree is byte-identical to origin/main. The baselines were re-measured at that anchor and the affected Phase 0 tasks are unchecked for re-recording: csharpier now reports 1581 files, the suite reports 6997 passing, and first-party coverage is 112355/132967 line and 26500/33480 branch. The 34-commit main advance touches no file in this delivery's write set. A further preflight round removed five defects. Two were blocking and of the same class: both promotion-state gates searched docs/features/potential/promoted without excluding this delivery's own promoted entry, which carries the tokens they match and an issue line naming #782, so each would have fired its positive branch before any promotion occurred. One of the two would have checked off AC8 on the strength of an unrelated 2026-08-07 WebView2 entry. The third blocking defect was a preamble that still told the executor to create the anchor tag at the branch tip, which P0-T2 now prohibits because it would make every anchored gate compare a tree against itself. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
…dead guards Addresses issue #782 findings C01, C02, C05, C06, C08, C09-message, C20, and C23. Adds the shared UiThread.DispatcherNotInitializedMessage constant, rewrites the Dispatcher getter to read the backing field once, corrects the WpfDispatcherYield comment and message, updates the breaking UiThread_Tests assertion, applies the lambda-capture fix in both ProgressTracker files, and removes the two dead null comparisons in RibbonViewer.EngineCommands.cs. Finding C03 is deliberately absent from that list. SD18 withdraws it after a measured, bisected regression, so this phase changes no line on its account and naming it would misdescribe the commit. The omission is recorded in the Phase 6 code-review artifact. Also carries the Phase 0 baseline evidence, re-recorded under SD23 against the re-anchored pre-782-base at 736c2cf. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
…ributes Addresses issue #782 findings C15 and C16. Moves the P74 Report/child/root-close region out of UtilitiesCS.Test/Threading/ProgressTracker_Tests.cs into a new partial-class file ProgressTracker_ReportAndViewerTests.cs, bringing the 514-line source file to 272 lines and the new file to 260. Splits the combined [TestClass, DoNotParallelize] attribute into two separate attribute lines. The region was extracted mechanically rather than retyped, so no test method name, attribute, or assertion changed. A scoped run of the 24 ProgressTracker_Tests cases confirms all 24 reassemble under their original fully-qualified names and pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
…install scope Phase 3 of issue #782. Replaces five hand-rolled reflective acquisitions of the private static UiThread dispatcher backing field with a single shared install scope, and corrects the documentation that described the pre-#778 mechanism. - C12/C13: UtilitiesCS.Test/TestHelpers/UiThreadDispatcherScope.cs now owns the only reflection site in that assembly. IdleAsyncQueue_Tests reimplements its ForceDispatcherNull/RestoreDispatcher helpers over the scope and drops its own DispatcherField() helper. - C10: the populated-branch UiThread_Tests case obtains its sentinel dispatcher from a dedicated STA host that shuts down and joins, instead of calling Dispatcher.CurrentDispatcher on a pooled MTA worker. - C11: the null-branch assertion lambda is expression-bodied. - C18/C25: EmailMoveMonitorTests reads the existing QuickFiler.Test fixture accessor and takes a typed Dispatcher snapshot; the two stale "avoid WindowsBase" clauses are removed. - C19: the three P27-T2 passages now describe the synchronous InvalidOperationException path rather than a NullReferenceException. Repository-wide reflective acquisitions of the backing field fall from six to two, the two being the UtilitiesCS.Test scope and the QuickFiler.Test fixture that a separate assembly must keep. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 4 of issue #782. - C14/SD7: IdleActionQueue_Tests gains [DoNotParallelize] and a [TestCleanup] that reuses the existing ResetStaticState() helper and additionally detaches the queue's idle handler, which is rebuilt over the private static method because the registered delegate instance is not nameable from the test. - C21: new WpfDispatcherYieldTests.YieldAsync_ProductionFallbackWithoutDispatcher_ThrowsNamingInit exercises the production fallback provider from a dedicated fresh thread, so the thread-affinitized lookup cannot pre-empt it. The class gains [DoNotParallelize] because it now installs into the process-global static. - C26: two new tests pin the guard on both initialization entry points - ProgressTrackerAsync.InitializeAsync faults its returned task, and ProgressTracker.Initialize throws synchronously, so the assertions differ in shape. - C20: the existing strict-contract assertion now pins the message with WithMessage("*UiThread.Init()*"). - S2-1: the QfcItemController Arrange comment no longer claims both dispatcher cases fail the same way; it now distinguishes the accessor throw from the never-pumped parked dispatcher. Fail-before and pass-after evidence for all three new tests is recorded under evidence/regression-testing/, together with an exception dossier for C10 and C02, whose hazards cannot be demonstrated by a deterministic failing test without violating the test-independence and no-timing-construct rules. Nine-assembly locally-filtered run: 7000 total, 7000 passed, 0 failed, against a recorded baseline of 6997 plus the three added tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 5 of issue #782. All edits are made in place; no #584 evidence file is renamed and no existing Timestamp: value is altered. - S3-6: #584 spec Status moves from Draft to Merged (PR #778, merge commit 1c3b210, 2026-09-04), and the three disagreeing file lists are reconciled - the in-scope list is extended to the authoritative six-file Write Set and the "Files/modules to change" section now cross-references the Write Set instead of carrying a third enumeration. - S3-7/SD10: the three ~40/~62 call-site estimates are replaced with the verified figure, stated against the pre-782-base tag so it stays true after Phase 1 removes two live reads. - S3-1: four passages asserting an execution order the recorded timestamps do not establish are restated without the ordering claim; the conclusion never depended on it, because the sibling positive test passed in the same run. - S3-2: the two formatter command cells now record the six-path invocation that actually ran, Appendix B is labelled as a reference rather than a transcript, and a new section 8 gap entry records the deviation and its whole-tree read-only mitigation. - S3-3: the evidence-artifact count is corrected from 34 to 38. - S3-4: the issue-update mirror gains a note explaining why its filename timestamp and its Timestamp: field differ and why neither is changed. - S3-5/SD3: fifteen EXIT_CODE fields are normalized to a single integer with the per-command breakdown preserved below; the two gates whose success outcome is non-zero additionally declare ExpectedExitCode. - S3-8: seven evaluative spans prohibited by the tonality rule are replaced with evidence-first wording. - S3-9/SD9: #584 finding F5 is dispositioned in both artifacts as discharged by C12 and C13 rather than C26, with the record that it was never promoted. The P5-T14 gate artifact records one deviation for the caller's attention: the P5-T10 premise that all ten remaining per-command values were 0 does not hold for three files, whose zero-match search commands recorded 1. The instruction was followed literally and every original value is preserved verbatim below the normalized field. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 6 of issue #782. - evidence/other/code-review.2026-09-05T23-00.md carries a disposition row for every finding identifier - C01 through C26, S2-1, S3-1 through S3-9, S4-1 and S4-2 - naming the file that changed or the recorded reason none did, and the commit that carried it. It additionally records nine labelled entries: the C03 omission with its measured regression, bisect and mechanism; the SD5 message-tail change; the SD4 retained test-method naming inaccuracy; the SD10 file-count divergence from the PR #778 review body; the SD9 attribution of #584 finding F5; the two SD14 supersessions of spec Constraint 8 clauses; the SD7 justification for the added serialization attribute; and the SD17 coverage-collection deviation. - evidence/other/upstream-followups-drm-copilot.2026-09-05T23-02.md records the two items that belong to drm-copilot: the S4-1 stale agent-memory notes and the S3-1 request to define Timestamp: semantics. Both live under .claude/, which is overwritten by push-down, so neither is edited here. - evidence/qa-gates/p6-t3-dotclaude-untouched.md records the .claude/ gate. Its committed-history condition holds with zero lines. Its worktree condition does not hold: two paths under .claude/agent-memory/atomic-planner/ are dirty, written by the planner at 22:17 before this executor's first commit at 22:32:36. P6-T3 is left unchecked and the residue is reported rather than worked around. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 7 pass 1. The whole-tree `dotnet tool run csharpier format .` removed one blank line between the closing #endregion and the class's closing brace in UtilitiesCS.Test/Threading/ProgressTracker_Tests.cs. The residue is from the Phase 2 split, which moved the trailing region out of this file. P2-T1's format run was scoped to the newly created part and did not re-format the part the split left behind, so this whole-tree run is the first to reach it. The Phase 7 loop restarts from P7-T1 because a tracked file was rewritten. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 7 of issue #782. The loop ran twice. Pass 1 did not close: the whole-tree formatter rewrote UtilitiesCS.Test/Threading/ProgressTracker_Tests.cs, committed as 4744892, and the loop restarted from step 1. Pass 2 closed clean, all five steps green with no tracked-file rewrite after step 1: - format: Formatted 1583 files, before- and after-images byte-identical - format check: exit 0, Checked 1583 files, baseline 1581 plus exactly the two files this delivery creates - analyzer build: exit 0, 0 Warning(s), 0 Error(s), 18 project build-output lines against a recorded baseline of 18 - nullable build: exit 0, 0 Warning(s), 0 Error(s), 18 CoreCompileInputs.cache deletion lines against a recorded baseline of 18; the aggregate CoreCompile token count is recorded as an observation and is not gated, per SD19 - tests with coverage: exit 0, 7000 total, 7000 passed, 0 failed, 0 skipped Coverage, first-party, all-descendant .//line selection pinned by SD22 so both sides come from one method: - line 112363/132961 = 84.51%, against a baseline of 112355/132967 = 84.50% - branch 26500/33480 = 79.15%, against a baseline of 26500/33480 = 79.15% - lines-valid differ by 6, or 0.0045%, so the two runs are comparable and the aggregate comparison is asserted rather than waived - changed-line coverage is 7 of 7 executable added production lines, 100%, and the uncovered enumeration is empty - RibbonViewer.EngineCommands.cs contributes zero executable changed lines because RibbonViewer.cs declares the partial type [ExcludeFromCodeCoverage]; that is recorded rather than reported as a zero-coverage row Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
…mary Phase 8 of issue #782. Fifteen of the seventeen acceptance criteria are checked off in their source documents. The two that remain unchecked were each resolved through their task's explicitly gated Branch B and carry a verbatim deferral line: - AC8, the C09 behavioural follow-up promotion. The filtered promoted-entry search returned zero files. Both mandatory exclusions fired as the plan predicted: the webview2 entry matches "apartment state" on line 86 under issue #476, and this delivery's own promoted entry matches "non-STA" on lines 63 and 107 under issue #782. - AC-U1, the pull request body. No pr_body_782.md exists. Both are owned by the orchestrator and performed outside this plan. evidence/other/ac-status-summary.2026-09-05T23-15.md is the single status summary. It carries the two gated branch records with their full command output and 17 criterion rows, each row's recorded checkbox state verified line by line against the state actually present in spec.md and user-story.md, and every cited artifact path confirmed present on disk. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 8 closure check for issue #782. The format-check condition holds: Checked 1583 files, identical to the count evidence/qa-gates/p7-t2-format-check.md records, confirming the Phase 8 Markdown edits did not disturb the Phase 7 clean pass. The additional confirmation holds: this task's subtracted porcelain output contains no path under the Write Set and no path under the feature folder. The byte-identity comparison does not hold. Two paths under .claude/agent-memory/atomic-planner/ appear on the closing side and not on the recorded baseline side, because the concurrent write landed after P0-T2 captured the baseline image and before this task captured the closing one. P8-T20 is left unchecked and the residue is reported rather than committed, deleted, or reverted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Terminal task of the issue #782 plan. This commit performs no promotion; it records which state the promotion is in. Branch B: the filtered promoted-entry search returned zero files, so the UiThread.Init() latch re-arm has not yet been promoted. The owner is the orchestrator, which performs promotion outside this plan. The mandatory exclusion fired as the plan predicted. This delivery's own promoted entry, 2026-09-05-pr-778-post-merge-review-residuals.md, matches the unfiltered pattern on line 56 with the token "single-shot latch" and carries - Issue: #782 on line 7. Both were re-derived against the current tree. Without the exclusion, Branch A would fire against this delivery's own issue number and record a promoted state that is false. C03 was withdrawn under SD18 after a measured regression, so the follow-up is not a deferred implementation of the same change: it asks for the retry semantics by a mechanism that does not re-arm the latch the UiSyncContext and AutoScaleFactor accessors consume, which is a different design. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
The P8-T21 plan line previously stated the repeated comparison's outcome before that comparison had been run. This commit replaces the prediction with the observed figures: Checked 1583 files, CHECK_EXIT_CODE=0, PORCELAIN_RAW_COUNT=2, SUBTRACTED_COUNT=2, WRITESET_OR_FEATURE_PATHS_IN_SUBTRACTED=0, BYTE_IDENTICAL=False. The plan file sits inside the four-path subtraction set, so amending it cannot change the subtracted comparison and this record terminates rather than requiring a further round. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Both P6-T3 acceptance conditions now hold with zero lines. The two .claude/agent-memory/atomic-planner/ paths that failed the porcelain condition at the 23-03 capture were written by the atomic-planner, not by this executor, and were cleared by the orchestrator with `git checkout -- .claude/` and `git clean -fd .claude/`. The artifact retains the superseded failing capture and its attribution so the gate history stays auditable. Committed ahead of P8-T20 because the artifact path is not in that task's four-path subtraction set. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
All P8-T20 acceptance conditions hold. The format check reproduces the Phase 7 count exactly (Checked 1583 files, exit 0), and the porcelain output is empty, so the four-path subtracted comparison is byte-identical to the empty baseline image recorded in p0-t2-base-ref.md. The byte-identity condition previously failed by the two .claude/agent-memory/atomic-planner/ paths recorded in P6-T3, which the orchestrator has since cleared. The artifact retains the superseded failing capture and its attribution so the gate history stays auditable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Promotes both deferred follow-ups through the MCP promotion lifecycle, which discharges AC8's first clause. Issue #787 carries the C09 behavioural half: UiThread.Init() accepts a call from any thread and installs a non-pumping dispatcher into set-once process-global state. Promoted as a bug because it is a missing precondition on an existing contract, not a new capability. Issue #788 carries finding C03, which this delivery withdrew under SD18. The entry records the measured regression, the bisect that attributes it to the single re-arm line, the interaction with the two lazy accessors that causes it, and three candidate approaches, so a future attempt does not repeat the naive form. Recording the withdrawal only as prose in a feature folder would have lost it when the folder is archived. AC8's second clause was already satisfied by the upstream follow-up record written in Phase 6. The acceptance-criteria status summary is appended rather than rewritten, so the executor's deferral record stays intact as the true state at the moment its gate ran. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Records the review of the branch against origin/main at 77c6d31. Verdict is PASS with zero blocking findings. The review re-ran the toolchain independently rather than accepting the reported figures, and re-derived every coverage number from the raw Cobertura documents. It also established the fact that makes the UiThread.cs modified-file coverage row waivable: the baseline and post-change uncovered line sets are identical, so the 77.11% to 76.83% movement is arithmetic from a covered three-line wrapped throw collapsing to one line against a fixed 19-line residue in untouched host-bound members, not a regression. No delivery artifact had recorded that comparison. Four remediation inputs are recorded. Two are procedural coverage triggers. Two are Should-fix accuracy defects in this delivery's own artifacts and are addressed next. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
… input record Addresses findings R3 and R4 of the feature review recorded in remediation-inputs.2026-09-05T23-48.md. Both are Should-fix accuracy defects in this delivery's own audit artifacts; neither blocks the pull request, which the review cleared with PASS and zero blocking findings. R3. spec.md AC10, spec.md AC11, the Behavioral Contract WpfDispatcherYield bullet, the Write Set test-file row, and the code-review entry (b) claimed that the removal of the WpfDispatcherYield message tail was pinned by the C20 WithMessage assertion. It was not: both assertions were the wildcard "*UiThread.Init()*", which also matches the pre-782 message because that message likewise contains UiThread.Init(). Both assertions now read WithMessage(UiThread.DispatcherNotInitializedMessage), which FluentAssertions compares against the entire message, and the prose is corrected to state exactly what that form establishes: a tail appended at a throw site fails the assertion at that site, and neither assertion detects an edit to the constant's own wording. The change is observed rather than derived: appending the removed tail at the WpfDispatcherYield throw site fails YieldAsync_WithoutDispatcher_RemainsStrict while the sibling test still passes, and both pass once the mutation is reverted. R4. evidence/baseline/p0-t7-coverage.md recorded the re-measured first-party figures 112355 and 26500 while naming the earlier collection's output document as its input. The artifact now records both collections with their own inputs and figures, marks the re-measurement authoritative, states that its output document is not retained, and supplies a reproduction procedure. R1 and R2 are accepted and waived respectively, as maintainer decisions following the reviewer's recommendations. No file was changed for either item. The dispositions are recorded in evidence/other/r1-r2-maintainer-disposition.2026-09-06T00-15.md. No production .cs file is changed. Full local toolchain passed in one uninterrupted pass: csharpier format and check (1583 files, exit 0), analyzer build (0 warnings, 0 errors), nullable build (0 warnings, 0 errors), and the nine-assembly run at 7000 passing with 0 failures. Those are locally-filtered figures with the four shell-icon classes excluded, not CI figures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Adds the three Phase 5 artifacts that could not be part of the first commit: r-p5-t3-staged-set.md records that commit's staged set and cannot be a member of the set it records, and r-p5-t5-post-commit-verification.md and r-p5-t6-closure.md both record the first commit's SHA and so could not exist before it. A second commit is used rather than an amend for that reason. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
All 53 tasks of remediation-plan.2026-09-06T00-15.md are checked off. The check-offs for [P5-T2] onward were written after the staging that produced commit b91dd85, so the plan file's completion state could not be part of either earlier commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
The re-audit after remediation returns PASS with zero blocking findings. It re-executed every gate rather than reading the reported figures, reproduced all four coverage counters from the raw Cobertura documents, and read the TRX counters directly, including the falsification run that now backs the corrected message-pinning claim with an observation rather than a derivation. Fixes the two attribution defects the re-audit raised. The R1/R2 disposition record was titled a maintainer disposition, but the deciding party is the orchestrator and no maintainer ratification was sought. The heading is corrected and the consequence stated: the R2 coverage waiver rests on orchestrator judgment plus the reviewer's identical-uncovered-line-set evidence, not on the ratification CLAUDE.md requires for a formal COM/VSTO exemption. The filename keeps its original token because the remediation plan's acceptance conditions reference that exact path. AC-U2 permitted two production behavior changes, one of which was withdrawn under finding C03. The criterion was satisfied by a narrower change than it allowed for, which is true but read as though the withdrawn behavior had shipped. It now says so explicitly and points at issue #788. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
AC-U1 requires one branch and one pull request delivering all in-scope findings, with the body mapping every finding identifier to the file that changed or to the recorded reason it did not. It was correctly unchecked until the pull request existed. Pull request 789 satisfies it, and the criterion now records which sections carry the mapping and which identifiers each covers. This is the last open acceptance criterion. Both sources are now fully checked: 12 of 12 in spec.md and 5 of 5 in user-story.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
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
refactor(782): consolidate the PR #778 post-merge review residuals into one delivery
Summary
UiThread.Dispatcherread its backing field exactly once, documents the deliberate non-lazy contract in XML comments, and routes both not-initialized throw sites through one sharedinternal constmessage that names only the publicInit()entry point and states the STA requirement.UtilitiesCS.Testmanipulation of theUiThread._dispatcherstatic from four independent reflection sites to a single disposable install scope with one reflection acquisition, and movesQuickFiler.Testonto the existingUiThreadDispatcherFixture.Currentaccessor.NullReferenceExceptionmechanism.EXIT_CODE:fields that did not parse as integers.Why
PR #778 changed
UiThread.Dispatcherfrom a null-returning accessor to one that throwsInvalidOperationException. The subsequent three-phase review confirmed the fix and found no functional regression, but produced 26 code findings plus a group of documentation and evidence defects. Individually none justified its own delivery. Collectively they were about to be archived along with the #584 feature folder and lost.Two of the findings were latent defects in the new code rather than hygiene: a test that created a dispatcher on a pooled MTA thread and never shut it down, and a double read of the non-volatile static inside the new getter, where the guard and the return value could observe different values.
What Changed
Production, 5 files, +52/-15
UtilitiesCS/Threading/UiThread.cs— single-read getter (C02);internal const string DispatcherNotInitializedMessageshared with the sibling throw site (C20); message names onlyInit()(C06) and states the STA/UI-thread requirement (C09, message half);<summary>,<remarks>and<exception>XML documentation on a file that previously carried none (C08); a comment recording why this accessor deliberately does not self-heal where its two siblings do (C05).UtilitiesCS/OutlookObjects/Folder/WpfDispatcherYield.cs— throw routed through the shared constant; the false comment corrected (C20).UtilitiesCS/Threading/ProgressTracker.cs,ProgressTrackerAsync.cs— theInvokeAsynclambdas use the already-captured dispatcher instead of re-reading the static (C23).TaskMaster/Ribbon/RibbonViewer.EngineCommands.cs— the two now-deaddispatcher != nullcomparisons removed (C01).Tests, 10 files plus the test project, +690/-385
UtilitiesCS.Test/TestHelpers/UiThreadDispatcherScope.cs, one disposable install scope holding the only reflection acquisition; the four previous sites migrate to it and it restores the prior value even when that value was null (C12, C13).UtilitiesCS.Test/Threading/ProgressTracker_Tests.cssplit into apartial classpair, following the four-fileTimeOutTask_Testsprecedent, so both halves sit under the 500-line limit and every fully-qualified test name is preserved (C16, C15). The new file is registered as a<Compile Include>.UiThread_Tests.csobtains its sentinel dispatcher on a dedicated STA thread inside ausing, soBeginInvokeShutdownand the thread join run on every exit path including a failing assertion (C10, C11).WpfDispatcherYieldproduction fallback and for bothProgressTrackerAsync.InitializeAsyncand the synchronousProgressTracker.Initialize(C21, C26).EmailMoveMonitorTests.csreads throughUiThreadDispatcherFixture.Currentinstead of its ownFieldInfo, so the order-independence guard fails rather than silently degrading if the field is renamed (C18, C25).IdleActionQueue_Tests, with the class serialized because the cleanup mutates process-globalApplication.Idlestate shared with two sibling classes (C14).NullReferenceExceptionprose corrected inIdleAsyncQueue_Tests.csandQfcItemController.InitializationTests.Part2.cs(C19, S2-1).Documentation and evidence, 23 files in the #584 folder
Ordering prose the timestamps contradict (S3-1); the formatter-command cells, which claimed a whole-tree run where the plan ran CSharpier over six explicit paths (S3-2); the evidence count corrected from 34 to the tracked figure of 38 (S3-3); a filename/
Timestamp:mismatch noted in place rather than by renaming a committed file (S3-4); fifteen non-integerEXIT_CODE:fields normalized (S3-5);spec.mdstatus and file lists reconciled (S3-6); call-site counts corrected to 49 live reads across 25 production files, with the review body's 26-file figure recorded as a divergence rather than silently adopted (S3-7); six evaluative spans replaced with neutral phrasing (S3-8); and the ProgressTrackerAsync synchronization follow-up recorded as discharged by the shared install scope rather than by the new test (S3-9).Architecture / How It Fits Together
UiThreadremains a static holder for UI-thread state captured once at host startup. The change tightens two things around it without altering the initialized path.First, the not-initialized precondition now has one message with one owner.
UiThread.DispatcherandWpfDispatcherYield's local guard both throwInvalidOperationException(UiThread.DispatcherNotInitializedMessage). The constant isinternal, both throw sites are in the same assembly, andUtilitiesCS.Testreaches it through the existingInternalsVisibleTogrant. TheWpfDispatcherYieldmessage loses its caller-specific tail as a direct consequence, which is intended and is asserted by the tests rather than left to inspection.Second, test manipulation of the process-global
_dispatcherstatic is funnelled throughUiThreadDispatcherScope, which is the only place inUtilitiesCS.Testthat acquires theFieldInfo. It isIDisposable, restores the captured prior value on dispose including when that value was null, and offers an explicitInstallNull()so a test can establish a known prior rather than depending on whatever a sibling assembly left behind. That matters becauseQuickFiler.TestandUtilitiesCS.Testshare a test host in the full-suite run.UiThread.Init()itself is byte-identical to its base form. See Follow-ups.Verification
Completed
The full C# toolchain passed in a single uninterrupted pass, in the order CLAUDE.md requires:
dotnet tool run csharpier format .— exit 0,Formatted 1583 files, before and after tree images byte-identical.dotnet tool run csharpier check .— exit 0,Checked 1583 files.msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true— exit 0,0 Warning(s),0 Error(s).msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true— exit 0,0 Warning(s),0 Error(s), all 19 projects recompiled so the gate was not vacuous.dotnet-coverage collect—Total tests: 7000,Passed: 7000,Failed: 0.Coverage did not regress. First-party line coverage is 112351/132961 and branch 26498/33480, byte-equal to the baseline on all four counters with equal denominators. Changed-line coverage over the implementation phases was 7 of 7 executable lines.
Two review cycles were run against the branch. Both returned PASS with zero Blocking findings. The second re-executed every gate independently rather than reading the reported figures, re-aggregated four Cobertura documents under two selections, and read the TRX counters directly.
UtilitiesCS/Threading/UiThread.csreads 77.11% to 76.83% on modified-file coverage. That is arithmetic, not a regression: the baseline and post-change uncovered line sets are the identical 19 lines, and the movement comes from a covered three-line wrapped throw collapsing to one line against a fixed residue in untouched host-bound members. No line moved from covered to uncovered.Recommended
vstest.console.exeover the nine test assemblies with/InIsolation.Note for local reproduction: this repository pins its SDK through
global.json, sodotnetmust be invoked with the repo-local.dotnet-sdkonPATHandDOTNET_ROOTset. Local runs also exclude fourUtilitiesCS.Testshell-icon classes that stallSHGetFileInfoon some workstations; CI covers them, so the CI total exceeds the 7000 figure above.Backward Compatibility / Migration Notes
No public API signature changed.
UiThread.Dispatcherkeeps its non-nullable declared type and its private setter.One observable change: the
InvalidOperationExceptionmessage text. It now names only the publicInit()rather than the privateInitialize(), states the STA/UI-thread requirement, and is shared by both throw sites.WpfDispatcherYield's previous message carried a caller-specific tail, "before yielding folder tree work", which the shared constant necessarily drops. Both assertions on that message were converted from a wildcard to an exact comparison against the constant, and the conversion was verified by mutation: appending the removed tail at the throw site makes the test fail, and reverting makes it pass.No file was renamed and no evidence file's
Timestamp:was altered.Risks and Mitigations
WpfDispatcherYieldsite. Intended per the review finding. Mitigated by the exact-match assertions, which now detect a caller-specific tail reappearing at either site.[DoNotParallelize]. The full nine-assembly suite passes.ProgressTracker_Testssplit could have dropped a test. Mitigated by asserting all 24 original fully-qualified test names are present and passing after the split; thepartial classshape preserves every name.Review Guide
Suggested order:
UtilitiesCS/Threading/UiThread.cs— the shared constant, the single-read getter, and the XML documentation.UtilitiesCS/OutlookObjects/Folder/WpfDispatcherYield.cs— the second throw site.UtilitiesCS.Test/TestHelpers/UiThreadDispatcherScope.cs— the new seam, and in particular its dispose semantics.EmailMoveMonitorTests.cs.ProgressTracker_Testssplit, which is the largest diff and is a mechanical move.The
ProgressTracker_Tests.cs/ProgressTracker_ReportAndViewerTests.cspair is the noisy part of the diff: 288 added and 249 removed lines that are almost entirely a move.The delivery's own audit trail is under
docs/features/active/2026-09-05-pr-778-post-merge-review-residuals-782/, including the plan, the research record, the evidence tree, and both review cycles' artifacts.Follow-ups
UiThread.Init()accepts a call from any thread and installs a non-pumping dispatcher into set-once process-global state. Deliberately out of scope here, because it is a production behavior change that breaks one existing test call site.Initialize()throws so a failed initialization could be retried. Implementing it causedDictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValueto fail reproducibly, bisected to the single re-arm line: with it,UtilitiesCS.TestplusTaskMaster.Testreturned 5179/5180 at a 21-second duration; without it, 5180/5180. The mechanism is that theUiSyncContextandAutoScaleFactorgetters callInit()lazily, so re-arming turns a one-time expensive WinForms failure into a repeated one on every accessor read, starving the thread pool. The entry records the measurement and three candidate approaches so a future attempt does not repeat the naive form..claude/agent-memory/task-researcher/, and the S3-1 request to defineTimestamp:semantics in theevidence-and-timestamp-conventionsskill. Nothing under.claude/is modified by this branch.maincarry the same pattern, and no repository rule codifies the prohibition. Disclosed rather than silently dropped.GitHub Auto-close