Skip to content

Single-entity View does not scroll when the entity is taller than the viewport (unbounded vertical viewport + ClipToBounds chain) #1343

Description

@JoshuaRowePhantom

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:

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdiagnosedRoot cause identified

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions