Skip to content

Bug: quickfiler-navigation-key-collision #232

Description

@drmoisan
  • Work Mode: full-bug

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

  1. Run QuickFiler in high-confidence mode with a page that has exactly one item.
  2. Click the "pop out" button for that item.
  3. 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().
  4. RemoveSpecificControlGroupAsync then unconditionally calls RegisterNavigation() again for the newly swapped-in page.
  5. 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

  • Attached minimal logs or screenshot
  • Snippet:
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

  • High
  • Blocker
  • Medium
  • Low

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions