From 1861832b99076d388b5f6630b196610695d0c99e Mon Sep 17 00:00:00 2001 From: mika kuns Date: Tue, 11 Aug 2026 18:32:30 +0200 Subject: [PATCH] fix(ui): move the tasks list inset off the ListBox onto the panel Padding on RowsListBox mirrored the ScrollViewer.Padding-excluded-from- Extent trap fixed elsewhere today, so move the 10,4 inset onto the VirtualizingStackPanel's Margin instead, matching the other scrollables. Headless measurement showed Avalonia's Fluent ListBox template actually forwards Padding onto PART_ItemsPresenter's margin already (content side), so this particular control was never affected -- both variants produce an identical Extent and a fully visible last row at max scroll. Kept the change for consistency and corrected the CLAUDE.md gotcha to reflect the verified behavior instead of assuming the same trap applied. --- src/ClaudeDo.Ui/CLAUDE.md | 2 +- src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ClaudeDo.Ui/CLAUDE.md b/src/ClaudeDo.Ui/CLAUDE.md index c106949b..7791d74b 100644 --- a/src/ClaudeDo.Ui/CLAUDE.md +++ b/src/ClaudeDo.Ui/CLAUDE.md @@ -111,7 +111,7 @@ snaps `CanResize="True"` windows, which is the opt-in), and it insets itself by - **`PathIcon` *fills* its geometry.** Line-art/stroke icons must be authored as filled geometry or rendered with a stroked `Path` (e.g. `Icon.PlanDay` via the `Path.plan-icon` style). A pure stroke path in a `PathIcon` is **invisible**. - **`NumericUpDown.Value` is `decimal?` and goes null while the box is empty** — i.e. every time the user clears a value to type a new one. Bound TwoWay to a non-nullable `int`/`decimal`, that null throws `InvalidCastException`. Either bind a `decimal?` property (as `AgentConfigEditorViewModel.MaxTurns` does) or add `Converter={StaticResource KeepLastNumber}`, which drops the null via `BindingOperations.DoNothing`. -- **`ScrollViewer.Padding` is excluded from the Extent (Avalonia 12).** At max scroll the content still sticks out below the viewport by the padding height, so the last line(s) are clipped and unreachable — even after `ScrollToEnd()`. Put the inset on the *content* (`Margin` on the inner `ItemsControl`/`StackPanel`) instead. Verified headlessly: 12,8,12,4 on the viewer → last item 20px below the viewport; same inset as content margin → fully visible. +- **`ScrollViewer.Padding` is excluded from the Extent (Avalonia 12).** At max scroll the content still sticks out below the viewport by the padding height, so the last line(s) are clipped and unreachable — even after `ScrollToEnd()`. Put the inset on the *content* (`Margin` on the inner `ItemsControl`/`StackPanel`) instead. Verified headlessly: 12,8,12,4 on the viewer → last item 20px below the viewport; same inset as content margin → fully visible. **`ListBox` does *not* have this trap** — its Fluent control-theme template forwards `Padding` onto `PART_ItemsPresenter`'s margin (content side), not the raw `ScrollViewer.Padding`; verified headlessly that `ListBox.Padding="10,4"` and a `Margin="10,4"` on the `ItemsPanelTemplate` panel produce an *identical* Extent and leave the last row fully visible at max scroll. Still fine to put the inset on the panel for consistency with the other scrollables above, just don't assume it's fixing a real bug there. - **Never bind bare punctuation gestures.** Window key bindings live on `MainWindow` (`Ctrl+K` search, `Ctrl+N` add-task). `OemQuestion` once held search focus and silently swallowed `#` app-wide on a German layout. - **`FocusClearing`'s Escape handler is scoped to `MainWindow`** (`AddClassHandler`, not ``) — it clears focus from a TextBox on Escape, mirroring click-outside. Modals are separate `Window` instances that bind their own Escape → close, so it never runs there. Mission Control's ConPTY tiles are in `MissionControlWindow`, also unaffected, so **Escape always reaches the PTY**. - **Review gate:** Approve & Merge stays disabled until the diff has been opened once, and re-locks per run → [review-merge](../../docs/explore-notes/review-merge.md). diff --git a/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml b/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml index 30261c92..832d6f7c 100644 --- a/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml @@ -91,15 +91,17 @@ + VirtualizingStackPanel actually bounds the realized container count. + Inset lives on the panel, NOT on the ListBox: Avalonia 12 leaves ScrollViewer.Padding + out of the Extent, so at max scroll the last row would sit below the viewport and stay + unreachable. --> + BorderThickness="0"> - +