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
Entity cards in the shared entity-card-tree-view style expand far beyond the viewport horizontally, pushing markdown/note content off the right edge with no working horizontal scrollbar and no width cap.
Evidence (attached picture): an entity card titled "Agent Definitions" (type note) whose markdown content — heading "# Agent Definitions" and body "Agent definitions are concrete agent configurations with their tools already reso…" — runs off the right edge of the pane. The card is far wider than the visible viewport and the text is clipped.
This refines #1045 items 5 and 6 (single outer ScrollViewer + word-wrapping). The goal is to make the card fit the viewport and wrap when the viewport is wide enough, yet still expose a single horizontal scrollbar when the viewport is narrower than an agreed minimum width — i.e. keep the useful "scroll when genuinely too narrow" behaviour that #1045 item 5 asked for, without the infinite-width overflow this bug describes.
Root Cause (confirmed — tightened)
All line numbers are current features/ HEAD.
The shared style enables horizontal auto-scroll on the tree:
In Avalonia, a ScrollViewer whose HorizontalScrollBarVisibility is Auto (or Visible) makes its ScrollContentPresenter set CanHorizontallyScroll = true, which measures its content with double.PositiveInfinity available width so the horizontal extent can be discovered. Every descendant is therefore measured unconstrained horizontally. This makes TextWrapping="Wrap" on the note/markdown body inert — the text reports its full single-line desired width — and makes HorizontalAlignment="Stretch" inert too (Stretch only fills available width, and the available width here is infinite). That oversized desired width propagates up through the entity-card-shellBorder (SharedStyles.axaml:210-217) → entity-card-tree-itemStackPanel → TreeViewItem (SharedStyles.axaml:236-263), so the whole card grows to the intrinsic width of its longest line and overflows the viewport.
The note/markdown body is rendered by SafeSelectableTextBlock inside a fixed-label field-row grid (Phantom.Workspaces/Templates/WorkspaceDataTemplates.axaml, ColumnDefinitions="200,*" at :231, :250, :262, :274, :282, :304, :316, :328, :337); even with TextWrapping="Wrap" set, it cannot wrap under the infinite measure, and the fixed 200px label column adds a hard floor on top.
In short: the diagnosis is the infinite-width measurement caused by HorizontalScrollBarVisibility="Auto". That is what makes wrapping and stretch inert.
Design / Fix (chosen)
Adopt the common web layout pattern — content stretches to fill when the viewport is larger than a minimum, and a horizontal scrollbar appears when the viewport is narrower than the minimum — but adapted for wrapping text.
The naive web recipe is: wrap content in a ScrollViewer HorizontalScrollBarVisibility="Auto", put the content in a container with a MinWidth (e.g. <Grid MinWidth="800">), children HorizontalAlignment="Stretch". That recipe alone does NOT work for wrapping text in Avalonia (see "Why MinWidth alone is not enough" below): under Auto horizontal scroll the content is measured at infinite width, so MinWidth sets the floor but nothing sets a ceiling, and wrapping text measures its full single-line width and overflows arbitrarily — exactly this bug.
The adaptation: give the single content wrapper both a MinWidthand a MaxWidth bound to the ScrollViewer's own viewport width.
The rule
Keep ScrollViewer.HorizontalScrollBarVisibility="Auto" (and vertical Auto, AllowAutoHide="False"). Introduce one items-region wrapper — a single container placed around the whole items area inside the tree's own ScrollViewer — with:
HorizontalAlignment="Stretch",
MinWidth="160" — the summed agreed minimum widths (derivation below); this is the floor at which the horizontal scrollbar appears, and
MaxWidth="{Binding Viewport.Width, ElementName=...}" bound to the tree's ScrollViewer viewport width (optionally minus a small vertical-scrollbar gutter); this is the ceiling that caps wrapping content to the viewport.
The two regimes
Regime 1 — viewport ≥ MinWidth (normal / wide):MaxWidth = Viewport.Width caps the wrapper at the visible width. Because the measure is now finite, Stretch fills the viewport and TextWrapping="Wrap" actually wraps. Extent == viewport ⇒ no horizontal scrollbar. Content fills and wraps.
Regime 2 — viewport < MinWidth (narrow): now MinWidth (160) > MaxWidth (=Viewport.Width). In Avalonia layout clamping (the MinMax used by Layoutable.MeasureCore, equivalent to clamp(desired, min, max) where min wins when min > max), the wrapper resolves to MinWidth. So the wrapper is 160 wide, wider than the viewport ⇒ extent > viewport ⇒ a single horizontal scrollbar appears, and content wraps at 160 (not at infinity). This is precisely the "scroll only when genuinely too narrow" behaviour #1045 item 5 wanted.
Why ONE wrapper, not per-item
The wrapper + MinWidth + viewport-MaxWidth is applied once around the whole items region (a single container inside the tree's ScrollViewer), not per TreeViewItem. This is what resolves the earlier objection that a per-item viewport-width binding fights indentation:
With a single wrapper constrained to [MinWidth, Viewport.Width], indentation is handled normally inside it. Each nested TreeViewItem still measures at available-width − indent (the 20,* grid, SharedStyles.axaml:242) within the wrapper.
Only the one wrapper's MinWidth drives the horizontal scrollbar. Nested items never each try to claim the full viewport width, so indent + itemWidth never independently overflows.
Where to apply it (concrete)
The tree's ScrollViewer lives inside the default TreeView template (ScrollViewer named PART_ScrollViewer, containing the ItemsPresenter). Reach the items host via the shared style and constrain it. Two equivalent placements:
(a) Constrain the TreeView's items panel (recommended — smallest surface). Override the ItemsPanel on the entity-card-tree-view style so the root items host is the single wrapper. $parent[ScrollViewer] from inside the panel resolves to PART_ScrollViewer (the tree's own ScrollViewer):
(b) Or wrap/constrain the ItemsPresenter in the TreeView template (if the ItemsPanel override is insufficient, retemplate the TreeView and put the MinWidth + viewport-MaxWidth on a Panel/Border immediately around PART_ItemsPresenter, still insidePART_ScrollViewer):
ScrollViewer.Viewport is a Size; bind MaxWidth to Viewport.Width. The viewport size is driven by the ScrollViewer's own arranged bounds (outer layout), not by the content's desired width, so binding MaxWidth → Viewport.Width does not create a feedback loop in either regime. When a vertical scrollbar is present (VerticalScrollBarVisibility="Auto", AllowAutoHide="False"), Viewport.Width already excludes the vertical-scrollbar column, so content will not sit under it; if any overlap is observed, subtract a small gutter (e.g. Viewport.Width − 12) to honour #1045's "scrollbars must not overlap content".
Recommended MinWidth value and derivation
MinWidth="160". This equals the intrinsic minimum of the card's own min-width columns and the agreed item minimum from #1045:
note/markdown field-row grid: label column min ~67 + value column min 67 (workspace-field-label / workspace-field-value, SharedStyles.axaml) ≈ 134, plus the entity-card-shell-border padding 10 + 10 = 20 ≈ 154 → rounds to the agreed 160 item MinWidth (SharedStyles.axaml:238).
If the card's minimum columns change, keep this wrapper MinWidth equal to the summed intrinsic minimum of those columns (label-min + value-min + card padding), or to the item MinWidth, whichever is authoritative.
Why MinWidth alone is not enough (the critical nuance)
The web example's content does not wrap (a Button and a plain TextBlock — bounded content whose desired width is finite). For our wrapping content (TextWrapping="Wrap" note/markdown bodies and header summaries), under HorizontalScrollBarVisibility="Auto" the ScrollContentPresenter sets CanHorizontallyScroll = true and measures the child at infinite available width. Wrapping text then reports its full single-line desired width and never wraps; MinWidth only raises the floor, it never caps the maximum, so the card still overflows arbitrarily (this bug's exact symptom). That is why the wrapper needs the viewport-bound MaxWidth in addition to MinWidth: the MaxWidth supplies the finite ceiling that both caps the card to the viewport and lets the text actually wrap. Answer: yes — plain-MinWidth-only fails for wrapping text; the viewport-MaxWidth is mandatory.
Secondary — soften the fixed 200px markdown label column
In the note/markdown field-row grids (WorkspaceDataTemplates.axaml, ColumnDefinitions="200,*" at :231, :250, :262, :274, :282, :304, :316, :328, :337) the 200px label column hard-codes a floor. At narrow viewports this floor plus the value column can still exceed MinWidth. Change it to Auto,* (or a MinWidth-bounded proportional column consistent with the 67px minimums from #1045) so the label never imposes a floor wider than the agreed minimum.
Genuinely un-wrappable inner content (a single very long unbroken token, a URL, or a code block) must wrap or scroll within the card rather than exporting an oversized desired width. The workspace-markdown-viewer control already disables its own nested scrollers (SharedStyles.axaml:755-758), so under a finite measure it wraps. Such remaining content should get its own local wrap or a local scroller so it clips/scrolls inside the card; it must not reintroduce an infinite-width horizontal measure at the card level.
Considered / Background
(a) HorizontalScrollBarVisibility="Disabled" + wrap (simpler alternative — gives up the H-scrollbar)
Set the tree's horizontal scroll to Disabled; the ScrollContentPresenter then measures at the finite viewport width, and the existing Stretch + TextWrapping="Wrap" wrap for free, with indentation accounted for automatically by the 20,* grid. This is simpler but removes the horizontal scrollbar entirely — at very narrow viewports the card compresses/clips rather than offering a scrollbar. The sibling entity-card-tree consumers already use exactly this:
Phantom.Workspaces.Agent.Gui/Controls/AgentChatToolsDetailControl.axaml:16-21 — TreeView Classes="entity-card-tree entity-card-tree-sticky" with inline ScrollViewer.HorizontalScrollBarVisibility="Disabled" + VerticalScrollBarVisibility="Auto".
Phantom.Workspaces/Templates/EntityBrowserWorkspaceTabView.axaml:7-15 — an outer ScrollViewer HorizontalScrollBarVisibility="Disabled" / VerticalScrollBarVisibility="Auto" wrapping a TreeView Classes="entity-card-tree entity-card-tree-entity".
The chosen approach differs from this by keeping Auto + a single wrapper with MinWidth, so it retains the scrollbar-when-narrow behaviour the maintainer wants and satisfies #1045 item 5 properly, while still wrapping when the viewport is wide.
An earlier proposal bound eachTreeViewItem's MaxWidth to the ScrollViewer's Viewport.Width while keeping Auto horizontal scroll. Rejected: it fights indentation. Every TreeViewItem is indented by its nesting level (the 20px indent column per level, SharedStyles.axaml:242). Forcing an item's width to the full viewport means a nested item occupies indent + full-viewport-width, which still overflows. Correcting it would require subtracting the cumulative indent per level — exactly the bookkeeping the single-wrapper approach avoids, because only the one wrapper is bound to the viewport and indentation is handled normally by the grid inside it.
(chosen fix) On TreeView.entity-card-tree-view (:264-269) keep HorizontalScrollBarVisibility="Auto" / VerticalScrollBarVisibility="Auto" / AllowAutoHide="False", and add a single items-region wrapper — either via an ItemsPanel override (recommended) or a retemplated PART_ItemsPresenter wrapper — carrying HorizontalAlignment="Stretch", MinWidth="160", and MaxWidth="{Binding $parent[ScrollViewer].Viewport.Width}" (optionally minus a scrollbar gutter).
Consumer NavigationTree (:251-259); no change — the fix is inherited from the shared style; it already delegates ScrollViewer config per #1045.
Precedent to mirror for the simpler Disabled alternative (no change): AgentChatToolsDetailControl.axaml:16-21 and EntityBrowserWorkspaceTabView.axaml:7-15. No existing control yet uses the MinWidth + viewport-MaxWidth + Auto wrapper pattern, so this introduces it.
Expected Tests
Follow the existing string-based AXAML-assertion pattern and the PhantomAvaloniaFact layout pattern (host.Measure/host.Arrange, then inspect visual descendants) and Subject_Scenario_ExpectedOutcome naming in Phantom.Workspaces.Gui.Shared.Tests/SharedStylesTests.cs and Phantom.Workspaces.Agent.Gui.Tests/AgentChatEditorControlTests.cs.
Note: two existing tests currently assert plain Auto and must be updated to also assert the new wrapper: SharedStylesTests.EntityCardTreeViewStyle_HorizontalScrollBar_OnlyWhenMinWidthHit_AndNotOverlapping (:715-732) and SharedStylesTests.EntityCardTreeViewStyle_HorizontalScrollBar_ConfiguredOnceOnStyle (:924-954) — keep the Auto / AllowAutoHide="False" / single-source assertions and add the MinWidth + viewport-MaxWidth wrapper assertion.
With a viewport wider than 160, a wide single-line note/markdown card arranges to a width == viewport (capped by the wrapper MaxWidth), wraps (height grows), and the tree's horizontal ScrollBar is not visible (extent == viewport).
With a viewport narrower than 160, the items wrapper resolves to MinWidth="160" (> viewport), producing a horizontal extent > viewport so a single horizontal scrollbar appears and content wraps at 160.
A nested (child) TreeViewItem arranges within wrapper-width − indent — i.e. indent + itemWidth <= wrapperWidth — confirming indentation is handled inside the single wrapper without any per-item viewport binding.
The entity-card-tree-view items host (ItemsPanel override or templated wrapper) declares MinWidth="160" and MaxWidth="{Binding ...Viewport.Width}" bound to the tree's ScrollViewer, with HorizontalScrollBarVisibility="Auto" retained.
EntityCard_LongMarkdown_WrapsInsteadOfOverflowing
EntityCardFieldBuildingTests
The SafeSelectableTextBlock rendering long markdown wraps (measured height grows, measured width stays bounded to available width) rather than reporting a single-line desired width; it is not wrapped in a horizontal scroller.
The note/markdown field-row grids no longer hard-code ColumnDefinitions="200,*" (use Auto,* / min-bounded) so the label does not impose an oversized floor.
NavigationTree (consuming entity-card-tree-view) does not redeclare ScrollViewer settings or the wrapper and therefore inherits the Auto + MinWidth/viewport-MaxWidth wrapper from the shared style.
Where a property is purely visual (exact pixel arrange, scrollbar gutter), assert the style/attached-property value and the measured/arranged width bound as above, and note remaining pixel behaviour as a manual/visual check.
Related to #1029, #1045.
Summary
Entity cards in the shared
entity-card-tree-viewstyle expand far beyond the viewport horizontally, pushing markdown/note content off the right edge with no working horizontal scrollbar and no width cap.Evidence (attached picture): an entity card titled "Agent Definitions" (type
note) whose markdown content — heading "# Agent Definitions" and body "Agent definitions are concrete agent configurations with their tools already reso…" — runs off the right edge of the pane. The card is far wider than the visible viewport and the text is clipped.This refines #1045 items 5 and 6 (single outer ScrollViewer + word-wrapping). The goal is to make the card fit the viewport and wrap when the viewport is wide enough, yet still expose a single horizontal scrollbar when the viewport is narrower than an agreed minimum width — i.e. keep the useful "scroll when genuinely too narrow" behaviour that #1045 item 5 asked for, without the infinite-width overflow this bug describes.
Root Cause (confirmed — tightened)
All line numbers are current
features/HEAD.The shared style enables horizontal auto-scroll on the tree:
Phantom.Workspaces.Gui.Shared/Styles/SharedStyles.axaml:264-269In Avalonia, a
ScrollViewerwhoseHorizontalScrollBarVisibilityisAuto(orVisible) makes itsScrollContentPresentersetCanHorizontallyScroll = true, which measures its content withdouble.PositiveInfinityavailable width so the horizontal extent can be discovered. Every descendant is therefore measured unconstrained horizontally. This makesTextWrapping="Wrap"on the note/markdown body inert — the text reports its full single-line desired width — and makesHorizontalAlignment="Stretch"inert too (Stretch only fills available width, and the available width here is infinite). That oversized desired width propagates up through theentity-card-shellBorder(SharedStyles.axaml:210-217) →entity-card-tree-itemStackPanel→TreeViewItem(SharedStyles.axaml:236-263), so the whole card grows to the intrinsic width of its longest line and overflows the viewport.The note/markdown body is rendered by
SafeSelectableTextBlockinside a fixed-label field-row grid (Phantom.Workspaces/Templates/WorkspaceDataTemplates.axaml,ColumnDefinitions="200,*"at:231, :250, :262, :274, :282, :304, :316, :328, :337); even withTextWrapping="Wrap"set, it cannot wrap under the infinite measure, and the fixed200px label column adds a hard floor on top.In short: the diagnosis is the infinite-width measurement caused by
HorizontalScrollBarVisibility="Auto". That is what makes wrapping and stretch inert.Design / Fix (chosen)
Adopt the common web layout pattern — content stretches to fill when the viewport is larger than a minimum, and a horizontal scrollbar appears when the viewport is narrower than the minimum — but adapted for wrapping text.
The naive web recipe is: wrap content in a
ScrollViewer HorizontalScrollBarVisibility="Auto", put the content in a container with aMinWidth(e.g.<Grid MinWidth="800">), childrenHorizontalAlignment="Stretch". That recipe alone does NOT work for wrapping text in Avalonia (see "WhyMinWidthalone is not enough" below): underAutohorizontal scroll the content is measured at infinite width, soMinWidthsets the floor but nothing sets a ceiling, and wrapping text measures its full single-line width and overflows arbitrarily — exactly this bug.The adaptation: give the single content wrapper both a
MinWidthand aMaxWidthbound to the ScrollViewer's own viewport width.The rule
Keep
ScrollViewer.HorizontalScrollBarVisibility="Auto"(and verticalAuto,AllowAutoHide="False"). Introduce one items-region wrapper — a single container placed around the whole items area inside the tree's own ScrollViewer — with:HorizontalAlignment="Stretch",MinWidth="160"— the summed agreed minimum widths (derivation below); this is the floor at which the horizontal scrollbar appears, andMaxWidth="{Binding Viewport.Width, ElementName=...}"bound to the tree'sScrollViewerviewport width (optionally minus a small vertical-scrollbar gutter); this is the ceiling that caps wrapping content to the viewport.The two regimes
Regime 1 — viewport ≥ MinWidth (normal / wide):
MaxWidth = Viewport.Widthcaps the wrapper at the visible width. Because the measure is now finite,Stretchfills the viewport andTextWrapping="Wrap"actually wraps. Extent == viewport ⇒ no horizontal scrollbar. Content fills and wraps.Regime 2 — viewport < MinWidth (narrow): now
MinWidth (160) > MaxWidth (=Viewport.Width). In Avalonia layout clamping (theMinMaxused byLayoutable.MeasureCore, equivalent toclamp(desired, min, max)whereminwins whenmin > max), the wrapper resolves toMinWidth. So the wrapper is160wide, wider than the viewport ⇒ extent > viewport ⇒ a single horizontal scrollbar appears, and content wraps at160(not at infinity). This is precisely the "scroll only when genuinely too narrow" behaviour #1045 item 5 wanted.Why ONE wrapper, not per-item
The wrapper +
MinWidth+ viewport-MaxWidthis applied once around the whole items region (a single container inside the tree'sScrollViewer), not perTreeViewItem. This is what resolves the earlier objection that a per-item viewport-width binding fights indentation:[MinWidth, Viewport.Width], indentation is handled normally inside it. Each nestedTreeViewItemstill measures at available-width − indent (the20,*grid,SharedStyles.axaml:242) within the wrapper.MinWidthdrives the horizontal scrollbar. Nested items never each try to claim the full viewport width, soindent + itemWidthnever independently overflows.Where to apply it (concrete)
The tree's
ScrollViewerlives inside the default TreeView template (ScrollViewernamedPART_ScrollViewer, containing theItemsPresenter). Reach the items host via the shared style and constrain it. Two equivalent placements:(a) Constrain the TreeView's items panel (recommended — smallest surface). Override the
ItemsPanelon theentity-card-tree-viewstyle so the root items host is the single wrapper.$parent[ScrollViewer]from inside the panel resolves toPART_ScrollViewer(the tree's own ScrollViewer):(b) Or wrap/constrain the
ItemsPresenterin the TreeView template (if the ItemsPanel override is insufficient, retemplate the TreeView and put theMinWidth+ viewport-MaxWidthon aPanel/Borderimmediately aroundPART_ItemsPresenter, still insidePART_ScrollViewer):ScrollViewer.Viewportis aSize; bindMaxWidthtoViewport.Width. The viewport size is driven by the ScrollViewer's own arranged bounds (outer layout), not by the content's desired width, so bindingMaxWidth → Viewport.Widthdoes not create a feedback loop in either regime. When a vertical scrollbar is present (VerticalScrollBarVisibility="Auto",AllowAutoHide="False"),Viewport.Widthalready excludes the vertical-scrollbar column, so content will not sit under it; if any overlap is observed, subtract a small gutter (e.g.Viewport.Width − 12) to honour #1045's "scrollbars must not overlap content".Recommended
MinWidthvalue and derivationMinWidth="160". This equals the intrinsic minimum of the card's own min-width columns and the agreed item minimum from #1045:~67+ value column min67(workspace-field-label/workspace-field-value,SharedStyles.axaml) ≈134, plus theentity-card-shell-borderpadding10 + 10 = 20≈154→ rounds to the agreed160itemMinWidth(SharedStyles.axaml:238).160keeps the wrapper floor equal to the item's ownMinWidthso the two never disagree.If the card's minimum columns change, keep this wrapper
MinWidthequal to the summed intrinsic minimum of those columns (label-min + value-min + card padding), or to the itemMinWidth, whichever is authoritative.Why
MinWidthalone is not enough (the critical nuance)The web example's content does not wrap (a
Buttonand a plainTextBlock— bounded content whose desired width is finite). For our wrapping content (TextWrapping="Wrap"note/markdown bodies and header summaries), underHorizontalScrollBarVisibility="Auto"theScrollContentPresentersetsCanHorizontallyScroll = trueand measures the child at infinite available width. Wrapping text then reports its full single-line desired width and never wraps;MinWidthonly raises the floor, it never caps the maximum, so the card still overflows arbitrarily (this bug's exact symptom). That is why the wrapper needs the viewport-boundMaxWidthin addition toMinWidth: theMaxWidthsupplies the finite ceiling that both caps the card to the viewport and lets the text actually wrap. Answer: yes — plain-MinWidth-only fails for wrapping text; the viewport-MaxWidthis mandatory.Secondary — soften the fixed
200px markdown label columnIn the note/markdown field-row grids (
WorkspaceDataTemplates.axaml,ColumnDefinitions="200,*"at:231, :250, :262, :274, :282, :304, :316, :328, :337) the200px label column hard-codes a floor. At narrow viewports this floor plus the value column can still exceedMinWidth. Change it toAuto,*(or aMinWidth-bounded proportional column consistent with the67px minimums from #1045) so the label never imposes a floor wider than the agreed minimum.Secondary — un-wrappable content scrolls/wraps locally
Genuinely un-wrappable inner content (a single very long unbroken token, a URL, or a code block) must wrap or scroll within the card rather than exporting an oversized desired width. The
workspace-markdown-viewercontrol already disables its own nested scrollers (SharedStyles.axaml:755-758), so under a finite measure it wraps. Such remaining content should get its own local wrap or a local scroller so it clips/scrolls inside the card; it must not reintroduce an infinite-width horizontal measure at the card level.Considered / Background
(a)
HorizontalScrollBarVisibility="Disabled"+ wrap (simpler alternative — gives up the H-scrollbar)Set the tree's horizontal scroll to
Disabled; theScrollContentPresenterthen measures at the finite viewport width, and the existingStretch+TextWrapping="Wrap"wrap for free, with indentation accounted for automatically by the20,*grid. This is simpler but removes the horizontal scrollbar entirely — at very narrow viewports the card compresses/clips rather than offering a scrollbar. The siblingentity-card-treeconsumers already use exactly this:Phantom.Workspaces.Agent.Gui/Controls/AgentChatToolsDetailControl.axaml:16-21—TreeView Classes="entity-card-tree entity-card-tree-sticky"with inlineScrollViewer.HorizontalScrollBarVisibility="Disabled"+VerticalScrollBarVisibility="Auto".Phantom.Workspaces/Templates/EntityBrowserWorkspaceTabView.axaml:7-15— an outerScrollViewer HorizontalScrollBarVisibility="Disabled"/VerticalScrollBarVisibility="Auto"wrapping aTreeView Classes="entity-card-tree entity-card-tree-entity".The chosen approach differs from this by keeping
Auto+ a single wrapper withMinWidth, so it retains the scrollbar-when-narrow behaviour the maintainer wants and satisfies #1045 item 5 properly, while still wrapping when the viewport is wide.(b) Per-item
MaxWidth→Viewport.Widthbinding (REJECTED)An earlier proposal bound each
TreeViewItem'sMaxWidthto the ScrollViewer'sViewport.Widthwhile keepingAutohorizontal scroll. Rejected: it fights indentation. EveryTreeViewItemis indented by its nesting level (the20px indent column per level,SharedStyles.axaml:242). Forcing an item's width to the full viewport means a nested item occupiesindent + full-viewport-width, which still overflows. Correcting it would require subtracting the cumulative indent per level — exactly the bookkeeping the single-wrapper approach avoids, because only the one wrapper is bound to the viewport and indentation is handled normally by the grid inside it.Affected Files
Phantom.Workspaces.Gui.Shared/Styles/SharedStyles.axamlTreeView.entity-card-tree-view(:264-269) keepHorizontalScrollBarVisibility="Auto"/VerticalScrollBarVisibility="Auto"/AllowAutoHide="False", and add a single items-region wrapper — either via anItemsPaneloverride (recommended) or a retemplatedPART_ItemsPresenterwrapper — carryingHorizontalAlignment="Stretch",MinWidth="160", andMaxWidth="{Binding $parent[ScrollViewer].Viewport.Width}"(optionally minus a scrollbar gutter).Phantom.Workspaces/Templates/WorkspaceDataTemplates.axaml200px label column in the note/markdown field-row grids (ColumnDefinitions="200,*"at:231, :250, :262, :274, :282, :304, :316, :328, :337) toAuto,*/ min-bounded proportional.Phantom.Workspaces.Gui.Shared/Controls/SafeSelectableTextBlock.csTextWrapping="Wrap"wraps.Phantom.Workspaces.Agent.Gui/Controls/AgentChatEditorControl.axamlNavigationTree(:251-259); no change — the fix is inherited from the shared style; it already delegates ScrollViewer config per #1045.Precedent to mirror for the simpler Disabled alternative (no change):
AgentChatToolsDetailControl.axaml:16-21andEntityBrowserWorkspaceTabView.axaml:7-15. No existing control yet uses theMinWidth+ viewport-MaxWidth+Autowrapper pattern, so this introduces it.Expected Tests
Follow the existing string-based AXAML-assertion pattern and the
PhantomAvaloniaFactlayout pattern (host.Measure/host.Arrange, then inspect visual descendants) andSubject_Scenario_ExpectedOutcomenaming inPhantom.Workspaces.Gui.Shared.Tests/SharedStylesTests.csandPhantom.Workspaces.Agent.Gui.Tests/AgentChatEditorControlTests.cs.Note: two existing tests currently assert plain
Autoand must be updated to also assert the new wrapper:SharedStylesTests.EntityCardTreeViewStyle_HorizontalScrollBar_OnlyWhenMinWidthHit_AndNotOverlapping(:715-732) andSharedStylesTests.EntityCardTreeViewStyle_HorizontalScrollBar_ConfiguredOnceOnStyle(:924-954) — keep theAuto/AllowAutoHide="False"/ single-source assertions and add theMinWidth+ viewport-MaxWidthwrapper assertion.EntityCardTreeView_ViewportWiderThanMin_ContentFillsAndWraps_NoHScrollSharedStylesTests(PhantomAvaloniaFact)160, a wide single-line note/markdown card arranges to a width == viewport (capped by the wrapperMaxWidth), wraps (height grows), and the tree's horizontalScrollBaris not visible (extent == viewport).EntityCardTreeView_ViewportNarrowerThanMin_ShowsHorizontalScrollBarSharedStylesTests(PhantomAvaloniaFact)160, the items wrapper resolves toMinWidth="160"(> viewport), producing a horizontal extent > viewport so a single horizontal scrollbar appears and content wraps at160.EntityCardTreeView_NestedItem_WrapsWithinIndentedWidthSharedStylesTests(PhantomAvaloniaFact)TreeViewItemarranges within wrapper-width − indent — i.e.indent + itemWidth <= wrapperWidth— confirming indentation is handled inside the single wrapper without any per-item viewport binding.EntityCardTreeView_ItemsWrapper_MaxWidthBoundToViewportSharedStylesTestsentity-card-tree-viewitems host (ItemsPanel override or templated wrapper) declaresMinWidth="160"andMaxWidth="{Binding ...Viewport.Width}"bound to the tree'sScrollViewer, withHorizontalScrollBarVisibility="Auto"retained.EntityCard_LongMarkdown_WrapsInsteadOfOverflowingEntityCardFieldBuildingTestsSafeSelectableTextBlockrendering long markdown wraps (measured height grows, measured width stays bounded to available width) rather than reporting a single-line desired width; it is not wrapped in a horizontal scroller.WorkspaceMarkdownFieldRow_LabelColumn_IsNotFixedTwoHundredSharedStylesTestsColumnDefinitions="200,*"(useAuto,*/ min-bounded) so the label does not impose an oversized floor.AgentChatEditor_NavigationTree_InheritsWrapperFromSharedStyleAgentChatEditorControlTestsNavigationTree(consumingentity-card-tree-view) does not redeclare ScrollViewer settings or the wrapper and therefore inherits theAuto+MinWidth/viewport-MaxWidthwrapper from the shared style.Where a property is purely visual (exact pixel arrange, scrollbar gutter), assert the style/attached-property value and the measured/arranged width bound as above, and note remaining pixel behaviour as a manual/visual check.
Relationship to other issues
entity-card-tree-viewstyle andentity-card-shelltemplate).MinWidth(scrollbar when viewport < min) while the viewport-MaxWidthguarantees wrap-to-viewport when wider.