Skip to content

Fixes #4473 - Fix Command system to support bubbling etc...#4620

Draft
tig wants to merge 121 commits intogui-cs:v2_developfrom
tig:copilot/fix-command-propagation-issue-clean
Draft

Fixes #4473 - Fix Command system to support bubbling etc...#4620
tig wants to merge 121 commits intogui-cs:v2_developfrom
tig:copilot/fix-command-propagation-issue-clean

Conversation

@tig
Copy link
Collaborator

@tig tig commented Jan 23, 2026

Command Bubbling Refactor - Progress Tracker

Branch: pr-4620
Last Updated: 2026-02-14


Status Summary

Stream Component Status Notes
2 View.Command.cs DONE Core infrastructure complete
3.1 Button DONE IAcceptTarget, Space/Enter→Accept
3.2 Shortcut DONE BubbleDown, Accept/Activate separation
3.3 Dialog DONE OnAccepting/OnActivating/OnAccepted
3.4 Menu System IN PROGRESS Menu.OnAccepting commented out; active work
3.5 TextView DONE Enter→NewLine vs Accept separation
3.6 TextField DONE HotKey char input fix
3.7 SelectorBase DONE CommandsToBubbleUp configured
3.8 FlagSelector DONE HotKey refactored
4 Tests MOSTLY DONE Some skipped tests remain
5 Documentation DONE command.md comprehensive

STREAM 2: View Command Framework — DONE

File: Terminal.Gui/ViewBase/View.Command.cs

All planned features implemented:

  • CommandsToBubbleUp property (read-only IReadOnlyList<Command>)
  • DefaultAcceptView property with auto-detection of IAcceptTarget + IsDefault
  • TryBubbleUpToSuperView() — checks DefaultAcceptView, then SuperView.CommandsToBubbleUp, then Padding
  • BubbleDown(target, ctx) — dispatches commands downward with IsBubblingDown=true
  • RaiseAccepting / RaiseActivating / RaiseHandlingHotKey — cancelable, with bubbling
  • RaiseAccepted / RaiseActivated — non-cancelable post-events
  • DefaultAcceptHandler / DefaultActivateHandler / DefaultHotKeyHandler
  • ICommandContext with IsBubblingUp, IsBubblingDown, Source (WeakReference), Binding
  • CommandEventArgs uses Handled (from HandledEventArgs)

STREAM 3: View-Specific Changes

3.1 Button — DONE

File: Terminal.Gui/Views/Button.cs

  • Implements IAcceptTarget interface
  • Space → Command.Accept (was HotKey)
  • Enter → Command.Accept (was HotKey)
  • Mouse clicks → Command.Accept
  • MouseHoldRepeat support
  • OnHotKeyCommand override invokes Command.Accept
  • Does NOT raise Activating (by design)

3.2 Shortcut — DONE

File: Terminal.Gui/Views/Shortcut.cs

  • CommandsToBubbleUp = [Command.Activate, Command.Accept]
  • OnActivating override — BubbleDown to CommandView (conditional: binding exists and source != CommandView)
  • OnAccepting override — BubbleDown to CommandView (same conditional)
  • OnActivated — invokes Action
  • OnAccepted — invokes Action
  • Accept and Activate are fully separate paths (no cross-invocation)
  • BubbleDown conditional is active and working

3.3 Dialog — DONE

Files: Terminal.Gui/Views/Dialog.cs, Terminal.Gui/Views/DialogTResult.cs

  • CommandsToBubbleUp = [Command.Accept] (in DialogTResult)
  • OnActivating override — sets Result from button index
  • OnAccepting override — handles non-default button detection
  • OnAccepted override — calls RequestStop after button press
  • DefaultAcceptView used for IsDefault button routing

3.4 Menu System — IN PROGRESS

Files: Terminal.Gui/Views/Menu/Menu.cs, MenuBar.cs, MenuItem.cs, PopoverMenu.cs

Done:

  • Menu.CommandsToBubbleUp = [Command.Accept, Command.Activate]
  • MenuItem inherits from Shortcut (gets BubbleDown behavior)
  • MenuBar inherits from Bar

Not Done (active work):

  • Menu.OnAccepting is fully commented out (~25 lines, lines 107-136)
    • QuitKey handling in menu context
    • SuperMenuItem propagation for PopoverMenu (no SuperView case)
    • SuperView null-check logic
  • Integration testing of full menu hierarchy bubbling

3.5 TextView — DONE

File: Terminal.Gui/Views/TextInput/TextView/TextView.Commands.cs

  • Enter key: Multiline ? Command.NewLine : Command.Accept
  • ProcessEnterKey calls RaiseAccepting when EnterKeyAddsLine=false
  • NewLine and Accept properly separated

3.6 TextField — DONE

File: Terminal.Gui/Views/TextInput/TextField/TextField.Commands.cs

  • Space key removed from bindings (allows text input)
  • Enter key → Command.Accept (default)
  • HotKey char input fix when focused (commit 84cd3f5)

3.7 SelectorBase — DONE

File: Terminal.Gui/Views/Selectors/SelectorBase.cs

  • CommandsToBubbleUp = [Command.Activate, Command.Accept]
  • Navigation commands (Up, Down, Left, Right) handled
  • OnAccepting override

3.8 FlagSelector — DONE


STREAM 4: Testing — MOSTLY DONE

Key test files:

  • Tests/UnitTestsParallelizable/Views/ShortcutTests.Command.cs — comprehensive
  • Tests/UnitTestsParallelizable/ViewBase/CommandBubblingTests.cs — hierarchy tests
  • Tests/UnitTestsParallelizable/ViewBase/CommandContextTests.cs
  • Tests/UnitTestsParallelizable/ViewBase/ViewCommandTests.cs
  • View-specific tests updated across Button, Dialog, SelectorBase, etc.

Remaining:

  • Some tests marked [Fact(Skip = "...")] — need to be enabled once Menu work completes
  • Menu hierarchy integration tests needed after Menu.OnAccepting is completed

STREAM 5: Documentation — DONE

File: docfx/docs/command.md

Comprehensive coverage including:

  • Command system overview and architecture
  • Command invocation flow diagram
  • Activate/Accept/HotKey comparison table
  • View command behavior matrix (all views)
  • CommandsToBubbleUp configuration table
  • BubbleDown pattern explanation
  • IAcceptTarget interface documentation
  • CWP pattern explanation with examples

Remaining Work

Must Complete (Menu System) — see menu-refactor.md

  1. Implement Menu.OnAccepting — uncomment/rewrite the commented-out logic:
    • QuitKey special-case handling
    • SuperMenuItem propagation when Menu has no SuperView (PopoverMenu case)
  2. Verify MenuBar + PopoverMenu + MenuItem integration
  3. Enable skipped menu-related tests

Verify Before Merge

  • Full test suite passes (dotnet test Tests/UnitTestsParallelizable)
  • No new warnings
  • Coverage not decreased
  • All [Fact(Skip=...)] tests resolved (enabled or removed with rationale)

tig and others added 9 commits January 23, 2026 12:08
- Add POST-GENERATION-VALIDATION.md with comprehensive checklist
  for validating AI-generated code before commits
- Add formatting.md with detailed spacing, brace, and blank line rules
- Update REFRESH.md to reference the validation checklist
- Update CLAUDE.md with references to new validation docs

These additions address the most common formatting violations
that AI agents make when writing code for Terminal.Gui.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add clean-code-review.md task guide for reimplementing branches
  with narrative-quality commit histories
- Remove scenario-modernization.md (outdated task)

The clean code review task provides a workflow for AI agents to
reimplement messy branch histories into clean, reviewer-friendly
commit sequences suitable for PR review.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Addresses: gui-cs#4473 (Menu etc. with Selectors are broken)

- Add command-propagation-plan.md with phased implementation plan
- Remove command-propagation-analysis.md (superseded by plan)

The plan introduces PropagatedCommands property to enable opt-in
command propagation from SubViews to SuperViews, solving the issue
where commands from nested views (e.g., FlagSelector in MenuBar)
don't reach their containing views.

Phase breakdown:
1. Foundation - PropagatedCommands infrastructure
2. WeakReference - Lifecycle-safe CommandContext.Source
3. Shortcut propagation - Enable Command.Activate for Shortcuts
4. Test cleanup - Re-enable skipped tests
5. MenuBar propagation - Full menu hierarchy support (deferred)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Addresses: gui-cs#4473 (Menu etc. with Selectors are broken)

This commit implements Phases 1 and 2 of the command propagation plan:

Phase 1 - PropagatedCommands Infrastructure:
- Add PropagatedCommands property to View (default: [Command.Accept])
- Add PropagateCommand helper method for command bubbling
- Refactor RaiseAccepting to use PropagateCommand helper
- Reorganize View.Command.cs regions for clarity

Phase 2 - WeakReference for CommandContext.Source:
- Change ICommandContext.Source from View? to WeakReference<View>?
- Update CommandContext to use WeakReference for lifecycle safety
- Update InvokeCommand overloads to wrap 'this' in WeakReference
- Update all views using ctx.Source to use TryGetTarget pattern:
  - Dialog, DialogTResult
  - MenuBar, PopoverMenu
  - ComboBox, OptionSelector
  - ScrollSlider, Shortcut

The PropagatedCommands property enables opt-in command propagation:
when a SubView raises a command that isn't handled and the command
is in the SuperView's PropagatedCommands list, the command bubbles up.

BREAKING CHANGE: ICommandContext.Source is now WeakReference<View>?
Code accessing ctx.Source must use TryGetTarget pattern:
  if (ctx?.Source?.TryGetTarget (out View? view) == true) { ... }

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a standalone test application for verifying Command.Activate
propagation through the Shortcut → Window hierarchy.

The example demonstrates:
- CheckBox (CanFocus=false) in Shortcut → propagates to Window
- CheckBox (CanFocus=true) in Shortcut → propagates to Window
- Button in Shortcut → propagates to Window

This serves as a manual test bed for the PropagatedCommands feature
and helps verify that command context (Source) is properly preserved
when commands bubble up through the view hierarchy.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add tests to verify PropagatedCommands and WeakReference changes:

CommandPropagationTests.cs (NEW - 588 lines):
- Tests for command propagation through view hierarchies
- Tests for WeakReference lifecycle safety
- Tests for PropagatedCommands customization
- Tests for Command.Accept and Command.Activate propagation

ViewCommandTests.cs (updated):
- Add tests for PropagatedCommands default value
- Add tests for PropagateCommand helper behavior
- Add tests for IsDefault button propagation

CommandContextTests.cs (updated):
- Update tests for WeakReference-based Source property
- Add TryGetTarget pattern tests

InputBindingTests.cs (updated):
- Update tests for WeakReference compatibility

All tests follow Terminal.Gui patterns and are added to
UnitTestsParallelizable for parallel execution.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
command.md:
- Document PropagatedCommands property
- Explain command propagation behavior
- Add examples for customizing propagated commands

events.md:
- Add section on command events and propagation
- Document CommandEventArgs and ICommandContext
- Explain WeakReference pattern for Source property

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update scenarios to use TryGetTarget pattern for accessing
CommandContext.Source after the WeakReference change:

Menus.cs:
- Update command handlers to use TryGetTarget

MouseTester.cs:
- Update mouse event handlers for new pattern

UICatalogRunnable.cs:
- Update command context handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change absolute path to relative path for cross-machine compatibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Jan 23, 2026

Codecov Report

