feat(lists): selectable list/folder colors with accent-aware swatches and custom picker
This commit is contained in:
@@ -12,7 +12,8 @@ Shared data layer: models, repositories, SQLite infrastructure, and git operatio
|
||||
- `InteractiveSessionId` = the claude session id an embedded ConPTY interactive task session runs under, persisted by `InteractiveLaunchSpecService` before launch so a closed/aborted session can be resumed → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md).
|
||||
- `Number` (INTEGER NOT NULL, unique index `idx_tasks_number`) — a global, monotonically increasing display alias for tasks. **Never reused**: deleting a task leaves a gap in numbering, ensuring that a task number that appears in a log or report always points to the same task (if it exists). Allocated by `TaskNumberAllocator.AddWithNumberAsync` on every insert via a persistent counter (`app_settings.next_task_number`). **Why not `MAX(number) + 1`:** deleting the highest-numbered task would free its number for reuse, breaking the immutability guarantee. The allocator uses an `UPDATE…RETURNING` statement to claim numbers atomically, retrying up to 5 times if a uniqueness collision occurs (the counter advances on each attempt, so retries always allocate fresh numbers). The one-time backfill for pre-existing rows lived in the `AddTaskNumbers` migration, squashed away 2026-08-26.
|
||||
- Legacy status values `Manual`/`Planning`/`Planned`/`Draft`/`Waiting` were retired. The backfill migration was squashed away (2026-08-26) — a database still holding those values can't be upgraded, see Schema below.
|
||||
- **ListEntity** — Id, Name, WorkingDir, DefaultCommitType, CreatedAt, IsManual (reminder list — tasks created here default to `IsManual`), Icon, FolderId
|
||||
- **ListEntity** — Id, Name, WorkingDir, DefaultCommitType, CreatedAt, IsManual (reminder list — tasks created here default to `IsManual`), Icon, Color, FolderId
|
||||
- `Color` (`color`, nullable) — dot colour: a swatch key (`Swatch3`), a literal `#RRGGBB`, or null = the hash-derived identity colour. Same value format as `ListFolderEntity.Color`; resolved UI-side by `ColorBrushConverter`.
|
||||
- `FolderId` (`folder_id`, nullable FK → `list_folders.id`, `ON DELETE SET NULL`) — the sidebar folder the list sits in; null = top level. Written only by the UI (`ListsIslandViewModel` drag/context menu), never by MCP.
|
||||
- **ListFolderEntity** — Id, Name, Icon, SortOrder, IsCollapsed, ParentFolderId, CreatedAt → `list_folders`. A pure sidebar grouping: no tasks, no config, no working dir. `ParentFolderId` (self-FK, `ON DELETE SET NULL`) lets folders nest arbitrarily deep — drag-and-drop only, no UI to pick a parent otherwise. Deliberately its own table rather than a flag on `lists`, so every caller that enumerates lists (MCP `list_task_lists`, "move to list", the queue picker, repo import) needs no folder filtering. Folder CRUD lives on `ListRepository`.
|
||||
- **ListConfigEntity** — ListId (PK, 1:1), Model, SystemPrompt, AgentPath, MaxTurns, SessionSkills, VerifyCommand, PermissionMode, TicketProjectId (all nullable). `VerifyCommand` is an optional post-merge gate; null/blank = no gate → [review-merge](../../docs/explore-notes/review-merge.md). `PermissionMode` is null = inherit the global default; resolution order task → list → global lives in `EffectiveRunConfigResolver`. `TicketProjectId` (`ticket_project_id`) links the list to a project in the external ticket system; null = not linked → `ClaudeDo.Worker/CLAUDE.md` Tickets section.
|
||||
|
||||
@@ -20,6 +20,7 @@ public class ListEntityConfiguration : IEntityTypeConfiguration<ListEntity>
|
||||
builder.Property(l => l.IsManual).HasColumnName("is_manual").IsRequired().HasDefaultValue(false);
|
||||
builder.Property(l => l.FindingsTracked).HasColumnName("findings_tracked").IsRequired().HasDefaultValue(false);
|
||||
builder.Property(l => l.Icon).HasColumnName("icon");
|
||||
builder.Property(l => l.Color).HasColumnName("color");
|
||||
builder.Property(l => l.FolderId).HasColumnName("folder_id");
|
||||
|
||||
builder.HasIndex(l => l.SortOrder).HasDatabaseName("idx_lists_sort");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ClaudeDo.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddListColor : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "color",
|
||||
table: "lists",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "color",
|
||||
table: "lists");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,6 +311,10 @@ namespace ClaudeDo.Data.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("color");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
@@ -16,6 +16,8 @@ public sealed class ListEntity
|
||||
public bool FindingsTracked { get; set; }
|
||||
// Icon key into the UI's icon library (e.g. "Star"); null = the default folder icon.
|
||||
public string? Icon { get; set; }
|
||||
// Dot colour: a swatch key ("Swatch3") or a literal "#RRGGBB"; null = the hash-derived default.
|
||||
public string? Color { get; set; }
|
||||
// Sidebar folder this list sits in (ListFolderEntity.Id); null = top level. ON DELETE SET NULL,
|
||||
// so deleting a folder moves its lists back to the top level instead of deleting them.
|
||||
public string? FolderId { get; set; }
|
||||
|
||||
@@ -101,7 +101,7 @@ public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList<Merg
|
||||
|
||||
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
|
||||
|
||||
public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType, bool IsManual = false, bool FindingsTracked = false, string? Icon = null);
|
||||
public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType, bool IsManual = false, bool FindingsTracked = false, string? Icon = null, string? Color = null);
|
||||
|
||||
// TicketProjectId is tri-state on purpose: a caller that doesn't own the field (anything but the
|
||||
// list-settings modal) must not be able to clear it by omission — SetConfigAsync copies the entity
|
||||
|
||||
Reference in New Issue
Block a user