fix(breadcrumb): make suggestions upgrade rebuild atomic to prevent SelectRow race (#398)#399
Merged
Merged
Conversation
… race - Build all suggestion rows into a local list before touching shared state - Introduce BreadcrumbStateModel.ReplaceRows to atomically swap rows - Eliminates transient empty/partial model window that exposed ArgumentOutOfRangeException Refs: #398 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014VDtzmx27QhoXiCNvyR385
…ass pairs per 500-line policy - BreadcrumbStateModelTests split into primary + BreadcrumbStateModelSequenceTests (state-transition sequences, #398 ReplaceRows coverage) - FolderBreadcrumbBridgeRouterTests split into primary + FolderBreadcrumbBridgeRouterInFlightTests (multi-message sequences, in-flight rebuild invariants) - Updated UtilitiesCS.Test.csproj with explicit Compile Include items for new test files - Regenerated coverage verification (first-party scoped, JaCoCo format): line 86.54%, branch 80.85% - Created remediation baseline, policy-audit, feature-audit, and code-review evidence artifacts Refs: #398 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014VDtzmx27QhoXiCNvyR385
Refs: #398 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014VDtzmx27QhoXiCNvyR385
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested title
fix(breadcrumb): make suggestions upgrade rebuild atomic to prevent SelectRow race (#398)
Summary
ArgumentOutOfRangeException("Row selection requires -1 or an index in [0, 0]", actual value 1) thrown fromBreadcrumbStateModel.SelectRowduring QuickFiler high-confidence loads with two or more folder suggestions.FolderBreadcrumbBridgeRouter.SetSuggestionsAsynccleared the breadcrumb model synchronously before its firstawait, then re-added rows one at a time on thread-pool continuations. The unawaitedUpgradeSuggestionsAsyncstarted byBreadcrumbBridgeCoordinator.SetSuggestionstherefore raced the host's subsequentSetFolderSelectedIndex(1)call against a transiently cleared/partial model.BreadcrumbStateModel.ReplaceRowsseam that swaps the backing rows in a single synchronous operation and reconciles the selection before publishing. No empty window is ever observable.TaskCompletionSourcefakes — no sleeps, no wall-clock waits, no temp files.FolderArray.Length > 1plus an asynchronously-resolving hierarchy provider.Why
QuickFiler's breadcrumb pipeline populates suggestion rows synchronously, then upgrades them to full ancestor-chain rows via a fire-and-forget async rebuild. The rebuild's up-front
Clear()violated the documented readback contract ("rows are populated immediately... so the selection contract holds without awaiting the provider"), aborting the entire QuickFiler load whenever the host selected a row before the rebuild finished. The crash was intermittent because a synchronously-resolving (cached) provider leaves no race window.What Changed
Core fix (production, 2 files):
UtilitiesCS/OutlookObjects/Folder/FolderBreadcrumbBridgeRouter.cs—SetSuggestionsAsyncnow builds rows into a local list (no_modelmutation while awaiting) and applies them atomically at the end.UtilitiesCS/OutlookObjects/Folder/BreadcrumbStateModel.cs— newReplaceRowsoperation: single synchronous backing-list swap that preserves a still-valid selection index.Tests:
QuickFiler.Test/Viewers/BreadcrumbBridgeCoordinatorTests.cs— regression testSelectRow_WhileSuggestionsUpgradeInFlight_DoesNotThrowAndAppliesSelection(failed pre-fix with the exact reported exception).UtilitiesCS.Test/OutlookObjects/Folder/FolderBreadcrumbBridgeRouterTests.cs+ newFolderBreadcrumbBridgeRouterInFlightTests.cs— in-flight invariants: row count never drops below the pre-upgrade count;FolderContains/GetSelectedFolder/SelectRowstay consistent mid-upgrade; selection survives the swap.UtilitiesCS.Test/OutlookObjects/Folder/BreadcrumbStateModelTests.cs+ newBreadcrumbStateModelSequenceTests.cs—ReplaceRowsseam coverage; files split as partial classes to satisfy the 500-line file limit (all four now 235–320 lines).UtilitiesCS.Test/UtilitiesCS.Test.csproj— explicit<Compile Include>items for the two new test files.Docs/evidence:
docs/features/active/2026-07-20-breadcrumb-suggestions-upgrade-race-398/with issue, plan, remediation plan, policy/code/feature audit artifacts (initial review + R4 re-audit PASS), and Phase 0 baseline / QA-gate / regression evidence.Architecture / How It Fits Together
QfcItemController.AssignFolderComboBox→ItemViewer.SetFolderSuggestions→BreadcrumbBridgeCoordinator.SetSuggestions(immediate plain rows + unawaited upgrade) →FolderBreadcrumbBridgeRouter.SetSuggestionsAsync(now: local build + atomicReplaceRowspublish) →BreadcrumbStateModel(state) → render JSON posted to the WebView2 page. Host selection calls (SetFolderSelectedIndex→SelectRow) can now interleave with the upgrade at any point without observing an empty or partial model.Verification
Completed (evidence in the feature folder):
ArgumentOutOfRangeExceptionsignature reproduced pre-fix (EXIT 1), pass-after post-fix (EXIT 0).csharpier check .(EXIT 0), analyzer build (EXIT 0), nullable build withTreatWarningsAsErrors(EXIT 0),vstest.console.exe /EnableCodeCoverage— 5061/5061 tests passing (5054 baseline + 7 new).issue.mdverified and checked off; R4 re-audit verdict PASS with zero blocking findings.Recommended:
Backward Compatibility / Migration Notes
BreadcrumbStateModel.ReplaceRowsis additive. Completed-upgrade semantics are byte-identical (suggestion rows with chains/probabilities; unresolvable scored rows fall back to plain rows; non-scored rows verbatim).Risks and Mitigations
ReplaceRowscould alter selection behavior for callers other than the upgrade path. Mitigation: dedicated seam tests plus the full 5061-test suite passing unchanged; completed-upgrade output verified identical.Review Guide
UtilitiesCS/OutlookObjects/Folder/FolderBreadcrumbBridgeRouter.cs— the local-build + atomic-publish rewrite ofSetSuggestionsAsync(the fix).UtilitiesCS/OutlookObjects/Folder/BreadcrumbStateModel.cs—ReplaceRowsseam and selection reconciliation.QuickFiler.Test/Viewers/BreadcrumbBridgeCoordinatorTests.cs— the end-to-end regression test.Follow-ups
<Compile Include>forPercentageFormatterTests.csinUtilitiesCS.Test.csproj(CS2002 warning on rebuild) — pre-existing at base, out of scope here, already tracked as a promoted potential entry._rowsreference swap.GitHub Auto-close