Repository navigation
View renderer (views/*): git-worktree entity cards overflow horizontally / path field does not wrap (MainWindow entity-pane, legacy entity-card-tree inner ScrollViewer) #1064
Description
Related to #1049 (agent chat editor) and #1029, #1045.
Consolidation update (revised): #1064 originally proposed a targeted inline setter, then a consolidated attached-property style that set
ScrollViewer.HorizontalScrollBarVisibility="Disabled"onTreeView.entity-card-tree.Disabledmakes wrapping work, but it removes horizontal scrolling entirely — so when the viewport is narrower than the card's hard minimum widths, the card is clipped with no way to scroll to reach it. The maintainer correctly objected: "With horizontal scrolling disabled completely, how do we scroll horizontally to view cards when their minimum size must be respected?" This body replacesDisabledwith the same two-regime wrapper #1049 proved for the siblingentity-card-tree-view, so the consolidatedentity-card-treestyle respects hard minimum widths and exposes a horizontal scrollbar precisely when the viewport drops below them — while still wrapping to the viewport when the viewport is wider. Everything below is proven headlessly in Avalonia 12.1.
Summary
In a saved View such as "Git Workspaces" / views/git-workspaces, the entity cards for git-worktree entities overflow horizontally: the long path value runs off the right edge, a horizontal scrollbar appears alongside the vertical one, and the text does not wrap to the viewport. The screenshot control is the View renderer: the entity-pane TreeView in Phantom.Workspaces/MainWindow.axaml (not the Entity Browser, not the agent chat editor). The same latent defect exists on every legacy entity-card-tree consumer whose inner scroller is left at Avalonia's default.
The consolidated fix makes any TreeView.entity-card-tree behave correctly by merely carrying the class, with two regimes:
- Viewport ≥ minimum widths: content caps to the viewport and wraps; no horizontal scrollbar.
- Viewport < minimum widths (~160px): the card keeps its hard minimum width and a single horizontal scrollbar appears so the minimum-width content is reachable; content wraps at the minimum.
This is the identical pattern #1049 shipped for entity-card-tree-view. Verified empirically with headless Avalonia 12.1 layout tests (measure + arrange + assert Bounds/Extent/Viewport), not by inspection.
Answering the maintainer's question directly
Horizontal scrolling is NOT globally disabled. The consolidated style keeps ScrollViewer.HorizontalScrollBarVisibility="Auto" and places one items-region wrapper around the whole items area with MinWidth = 160 (the summed hard minimums from #1045) and MaxWidth bound to the tree's own inner-scroller viewport width. That yields exactly two regimes:
- Viewport ≥ 160 (normal/wide):
MaxWidth = Viewport.Widthcaps the wrapper to the visible width. The measure is now finite, soStretchfills andTextWrapping="Wrap"wraps. Extent == viewport ⇒ no horizontal scrollbar. - Viewport < 160 (narrower than the hard minimum):
MinWidth (160) > MaxWidth (= Viewport.Width). Avalonia's layout clamp (minwins whenmin > max) resolves the wrapper to160, wider than the viewport ⇒ extent > viewport ⇒ a single horizontal scrollbar appears, and you can scroll to reveal the card's minimum-width content. Content wraps at160, never at infinity.
So the hard minimums are respected and always reachable: below the minimum you scroll horizontally; above it, the card caps to the viewport and wraps. The earlier Disabled-only variant is rejected precisely because in regime 2 it clips the card with no scrollbar.
Root Cause
The inner scroller of a TreeView.entity-card-tree defaults to HorizontalScrollBarVisibility="Auto", and there is no base TreeView.entity-card-tree rule configuring it (only TreeView.entity-card-tree-view, SharedStyles.axaml:265-278, has the #1049 wrapper). Under Auto, the ScrollContentPresenter sets CanHorizontallyScroll = true and measures content at double.PositiveInfinity width, so TextWrapping="Wrap" on the path value (workspace-field-read-value) is inert; the card grows to intrinsic width and the inner scroller shows a horizontal scrollbar.
Confirmed empirically (baseline): with the inner scroller at default Auto, a bare tree arranges the card to 2370px inside an 800px viewport, Extent.Width = 2370 > Viewport.Width = 800, and the path value stays a single 16px line. Wrapping the tree in an outer Disabled ScrollViewer does not help — the inner Auto scroller re-introduces the infinite measure.
Design / Fix (consolidated style — two-regime wrapper)
Recommendation: an attached-property style on TreeView.entity-card-tree that (a) sets H=Auto, and (b) provides a single items-region wrapper via ItemsPanel with MinWidth=160 and MaxWidth bound to $parent[ScrollViewer].Viewport.Width. Not Disabled; not the nested-template /template/ ScrollViewer#PART_ScrollViewer selector (the inner scroller is unnamed); not a full control-template replacement.
Add this canonical rule to Phantom.Workspaces.Gui.Shared/Styles/SharedStyles.axaml (identical mechanism to entity-card-tree-view at :265-278):
<!-- Consolidated (#1064): any TreeView.entity-card-tree respects the card's hard minimum
widths AND stays reachable. H=Auto keeps a horizontal scrollbar available; a single
items-region wrapper caps content to the inner scroller's viewport (so it wraps and
never overflows when wide) but floors at MinWidth=160 (so a horizontal scrollbar appears
and the minimum-width card is scrollable when the viewport is narrower than the minimum).
The tree's own inner scroller supplies vertical scrolling; no surrounding ScrollViewer. -->
<Style Selector="TreeView.entity-card-tree">
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.AllowAutoHide" Value="False" />
<Setter Property="ItemsPanel">
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
HorizontalAlignment="Stretch"
MinWidth="160"
MaxWidth="{Binding $parent[ScrollViewer].Viewport.Width}" />
</ItemsPanelTemplate>
</Setter>
</Style>Recommended: single shared base style for both classes
entity-card-tree and entity-card-tree-view now use identical ScrollViewer config and the same wrapper. Consolidate them into one multi-selector rule (mirroring the shared TreeViewItem template at SharedStyles.axaml:236), keeping the entity-card-tree-view-only Background as a separate setter:
<Style Selector="TreeView.entity-card-tree, TreeView.entity-card-tree-view">
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.AllowAutoHide" Value="False" />
<Setter Property="ItemsPanel">
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch"
MinWidth="160"
MaxWidth="{Binding $parent[ScrollViewer].Viewport.Width}" />
</ItemsPanelTemplate>
</Setter>
</Style>
<Style Selector="TreeView.entity-card-tree-view">
<Setter Property="Background" Value="{DynamicResource Theme.Surface.EntityPane.Background}" />
</Style>This makes #1064 and #1049 share one authoritative wrapper. If a single shared base is preferred it is recommended; otherwise duplicate the entity-card-tree rule verbatim.
Which viewport-width source resolves (proven experimentally)
$parent[ScrollViewer].Viewport.Width— WINNER (resolved, gutter-correct). From inside theItemsPanel, the$parent[ScrollViewer]ancestor lookup resolves by TYPE to the templated inner (unnamed)ScrollViewer— the unnamed-ness that defeats#PART_ScrollViewername selectors is irrelevant to a type-based ancestor lookup. Asserted: the wrapper's resolvedMaxWidthequals the inner scroller'sViewport.Width(784 when a vertical scrollbar is present), i.e. it intrinsically excludes the vertical-scrollbar gutter, so content never sits under the scrollbar (entity-card-tree-view refinements: min-widths, indent column, sticky scope, blue-border Z-order, horizontal scrollbar, editor item word-wrap (follow-up to #1029) #1045).$parent[TreeView].Bounds.Width— also resolved, but REJECTED. It resolves to the tree's full laid-out width (800, including the gutter). It only avoids a spurious horizontal scrollbar becauseHorizontalAlignment="Stretch"re-caps the panel to the available viewport in arrange; itsMaxWidthover-reports by the gutter and is less robust. Prefer theScrollViewer.Viewport.Widthsource.ScrollViewer.Viewportattached-value — not needed; the ancestor-lookup binding is sufficient and is exactly what entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049 already ships.
Attached-property flow (unchanged from last round): the ScrollViewer.* attached properties set on the TreeView flow to the templated inner scroller — asserted HorizontalScrollBarVisibility = Auto, VerticalScrollBarVisibility = Auto, AllowAutoHide = false.
Pure-style solution — no fallback needed
The pure-style, name-free solution works: H=Auto via attached property + ItemsPanel wrapper with MinWidth + MaxWidth={Binding $parent[ScrollViewer].Viewport.Width}. No attached behavior, no named ScrollViewer, and no control-template replacement are required. (Had it failed, the minimal fallback would have been a one-line template tweak to name the inner ScrollViewer PART_ScrollViewer so MaxWidth="{Binding #PART_ScrollViewer.Viewport.Width}" could target it — but this was not necessary.)
Secondary — soften the fixed 200px label column (as in #1049)
At the narrow regime the wrapper floors at 160, but the field-row grids' fixed ColumnDefinitions="200,*" label column (WorkspaceDataTemplates.axaml) then starves the value column to 0 (empirically pathW=0, single line). With the label column softened to Auto,* (the #1049 secondary fix), the value column keeps room and the path wraps at the 160 min (empirically 84 × 428). The two-regime scrollbar behavior is correct either way; softening the label column is what lets the minimum-width content actually wrap rather than clip.
Why not the earlier variants
Disabled-only (rejected/insufficient): wraps, but removes the horizontal scrollbar entirely, so below the minimum widths the card is clipped with no way to scroll to reach it — the maintainer's exact objection. The two-regime wrapper supersedes it.- Nested-template
/template/ ScrollViewer#PART_ScrollViewer: the innerScrollVieweris unnamed in Avalonia 12.1, so#PART_ScrollViewermatches nothing. (Note: the ancestor lookup$parent[ScrollViewer]still resolves it by type — that is what the wrapper uses.) - Full
ControlTemplatereplacement: unnecessary and costly; the attached-property +ItemsPanelwrapper already achieves correct wrapping, minimum-respecting scroll, and vertical scroll. - Per-item
MaxWidth → Viewport.Width: rejected (fights indentation); the single wrapper handles indentation normally inside it (see entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049).
One scroller, not two
The consolidated style means the tree's own inner scroller is the single scroller. The nested outer + inner arrangement in MainWindow.axaml and EntityBrowserWorkspaceTabView.axaml is the anti-pattern: the outer Disabled scroller cannot cap the inner scroller's measure, and it also cannot provide the below-minimum horizontal scrollbar the wrapper now supplies. Once the class is applied, the outer scrollers are removable.
Experimental Evidence
Headless Avalonia 12.1 (Avalonia.Headless + Avalonia.Headless.XUnit), project C:\dev\test\Avalonia\WrapExperiment (TwoRegimeExperiments.cs, plus the retained StyleOnlyExperiments.cs baseline). A bare TreeView with NO surrounding ScrollViewer, only the candidate class + consolidated two-regime style applied, rendering a git-worktree card whose path is the 154-char worktree path from the screenshot. Each row is measured/arranged at the given client size, then Bounds/Extent/Viewport and the inner scroller's props are asserted. Full suite: 43/43 pass.
Two regimes (standard card, fixed 200,* label column)
| # | Configuration (bare tree, no outer SV) | Viewport (W×H) | Wrapper W | Item W | Inner Extent.W |
path (W×H) |
H-bar | V-bar | Verdict |
|---|---|---|---|---|---|---|---|---|---|
| 1 | No style (inner default Auto) |
800×600 | — | 2370 | 2370 (> viewport) | 2156 × 16 (single line) | yes | no | FAIL — reproduces overflow |
| 2 | Two-regime style — Regime 1 | 800×600 | 800 | 800 | 800 (== viewport) | 586 × 77 (wraps) | no | no | PASS — caps to viewport, wraps |
| 3 | Two-regime style — Regime 2 | 120×600 | 160 (MinWidth wins) | 160 | 160 (> viewport 120) | starved by 200px label (pathW=0) |
yes | no | PASS — H-scrollbar appears; card reachable |
Two regimes (softened Auto,* label column — value column no longer starved)
| # | Configuration | Viewport (W×H) | Wrapper W | Inner Extent.W |
path (W×H) |
H-bar | Verdict |
|---|---|---|---|---|---|---|---|
| 4 | Two-regime style — Regime 1 | 800×600 | 800 | 800 (== viewport) | 724 × 62 (wraps) | no | PASS — caps + wraps |
| 5 | Two-regime style — Regime 2 | 120×600 | 160 | 160 (> viewport 120) | 84 × 428 (wraps at min) | yes | PASS — wraps at min AND scrollable |
Vertical scroll, gutter, and scroll-reach
| # | Configuration | Viewport (W×H) | Inner Extent.H |
V-bar | H-bar | Notes |
|---|---|---|---|---|---|---|
| 6 | Two-regime style, 20 tall items | 800×300 → viewport 784×300 | 3420 (> 300) | yes | no | Vertical scroll via the tree's own inner scroller; viewport width shrinks 800→784 (16px gutter) — scrollbar does not overlap content (#1045). |
| 7 | Regime 2 scroll-reach | 120×600 | — | — | yes | Extent.W − Viewport.W > 0; set Offset.X = maxOffset ⇒ Offset.X > 0: the minimum-width content is reachable by horizontal scroll. |
Which viewport-width binding resolved
| Binding source | Resolved? | Resolved value (vertical-scroll case) | Verdict |
|---|---|---|---|
$parent[ScrollViewer].Viewport.Width |
Yes | wrapper MaxWidth == inner Viewport.Width = 784 (gutter excluded) |
WINNER — name-free type ancestor lookup reaches the unnamed inner scroller; gutter-correct; matches #1049 |
$parent[TreeView].Bounds.Width |
Yes | wrapper MaxWidth = 800 (gutter included); no spurious H-bar only because Stretch re-caps in arrange |
Rejected — over-reports by the gutter, less robust |
Key asserted numbers: regime 1 wrapper/extent collapse to exactly the viewport (800→800), path wraps; regime 2 wrapper clamps to MinWidth=160 > viewport, Extent.W=160, a horizontal scrollbar appears and content is reachable; vertical Extent.H=3420 > 300 with a 16px gutter; binding proof wrapper MaxWidth == inner Viewport.Width = 784.
Locked-in passing tests (TwoRegimeExperiments): TwoRegime_SV_AttachedProperty_FlowsToInnerScrollViewer, TwoRegime_SV_WideViewport_CapsToViewport_Wraps_NoHScroll, TwoRegime_SV_NarrowViewport_ShowsHScrollbar_WrapsAtMin, TwoRegime_SV_NarrowViewport_CanScrollToRevealMinContent, TwoRegime_SV_NestedItem_WrapsWithinWrapper, TwoRegime_SV_ManyItems_VerticalScrollIntact_NoHScroll, TwoRegime_SV_SoftLabel_NarrowViewport_WrapsAtMin_AndScrolls, TwoRegime_SV_SoftLabel_WideViewport_CapsAndWraps_NoHScroll, TwoRegime_SV_WrapperMaxWidth_ResolvesToInnerViewport, plus candidate-B comparators TwoRegime_TV_WideViewport_CapsToTreeBounds_Wraps_NoHScroll, TwoRegime_TV_NarrowViewport_ShowsHScrollbar, TwoRegime_TV_ManyItems_RecordResolvedWidths. The StyleOnlyExperiments.* and ViewRendererExperiments.* tests remain green.
Remediation Inventory
Surrounding ScrollViewers to remove
| # | File | Lines | Wraps | Why removable |
|---|---|---|---|---|
| 1 | Phantom.Workspaces/MainWindow.axaml |
172-187 | View-renderer entity-pane TreeView (:177) |
The outer ScrollViewer H=Disabled V=Visible is superseded: the styled tree's inner scroller now supplies V=Auto vertical scrolling, wrap-to-viewport (regime 1), and the below-minimum horizontal scrollbar (regime 2) via the wrapper. No StickyScroll attached — clean removal. |
| 2 | Phantom.Workspaces/Templates/EntityBrowserWorkspaceTabView.axaml |
7-11 | BrowserTreeView (:12) |
The outer BrowserScrollViewer H=Disabled V=Auto is superseded by the styled inner scroller + wrapper. Caveat: it carries controls:StickyScroll.IsEnabled="True"; add the entity-card-tree-sticky class to the TreeView so the existing selector TreeView.entity-card-tree.entity-card-tree-sticky ScrollViewer (SharedStyles.axaml:307-308) enables StickyScroll on the inner scroller. |
Count: 2 surrounding ScrollViewers should be removed. (Unchanged from the previous round; the change is that the class now carries the two-regime wrapper, not just H=Disabled.)
TreeViews to restyle
| # | File | Line | Current classes | What to add/change |
|---|---|---|---|---|
| 1 | Phantom.Workspaces/MainWindow.axaml |
177 | entity-card-tree entity-card-tree-entity |
Correct automatically once the consolidated two-regime TreeView.entity-card-tree rule is added. Remove the surrounding ScrollViewer (inventory row 1). No per-site setter. |
| 2 | Phantom.Workspaces/Templates/EntityBrowserWorkspaceTabView.axaml |
12-14 | entity-card-tree entity-card-tree-entity |
Correct automatically via the base rule. Remove the surrounding ScrollViewer (inventory row 2) and add entity-card-tree-sticky to keep sticky scrolling. |
Count: 2 TreeViews (both fixed by the single base rule + outer-scroller removal).
Keep as-is / already correct
Phantom.Workspaces.Agent.Gui/Controls/AgentChatToolsDetailControl.axaml:16-21— legacyentity-card-tree, no surroundingScrollViewer; currently setsH=Disabledinline. Once the two-regime base rule lands, its inlineScrollViewer.*setters are superseded and should be removed so it inherits the two-regime behavior (otherwise the inlineH=Disabledwould keep it clip-only in the narrow regime). This is a behavior improvement (it gains the below-minimum scrollbar), not just cleanup.Phantom.Workspaces.Agent.Gui/Controls/AgentChatEditorControl.axaml:251—entity-card-tree-view(entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049's wrapper). Same pattern View renderer (views/*): git-worktree entity cards overflow horizontally / path field does not wrap (MainWindow entity-pane, legacy entity-card-tree inner ScrollViewer) #1064 now adopts. If the shared base style is used, this is backed by the same rule; leave the consumer unchanged.
Affected Files
| File | Change |
|---|---|
Phantom.Workspaces.Gui.Shared/Styles/SharedStyles.axaml |
Primary. Add the consolidated two-regime TreeView.entity-card-tree rule (H=Auto, V=Auto, AllowAutoHide=False, ItemsPanel wrapper MinWidth=160 + MaxWidth={Binding $parent[ScrollViewer].Viewport.Width}). Recommended: fold entity-card-tree and entity-card-tree-view (:265-278) into one shared multi-selector base rule (keeping the entity-card-tree-view Background separate). |
Phantom.Workspaces/Templates/WorkspaceDataTemplates.axaml |
Secondary. Soften the fixed ColumnDefinitions="200,*" label column in the field-row grids to Auto,* (or a min-bounded proportional column) so the value column is not starved at the 160 min and path/markdown wraps in the narrow regime. |
Phantom.Workspaces/MainWindow.axaml |
Remove the surrounding ScrollViewer (:172-187); the styled TreeView (:177) now wraps + scrolls (both axes) on its own inner scroller. |
Phantom.Workspaces/Templates/EntityBrowserWorkspaceTabView.axaml |
Remove the surrounding BrowserScrollViewer (:7-11); add entity-card-tree-sticky to BrowserTreeView (:12) to keep sticky scrolling. |
Phantom.Workspaces.Agent.Gui/Controls/AgentChatToolsDetailControl.axaml |
Remove the inline ScrollViewer.HorizontalScrollBarVisibility="Disabled" (:16-21) so it inherits the two-regime behavior instead of staying clip-only when narrow. |
Phantom.Workspaces/Controls/EntityCardControl.axaml |
No change — the header grid wraps correctly under a finite measure. |
Phantom.Workspaces.Agent.Gui/Controls/AgentChatEditorControl.axaml |
No change — entity-card-tree-view (#1049) is the same pattern; backed by the shared base rule if consolidated. |
Expected Tests
Naming Subject_Scenario_ExpectedOutcome, matching Phantom.Workspaces.Gui.Shared.Tests/SharedStylesTests.cs ([PhantomAvaloniaFact] layout tests + string-AXAML assertions) and Phantom.Workspaces.Agent.Gui.Tests/MainWindowAxamlTests.cs. The headless assertions below already pass in the experiment project (TwoRegimeExperiments.cs) and should be ported.
| Test Name | Class | What It Verifies |
|---|---|---|
EntityCardTree_Class_SetsAutoHScroll_AndWrapperMinMax |
SharedStylesTests |
The consolidated TreeView.entity-card-tree style declares ScrollViewer.HorizontalScrollBarVisibility="Auto" / VerticalScrollBarVisibility="Auto" / AllowAutoHide="False" and an ItemsPanel wrapper with MinWidth="160" and MaxWidth="{Binding $parent[ScrollViewer].Viewport.Width}". |
EntityCardTree_AttachedScrollProperties_FlowToInnerScrollViewer |
SharedStylesTests (PhantomAvaloniaFact) |
After applying the class to a bare TreeView, the templated inner (unnamed) ScrollViewer reports H=Auto / V=Auto / AllowAutoHide=false. |
EntityCardTree_ViewportWiderThanMin_CapsToViewport_Wraps_NoHScroll |
SharedStylesTests (PhantomAvaloniaFact) |
Regime 1: at viewport 800 the wrapper/extent == viewport (800), the path wraps (height grows), inner Extent.Width == Viewport.Width, no horizontal scrollbar. |
EntityCardTree_ViewportNarrowerThanMin_ShowsHScrollbar_AndIsReachable |
SharedStylesTests (PhantomAvaloniaFact) |
Regime 2: at viewport 120 the wrapper clamps to MinWidth=160 > viewport, inner Extent.Width=160 > Viewport.Width, a horizontal scrollbar appears, and Offset.X can be scrolled > 0 to reveal the minimum-width content. |
EntityCardTree_NestedItem_WrapsWithinWrapperWidth |
SharedStylesTests (PhantomAvaloniaFact) |
A nested (indented) TreeViewItem arranges within the wrapper width (indent + itemWidth <= wrapperWidth), confirming indentation is handled inside the single wrapper. |
EntityCardTree_ManyItems_InnerScroller_ProvidesVerticalScroll |
SharedStylesTests (PhantomAvaloniaFact) |
With items exceeding height and no outer scroller, inner Extent.Height > Viewport.Height, a vertical scrollbar appears, the viewport width shrinks by a gutter (no overlap), and no horizontal scrollbar. |
EntityCardTree_WrapperMaxWidth_ResolvesToInnerScrollerViewport |
SharedStylesTests (PhantomAvaloniaFact) |
The wrapper's resolved MaxWidth equals the inner scroller's Viewport.Width (gutter-excluded, e.g. 784), proving $parent[ScrollViewer] resolves name-free to the unnamed inner scroller. |
EntityCardTree_DefaultInnerAutoScroll_OverflowsAndDoesNotWrap |
SharedStylesTests (PhantomAvaloniaFact) |
Regression guard: legacy entity-card-tree with no wrapper (default Auto) ⇒ item width ≫ viewport and the path stays a single line (the bug). |
WorkspaceFieldRow_LabelColumn_IsNotFixedTwoHundred |
SharedStylesTests |
The field-row grids no longer hard-code ColumnDefinitions="200,*" (use Auto,* / min-bounded) so the value column is not starved at the 160 min. |
MainWindowView_EntityPaneTree_HasNoSurroundingScrollViewer |
MainWindowAxamlTests (string-AXAML) |
The entity-pane TreeView (CurrentViewPopulation.RootEntities) is not wrapped in a surrounding ScrollViewer; it relies on the consolidated class. |
EntityBrowserView_BrowserTree_HasNoSurroundingScrollViewer_AndIsSticky |
entity-browser tests (string-AXAML) | BrowserTreeView has no surrounding ScrollViewer and carries entity-card-tree-sticky so sticky scrolling is preserved on the inner scroller. |
AgentChatToolsDetail_Tree_InheritsTwoRegimeWrapper_NoInlineDisabled |
agent-gui tests (string-AXAML) | AgentChatToolsDetailControl no longer sets inline HorizontalScrollBarVisibility="Disabled", so it inherits the two-regime behavior. |
EntityCardTreeView_StillMatchesEntityCardTree_TwoRegime |
SharedStylesTests (PhantomAvaloniaFact) |
Guards that #1049's entity-card-tree-view and #1064's entity-card-tree produce the same two-regime behavior (shared base rule), wrapping with no h-scrollbar wide and a single h-scrollbar below the min. |
Where a value is purely visual (exact gutter pixels), assert the style/attached-property value and the measured/arranged widths as above.
Considered / Background
Disabled-only consolidated style (previous round — rejected/insufficient): settingTreeView.entity-card-treetoScrollViewer.HorizontalScrollBarVisibility="Disabled"wraps content but removes horizontal scrolling entirely, so when the viewport is narrower than the card's hard minimum widths the card is clipped with no scrollbar to reach it. This is the maintainer's objection and the reason this body switches to the entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049 two-regime wrapper. (Empirical:Disabledcapped card == viewport at 800/400 and wrapped, but had no mechanism to scroll below the ~160 minimum.)- Nested-template selector
/template/ ScrollViewer#PART_ScrollViewer: rejected — the innerScrollVieweris unnamed in the Avalonia 12.1TreeViewtemplate, so#PART_ScrollViewermatches nothing. (The ancestor lookup$parent[ScrollViewer]in the wrapper still resolves it by type.) - Full
ControlTemplatereplacement: rejected — duplicates the Fluent template for no benefit; the attached-property +ItemsPanelwrapper is sufficient. $parent[TreeView].Bounds.Widthviewport source: resolves but rejected — includes the vertical-scrollbar gutter (800 vs the correct 784) and relies onStretchre-capping; the$parent[ScrollViewer].Viewport.Widthsource is gutter-correct and matches entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049.- Superseded (incorrect) explanation: the original body attributed this overflow to the Entity Browser. The screenshot is actually a saved View (
views/git-workspaces) rendered by theMainWindow.axamlentity-paneTreeView. The mechanism (legacyentity-card-tree+ innerAutoscroller defeating wrap) is the same, and the Entity Browser shares the identical latent defect, but the reported control is the View renderer. - Superseded (targeted-only) fix: View renderer (views/*): git-worktree entity cards overflow horizontally / path field does not wrap (MainWindow entity-pane, legacy entity-card-tree inner ScrollViewer) #1064 previously recommended an inline
H=Disabledon theMainWindow.axamltree inside its outerScrollViewer. The consolidated two-regime style is better: the outer scroller is unnecessary and a single class rule fixes every legacy consumer with correct below-minimum scrolling. - Relationship to entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049: View renderer (views/*): git-worktree entity cards overflow horizontally / path field does not wrap (MainWindow entity-pane, legacy entity-card-tree inner ScrollViewer) #1064 now uses the same
MinWidth=160+ viewport-MaxWidth+Autowrapper pattern as entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049'sentity-card-tree-view. A shared base style backing both classes is recommended (see Design / Fix). entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049 remains correct and, if consolidated, is backed by the same rule. - Why not just
TextWrapping="Wrap"? It is alreadyWrap(workspace-field-read-value); wrapping is inert until the measure is finite, which the viewport-MaxWidthsupplies.
Activity
[-]entity-card-tree-view: card text still does not wrap to the viewport — empirically verified fix (legacy entity-card-tree inner ScrollViewer)[/-][+]View renderer (views/*): git-worktree entity cards overflow horizontally / path field does not wrap (MainWindow entity-pane, legacy entity-card-tree inner ScrollViewer)[/+]JoshuaRowePhantom commentedon Jul 16, 2026
Verification result
Status: ❌ 8 issue(s) found
Commits checked:
2aca56e3(Fix #1064) onbatch/gui1(branched off features80bce408)What is correct
The core fix is implemented and matches the diagnosed root cause: a consolidated
TreeView.entity-card-treestyle inSharedStyles.axamlsetsScrollViewer.HorizontalScrollBarVisibility=Auto/VerticalScrollBarVisibility=Auto/AllowAutoHide=Falseplus anItemsPanelwrapper (MinWidth=160+MaxWidth={Binding \[ScrollViewer].Viewport.Width}); the field-row grids drop the fixed200,*forAuto,*; and the surroundingScrollVieweron the MainWindow entity-pane tree and the inlineH=DisabledonAgentChatToolsDetailControlare removed. These are covered by static AXAML-string tests that all pass.Outstanding gaps
The problem is test depth: the issue's Expected Tests explicitly call for the two-regime runtime headless (
PhantomAvaloniaFact) layout assertions ("already pass in the experiment projectTwoRegimeExperiments.csand should be ported"). Only static string-match tests were added — the actual behavioral proof of the two regimes is absent. Missing named tests:EntityCardTree_AttachedScrollProperties_FlowToInnerScrollViewer(runtime: attached scroll props reach the templated inner ScrollViewer)EntityCardTree_ViewportWiderThanMin_CapsToViewport_Wraps_NoHScroll(core regime 1 — wide viewport wraps, no H-scrollbar)EntityCardTree_ViewportNarrowerThanMin_ShowsHScrollbar_AndIsReachable(core regime 2 — narrow viewport keeps MinWidth=160, H-scrollbar appears and is scrollable)EntityCardTree_NestedItem_WrapsWithinWrapperWidthEntityCardTree_ManyItems_InnerScroller_ProvidesVerticalScrollEntityCardTree_WrapperMaxWidth_ResolvesToInnerScrollerViewportEntityCardTree_DefaultInnerAutoScroll_OverflowsAndDoesNotWrap(regression guard)EntityBrowserView_BrowserTree_HasNoSurroundingScrollViewer_AndIsSticky— diverged: the browser tree deliberately retains its surroundingScrollViewer(implemented instead as the weakerEntityBrowserView_BrowserTree_HasEntityCardTreeWrapperClass). Per the issue the surrounding scroller should be removed and sticky preserved on the inner scroller; either implement as specified or justify the retained scroller and update the expected test accordingly.None of the added tests assert the wrap-vs-scrollbar layout at either viewport regime, so the fix's headline behavior is unverified at runtime. Port the
TwoRegimeExperiments.csheadless assertions for theentity-card-treeclass (the siblingentity-card-tree-viewalready has such a runtime test —EntityCardTreeViewStyle_HorizontalScrollBar_OnlyWhenMinWidthHit_AndNotOverlapping).Tests
Fast suite (whole repo, from
worktrees/9): Executed 4109 / Passed 4109 / Failed 0. All added tests pass, but they do not cover the described runtime two-regime cases. Not labeledverified-locallypending the missing headless tests. Diff touches only GUI styles/templates (no filesystem/Git layer) — slow tests not required.JoshuaRowePhantom commentedon Jul 16, 2026
Verification result
Status: ✅ Verified
Commits checked (this issue):
2aca56e3(core two-regime wrapper) +5fb08740(re-fix: runtime proofs + browser-tree reconcile) onbatch/gui1(branched off features80bce408).Prior gaps — all closed
The earlier verification (8 issues) required the two-regime runtime headless (
PhantomAvaloniaFact) layout proofs ported fromTwoRegimeExperiments.csand reconciliation of the browser-tree divergence.5fb08740closes every one:EntityCardTree_AttachedScrollProperties_FlowToInnerScrollViewer— present; asserts inner ScrollViewer H=Auto/V=Auto/AllowAutoHide=false at runtime.EntityCardTree_ViewportWiderThanMin_CapsToViewport_Wraps_NoHScroll— regime 1: wrapper/extent cap to viewport, path wraps, no H-scrollbar.EntityCardTree_ViewportNarrowerThanMin_ShowsHScrollbar_AndIsReachable— regime 2: wrapper clamps to MinWidth=160 > viewport, extent > viewport, H-scrollbar appears,Offset.Xscrolls > 0.EntityCardTree_NestedItem_WrapsWithinWrapperWidth— nested item arranges within wrapper width.EntityCardTree_ManyItems_InnerScroller_ProvidesVerticalScroll— inner extent height > viewport, V-scrollbar, gutter reserved, no H-scrollbar.EntityCardTree_WrapperMaxWidth_ResolvesToInnerScrollerViewport— wrapper MaxWidth == inner Viewport.Width (gutter-excluded), proving name-free\[ScrollViewer]resolution.EntityCardTree_DefaultInnerAutoScroll_OverflowsAndDoesNotWrap— regression guard: no wrapper => card overflows, path single line.EntityCardTreeView_StillMatchesEntityCardTree_TwoRegime— parity guard for entity-card-tree-view: entity cards expand far beyond the viewport horizontally; must be capped to viewport width (follow-up to #1045) #1049's sibling class.EntityBrowserView_BrowserTree_HasNoSurroundingScrollViewer_AndIsSticky— divergence reconciled: the surroundingBrowserScrollVieweris removed fromEntityBrowserWorkspaceTabView.axamlandentity-card-tree-stickyadded so StickyScroll is preserved on the tree's own inner scroller (TreeView.entity-card-tree.entity-card-tree-sticky ScrollViewerselector exists in SharedStyles.axaml). Test asserts the outer scroller/inline Disabled/StickyScroll are gone and the sticky class is present.The remaining Expected Tests (
EntityCardTree_Class_SetsAutoHScroll...,WorkspaceFieldRow_LabelColumn_IsNotFixedTwoHundred*,MainWindowView_EntityPaneTree_HasNoSurroundingScrollViewer,AgentChatToolsDetail_Tree_Inherits...) are present as static AXAML assertions (accepted in the prior round). Root cause (defaultAutoinner scroller measuring at infinity) is fully addressed by the consolidated wrapper.Outstanding gaps
None — all criteria satisfied.
Tests
Fast suite (whole repo, from
worktrees/9): Executed 4124 / Passed 4124 / Failed 0. All ported runtime layout proofs pass. Diff touches only GUI styles/templates/tests + a test-onlyAvalonia.Markup.Xaml.Loaderpackage (no filesystem/Git layer) — slow tests not required.Fix #1064: two-regime entity-card-tree scroll wrapper so cards wrap a…
Fix #1066: single-entity view reuses entity-card shell, centered with…
Fix #1064: port two-regime runtime layout proofs and remove browser-t…
JoshuaRowePhantom commentedon Jul 16, 2026
Checked in
Commit: 4fecc1e
Branch fast-forwarded: features -> 4fecc1e
Batch
batch/gui1landed onfeatures(fast suite 4124/4124 green; new GUI tests stable 5/5).Fix #1177 follow-up: preserve two-regime wrapper on virtualized entit…