Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
5 changes: 3 additions & 2 deletions src/Prompuff.App/Views/LibraryView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@
<!-- Selection mode: bulk actions for the picked prompts -->
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="12" IsVisible="{Binding IsSelecting}">
<StackPanel VerticalAlignment="Center" Spacing="1">
<TextBlock Classes="section-title" Text="{Binding SelectionLabel}" />
<TextBlock Classes="compact faint" Text="Click prompts to pick them. Shift picks a range, Esc stops." />
<TextBlock Classes="section-title" Text="{Binding SelectionLabel}" TextTrimming="CharacterEllipsis" />
<TextBlock Classes="compact faint" Text="Click prompts to pick them. Shift picks a range, Esc stops." TextTrimming="CharacterEllipsis"
ToolTip.Tip="Click prompts to pick them. Shift picks a range, Esc stops." />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<Button Classes="ghost sm" Command="{Binding SelectAllCommand}" Content="Select all" />
Expand Down
13 changes: 7 additions & 6 deletions src/Prompuff.App/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,10 @@
<KeyBinding Gesture="Enter" Command="{Binding ConfirmCommand}" />
</Border.KeyBindings>
<StackPanel Margin="20,18" Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="10">
<c:Icon Kind="TriangleAlert" Size="16" Foreground="{DynamicResource AmberBrush}" IsVisible="{Binding IsError}" />
<TextBlock Classes="section-title" Text="{Binding Title}" TextWrapping="Wrap" />
</StackPanel>
<Grid ColumnDefinitions="Auto,*">
<c:Icon Kind="TriangleAlert" Size="16" Foreground="{DynamicResource AmberBrush}" IsVisible="{Binding IsError}" VerticalAlignment="Top" Margin="0,2,10,0" />
<TextBlock Grid.Column="1" Classes="section-title" Text="{Binding Title}" TextWrapping="Wrap" />
</Grid>
<TextBlock Classes="muted" Text="{Binding Message}" TextWrapping="Wrap" LineHeight="19.5" />
<TextBox x:Name="DialogInput" IsVisible="{Binding HasInput}" Text="{Binding InputText}" PlaceholderText="{Binding InputPlaceholder}" Height="32" />
<Button Classes="ghost sm" HorizontalAlignment="Left" IsVisible="{Binding HasDetails}" Command="{Binding ToggleDetailsCommand}" Margin="-8,0,0,0">
Expand All @@ -475,11 +475,12 @@
</Panel>
</Grid>

<!-- Toast. The outer panel stays in the window, so screen readers hear its name change as a toast appears. -->
<!-- Toast. The outer panel stays in the window, so screen readers hear its name change as a toast appears.
It floats above the footer bars, so it never covers their buttons, such as a workflow's Start over. -->
<Panel x:Name="ToastAnnouncer"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="20"
Margin="20,20,20,56"
IsHitTestVisible="False"
AutomationProperties.LiveSetting="Polite" AutomationProperties.AccessibilityView="Content" AutomationProperties.ControlTypeOverride="Text"
AutomationProperties.Name="{Binding Toasts.Current.Announcement}">
Expand Down
4 changes: 1 addition & 3 deletions src/Prompuff.App/Views/PromptEditorView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,7 @@
<Border Background="{DynamicResource Bg1Brush}" BorderBrush="{DynamicResource Border2Brush}" BorderThickness="1" CornerRadius="6" Padding="12" Margin="0,4,0,0">
<StackPanel Spacing="8">
<TextBlock Classes="label" Text="TEMPLATE" />
<ScrollViewer MaxHeight="220">
<SelectableTextBlock Text="{Binding Body}" FontFamily="{StaticResource MonoFont}" FontSize="12" LineHeight="18" TextWrapping="Wrap" Foreground="{DynamicResource Text2Brush}" />
</ScrollViewer>
<SelectableTextBlock Text="{Binding Body}" FontFamily="{StaticResource MonoFont}" FontSize="12" LineHeight="18" TextWrapping="Wrap" Foreground="{DynamicResource Text2Brush}" />
</StackPanel>
</Border>
</StackPanel>
Expand Down
26 changes: 13 additions & 13 deletions src/Prompuff.App/Views/SettingsView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@

<!-- Appearance -->
<StackPanel Spacing="4" IsVisible="{Binding IsAppearance}">
<TextBlock Classes="page-title" Text="Appearance" Margin="0,0,0,8" />
<TextBlock Classes="page-title" Text="Appearance" Margin="0,0,0,12" />
<Border Classes="setting-row">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel>
Expand Down Expand Up @@ -148,7 +148,7 @@

<!-- Quick save -->
<StackPanel Spacing="4" IsVisible="{Binding IsQuickSave}">
<TextBlock Classes="page-title" Text="Quick save" Margin="0,0,0,4" />
<TextBlock Classes="page-title" Text="Quick save" Margin="0,0,0,12" />
<TextBlock Classes="muted" TextWrapping="Wrap" LineHeight="19.5" Margin="0,0,0,8"
Text="Stash a prompt from any app: copy it, then press the hotkey or use Prompuff's tray icon. Quick save opens with the clipboard, and Enter stashes it." />
<Border Classes="setting-row" IsVisible="{Binding IsHotkeySupported}">
Expand Down Expand Up @@ -213,7 +213,7 @@
<TextBlock Classes="label" Text="DATA FOLDER" />
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="8">
<Border Classes="well" CornerRadius="4" Padding="10,7">
<SelectableTextBlock Text="{Binding DataDirectory}" FontFamily="{StaticResource MonoFont}" FontSize="12" TextTrimming="CharacterEllipsis" />
<SelectableTextBlock Text="{Binding DataDirectory}" FontFamily="{StaticResource MonoFont}" FontSize="12" TextWrapping="Wrap" />
</Border>
<Button Grid.Column="1" Command="{Binding OpenDataFolderCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
Expand Down Expand Up @@ -276,7 +276,7 @@
<TextBlock Classes="strong" Text="Import" />
<TextBlock Classes="compact faint" TextWrapping="Wrap"
Text="Pick .md files, a .zip export, or a whole folder such as an Obsidian vault. Workflows come in too. Prompts and workflows you already have are skipped, and anything that can't be read is reported." />
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,6,0,0">
<WrapPanel ItemSpacing="8" LineSpacing="8" Margin="0,6,0,0">
<Button Command="{Binding ImportCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<c:Icon Kind="Download" Size="13" />
Expand All @@ -289,7 +289,7 @@
<TextBlock Text="Choose a folder" />
</StackPanel>
</Button>
</StackPanel>
</WrapPanel>
</StackPanel>
</Border>
<Border Grid.Column="1" Classes="panel" Padding="16">
Expand Down Expand Up @@ -323,7 +323,7 @@

<!-- Integrations -->
<StackPanel Spacing="4" IsVisible="{Binding IsIntegrations}">
<TextBlock Classes="page-title" Text="Integrations" Margin="0,0,0,4" />
<TextBlock Classes="page-title" Text="Integrations" Margin="0,0,0,12" />
<TextBlock Classes="muted" TextWrapping="Wrap" LineHeight="19.5" Margin="0,0,0,8"
Text="Use your library from a terminal or an AI tool. It all runs on this machine, and nothing here is on until you turn it on." />
<Border Classes="setting-row">
Expand Down Expand Up @@ -394,7 +394,7 @@

<!-- Updates -->
<StackPanel Spacing="4" IsVisible="{Binding IsUpdates}">
<TextBlock Classes="page-title" Text="Updates" Margin="0,0,0,8" />
<TextBlock Classes="page-title" Text="Updates" Margin="0,0,0,12" />
<Border Classes="setting-row">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel>
Expand Down Expand Up @@ -459,9 +459,9 @@
<ItemsControl ItemsSource="{Binding Shortcuts}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:ShortcutRow">
<Border Height="36" Padding="14,0" Background="{DynamicResource Bg2Brush}" BorderBrush="{DynamicResource Border1Brush}" BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Text="{Binding Label}" VerticalAlignment="Center" />
<Border MinHeight="36" Padding="14,8" Background="{DynamicResource Bg2Brush}" BorderBrush="{DynamicResource Border1Brush}" BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="12">
<TextBlock Text="{Binding Label}" VerticalAlignment="Center" TextWrapping="Wrap" />
<Border Grid.Column="1" Classes="kbd">
<TextBlock Text="{Binding Keys}" />
</Border>
Expand All @@ -474,7 +474,7 @@
</StackPanel>

<!-- About -->
<StackPanel Spacing="20" IsVisible="{Binding IsAbout}">
<StackPanel Spacing="16" IsVisible="{Binding IsAbout}">
<TextBlock Classes="page-title" Text="About" />
<Border Classes="panel" CornerRadius="12" Padding="24">
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="24">
Expand All @@ -483,7 +483,7 @@
<TextBlock Text="Prompuff" FontSize="22" FontWeight="Bold" LetterSpacing="-0.44" />
<TextBlock Classes="muted" Text="Your good prompts deserve a home." />
<TextBlock Classes="data faint" FontSize="12" Text="{Binding AboutVersion}" Margin="0,6,0,0" />
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,8,0,0">
<WrapPanel ItemSpacing="8" LineSpacing="8" Margin="0,8,0,0">
<Button Classes="sm" Command="{Binding CheckForUpdatesCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<c:Icon Kind="RefreshCw" Size="12" />
Expand All @@ -502,7 +502,7 @@
<TextBlock Text="Licenses" />
</StackPanel>
</Button>
</StackPanel>
</WrapPanel>
</StackPanel>
</Grid>
</Border>
Expand Down
73 changes: 73 additions & 0 deletions tests/Prompuff.App.Tests/LayoutTests.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
private static List<string> Overflowing(AppHarness app, string screen)
{
var failures = new List<string>();
foreach (var control in app.Window.GetVisualDescendants().OfType<Control>())
{
if (!control.IsEffectivelyVisible || control.Bounds.Width < 1 || control.FindAncestorOfType<ScrollViewer>() 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<Control>() 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<Border>() 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;
}

/// <summary>At the window's minimum size, 960 × 600, the library and every Settings section fit without clipping.</summary>
[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<string>();

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<SettingsSection>())
{
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()));
}
}
Loading