❌ Patch coverage is 80.58435% with 206 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.09%. Comparing base (83ea4a4) to head (716509a).
⚠️ Report is 31 commits behind head on v2_develop.

Files with missing lines Patch % Lines
Terminal.Gui/ViewBase/View.Command.cs 88.81% 1 Missing and 17 partials ⚠️
Terminal.Gui/Views/ProgressBar.cs 25.00% 14 Missing and 1 partial ⚠️
Terminal.Gui/Views/Menu/MenuBar.cs 72.54% 8 Missing and 6 partials ⚠️
Terminal.Gui/Views/Selectors/SelectorBase.cs 67.44% 5 Missing and 9 partials ⚠️
Terminal.Gui/Views/Selectors/FlagSelector.cs 82.89% 6 Missing and 7 partials ⚠️
Terminal.Gui/Views/Shortcut.cs 92.65% 1 Missing and 12 partials ⚠️
Terminal.Gui/Views/Menu/PopoverMenu.cs 7.69% 11 Missing and 1 partial ⚠️
Terminal.Gui/Views/Dialog.cs 66.66% 4 Missing and 5 partials ⚠️
Terminal.Gui/Input/Mouse/Mouse.cs 68.00% 8 Missing ⚠️
Terminal.Gui/ViewBase/WeakReferenceExtensions.cs 25.00% 5 Missing and 1 partial ⚠️
... and 36 more
Additional details and impacted files
@@              Coverage Diff               @@
##           v2_develop    #4620      +/-   ##
==============================================
- Coverage       77.35%   76.09%   -1.26%     
==============================================
  Files             444      447       +3     
  Lines           44992    44991       -1     
  Branches         6536     6583      +47     
==============================================
- Hits            34805    34238     -567     
- Misses           8296     8806     +510     
- Partials         1891     1947      +56     
Files with missing lines Coverage Δ
Terminal.Gui/App/PopoverBaseImpl.cs 72.72% <100.00%> (-8.67%) ⬇️
Terminal.Gui/Input/CommandBinding.cs 100.00% <100.00%> (ø)
Terminal.Gui/Input/CommandBindingsBase.cs 95.52% <100.00%> (ø)
Terminal.Gui/Input/CommandEventArgs.cs 100.00% <100.00%> (ø)
Terminal.Gui/Input/Keyboard/KeyBindings.cs 100.00% <100.00%> (ø)
Terminal.Gui/Input/Mouse/MouseBindings.cs 100.00% <100.00%> (ø)
Terminal.Gui/ViewBase/IValue.cs 100.00% <ø> (ø)
Terminal.Gui/Views/Button.cs 100.00% <100.00%> (+2.85%) ⬆️
Terminal.Gui/Views/FileDialogs/FileDialog.cs 50.11% <100.00%> (-2.68%) ⬇️
Terminal.Gui/Views/Menu/MenuItem.cs 100.00% <100.00%> (+9.19%) ⬆️
... and 49 more

... and 38 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 83ea4a4...716509a. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tig tig changed the title Fix command propagation for nested views (Shortcut hierarchy) Fixes #4473 - Fix command propagation for nested views (Shortcut hierarchy) Jan 23, 2026
@tig tig changed the title Fixes #4473 - Fix command propagation for nested views (Shortcut hierarchy) Fixes #4473 - Fix command propagation for nested views Jan 23, 2026
tig added 2 commits January 23, 2026 15:59
Clarify and strengthen event forwarding between Shortcut and its CommandView, ensuring Activating and Accepting events propagate correctly without recursion. MouseHighlightStates now defaults to MouseState.In for better UI feedback. Refactor property implementations and clean up code. Add comprehensive unit tests verifying CheckBox state changes, event propagation, and default behaviors. Enhance ShortcutTestWindow to log detailed event info and demonstrate propagation scenarios.
@tig tig marked this pull request as draft January 26, 2026 19:44
tig added 11 commits January 26, 2026 12:46
- Updated Key equality operators to handle nulls safely and avoid NullReferenceExceptions.
- ComboBox Accept command now uses non-nullable View in TryGetTarget and passes a WeakReference<View> to CommandContext for consistency.
- Shortcut.Key property now always returns Key.Empty if unset, and setter no longer throws on null.
- Simplified GetWidthDimAuto by removing unnecessary null-forgiving operators.
- Fixed CommandViewOnActivating logic to prevent recursion and ensure correct event propagation.
- Cleaned up redundant key initialization and clarified KeyView.Text assignment.
- Overall, these changes improve code safety, event handling, and clarity.
Refactored the Shortcut class to replace explicit backing fields
(_alignmentModes and _minimumKeyTextSize) with auto-implemented
properties for AlignmentModes and MinimumKeyTextSize. Set default
values directly in the property declarations and used the C# field
keyword in setters. This streamlines property definitions and
simplifies the codebase.
…/Terminal.Gui into copilot/fix-command-propagation-issue-clean
…/Terminal.Gui into copilot/fix-command-propagation-issue-clean
Improve command/event handling for menus and shortcuts

