Skip to content

Upgrade DefaultCommandNotBoundHandler to Support Command Bubbling #4880

@tig

Description

@tig

Context

CommandsToBubbleUp and CommandBridge currently only work for Command.Accept and Command.Activate:

  • TryBubbleUp — only called from DefaultAcceptHandler/DefaultActivateHandler. Unbound commands hit DefaultCommandNotBoundHandler which just raises CommandNotBound and stops.
  • CommandBridge — hard-codes Accepted/Activated event subscriptions. Cannot bridge arbitrary commands.
  • DefaultCommandNotBoundHandler — currently a one-liner that raises CommandNotBound. Has no dispatch, no TryBubbleUp, no composite view support.

Tabs needs directional commands (Command.Up/Down/Left/Right) from a focused TitleView to bubble up to the owning Tabs view. Upgrading the not-bound path to support the full lifecycle (dispatch, bubbling, composite views) makes CommandsToBubbleUp work for any command generically.

Phases

Phase A: Reorganize existing tests

Move all bubbling-focused tests from ViewCommandTests.cs to a new CommandBubblingTests.cs. No behavior changes — just reorganization. All tests must pass before and after.

Tests to move → Tests/UnitTestsParallelizable/ViewBase/CommandBubblingTests.cs:

From #region Command Propagation Tests (lines 355-923) — move entire region:

  • CommandsToBubbleUp_DefaultIsEmpty
  • Accept_Command_DoesNotBubbleByDefault
  • Activate_Command_DoesNotBubbleByDefault
  • CommandsToBubbleUp_CanDisableAllPropagation
  • CommandsToBubbleUp_CanBeCustomized
  • Activate_BubblingUp_Fires_Activated_On_SuperView
  • Activate_BubblingUp_Fires_Activated_In_Deep_Hierarchy
  • ConsumeDispatch_Blocks_Further_Bubbling
  • CommandsToBubbleUp_StopsWhenHandled
  • CommandsToBubbleUp_WorksInDeepHierarchy
  • CommandsToBubbleUp_StopsAtIntermediateHandler
  • All 9 Padding bubble tests
  • All 6 Border bubble tests

From #region DispatchDown Tests (lines 1689-1959) — move entire region + helper class

From #region IAcceptTarget Tests — move bubbling subset:

  • Button_BubblesUp_To_SuperView
  • IAcceptTarget_In_Deep_Hierarchy_BubblesUp (1/2/3)
  • IAcceptTarget_Handled_Does_Not_BubbleUp

From #region DefaultAcceptView Tests — move bubbling subset:

  • DefaultAcceptView_Peer_Accept_Bubbles_To_DefaultAcceptView
  • DefaultAcceptView_Non_IAcceptTarget_Peer_Accept_Bubbles_To_DefaultAcceptView
  • DefaultAcceptView_Peer_IAcceptTarget_NonDefault_Accept_Bubbles_To_DefaultAcceptView
  • DefaultAcceptView_Accept_DoesNotBubble_To_DefaultAcceptView_WhenHandled
  • NonIAcceptTarget_Without_CommandsToBubbleUp_DoesNotRedirect_To_DefaultAcceptView

From #region OnAccept/Accept tests:

  • Accept_Command_Bubbles_Up_To_SuperView

From #region Values Chain Tests:

  • Values_BubbleActivatedUp_Carries_Composite_Value_To_Ancestor

From #region Bridge Cancellation Bug (lines 2739-3008) — move entire region + helper classes

Phase B: Write failing tests proving the gap

Add tests to CommandBubblingTests.cs that demonstrate unbound commands cannot bubble today. These tests must fail initially, proving the limitation.

Unbound command bubbling (via DefaultCommandNotBoundHandler):

  • UnboundCommand_BubblesToSuperView_ViaCommandsToBubbleUp — subview invokes Command.Up (unbound), superView has CommandsToBubbleUp = [Command.Up]
  • UnboundCommand_DoesNotBubble_WhenNotInCommandsToBubbleUp
  • UnboundCommand_BubblesFromPaddingSubView_ToOwner
  • UnboundCommand_BubblesFromBorderSubView_ToOwner
  • UnboundCommand_BubblesInDeepHierarchy
  • UnboundCommand_StopsWhenHandled
  • UnboundCommand_BubblingUp_ReturnsNotHandled — notification-only semantics

Composite view support for unbound commands:

  • UnboundCommand_DispatchesToTarget_InCompositeView — composite view with dispatch target receives unbound command, verifies TryDispatchToTarget fires
  • UnboundCommand_CompositeView_RaisesPostEvent_AfterDispatch

CommandBridge for arbitrary commands:

  • CommandBridge_BridgesUnboundCommand — bridge connects Command.Up, remote fires it, owner receives it
  • CommandBridge_IgnoresUnregisteredCommand — bridge for Command.Up only, Command.Down does not bridge

Phase C: Upgrade CommandBridge to support arbitrary commands

CommandBridge currently hard-codes Accepted/Activated subscriptions. Generalize to support any command.

File: Terminal.Gui/Input/CommandBridge.cs

Phase D: Upgrade DefaultCommandNotBoundHandler

Upgrade RaiseCommandNotBound and DefaultCommandNotBoundHandler to follow the full Activate-like lifecycle:

RaiseCommandNotBound — add TryDispatchToTarget and TryBubbleUp:

1. OnCommandNotBound(args) → if handled, return true
2. CommandNotBound?.Invoke(args)
3. if (!handled) TryDispatchToTarget(ctx)        ← NEW
4. if (!handled) TryBubbleUp(ctx, handled)       ← NEW
5. return args.Handled

DefaultCommandNotBoundHandler — full lifecycle with dispatch, bubbling, composite view support, and BubbleActivatedUp. No SetFocus. Includes _dispatchState reset/tracking, dispatch-routing guard, RefreshValue, BubbleActivatedUp, and CommandWillBubbleToAncestor.

ArrangerButton.cs — remove AddCommand(Command.Up/Down/Left/Right, DefaultAcceptHandler). These fall through to the upgraded DefaultCommandNotBoundHandler.

Files:

  • Terminal.Gui/ViewBase/View.Command.cs
  • Terminal.Gui/ViewBase/Adornment/ArrangerButton.cs

Files Summary

  1. Tests/UnitTestsParallelizable/ViewBase/CommandBubblingTests.cs — NEW (phases A+B)
  2. Tests/UnitTestsParallelizable/ViewBase/ViewCommandTests.cs — remove moved tests (phase A)
  3. Terminal.Gui/Input/CommandBridge.cs — generalize (phase C)
  4. Terminal.Gui/ViewBase/View.Command.cs — upgrade handler + raise method (phase D)
  5. Terminal.Gui/ViewBase/Adornment/ArrangerButton.cs — remove AddCommand calls (phase D)

Verification

After each phase:

  • dotnet test --project Tests/UnitTestsParallelizable --filter-class "*CommandBubblingTests"
  • dotnet test --project Tests/UnitTestsParallelizable --filter-class "*ViewCommandTests"
  • Phase B: new tests must FAIL (proving the gap)
  • Phase C+D: all tests must PASS
  • Full suite: only pre-existing TabsTests failures

Metadata

Metadata

Assignees

No fields configured for Feature.

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions