Skip to content

Commit 8c2e782

Browse files
committed
Make desktop error banners selectable and copyable
The error banner used WinUI's nonselectable message presentation, preventing users from copying diagnostics. Keep the native banner while rendering its message in a selectable text control. Replace the message body with wrapped, scrollable text and add a Copy action. Copy uses the full message and reports success separately. Clipboard failures preserve the original error and offer text selection as a fallback; a new error clears prior copy feedback. The text exposes its contents to accessibility tools and marks errors as assertive accessibility updates. Users can copy either selected text or the complete diagnostic without transcribing it, including long messages and Unicode paths. The ARM64 single-file desktop build passed. UI automation selected a real validation error containing a Unicode path and verified that Copy produced exactly the displayed text. Holding the Windows clipboard open confirmed that copy failure retained the error and showed the fallback. The banner was visually inspected. No Rust behavior changed. Design-Signed-Off-By: Codex (GPT-6); design-review=cfcf
1 parent fa15828 commit 8c2e782

2 files changed

Lines changed: 16 additions & 2 deletions

File tree

‎flare-winui/Shell.xaml.h‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ inline constexpr wchar_t shell_xaml[] = LR"xaml(
1414
<NavigationView.PaneFooter><StackPanel Margin="18,0,12,18" Spacing="5"><TextBlock Text="THIS PC" FontSize="10" CharacterSpacing="100" Style="{StaticResource Secondary}"/><TextBlock x:Name="DeviceName" Text="Device details pending" FontSize="12" TextWrapping="Wrap"/><TextBlock x:Name="DeviceState" Text="Checked before loading" Style="{StaticResource Caption}"/></StackPanel></NavigationView.PaneFooter>
1515
<Border Background="{ThemeResource WorkspaceBrush}" BorderBrush="{ThemeResource StrokeBrush}" BorderThickness="1,1,0,0" CornerRadius="4,0,0,0"><Grid x:Name="Workspace" Padding="24" RowSpacing="0"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
1616
<Grid x:Name="PageHeader" Margin="0,0,0,20"><Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions><TextBlock x:Name="PageTitle" Text="Overview" FontSize="28" FontWeight="SemiBold"/><StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" VerticalAlignment="Top"><Button x:Name="RefreshButton" Content="Refresh" AutomationProperties.Name="Refresh model library"/><Button x:Name="AddModelButton" Content="+ Add model"/></StackPanel></Grid>
17-
<InfoBar x:Name="Notice" Grid.Row="1" IsOpen="False" IsClosable="True" Severity="Error"/><Grid Grid.Row="2">
17+
<InfoBar x:Name="Notice" Grid.Row="1" IsOpen="False" IsClosable="True" Severity="Error">
18+
<InfoBar.ActionButton><Button x:Name="CopyErrorButton" Content="Copy" AutomationProperties.Name="Copy error message" ToolTipService.ToolTip="Copy the full error message"/></InfoBar.ActionButton>
19+
<StackPanel Spacing="8" Margin="0,0,0,12"><ScrollViewer MaxHeight="240" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" HorizontalScrollMode="Disabled"><TextBlock x:Name="NoticeMessage" IsTextSelectionEnabled="True" TextWrapping="Wrap" AutomationProperties.LiveSetting="Assertive"/></ScrollViewer><TextBlock x:Name="ErrorCopyStatus" FontSize="12" TextWrapping="Wrap" Visibility="Collapsed" AutomationProperties.LiveSetting="Polite"/></StackPanel>
20+
</InfoBar><Grid Grid.Row="2">
1821
)xaml"
1922
LR"xaml(
2023
<ScrollViewer x:Name="OverviewPage" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" HorizontalScrollMode="Disabled" HorizontalContentAlignment="Stretch"><StackPanel Spacing="14">

‎flare-winui/main.cpp‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ struct DesktopApp : ApplicationT<DesktopApp, Markup::IXamlMetadataProvider> {
159159
void show_error(std::wstring const& message) {
160160
auto notice = control<InfoBar>(L"Notice");
161161
notice.Title(L"Flare could not complete the operation");
162-
notice.Message(message);
162+
text(L"NoticeMessage",message);
163+
text(L"ErrorCopyStatus",L"");
164+
control<TextBlock>(L"ErrorCopyStatus").Visibility(Visibility::Collapsed);
163165
notice.Severity(InfoBarSeverity::Error);
164166
notice.Margin(Thickness{0,0,0,16});
165167
notice.IsOpen(true);
@@ -281,6 +283,15 @@ struct DesktopApp : ApplicationT<DesktopApp, Markup::IXamlMetadataProvider> {
281283
clicked(L"ViewDownloadsButton", [this] { select_page(2); });
282284
clicked(L"DownloadsFindButton", [this] { select_source(true); select_page(1); });
283285
clicked(L"CopyEndpointButton",[this]{if(endpoint_.empty())return;copy_text(endpoint_+L"/v1");copied_endpoint_=endpoint_;endpoint_copy_until_=std::chrono::steady_clock::now()+std::chrono::seconds(2);text(L"EndpointCopyStatus",L"Copied");});
286+
clicked(L"CopyErrorButton",[this]{
287+
try {
288+
copy_text(std::wstring(control<TextBlock>(L"NoticeMessage").Text()));
289+
text(L"ErrorCopyStatus",L"Copied");
290+
} catch(hresult_error const&) {
291+
text(L"ErrorCopyStatus",L"Clipboard unavailable. Select the message and press Ctrl+C.");
292+
}
293+
control<TextBlock>(L"ErrorCopyStatus").Visibility(Visibility::Visible);
294+
});
284295
initialize_chat();
285296
clicked(L"RetryModelButton", [this] { if (!pending_model_id_.empty()) activate(pending_model_id_, pending_model_name_); });
286297
for (auto name : { L"StorageButton", L"DownloadStorageButton" }) clicked(name, [this] { storage_dialog(); });

0 commit comments

Comments
 (0)