feat(ui): list reordering, quick actions, and resizable modals

- Drag-to-reorder user lists in the sidebar, persisted via a new
  list sort_order column (AddListSortOrder migration, backfilled by
  creation time) and ListRepository.ReorderAsync
- "Open in Explorer" / "Open in Terminal" context-menu actions on lists
- "Clear all completed" button on the Tasks island
- Inline-edit subtask titles (empty text deletes the step) and
  click-to-copy task ID in the Details island
- Make modal and planning windows resizable (BorderOnly decorations
  with min sizes) instead of fixed-size borderless

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-06-01 15:28:17 +02:00
parent 4148dcdb18
commit ab44ba5e41
24 changed files with 1093 additions and 27 deletions

View File

@@ -826,6 +826,15 @@
<Setter Property="Opacity" Value="0.5" />
<Setter Property="TextDecorations" Value="Strikethrough" />
</Style>
<Style Selector="TextBox.subtask-edit">
<Setter Property="Padding" Value="4,2" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Background" Value="{StaticResource Surface2Brush}" />
<Setter Property="BorderBrush" Value="{DynamicResource LineBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" />
</Style>
<!-- ============================================================ -->
<!-- SECTION LABELS (OVERDUE / TASKS / COMPLETED) -->

View File

@@ -840,6 +840,35 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
CloseDetail?.Invoke();
}
[RelayCommand]
private async System.Threading.Tasks.Task CommitSubtaskEditAsync(SubtaskRowViewModel? row)
{
if (row is null || !row.IsEditing) return;
row.IsEditing = false;
var title = row.Title?.Trim() ?? "";
await using var ctx = _dbFactory.CreateDbContext();
var repo = new SubtaskRepository(ctx);
// Emptying the text removes the step.
if (string.IsNullOrEmpty(title))
{
await repo.DeleteAsync(row.Id);
Subtasks.Remove(row);
return;
}
var subs = await repo.GetByTaskIdAsync(Task?.Id ?? "");
var entity = subs.FirstOrDefault(s => s.Id == row.Id);
if (entity is null) return;
if (entity.Title != title)
{
entity.Title = title;
await repo.UpdateAsync(entity);
}
row.Title = title;
}
[RelayCommand]
private async System.Threading.Tasks.Task AddSubtaskAsync()
{
@@ -943,6 +972,7 @@ public sealed partial class SubtaskRowViewModel : ViewModelBase
public required string Id { get; init; }
[ObservableProperty] private string _title = "";
[ObservableProperty] private bool _done;
[ObservableProperty] private bool _isEditing;
[ObservableProperty] private ClaudeDo.Data.Models.TaskStatus _status;
[ObservableProperty] private ClaudeDo.Data.Models.WorktreeState _worktreeState = ClaudeDo.Data.Models.WorktreeState.Active;
}

View File

@@ -12,6 +12,8 @@ public sealed partial class ListNavItemViewModel : ViewModelBase
[ObservableProperty] private bool _isActive;
[ObservableProperty] private string? _workingDir;
[ObservableProperty] private string _defaultCommitType = CommitTypeRegistry.DefaultType;
[ObservableProperty] private bool _dropHintAbove;
[ObservableProperty] private bool _dropHintBelow;
public string? IconKey { get; init; }
public string? DotColorKey { get; init; }
}

View File