Refactor Shortcut, Menu, MenuBar, and PopoverMenu to enhance command propagation, event handling, and resource management. Set PropagatedCommands by default, improve design-time support, and clarify event subscriptions. Add helper methods for event origin detection, enforce proper menu hierarchy, and update documentation and logging for better maintainability and debugging.
tig added 2 commits February 11, 2026 17:43
Update SelectorBase to use IsBubblingDown guard for BubbleDown, allowing programmatic InvokeCommand calls and preventing recursion. Remove OnHandlingHotKey override to avoid double-Activate bugs. Add custom HotKey handler to FlagSelector: restores focus, no flag toggle when focused. Rewrite OptionSelector and FlagSelector tests for correct HotKey/Activate behavior. Update command.md docs and table to match keyboard spec. Skip unrelated Menu HotKey test.
@tig tig force-pushed the copilot/fix-command-propagation-issue-clean branch from c10c405 to 608ac79 Compare February 12, 2026 01:27
tig added 9 commits February 11, 2026 19:17
…/Terminal.Gui into copilot/fix-command-propagation-issue-clean
Update OnAccepted to use ICommandContext instead of CommandEventArgs across View and related classes. Revise all overrides and usages in Dialogs, CharMap, MenuBar, MenuItem, Prompt, OptionSelector, SelectorBase, Shortcut, and tests. Add logging to command handlers for improved debugging. Improve OptionSelector activation and cycling logic. Modernize and streamline command event handling throughout the codebase.
Refactor context menu lifecycle in TextField and TextView to create, register, and dispose menus on focus changes, improving resource management and preventing leaks. Update mouse event handling to avoid null reference errors. In debug builds, assign unique Ids to context menus for easier debugging.

In FlagSelector, streamline checkbox event handling by attaching handlers in OnSubViewAdded and removing the _updatingChecked flag. Move activation and value change logic to dedicated methods for clarity. Clean up redundant and commented code.

