Summary
QuickFiler throws System.ArgumentException: Cannot add key because it already exists. Key 2 SourceId Collection when a page transition (OK/Skip, including the automatic skip triggered by popping out the last item on a page) swaps in a new page without unregistering the outgoing page's keyboard navigation keys or registering the incoming page's keys, leaving stale "Collection"-sourced keys in the shared KbdActions registry that later collide. Bundled with this fix, at the user's request, is an additive debug-logging change so that every folder-confidence probability calculation is logged (item summary, score, caller) to make a separately-observed "only a subset of items appears in high-confidence mode" symptom empirically diagnosable in future sessions.
Environment
- OS/version: Windows (VSTO Outlook add-in host)
- Repo/branch: TaskMaster, branch
TaskMaster-wt-2026-07-03-10-11, HEAD 00507b59
- Command/flags used: QuickFiler run in "high confidence mode"
- Data source or fixture: Live Outlook inbox (production run, not a test fixture)
Steps to Reproduce
- Run QuickFiler in high-confidence mode with a page that has exactly one item.
- Click the "pop out" button for that item.
- Internally:
RemoveSpecificControlGroupAsync unregisters the current page's key(s), removes the item (count reaches 0), and calls QfcFormController.SkipGroupAsync() to bring forward the next cached page via LoadControlsAndHandlers_01(TableLayoutPanel, List<QfcItemGroup>), which swaps _itemGroups without calling UnregisterNavigation()/RegisterNavigation().
RemoveSpecificControlGroupAsync then unconditionally calls RegisterNavigation() again for the newly swapped-in page.
- If any key in the newly-active page's range ("1".."N") is still occupied by an orphaned entry left behind by an earlier page that was abandoned mid-page via the same defective swap path (OK or Skip while items remained),
KbdActions.Add throws ArgumentException.
Expected Behavior
Keyboard digit-navigation keys ("1".."N", SourceId = "Collection") always match exactly the currently-displayed page's items, regardless of whether the page changed via individual item removal, "OK" (move + load next), or "Skip". No stale keys should ever remain registered for a page that is no longer displayed.
Actual Behavior
QfcCollectionController.LoadControlsAndHandlers_01(TableLayoutPanel, List<QfcItemGroup>) (QuickFiler/Controllers/QfcCollectionController.cs:252-262) - the swap-in path used by both the "OK" flow (QfcFormController.EventHandlers.cs:136-143) and the "Skip" flow (QfcFormController.EventHandlers.cs:361-395, also invoked internally from RemoveSpecificControlGroupAsync's zero-item branch) - never calls UnregisterNavigation() for the outgoing page or RegisterNavigation()/WireUpAsyncKeyboardHandler() for the incoming page. Its unused sibling SwapItemGroups (line 870-878) shows the correct pattern. _kbdHandler.StringActionsAsync is a single, session-lifetime collection shared across all pages, so keys orphaned by this gap collide the next time RegisterNavigation() walks from position 0 - which happens unconditionally at the end of RemoveSpecificControlGroupAsync (line 1219).
Logs / Screenshots
System.ArgumentException
HResult=0x80070057
Message=Cannot add key because it already exists. Key 2 SourceId Collection
Parameter name: instance
Source=QuickFiler
StackTrace:
at QuickFiler.Controllers.KbdActions`3.Add(UClass instance) in KbdActions.cs:line 121
at QuickFiler.Controllers.QfcCollectionController.RegisterNavigationAsyncAction(Int32 itemIndex, Int32 digits) in QfcCollectionController.cs:line 1346
at QuickFiler.Controllers.QfcCollectionController.RegisterNavigation() in QfcCollectionController.cs:line 1325
at QuickFiler.Controllers.QfcCollectionController.<RemoveSpecificControlGroupAsync>d__96.MoveNext() in QfcCollectionController.cs:line 1219
at QuickFiler.Controllers.QfcCollectionController.<PopOutControlGroupAsync>d__84.MoveNext() in QfcCollectionController.cs:line 966
at QuickFiler.Controllers.QfcItemController.<BtnPopOut_Click>d__163.MoveNext() in QfcItemController.EventHandlers.cs:line 67
Impact / Severity
Rationale: crashes the active QuickFiler session (unhandled exception surfaced to the user) during ordinary bulk-processing use; reachable via everyday OK/Skip page transitions, not an edge case.
Source
From: docs/features/potential/2026-07-03-quickfiler-navigation-key-collision.md
Summary
QuickFiler throws
System.ArgumentException: Cannot add key because it already exists. Key 2 SourceId Collectionwhen a page transition (OK/Skip, including the automatic skip triggered by popping out the last item on a page) swaps in a new page without unregistering the outgoing page's keyboard navigation keys or registering the incoming page's keys, leaving stale"Collection"-sourced keys in the sharedKbdActionsregistry that later collide. Bundled with this fix, at the user's request, is an additive debug-logging change so that every folder-confidence probability calculation is logged (item summary, score, caller) to make a separately-observed "only a subset of items appears in high-confidence mode" symptom empirically diagnosable in future sessions.Environment
TaskMaster-wt-2026-07-03-10-11, HEAD00507b59Steps to Reproduce
RemoveSpecificControlGroupAsyncunregisters the current page's key(s), removes the item (count reaches 0), and callsQfcFormController.SkipGroupAsync()to bring forward the next cached page viaLoadControlsAndHandlers_01(TableLayoutPanel, List<QfcItemGroup>), which swaps_itemGroupswithout callingUnregisterNavigation()/RegisterNavigation().RemoveSpecificControlGroupAsyncthen unconditionally callsRegisterNavigation()again for the newly swapped-in page.KbdActions.AddthrowsArgumentException.Expected Behavior
Keyboard digit-navigation keys ("1".."N",
SourceId = "Collection") always match exactly the currently-displayed page's items, regardless of whether the page changed via individual item removal, "OK" (move + load next), or "Skip". No stale keys should ever remain registered for a page that is no longer displayed.Actual Behavior
QfcCollectionController.LoadControlsAndHandlers_01(TableLayoutPanel, List<QfcItemGroup>)(QuickFiler/Controllers/QfcCollectionController.cs:252-262) - the swap-in path used by both the "OK" flow (QfcFormController.EventHandlers.cs:136-143) and the "Skip" flow (QfcFormController.EventHandlers.cs:361-395, also invoked internally fromRemoveSpecificControlGroupAsync's zero-item branch) - never callsUnregisterNavigation()for the outgoing page orRegisterNavigation()/WireUpAsyncKeyboardHandler()for the incoming page. Its unused siblingSwapItemGroups(line 870-878) shows the correct pattern._kbdHandler.StringActionsAsyncis a single, session-lifetime collection shared across all pages, so keys orphaned by this gap collide the next timeRegisterNavigation()walks from position 0 - which happens unconditionally at the end ofRemoveSpecificControlGroupAsync(line 1219).Logs / Screenshots
Impact / Severity
Rationale: crashes the active QuickFiler session (unhandled exception surfaced to the user) during ordinary bulk-processing use; reachable via everyday OK/Skip page transitions, not an edge case.
Source
From: docs/features/potential/2026-07-03-quickfiler-navigation-key-collision.md