Summary
Three related defects in EfcItemController keyboard-action registration: the KbdActions<> indexer
setter silently drops unregistered keys, the async expansion path never registers or removes the
'B'/'D' jump keys that the sync path does, and the resulting asymmetry can make a later sync
expansion throw ArgumentException.
Environment
- OS/version: Windows 11 Pro 10.0.26200
- Runtime: .NET Framework 4.8.1 WinForms VSTO add-in
- UI path:
QuickFiler/Controllers/EfcItemController.cs expansion and keyboard-registration paths
- Data source or fixture: n/a
Steps to Reproduce
Defect B is the user-visible one:
- Open the Email Filer and expand an item through the async expansion path (
ToggleExpansionAsync).
- Press
B or D to jump to the message body / detail.
- Observe that the jump keys are not registered.
- Now expand through the sync path (
ToggleExpansion(ToggleState.On)), collapse through the async
path, then expand through the sync path again.
- Observe an
ArgumentException from KbdActions<>.Add.
Expected Behavior
RegisterActions registers the actions it is given.
- The async and sync expansion paths produce the same keyboard-action state.
- Repeated expand/collapse cycles in any order do not throw.
Actual Behavior
A - RegisterActions registers nothing (EfcItemController.cs:691).
_keyboardHandler.CharActions[action.Key] = action.Value uses the KbdActions<> indexer setter
(KbdActions.cs:38-47), which performs a Find(key) and only assigns when the element is non-null.
A missing key is a silent no-op, not an insert. Combined with the !overwriteDuplicates filter at
:687-690 - which removes exactly the keys that are present - the overwriteDuplicates: false path
is guaranteed to register nothing. RegisterActions currently has zero call sites, so this is latent.
B - async and sync expansion paths are not equivalent (EfcItemController.cs:931-956 vs :862-905).
ToggleExpansion(ToggleState.On) registers 'B'/'D' in CharActions (:879-888) and
ToggleExpansion(Off) removes them (:902-903). ToggleExpansionOn() (:944-956) and
ToggleExpansionOff() (:931-942) - the bodies dispatched by ToggleExpansionAsync (:913, :922) -
do neither.
C - duplicate registration throws (EfcItemController.cs:879-888).
KbdActions<>.Add throws ArgumentException when the (sourceId, key) pair already exists
(KbdActions.cs:92-98). Because of B, a sync-On -> async-Off -> sync-On sequence leaves the
"Item"/'B' and "Item"/'D' entries in place and the second sync-On throws on a UI-thread call path.
Logs / Screenshots
Impact / Severity
Defect C is an unhandled exception on a UI-thread call path. Defect B silently removes documented
keyboard navigation. Defect A is latent today but will surface the moment RegisterActions is wired up.
Source
From: docs/features/potential/2026-08-07-efc-item-controller-keyboard-registration-defects.md
Summary
Three related defects in
EfcItemControllerkeyboard-action registration: theKbdActions<>indexersetter silently drops unregistered keys, the async expansion path never registers or removes the
'B'/'D'jump keys that the sync path does, and the resulting asymmetry can make a later syncexpansion throw
ArgumentException.Environment
QuickFiler/Controllers/EfcItemController.csexpansion and keyboard-registration pathsSteps to Reproduce
Defect B is the user-visible one:
ToggleExpansionAsync).BorDto jump to the message body / detail.ToggleExpansion(ToggleState.On)), collapse through the asyncpath, then expand through the sync path again.
ArgumentExceptionfromKbdActions<>.Add.Expected Behavior
RegisterActionsregisters the actions it is given.Actual Behavior
A -
RegisterActionsregisters nothing (EfcItemController.cs:691)._keyboardHandler.CharActions[action.Key] = action.Valueuses theKbdActions<>indexer setter(
KbdActions.cs:38-47), which performs aFind(key)and only assigns when the element is non-null.A missing key is a silent no-op, not an insert. Combined with the
!overwriteDuplicatesfilter at:687-690- which removes exactly the keys that are present - theoverwriteDuplicates: falsepathis guaranteed to register nothing.
RegisterActionscurrently has zero call sites, so this is latent.B - async and sync expansion paths are not equivalent (
EfcItemController.cs:931-956vs:862-905).ToggleExpansion(ToggleState.On)registers'B'/'D'inCharActions(:879-888) andToggleExpansion(Off)removes them (:902-903).ToggleExpansionOn()(:944-956) andToggleExpansionOff()(:931-942) - the bodies dispatched byToggleExpansionAsync(:913,:922) -do neither.
C - duplicate registration throws (
EfcItemController.cs:879-888).KbdActions<>.AddthrowsArgumentExceptionwhen the(sourceId, key)pair already exists(
KbdActions.cs:92-98). Because of B, a sync-On -> async-Off -> sync-On sequence leaves the"Item"/'B'and"Item"/'D'entries in place and the second sync-On throws on a UI-thread call path.Logs / Screenshots
Impact / Severity
Defect C is an unhandled exception on a UI-thread call path. Defect B silently removes documented
keyboard navigation. Defect A is latent today but will surface the moment
RegisterActionsis wired up.Source
From: docs/features/potential/2026-08-07-efc-item-controller-keyboard-registration-defects.md