@@ -82,6 +82,52 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
finally { _worktreesOverviewOpen = false; }
}
[RelayCommand]
private void OpenInExplorer(ListNavItemViewModel? row)
{
var dir = row?.WorkingDir;
if (string.IsNullOrWhiteSpace(dir) || !System.IO.Directory.Exists(dir)) return;
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = dir,
UseShellExecute = true,
});
}
catch { /* best-effort */ }
}
[RelayCommand]
private void OpenInTerminal(ListNavItemViewModel? row)
{
var dir = row?.WorkingDir;
if (string.IsNullOrWhiteSpace(dir) || !System.IO.Directory.Exists(dir)) return;
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "wt.exe",
Arguments = $"-d \"{dir}\"",
UseShellExecute = true,
});
}
catch
{
// Windows Terminal not installed — fall back to a plain console at the directory.
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "cmd.exe",
WorkingDirectory = dir,
UseShellExecute = true,
});
}
catch { /* best-effort */ }
}
}
public ObservableCollection<ListNavItemViewModel> Items { get; } = new();
public ObservableCollection<ListNavItemViewModel> SmartLists { get; } = new();
public ObservableCollection<ListNavItemViewModel> UserLists { get; } = new();
@@ -231,6 +277,57 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
}
}
public void ClearDropHints()
{
foreach (var r in UserLists)
{
r.DropHintAbove = false;
r.DropHintBelow = false;
}
}
public void SetDropHint(ListNavItemViewModel target, bool placeBelow)
{
foreach (var r in UserLists)
{
var isTarget = ReferenceEquals(r, target);
r.DropHintAbove = isTarget && !placeBelow;
r.DropHintBelow = isTarget && placeBelow;
}
}
public async Task ReorderAsync(ListNavItemViewModel source, ListNavItemViewModel target, bool placeBelow)
{
if (source.Kind != ListKind.User || target.Kind != ListKind.User) return;
if (ReferenceEquals(source, target)) return;
MoveWithinCollection(UserLists, source, target, placeBelow);
var orderedIds = UserLists.Select(i => i.Id["user:".Length..]).ToList();
await using var ctx = await _dbFactory.CreateDbContextAsync();
var lists = new ListRepository(ctx);
await lists.ReorderAsync(orderedIds);
}
private static void MoveWithinCollection(
ObservableCollection<ListNavItemViewModel> coll,
ListNavItemViewModel source,
ListNavItemViewModel target,
bool placeBelow)
{
var srcIdx = coll.IndexOf(source);
var tgtIdx = coll.IndexOf(target);
if (srcIdx < 0 || tgtIdx < 0 || srcIdx == tgtIdx) return;
var finalIdx = placeBelow ? tgtIdx + 1 : tgtIdx;
if (srcIdx < finalIdx) finalIdx--;
if (finalIdx < 0) finalIdx = 0;
if (finalIdx >= coll.Count) finalIdx = coll.Count - 1;
if (finalIdx == srcIdx) return;
coll.Move(srcIdx, finalIdx);
}
partial void OnSelectedListChanged(ListNavItemViewModel? value)
{
foreach (var i in Items) i.IsActive = ReferenceEquals(i, value);

View File

@@ -487,6 +487,38 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
TasksChanged?.Invoke(this, EventArgs.Empty);
}
[RelayCommand]
private async Task ClearCompletedAsync()
{
if (CompletedItems.Count == 0) return;
// Delete children before parents so the parent-child FK (Restrict) doesn't
// block removing a completed planning parent together with its done children.
var toDelete = CompletedItems.OrderByDescending(r => r.IsChild).ToList();
if (ConfirmAsync is not null)
{
var ok = await ConfirmAsync($"Clear {toDelete.Count} completed task(s)? This cannot be undone.");
if (!ok) return;
}
await using var db = await _dbFactory.CreateDbContextAsync();
var repo = new TaskRepository(db);
foreach (var row in toDelete)
{
try
{
await repo.DeleteAsync(row.Id);
Items.Remove(row);
}
catch { /* still referenced by open child tasks; leave it visible */ }
}
Regroup();
UpdateSubtitle();
TasksChanged?.Invoke(this, EventArgs.Empty);
}
[RelayCommand]
private async Task ToggleStarAsync(TaskRowViewModel row)
{

View File

@@ -50,7 +50,10 @@
<StackPanel Grid.Column="1" Spacing="0">
<TextBlock Classes="meta"
Text="{Binding TaskIdBadge}"
Margin="0,0,0,4"/>
Margin="0,0,0,4"
Cursor="Hand"
ToolTip.Tip="Copy task ID"
Tapped="OnTaskIdTapped"/>
<TextBox Text="{Binding EditableTitle, Mode=TwoWay}"
FontSize="{StaticResource FontSizeTaskTitle}" FontWeight="Medium"
BorderThickness="0" Background="Transparent"
@@ -186,13 +189,30 @@
Width="16" Height="16"
Cursor="Hand"/>
</Button>
<TextBlock Grid.Column="1"
Classes="subtask-title"
Text="{Binding Title}"
<Panel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Classes="subtask-title"
Text="{Binding Title}"
IsVisible="{Binding !IsEditing}"
FontSize="{StaticResource FontSizeBody}"
Foreground="{DynamicResource TextDimBrush}"
VerticalAlignment="Center"
TextWrapping="Wrap"
Cursor="Ibeam"
Tapped="OnSubtaskTitleTapped"/>
<TextBox Classes="subtask-edit"
Text="{Binding Title, Mode=TwoWay}"
IsVisible="{Binding IsEditing}"
FontSize="{StaticResource FontSizeBody}"
Foreground="{DynamicResource TextDimBrush}"
VerticalAlignment="Center"
TextWrapping="Wrap"/>
AcceptsReturn="False"
TextWrapping="Wrap"
LostFocus="OnSubtaskEditLostFocus">
<TextBox.KeyBindings>
<KeyBinding Gesture="Enter"
Command="{Binding $parent[ItemsControl].((vm:DetailsIslandViewModel)DataContext).CommitSubtaskEditCommand}"
CommandParameter="{Binding}"/>
</TextBox.KeyBindings>
</TextBox>
</Panel>
</Grid>
</Border>
</DataTemplate>

View File

@@ -1,9 +1,13 @@
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
using ClaudeDo.Ui.ViewModels.Islands;
using ClaudeDo.Ui.Views.Modals;
using ClaudeDo.Ui.Views.Planning;
@@ -135,6 +139,31 @@ public partial class DetailsIslandView : UserControl
return await tcs.Task;
}
private void OnSubtaskTitleTapped(object? sender, TappedEventArgs e)
{
if (sender is not Control c || c.DataContext is not SubtaskRowViewModel row) return;
row.IsEditing = true;
var box = (c.GetVisualParent() as Panel)?.GetVisualDescendants().OfType<TextBox>().FirstOrDefault();
if (box is not null)
Dispatcher.UIThread.Post(() => { box.Focus(); box.SelectAll(); }, DispatcherPriority.Background);
}
private void OnSubtaskEditLostFocus(object? sender, RoutedEventArgs e)
{
if (DataContext is DetailsIslandViewModel vm
&& sender is Control c && c.DataContext is SubtaskRowViewModel row)
vm.CommitSubtaskEditCommand.Execute(row);
}
private async void OnTaskIdTapped(object? sender, TappedEventArgs e)
{
if (DataContext is not DetailsIslandViewModel vm || vm.Task is null) return;
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard is null) return;
await clipboard.SetTextAsync(vm.Task.Id);
}
private async void OnCopyDescriptionClick(object? sender, RoutedEventArgs e)
{
if (DataContext is not DetailsIslandViewModel vm) return;

View File

@@ -113,8 +113,18 @@
<ItemsControl ItemsSource="{Binding UserLists}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:ListNavItemViewModel">
<Border Classes="list-item" Classes.active="{Binding IsActive}"
Tapped="OnItemTapped">
<Grid RowDefinitions="Auto,Auto,Auto">
<!-- Above-row drop indicator -->
<Border Grid.Row="0" Height="2" VerticalAlignment="Center" Margin="4,0"
Background="{DynamicResource MossBrush}" CornerRadius="1"
IsVisible="{Binding DropHintAbove}"/>
<Border Grid.Row="1" Classes="list-item" Classes.active="{Binding IsActive}"
Tapped="OnItemTapped"
DragDrop.AllowDrop="True"
DragDrop.DragOver="OnListDragOver"
DragDrop.Drop="OnListDrop">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Settings..."
@@ -123,6 +133,15 @@
<MenuItem Header="Worktrees…"
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenWorktreesOverviewCommand}"
CommandParameter="{Binding}"/>
<Separator IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<MenuItem Header="Open in Explorer"
IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenInExplorerCommand}"
CommandParameter="{Binding}"/>
<MenuItem Header="Open in Terminal"
IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenInTerminalCommand}"
CommandParameter="{Binding}"/>
</ContextMenu>
</Border.ContextMenu>
<Grid ColumnDefinitions="20,*,Auto">
@@ -152,6 +171,12 @@
Text="{Binding Count}"/>
</Grid>
</Border>
<!-- Below-row drop indicator (last item only) -->
<Border Grid.Row="2" Height="2" VerticalAlignment="Center" Margin="4,0"
Background="{DynamicResource MossBrush}" CornerRadius="1"
IsVisible="{Binding DropHintBelow}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

View File

@@ -1,9 +1,11 @@
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.VisualTree;
using ClaudeDo.Ui.ViewModels;
using ClaudeDo.Ui.ViewModels.Islands;
using ClaudeDo.Ui.ViewModels.Modals;
@@ -13,9 +15,13 @@ namespace ClaudeDo.Ui.Views.Islands;
public partial class ListsIslandView : UserControl
{
private static readonly DataFormat<string> ListRowFormat =
DataFormat.CreateStringApplicationFormat("claudedo-list-row");
public ListsIslandView()
{
InitializeComponent();
AddHandler(PointerPressedEvent, OnTunnelPointerPressed, RoutingStrategies.Tunnel);
DataContextChanged += (_, _) =>
{
if (DataContext is ListsIslandViewModel vm)
@@ -84,6 +90,127 @@ public partial class ListsIslandView : UserControl
vm.SelectCommand.Execute(item);
}
private async void OnTunnelPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (DataContext is not ListsIslandViewModel vm) return;
if (e.Source is not Visual src) return;
var border = FindListItemBorder(src);
if (border?.DataContext is not ListNavItemViewModel row || row.Kind != ListKind.User) return;
if (!e.GetCurrentPoint(border).Properties.IsLeftButtonPressed) return;
// Double-click opens the list's settings instead of starting a drag. Handled here
// because DoDragDropAsync captures the pointer and would swallow a DoubleTapped event.
if (e.ClickCount == 2)
{
vm.OpenListSettingsCommand.Execute(row);
return;
}
// Select now so the right pane updates whether the gesture becomes a click or a drag
// (the Tapped handler doesn't fire once DoDragDropAsync captures the pointer).
vm.SelectCommand.Execute(row);
var data = new DataTransfer();
data.Add(DataTransferItem.Create(ListRowFormat, row.Id));
try
{
await DragDrop.DoDragDropAsync(e, data, DragDropEffects.Move);
}
finally
{
vm.ClearDropHints();
}
}
private void OnListDragOver(object? sender, DragEventArgs e)
{
if (DataContext is not ListsIslandViewModel vm) { e.DragEffects = DragDropEffects.None; return; }
if (!e.DataTransfer?.Contains(ListRowFormat) ?? true)
{
e.DragEffects = DragDropEffects.None;
vm.ClearDropHints();
return;
}
if (sender is not Border b || b.DataContext is not ListNavItemViewModel target || target.Kind != ListKind.User)
{
e.DragEffects = DragDropEffects.None;
vm.ClearDropHints();
return;
}
var sourceId = e.DataTransfer?.TryGetValue(ListRowFormat);
if (string.IsNullOrEmpty(sourceId) || sourceId == target.Id)
{
e.DragEffects = DragDropEffects.None;
vm.ClearDropHints();
return;
}
var placeBelow = e.GetPosition(b).Y > b.Bounds.Height / 2;
// Canonicalize: "drop below X" == "drop above X+1". Only the last row shows a below-line.
ListNavItemViewModel hintRow = target;
bool hintBelow = false;
if (placeBelow)
{
var next = FindNextUserList(vm, target);
if (next is not null) { hintRow = next; hintBelow = false; }
else { hintRow = target; hintBelow = true; }
}
if (hintRow.Id == sourceId)
{
e.DragEffects = DragDropEffects.None;
vm.ClearDropHints();
return;
}
vm.SetDropHint(hintRow, hintBelow);
e.DragEffects = DragDropEffects.Move;
}
private async void OnListDrop(object? sender, DragEventArgs e)
{
if (DataContext is not ListsIslandViewModel vm) return;
try
{
if (sender is not Border b || b.DataContext is not ListNavItemViewModel target || target.Kind != ListKind.User) return;
var sourceId = e.DataTransfer?.TryGetValue(ListRowFormat);
if (string.IsNullOrEmpty(sourceId) || sourceId == target.Id) return;
var source = vm.UserLists.FirstOrDefault(r => r.Id == sourceId);
if (source is null) return;
var placeBelow = e.GetPosition(b).Y > b.Bounds.Height / 2;
vm.ClearDropHints();
await vm.ReorderAsync(source, target, placeBelow);
}
catch
{
vm.ClearDropHints();
throw;
}
}
private static Border? FindListItemBorder(Visual? v)
{
while (v is not null)
{
if (v is Border b && b.Classes.Contains("list-item")) return b;
v = v.GetVisualParent();
}
return null;
}
private static ListNavItemViewModel? FindNextUserList(ListsIslandViewModel vm, ListNavItemViewModel row)
{
var idx = vm.UserLists.IndexOf(row);
if (idx < 0) return null;
return idx + 1 < vm.UserLists.Count ? vm.UserLists[idx + 1] : null;
}
private async System.Threading.Tasks.Task ShowSettingsAsync(SettingsModalViewModel settingsVm)
{
var owner = TopLevel.GetTopLevel(this) as Window;

View File

@@ -126,8 +126,17 @@
<Binding Path="IsShowingCompleted"/>
</MultiBinding>
</StackPanel.IsVisible>
<TextBlock Classes="eyebrow section-label"
Text="{Binding CompletedHeader}" Margin="14,14,14,6"/>
<Grid ColumnDefinitions="*,Auto" Margin="14,14,14,6">
<TextBlock Grid.Column="0" Classes="eyebrow section-label"
Text="{Binding CompletedHeader}" VerticalAlignment="Center"/>
<Button Grid.Column="1" Classes="icon-btn"
Command="{Binding ClearCompletedCommand}"
ToolTip.Tip="Clear all completed"
VerticalAlignment="Center">
<PathIcon Data="{StaticResource Icon.Trash}" Width="13" Height="13"
Foreground="{DynamicResource BloodBrush}"/>
</Button>
</Grid>
<ItemsControl ItemsSource="{Binding CompletedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:TaskRowViewModel">

View File

@@ -5,9 +5,11 @@
x:Class="ClaudeDo.Ui.Views.Modals.DiffModalView"
x:DataType="vm:DiffModalViewModel"
Title="Diff"
Width="1200" Height="800"
WindowDecorations="None"
Width="1200" Height="800" MinWidth="700" MinHeight="450"
CanResize="True"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}">

View File

@@ -8,8 +8,9 @@
Width="520" Height="720"
CanResize="True"
MinWidth="460" MinHeight="520"
WindowDecorations="None"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}">

