Summary
When a single entity's "View" (the EntityWorkspaceTabViewModel workspace tab) is taller than the visible pane, the view cannot be scrolled — the lower portion of the entity is clipped and unreachable, and the mouse wheel does nothing. The tab's DataTemplate wraps the card in a ScrollViewer with VerticalScrollBarVisibility="Auto", but the content chain never establishes a bounded vertical viewport, so at runtime Extent.Height == Viewport.Height and no scrollbar ever engages. Two nested ClipToBounds="True" layers then silently clip the overflow instead of surfacing it.
Root Cause
The single-entity view is rendered by the DataTemplate for EntityWorkspaceTabViewModel in features\Phantom.Workspaces\Templates\WorkspaceDataTemplates.axaml:35-55 (added by commit c58069a6, "Fix #1066"):
<DataTemplate DataType="vm:EntityWorkspaceTabViewModel">
<ScrollViewer Classes="entity-card-single"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
AllowAutoHide="False">
<ContentControl Classes="entity-card-shell entity-card-single-host"
DataContext="{Binding EntityCardNode}"
Content="{Binding}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="0,12,0,0"
MinWidth="160">
<ContentControl.MaxWidth> ... </ContentControl.MaxWidth>
</ContentControl>
</ScrollViewer>
</DataTemplate>
The visual tree inside the tab is:
DockControl (document host — MainWindow.axaml:253-266, no outer scroll wrapper)
└─ ScrollViewer (entity-card-single, V=Auto, H=Auto, AllowAutoHide=False)
└─ ContentControl (entity-card-shell entity-card-single-host, VAlign=Top, HAlign=Center, no Height/MaxHeight)
└─ ControlTemplate root: StackPanel (SharedStyles.axaml:207)
└─ Border (entity-card-shell-border, ClipToBounds=True) (SharedStyles.axaml:210)
└─ ContentPresenter
└─ EntityCardControl root: StackPanel (ClipToBounds=True) (EntityCardControl.axaml:10-14)
└─ header / field editors / external card / raw JSON / status
The failure is a measure-constraint / height-propagation problem, not a disabled scrollbar:
- The inner
ContentControl (WorkspaceDataTemplates.axaml:40) uses VerticalAlignment="Top" and sets no Height/MaxHeight, so its DesiredSize.Height is the full (unbounded-upward) height the card wants.
- A
ScrollViewer only shows a vertical scrollbar when its arranged Bounds.Height is less than the content's DesiredSize.Height. When the dock document does not impose a finite height on the ScrollViewer — or when an ancestor measures the Y axis with PositiveInfinity (an Auto-height slot / StackPanel-like measure / nested outer scroller) — the ScrollViewer's Viewport.Height equals its Extent.Height, so no scrollbar appears and the wheel has no effect. This matches the reported symptom exactly.
ClipToBounds="True" on both the shell Border (features\Phantom.Workspaces.Gui.Shared\Styles\SharedStyles.axaml:210) and the EntityCardControl root StackPanel (features\Phantom.Workspaces\Controls\EntityCardControl.axaml:13) means the overflow of the tall card is silently clipped rather than made visible — hiding the fact that layout produced an unbounded-height chain.
There is no VerticalScrollBarVisibility="Disabled" and no MaxHeight anywhere in the chain; the string VerticalScrollBarVisibility="Auto" is present but ineffective because the viewport is never bounded relative to content.
Host context. The tab body is hosted directly in Phantom.Dock.Avalonia's DockControl (features\Phantom.Workspaces\MainWindow.axaml:253-266, AutoCreateDataTemplates="False"), with no app-level ScrollViewer around the document. The EntityWorkspaceTabViewModel DataTemplate is the sole layout for the tab body, so responsibility for making the over-tall entity scrollable lives entirely in that template/shell chrome — confirming the bug is here, not in outer chrome.
Why it slipped through. Commit c58069a6 (Fix #1066) added only the horizontally-centred, width-capped chrome and covered it with XAML text-scan tests (features\Phantom.Workspaces.Agent.Gui.Tests\MainWindowAxamlTests.cs:435-451) that assert the literal string VerticalScrollBarVisibility="Auto" but never realize the visual tree, so they cannot detect that Extent/Viewport never diverge at runtime.
Affected Files
| File |
Contribution |
features\Phantom.Workspaces\Templates\WorkspaceDataTemplates.axaml |
EntityWorkspaceTabViewModel DataTemplate (:35-55); ScrollViewer :36; ContentControl VerticalAlignment="Top", no Height/MaxHeight :40-46 |
features\Phantom.Workspaces.Gui.Shared\Styles\SharedStyles.axaml |
entity-card-shell template root StackPanel (:207); shell Border ClipToBounds="True" (:210) |
features\Phantom.Workspaces\Controls\EntityCardControl.axaml |
Card root StackPanel ClipToBounds="True" (:10-14) |
features\Phantom.Workspaces\MainWindow.axaml |
Dock host, no outer scroll wrapper (:253-266) |
features\Phantom.Workspaces.Agent.Gui.Tests\MainWindowAxamlTests.cs |
XAML text-scan tests that can't catch the runtime failure (:435-451) |
Design / Fix
The essential change: make the ScrollViewer arrange its content against a finite vertical viewport, and stop clipping overflow in the scrollable chain.
Option A — most surgical. In WorkspaceDataTemplates.axaml:40-46, remove VerticalAlignment="Top" from the ContentControl (move the 12px top gap onto a wrapping panel/margin that does not force top-alignment), so the ScrollViewer compares its finite arranged height against real content height. Verify no ancestor imposes an infinite vertical measure on the ScrollViewer.
Option B — belt-and-braces. Wrap the ContentControl in a Grid (single */Auto row) inside the ScrollViewer and remove ClipToBounds="True" from the shell Border (SharedStyles.axaml:210) and the EntityCardControl root StackPanel (EntityCardControl.axaml:13) so overflow is scrollable/visible rather than silently clipped:
<ScrollViewer Classes="entity-card-single"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
AllowAutoHide="False">
<Grid RowDefinitions="Auto">
<ContentControl Grid.Row="0"
Classes="entity-card-shell entity-card-single-host"
DataContext="{Binding EntityCardNode}"
Content="{Binding}"
HorizontalAlignment="Center"
Margin="0,12,0,0"
MinWidth="160">
...
</ContentControl>
</Grid>
</ScrollViewer>
Whichever option is chosen, confirm the dock parent arranges the ScrollViewer into a finite-height slot, and preserve the #1066 width-capping / horizontal-centering behaviour and the #1064 two-regime horizontal scroll handling.
Expected Tests
Existing coverage is XAML text-scans only (MainWindowAxamlTests.cs:391-451, SharedStylesTests.cs, EntityWorkspaceTabViewModelTests.cs — none realize an over-tall card and assert scroll behaviour). Add headless layout tests (using HeadlessUnitTestSession per project conventions), in a new SingleEntityViewScrollTests in features\Phantom.Workspaces.Agent.Gui.Tests\:
| Test Name |
Class |
What It Verifies |
SingleEntityView_WhenContentTallerThanViewport_ScrollViewerExtentExceedsViewport |
SingleEntityViewScrollTests |
With an over-tall entity, the ScrollViewer's Extent.Height > Viewport.Height |
SingleEntityView_WhenContentTallerThanViewport_VerticalScrollBarIsVisible |
SingleEntityViewScrollTests |
The vertical scrollbar becomes visible |
SingleEntityView_WhenContentTallerThanViewport_MouseWheelChangesVerticalOffset |
SingleEntityViewScrollTests |
A wheel gesture changes the vertical Offset |
SingleEntityView_WhenContentFitsViewport_VerticalScrollBarStaysHidden |
SingleEntityViewScrollTests |
Short content does not show a scrollbar |
SingleEntityView_EntityCardShellChrome_DoesNotClipOverflowVertically |
SingleEntityViewScrollTests |
Guards against re-introducing ClipToBounds="True" in the scrollable chain |
If full headless realization of the templated shell proves infeasible, an intermediate guard — asserting the DataTemplate's visual tree does not put VerticalAlignment="Top" on the immediate child of the vertically-scrolling ScrollViewer — at least prevents regression of Option A.
Summary
When a single entity's "View" (the
EntityWorkspaceTabViewModelworkspace tab) is taller than the visible pane, the view cannot be scrolled — the lower portion of the entity is clipped and unreachable, and the mouse wheel does nothing. The tab'sDataTemplatewraps the card in aScrollViewerwithVerticalScrollBarVisibility="Auto", but the content chain never establishes a bounded vertical viewport, so at runtimeExtent.Height == Viewport.Heightand no scrollbar ever engages. Two nestedClipToBounds="True"layers then silently clip the overflow instead of surfacing it.Root Cause
The single-entity view is rendered by the
DataTemplateforEntityWorkspaceTabViewModelinfeatures\Phantom.Workspaces\Templates\WorkspaceDataTemplates.axaml:35-55(added by commitc58069a6, "Fix #1066"):The visual tree inside the tab is:
The failure is a measure-constraint / height-propagation problem, not a disabled scrollbar:
ContentControl(WorkspaceDataTemplates.axaml:40) usesVerticalAlignment="Top"and sets noHeight/MaxHeight, so itsDesiredSize.Heightis the full (unbounded-upward) height the card wants.ScrollVieweronly shows a vertical scrollbar when its arrangedBounds.Heightis less than the content'sDesiredSize.Height. When the dock document does not impose a finite height on the ScrollViewer — or when an ancestor measures the Y axis withPositiveInfinity(an Auto-height slot / StackPanel-like measure / nested outer scroller) — the ScrollViewer'sViewport.Heightequals itsExtent.Height, so no scrollbar appears and the wheel has no effect. This matches the reported symptom exactly.ClipToBounds="True"on both the shellBorder(features\Phantom.Workspaces.Gui.Shared\Styles\SharedStyles.axaml:210) and theEntityCardControlrootStackPanel(features\Phantom.Workspaces\Controls\EntityCardControl.axaml:13) means the overflow of the tall card is silently clipped rather than made visible — hiding the fact that layout produced an unbounded-height chain.There is no
VerticalScrollBarVisibility="Disabled"and noMaxHeightanywhere in the chain; the stringVerticalScrollBarVisibility="Auto"is present but ineffective because the viewport is never bounded relative to content.Host context. The tab body is hosted directly in
Phantom.Dock.Avalonia'sDockControl(features\Phantom.Workspaces\MainWindow.axaml:253-266,AutoCreateDataTemplates="False"), with no app-levelScrollVieweraround the document. TheEntityWorkspaceTabViewModelDataTemplateis the sole layout for the tab body, so responsibility for making the over-tall entity scrollable lives entirely in that template/shell chrome — confirming the bug is here, not in outer chrome.Why it slipped through. Commit
c58069a6(Fix #1066) added only the horizontally-centred, width-capped chrome and covered it with XAML text-scan tests (features\Phantom.Workspaces.Agent.Gui.Tests\MainWindowAxamlTests.cs:435-451) that assert the literal stringVerticalScrollBarVisibility="Auto"but never realize the visual tree, so they cannot detect thatExtent/Viewportnever diverge at runtime.Affected Files
features\Phantom.Workspaces\Templates\WorkspaceDataTemplates.axamlEntityWorkspaceTabViewModelDataTemplate (:35-55);ScrollViewer:36;ContentControl VerticalAlignment="Top", no Height/MaxHeight:40-46features\Phantom.Workspaces.Gui.Shared\Styles\SharedStyles.axamlentity-card-shelltemplate rootStackPanel(:207); shellBorder ClipToBounds="True"(:210)features\Phantom.Workspaces\Controls\EntityCardControl.axamlStackPanel ClipToBounds="True"(:10-14)features\Phantom.Workspaces\MainWindow.axaml:253-266)features\Phantom.Workspaces.Agent.Gui.Tests\MainWindowAxamlTests.cs:435-451)Design / Fix
The essential change: make the
ScrollViewerarrange its content against a finite vertical viewport, and stop clipping overflow in the scrollable chain.Option A — most surgical. In
WorkspaceDataTemplates.axaml:40-46, removeVerticalAlignment="Top"from theContentControl(move the 12px top gap onto a wrapping panel/margin that does not force top-alignment), so the ScrollViewer compares its finite arranged height against real content height. Verify no ancestor imposes an infinite vertical measure on the ScrollViewer.Option B — belt-and-braces. Wrap the
ContentControlin aGrid(single*/Autorow) inside the ScrollViewer and removeClipToBounds="True"from the shellBorder(SharedStyles.axaml:210) and theEntityCardControlrootStackPanel(EntityCardControl.axaml:13) so overflow is scrollable/visible rather than silently clipped:Whichever option is chosen, confirm the dock parent arranges the ScrollViewer into a finite-height slot, and preserve the #1066 width-capping / horizontal-centering behaviour and the #1064 two-regime horizontal scroll handling.
Expected Tests
Existing coverage is XAML text-scans only (
MainWindowAxamlTests.cs:391-451,SharedStylesTests.cs,EntityWorkspaceTabViewModelTests.cs— none realize an over-tall card and assert scroll behaviour). Add headless layout tests (usingHeadlessUnitTestSessionper project conventions), in a newSingleEntityViewScrollTestsinfeatures\Phantom.Workspaces.Agent.Gui.Tests\:SingleEntityView_WhenContentTallerThanViewport_ScrollViewerExtentExceedsViewportSingleEntityViewScrollTestsExtent.Height > Viewport.HeightSingleEntityView_WhenContentTallerThanViewport_VerticalScrollBarIsVisibleSingleEntityViewScrollTestsSingleEntityView_WhenContentTallerThanViewport_MouseWheelChangesVerticalOffsetSingleEntityViewScrollTestsOffsetSingleEntityView_WhenContentFitsViewport_VerticalScrollBarStaysHiddenSingleEntityViewScrollTestsSingleEntityView_EntityCardShellChrome_DoesNotClipOverflowVerticallySingleEntityViewScrollTestsClipToBounds="True"in the scrollable chainIf full headless realization of the templated shell proves infeasible, an intermediate guard — asserting the DataTemplate's visual tree does not put
VerticalAlignment="Top"on the immediate child of the vertically-scrollingScrollViewer— at least prevents regression of Option A.