Update SelectorBase to prevent double event bubbling. These changes enhance robustness, maintainability, and debuggability of UI components.
@tig tig changed the title Fixes #4473 - Fix command propagation for nested views Fixes #4473 - Fix Command system to support bubbling etc... Feb 13, 2026
tig and others added 17 commits February 13, 2026 14:47
Updated Dialog<TResult> to use command binding source for Accept actions. Added and revised tests in ViewCommandTests and DialogTests to verify Accept command bubbling to DefaultAcceptView and dialog acceptance from DatePicker, including event count assertions. Adjusted test expectations for new bubbling behavior.
- Always forward Accept to DefaultAcceptView after Accepting, regardless of bubbling source
- Dialog sets Result based on pressed button or default before base Accepting
- Use command context Source consistently in Accepting logic
- Update tests to reflect new Accept bubbling and event firing behavior
- Ensure dialog Accepted event is not fired for handled Cancel actions
- Improves consistency and clarity of Accept handling and event order
Refactored View, Dialog, and Wizard command logic to ensure Accept/Enter key events propagate correctly and dialogs are properly accepted. Updated input injection and dialog acceptance tests for accuracy. Removed obsolete ProgressBar and SpinnerView command tests. Code refactored for clarity and maintainability.
Align FlagSelector with spec: hotkey now only restores focus and never toggles value, even when focused. Refactor activation logic to ensure correct event bubbling and value changes, especially for user interactions and programmatic activation. Update tests to reflect new double-click behavior. Minor code cleanups and documentation improvements.
HotKey now only restores focus and never toggles the flag value.
A new Command.HotKey handler is added to skip activation.
OnHandlingHotKey is simplified to be a no-op when focused.
Comments and documentation are updated for clarity.
Refactored HotKey handling in FlagSelector to properly restore focus without toggling when not focused, using a new _suppressHotKeyActivate flag and method overrides. Removed the old AddCommand HotKey handler. Also fixed logic for removing the "None" checkbox to only do so when ShowNoneFlag is not set, and cleaned up related redundant code.
Previously, typing a character matching the TextField's HotKey (e.g., 'E' in "_Enter Path") would trigger the HotKey even when the TextField was focused, preventing normal text input. Now, when the TextField has focus, the HotKey is canceled and the character is inserted as expected. This is achieved by overriding OnHandlingHotKey in TextField and updating the View base logic to allow key processing when the HotKey is canceled. Includes code cleanups, improved null checks, and a new unit test to verify correct behavior. Minor formatting and style improvements are also included.
Removed Space key binding from PopoverMenu to allow text input. Improved TextView hotkey handling so Space and other printable characters are not consumed by Command.Activate. Added unit tests to verify correct insertion of all printable characters and proper Space key behavior. Clarified hotkey handling differences between TextField and TextView.
- Uncomment BubbleDown conditionals in OnActivating and OnAccepting so
  commands only bubble down when triggered by a binding (not programmatic)
  and the source is not the CommandView itself
- Remove incorrect InvokeCommand(Activate) from OnAccepting, keeping
  Accept and Activate as separate command paths per design
- Add custom Activate handler that returns false when IsBubblingUp is true,
  allowing SubViews like CheckBox to complete their own activation
- Split ShortcutTests into partial classes (Command, KeyDown, Mouse)
- Add 5 new tests covering Accept/Activate separation, BubbleDown via
  Space key, Enter key Accept path, and Action invocation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Don't set Handled when activation comes from the CommandView so
ColorPicker16.OnActivated runs and picks the color from the mouse
position. Only set Handled when cycling via HotKey or other sources.
Fix the matching code example in shortcut.md which had the logic
inverted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Menu, add tests

- Move TargetView and Command properties from MenuItem down to Shortcut base class
- Move OnAccepted logic (TargetView invocation) from MenuItem to Shortcut
- Fix Menu.OnSubViewAdded: replace AddCommand with Accepting event (collision fix)
- Remove dead code from MenuItem (~65 lines) and Menu (OnAccepting, OnVisibleChanged)
- Simplify Menu.SelectedMenuItem to getter-only property
- Add 9 Shortcut.Command tests, 8 MenuItem tests, 10 Menu tests, 5 Bar tests
- Update command.md with Shortcut/MenuItem/Menu/Bar command table rows
- Add menus-refactor.md plan for upcoming MenuBar/PopoverMenu work
- Clean up stale plan files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mouse clicks on a MenuBarItem land on its CommandView SubView, so the
event source is CommandView, not MenuBarItem. Added OnActivating override
that walks the SuperView chain via FindMenuBarItemForSource to find the
containing MenuBarItem and toggle its PopoverMenu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added MenuItem click non-functional issue to Phase 6d
- Added Phase 8: fix integration tests (MenuBar/PopoverMenu, FileDialog)
- Updated execution order and verification steps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant