Repository navigation
fix(644): replace the count-bound navigation unregister loop with a key ledger - #702
Merged
drmoisan merged 10 commits intoAug 30, 2026
Merged
Conversation
Preparation-mode output for issue #644. Carries the promoted issue record, the full-bug spec with 18 acceptance criteria, the research artifact, and the atomic plan after two preflight rounds. The plan cleared atomic-executor preflight with PREFLIGHT: ALL CLEAR and passes the MCP plan validator. No production or test code is changed by this commit; atomic execution is performed later by parallel-orchestrator.
…vigation-count-mismatch-orphan-644
Interrupted mid-toolchain: production and test changes to QfcCollectionController's navigation/unregister path are staged as they stood when execution was paused. CSharpier/analyzer/nullable/MSTest gates have not been re-run against this diff; treat as unverified until the resumed session completes the toolchain loop. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Plan checkbox state through P4-T7 and the full evidence tree (baseline, regression-testing, qa-gates, other) as captured before the toolchain loop finished. P4-T7 file-size audit is the last recorded evidence file; CSharpier/analyzer/nullable/vstest/coverage final gates had not all been confirmed against the current diff at pause time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RegisterNavigation now records the exact (SourceId, Key) pairs it added, in a lazily-initialised private ledger, and UnregisterNavigation replays that ledger and clears it instead of looping to _itemGroups.Count. An _itemGroups mutation between register and unregister can therefore no longer orphan a registration. The _registeredDigits field, its assignment, and the format expression derived from it are deleted together. That deletion is a supersession of #472, not a revert of it: #472 owns the key format and #644 owns the key cardinality, and a ledger that replays recorded strings verbatim subsumes both. The #472 guarantee is strictly strengthened. The three deletions are indivisible because removing only the format expression would leave a private field assigned and never read, which is CS0414 and which the type-check gate promotes to an error. Test side: six new ledger regression tests, three arrangement-only amendments in the frozen characterisation file, one flipped assertion with a rewritten documentation paragraph in the digits file, and comment corrections in the #468 defects file. Refs #644. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…for issue #644 Corrects the two stale CR-1 test-documentation regions in QuickFiler.Test/Controllers/QfcCollectionControllerNavigationDigitsTests.cs, both on the test UnregisterNavigation_AfterRegisteringAtOneDigitAndGrowingToTen_RemovesTheOneDigitKeys: the summary XML documentation block at lines 189-196, and the .BeEmpty(...) assertion because-message at line 222. Both still described the superseded recorded-width and count-bounded-loop mechanism that the ledger replay replaced. Documentation-comment and string-literal text only; no assertion, test name, attribute, or executable line was altered. Redacts the three PA-7 host-identity instances that this branch would otherwise introduce: two absolute host paths naming the account and an agent-worktree identifier, at research/research.2026-08-29T07-55.md line 5 and policy-audit.2026-08-29T23-06.md line 482, and one quoted search pattern carrying bare account tokens at policy-audit.2026-08-29T23-06.md line 483. A three-pattern sweep over the whole feature folder with no path exclusion returns zero on every pattern. Full C# toolchain clean in a single pass: csharpier format and check, analyzer build, nullable build, and vstest with 1254 of 1254 tests passing. No acceptance criterion changed state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…erNavigationDigitsTests (#644) Two text-only regions in QuickFiler.Test/Controllers/QfcCollectionControllerNavigationDigitsTests.cs named the deleted _registeredDigits recorded-width mechanism as the reason for their assertions. CR-6 at line 179 replaced the first BeEmpty because-message. CR-2 at line 145 replaced an unqualified After-the-fix summary sentence with a past-tense form attributed to issue #472. No executable line changed: no assertion, no Should chain, no test name, no attribute. The file remains 226 lines with 3 TestMethod attributes. The repository-wide class-scoped sweep for this defect pattern drops from 4 hits to 3, and all 3 survivors are legitimate past-tense history, which closes the pattern. Toolchain: csharpier format and check, msbuild analyzer rebuild, msbuild warnings-as-errors rebuild, and vstest for QuickFiler.Test all passed in one clean pass with 1254 of 1254 tests passing. Evidence artifacts are included under the feature folder.
… pause Agent-memory accrued during the resumed item-644 feature-review and remediation cycle (CR-6/CR-2 comment fixes, class-scoped sweep to zero new hits) that had not yet been committed when the weekly usage cap was hit mid-run. Preserving before this worktree sits idle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rification Closing feature-review pass for the navigation key-ledger change on issue #644. The reaudit confirms both cycle-2 remediation items are closed, returns a total blocking count of zero across the policy audit, code review and feature audit, and records a GO verdict for opening the pull request. AC-16 is carried forward unchanged as PARTIAL and remains unchecked in spec.md. The repository coverage comparison it demands is undecidable at the instrument's measured noise floor, and the residual risk is recorded as accepted rather than presented as a pass. Also records an independent re-run of all four C# gates in a freshly bootstrapped worktree at the branch tip: csharpier check, the analyzer rebuild, the warnings-as-errors rebuild and vstest all returned exit 0, with 1254 of 1254 tests passing. The feature-review agent-memory entry that recorded the host-identity finding was itself carrying a drive-rooted path including the account name. That line is redacted here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQwL4g1ATtc3kSQGnttcpX
…r issue #644 The PR-creation readiness check requires local_execution_overrides to be an empty list. Any run that recorded even one override cannot open its pull request while that record stands, and no drain or adjudication procedure for the field is documented anywhere under .claude/. Verified on this branch: both the readiness preflight and a real gh pr create returned that single error with every other readiness condition passing. Committed before the pull request is opened so the branch head does not move after a CI gate has run against it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQwL4g1ATtc3kSQGnttcpX
2 of 5 tasks
drmoisan
deleted the
bug/qfc-unregister-navigation-count-mismatch-orphan-644
branch
September 2, 2026 13:31
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(644): replace the count-bound navigation unregister loop with a key ledger
Summary
QfcCollectionController.UnregisterNavigationno longer derives which navigation keys to remove from the current_itemGroups.Count. It replays a ledger of the exact(SourceId, Key)pairs thatRegisterNavigationactually added, then drains that ledger._itemGroupswithout an unregister/register bracket, the count the unregister loop read no longer matched the count in force at registration, so the loop stopped short and leftKbdActionsregistrations behind.boolreturned byKbdActions.Remove. It surfaced later as anArgumentExceptionorInvalidOperationExceptionfrom an unrelatedAddorFind._registeredDigitsintroduced by the Bug: qfc-collection-navigation-digits-desync #472 fix is removed entirely; the ledger supersedes it.Why
UnregisterNavigationandRegisterNavigationdid not agree on the number of keys in play.RegisterNavigationadded one keyboard action per item group at the width in force at that moment.UnregisterNavigationthen re-derived the removal set from a live read of_itemGroups.Count.RemoveSpecificControlGroup(int)mutates_itemGroupswith no unregister/register bracket around the mutation, so any removal through that path left the two sides disagreeing, and every registration past the new, lower count was orphaned.The unbracketed mutation is reachable from
RemoveBelowThresholdAsyncvia theRemoveGroupByEntryIdseam, and from the'R'char action inQfcItemController.EventWiring.cs.This is a distinct defect from #472, which was already fixed on this code. #472 concerned the format of the keys removed — a width-1 versus width-2 mismatch — and was fixed by recording the registration width in
_registeredDigitsand replaying it. #644 concerns the count of them. Recording a width is not sufficient, because a width cannot express "a group was removed from the middle of the set". Only recording the key set itself can.The fix was deliberately kept out of #472's scope under the
CLAUDE.mdBugfix Workflow rule that a deeper design problem uncovered mid-fix opens a new issue rather than widening the current one.What Changed
Core fix —
QuickFiler/Controllers/QfcCollectionController.cs(27 lines)private int _registeredDigitsfield withprivate List<(string SourceId, string Key)> _registeredNavigationKeys, exposed through a lazily-initialisingRegisteredNavigationKeysproperty.RegisterNavigationAsyncActionnow readsSourceIdandKeyback off the constructedKaStringAsyncinstance and appends them to the ledger strictly after a successfulAdd. Ordering is deliberate: a duplicate-keyArgumentExceptionfromAddtherefore leaves the ledger clean rather than recording a key that was never registered.UnregisterNavigationiterates the ledger, removes each recorded pair verbatim, and clears the ledger. It no longer reads_itemGroupsat all.RegisterNavigationno longer assigns_registeredDigits.Tests
QuickFiler.Test/Controllers/QfcCollectionControllerNavigationLedgerTests.cs(361 lines) — six tests covering theRemoveBelowThresholdAsyncpath, the synchronousRemoveSpecificControlGroup(int)path, the width-crossing case (ten groups at width 2 shrinking to nine), repeated register/unregister state transitions, the empty-ledger negative case, and the proof thatUnregisterNavigationcompletes with_itemGroupsset to null.QfcCollectionControllerTests.cs,QfcCollectionControllerDefects468Tests.cs, andQfcCollectionControllerNavigationDigitsTests.cs, where the ledger changes the outcome the existing characterisation tests pinned.QfcCollectionControllerTests.cssits at the 500-line ceiling and its[TestMethod]count is frozen by issue Bug: qfc-collection-controller-unreachable-load-paths #468; both constraints are preserved.<Compile Include>line added toQuickFiler.Test/QuickFiler.Test.csprojfor the new file.Documentation and evidence
All planning, evidence and audit artifacts are under
docs/features/active/2026-08-27-qfc-unregister-navigation-count-mismatch-orphan-644/, including the atomic plan, two remediation cycles, and three timestamped audit sets.Architecture / How It Fits Together
KbdActions.StringActionsAsyncis a keyed registry.QfcCollectionControlleris the only component that registers navigation entries into it for the QuickFiler item collection.Before: registration wrote N entries keyed by position; unregistration recomputed N from the live collection and removed positions
1..N. The registry and the collection were coupled through a number that either side could change independently.After: registration is the sole author of the ledger, and unregistration is a pure replay of it. The registry is now coupled to the ledger, which only registration writes, so a mutation of
_itemGroupsbetween the two operations cannot desynchronise them.UnregisterNavigationhas no remaining read of_itemGroups, which is asserted directly by a test that sets that field to null.Verification
Completed
Independently re-run at the branch tip in a freshly bootstrapped worktree, in the mandated order, in one clean pass:
dotnet tool run csharpier check .Checked 1562 files, none unformattedmsbuild 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\bin\Debug\QuickFiler.Test.dll /InIsolation /TestCaseFilter:"TestCategory!=LiveOutlook"/t:Rebuildis used rather than/t:BuildsoCoreCompileruns on every project and the analyzer gate is not vacuous.Three review passes were run against this branch, plus two remediation cycles. The closing pass returns 0 blocking findings and a GO verdict. Evidence is at
docs/features/active/2026-08-27-qfc-unregister-navigation-count-mismatch-orphan-644/evidence/other/resume-toolchain-verification.2026-08-30T13-20.md.Recommended
'R'char action andRemoveBelowThresholdAsyncagainst a live Outlook session, since theTestCategory!=LiveOutlookfilter excludes host-bound tests from the local gate.Backward Compatibility / Migration Notes
RegisterNavigationandUnregisterNavigationkeep their signatures._registeredDigitswas a private field; its removal is not observable by any caller.UnregisterNavigationnow removes every registered key rather than a truncated prefix. Three existing characterisation tests encoded the old outcome and are amended accordingly — that amendment is the visible cost of the fix, and is itself the reason this work was split out of Bug: qfc-collection-navigation-digits-desync #472.Risks and Mitigations
AC-16 (no coverage regression on changed lines) is a PARTIAL and is deliberately left unchecked. It is not satisfied and is not presented as satisfied.
The acceptance clause demands a
>=comparison of a repository-wide coverage percentage at two-decimal resolution. That comparison is not decidable at that resolution by this instrument. Two runs over a byte-identical tree, with no intervening change of any kind, produced 54793 and 54811 covered lines against an identical 64221-line denominator — 85.3194% and 85.3475%, straddling the 85.3303% baseline. The instrument's root-level noise is therefore roughly 0.03 percentage points, about three times the 0.01-point shortfall the gate reported, so the observed delta carries no information about whether a regression occurred.The disposition explicitly does not rest on selecting the favourable run. Both runs are recorded; the basis is the measured indecidability of the clause.
QuickFiler/Controllers/QfcCollectionController.cs, carries[ExcludeFromCodeCoverage], and appears in 0 of the 558 class entries of the post-processed coverage document, so it sits in neither the numerator nor the denominator and cannot move the figure mechanically. That attribute was verified to be pre-existing at the branch's base commit and untouched by this change, so the argument is not circular.lines-validis identical across all three runs, confirming no production file changed instrumented size.Other risks:
RegisterNavigationAsyncAction. Mitigated by that method being the single write path, and by the record-after-successful-Addordering.UnregisterNavigationis never called. Mitigated byClear()on every unregister and by the state-transition test that asserts repeated cycles leave the registry empty.Review Guide
QuickFiler/Controllers/QfcCollectionController.cs— the whole fix, 27 lines across three hunks. ReadUnregisterNavigationfirst, then the record-after-Addordering inRegisterNavigationAsyncAction.QuickFiler.Test/Controllers/QfcCollectionControllerNavigationLedgerTests.cs— the new coverage.The feature folder is the bulk of the diff by line count and is entirely documentation.
Follow-ups
>=coverage comparison misfires on any plan that adopts it, because it demands a decision at 0.01 points from an instrument with a ~0.03-point noise floor. This design defect should be tracked as its own issue rather than left as prose in a feature folder.HintPathversion skew inUtilitiesCS.csprojandVBFunctions.csprojis pre-existing, byte-identical at the merge base, and untouched here. Reported separately for promotion.GitHub Auto-close