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:
@@ -16,6 +16,9 @@ public class ListEntityConfiguration : IEntityTypeConfiguration<ListEntity>
|
|||||||
builder.Property(l => l.CreatedAt).HasColumnName("created_at").IsRequired();
|
builder.Property(l => l.CreatedAt).HasColumnName("created_at").IsRequired();
|
||||||
builder.Property(l => l.WorkingDir).HasColumnName("working_dir");
|
builder.Property(l => l.WorkingDir).HasColumnName("working_dir");
|
||||||
builder.Property(l => l.DefaultCommitType).HasColumnName("default_commit_type").IsRequired().HasDefaultValue("chore");
|
builder.Property(l => l.DefaultCommitType).HasColumnName("default_commit_type").IsRequired().HasDefaultValue("chore");
|
||||||
|
builder.Property(l => l.SortOrder).HasColumnName("sort_order").IsRequired().HasDefaultValue(0);
|
||||||
|
|
||||||
|
builder.HasIndex(l => l.SortOrder).HasDatabaseName("idx_lists_sort");
|
||||||
|
|
||||||
builder.HasOne(l => l.Config)
|
builder.HasOne(l => l.Config)
|
||||||
.WithOne(c => c.List)
|
.WithOne(c => c.List)
|
||||||
|
|||||||
600
src/ClaudeDo.Data/Migrations/20260601114247_AddListSortOrder.Designer.cs
generated
Normal file
600
src/ClaudeDo.Data/Migrations/20260601114247_AddListSortOrder.Designer.cs
generated
Normal file
@@ -0,0 +1,600 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using ClaudeDo.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ClaudeDoDbContext))]
|
||||||
|
[Migration("20260601114247_AddListSortOrder")]
|
||||||
|
partial class AddListSortOrder
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.AppSettingsEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("CentralWorktreeRoot")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("central_worktree_root");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultClaudeInstructions")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("")
|
||||||
|
.HasColumnName("default_claude_instructions");
|
||||||
|
|
||||||
|
b.Property<int>("DefaultMaxTurns")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(30)
|
||||||
|
.HasColumnName("default_max_turns");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultModel")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sonnet")
|
||||||
|
.HasColumnName("default_model");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultPermissionMode")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("bypassPermissions")
|
||||||
|
.HasColumnName("default_permission_mode");
|
||||||
|
|
||||||
|
b.Property<string>("RepoImportFolders")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("repo_import_folders");
|
||||||
|
|
||||||
|
b.Property<int>("WorktreeAutoCleanupDays")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(7)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_days");
|
||||||
|
|
||||||
|
b.Property<bool>("WorktreeAutoCleanupEnabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("worktree_auto_cleanup_enabled");
|
||||||
|
|
||||||
|
b.Property<string>("WorktreeStrategy")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("sibling")
|
||||||
|
.HasColumnName("worktree_strategy");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("app_settings", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
DefaultClaudeInstructions = "",
|
||||||
|
DefaultMaxTurns = 100,
|
||||||
|
DefaultModel = "sonnet",
|
||||||
|
DefaultPermissionMode = "auto",
|
||||||
|
WorktreeAutoCleanupDays = 7,
|
||||||
|
WorktreeAutoCleanupEnabled = false,
|
||||||
|
WorktreeStrategy = "sibling"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.HasKey("ListId");
|
||||||
|
|
||||||
|
b.ToTable("list_config", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultCommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("default_commit_type");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SortOrder")
|
||||||
|
.HasDatabaseName("idx_lists_sort");
|
||||||
|
|
||||||
|
b.ToTable("lists", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.PrimeScheduleEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("enabled");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("EndDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("end_date");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LastRunAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last_run_at");
|
||||||
|
|
||||||
|
b.Property<string>("PromptOverride")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt_override");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("StartDate")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("start_date");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("TimeOfDay")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("time_of_day");
|
||||||
|
|
||||||
|
b.Property<bool>("WorkdaysOnly")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("workdays_only");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("prime_schedules", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("Completed")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("completed");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("OrderNum")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("order_num");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_subtasks_task_id");
|
||||||
|
|
||||||
|
b.ToTable("subtasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AgentPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("agent_path");
|
||||||
|
|
||||||
|
b.Property<string>("BlockedByTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("blocked_by_task_id");
|
||||||
|
|
||||||
|
b.Property<string>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_by");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMyDay")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_my_day");
|
||||||
|
|
||||||
|
b.Property<bool>("IsStarred")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_starred");
|
||||||
|
|
||||||
|
b.Property<string>("ListId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("list_id");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("model");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("notes");
|
||||||
|
|
||||||
|
b.Property<string>("ParentTaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("parent_task_id");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("PlanningFinalizedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_finalized_at");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningPhase")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("none")
|
||||||
|
.HasColumnName("planning_phase");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_id");
|
||||||
|
|
||||||
|
b.Property<string>("PlanningSessionToken")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("planning_session_token");
|
||||||
|
|
||||||
|
b.Property<string>("Result")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("status");
|
||||||
|
|
||||||
|
b.Property<string>("SystemPrompt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("system_prompt");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockedByTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_blocked_by");
|
||||||
|
|
||||||
|
b.HasIndex("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
|
b.HasIndex("ParentTaskId")
|
||||||
|
.HasDatabaseName("idx_tasks_parent_task_id");
|
||||||
|
|
||||||
|
b.HasIndex("Status")
|
||||||
|
.HasDatabaseName("idx_tasks_status");
|
||||||
|
|
||||||
|
b.HasIndex("ListId", "SortOrder")
|
||||||
|
.HasDatabaseName("idx_tasks_list_sort");
|
||||||
|
|
||||||
|
b.ToTable("tasks", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("ErrorMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("error_markdown");
|
||||||
|
|
||||||
|
b.Property<int?>("ExitCode")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("exit_code");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
b.Property<bool>("IsRetry")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_retry");
|
||||||
|
|
||||||
|
b.Property<string>("LogPath")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("log_path");
|
||||||
|
|
||||||
|
b.Property<string>("Prompt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt");
|
||||||
|
|
||||||
|
b.Property<string>("ResultMarkdown")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result_markdown");
|
||||||
|
|
||||||
|
b.Property<int>("RunNumber")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("run_number");
|
||||||
|
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("session_id");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("StructuredOutputJson")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("structured_output");
|
||||||
|
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensIn")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_in");
|
||||||
|
|
||||||
|
b.Property<int?>("TokensOut")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("tokens_out");
|
||||||
|
|
||||||
|
b.Property<int?>("TurnCount")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("turn_count");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TaskId")
|
||||||
|
.HasDatabaseName("idx_task_runs_task_id");
|
||||||
|
|
||||||
|
b.ToTable("task_runs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("TaskId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("task_id");
|
||||||
|
|
||||||
|
b.Property<string>("BaseCommit")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("base_commit");
|
||||||
|
|
||||||
|
b.Property<string>("BranchName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("branch_name");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("DiffStat")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("diff_stat");
|
||||||
|
|
||||||
|
b.Property<string>("HeadCommit")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("head_commit");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("active")
|
||||||
|
.HasColumnName("state");
|
||||||
|
|
||||||
|
b.HasKey("TaskId");
|
||||||
|
|
||||||
|
b.ToTable("worktrees", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListConfigEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithOne("Config")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.ListConfigEntity", "ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.SubtaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Subtasks")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BlockedByTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Parent")
|
||||||
|
.WithMany("Children")
|
||||||
|
.HasForeignKey("ParentTaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
|
||||||
|
b.Navigation("Parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskRunEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithMany("Runs")
|
||||||
|
.HasForeignKey("TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.WorktreeEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", "Task")
|
||||||
|
.WithOne("Worktree")
|
||||||
|
.HasForeignKey("ClaudeDo.Data.Models.WorktreeEntity", "TaskId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Children");
|
||||||
|
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddListSortOrder : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "sort_order",
|
||||||
|
table: "lists",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
// Backfill existing rows with a dense order (0..N-1) by creation time
|
||||||
|
// so today's sidebar order is preserved after the migration.
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
WITH ordered AS (
|
||||||
|
SELECT id, (row_number() OVER (ORDER BY created_at) - 1) AS rn
|
||||||
|
FROM lists
|
||||||
|
)
|
||||||
|
UPDATE lists SET sort_order = (SELECT rn FROM ordered WHERE ordered.id = lists.id);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx_lists_sort",
|
||||||
|
table: "lists",
|
||||||
|
column: "sort_order");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx_lists_sort",
|
||||||
|
table: "lists");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "sort_order",
|
||||||
|
table: "lists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,12 +140,21 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("name");
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(0)
|
||||||
|
.HasColumnName("sort_order");
|
||||||
|
|
||||||
b.Property<string>("WorkingDir")
|
b.Property<string>("WorkingDir")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("working_dir");
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SortOrder")
|
||||||
|
.HasDatabaseName("idx_lists_sort");
|
||||||
|
|
||||||
b.ToTable("lists", (string)null);
|
b.ToTable("lists", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public sealed class ListEntity
|
|||||||
public required DateTime CreatedAt { get; init; }
|
public required DateTime CreatedAt { get; init; }
|
||||||
public string? WorkingDir { get; set; }
|
public string? WorkingDir { get; set; }
|
||||||
public string DefaultCommitType { get; set; } = CommitTypeRegistry.DefaultType;
|
public string DefaultCommitType { get; set; } = CommitTypeRegistry.DefaultType;
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
|
||||||
// Navigation properties
|
// Navigation properties
|
||||||
public ListConfigEntity? Config { get; set; }
|
public ListConfigEntity? Config { get; set; }
|
||||||
|
|||||||
@@ -33,7 +33,19 @@ public sealed class ListRepository
|
|||||||
|
|
||||||
public async Task<List<ListEntity>> GetAllAsync(CancellationToken ct = default)
|
public async Task<List<ListEntity>> GetAllAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await _context.Lists.OrderBy(l => l.CreatedAt).ToListAsync(ct);
|
return await _context.Lists.OrderBy(l => l.SortOrder).ThenBy(l => l.CreatedAt).ToListAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReorderAsync(IReadOnlyList<string> orderedListIds, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var idSet = orderedListIds.ToHashSet();
|
||||||
|
var entities = await _context.Lists.Where(l => idSet.Contains(l.Id)).ToListAsync(ct);
|
||||||
|
for (int i = 0; i < orderedListIds.Count; i++)
|
||||||
|
{
|
||||||
|
var e = entities.FirstOrDefault(x => x.Id == orderedListIds[i]);
|
||||||
|
if (e is not null) e.SortOrder = i;
|
||||||
|
}
|
||||||
|
await _context.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ListConfigEntity?> GetConfigAsync(string listId, CancellationToken ct = default)
|
public async Task<ListConfigEntity?> GetConfigAsync(string listId, CancellationToken ct = default)
|
||||||
|
|||||||
@@ -826,6 +826,15 @@
|
|||||||
<Setter Property="Opacity" Value="0.5" />
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
<Setter Property="TextDecorations" Value="Strikethrough" />
|
<Setter Property="TextDecorations" Value="Strikethrough" />
|
||||||
</Style>
|
</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) -->
|
<!-- SECTION LABELS (OVERDUE / TASKS / COMPLETED) -->
|
||||||
|
|||||||
@@ -840,6 +840,35 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
|||||||
CloseDetail?.Invoke();
|
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]
|
[RelayCommand]
|
||||||
private async System.Threading.Tasks.Task AddSubtaskAsync()
|
private async System.Threading.Tasks.Task AddSubtaskAsync()
|
||||||
{
|
{
|
||||||
@@ -943,6 +972,7 @@ public sealed partial class SubtaskRowViewModel : ViewModelBase
|
|||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
[ObservableProperty] private string _title = "";
|
[ObservableProperty] private string _title = "";
|
||||||
[ObservableProperty] private bool _done;
|
[ObservableProperty] private bool _done;
|
||||||
|
[ObservableProperty] private bool _isEditing;
|
||||||
[ObservableProperty] private ClaudeDo.Data.Models.TaskStatus _status;
|
[ObservableProperty] private ClaudeDo.Data.Models.TaskStatus _status;
|
||||||
[ObservableProperty] private ClaudeDo.Data.Models.WorktreeState _worktreeState = ClaudeDo.Data.Models.WorktreeState.Active;
|
[ObservableProperty] private ClaudeDo.Data.Models.WorktreeState _worktreeState = ClaudeDo.Data.Models.WorktreeState.Active;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public sealed partial class ListNavItemViewModel : ViewModelBase
|
|||||||
[ObservableProperty] private bool _isActive;
|
[ObservableProperty] private bool _isActive;
|
||||||
[ObservableProperty] private string? _workingDir;
|
[ObservableProperty] private string? _workingDir;
|
||||||
[ObservableProperty] private string _defaultCommitType = CommitTypeRegistry.DefaultType;
|
[ObservableProperty] private string _defaultCommitType = CommitTypeRegistry.DefaultType;
|
||||||
|
[ObservableProperty] private bool _dropHintAbove;
|
||||||
|
[ObservableProperty] private bool _dropHintBelow;
|
||||||
public string? IconKey { get; init; }
|
public string? IconKey { get; init; }
|
||||||
public string? DotColorKey { get; init; }
|
public string? DotColorKey { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,52 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
|
|||||||
finally { _worktreesOverviewOpen = false; }
|
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> 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();
|
||||||
@@ -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)
|
partial void OnSelectedListChanged(ListNavItemViewModel? value)
|
||||||
{
|
{
|
||||||
foreach (var i in Items) i.IsActive = ReferenceEquals(i, value);
|
foreach (var i in Items) i.IsActive = ReferenceEquals(i, value);
|
||||||
|
|||||||
@@ -487,6 +487,38 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
|||||||
TasksChanged?.Invoke(this, EventArgs.Empty);
|
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]
|
[RelayCommand]
|
||||||
private async Task ToggleStarAsync(TaskRowViewModel row)
|
private async Task ToggleStarAsync(TaskRowViewModel row)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,7 +50,10 @@
|
|||||||
<StackPanel Grid.Column="1" Spacing="0">
|
<StackPanel Grid.Column="1" Spacing="0">
|
||||||
<TextBlock Classes="meta"
|
<TextBlock Classes="meta"
|
||||||
Text="{Binding TaskIdBadge}"
|
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}"
|
<TextBox Text="{Binding EditableTitle, Mode=TwoWay}"
|
||||||
FontSize="{StaticResource FontSizeTaskTitle}" FontWeight="Medium"
|
FontSize="{StaticResource FontSizeTaskTitle}" FontWeight="Medium"
|
||||||
BorderThickness="0" Background="Transparent"
|
BorderThickness="0" Background="Transparent"
|
||||||
@@ -186,13 +189,30 @@
|
|||||||
Width="16" Height="16"
|
Width="16" Height="16"
|
||||||
Cursor="Hand"/>
|
Cursor="Hand"/>
|
||||||
</Button>
|
</Button>
|
||||||
<TextBlock Grid.Column="1"
|
<Panel Grid.Column="1" VerticalAlignment="Center">
|
||||||
Classes="subtask-title"
|
<TextBlock Classes="subtask-title"
|
||||||
Text="{Binding Title}"
|
Text="{Binding Title}"
|
||||||
|
IsVisible="{Binding !IsEditing}"
|
||||||
FontSize="{StaticResource FontSizeBody}"
|
FontSize="{StaticResource FontSizeBody}"
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
Foreground="{DynamicResource TextDimBrush}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
TextWrapping="Wrap"/>
|
TextWrapping="Wrap"
|
||||||
|
Cursor="Ibeam"
|
||||||
|
Tapped="OnSubtaskTitleTapped"/>
|
||||||
|
<TextBox Classes="subtask-edit"
|
||||||
|
Text="{Binding Title, Mode=TwoWay}"
|
||||||
|
IsVisible="{Binding IsEditing}"
|
||||||
|
FontSize="{StaticResource FontSizeBody}"
|
||||||
|
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>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
using System.Linq;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Input.Platform;
|
using Avalonia.Input.Platform;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
using ClaudeDo.Ui.ViewModels.Islands;
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
using ClaudeDo.Ui.Views.Modals;
|
using ClaudeDo.Ui.Views.Modals;
|
||||||
using ClaudeDo.Ui.Views.Planning;
|
using ClaudeDo.Ui.Views.Planning;
|
||||||
@@ -135,6 +139,31 @@ public partial class DetailsIslandView : UserControl
|
|||||||
return await tcs.Task;
|
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)
|
private async void OnCopyDescriptionClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not DetailsIslandViewModel vm) return;
|
if (DataContext is not DetailsIslandViewModel vm) return;
|
||||||
|
|||||||
@@ -113,8 +113,18 @@
|
|||||||
<ItemsControl ItemsSource="{Binding UserLists}">
|
<ItemsControl ItemsSource="{Binding UserLists}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate DataType="vm:ListNavItemViewModel">
|
<DataTemplate DataType="vm:ListNavItemViewModel">
|
||||||
<Border Classes="list-item" Classes.active="{Binding IsActive}"
|
<Grid RowDefinitions="Auto,Auto,Auto">
|
||||||
Tapped="OnItemTapped">
|
|
||||||
|
<!-- 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>
|
<Border.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="Settings..."
|
<MenuItem Header="Settings..."
|
||||||
@@ -123,6 +133,15 @@
|
|||||||
<MenuItem Header="Worktrees…"
|
<MenuItem Header="Worktrees…"
|
||||||
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenWorktreesOverviewCommand}"
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenWorktreesOverviewCommand}"
|
||||||
CommandParameter="{Binding}"/>
|
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>
|
</ContextMenu>
|
||||||
</Border.ContextMenu>
|
</Border.ContextMenu>
|
||||||
<Grid ColumnDefinitions="20,*,Auto">
|
<Grid ColumnDefinitions="20,*,Auto">
|
||||||
@@ -152,6 +171,12 @@
|
|||||||
Text="{Binding Count}"/>
|
Text="{Binding Count}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</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>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
using ClaudeDo.Ui.ViewModels;
|
using ClaudeDo.Ui.ViewModels;
|
||||||
using ClaudeDo.Ui.ViewModels.Islands;
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
using ClaudeDo.Ui.ViewModels.Modals;
|
using ClaudeDo.Ui.ViewModels.Modals;
|
||||||
@@ -13,9 +15,13 @@ namespace ClaudeDo.Ui.Views.Islands;
|
|||||||
|
|
||||||
public partial class ListsIslandView : UserControl
|
public partial class ListsIslandView : UserControl
|
||||||
{
|
{
|
||||||
|
private static readonly DataFormat<string> ListRowFormat =
|
||||||
|
DataFormat.CreateStringApplicationFormat("claudedo-list-row");
|
||||||
|
|
||||||
public ListsIslandView()
|
public ListsIslandView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
AddHandler(PointerPressedEvent, OnTunnelPointerPressed, RoutingStrategies.Tunnel);
|
||||||
DataContextChanged += (_, _) =>
|
DataContextChanged += (_, _) =>
|
||||||
{
|
{
|
||||||
if (DataContext is ListsIslandViewModel vm)
|
if (DataContext is ListsIslandViewModel vm)
|
||||||
@@ -84,6 +90,127 @@ public partial class ListsIslandView : UserControl
|
|||||||
vm.SelectCommand.Execute(item);
|
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)
|
private async System.Threading.Tasks.Task ShowSettingsAsync(SettingsModalViewModel settingsVm)
|
||||||
{
|
{
|
||||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||||
|
|||||||
@@ -126,8 +126,17 @@
|
|||||||
<Binding Path="IsShowingCompleted"/>
|
<Binding Path="IsShowingCompleted"/>
|
||||||
</MultiBinding>
|
</MultiBinding>
|
||||||
</StackPanel.IsVisible>
|
</StackPanel.IsVisible>
|
||||||
<TextBlock Classes="eyebrow section-label"
|
<Grid ColumnDefinitions="*,Auto" Margin="14,14,14,6">
|
||||||
Text="{Binding CompletedHeader}" 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 ItemsSource="{Binding CompletedItems}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate DataType="vm:TaskRowViewModel">
|
<DataTemplate DataType="vm:TaskRowViewModel">
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
x:Class="ClaudeDo.Ui.Views.Modals.DiffModalView"
|
x:Class="ClaudeDo.Ui.Views.Modals.DiffModalView"
|
||||||
x:DataType="vm:DiffModalViewModel"
|
x:DataType="vm:DiffModalViewModel"
|
||||||
Title="Diff"
|
Title="Diff"
|
||||||
Width="1200" Height="800"
|
Width="1200" Height="800" MinWidth="700" MinHeight="450"
|
||||||
WindowDecorations="None"
|
CanResize="True"
|
||||||
|
WindowDecorations="BorderOnly"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="{DynamicResource SurfaceBrush}">
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
Width="520" Height="720"
|
Width="520" Height="720"
|
||||||
CanResize="True"
|
CanResize="True"
|
||||||
MinWidth="460" MinHeight="520"
|
MinWidth="460" MinHeight="520"
|
||||||
WindowDecorations="None"
|
WindowDecorations="BorderOnly"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="{DynamicResource SurfaceBrush}">
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
x:Class="ClaudeDo.Ui.Views.Modals.MergeModalView"
|
x:Class="ClaudeDo.Ui.Views.Modals.MergeModalView"
|
||||||
x:DataType="vm:MergeModalViewModel"
|
x:DataType="vm:MergeModalViewModel"
|
||||||
Title="Merge worktree"
|
Title="Merge worktree"
|
||||||
Width="560" Height="460"
|
Width="560" Height="460" MinWidth="460" MinHeight="360"
|
||||||
CanResize="False"
|
CanResize="True"
|
||||||
WindowDecorations="None"
|
WindowDecorations="BorderOnly"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="{DynamicResource SurfaceBrush}">
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
x:Class="ClaudeDo.Ui.Views.Modals.RepoImportModalView"
|
x:Class="ClaudeDo.Ui.Views.Modals.RepoImportModalView"
|
||||||
x:DataType="vm:RepoImportModalViewModel"
|
x:DataType="vm:RepoImportModalViewModel"
|
||||||
Title="Add repos as lists"
|
Title="Add repos as lists"
|
||||||
Width="560" Height="480"
|
Width="560" Height="480" MinWidth="420" MinHeight="320"
|
||||||
WindowDecorations="None"
|
CanResize="True"
|
||||||
|
WindowDecorations="BorderOnly"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="{DynamicResource SurfaceBrush}">
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
<Window.KeyBindings>
|
<Window.KeyBindings>
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
x:Class="ClaudeDo.Ui.Views.Modals.SettingsModalView"
|
x:Class="ClaudeDo.Ui.Views.Modals.SettingsModalView"
|
||||||
x:DataType="vm:SettingsModalViewModel"
|
x:DataType="vm:SettingsModalViewModel"
|
||||||
Title="Settings"
|
Title="Settings"
|
||||||
Width="580" Height="760"
|
Width="580" Height="760" MinWidth="480" MinHeight="520"
|
||||||
WindowDecorations="None"
|
CanResize="True"
|
||||||
|
WindowDecorations="BorderOnly"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="{DynamicResource SurfaceBrush}">
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="{DynamicResource SurfaceBrush}"
|
Background="{DynamicResource SurfaceBrush}"
|
||||||
WindowDecorations="BorderOnly"
|
WindowDecorations="BorderOnly"
|
||||||
ExtendClientAreaToDecorationsHint="True">
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1">
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<converters:WorktreeStateColorConverter x:Key="WorktreeStateColor"/>
|
<converters:WorktreeStateColorConverter x:Key="WorktreeStateColor"/>
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
x:DataType="vm:ConflictResolutionViewModel"
|
x:DataType="vm:ConflictResolutionViewModel"
|
||||||
x:Class="ClaudeDo.Ui.Views.Planning.ConflictResolutionView"
|
x:Class="ClaudeDo.Ui.Views.Planning.ConflictResolutionView"
|
||||||
Title="Merge conflict"
|
Title="Merge conflict"
|
||||||
Width="560" SizeToContent="Height"
|
Width="560" SizeToContent="Height" MinWidth="460"
|
||||||
WindowDecorations="None"
|
CanResize="True"
|
||||||
|
WindowDecorations="BorderOnly"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="{DynamicResource SurfaceBrush}">
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
x:Class="ClaudeDo.Ui.Views.Planning.PlanningDiffView"
|
x:Class="ClaudeDo.Ui.Views.Planning.PlanningDiffView"
|
||||||
x:DataType="vm:PlanningDiffViewModel"
|
x:DataType="vm:PlanningDiffViewModel"
|
||||||
Title="Planning — Combined diff"
|
Title="Planning — Combined diff"
|
||||||
Width="1100" Height="700"
|
Width="1100" Height="700" MinWidth="700" MinHeight="450"
|
||||||
WindowDecorations="None"
|
CanResize="True"
|
||||||
|
WindowDecorations="BorderOnly"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Background="{DynamicResource SurfaceBrush}">
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user