fix(ui): unfreeze the operation spinner and attribute rebind churn
Three causes behind the stuck indicator: - OperationStatus.End did not retire the generation, so tick work posted while the dispatcher was blocked drained afterwards and set ShowIndicator back to true on a finished operation. - The startup update check ran in Task.Run; OperationStatus writes its observable properties on the calling thread, so the final ShowIndicator=false never reached the binding. - OperationIndicator overwrote its own DataContext from the Status property, which re-targeted the call-site binding. It now scopes the DataContext to an inner panel and collapses itself when Status is null; every call site switched from DataContext= to Status=. Also gates the footer connection pill in the command body instead of CanExecute (a disabled command greyed out the ONLINE chip), and extends OperationTiming: pid per line, 4 MB rollover, fast successes dropped (failures always kept), and DetailsIsland.BindAsync now carries the selection trigger via TasksIslandViewModel.SelectFrom.
This commit is contained in:
@@ -3,8 +3,13 @@
|
||||
xmlns:svc="using:ClaudeDo.Ui.Services"
|
||||
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||
x:Class="ClaudeDo.Ui.Views.Controls.OperationIndicator"
|
||||
x:Name="Root"
|
||||
x:DataType="svc:OperationStatus">
|
||||
<!-- DataContext is scoped to the inner panel, never to the UserControl itself: overriding the
|
||||
control's own DataContext would re-target the `Status="{Binding ...}"` binding at every call
|
||||
site (it resolves against this control's DataContext) and throw an InvalidCastException. -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="{StaticResource SpaceSm}" VerticalAlignment="Center"
|
||||
DataContext="{Binding #Root.Status}"
|
||||
IsVisible="{Binding ShowIndicator}">
|
||||
<Ellipse Classes="spinner" VerticalAlignment="Center"/>
|
||||
<TextBlock Classes="meta" VerticalAlignment="Center" Text="{Binding Label}"/>
|
||||
|
||||
@@ -15,12 +15,21 @@ public partial class OperationIndicator : UserControl
|
||||
set => SetValue(StatusProperty, value);
|
||||
}
|
||||
|
||||
public OperationIndicator() => InitializeComponent();
|
||||
public OperationIndicator()
|
||||
{
|
||||
InitializeComponent();
|
||||
IsVisible = false;
|
||||
}
|
||||
|
||||
/// A null `Status` leaves the inner panel's DataContext null, so its `IsVisible="{Binding
|
||||
/// ShowIndicator}"` can't resolve and falls back to the property default — *visible*. That
|
||||
/// renders a label-less spinner plus the stalled hint forever. Collapse the whole control
|
||||
/// instead. Deliberately a direct set, not a binding: no call site binds `IsVisible` on the
|
||||
/// indicator, and a local value would win over one anyway.
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == StatusProperty)
|
||||
DataContext = change.GetNewValue<OperationStatus?>();
|
||||
IsVisible = change.GetNewValue<OperationStatus?>() is not null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
Command="{Binding Prep.PlanDayCommand}"
|
||||
IsEnabled="{Binding Prep.IsPlanDayEnabled}"
|
||||
Content="{loc:Tr details.planDay}"/>
|
||||
<ctl:OperationIndicator DataContext="{Binding Prep.PrepOperation}"/>
|
||||
<ctl:OperationIndicator Status="{Binding Prep.PrepOperation}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Panel>
|
||||
|
||||
@@ -31,7 +31,7 @@ public partial class TaskRowView : UserControl
|
||||
|
||||
// OnTunnelPointerPressed (TasksIslandView) only selects on the left button, so
|
||||
// right-click needs its own explicit selection before the menu opens.
|
||||
vm.SelectedTask = row;
|
||||
vm.SelectFrom(row, "context-menu");
|
||||
|
||||
var menu = new ContextMenu { DataContext = row };
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
Foreground="{DynamicResource TextMuteBrush}"
|
||||
Text="{Binding Subtitle}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<ctl:OperationIndicator DataContext="{Binding PlanningOperation}"/>
|
||||
<ctl:OperationIndicator Status="{Binding PlanningOperation}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="4"
|
||||
|
||||
@@ -131,7 +131,7 @@ public partial class TasksIslandView : UserControl
|
||||
if (!e.GetCurrentPoint(button).Properties.IsLeftButtonPressed) return;
|
||||
|
||||
// Select now so the details pane updates whether the gesture becomes a click or a drag.
|
||||
if (DataContext is TasksIslandViewModel vm) vm.SelectedTask = row;
|
||||
if (DataContext is TasksIslandViewModel vm) vm.SelectFrom(row, "pointer-press");
|
||||
|
||||
// If the click landed on a nested Button (e.g. the done-toggle checkbox or star),
|
||||
// don't start a drag — that would capture the pointer and swallow the inner Click.
|
||||
|
||||
@@ -18,7 +18,7 @@ internal static class JumpToTaskHelper
|
||||
s.Lists.SelectedList = item;
|
||||
|
||||
var existing = s.Tasks.Items.FirstOrDefault(r => r.Id == taskId);
|
||||
if (existing is not null) { s.Tasks.SelectedTask = existing; return; }
|
||||
if (existing is not null) { s.Tasks.SelectFrom(existing, "jump-to-task"); return; }
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
void OnChanged(object? _, NotifyCollectionChangedEventArgs __)
|
||||
@@ -32,7 +32,7 @@ internal static class JumpToTaskHelper
|
||||
{
|
||||
await Task.WhenAny(tcs.Task, Task.Delay(5000));
|
||||
var row = s.Tasks.Items.FirstOrDefault(r => r.Id == taskId);
|
||||
if (row is not null) s.Tasks.SelectedTask = row;
|
||||
if (row is not null) s.Tasks.SelectFrom(row, "jump-to-task");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<MenuItem Header="{loc:Tr shell.menu.about}" Command="{Binding OpenAboutCommand}"/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<controls:OperationIndicator DataContext="{Binding UpdateCheck.Op}" Margin="8,0,0,0"/>
|
||||
<controls:OperationIndicator Status="{Binding UpdateCheck.Op}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Middle: draggable strip -->
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
Command="{Binding ForgetFoldersCommand}"
|
||||
IsVisible="{Binding HasFolders}"/>
|
||||
</Grid>
|
||||
<ctl:OperationIndicator DataContext="{Binding ScanOp}"/>
|
||||
<ctl:OperationIndicator Status="{Binding ScanOp}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Repo checklist -->
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
<Button Classes="btn" Content="{loc:Tr settings.files.restoreDefaultAgents}"
|
||||
Command="{Binding Files.RestoreDefaultAgentsCommand}"
|
||||
HorizontalAlignment="Left"/>
|
||||
<ctl:OperationIndicator DataContext="{Binding Files.RestoreOp}"/>
|
||||
<ctl:OperationIndicator Status="{Binding Files.RestoreOp}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Classes="section-label" Text="{loc:Tr settings.files.promptsSection}"/>
|
||||
@@ -504,14 +504,14 @@
|
||||
<Button Grid.Column="1" Classes="btn" Content="{loc:Tr settings.skills.installButton}"
|
||||
Command="{Binding SessionSkills.InstallCommand}"/>
|
||||
</Grid>
|
||||
<ctl:OperationIndicator DataContext="{Binding SessionSkills.InstallOp}"/>
|
||||
<ctl:OperationIndicator Status="{Binding SessionSkills.InstallOp}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Border BorderBrush="{DynamicResource LineBrush}" BorderThickness="0,1,0,0" Margin="0,2,0,0"/>
|
||||
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Classes="section-label" Text="{loc:Tr settings.skills.installedSection}"/>
|
||||
<ctl:OperationIndicator DataContext="{Binding SessionSkills.UpdateOp}"/>
|
||||
<ctl:OperationIndicator Status="{Binding SessionSkills.UpdateOp}"/>
|
||||
<ItemsControl ItemsSource="{Binding SessionSkills.Skills}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="services:SessionSkillDto">
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
IsVisible="{Binding EmptyStateVisible}"/>
|
||||
<Button Classes="btn" Content="{loc:Tr modals.weeklyReport.regenerate}" Command="{Binding GenerateCommand}"
|
||||
IsVisible="{Binding HasReport}"/>
|
||||
<ctl:OperationIndicator DataContext="{Binding GenerateOperation}"/>
|
||||
<ctl:OperationIndicator Status="{Binding GenerateOperation}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock DockPanel.Dock="Top" Classes="meta" Margin="0,8,0,0"
|
||||
|
||||
Reference in New Issue
Block a user