style(ui): polish islands and remove terminal traffic-light dots
This commit is contained in:
@@ -245,6 +245,34 @@
|
|||||||
<Setter Property="BoxShadow" Value="0 0 0 3 #387C9166" />
|
<Setter Property="BoxShadow" Value="0 0 0 3 #387C9166" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- FLAT BUTTON — replaces the entire Button template with a -->
|
||||||
|
<!-- bare ContentPresenter so no Fluent chrome (bg / hover / -->
|
||||||
|
<!-- pressed) can render. Used to wrap TaskRowView for clicks. -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Style Selector="Button.flat">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<ContentPresenter Name="PART_ContentPresenter"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Padding="0"
|
||||||
|
CornerRadius="0"
|
||||||
|
Content="{TemplateBinding Content}"
|
||||||
|
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||||
|
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
<!-- TASK ROW -->
|
<!-- TASK ROW -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
@@ -264,12 +292,10 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.task-row:pointerover">
|
<Style Selector="Border.task-row:pointerover">
|
||||||
<Setter Property="Background" Value="{StaticResource AccentSoftBrush}" />
|
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource LineBrightBrush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrightBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.task-row.selected">
|
<Style Selector="Border.task-row.selected">
|
||||||
<Setter Property="Background" Value="{StaticResource Surface3Brush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrightBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
@@ -278,7 +304,7 @@
|
|||||||
<Setter Property="Width" Value="18" />
|
<Setter Property="Width" Value="18" />
|
||||||
<Setter Property="Height" Value="18" />
|
<Setter Property="Height" Value="18" />
|
||||||
<Setter Property="StrokeThickness" Value="1.5" />
|
<Setter Property="StrokeThickness" Value="1.5" />
|
||||||
<Setter Property="Stroke" Value="{StaticResource TextFaintBrush}" />
|
<Setter Property="Stroke" Value="{StaticResource TextMuteBrush}" />
|
||||||
<Setter Property="Fill" Value="Transparent" />
|
<Setter Property="Fill" Value="Transparent" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Ellipse.task-check.done">
|
<Style Selector="Ellipse.task-check.done">
|
||||||
@@ -663,9 +689,10 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- Selected state: shift background to accent-soft -->
|
<!-- Selected state: rely on the left accent bar from TaskRowView;
|
||||||
|
no heavy bg or perimeter border. -->
|
||||||
<Style Selector="Border.task-row.selected">
|
<Style Selector="Border.task-row.selected">
|
||||||
<Setter Property="Background" Value="{StaticResource AccentSoftBrush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrightBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- Left accent bar for selected row -->
|
<!-- Left accent bar for selected row -->
|
||||||
@@ -678,12 +705,13 @@
|
|||||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- Done state: dim the whole row and the title -->
|
<!-- Done state: dim the whole row and strike through the title -->
|
||||||
<Style Selector="Border.task-row.done">
|
<Style Selector="Border.task-row.done">
|
||||||
<Setter Property="Opacity" Value="0.55" />
|
<Setter Property="Opacity" Value="0.45" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.task-row.done TextBlock.task-title">
|
<Style Selector="Border.task-row.done TextBlock.task-title">
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
||||||
|
<Setter Property="TextDecorations" Value="Strikethrough" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Text;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using ClaudeDo.Data;
|
using ClaudeDo.Data;
|
||||||
using ClaudeDo.Data.Repositories;
|
using ClaudeDo.Data.Repositories;
|
||||||
|
using ClaudeDo.Ui.Helpers;
|
||||||
using ClaudeDo.Ui.Services;
|
using ClaudeDo.Ui.Services;
|
||||||
using ClaudeDo.Ui.ViewModels.Modals;
|
using ClaudeDo.Ui.ViewModels.Modals;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -17,7 +19,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
|
|
||||||
// Current task row (set by IslandsShellViewModel via Bind)
|
// Current task row (set by IslandsShellViewModel via Bind)
|
||||||
[ObservableProperty] private TaskRowViewModel? _task;
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(RunNowCommand))]
|
||||||
|
private TaskRowViewModel? _task;
|
||||||
|
|
||||||
// Editable fields
|
// Editable fields
|
||||||
[ObservableProperty] private string _editableTitle = "";
|
[ObservableProperty] private string _editableTitle = "";
|
||||||
@@ -28,12 +32,22 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
public string TaskIdBadge => Task != null ? $"#T{Task.Id[..Math.Min(3, Task.Id.Length)].ToUpperInvariant()}" : "";
|
public string TaskIdBadge => Task != null ? $"#T{Task.Id[..Math.Min(3, Task.Id.Length)].ToUpperInvariant()}" : "";
|
||||||
|
|
||||||
// Agent strip fields
|
// Agent strip fields
|
||||||
[ObservableProperty] private string _agentStatusLabel = "Idle";
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(RunNowCommand))]
|
||||||
|
private string _agentStatusLabel = "Idle";
|
||||||
public bool IsRunning => AgentStatusLabel == "Running";
|
public bool IsRunning => AgentStatusLabel == "Running";
|
||||||
|
public bool IsDone => AgentStatusLabel == "Done";
|
||||||
|
public bool IsFailed => AgentStatusLabel == "Failed";
|
||||||
|
|
||||||
partial void OnAgentStatusLabelChanged(string value) => OnPropertyChanged(nameof(IsRunning));
|
partial void OnAgentStatusLabelChanged(string value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(IsRunning));
|
||||||
|
OnPropertyChanged(nameof(IsDone));
|
||||||
|
OnPropertyChanged(nameof(IsFailed));
|
||||||
|
}
|
||||||
[ObservableProperty] private string? _model;
|
[ObservableProperty] private string? _model;
|
||||||
[ObservableProperty] private string? _worktreePath;
|
[ObservableProperty] private string? _worktreePath;
|
||||||
|
[ObservableProperty] private string? _worktreeBaseCommit;
|
||||||
[ObservableProperty] private string? _branchLine;
|
[ObservableProperty] private string? _branchLine;
|
||||||
[ObservableProperty] private int _turns;
|
[ObservableProperty] private int _turns;
|
||||||
[ObservableProperty] private int _tokens;
|
[ObservableProperty] private int _tokens;
|
||||||
@@ -61,6 +75,10 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
public ObservableCollection<LogLineViewModel> Log { get; } = new();
|
public ObservableCollection<LogLineViewModel> Log { get; } = new();
|
||||||
public ObservableCollection<SubtaskRowViewModel> Subtasks { get; } = new();
|
public ObservableCollection<SubtaskRowViewModel> Subtasks { get; } = new();
|
||||||
|
|
||||||
|
// Claude CLI stream-json parser + buffer for partial text deltas
|
||||||
|
private readonly StreamLineFormatter _formatter = new();
|
||||||
|
private readonly StringBuilder _claudeBuf = new();
|
||||||
|
|
||||||
// The task ID we are currently subscribed to for live log messages
|
// The task ID we are currently subscribed to for live log messages
|
||||||
private string? _subscribedTaskId;
|
private string? _subscribedTaskId;
|
||||||
|
|
||||||
@@ -89,23 +107,92 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
|
|
||||||
// Subscribe once; filter by current task id inside the handler
|
// Subscribe once; filter by current task id inside the handler
|
||||||
_worker.TaskMessageEvent += OnTaskMessage;
|
_worker.TaskMessageEvent += OnTaskMessage;
|
||||||
|
|
||||||
|
// Re-evaluate RunNow CanExecute when worker connection flips.
|
||||||
|
_worker.PropertyChanged += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(WorkerClient.IsConnected))
|
||||||
|
RunNowCommand.NotifyCanExecuteChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the task row's live status changes (e.g. TaskStarted/Finished), mirror it.
|
||||||
|
_worker.TaskStartedEvent += (slot, taskId, startedAt) =>
|
||||||
|
{
|
||||||
|
if (Task?.Id == taskId) AgentStatusLabel = "Running";
|
||||||
|
};
|
||||||
|
_worker.TaskFinishedEvent += (slot, taskId, status, finishedAt) =>
|
||||||
|
{
|
||||||
|
if (Task?.Id != taskId) return;
|
||||||
|
FlushClaudeBuffer();
|
||||||
|
Log.Add(new LogLineViewModel
|
||||||
|
{
|
||||||
|
Kind = LogKind.Done,
|
||||||
|
Text = $"── {status.ToUpperInvariant()} · {finishedAt.ToLocalTime():HH:mm:ss} ──",
|
||||||
|
});
|
||||||
|
AgentStatusLabel = status;
|
||||||
|
// Re-query to pick up worktree created during the run.
|
||||||
|
_ = RefreshWorktreeAsync(taskId);
|
||||||
|
};
|
||||||
|
|
||||||
|
_worker.WorktreeUpdatedEvent += taskId =>
|
||||||
|
{
|
||||||
|
if (Task?.Id == taskId) _ = RefreshWorktreeAsync(taskId);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTaskMessage(string taskId, string line)
|
private void OnTaskMessage(string taskId, string line)
|
||||||
{
|
{
|
||||||
if (taskId != _subscribedTaskId) return;
|
if (taskId != _subscribedTaskId) return;
|
||||||
// Parse a simple prefix convention: "[sys]", "[tool]", "[claude]", etc.
|
|
||||||
// Fall back to Msg for unrecognised lines.
|
// `[stdout] ...json...` lines are Claude CLI stream-json; parse through the
|
||||||
|
// formatter so the user sees human text, not raw JSON.
|
||||||
|
if (line.StartsWith("[stdout]", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var body = line["[stdout]".Length..].TrimStart();
|
||||||
|
var formatted = _formatter.FormatLine(body);
|
||||||
|
if (formatted is null) return; // filter noise (message_start, etc.)
|
||||||
|
AppendClaudeText(formatted);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-stdout tagged lines: flush any buffered text then classify by prefix.
|
||||||
|
FlushClaudeBuffer();
|
||||||
|
|
||||||
var kind = line.StartsWith("[sys]", StringComparison.OrdinalIgnoreCase) ? LogKind.Sys
|
var kind = line.StartsWith("[sys]", StringComparison.OrdinalIgnoreCase) ? LogKind.Sys
|
||||||
: line.StartsWith("[tool]", StringComparison.OrdinalIgnoreCase) ? LogKind.Tool
|
: line.StartsWith("[tool]", StringComparison.OrdinalIgnoreCase) ? LogKind.Tool
|
||||||
: line.StartsWith("[claude]", StringComparison.OrdinalIgnoreCase) ? LogKind.Claude
|
: line.StartsWith("[claude]", StringComparison.OrdinalIgnoreCase) ? LogKind.Claude
|
||||||
: line.StartsWith("[stdout]", StringComparison.OrdinalIgnoreCase) ? LogKind.Stdout
|
|
||||||
: line.StartsWith("[stderr]", StringComparison.OrdinalIgnoreCase) ? LogKind.Stderr
|
: line.StartsWith("[stderr]", StringComparison.OrdinalIgnoreCase) ? LogKind.Stderr
|
||||||
: line.StartsWith("[done]", StringComparison.OrdinalIgnoreCase) ? LogKind.Done
|
: line.StartsWith("[done]", StringComparison.OrdinalIgnoreCase) ? LogKind.Done
|
||||||
: LogKind.Msg;
|
: LogKind.Msg;
|
||||||
Log.Add(new LogLineViewModel { Kind = kind, Text = line });
|
Log.Add(new LogLineViewModel { Kind = kind, Text = line });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AppendClaudeText(string chunk)
|
||||||
|
{
|
||||||
|
_claudeBuf.Append(chunk);
|
||||||
|
// Emit a log entry for every completed line; keep the trailing remainder buffered.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var text = _claudeBuf.ToString();
|
||||||
|
var nl = text.IndexOf('\n');
|
||||||
|
if (nl < 0) break;
|
||||||
|
var piece = text[..nl].TrimEnd('\r');
|
||||||
|
if (!string.IsNullOrWhiteSpace(piece))
|
||||||
|
Log.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = piece });
|
||||||
|
_claudeBuf.Clear();
|
||||||
|
_claudeBuf.Append(text[(nl + 1)..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlushClaudeBuffer()
|
||||||
|
{
|
||||||
|
if (_claudeBuf.Length == 0) return;
|
||||||
|
var piece = _claudeBuf.ToString().TrimEnd();
|
||||||
|
_claudeBuf.Clear();
|
||||||
|
if (!string.IsNullOrWhiteSpace(piece))
|
||||||
|
Log.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = piece });
|
||||||
|
}
|
||||||
|
|
||||||
public void Bind(TaskRowViewModel? row)
|
public void Bind(TaskRowViewModel? row)
|
||||||
{
|
{
|
||||||
_loadCts?.Cancel();
|
_loadCts?.Cancel();
|
||||||
@@ -117,6 +204,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(TaskIdBadge));
|
OnPropertyChanged(nameof(TaskIdBadge));
|
||||||
Log.Clear();
|
Log.Clear();
|
||||||
Subtasks.Clear();
|
Subtasks.Clear();
|
||||||
|
_claudeBuf.Clear();
|
||||||
|
|
||||||
if (row == null)
|
if (row == null)
|
||||||
{
|
{
|
||||||
@@ -137,11 +225,14 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var ctx = _dbFactory.CreateDbContext();
|
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||||
var taskRepo = new TaskRepository(ctx);
|
|
||||||
var subtaskRepo = new SubtaskRepository(ctx);
|
var subtaskRepo = new SubtaskRepository(ctx);
|
||||||
|
|
||||||
var entity = await taskRepo.GetByIdAsync(row.Id);
|
// Own query with Include so WorktreePath/BranchLine are populated.
|
||||||
|
var entity = await ctx.Tasks
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(t => t.Worktree)
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == row.Id, ct);
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
if (entity == null) return;
|
if (entity == null) return;
|
||||||
|
|
||||||
@@ -163,6 +254,27 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
catch (OperationCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async System.Threading.Tasks.Task RefreshWorktreeAsync(string taskId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var ctx = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var entity = await ctx.Tasks
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(t => t.Worktree)
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == taskId);
|
||||||
|
if (entity == null || Task?.Id != taskId) return;
|
||||||
|
|
||||||
|
WorktreePath = entity.Worktree?.Path;
|
||||||
|
WorktreeBaseCommit = entity.Worktree?.BaseCommit;
|
||||||
|
BranchLine = entity.Worktree is { } w ? $"{w.BranchName} ← main" : null;
|
||||||
|
AgentStatusLabel = entity.Status.ToString();
|
||||||
|
if (Task is { } row && entity.Worktree?.DiffStat is { } stat)
|
||||||
|
row.DiffStat = stat;
|
||||||
|
}
|
||||||
|
catch { /* best-effort refresh */ }
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanOpenDiff))]
|
[RelayCommand(CanExecute = nameof(CanOpenDiff))]
|
||||||
private async System.Threading.Tasks.Task OpenDiffAsync()
|
private async System.Threading.Tasks.Task OpenDiffAsync()
|
||||||
{
|
{
|
||||||
@@ -170,6 +282,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
var diffVm = new DiffModalViewModel(_services.GetRequiredService<ClaudeDo.Data.Git.GitService>())
|
var diffVm = new DiffModalViewModel(_services.GetRequiredService<ClaudeDo.Data.Git.GitService>())
|
||||||
{
|
{
|
||||||
WorktreePath = WorktreePath,
|
WorktreePath = WorktreePath,
|
||||||
|
BaseRef = WorktreeBaseCommit,
|
||||||
};
|
};
|
||||||
await diffVm.LoadAsync();
|
await diffVm.LoadAsync();
|
||||||
await ShowDiffModal(diffVm);
|
await ShowDiffModal(diffVm);
|
||||||
@@ -178,19 +291,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
private bool CanOpenDiff() => WorktreePath != null;
|
private bool CanOpenDiff() => WorktreePath != null;
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanOpenWorktree))]
|
[RelayCommand(CanExecute = nameof(CanOpenWorktree))]
|
||||||
private async System.Threading.Tasks.Task OpenWorktreeAsync()
|
private void OpenWorktree()
|
||||||
{
|
|
||||||
if (WorktreePath == null || ShowWorktreeModal == null) return;
|
|
||||||
var vm = _services.GetRequiredService<WorktreeModalViewModel>();
|
|
||||||
vm.WorktreePath = WorktreePath;
|
|
||||||
await vm.LoadAsync();
|
|
||||||
await ShowWorktreeModal(vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanOpenWorktree() => WorktreePath != null;
|
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanOpenWorktree))]
|
|
||||||
private void OpenInExplorer()
|
|
||||||
{
|
{
|
||||||
if (WorktreePath == null) return;
|
if (WorktreePath == null) return;
|
||||||
try
|
try
|
||||||
@@ -204,11 +305,12 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
catch { /* explorer open is best-effort */ }
|
catch { /* explorer open is best-effort */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CanOpenWorktree() => WorktreePath != null;
|
||||||
|
|
||||||
partial void OnWorktreePathChanged(string? value)
|
partial void OnWorktreePathChanged(string? value)
|
||||||
{
|
{
|
||||||
OpenDiffCommand.NotifyCanExecuteChanged();
|
OpenDiffCommand.NotifyCanExecuteChanged();
|
||||||
OpenWorktreeCommand.NotifyCanExecuteChanged();
|
OpenWorktreeCommand.NotifyCanExecuteChanged();
|
||||||
OpenInExplorerCommand.NotifyCanExecuteChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -270,6 +372,25 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
if (Task == null) return;
|
if (Task == null) return;
|
||||||
await _worker.CancelTaskAsync(Task.Id);
|
await _worker.CancelTaskAsync(Task.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanRunNow))]
|
||||||
|
private async System.Threading.Tasks.Task RunNowAsync()
|
||||||
|
{
|
||||||
|
if (Task == null) return;
|
||||||
|
AgentStatusLabel = "Running";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _worker.RunNowAsync(Task.Id);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
AgentStatusLabel = "Failed";
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanRunNow() =>
|
||||||
|
Task != null && _worker.IsConnected && !IsRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed partial class SubtaskRowViewModel : ViewModelBase
|
public sealed partial class SubtaskRowViewModel : ViewModelBase
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using ClaudeDo.Data;
|
using ClaudeDo.Data;
|
||||||
using ClaudeDo.Data.Repositories;
|
using ClaudeDo.Data.Repositories;
|
||||||
|
using ClaudeDo.Ui.ViewModels.Modals;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.ViewModels.Islands;
|
namespace ClaudeDo.Ui.ViewModels.Islands;
|
||||||
|
|
||||||
@@ -12,11 +14,23 @@ public enum ListKind { Smart, Virtual, User }
|
|||||||
public sealed partial class ListsIslandViewModel : ViewModelBase
|
public sealed partial class ListsIslandViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||||
|
private readonly IServiceProvider? _services;
|
||||||
|
|
||||||
public event EventHandler? SelectionChanged;
|
public event EventHandler? SelectionChanged;
|
||||||
public event EventHandler? FocusSearchRequested;
|
public event EventHandler? FocusSearchRequested;
|
||||||
public void RequestFocusSearch() => FocusSearchRequested?.Invoke(this, EventArgs.Empty);
|
public void RequestFocusSearch() => FocusSearchRequested?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
public Func<SettingsModalViewModel, Task>? ShowSettingsModal { get; set; }
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task OpenSettings()
|
||||||
|
{
|
||||||
|
if (ShowSettingsModal is null || _services is null) return;
|
||||||
|
var settingsVm = _services.GetRequiredService<SettingsModalViewModel>();
|
||||||
|
await settingsVm.LoadAsync();
|
||||||
|
await ShowSettingsModal(settingsVm);
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableCollection<ListNavItemViewModel> Items { get; } = new();
|
public ObservableCollection<ListNavItemViewModel> Items { get; } = new();
|
||||||
public ObservableCollection<ListNavItemViewModel> SmartLists { get; } = new();
|
public ObservableCollection<ListNavItemViewModel> SmartLists { get; } = new();
|
||||||
public ObservableCollection<ListNavItemViewModel> UserLists { get; } = new();
|
public ObservableCollection<ListNavItemViewModel> UserLists { get; } = new();
|
||||||
@@ -28,9 +42,10 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
|
|||||||
public string MachineName { get; } = Environment.MachineName;
|
public string MachineName { get; } = Environment.MachineName;
|
||||||
public string UserInitials { get; }
|
public string UserInitials { get; }
|
||||||
|
|
||||||
public ListsIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory)
|
public ListsIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IServiceProvider? services = null)
|
||||||
{
|
{
|
||||||
_dbFactory = dbFactory;
|
_dbFactory = dbFactory;
|
||||||
|
_services = services;
|
||||||
var parts = Environment.UserName.Split('.', '_', '-', ' ');
|
var parts = Environment.UserName.Split('.', '_', '-', ' ');
|
||||||
UserInitials = parts.Length >= 2
|
UserInitials = parts.Length >= 2
|
||||||
? $"{parts[0][0]}{parts[1][0]}".ToUpperInvariant()
|
? $"{parts[0][0]}{parts[1][0]}".ToUpperInvariant()
|
||||||
|
|||||||
@@ -130,10 +130,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
|||||||
var running = Items.Count(i => i.Status == TaskStatus.Running);
|
var running = Items.Count(i => i.Status == TaskStatus.Running);
|
||||||
var review = Items.Count(i => i.Status == TaskStatus.Done && i.Branch != null);
|
var review = Items.Count(i => i.Status == TaskStatus.Done && i.Branch != null);
|
||||||
|
|
||||||
var weekday = now.ToString("dddd", CultureInfo.CurrentCulture);
|
Subtitle = open == 1 ? "1 open task" : $"{open} open tasks";
|
||||||
var month = now.ToString("MMM", CultureInfo.CurrentCulture);
|
|
||||||
var day = now.Day;
|
|
||||||
Subtitle = $"{weekday}, {month} {day} · {open} open";
|
|
||||||
|
|
||||||
if (running > 0 || review > 0)
|
if (running > 0 || review > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public sealed partial class DiffModalViewModel : ViewModelBase
|
|||||||
private readonly GitService _git;
|
private readonly GitService _git;
|
||||||
|
|
||||||
public required string WorktreePath { get; init; }
|
public required string WorktreePath { get; init; }
|
||||||
|
public string? BaseRef { get; init; }
|
||||||
|
|
||||||
public ObservableCollection<DiffFileViewModel> Files { get; } = new();
|
public ObservableCollection<DiffFileViewModel> Files { get; } = new();
|
||||||
|
|
||||||
@@ -62,7 +63,12 @@ public sealed partial class DiffModalViewModel : ViewModelBase
|
|||||||
Files.Clear();
|
Files.Clear();
|
||||||
|
|
||||||
string raw;
|
string raw;
|
||||||
try { raw = await _git.GetDiffAsync(WorktreePath, ct); }
|
try
|
||||||
|
{
|
||||||
|
raw = BaseRef is not null
|
||||||
|
? await _git.GetBranchDiffAsync(WorktreePath, BaseRef, ct)
|
||||||
|
: await _git.GetDiffAsync(WorktreePath, ct);
|
||||||
|
}
|
||||||
catch { return; }
|
catch { return; }
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(raw)) return;
|
if (string.IsNullOrWhiteSpace(raw)) return;
|
||||||
|
|||||||
@@ -42,6 +42,15 @@
|
|||||||
<PathIcon Data="{StaticResource Icon.X}" Width="12" Height="12"
|
<PathIcon Data="{StaticResource Icon.X}" Width="12" Height="12"
|
||||||
Foreground="{DynamicResource BloodBrush}"/>
|
Foreground="{DynamicResource BloodBrush}"/>
|
||||||
</Button>
|
</Button>
|
||||||
|
<!-- Hand off button — only when idle -->
|
||||||
|
<Button Grid.Column="3"
|
||||||
|
Classes="btn accent"
|
||||||
|
Content="Hand off"
|
||||||
|
Command="{Binding RunNowCommand}"
|
||||||
|
IsVisible="{Binding !IsRunning}"
|
||||||
|
ToolTip.Tip="Hand task off to Claude"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Padding="10,4"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Row 2: WORKTREE label + path + copy button -->
|
<!-- Row 2: WORKTREE label + path + copy button -->
|
||||||
@@ -123,13 +132,14 @@
|
|||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,4,0,0">
|
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,4,0,0">
|
||||||
<Button Classes="btn" Content="Open diff" Command="{Binding OpenDiffCommand}"/>
|
<Button Classes="btn" Content="Open diff" Command="{Binding OpenDiffCommand}"/>
|
||||||
<Button Classes="btn" Content="Worktree" Command="{Binding OpenWorktreeCommand}"/>
|
<Button Classes="btn" Command="{Binding OpenWorktreeCommand}"
|
||||||
<Button Classes="btn" ToolTip.Tip="Open in file explorer"
|
ToolTip.Tip="Open worktree in file explorer">
|
||||||
Command="{Binding OpenInExplorerCommand}"
|
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||||
Padding="8,6">
|
<PathIcon Data="{StaticResource Icon.ArrowOut}"
|
||||||
<PathIcon Data="{StaticResource Icon.ArrowOut}"
|
Width="11" Height="11"
|
||||||
Width="12" Height="12"
|
Foreground="{DynamicResource TextDimBrush}"/>
|
||||||
Foreground="{DynamicResource TextDimBrush}"/>
|
<TextBlock Text="Worktree" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -88,66 +88,70 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- ── Scrollable body ── -->
|
<!-- ── Main body: agent strip (auto) · terminal (flex) · steps+notes (auto/capped) ── -->
|
||||||
<ScrollViewer>
|
<Grid RowDefinitions="Auto,*,Auto">
|
||||||
<StackPanel Spacing="0">
|
|
||||||
|
|
||||||
<!-- Agent strip -->
|
<!-- Agent strip -->
|
||||||
<islands:AgentStripView/>
|
<islands:AgentStripView Grid.Row="0"/>
|
||||||
|
|
||||||
<!-- Session terminal -->
|
<!-- Session terminal — fills remaining vertical space -->
|
||||||
<islands:SessionTerminalView Height="260" Margin="0"/>
|
<islands:SessionTerminalView Grid.Row="1" MinHeight="220" Margin="0,0,0,0"/>
|
||||||
|
|
||||||
<!-- Subtasks section -->
|
<!-- Steps + Notes in a capped scroller so they never squeeze the terminal -->
|
||||||
<StackPanel Margin="18,12,18,0"
|
<ScrollViewer Grid.Row="2" MaxHeight="240"
|
||||||
IsVisible="{Binding Subtasks.Count}">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<TextBlock Classes="section-label" Text="STEPS"
|
<StackPanel Spacing="0">
|
||||||
Margin="0,0,0,6"/>
|
|
||||||
<ItemsControl ItemsSource="{Binding Subtasks}">
|
<!-- Subtasks section -->
|
||||||
<ItemsControl.ItemTemplate>
|
<StackPanel Margin="18,12,18,0"
|
||||||
<DataTemplate DataType="vm:SubtaskRowViewModel">
|
IsVisible="{Binding Subtasks.Count}">
|
||||||
<Border Classes="subtask-row"
|
<TextBlock Classes="section-label" Text="STEPS"
|
||||||
Classes.done="{Binding Done}">
|
Margin="0,0,0,6"/>
|
||||||
<Grid ColumnDefinitions="Auto,*">
|
<ItemsControl ItemsSource="{Binding Subtasks}">
|
||||||
<!-- Ellipse checkbox -->
|
<ItemsControl.ItemTemplate>
|
||||||
<Ellipse Grid.Column="0"
|
<DataTemplate DataType="vm:SubtaskRowViewModel">
|
||||||
Classes="task-check"
|
<Border Classes="subtask-row"
|
||||||
Classes.done="{Binding Done}"
|
Classes.done="{Binding Done}">
|
||||||
Width="16" Height="16"
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
VerticalAlignment="Center"
|
<Ellipse Grid.Column="0"
|
||||||
Cursor="Hand"
|
Classes="task-check"
|
||||||
Margin="0,0,8,0"/>
|
Classes.done="{Binding Done}"
|
||||||
<TextBlock Grid.Column="1"
|
Width="16" Height="16"
|
||||||
Classes="subtask-title"
|
|
||||||
Text="{Binding Title}"
|
|
||||||
FontSize="13"
|
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
TextWrapping="Wrap"/>
|
Cursor="Hand"
|
||||||
</Grid>
|
Margin="0,0,8,0"/>
|
||||||
</Border>
|
<TextBlock Grid.Column="1"
|
||||||
</DataTemplate>
|
Classes="subtask-title"
|
||||||
</ItemsControl.ItemTemplate>
|
Text="{Binding Title}"
|
||||||
</ItemsControl>
|
FontSize="13"
|
||||||
</StackPanel>
|
Foreground="{DynamicResource TextDimBrush}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
TextWrapping="Wrap"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Notes section -->
|
<!-- Notes section -->
|
||||||
<StackPanel Margin="18,12,18,12">
|
<StackPanel Margin="18,12,18,12">
|
||||||
<TextBlock Classes="section-label" Text="NOTES" Margin="0,0,0,6"/>
|
<TextBlock Classes="section-label" Text="NOTES" Margin="0,0,0,6"/>
|
||||||
<TextBox Text="{Binding Notes, Mode=TwoWay}"
|
<TextBox Text="{Binding Notes, Mode=TwoWay}"
|
||||||
AcceptsReturn="True"
|
AcceptsReturn="True"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
MinHeight="80"
|
MinHeight="80"
|
||||||
Padding="12"
|
Padding="12"
|
||||||
PlaceholderText="Notes..."
|
PlaceholderText="Notes..."
|
||||||
Background="{DynamicResource Surface2Brush}"
|
Background="{DynamicResource Surface2Brush}"
|
||||||
BorderBrush="{DynamicResource LineBrush}"
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="8"
|
CornerRadius="8"
|
||||||
LostFocus="NotesLostFocus"/>
|
LostFocus="NotesLostFocus"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -15,10 +15,6 @@
|
|||||||
<!-- ── Header ── -->
|
<!-- ── Header ── -->
|
||||||
<Border DockPanel.Dock="Top" Classes="island-header">
|
<Border DockPanel.Dock="Top" Classes="island-header">
|
||||||
<StackPanel Margin="14,12,14,0" Spacing="4">
|
<StackPanel Margin="14,12,14,0" Spacing="4">
|
||||||
<TextBlock Classes="eyebrow"
|
|
||||||
Text="NAVIGATOR"
|
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="9"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}" LetterSpacing="1.2"/>
|
|
||||||
<TextBlock FontFamily="{DynamicResource SansFamily}" FontSize="18"
|
<TextBlock FontFamily="{DynamicResource SansFamily}" FontSize="18"
|
||||||
FontWeight="SemiBold" Foreground="{DynamicResource TextBrush}"
|
FontWeight="SemiBold" Foreground="{DynamicResource TextBrush}"
|
||||||
Text="Lists"/>
|
Text="Lists"/>
|
||||||
@@ -69,7 +65,9 @@
|
|||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<!-- More button -->
|
<!-- More button -->
|
||||||
<Button Grid.Column="2" Classes="icon-btn" VerticalAlignment="Center">
|
<Button Grid.Column="2" Classes="icon-btn" VerticalAlignment="Center"
|
||||||
|
Command="{Binding OpenSettingsCommand}"
|
||||||
|
ToolTip.Tip="Settings">
|
||||||
<PathIcon Data="{StaticResource Icon.MoreHorizontal}"
|
<PathIcon Data="{StaticResource Icon.MoreHorizontal}"
|
||||||
Width="14" Height="14"
|
Width="14" Height="14"
|
||||||
Foreground="{DynamicResource TextMuteBrush}"/>
|
Foreground="{DynamicResource TextMuteBrush}"/>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ClaudeDo.Ui.ViewModels.Islands;
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
|
using ClaudeDo.Ui.ViewModels.Modals;
|
||||||
|
using ClaudeDo.Ui.Views.Modals;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Views.Islands;
|
namespace ClaudeDo.Ui.Views.Islands;
|
||||||
|
|
||||||
@@ -12,7 +14,10 @@ public partial class ListsIslandView : UserControl
|
|||||||
DataContextChanged += (_, _) =>
|
DataContextChanged += (_, _) =>
|
||||||
{
|
{
|
||||||
if (DataContext is ListsIslandViewModel vm)
|
if (DataContext is ListsIslandViewModel vm)
|
||||||
|
{
|
||||||
vm.FocusSearchRequested += (_, _) => SearchBox.Focus();
|
vm.FocusSearchRequested += (_, _) => SearchBox.Focus();
|
||||||
|
vm.ShowSettingsModal = ShowSettingsAsync;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,4 +27,12 @@ public partial class ListsIslandView : UserControl
|
|||||||
&& DataContext is ListsIslandViewModel vm)
|
&& DataContext is ListsIslandViewModel vm)
|
||||||
vm.SelectCommand.Execute(item);
|
vm.SelectCommand.Execute(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async System.Threading.Tasks.Task ShowSettingsAsync(SettingsModalViewModel settingsVm)
|
||||||
|
{
|
||||||
|
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||||
|
if (owner == null) return;
|
||||||
|
var modal = new SettingsModalView { DataContext = settingsVm };
|
||||||
|
await modal.ShowDialog(owner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,6 @@
|
|||||||
<Grid DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto"
|
<Grid DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto"
|
||||||
Background="{DynamicResource Surface2Brush}"
|
Background="{DynamicResource Surface2Brush}"
|
||||||
Height="28">
|
Height="28">
|
||||||
<!-- Traffic-light dots -->
|
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4"
|
|
||||||
VerticalAlignment="Center" Margin="10,0,0,0">
|
|
||||||
<Ellipse Classes="dot-red"/>
|
|
||||||
<Ellipse Classes="dot-yellow"/>
|
|
||||||
<Ellipse Classes="dot-green"/>
|
|
||||||
</StackPanel>
|
|
||||||
<!-- Session label -->
|
<!-- Session label -->
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Text="{Binding BranchLine, StringFormat='claude-session · {0}'}"
|
Text="{Binding BranchLine, StringFormat='claude-session · {0}'}"
|
||||||
@@ -26,36 +19,34 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<!-- LIVE chip -->
|
<!-- LIVE chip -->
|
||||||
<Border Grid.Column="2" Classes="live-chip"
|
<Border Grid.Column="2" Classes="live-chip pulsing"
|
||||||
Classes.pulsing="{Binding IsRunning}"
|
IsVisible="{Binding IsRunning}"
|
||||||
Margin="0,0,8,0" VerticalAlignment="Center">
|
Margin="0,0,8,0" VerticalAlignment="Center">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
|
||||||
<Ellipse VerticalAlignment="Center"/>
|
<Ellipse VerticalAlignment="Center"/>
|
||||||
<TextBlock Text="LIVE" VerticalAlignment="Center"/>
|
<TextBlock Text="LIVE" VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
<!-- DONE chip -->
|
||||||
|
<Border Grid.Column="2" Classes="live-chip done"
|
||||||
<!-- ── Prompt input (docked bottom) ── -->
|
IsVisible="{Binding IsDone}"
|
||||||
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="Auto,*"
|
Margin="0,0,8,0" VerticalAlignment="Center">
|
||||||
Margin="0,0,0,0"
|
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
|
||||||
Background="{DynamicResource Surface2Brush}">
|
<Ellipse VerticalAlignment="Center" Fill="{DynamicResource MossBrush}"/>
|
||||||
<Border Grid.ColumnSpan="2"
|
<TextBlock Text="DONE" VerticalAlignment="Center"
|
||||||
BorderBrush="{DynamicResource LineBrush}"
|
Foreground="{DynamicResource MossBrush}"/>
|
||||||
BorderThickness="0,1,0,0"/>
|
</StackPanel>
|
||||||
<TextBlock Grid.Column="0" Text="›"
|
</Border>
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
<!-- FAILED chip -->
|
||||||
FontSize="11" Foreground="{DynamicResource MossBrush}"
|
<Border Grid.Column="2" Classes="live-chip failed"
|
||||||
VerticalAlignment="Center" Margin="10,0,8,0"/>
|
IsVisible="{Binding IsFailed}"
|
||||||
<TextBox Grid.Column="1"
|
Margin="0,0,8,0" VerticalAlignment="Center">
|
||||||
Text="{Binding PromptInput, Mode=TwoWay}"
|
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="11"
|
<Ellipse VerticalAlignment="Center" Fill="{DynamicResource BloodBrush}"/>
|
||||||
Background="Transparent" BorderThickness="0"
|
<TextBlock Text="FAILED" VerticalAlignment="Center"
|
||||||
Padding="0,6">
|
Foreground="{DynamicResource BloodBrush}"/>
|
||||||
<TextBox.KeyBindings>
|
</StackPanel>
|
||||||
<KeyBinding Gesture="Enter" Command="{Binding SendPromptCommand}"/>
|
</Border>
|
||||||
</TextBox.KeyBindings>
|
|
||||||
</TextBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- ── Log output ── -->
|
<!-- ── Log output ── -->
|
||||||
@@ -74,12 +65,12 @@
|
|||||||
Classes="log-kind"
|
Classes="log-kind"
|
||||||
Tag="{Binding ClassName}"
|
Tag="{Binding ClassName}"
|
||||||
Text="{Binding KindMarker}"/>
|
Text="{Binding KindMarker}"/>
|
||||||
<!-- Message text — inherits terminal TextBlock color from parent selector -->
|
<!-- Message text — selectable so the user can copy raw output -->
|
||||||
<TextBlock Grid.Column="2"
|
<SelectableTextBlock Grid.Column="2"
|
||||||
Text="{Binding Text}" Tag="{Binding ClassName}"
|
Text="{Binding Text}" Tag="{Binding ClassName}"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="11"
|
FontFamily="{DynamicResource MonoFont}" FontSize="11"
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
Foreground="{DynamicResource TextDimBrush}"
|
||||||
TextWrapping="Wrap"/>
|
TextWrapping="Wrap"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
IsVisible="{Binding IsSelected}"/>
|
IsVisible="{Binding IsSelected}"/>
|
||||||
|
|
||||||
<!-- Done toggle -->
|
<!-- Done toggle -->
|
||||||
<Button Grid.Column="1" Classes="check-btn" VerticalAlignment="Top"
|
<Button Grid.Column="1" Classes="flat" VerticalAlignment="Top"
|
||||||
Margin="0,2,0,0"
|
Margin="0,2,0,0"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleDoneCommand}"
|
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleDoneCommand}"
|
||||||
CommandParameter="{Binding}">
|
CommandParameter="{Binding}">
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
SystemDecorations="None"
|
SystemDecorations="None"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="Transparent"
|
Background="{StaticResource SurfaceBrush}">
|
||||||
TransparencyLevelHint="AcrylicBlur">
|
|
||||||
|
|
||||||
<Window.KeyBindings>
|
<Window.KeyBindings>
|
||||||
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||||
@@ -46,13 +45,10 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Window.Styles>
|
</Window.Styles>
|
||||||
|
|
||||||
<!-- Outer container -->
|
<!-- Outer container — rectangular so the OS window rectangle stays filled (no black corners) -->
|
||||||
<Border CornerRadius="{StaticResource ModalCornerRadius}"
|
<Border Background="{StaticResource SurfaceBrush}"
|
||||||
BoxShadow="{StaticResource ModalShadow}"
|
|
||||||
Background="{StaticResource SurfaceBrush}"
|
|
||||||
BorderBrush="{StaticResource LineBrush}"
|
BorderBrush="{StaticResource LineBrush}"
|
||||||
BorderThickness="1"
|
BorderThickness="1">
|
||||||
ClipToBounds="True">
|
|
||||||
<Grid RowDefinitions="36,*">
|
<Grid RowDefinitions="36,*">
|
||||||
|
|
||||||
<!-- Title bar / drag handle -->
|
<!-- Title bar / drag handle -->
|
||||||
|
|||||||
Reference in New Issue
Block a user