diff --git a/ROADMAP.md b/ROADMAP.md
index d00492c..844c8ba 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -231,7 +231,7 @@ An audit on 2026-10-10 went through the UI tests' screenshots, the themes and th
- [x] Screen readers and the keyboard: the rating takes the keyboard and reads out as "Usefulness, Solid", toasts and Quick save's error are announced, a heart says whether its prompt is a favorite, Show details says when it's open, and the workflow's Previous and Next buttons point left and right like its step strip.
- [x] Contrast and focus: danger buttons on hover, the focused list row against a picked one, the picked option in segmented controls, control edges at 3:1 (the editor's empty rating pips too), counts on the active sidebar row and tags on their tinted fill, and focus outlines that follow each control's corners. `ThemeTests` holds every theme to the new pairs.
-- [ ] Layout: long dialog titles wrap, the library and Settings fit the 960-pixel minimum width with a screenshot test at that size, toasts stay clear of the buttons beneath them, the Render tab scrolls once, the data folder's path shows in full on hover, and Settings sections share one spacing.
+- [x] Layout: long dialog titles wrap, the library and Settings fit the 960-pixel minimum width with a test at that size, toasts stay clear of the buttons beneath them, the Render tab scrolls once, the data folder's path wraps instead of being cut off, and Settings pages share one spacing under their titles.
- [ ] Copy and polish: Quick save's button says Save, the list view shows the rating's word, and small icon buttons use one icon size.
- [ ] Publish v1.1.1.
diff --git a/src/Prompuff.App/Views/LibraryView.axaml b/src/Prompuff.App/Views/LibraryView.axaml
index 0c62e8d..7a9c862 100644
--- a/src/Prompuff.App/Views/LibraryView.axaml
+++ b/src/Prompuff.App/Views/LibraryView.axaml
@@ -99,8 +99,9 @@
-
-
+
+
diff --git a/src/Prompuff.App/Views/MainWindow.axaml b/src/Prompuff.App/Views/MainWindow.axaml
index 5a2a0ac..bc87aea 100644
--- a/src/Prompuff.App/Views/MainWindow.axaml
+++ b/src/Prompuff.App/Views/MainWindow.axaml
@@ -447,10 +447,10 @@
-
-
-
-
+
+
+
+
-
+
diff --git a/src/Prompuff.App/Views/PromptEditorView.axaml b/src/Prompuff.App/Views/PromptEditorView.axaml
index 139267e..2d132a9 100644
--- a/src/Prompuff.App/Views/PromptEditorView.axaml
+++ b/src/Prompuff.App/Views/PromptEditorView.axaml
@@ -256,9 +256,7 @@
-
-
-
+
diff --git a/src/Prompuff.App/Views/SettingsView.axaml b/src/Prompuff.App/Views/SettingsView.axaml
index b2cc078..e4f771c 100644
--- a/src/Prompuff.App/Views/SettingsView.axaml
+++ b/src/Prompuff.App/Views/SettingsView.axaml
@@ -85,7 +85,7 @@
-
+
@@ -148,7 +148,7 @@
-
+
@@ -213,7 +213,7 @@
-
+
@@ -323,7 +323,7 @@
-
+
@@ -394,7 +394,7 @@
-
+
@@ -459,9 +459,9 @@
-
-
-
+
+
+
@@ -474,7 +474,7 @@
-
+
@@ -483,7 +483,7 @@
-
+
@@ -502,7 +502,7 @@
-
+
diff --git a/tests/Prompuff.App.Tests/LayoutTests.cs b/tests/Prompuff.App.Tests/LayoutTests.cs
new file mode 100644
index 0000000..8688c30
--- /dev/null
+++ b/tests/Prompuff.App.Tests/LayoutTests.cs
@@ -0,0 +1,73 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Headless.XUnit;
+using Avalonia.Media;
+using Avalonia.VisualTree;
+using Prompuff.App.ViewModels;
+
+namespace Prompuff.App.Tests;
+
+public class LayoutTests
+{
+ ///
+ /// Text that runs past the space it was given, with nothing to trim or wrap it, and buttons pushed past the panel
+ /// they sit in. Both are what a narrow window does to a layout that only fits a wide one.
+ ///
+ private static List Overflowing(AppHarness app, string screen)
+ {
+ var failures = new List();
+ foreach (var control in app.Window.GetVisualDescendants().OfType())
+ {
+ if (!control.IsEffectivelyVisible || control.Bounds.Width < 1 || control.FindAncestorOfType() is { HorizontalScrollBarVisibility: not Avalonia.Controls.Primitives.ScrollBarVisibility.Disabled })
+ {
+ continue;
+ }
+
+ if (control is TextBlock { TextTrimming: var trimming, TextWrapping: TextWrapping.NoWrap } text && trimming == TextTrimming.None
+ && text.TextLayout.WidthIncludingTrailingWhitespace > text.Bounds.Width + 1 && text.FindAncestorOfType() is not { ClipToBounds: true })
+ {
+ failures.Add($"{screen}: \"{text.Text}\" needs {text.TextLayout.WidthIncludingTrailingWhitespace:0} px but has {text.Bounds.Width:0}");
+ }
+
+ if (control is Button button && button.FindAncestorOfType() is { } panel
+ && button.TranslatePoint(new Point(button.Bounds.Width, 0), panel) is { } right && right.X > panel.Bounds.Width + 1)
+ {
+ failures.Add($"{screen}: the button \"{Controls.AccessibleNames.Describe(button)}\" runs {right.X - panel.Bounds.Width:0} px past its panel");
+ }
+ }
+
+ return failures;
+ }
+
+ /// At the window's minimum size, 960 × 600, the library and every Settings section fit without clipping.
+ [AvaloniaFact]
+ public async Task The_library_and_settings_fit_the_smallest_window()
+ {
+ await using var app = await AppHarness.StartAsync(width: 960, height: 600);
+ var failures = new List();
+
+ failures.AddRange(Overflowing(app, "library cards"));
+ app.Screenshot("min-size-library-cards");
+ app.ViewModel.Library.IsListLayout = true;
+ await app.SettleAsync();
+ failures.AddRange(Overflowing(app, "library list"));
+ app.Screenshot("min-size-library-list");
+
+ app.ViewModel.Library.SelectAll();
+ await app.SettleAsync();
+ failures.AddRange(Overflowing(app, "library selecting"));
+ app.Screenshot("min-size-library-selecting");
+ app.ViewModel.Library.ClearSelection();
+
+ await app.ViewModel.Sidebar.OpenSettingsCommand.ExecuteAsync(null);
+ foreach (var section in Enum.GetValues())
+ {
+ app.ViewModel.Settings.Select(section);
+ await app.SettleAsync();
+ failures.AddRange(Overflowing(app, $"settings {section}"));
+ app.Screenshot($"min-size-settings-{section.ToString().ToLowerInvariant()}");
+ }
+
+ Assert.True(failures.Count == 0, $"{failures.Count} things don't fit at 960 × 600:\n" + string.Join("\n", failures.Distinct()));
+ }
+}