View File

@@ -5,10 +5,11 @@
x:Class="ClaudeDo.Ui.Views.Modals.MergeModalView"
x:DataType="vm:MergeModalViewModel"
Title="Merge worktree"
Width="560" Height="460"
CanResize="False"
WindowDecorations="None"
Width="560" Height="460" MinWidth="460" MinHeight="360"
CanResize="True"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}">

View File

@@ -5,9 +5,11 @@
x:Class="ClaudeDo.Ui.Views.Modals.RepoImportModalView"
x:DataType="vm:RepoImportModalViewModel"
Title="Add repos as lists"
Width="560" Height="480"
WindowDecorations="None"
Width="560" Height="480" MinWidth="420" MinHeight="320"
CanResize="True"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}">
<Window.KeyBindings>

View File

@@ -7,9 +7,11 @@
x:Class="ClaudeDo.Ui.Views.Modals.SettingsModalView"
x:DataType="vm:SettingsModalViewModel"
Title="Settings"
Width="580" Height="760"
WindowDecorations="None"
Width="580" Height="760" MinWidth="480" MinHeight="520"
CanResize="True"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}">

View File

@@ -11,7 +11,8 @@
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True">
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1">
<Window.Resources>
<converters:WorktreeStateColorConverter x:Key="WorktreeStateColor"/>

View File

@@ -5,9 +5,11 @@
x:DataType="vm:ConflictResolutionViewModel"
x:Class="ClaudeDo.Ui.Views.Planning.ConflictResolutionView"
Title="Merge conflict"
Width="560" SizeToContent="Height"
WindowDecorations="None"
Width="560" SizeToContent="Height" MinWidth="460"
CanResize="True"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}">

View File

@@ -5,9 +5,11 @@
x:Class="ClaudeDo.Ui.Views.Planning.PlanningDiffView"
x:DataType="vm:PlanningDiffViewModel"
Title="Planning — Combined diff"
Width="1100" Height="700"
WindowDecorations="None"
Width="1100" Height="700" MinWidth="700" MinHeight="450"
CanResize="True"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}">