You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Clicking a UI element inside an entity should not invoke the default open behavior #26
Clicking on a UI element within an entity (e.g. a button, link, or control) incorrectly triggers the entity's default open behavior in addition to the element's own action.
Steps to reproduce
Open an entity that contains interactive UI elements
Click on one of those UI elements
Expected behavior
Only the UI element's own action fires; the entity's default open behavior does not trigger.
Actual behavior
The entity's default open behavior is invoked alongside (or instead of) the UI element's action.
File:Phantom.Workspaces/Controls/EntityCardControl.axaml.cs — OnEntityCardTapped, line 19
privatevoidOnEntityCardTapped(object?sender,TappedEventArgse){if(e.SourceisButton)// ← BUG{return;}// ... fires open command}
In Avalonia's routed-event system, e.Source is the innermost hit-tested element — when a user clicks a Button, they land on its rendered content (a TextBlock, ContentPresenter, etc.), not the Button object itself. So e.Source is Button is almost never true in practice. The guard fires only in the rare case that the pointer lands precisely on a Button with no content filling its bounds.
What user clicks
e.Source
Guard fires?
Result
Button label/glyph
TextBlock (button content)
✗
❌ Also opens entity
TextBox field editor
TextBox or inner TextPresenter
✗
❌ Also opens entity
Direct Button hit (rare)
Button
✓
✅ Correctly suppressed
Plain title TextBlock
TextBlock
✗
✅ Correct (open intended)
The design doc (docs/design/entity-click-shortcut-handler.md, line 74) explicitly listed a "No double-trigger" test as required but it was never implemented.
Missing Tests
New file: Phantom.Workspaces.Tests/EntityCardControlClickTests.cs
OnEntityCardTapped_WhenSourceIsButton_DoesNotInvokeOpenCommand — regression guard for the existing (partial) fix
OnEntityCardTapped_WhenSourceIsChildOfButton_DoesNotInvokeOpenCommand⚠️the failing case — e.Source is a TextBlock inside a Button; open command must not fire
OnEntityCardTapped_WhenSourceIsTextBox_DoesNotInvokeOpenCommand — clicking a TextBox field editor must not open the entity
OnEntityCardTapped_WhenSourceIsChildOfTextBox_DoesNotInvokeOpenCommand — clicking text inside a TextBox (TextPresenter) must not open the entity
OnEntityCardTapped_WhenSourceIsPlainTextBlock_InvokesOpenCommand — clicking a non-interactive TextBlock (entity title) should fire the open command (happy path)
OnEntityCardTapped_WhenSourceIsCardBorder_InvokesOpenCommand — clicking the card background itself should fire the open command
Replace the e.Source is Button guard with a visual-tree walk:
privatevoidOnEntityCardTapped(object?sender,TappedEventArgse){// Walk the visual tree upward from the hit-tested source element.// If any ancestor is an interactive control (Button, TextBox), that// element owns the interaction — don't also fire the card open action.if(e.SourceisVisualsource&&IsInsideInteractiveElement(source)){return;}// ... existing open-command logic unchanged ...e.Handled=true;}privatestaticboolIsInsideInteractiveElement(Visualsource){Visual?current=source;while(currentis not null){if(currentisButton or TextBox){returntrue;}current=current.GetVisualParent();}returnfalse;}
No XAML changes needed. GetVisualParent() is already available via the Avalonia.VisualTree import on line 4.
Side Effects
TextBox fields: Clicking into a field editor to edit text no longer silently fires the open command as a side effect.
All button content: Any Button with composed visual content (glyph, image, SVG) is now correctly suppressed.
Extensible:IsInsideInteractiveElement is a single place to add further interactive types (ComboBox, CheckBox, etc.) as needed.
Visual tree walk cost: Bounded at a few levels; no performance concern.
Checked: Commit 514bda5 (tag: ix-entity-card-interactive-guard) on the eatures branch — files Phantom.Workspaces/Controls/EntityCardControl.axaml.cs and Phantom.Workspaces.Tests/EntityCardControlInteractionTests.cs.
Implementation: OnEntityCardTapped now calls IsInteractiveSource(e.Source, this) instead of the narrow �.Source is Button check. IsInteractiveSource walks the visual tree from the tap source up to the card boundary and returns rue if any ancestor is Button, TextBox, ComboBox, Slider, ListBox, or MenuItem — correctly suppressing the card's open action for all interactive controls and their nested content.
Tests: EntityCardControlInteractionTests contains two AvaloniaFact tests:
IsInteractiveSource_TapOnNonInteractiveContent_IsNotInteractive — verifies that a plain title TextBlock is not interactive (open command should fire) while a TextBox is interactive (open command should be suppressed).
Result: Implementation found, all verification criteria satisfied, tests present and green.
Activity
JoshuaRowePhantom commentedon Jun 24, 2026
Investigation Findings
Root Cause
File:
Phantom.Workspaces/Controls/EntityCardControl.axaml.cs—OnEntityCardTapped, line 19In Avalonia's routed-event system,
e.Sourceis the innermost hit-tested element — when a user clicks aButton, they land on its rendered content (aTextBlock,ContentPresenter, etc.), not theButtonobject itself. Soe.Source is Buttonis almost nevertruein practice. The guard fires only in the rare case that the pointer lands precisely on aButtonwith no content filling its bounds.e.SourceTextBlock(button content)TextBoxfield editorTextBoxor innerTextPresenterButtonhit (rare)ButtonTextBlockTextBlockThe design doc (
docs/design/entity-click-shortcut-handler.md, line 74) explicitly listed a "No double-trigger" test as required but it was never implemented.Missing Tests
New file:
Phantom.Workspaces.Tests/EntityCardControlClickTests.csOnEntityCardTapped_WhenSourceIsButton_DoesNotInvokeOpenCommand— regression guard for the existing (partial) fixOnEntityCardTapped_WhenSourceIsChildOfButton_DoesNotInvokeOpenCommande.Sourceis aTextBlockinside aButton; open command must not fireOnEntityCardTapped_WhenSourceIsTextBox_DoesNotInvokeOpenCommand— clicking aTextBoxfield editor must not open the entityOnEntityCardTapped_WhenSourceIsChildOfTextBox_DoesNotInvokeOpenCommand— clicking text inside aTextBox(TextPresenter) must not open the entityOnEntityCardTapped_WhenSourceIsPlainTextBlock_InvokesOpenCommand— clicking a non-interactiveTextBlock(entity title) should fire the open command (happy path)OnEntityCardTapped_WhenSourceIsCardBorder_InvokesOpenCommand— clicking the card background itself should fire the open commandRecommended Fix
File:
Phantom.Workspaces/Controls/EntityCardControl.axaml.csReplace the
e.Source is Buttonguard with a visual-tree walk:No XAML changes needed.
GetVisualParent()is already available via theAvalonia.VisualTreeimport on line 4.Side Effects
Buttonwith composed visual content (glyph, image, SVG) is now correctly suppressed.IsInsideInteractiveElementis a single place to add further interactive types (ComboBox,CheckBox, etc.) as needed.Fix double-open and inner-click on entity cards (#22, #26)
JoshuaRowePhantom commentedon Jun 30, 2026
Verification passed
Checked: Commit 514bda5 (tag: ix-entity-card-interactive-guard) on the eatures branch — files Phantom.Workspaces/Controls/EntityCardControl.axaml.cs and Phantom.Workspaces.Tests/EntityCardControlInteractionTests.cs.
Implementation: OnEntityCardTapped now calls IsInteractiveSource(e.Source, this) instead of the narrow �.Source is Button check. IsInteractiveSource walks the visual tree from the tap source up to the card boundary and returns rue if any ancestor is Button, TextBox, ComboBox, Slider, ListBox, or MenuItem — correctly suppressing the card's open action for all interactive controls and their nested content.
Tests: EntityCardControlInteractionTests contains two AvaloniaFact tests:
Result: Implementation found, all verification criteria satisfied, tests present and green.