Compare commits
131 Commits
v1.3.0
...
869cf72abe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
869cf72abe | ||
|
|
f1715a34fa | ||
|
|
26998f05ff | ||
|
|
7db8f213d8 | ||
|
|
37738e3c8f | ||
|
|
81fd186fb2 | ||
|
|
3127930454 | ||
|
|
bed4255a5e | ||
|
|
dff06d9e35 | ||
|
|
0efad7a004 | ||
|
|
eaf27e8b3a | ||
|
|
13c3393e3a | ||
|
|
4704a28e5d | ||
|
|
1cb5171fba | ||
|
|
4684a0af76 | ||
|
|
6c27ffbdca | ||
|
|
21f1cf2a85 | ||
|
|
c88ed9d5eb | ||
|
|
9c1f20f2d9 | ||
|
|
e8d018dd54 | ||
|
|
1ca32a6bdd | ||
|
|
b86677d554 | ||
|
|
3e072fae66 | ||
|
|
4a36fbe5e0 | ||
|
|
9e5a3fe962 | ||
|
|
3f98fd0ae5 | ||
|
|
8420b87bd1 | ||
|
|
c0978df19a | ||
|
|
3ac9e030e2 | ||
|
|
4c6e6594dc | ||
|
|
5170914a7a | ||
|
|
b1f4349dab | ||
|
|
23326a1833 | ||
|
|
ca0594328a | ||
|
|
22d06acb35 | ||
|
|
ab44ba5e41 | ||
|
|
6c3afce329 | ||
|
|
f8e387bbc1 | ||
|
|
2a36998ac7 | ||
|
|
4148dcdb18 | ||
|
|
5783790733 | ||
|
|
edfb702ecc | ||
|
|
549b87bb74 | ||
|
|
400a078aec | ||
|
|
5baa1d7fbb | ||
|
|
1246bf7b88 | ||
|
|
00dc7ebccc | ||
|
|
0139607008 | ||
|
|
4ecd855fb1 | ||
|
|
759d9057ff | ||
|
|
2f1dcdc102 | ||
|
|
133f2d2f1d | ||
|
|
e2bb43ad6d | ||
|
|
867dc37228 | ||
|
|
4963a726de | ||
|
|
926471da6b | ||
|
|
9be8e6b3e0 | ||
|
|
b9e5dfccde | ||
|
|
c669370ecf | ||
|
|
4688e884bd | ||
|
|
8b21b0e646 | ||
|
|
4a786eb732 | ||
|
|
cd64f287c3 | ||
|
|
3585ad5ee2 | ||
|
|
990935e67d | ||
|
|
1b5a9285e6 | ||
|
|
e8f880e72f | ||
|
|
3228a08c7a | ||
|
|
ccec791fc1 | ||
|
|
187fb641fe | ||
|
|
0a719568ea | ||
|
|
ccec591ba2 | ||
|
|
a4cb03b1b5 | ||
|
|
f53292e134 | ||
|
|
539ebecf3a | ||
|
|
dff5651db7 | ||
|
|
9f49b0131f | ||
|
|
fb3a6acf52 | ||
|
|
4f84b15b6a | ||
|
|
27b0d51db0 | ||
|
|
2a381048fe | ||
|
|
bddef5abef | ||
|
|
51d3ea2e1c | ||
|
|
335b422e23 | ||
|
|
08f3babca4 | ||
|
|
9082f2ed71 | ||
|
|
0f64b1c6e0 | ||
|
|
dd453874ba | ||
|
|
00e1d2d6c9 | ||
|
|
9a9113542d | ||
|
|
8e595a1e43 | ||
|
|
97fc715856 | ||
|
|
ed8607d4c9 | ||
|
|
929e0ca1ee | ||
|
|
40a36308ae | ||
|
|
b9f5d829c8 | ||
|
|
e0dda3e71b | ||
|
|
d4c66dea63 | ||
|
|
a132127e9e | ||
|
|
6e3125e78d | ||
|
|
b00e4d994f | ||
|
|
16717ab9e9 | ||
|
|
7af892f410 | ||
|
|
e86464e802 | ||
|
|
df7337810e | ||
|
|
8944074997 | ||
|
|
fbd5d9f7ca | ||
|
|
5fdd9f0b4c | ||
|
|
bce4e0a1e6 | ||
|
|
229f865e7e | ||
|
|
a444033aa9 | ||
|
|
2265829a29 | ||
|
|
50e05b9140 | ||
|
|
538839c004 | ||
|
|
8d07fc298c | ||
|
|
e1bfbb0fa6 | ||
|
|
b1006ac7b0 | ||
|
|
4f5db367a7 | ||
|
|
c20fbe3613 | ||
|
|
16b0d1177a | ||
|
|
a1f05da97b | ||
|
|
0c0c73bc9e | ||
|
|
3d4a64a8fd | ||
|
|
bff15c9bf3 | ||
|
|
f40de4bbe0 | ||
|
|
e120b0fd70 | ||
|
|
e8ce725897 | ||
|
|
7a6bfbe1b4 | ||
|
|
5a25818e3a | ||
|
|
f0f8cd103d | ||
|
|
d52f23f7c8 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -45,6 +45,8 @@ artifacts/
|
|||||||
|
|
||||||
# Avalonia / XAML designer
|
# Avalonia / XAML designer
|
||||||
*.designer.cs
|
*.designer.cs
|
||||||
|
# ...but EF Core migration Designer files are real source and must be tracked
|
||||||
|
!**/Migrations/*.Designer.cs
|
||||||
|
|
||||||
# Project-specific
|
# Project-specific
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Two-process system communicating over SignalR (`127.0.0.1:47821`):
|
|||||||
- EF Core migrations manage schema (Migrations/ folder in ClaudeDo.Data)
|
- EF Core migrations manage schema (Migrations/ folder in ClaudeDo.Data)
|
||||||
- `IDbContextFactory<ClaudeDoDbContext>` used by singleton consumers (e.g. Worker)
|
- `IDbContextFactory<ClaudeDoDbContext>` used by singleton consumers (e.g. Worker)
|
||||||
- Entity configuration via `IEntityTypeConfiguration<T>` in Configuration/ folder
|
- Entity configuration via `IEntityTypeConfiguration<T>` in Configuration/ folder
|
||||||
- Task status flow: Idle | Queued -> Running -> Done | Failed | Cancelled
|
- Task status flow: Idle | Queued -> Running -> WaitingForReview -> Done | Failed | Cancelled. A standalone task's successful run lands in WaitingForReview (planning children go straight to Done); from review you can approve (Done), reject-rerun (Queued, resumes the session with feedback), reject-park (Idle), or cancel (Cancelled).
|
||||||
- Worktree state flow: Active -> Merged | Discarded | Kept
|
- Worktree state flow: Active -> Merged | Discarded | Kept
|
||||||
- The queue picker claims tasks by `Status=Queued` (with `BlockedByTaskId IS NULL`); the legacy tag system was removed
|
- The queue picker claims tasks by `Status=Queued` (with `BlockedByTaskId IS NULL`); the legacy tag system was removed
|
||||||
- Interfaces live in an `Interfaces/` subfolder beside their consumers (namespace unchanged)
|
- Interfaces live in an `Interfaces/` subfolder beside their consumers (namespace unchanged)
|
||||||
|
|||||||
12
docs/open.md
12
docs/open.md
@@ -161,11 +161,13 @@ Voraussetzung: funktionierendes Gitea-Release unter `git.kuns.dev/releases/Claud
|
|||||||
|
|
||||||
## 4. Service-Deployment
|
## 4. Service-Deployment
|
||||||
|
|
||||||
### 4.1 Worker-Autostart als Per-User-Task ✅ (ersetzt Windows-Service)
|
### 4.1 Worker-Autostart via Startup-Shortcut ✅ (ersetzt Scheduled Task + Windows-Service)
|
||||||
- Der Worker läuft **nicht mehr als Windows-Service** (LocalSystem konnte die Claude-CLI-Auth des Users nicht sehen). Stattdessen: per-user **Logon-Scheduled-Task** „ClaudeDoWorker" (`schtasks /Create /XML`), läuft als angemeldeter User, versteckt, mit Restart-on-Failure.
|
- Der Worker läuft als `WinExe` (kein Konsolenfenster) + Serilog-File-Sink (`~/.todo-app/logs/worker-*.log`) + Single-Instance-Mutex.
|
||||||
- Worker ist `WinExe` (kein Konsolenfenster) + Serilog-File-Sink (`~/.todo-app/logs/worker-*.log`) + Single-Instance-Mutex.
|
- Autostart über eine **Startup-Ordner-Verknüpfung** `ClaudeDo Worker.lnk` (`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\`), die der Installer via `AutostartShortcut`/`ShortcutFactory` COM-Helper anlegt. Kein Scheduled Task, kein Windows-Service.
|
||||||
- Installer migriert beim Update den alten Service automatisch weg (`sc stop`/`delete`) und registriert die Task; Uninstall entfernt Task + Worker-Prozess. App startet/neustartet den Worker als Prozess und sorgt beim Start dafür, dass er läuft.
|
- `StartWorkerStep` startet den Worker per `Process.Start`; `StopWorkerStep` beendet ihn per prozessbasiertem Kill.
|
||||||
- Implementiert 2026-05-29, getestet (Build + Unit-Tests grün), **manuelle E2E-Verifikation am Gerät ausstehend** (Update von 1.0.2-alpha → Task, Logoff/Logon-Autostart, Uninstall).
|
- Die App (`IslandsShellViewModel`) startet den Worker nicht selbst. Bei offline-Worker ~12s nach App-Start: einmaliges `WorkerConnectionModal` (Start Worker / Rerun Installer / Dismiss); Connection-Status-Pill in der Fußzeile ist ein Button zum erneuten Öffnen des Modals.
|
||||||
|
- `UninstallRunner` löscht die Startup-`.lnk`; migriert ältere Installs durch best-effort-Löschen des Legacy-Scheduled-Tasks „ClaudeDoWorker" und des Legacy-Windows-Service.
|
||||||
|
- **Manuelle E2E-Verifikation am Gerät ausstehend** (Logoff/Logon-Autostart, Update-Pfad, Uninstall).
|
||||||
|
|
||||||
### 4.2 Pfad-Auflösung absolut ✅
|
### 4.2 Pfad-Auflösung absolut ✅
|
||||||
- `WorkerConfig.Load` expandiert `~`/`%USERPROFILE%` für alle Pfad-Felder.
|
- `WorkerConfig.Load` expandiert `~`/`%USERPROFILE%` für alle Pfad-Felder.
|
||||||
|
|||||||
33
docs/plan.md
33
docs/plan.md
@@ -231,36 +231,21 @@ Beispiel: `feat(lager-app): add barcode scan retry logic`
|
|||||||
|
|
||||||
DB-Zugriff via Microsoft.Data.Sqlite + Repository-Layer (`TaskRepository`, `ListRepository`). Git-Operationen (UI + Worker) über gemeinsamen `GitService` in `ClaudeDo.Data`. MVVM via CommunityToolkit.Mvvm.
|
DB-Zugriff via Microsoft.Data.Sqlite + Repository-Layer (`TaskRepository`, `ListRepository`). Git-Operationen (UI + Worker) über gemeinsamen `GitService` in `ClaudeDo.Data`. MVVM via CommunityToolkit.Mvvm.
|
||||||
|
|
||||||
## Worker als Windows-Service (Ziel-Deployment)
|
## Worker-Deployment (Autostart via Startup-Shortcut)
|
||||||
|
|
||||||
Initial läuft der Worker als Console-Prozess (lokales Dev-Setup). Im Endzustand soll er als **Windows-Service** automatisch starten.
|
Der Worker läuft als **WinExe** (kein Konsolenfenster) — kein Windows-Service, kein Scheduled Task.
|
||||||
|
|
||||||
**Code-seitig:**
|
**Autostart:** Der Installer legt eine Verknüpfung `ClaudeDo Worker.lnk` im Startup-Ordner des Users an (`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\`). Dafür nutzt `ClaudeDo.Installer` den Helper `AutostartShortcut` (mit extrahiertem `ShortcutFactory` COM-Helper). Beim Windows-Logon startet Windows die Verknüpfung automatisch — ohne Elevated-Rechte und mit vollem Zugriff auf die `~/.claude/`-Session des Users.
|
||||||
- Paket `Microsoft.Extensions.Hosting.WindowsServices` referenzieren.
|
|
||||||
- In `Program.cs`: `builder.Host.UseWindowsService(o => o.ServiceName = "ClaudeDoWorker")`.
|
|
||||||
- Logging zusätzlich über `EventLog` (`builder.Logging.AddEventLog(...)`), damit Service-Fehler im Windows Event Viewer landen.
|
|
||||||
- Alle Pfade in `worker.config.json` **absolut** auflösen (`%USERPROFILE%` / `~` expandieren) — der Service-Working-Directory ist standardmäßig `C:\Windows\System32`.
|
|
||||||
- `StaleTaskRecovery` (siehe oben) sorgt nach Service-Restart automatisch für das Aufräumen hängender `running`-Tasks.
|
|
||||||
- Restart-Verhalten via `sc.exe failure`-Konfig oder beim Install.
|
|
||||||
|
|
||||||
**Install:**
|
**Manueller Start (App-seitig):** Der Installer-Step `StartWorkerStep` startet den Worker beim Install/Update via `Process.Start` direkt. Die App (`IslandsShellViewModel`) startet den Worker **nicht** selbst. Stattdessen: ist der Worker ~12 Sekunden nach App-Start noch offline, erscheint einmalig ein `WorkerConnectionModal` mit drei Optionen (Start Worker / Rerun Installer / Dismiss). Der Connection-Status-Pill in der Fußzeile ist ein klickbarer Button, der das Modal auf Anfrage erneut öffnet.
|
||||||
- Veröffentlichen mit `dotnet publish -c Release -r win-x64 --self-contained false`.
|
|
||||||
- Service registrieren:
|
|
||||||
```cmd
|
|
||||||
sc.exe create ClaudeDoWorker binPath= "C:\Path\To\ClaudeDo.Worker.exe" start= auto
|
|
||||||
sc.exe failure ClaudeDoWorker reset= 60 actions= restart/5000/restart/10000/restart/30000
|
|
||||||
```
|
|
||||||
- Später optional: kleines `ClaudeDo.Installer`-Projekt (WiX oder MSIX), das das auch macht.
|
|
||||||
|
|
||||||
**Auth-Konflikt mit "User-CLI-Session" beachten:**
|
**Stop/Uninstall:** `StopWorkerStep` beendet den Worker via prozessbasiertem Kill (kein `schtasks /End` mehr). `UninstallRunner` löscht die Startup-`.lnk`. Als Migrations-Schritt für ältere Installationen löscht der Uninstaller auch den Legacy-Scheduled-Task „ClaudeDoWorker" und den Legacy-Windows-Service (best-effort).
|
||||||
Der Worker-Service läuft per Default unter `LocalSystem` — der hat **keinen Zugriff** auf die `~/.claude/`-Session des interaktiven Users, in der der CLI-Login liegt. Optionen:
|
|
||||||
|
|
||||||
1. **Empfohlen:** Service unter dem **User-Account** laufen lassen (`sc.exe config ClaudeDoWorker obj= ".\<username>" password= "..."` oder via `services.msc` → "Log On As"). Dann greift die bestehende `claude login`-Session des Users. Voraussetzung: User-Account hat das Recht "Log on as a service".
|
**Logging:** Serilog-File-Sink nach `~/.todo-app/logs/worker-*.log`. Single-Instance-Mutex verhindert parallele Instanzen.
|
||||||
2. **Fallback:** Wieder auf API-Key wechseln (`ANTHROPIC_API_KEY` als Umgebungsvariable des Service oder im `worker.config.json`). Dann ist der Service unabhängig vom User-Profil — verliert aber den Vorteil "kein Key-Handling".
|
|
||||||
|
|
||||||
Entscheidung wird beim Service-Deployment getroffen, bleibt für die initiale Console-Variante irrelevant. Service-Modus erfordert keine Schema- oder API-Änderungen am Worker.
|
**Pfade:** `WorkerConfig.Load` expandiert `~`/`%USERPROFILE%` für alle Pfad-Felder.
|
||||||
|
|
||||||
**SignalR im Service-Modus:** Bindung bleibt `127.0.0.1:47821`. Da die UI auf demselben Rechner läuft, ist Loopback-Erreichbarkeit gegeben — Windows-Firewall greift bei Loopback nicht.
|
**SignalR:** Bindung bleibt `127.0.0.1:47821`. Da die UI auf demselben Rechner läuft, ist Loopback-Erreichbarkeit gegeben — Windows-Firewall greift bei Loopback nicht.
|
||||||
|
|
||||||
## Project-Layout (Monorepo)
|
## Project-Layout (Monorepo)
|
||||||
|
|
||||||
@@ -319,4 +304,4 @@ Vorteil Monorepo: gemeinsames `schema.sql`, atomische Änderungen über UI+Worke
|
|||||||
- Bulk-Discard alter Worktrees.
|
- Bulk-Discard alter Worktrees.
|
||||||
- Anzeige der ndjson-Message-Chronik im UI.
|
- Anzeige der ndjson-Message-Chronik im UI.
|
||||||
- Windows Job Objects für garantierten Child-Cleanup beim Worker-Crash.
|
- Windows Job Objects für garantierten Child-Cleanup beim Worker-Crash.
|
||||||
- Installer-Projekt (`ClaudeDo.Installer`, WiX/MSIX), das den Service registriert + UI shortcut anlegt.
|
- Install-Skripte/Doku für manuelles Deployment ohne Installer.
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# UI Normalization — Visual Check
|
||||||
|
|
||||||
|
Run the app and walk each surface. Lane B intentionally shifted some values (12px→13px, 9px→10px, 16px→18px, off-palette colors folded to the palette), so small differences are expected — you're checking nothing looks *broken*.
|
||||||
|
|
||||||
|
## Global
|
||||||
|
- [ ] All text renders in **Inter Tight** (sans), not Segoe UI. Labels that were previously "off" (Settings field labels) now match.
|
||||||
|
- [ ] Mono text (chips, log lines, file paths, eyebrows, titlebar titles) still renders in JetBrains Mono.
|
||||||
|
|
||||||
|
## Main window
|
||||||
|
- [ ] Status-bar connection dot color: online = moss green, reconnecting = peat/amber, offline = blood red.
|
||||||
|
- [ ] Islands, task rows, chips, agent strips, terminal all look unchanged.
|
||||||
|
|
||||||
|
## Task row
|
||||||
|
- [ ] Schedule flyout (the date popup) renders with a visible border (was a broken/missing `BorderBrush` key — now `LineBrush`).
|
||||||
|
|
||||||
|
## Modals — now wrapped in ModalShell (check titlebar drag, ✕ close, footer buttons)
|
||||||
|
- [ ] **Settings** — titlebar "SETTINGS", drag works, ✕ closes, Cancel/Save footer. Tabs (General/Worktrees/Files/Prime Claude) intact.
|
||||||
|
- [ ] **List settings** — Delete (left) + Cancel/Save (right) footer; section panels intact.
|
||||||
|
- [ ] **Merge** — task summary + action buttons.
|
||||||
|
- [ ] **About** — version/data/logs/config labels.
|
||||||
|
- [ ] **Unfinished planning** — body text + primary action.
|
||||||
|
- [ ] **Repo import** — toolbar at top of body, repo list scrolls, footer.
|
||||||
|
- [ ] **Worktrees overview** — rows render; force-remove/phantom text is red (StatusError); state badge text legible. NOTE: window decorations changed to borderless (ModalShell draws the border) — confirm it still looks right.
|
||||||
|
- [ ] **Diff modal** — diff text mono, add/del colors, merge button in footer.
|
||||||
|
- [ ] **Conflict resolution** — now ModalShell; conflict list mono; error text red.
|
||||||
|
|
||||||
|
## Not wrapped in ModalShell (intentional — distinct chrome)
|
||||||
|
- [ ] **Worktree modal** (the big 1100×720 acrylic-blur diff window) — unchanged look, fonts slightly normalized.
|
||||||
|
- [ ] **Planning diff view** (embedded) — diff renders, mono font, warning text red.
|
||||||
|
|
||||||
|
## Date picker
|
||||||
|
- [ ] Selected day: accent background with light text (was hardcoded white → TextBrush).
|
||||||
|
|
||||||
|
## If something looks wrong
|
||||||
|
- Font/size off → check the snap mapping in `2026-05-30-ui-normalization.md` (11→Mono=11, 12→Body=13).
|
||||||
|
- A modal's layout broke → that modal's body may have coupled to the old Grid rows; revert just that file's ModalShell wrap and keep only the token changes (the fallback noted in the plan).
|
||||||
473
docs/superpowers/plans/2026-05-30-ui-normalization.md
Normal file
473
docs/superpowers/plans/2026-05-30-ui-normalization.md
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
# UI Normalization Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Make the design tokens the single source of truth for every visual value in the Avalonia UI, remove duplicated styles, and add a reusable `ModalShell` control for the copy-pasted modal chrome.
|
||||||
|
|
||||||
|
**Architecture:** Establish global control defaults in `App.axaml`, expand/repoint brushes in `Tokens.axaml`, promote shared styles into `IslandStyles.axaml`, then mechanically migrate every view to reference tokens (snapping stray values to the nearest token per "lane B"). Off-palette colors fold into the existing palette. A new `ModalShell` templated control replaces the per-modal titlebar/border/footer markup.
|
||||||
|
|
||||||
|
**Tech Stack:** .NET 8, Avalonia 12 (Fluent theme, dark variant), compiled XAML (`x:DataType`), CommunityToolkit.Mvvm.
|
||||||
|
|
||||||
|
**Verification model:** There are no unit tests for XAML. The "test" for every task is a clean build:
|
||||||
|
- `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj` (compiles Ui + Data; validates all StaticResource keys and compiled bindings)
|
||||||
|
|
||||||
|
Build with the `.csproj` directly — `.slnx` requires .NET 9 and will fail on this machine (.NET 8).
|
||||||
|
|
||||||
|
**Normalization rules (apply everywhere unless a task says otherwise):**
|
||||||
|
|
||||||
|
Font sizes — replace every `FontSize="N"` literal with the token whose value it snaps to:
|
||||||
|
| literal | token |
|
||||||
|
|---|---|
|
||||||
|
| 9 | `{StaticResource FontSizeEyebrow}` (10) |
|
||||||
|
| 10 | `{StaticResource FontSizeEyebrow}` (10) |
|
||||||
|
| 11 | `{StaticResource FontSizeMono}` (11) |
|
||||||
|
| 12 | `{StaticResource FontSizeBody}` (13) |
|
||||||
|
| 13 | `{StaticResource FontSizeBody}` (13) |
|
||||||
|
| 14 | `{StaticResource FontSizeTaskTitle}` (14) |
|
||||||
|
| 16 | `{StaticResource FontSizeH3}` (18) |
|
||||||
|
| 18 | `{StaticResource FontSizeH3}` (18) |
|
||||||
|
| 24 | `{StaticResource FontSizeH2}` (24) |
|
||||||
|
| 32 | `{StaticResource FontSizeH1}` (32) |
|
||||||
|
|
||||||
|
Spacing — modal body padding literals `16` and `20` snap to `18`; keep other axis values mapped to the nearest of SpaceXs=4/SpaceSm=8/SpaceMd=12/SpaceLg=14/SpaceXl=18/Space2Xl=24. Leave values that already equal a token as plain numbers (do **not** churn every margin into a resource ref — only modal body padding is standardized).
|
||||||
|
|
||||||
|
Corner radius — `4` → `6`; TextBox inputs use `8`.
|
||||||
|
|
||||||
|
Colors — fold off-palette to palette:
|
||||||
|
| literal / named | replacement |
|
||||||
|
|---|---|
|
||||||
|
| `#4CAF50` (online dot) | `{DynamicResource StatusRunningBrush}` |
|
||||||
|
| `#FFA726` (reconnecting dot) | `{DynamicResource StatusReviewBrush}` |
|
||||||
|
| `#EF5350` (offline / phantom) | `{DynamicResource StatusErrorBrush}` |
|
||||||
|
| `OrangeRed`, `Orange` | `{DynamicResource BloodBrush}` |
|
||||||
|
| `White` (badge / danger text) | `{DynamicResource TextBrush}` |
|
||||||
|
| `White` (on accent primary button) | `{DynamicResource DeepBrush}` |
|
||||||
|
| `#FF080C0B` (terminal bg) | `{DynamicResource VoidBrush}` |
|
||||||
|
| `#0DFFFFFF` (island hairline) | `{DynamicResource HairlineOverlayBrush}` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Foundation
|
||||||
|
|
||||||
|
### Task 1: Add new brushes & repoint badges in Tokens.axaml
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Design/Tokens.axaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add named tint, hairline brushes**
|
||||||
|
|
||||||
|
In the BRUSHES section (after the Status*Brush block ending ~line 85), add:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Subtle white overlay (island hairline border) -->
|
||||||
|
<SolidColorBrush x:Key="HairlineOverlayBrush" Color="#0DFFFFFF" />
|
||||||
|
|
||||||
|
<!-- Status tints (12% fill / 30% border of the status hue) — reused by chips & agent strips -->
|
||||||
|
<SolidColorBrush x:Key="RunningTintBrush" Color="#1F7C9166" />
|
||||||
|
<SolidColorBrush x:Key="RunningTintBorderBrush" Color="#4C7C9166" />
|
||||||
|
<SolidColorBrush x:Key="ReviewTintBrush" Color="#1FD4A574" />
|
||||||
|
<SolidColorBrush x:Key="ReviewTintBorderBrush" Color="#4CD4A574" />
|
||||||
|
<SolidColorBrush x:Key="ErrorTintBrush" Color="#1FC87060" />
|
||||||
|
<SolidColorBrush x:Key="ErrorTintBorderBrush" Color="#4CC87060" />
|
||||||
|
<SolidColorBrush x:Key="QueuedTintBrush" Color="#1F8B9D7A" />
|
||||||
|
<SolidColorBrush x:Key="QueuedTintBorderBrush" Color="#4C8B9D7A" />
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build to verify tokens parse**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: PASS (no errors).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/Design/Tokens.axaml
|
||||||
|
git commit -m "feat(ui): add named tint and hairline overlay brush tokens"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Global control defaults in App.axaml
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.App/App.axaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add Window default style**
|
||||||
|
|
||||||
|
Inside `<Application.Styles>`, after `<StyleInclude Source="avares://ClaudeDo.Ui/Design/IslandStyles.axaml" />` and before the ListBoxItem styles, add:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Global defaults: every Window inherits Inter Tight + body size.
|
||||||
|
Controls that need mono opt in via their own class/style. -->
|
||||||
|
<Style Selector="Window">
|
||||||
|
<Setter Property="FontFamily" Value="{DynamicResource SansFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{DynamicResource FontSizeBody}" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" />
|
||||||
|
</Style>
|
||||||
|
```
|
||||||
|
|
||||||
|
(FontFamily/FontSize/Foreground are inherited properties in Avalonia, so setting them on the Window root propagates to all descendant text controls.)
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.App/App.axaml
|
||||||
|
git commit -m "feat(ui): set global Inter Tight font default on all windows"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Promote shared styles into IslandStyles.axaml
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Design/IslandStyles.axaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add shared modal styles**
|
||||||
|
|
||||||
|
At the end of the `<Styles>` element (before the closing `</Styles>` at line ~901), add:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- SHARED MODAL STYLES (promoted from per-modal Window.Styles) -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Style Selector="TextBlock.field-label">
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
|
<Setter Property="Margin" Value="0,0,0,4" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.path-mono">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
|
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Standalone modal action buttons (not the .btn family) -->
|
||||||
|
<Style Selector="Button.primary">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentDimBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.danger">
|
||||||
|
<Setter Property="Background" Value="{StaticResource BloodBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
</Style>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `TextBlock.section-label` already exists at line ~864 — do NOT re-add it.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Replace hardcoded values inside existing IslandStyles rules**
|
||||||
|
|
||||||
|
Apply the normalization rules to the existing style setters in this file:
|
||||||
|
- Every `FontSize="N"` setter → the snapped token ref (table above). Specific lines: 149 (10→FontSizeEyebrow), 206 (11→FontSizeMono), 252 (13→FontSizeBody), 397 (11→FontSizeMono), 453 (9→FontSizeEyebrow), 475 (10→FontSizeEyebrow), 483 (10→FontSizeEyebrow), 556 (12→FontSizeBody), 573 (9→FontSizeEyebrow), 597 (12→FontSizeBody), 622 (10→FontSizeEyebrow), 638 (12→FontSizeBody), 697 (14→FontSizeTaskTitle), 771 (10→FontSizeEyebrow), 783 (10→FontSizeEyebrow), 788 (10→FontSizeEyebrow), 819 (11→FontSizeMono), 867 (10→FontSizeEyebrow), 884 (9→FontSizeEyebrow).
|
||||||
|
- Chip tint backgrounds/borders → named brushes:
|
||||||
|
- line 155/156 `#1F7C9166`/`#4C7C9166` → `{StaticResource RunningTintBrush}`/`{StaticResource RunningTintBorderBrush}`
|
||||||
|
- 163/164 review tints → `ReviewTintBrush`/`ReviewTintBorderBrush`
|
||||||
|
- 171/172 error tints → `ErrorTintBrush`/`ErrorTintBorderBrush`
|
||||||
|
- 179/180 queued tints → `QueuedTintBrush`/`QueuedTintBorderBrush`
|
||||||
|
- agent-strip tints at 361/362 (`#147C9166`/`#4C7C9166`), 365/366, 368/369, 374/375 → the matching `*TintBrush`/`*TintBorderBrush` (snap the `#14` alpha to the shared `#1F` tint).
|
||||||
|
- line 123 `#0DFFFFFF` → `{StaticResource HairlineOverlayBrush}`.
|
||||||
|
- line 389 & 810 `#FF080C0B` → `{StaticResource VoidBrush}`.
|
||||||
|
- line 887 badge `White` → `{StaticResource TextBrush}`.
|
||||||
|
- Badge brushes at lines 88-90: replace the three `<SolidColorBrush>` definitions with palette refs:
|
||||||
|
```xml
|
||||||
|
<SolidColorBrush x:Key="DraftBadgeBrush" Color="{StaticResource TextMuteColor}"/>
|
||||||
|
<SolidColorBrush x:Key="PlanningBadgeBrush" Color="{StaticResource PeatColor}"/>
|
||||||
|
<SolidColorBrush x:Key="PlannedBadgeBrush" Color="{StaticResource SageColor}"/>
|
||||||
|
```
|
||||||
|
- Corner radius `4` setters (447 live-chip, 813 task-live-tail `5`→leave, badges 878 `3`→leave) → only snap `4`→`6` where it appears as `CornerRadius="4"` on live-chip (447) and kbd (614) and badge tints. Leave `3` and `5` as-is (no nearby token; they're intentional micro-radii). NOTE: if unsure, leave radius alone — radius churn is lowest priority.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/Design/IslandStyles.axaml
|
||||||
|
git commit -m "refactor(ui): tokenize IslandStyles values and add shared modal styles"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Per-view token migration (independent; parallelizable)
|
||||||
|
|
||||||
|
For each task: open the file, apply the **normalization rules** (font/color/spacing/radius tables at top). Remove any local `Window.Styles` block that only redefines `section-label`, `field-label`, `path-mono`, `Button.primary`, or `Button.danger` (now shared from IslandStyles). Keep local styles that are genuinely unique to that view. After each file, build and commit.
|
||||||
|
|
||||||
|
Each task ends with:
|
||||||
|
- Build: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj` → PASS
|
||||||
|
- Commit: `git add <file> && git commit -m "refactor(ui): tokenize <view>"`
|
||||||
|
|
||||||
|
### Task 4: MainWindow.axaml
|
||||||
|
- Snap all `FontSize` literals (lines ~46,52,59,67,112,136,209,222,231).
|
||||||
|
- Status dots: `#4CAF50`→`StatusRunningBrush`, `#FFA726`→`StatusReviewBrush`, `#EF5350`→`StatusErrorBrush` (lines ~200,203,205).
|
||||||
|
|
||||||
|
### Task 5: Islands — ListsIslandView.axaml, TasksIslandView.axaml
|
||||||
|
- ListsIslandView: snap FontSize (18,10,12 at lines ~18,49,57,58,59); username TextBlock (~57) gets no explicit FontFamily (inherits SansFont now — correct, leave it).
|
||||||
|
- TasksIslandView: snap FontSize (24,11 at ~15,19).
|
||||||
|
|
||||||
|
### Task 6: DetailsIslandView.axaml
|
||||||
|
- Snap all FontSize (10,14,11,10,13,12 at lines ~54,57,92,114,138,142,199,269).
|
||||||
|
- `OrangeRed`→`BloodBrush` (~154).
|
||||||
|
- TextBox `CornerRadius="6"`→`8` (~172,274). TextBox `Padding="8"` leave.
|
||||||
|
- Remove any redundant inline label styles superseded by shared `field-label`.
|
||||||
|
|
||||||
|
### Task 7: TaskRowView.axaml (includes the BorderBrush bug fix)
|
||||||
|
- Snap FontSize (10,14 at ~85,103).
|
||||||
|
- **Bug fix:** `BorderBrush="{DynamicResource BorderBrush}"` → `{DynamicResource LineBrush}` (the schedule-flyout border, ~line 188/222). `BorderBrush` is not a defined key.
|
||||||
|
- Schedule flyout: title/labels inherit SansFont now (leave unset).
|
||||||
|
|
||||||
|
### Task 8: AgentStripView.axaml, SessionTerminalView.axaml
|
||||||
|
- AgentStrip: snap FontSize (10,9 at ~22,29,73,78); commit chip radius `4`→`6` (~102).
|
||||||
|
- SessionTerminal: snap FontSize (10,11 at ~17,69).
|
||||||
|
|
||||||
|
### Task 9: ThemedDatePicker.axaml
|
||||||
|
- Snap any FontSize literals; popup border `CornerRadius="10"` → leave (10 = ChipCornerRadius value, acceptable) OR `{StaticResource ChipCornerRadius}`. Tokenize colors if any literals present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — ModalShell control
|
||||||
|
|
||||||
|
### Task 10: Create ModalShell control
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml.cs`
|
||||||
|
- Create: `src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the code-behind (templated control)**
|
||||||
|
|
||||||
|
`ModalShell.axaml.cs`:
|
||||||
|
```csharp
|
||||||
|
using System;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Input;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Views.Controls;
|
||||||
|
|
||||||
|
/// <summary>Reusable modal chrome: titlebar (drag + close) wrapping a body and optional footer.</summary>
|
||||||
|
public class ModalShell : ContentControl
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<string?> TitleProperty =
|
||||||
|
AvaloniaProperty.Register<ModalShell, string?>(nameof(Title));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<object?> FooterProperty =
|
||||||
|
AvaloniaProperty.Register<ModalShell, object?>(nameof(Footer));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<ICommand?> CloseCommandProperty =
|
||||||
|
AvaloniaProperty.Register<ModalShell, ICommand?>(nameof(CloseCommand));
|
||||||
|
|
||||||
|
public string? Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||||
|
public object? Footer { get => GetValue(FooterProperty); set => SetValue(FooterProperty, value); }
|
||||||
|
public ICommand? CloseCommand { get => GetValue(CloseCommandProperty); set => SetValue(CloseCommandProperty, value); }
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
if (e.NameScope.Find<Border>("PART_TitleBar") is { } bar)
|
||||||
|
bar.PointerPressed += OnTitleBarPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed
|
||||||
|
&& VisualRoot is Window w)
|
||||||
|
w.BeginMoveDrag(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write the ControlTheme**
|
||||||
|
|
||||||
|
`ModalShell.axaml`:
|
||||||
|
```xml
|
||||||
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls">
|
||||||
|
<ControlTheme x:Key="{x:Type ctl:ModalShell}" TargetType="ctl:ModalShell">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<Border Background="{DynamicResource SurfaceBrush}"
|
||||||
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{DynamicResource ModalCornerRadius}"
|
||||||
|
ClipToBounds="True">
|
||||||
|
<DockPanel>
|
||||||
|
<!-- Title bar -->
|
||||||
|
<Border Name="PART_TitleBar" DockPanel.Dock="Top" Height="36"
|
||||||
|
Background="{DynamicResource DeepBrush}"
|
||||||
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
|
BorderThickness="0,0,0,1">
|
||||||
|
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
|
||||||
|
<TextBlock Text="{TemplateBinding Title}"
|
||||||
|
FontFamily="{DynamicResource MonoFont}"
|
||||||
|
FontSize="{DynamicResource FontSizeMono}"
|
||||||
|
LetterSpacing="1.4"
|
||||||
|
Foreground="{DynamicResource TextBrush}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Button Grid.Column="1" Classes="icon-btn" Content="✕"
|
||||||
|
FontSize="{DynamicResource FontSizeBody}"
|
||||||
|
Command="{TemplateBinding CloseCommand}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<!-- Footer (optional) -->
|
||||||
|
<Border Name="PART_Footer" DockPanel.Dock="Bottom"
|
||||||
|
Background="{DynamicResource DeepBrush}"
|
||||||
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
|
BorderThickness="0,1,0,0"
|
||||||
|
IsVisible="{TemplateBinding Footer, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
|
<ContentPresenter Content="{TemplateBinding Footer}" Margin="16,8"/>
|
||||||
|
</Border>
|
||||||
|
<!-- Body -->
|
||||||
|
<ContentPresenter Content="{TemplateBinding Content}"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</ControlTheme>
|
||||||
|
</ResourceDictionary>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Register the ControlTheme**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.App/App.axaml`, inside `<ResourceDictionary.MergedDictionaries>` (after the Tokens include), add:
|
||||||
|
```xml
|
||||||
|
<ResourceInclude Source="avares://ClaudeDo.Ui/Views/Controls/ModalShell.axaml" />
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Build**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml.cs src/ClaudeDo.App/App.axaml
|
||||||
|
git commit -m "feat(ui): add reusable ModalShell control"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 11: Migrate SettingsModalView to ModalShell (reference migration)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml`
|
||||||
|
- Modify (if needed): `src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace chrome with ModalShell**
|
||||||
|
|
||||||
|
- Add namespace if missing: `xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"` (already present).
|
||||||
|
- Remove the local `Window.Styles` entries for `section-label`, `field-label`, `path-mono`, `Button.danger`, `Button.primary` (now shared). Keep any genuinely unique styles.
|
||||||
|
- Replace the outer `<Border>...<Grid RowDefinitions="36,*,52">` structure with:
|
||||||
|
```xml
|
||||||
|
<ctl:ModalShell Title="SETTINGS" CloseCommand="{Binding CancelCommand}">
|
||||||
|
<ctl:ModalShell.Footer>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||||
|
<Button Content="Cancel" Command="{Binding CancelCommand}" MinWidth="90"/>
|
||||||
|
<Button Content="Save" Classes="primary" Command="{Binding SaveCommand}" IsEnabled="{Binding !IsBusy}" MinWidth="90"/>
|
||||||
|
</StackPanel>
|
||||||
|
</ctl:ModalShell.Footer>
|
||||||
|
<!-- existing DockPanel body (tabs + validation strip) goes here unchanged -->
|
||||||
|
</ctl:ModalShell>
|
||||||
|
```
|
||||||
|
- The body is the existing `<DockPanel Grid.Row="1">` content minus `Grid.Row`.
|
||||||
|
- Snap remaining FontSize literals in the body per the rules.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Remove obsolete drag handler if now unused**
|
||||||
|
|
||||||
|
If `TitleBar_PointerPressed` in `SettingsModalView.axaml.cs` is no longer referenced (ModalShell handles dragging), delete the method and the `x:Name="TitleBar"`/`PointerPressed` wiring. If the build complains about an unused handler, that's the signal.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml.cs
|
||||||
|
git commit -m "refactor(ui): migrate SettingsModal to ModalShell"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 12: Migrate remaining modals to ModalShell
|
||||||
|
|
||||||
|
Repeat the Task 11 pattern for each modal below. One commit per file. Each: swap chrome → `ModalShell`, lift action buttons into `ModalShell.Footer`, drop local duplicate styles, delete now-unused `*_PointerPressed` drag handlers, snap FontSize/colors per rules, build, commit.
|
||||||
|
|
||||||
|
- [ ] **12a:** `ListSettingsModalView.axaml` (+ `.axaml.cs`)
|
||||||
|
- [ ] **12b:** `MergeModalView.axaml` (+ `.axaml.cs`)
|
||||||
|
- [ ] **12c:** `AboutModalView.axaml` (+ `.axaml.cs`) — labels inherit SansFont now.
|
||||||
|
- [ ] **12d:** `UnfinishedPlanningModalView.axaml` (+ `.axaml.cs`)
|
||||||
|
- [ ] **12e:** `RepoImportModalView.axaml` (+ `.axaml.cs`)
|
||||||
|
- [ ] **12f:** `WorktreesOverviewModalView.axaml` (+ `.axaml.cs`) — also fold `Border.wt-row` to reuse `task-row` if trivial; snap FontSize; `#EF5350`→`StatusErrorBrush`; `White` badge text→`TextBrush`.
|
||||||
|
|
||||||
|
Each ends with build PASS + `git commit -m "refactor(ui): migrate <Modal> to ModalShell"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 13: DiffModalView, PlanningDiffView, ConflictResolutionView (Static→Dynamic + chrome)
|
||||||
|
|
||||||
|
These three currently use `StaticResource` for token lookups. Migrate chrome to `ModalShell` where they are full windows, and convert token references.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Convert resource references**
|
||||||
|
|
||||||
|
In each of `DiffModalView.axaml`, `PlanningDiffView.axaml`, `ConflictResolutionView.axaml`: change every `{StaticResource <Brush/Token>}` used in an **element attribute** to `{DynamicResource ...}`. Leave `{StaticResource ...}` inside `<Style>`/`Setter` blocks (Avalonia styles resolve StaticResource fine and DynamicResource in setters is discouraged).
|
||||||
|
|
||||||
|
- [ ] **Step 2: Apply normalization rules**
|
||||||
|
|
||||||
|
- Snap FontSize literals.
|
||||||
|
- `Consolas,Menlo,monospace` raw font (PlanningDiffView ~98, ConflictResolution ~47) → `{DynamicResource MonoFont}`.
|
||||||
|
- `Orange`/`OrangeRed` → `{DynamicResource BloodBrush}`.
|
||||||
|
- DiffModal tints `#1A4A6B4A`/`#1AC87060` → `{DynamicResource RunningTintBrush}`/`{DynamicResource ErrorTintBrush}`.
|
||||||
|
- Migrate window chrome to `ModalShell` if the file is a Window with the titlebar/footer pattern (DiffModalView, ConflictResolutionView). PlanningDiffView is an embedded view — only convert resources + fonts, no ModalShell.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build + commit (one per file)**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj` → PASS
|
||||||
|
Commit: `git commit -m "refactor(ui): tokenize and dynamic-ize <view>"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 — Final verification
|
||||||
|
|
||||||
|
### Task 14: Full build + visual checklist
|
||||||
|
|
||||||
|
- [ ] **Step 1: Build both projects**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj
|
||||||
|
dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj
|
||||||
|
```
|
||||||
|
Expected: both PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Grep for stragglers**
|
||||||
|
|
||||||
|
Confirm no remaining hardcoded values slipped through:
|
||||||
|
- `FontSize="` with a numeric literal in any `Views/**/*.axaml` (should be near-zero; only token refs remain).
|
||||||
|
- Off-palette hex (`#4CAF50`, `#FFA726`, `#EF5350`, `#FF080C0B`, `OrangeRed`, `Orange`) — should be zero.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Produce the human visual-check checklist**
|
||||||
|
|
||||||
|
Write a short checklist (`docs/superpowers/plans/2026-05-30-ui-normalization-visualcheck.md`) listing each view/modal and what to eyeball (font looks like Inter Tight, status dots correct color, modal titlebars/footers intact, badges distinguishable, diff/planning views render). This is the regression gate the user runs by launching the app.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review notes
|
||||||
|
|
||||||
|
- **Spec coverage:** global defaults (T2), token source-of-truth fonts/spacing/radius (rules + T3–T13), color fold (T1,T3,T4,T6,T12,T13), shared styles (T3), ModalShell (T10–T13), bug fixes — BorderBrush (T7), Static→Dynamic (T13). All spec sections mapped.
|
||||||
|
- **Risk note:** ModalShell migration (T11–T13) is the highest-risk part because each modal's body layout differs. Tasks are per-file so a failure is isolated. If a modal's body has tight coupling to the old Grid rows, keeping that modal's hand-rolled chrome (and only tokenizing it) is an acceptable fallback — note it in the commit.
|
||||||
|
- **Line numbers** are from the pre-change audit and may drift as edits land; treat them as guides, locate by content.
|
||||||
175
docs/superpowers/plans/2026-06-01-waiting-for-review-state.md
Normal file
175
docs/superpowers/plans/2026-06-01-waiting-for-review-state.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# Waiting for Review — Task State — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Add a `WaitingForReview` lifecycle state that standalone tasks enter after a successful run, with approve / reject-rerun / reject-park / cancel exits, exposed via UI and MCP.
|
||||||
|
|
||||||
|
**Architecture:** New enum value + nullable `ReviewFeedback` column. `TaskStateService` gains review transitions. `TaskRunner.HandleSuccess` routes standalone-task success to review. `QueueService.RunInSlotAsync` resumes the Claude session when re-running a rejected task. New MCP `review_task` tool + UI commands.
|
||||||
|
|
||||||
|
**Tech Stack:** .NET 8, EF Core (SQLite, TEXT enum), SignalR, Avalonia MVVM, xUnit.
|
||||||
|
|
||||||
|
**Scope decision (locked):** Only standalone tasks (`ParentTaskId == null`) route to `WaitingForReview`. Planning **child** tasks continue to `Done` on success so the sequential planning chain (which advances on *terminal* states) is unaffected. Flagged for user confirmation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Data layer — enum, converter, column
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Data/Models/TaskEntity.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs`
|
||||||
|
- Create: EF migration via CLI
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Add `WaitingForReview` to `TaskStatus` enum (after `Running`) and add `public string? ReviewFeedback { get; set; }` to `TaskEntity`.
|
||||||
|
- [ ] **Step 2:** In `TaskEntityConfiguration`, add `TaskStatus.WaitingForReview => "waiting_for_review"` to `StatusToString` and `"waiting_for_review" => TaskStatus.WaitingForReview` to `StatusFromString`; map the column: `builder.Property(t => t.ReviewFeedback).HasColumnName("review_feedback");`
|
||||||
|
- [ ] **Step 3:** Create migration: `dotnet ef migrations add AddReviewFeedback --project src/ClaudeDo.Data/ClaudeDo.Data.csproj`. Verify it only adds the `review_feedback` TEXT column (nullable). If `dotnet ef` unavailable, hand-write the migration + designer following the latest migration in `Migrations/`.
|
||||||
|
- [ ] **Step 4:** Build `dotnet build src/ClaudeDo.Data/ClaudeDo.Data.csproj`. Expected: success.
|
||||||
|
- [ ] **Step 5:** Commit.
|
||||||
|
|
||||||
|
## Task 2: Worker — review transitions in TaskStateService
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Worker/State/TaskStateService.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Worker/State/Interfaces/ITaskStateService.cs` (add new method signatures)
|
||||||
|
- Test: `tests/ClaudeDo.Worker.Tests/...` (state transition tests)
|
||||||
|
|
||||||
|
New methods (all return `TransitionResult`, broadcast `TaskUpdated`):
|
||||||
|
|
||||||
|
- `SubmitForReviewAsync(taskId, finishedAt, result, ct)` — guard `Status == Running`; set `Status=WaitingForReview, FinishedAt, Result`. Does NOT call `OnChildTerminalAsync` (review is non-terminal; only invoked for standalone tasks anyway).
|
||||||
|
- `ApproveReviewAsync(taskId, ct)` — guard `Status == WaitingForReview`; set `Status=Done`.
|
||||||
|
- `RejectToQueueAsync(taskId, feedback, ct)` — reject empty/whitespace feedback (`TransitionResult(false, "Feedback is required to reject for re-run.")`); guard `Status == WaitingForReview`; set `Status=Queued, ReviewFeedback=feedback`; `_waker.Wake()`.
|
||||||
|
- `RejectToIdleAsync(taskId, ct)` — guard `Status == WaitingForReview`; set `Status=Idle, ReviewFeedback=null` (leave `Result` intact).
|
||||||
|
- `ClearReviewFeedbackAsync(taskId, ct)` — set `ReviewFeedback=null` (no status change, no guard); used by the runner after consuming feedback.
|
||||||
|
- Extend `CancelAsync` guard: `(Status == Running || Status == Queued || Status == WaitingForReview)`.
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Write failing tests in a new `tests/ClaudeDo.Worker.Tests/State/ReviewTransitionTests.cs` (follow existing TaskStateService test setup). Cover: submit-for-review from Running; approve from WaitingForReview→Done; reject-to-queue stores feedback + status Queued; empty feedback rejected; reject-to-idle clears feedback + keeps Result; cancel from WaitingForReview→Cancelled; invalid (approve from Idle) returns `!Ok`.
|
||||||
|
- [ ] **Step 2:** Run tests, expect FAIL (methods missing).
|
||||||
|
- [ ] **Step 3:** Implement the methods + interface signatures + CancelAsync guard.
|
||||||
|
- [ ] **Step 4:** Run tests, expect PASS.
|
||||||
|
- [ ] **Step 5:** Commit.
|
||||||
|
|
||||||
|
## Task 3: Worker — route standalone success to review
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Worker/Runner/TaskRunner.cs` (`HandleSuccess`)
|
||||||
|
|
||||||
|
- [ ] **Step 1:** In `HandleSuccess`, after commit, branch:
|
||||||
|
```csharp
|
||||||
|
var finishedAt = DateTime.UtcNow;
|
||||||
|
if (task.ParentTaskId is null)
|
||||||
|
{
|
||||||
|
await _state.SubmitForReviewAsync(task.Id, finishedAt, result.ResultMarkdown, CancellationToken.None);
|
||||||
|
await _broadcaster.WorkerLog($"Finished \"{task.Title}\" (waiting for review)", WorkerLogLevel.Success, DateTime.UtcNow);
|
||||||
|
await _broadcaster.TaskFinished(slot, task.Id, "waiting_for_review", finishedAt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _state.CompleteAsync(task.Id, finishedAt, result.ResultMarkdown, CancellationToken.None);
|
||||||
|
await _broadcaster.WorkerLog($"Finished \"{task.Title}\" (done)", WorkerLogLevel.Success, DateTime.UtcNow);
|
||||||
|
await _broadcaster.TaskFinished(slot, task.Id, "done", finishedAt);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- [ ] **Step 2:** Build worker. Expected: success.
|
||||||
|
- [ ] **Step 3:** Commit.
|
||||||
|
|
||||||
|
## Task 4: Worker — resume-aware re-run in QueueService
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Worker/Queue/QueueService.cs` (`RunInSlotAsync`)
|
||||||
|
- Test: `tests/ClaudeDo.Worker.Tests/...`
|
||||||
|
|
||||||
|
- [ ] **Step 1:** In `RunInSlotAsync`, after loading `task`:
|
||||||
|
```csharp
|
||||||
|
if (!string.IsNullOrWhiteSpace(task.ReviewFeedback))
|
||||||
|
{
|
||||||
|
var feedback = task.ReviewFeedback!;
|
||||||
|
string? sessionId;
|
||||||
|
using (var ctx = _dbFactory.CreateDbContext())
|
||||||
|
sessionId = (await new TaskRunRepository(ctx).GetLatestByTaskIdAsync(taskId, ct))?.SessionId;
|
||||||
|
await _state.ClearReviewFeedbackAsync(taskId, ct); // inject ITaskStateService
|
||||||
|
if (sessionId is not null)
|
||||||
|
{
|
||||||
|
await _runner.ContinueAsync(taskId, feedback, "queue", ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
task.Description = string.IsNullOrWhiteSpace(task.Description)
|
||||||
|
? $"Reviewer feedback: {feedback}"
|
||||||
|
: $"{task.Description}\n\nReviewer feedback: {feedback}";
|
||||||
|
}
|
||||||
|
await _runner.RunAsync(task, "queue", ct);
|
||||||
|
```
|
||||||
|
Inject `ITaskStateService _state` into `QueueService` (add to ctor + DI already provides it).
|
||||||
|
- [ ] **Step 2:** Build worker, expect success.
|
||||||
|
- [ ] **Step 3:** Commit.
|
||||||
|
|
||||||
|
## Task 5: MCP — review_task tool + status reference
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Worker/External/ExternalMcpService.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Add `review_task` tool:
|
||||||
|
```csharp
|
||||||
|
[McpServerTool, Description(
|
||||||
|
"Review a task that is WaitingForReview. decision: 'approve' (→ Done), " +
|
||||||
|
"'reject_rerun' (→ Queued, resumes the agent session with feedback — feedback required), " +
|
||||||
|
"'reject_park' (→ Idle for manual editing), 'cancel' (→ Cancelled). ")]
|
||||||
|
public async Task<TaskDto> ReviewTask(string taskId, string decision, string? feedback, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
|
||||||
|
?? throw new InvalidOperationException($"Task {taskId} not found.");
|
||||||
|
TransitionResult r = decision.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"approve" => await _state.ApproveReviewAsync(taskId, cancellationToken),
|
||||||
|
"reject_rerun" => await _state.RejectToQueueAsync(taskId, feedback ?? "", cancellationToken),
|
||||||
|
"reject_park" => await _state.RejectToIdleAsync(taskId, cancellationToken),
|
||||||
|
"cancel" => await _state.CancelAsync(taskId, DateTime.UtcNow, cancellationToken),
|
||||||
|
_ => throw new InvalidOperationException($"Unknown decision '{decision}'. Use approve, reject_rerun, reject_park, or cancel."),
|
||||||
|
};
|
||||||
|
if (!r.Ok) throw new InvalidOperationException(r.Reason ?? "Review action failed.");
|
||||||
|
return ToDto((await _tasks.GetByIdAsync(taskId, cancellationToken))!);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- [ ] **Step 2:** Add `WaitingForReview` to `GetTaskStatusValues` list; update the validation strings in `ListTasks` and the lifecycle text in `GetTask`/`UpdateTaskStatus` to include `WaitingForReview`.
|
||||||
|
- [ ] **Step 3:** Build worker, expect success.
|
||||||
|
- [ ] **Step 4:** Commit.
|
||||||
|
|
||||||
|
## Task 6: UI — client + hub methods
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Worker/Hub/WorkerHub.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Services/WorkerClient.cs`
|
||||||
|
- Modify: `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Hub: add `ApproveReview(taskId)`, `RejectReviewToQueue(taskId, feedback)`, `RejectReviewToIdle(taskId)`, `CancelReview(taskId)` — each calls the matching `_state` method via `HubGuard`-style mapping (`if (!result.Ok) throw new HubException(...)`).
|
||||||
|
- [ ] **Step 2:** `IWorkerClient` + `WorkerClient`: add `ApproveReviewAsync`, `RejectReviewToQueueAsync(taskId, feedback)`, `RejectReviewToIdleAsync`, `CancelReviewAsync` invoking the hub methods. Add no-op/stub impls to `StubWorkerClient`.
|
||||||
|
- [ ] **Step 3:** Build App + Ui.Tests. Expected: success.
|
||||||
|
- [ ] **Step 4:** Commit.
|
||||||
|
|
||||||
|
## Task 7: UI — converter, row VM, view buttons
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Converters/StatusColorConverter.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs` (commands)
|
||||||
|
- Modify: the task row/detail AXAML to surface Approve / Reject / Park / Cancel when `IsWaitingForReview`
|
||||||
|
|
||||||
|
- [ ] **Step 1:** `StatusColorConverter`: add `"waiting_for_review" => Brushes.MediumPurple,` (placeholder — user does visual pass).
|
||||||
|
- [ ] **Step 2:** `TaskRowViewModel`: add `public bool IsWaitingForReview => Status == TaskStatus.WaitingForReview;`, raise it in `OnStatusChanged`, and add `(TaskStatus.WaitingForReview, _) => "review"` to `StatusChipClass`.
|
||||||
|
- [ ] **Step 3:** `TasksIslandViewModel`: add relay commands `ApproveReview`, `RejectReviewRerun` (prompts for feedback), `RejectReviewPark`, `CancelReview` operating on the selected/target row, calling the new client methods.
|
||||||
|
- [ ] **Step 4:** Add buttons to the relevant view bound to those commands, visible when `IsWaitingForReview`. Reject-rerun uses a text-input flyout/dialog for required feedback.
|
||||||
|
- [ ] **Step 5:** Build App + Ui.Tests. Expected: success. (Visual layout: flagged for user's visual pass — cannot render here.)
|
||||||
|
- [ ] **Step 6:** Commit.
|
||||||
|
|
||||||
|
## Task 8: Docs + full verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: root `CLAUDE.md`, `src/ClaudeDo.Data/CLAUDE.md`, `src/ClaudeDo.Worker/CLAUDE.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Update status flow lines + worker transition table to include `WaitingForReview` and the new transitions.
|
||||||
|
- [ ] **Step 2:** Build all projects (csproj individually — `.slnx` needs .NET 9) and run `dotnet test tests/ClaudeDo.Worker.Tests`, `tests/ClaudeDo.Ui.Tests`, `tests/ClaudeDo.Data.Tests`. Expected: all green.
|
||||||
|
- [ ] **Step 3:** Commit.
|
||||||
|
|
||||||
|
## Self-Review notes
|
||||||
|
|
||||||
|
- Spec coverage: §1 state machine → Tasks 2,3; §2 data → Task 1; §3 transitions → Task 2; §4 resume → Task 4; §5 MCP → Task 5; §6 hub → Task 6; §7 UI → Tasks 6,7; §8 docs → Task 8; testing → Tasks 2,4,8.
|
||||||
|
- Method names consistent across tasks: `SubmitForReviewAsync`, `ApproveReviewAsync`, `RejectToQueueAsync`, `RejectToIdleAsync`, `ClearReviewFeedbackAsync` (state); `ApproveReview`/`RejectReviewToQueue`/`RejectReviewToIdle`/`CancelReview` (hub); `ApproveReviewAsync`/`RejectReviewToQueueAsync`/`RejectReviewToIdleAsync`/`CancelReviewAsync` (client).
|
||||||
829
docs/superpowers/plans/2026-06-01-worker-lifecycle.md
Normal file
829
docs/superpowers/plans/2026-06-01-worker-lifecycle.md
Normal file
@@ -0,0 +1,829 @@
|
|||||||
|
# Worker Lifecycle Redesign Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Make the worker owned by a single external mechanism (a per-user Startup-folder shortcut in production), stop the App from auto-spawning its own worker, and show an actionable prompt when the App can't connect.
|
||||||
|
|
||||||
|
**Architecture:** Installer creates a `.lnk` in the Windows Startup folder instead of a Scheduled Task (migrating existing installs by deleting the old task). The App's `IslandsShellViewModel` drops `EnsureWorkerRunningAsync` and instead runs a one-shot grace timer that opens a `WorkerConnectionModal` (Start Worker / Rerun Installer / Dismiss) if still offline; the footer status pill becomes a button that reopens it.
|
||||||
|
|
||||||
|
**Tech Stack:** .NET 8, WPF installer (COM `IShellLink` for shortcuts), Avalonia + CommunityToolkit.Mvvm UI, xUnit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
**Installer (`src/ClaudeDo.Installer`)**
|
||||||
|
- Create: `Core/ShortcutFactory.cs` — shared `IShellLink` COM helper (`CreateShortcut`).
|
||||||
|
- Create: `Core/AutostartShortcut.cs` — install/remove the worker Startup-folder `.lnk`.
|
||||||
|
- Modify: `Steps/CreateShortcutsStep.cs` — use `ShortcutFactory`, drop embedded COM.
|
||||||
|
- Modify: `Steps/RegisterAutostartStep.cs` — Startup shortcut + legacy-task delete (no more task XML).
|
||||||
|
- Modify: `Steps/StartWorkerStep.cs` — `Process.Start` instead of `schtasks /Run`.
|
||||||
|
- Modify: `Steps/StopWorkerStep.cs` — drop `schtasks /End`.
|
||||||
|
- Modify: `Core/UninstallRunner.cs` — remove the Startup `.lnk`.
|
||||||
|
- Delete: `Core/ScheduledTaskXml.cs` (and its test).
|
||||||
|
|
||||||
|
**App (`src/ClaudeDo.Ui`)**
|
||||||
|
- Create: `ViewModels/Modals/WorkerConnectionModalViewModel.cs`.
|
||||||
|
- Create: `Views/Modals/WorkerConnectionModalView.axaml` (+ `.axaml.cs`).
|
||||||
|
- Modify: `ViewModels/IslandsShellViewModel.cs` — remove auto-spawn; add hook, command, grace timer, decision gate.
|
||||||
|
- Modify: `Views/MainWindow.axaml.cs` — wire the new modal.
|
||||||
|
- Modify: `Views/MainWindow.axaml` — clickable status pill.
|
||||||
|
|
||||||
|
**Tests**
|
||||||
|
- Modify: `tests/ClaudeDo.Installer.Tests/` — delete `ScheduledTaskXmlTests.cs`; add `ShortcutFactoryTests.cs`, `AutostartShortcutTests.cs`.
|
||||||
|
- Add: `tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: ShortcutFactory (shared COM helper)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ClaudeDo.Installer/Core/ShortcutFactory.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Installer/Steps/CreateShortcutsStep.cs`
|
||||||
|
- Test: `tests/ClaudeDo.Installer.Tests/ShortcutFactoryTests.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
|
`tests/ClaudeDo.Installer.Tests/ShortcutFactoryTests.cs`:
|
||||||
|
```csharp
|
||||||
|
using System.IO;
|
||||||
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Tests;
|
||||||
|
|
||||||
|
public class ShortcutFactoryTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateShortcut_writes_lnk_file()
|
||||||
|
{
|
||||||
|
var dir = Path.Combine(Path.GetTempPath(), "cdshortcut-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var target = Path.Combine(dir, "fake.exe");
|
||||||
|
File.WriteAllText(target, "");
|
||||||
|
var lnk = Path.Combine(dir, "x.lnk");
|
||||||
|
|
||||||
|
ShortcutFactory.CreateShortcut(lnk, target, dir, "desc");
|
||||||
|
|
||||||
|
Assert.True(File.Exists(lnk));
|
||||||
|
}
|
||||||
|
finally { Directory.Delete(dir, recursive: true); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Installer.Tests --filter ShortcutFactoryTests`
|
||||||
|
Expected: FAIL — `ShortcutFactory` does not exist (compile error).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Create `ShortcutFactory` (move COM interop out of `CreateShortcutsStep`)**
|
||||||
|
|
||||||
|
`src/ClaudeDo.Installer/Core/ShortcutFactory.cs`:
|
||||||
|
```csharp
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
public static class ShortcutFactory
|
||||||
|
{
|
||||||
|
public static void CreateShortcut(string shortcutPath, string targetPath, string workingDir, string description)
|
||||||
|
{
|
||||||
|
var link = (IShellLink)new ShellLink();
|
||||||
|
link.SetPath(targetPath);
|
||||||
|
link.SetWorkingDirectory(workingDir);
|
||||||
|
link.SetDescription(description);
|
||||||
|
link.SetIconLocation(targetPath, 0);
|
||||||
|
|
||||||
|
var file = (IPersistFile)link;
|
||||||
|
file.Save(shortcutPath, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||||
|
private class ShellLink { }
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||||
|
private interface IShellLink
|
||||||
|
{
|
||||||
|
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, int fFlags);
|
||||||
|
void GetIDList(out IntPtr ppidl);
|
||||||
|
void SetIDList(IntPtr pidl);
|
||||||
|
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||||
|
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||||
|
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||||
|
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||||
|
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
||||||
|
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||||
|
void GetHotkey(out short pwHotkey);
|
||||||
|
void SetHotkey(short wHotkey);
|
||||||
|
void GetShowCmd(out int piShowCmd);
|
||||||
|
void SetShowCmd(int iShowCmd);
|
||||||
|
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
||||||
|
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||||
|
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
||||||
|
void Resolve(IntPtr hwnd, int fFlags);
|
||||||
|
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Replace the embedded COM in `CreateShortcutsStep` with the helper**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Installer/Steps/CreateShortcutsStep.cs`: delete the private `CreateShortcut` method and the entire `#region COM Interop for IShellLink` block (lines 47-90), remove the now-unused `using System.Runtime.InteropServices;`, `using System.Runtime.InteropServices.ComTypes;`, and `using System.Text;`. Replace the two `CreateShortcut(...)` call sites with `ShortcutFactory.CreateShortcut(...)`:
|
||||||
|
```csharp
|
||||||
|
ShortcutFactory.CreateShortcut(startMenuPath, appExe, workingDir, "ClaudeDo Task Manager");
|
||||||
|
```
|
||||||
|
```csharp
|
||||||
|
ShortcutFactory.CreateShortcut(desktopPath, appExe, workingDir, "ClaudeDo Task Manager");
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run tests to verify they pass**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Installer.Tests --filter ShortcutFactoryTests`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Installer/Core/ShortcutFactory.cs src/ClaudeDo.Installer/Steps/CreateShortcutsStep.cs tests/ClaudeDo.Installer.Tests/ShortcutFactoryTests.cs
|
||||||
|
git commit -m "refactor(installer): extract ShortcutFactory COM helper"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: AutostartShortcut helper
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ClaudeDo.Installer/Core/AutostartShortcut.cs`
|
||||||
|
- Test: `tests/ClaudeDo.Installer.Tests/AutostartShortcutTests.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing tests**
|
||||||
|
|
||||||
|
`tests/ClaudeDo.Installer.Tests/AutostartShortcutTests.cs`:
|
||||||
|
```csharp
|
||||||
|
using System.IO;
|
||||||
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Tests;
|
||||||
|
|
||||||
|
public class AutostartShortcutTests
|
||||||
|
{
|
||||||
|
private static string TempDir()
|
||||||
|
{
|
||||||
|
var dir = Path.Combine(Path.GetTempPath(), "cdautostart-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Install_creates_lnk_with_expected_name()
|
||||||
|
{
|
||||||
|
var startup = TempDir();
|
||||||
|
var workerDir = TempDir();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var workerExe = Path.Combine(workerDir, "ClaudeDo.Worker.exe");
|
||||||
|
File.WriteAllText(workerExe, "");
|
||||||
|
|
||||||
|
AutostartShortcut.Install(startup, workerExe);
|
||||||
|
|
||||||
|
Assert.True(File.Exists(Path.Combine(startup, AutostartShortcut.FileName)));
|
||||||
|
}
|
||||||
|
finally { Directory.Delete(startup, true); Directory.Delete(workerDir, true); }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Remove_deletes_existing_lnk()
|
||||||
|
{
|
||||||
|
var startup = TempDir();
|
||||||
|
var workerDir = TempDir();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var workerExe = Path.Combine(workerDir, "ClaudeDo.Worker.exe");
|
||||||
|
File.WriteAllText(workerExe, "");
|
||||||
|
AutostartShortcut.Install(startup, workerExe);
|
||||||
|
|
||||||
|
AutostartShortcut.Remove(startup);
|
||||||
|
|
||||||
|
Assert.False(File.Exists(Path.Combine(startup, AutostartShortcut.FileName)));
|
||||||
|
}
|
||||||
|
finally { Directory.Delete(startup, true); Directory.Delete(workerDir, true); }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Remove_is_noop_when_missing()
|
||||||
|
{
|
||||||
|
var startup = TempDir();
|
||||||
|
try { AutostartShortcut.Remove(startup); } // must not throw
|
||||||
|
finally { Directory.Delete(startup, true); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests to verify they fail**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Installer.Tests --filter AutostartShortcutTests`
|
||||||
|
Expected: FAIL — `AutostartShortcut` does not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Create `AutostartShortcut`**
|
||||||
|
|
||||||
|
`src/ClaudeDo.Installer/Core/AutostartShortcut.cs`:
|
||||||
|
```csharp
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
public static class AutostartShortcut
|
||||||
|
{
|
||||||
|
public const string FileName = "ClaudeDo Worker.lnk";
|
||||||
|
|
||||||
|
public static string DefaultStartupDir =>
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.Startup);
|
||||||
|
|
||||||
|
public static string PathIn(string startupDir) => Path.Combine(startupDir, FileName);
|
||||||
|
|
||||||
|
public static void Install(string startupDir, string workerExe)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(startupDir);
|
||||||
|
var workingDir = Path.GetDirectoryName(workerExe) ?? startupDir;
|
||||||
|
ShortcutFactory.CreateShortcut(PathIn(startupDir), workerExe, workingDir, "ClaudeDo background worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Remove(string startupDir)
|
||||||
|
{
|
||||||
|
var path = PathIn(startupDir);
|
||||||
|
if (File.Exists(path)) File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests to verify they pass**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Installer.Tests --filter AutostartShortcutTests`
|
||||||
|
Expected: PASS (3 tests).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Installer/Core/AutostartShortcut.cs tests/ClaudeDo.Installer.Tests/AutostartShortcutTests.cs
|
||||||
|
git commit -m "feat(installer): add AutostartShortcut helper for Startup-folder lnk"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: RegisterAutostartStep → Startup shortcut + task migration
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Installer/Steps/RegisterAutostartStep.cs`
|
||||||
|
- Delete: `src/ClaudeDo.Installer/Core/ScheduledTaskXml.cs`
|
||||||
|
- Delete: `tests/ClaudeDo.Installer.Tests/ScheduledTaskXmlTests.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace the step body**
|
||||||
|
|
||||||
|
Rewrite `src/ClaudeDo.Installer/Steps/RegisterAutostartStep.cs` to:
|
||||||
|
```csharp
|
||||||
|
using System.IO;
|
||||||
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Steps;
|
||||||
|
|
||||||
|
public sealed class RegisterAutostartStep : IInstallStep
|
||||||
|
{
|
||||||
|
public const string LegacyTaskName = "ClaudeDoWorker";
|
||||||
|
private const string LegacyServiceName = "ClaudeDoWorker";
|
||||||
|
|
||||||
|
public string Name => "Register Autostart";
|
||||||
|
|
||||||
|
public async Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var workerExe = Path.Combine(ctx.InstallDirectory, "worker", "ClaudeDo.Worker.exe");
|
||||||
|
if (!File.Exists(workerExe))
|
||||||
|
return StepResult.Fail($"Worker executable not found: {workerExe}");
|
||||||
|
|
||||||
|
// 1) Migrate away the legacy Windows service if present.
|
||||||
|
progress.Report("Checking for legacy worker service...");
|
||||||
|
var (queryExit, _) = await ProcessRunner.RunAsync("sc.exe", $"query {LegacyServiceName}", null, progress, ct);
|
||||||
|
if (queryExit == 0)
|
||||||
|
{
|
||||||
|
progress.Report("Removing legacy worker service...");
|
||||||
|
await ProcessRunner.RunAsync("sc.exe", $"stop {LegacyServiceName}", null, progress, ct);
|
||||||
|
await ProcessRunner.RunAsync("sc.exe", $"delete {LegacyServiceName}", null, progress, ct);
|
||||||
|
for (var i = 0; i < 30; i++)
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
var (q, _) = await ProcessRunner.RunAsync("sc.exe", $"query {LegacyServiceName}", null, progress, ct);
|
||||||
|
if (q != 0) break;
|
||||||
|
await Task.Delay(1000, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Migrate away the legacy logon scheduled task if present (best-effort).
|
||||||
|
progress.Report("Removing legacy logon task...");
|
||||||
|
await ProcessRunner.RunAsync("schtasks.exe", $"/Delete /TN \"{LegacyTaskName}\" /F", null, progress, ct);
|
||||||
|
|
||||||
|
// 3) Register per-user autostart via a Startup-folder shortcut.
|
||||||
|
progress.Report("Creating Startup shortcut...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AutostartShortcut.Install(AutostartShortcut.DefaultStartupDir, workerExe);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StepResult.Fail($"Failed to create Startup shortcut: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return StepResult.Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Delete the obsolete scheduled-task code and its test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
git rm src/ClaudeDo.Installer/Core/ScheduledTaskXml.cs tests/ClaudeDo.Installer.Tests/ScheduledTaskXmlTests.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build the installer to verify it compiles**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.Installer/ClaudeDo.Installer.csproj`
|
||||||
|
Expected: Build succeeded. (If `RegisterAutostartStep.TaskName` was referenced elsewhere, the build will flag it — Task 4 and Task 5 update those references; if the build fails only there, proceed to those tasks before re-running.)
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Installer/Steps/RegisterAutostartStep.cs
|
||||||
|
git commit -m "feat(installer): register autostart via Startup shortcut, drop scheduled task"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: StartWorkerStep + StopWorkerStep
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Installer/Steps/StartWorkerStep.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Installer/Steps/StopWorkerStep.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Rewrite `StartWorkerStep` to launch the exe directly**
|
||||||
|
|
||||||
|
`src/ClaudeDo.Installer/Steps/StartWorkerStep.cs`:
|
||||||
|
```csharp
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Steps;
|
||||||
|
|
||||||
|
public sealed class StartWorkerStep : IInstallStep
|
||||||
|
{
|
||||||
|
public string Name => "Start Worker";
|
||||||
|
|
||||||
|
public Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var workerExe = Path.Combine(ctx.InstallDirectory, "worker", "ClaudeDo.Worker.exe");
|
||||||
|
if (!File.Exists(workerExe))
|
||||||
|
return Task.FromResult(StepResult.Fail($"Worker executable not found: {workerExe}"));
|
||||||
|
|
||||||
|
progress.Report("Starting worker...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(workerExe) { UseShellExecute = true });
|
||||||
|
return Task.FromResult(StepResult.Ok());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Task.FromResult(StepResult.Fail($"Failed to start worker: {ex.Message}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Drop the `schtasks /End` call in `StopWorkerStep`**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Installer/Steps/StopWorkerStep.cs`, remove these two lines (the task no longer exists; the process kill below is the real stop):
|
||||||
|
```csharp
|
||||||
|
progress.Report("Stopping worker task (if running)...");
|
||||||
|
await ProcessRunner.RunAsync("schtasks.exe", $"/End /TN \"{TaskName}\"", null, progress, ct);
|
||||||
|
```
|
||||||
|
Keep the `public const string TaskName = "ClaudeDoWorker";` line — `UninstallRunner` still references it for legacy-task cleanup (Task 5). The method keeps its `async` modifier (it still has `await Task.CompletedTask;`).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build to verify it compiles**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.Installer/ClaudeDo.Installer.csproj`
|
||||||
|
Expected: Build succeeded.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Installer/Steps/StartWorkerStep.cs src/ClaudeDo.Installer/Steps/StopWorkerStep.cs
|
||||||
|
git commit -m "feat(installer): start worker via Process.Start, drop schtasks stop"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: UninstallRunner removes the Startup shortcut
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Installer/Core/UninstallRunner.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add Startup `.lnk` removal**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Installer/Core/UninstallRunner.cs`, the shortcut-removal block (step 4, around lines 53-60) currently removes the Desktop and Start Menu `.lnk`s. Add the Startup shortcut removal right after them:
|
||||||
|
```csharp
|
||||||
|
// 4) Remove shortcuts (best-effort — a stuck .lnk must not block the rest).
|
||||||
|
progress.Report("Removing shortcuts...");
|
||||||
|
TryDeleteFile(Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory),
|
||||||
|
"ClaudeDo.lnk"));
|
||||||
|
TryDeleteFile(Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
|
||||||
|
"Programs", "ClaudeDo.lnk"));
|
||||||
|
TryDeleteFile(AutostartShortcut.PathIn(AutostartShortcut.DefaultStartupDir));
|
||||||
|
```
|
||||||
|
The existing `schtasks /Delete /TN "{StopWorkerStep.TaskName}" /F` line (step 3) stays — it cleans up the legacy task on machines that still have it.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build to verify it compiles**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.Installer/ClaudeDo.Installer.csproj`
|
||||||
|
Expected: Build succeeded.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Installer/Core/UninstallRunner.cs
|
||||||
|
git commit -m "feat(installer): remove Startup worker shortcut on uninstall"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: App stops auto-spawning the worker
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Remove the auto-spawn call**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs`, delete this line from the constructor (line 224):
|
||||||
|
```csharp
|
||||||
|
_ = EnsureWorkerRunningAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Remove the `EnsureWorkerRunningAsync` method and its flag**
|
||||||
|
|
||||||
|
Delete the `_ensureRunningAttempted` field (line 308) and the whole `EnsureWorkerRunningAsync` method (lines 310-320):
|
||||||
|
```csharp
|
||||||
|
private bool _ensureRunningAttempted;
|
||||||
|
|
||||||
|
private async Task EnsureWorkerRunningAsync()
|
||||||
|
{
|
||||||
|
if (_ensureRunningAttempted) return;
|
||||||
|
_ensureRunningAttempted = true;
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(4));
|
||||||
|
if (Worker?.IsConnected == true) return;
|
||||||
|
var exe = _workerLocator.Find();
|
||||||
|
if (exe is null) return;
|
||||||
|
try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(exe) { UseShellExecute = true }); }
|
||||||
|
catch { /* logon task is the primary mechanism; this is a convenience */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Keep `RestartWorkerAsync` / `RestartWorkerService` (still used by the existing Restart button). `_workerLocator` stays in use (RestartWorkerService + Task 8).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build to verify it compiles**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: Build succeeded (no remaining references to `EnsureWorkerRunningAsync`).
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
|
||||||
|
git commit -m "refactor(ui): stop auto-spawning the worker on app start"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 7: WorkerConnectionModal (VM + View)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ClaudeDo.Ui/ViewModels/Modals/WorkerConnectionModalViewModel.cs`
|
||||||
|
- Create: `src/ClaudeDo.Ui/Views/Modals/WorkerConnectionModalView.axaml`
|
||||||
|
- Create: `src/ClaudeDo.Ui/Views/Modals/WorkerConnectionModalView.axaml.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the ViewModel**
|
||||||
|
|
||||||
|
`src/ClaudeDo.Ui/ViewModels/Modals/WorkerConnectionModalViewModel.cs`:
|
||||||
|
```csharp
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using ClaudeDo.Ui.Services;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.ViewModels.Modals;
|
||||||
|
|
||||||
|
public sealed partial class WorkerConnectionModalViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly WorkerLocator _workerLocator;
|
||||||
|
private readonly InstallerLocator _installerLocator;
|
||||||
|
|
||||||
|
public WorkerConnectionModalViewModel(WorkerLocator workerLocator, InstallerLocator installerLocator)
|
||||||
|
{
|
||||||
|
_workerLocator = workerLocator;
|
||||||
|
_installerLocator = installerLocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action? CloseAction { get; set; }
|
||||||
|
|
||||||
|
[RelayCommand] private void Close() => CloseAction?.Invoke();
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void StartWorker()
|
||||||
|
{
|
||||||
|
var exe = _workerLocator.Find();
|
||||||
|
if (exe is null) return;
|
||||||
|
try { Process.Start(new ProcessStartInfo(exe) { UseShellExecute = true }); }
|
||||||
|
catch { /* nothing useful to show */ }
|
||||||
|
CloseAction?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void RerunInstaller()
|
||||||
|
{
|
||||||
|
var path = _installerLocator.Find();
|
||||||
|
if (path is null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
catch { /* nothing useful to show */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Create the View (mirrors `AboutModalView` + `ModalShell`)**
|
||||||
|
|
||||||
|
`src/ClaudeDo.Ui/Views/Modals/WorkerConnectionModalView.axaml`:
|
||||||
|
```xml
|
||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
||||||
|
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
||||||
|
x:Class="ClaudeDo.Ui.Views.Modals.WorkerConnectionModalView"
|
||||||
|
x:DataType="vm:WorkerConnectionModalViewModel"
|
||||||
|
Title="Worker not reachable"
|
||||||
|
Width="520" Height="240"
|
||||||
|
WindowDecorations="None"
|
||||||
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
|
<Window.KeyBindings>
|
||||||
|
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||||
|
</Window.KeyBindings>
|
||||||
|
|
||||||
|
<ctl:ModalShell Title="WORKER NOT REACHABLE" CloseCommand="{Binding CloseCommand}">
|
||||||
|
<Grid RowDefinitions="*,Auto" Margin="20,16">
|
||||||
|
<TextBlock Grid.Row="0" Classes="meta" TextWrapping="Wrap"
|
||||||
|
Text="ClaudeDo can't reach the background worker. It is normally started automatically at logon. You can start it now, or reinstall if the problem persists."/>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="8"
|
||||||
|
HorizontalAlignment="Right" Margin="0,16,0,0">
|
||||||
|
<Button Classes="btn" Content="Dismiss" Command="{Binding CloseCommand}"/>
|
||||||
|
<Button Classes="btn" Content="Rerun Installer" Command="{Binding RerunInstallerCommand}"/>
|
||||||
|
<Button Classes="btn primary" Content="Start Worker" Command="{Binding StartWorkerCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ctl:ModalShell>
|
||||||
|
</Window>
|
||||||
|
```
|
||||||
|
|
||||||
|
`src/ClaudeDo.Ui/Views/Modals/WorkerConnectionModalView.axaml.cs`:
|
||||||
|
```csharp
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Views.Modals;
|
||||||
|
|
||||||
|
public partial class WorkerConnectionModalView : Window
|
||||||
|
{
|
||||||
|
public WorkerConnectionModalView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build to verify it compiles**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: Build succeeded.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/ViewModels/Modals/WorkerConnectionModalViewModel.cs src/ClaudeDo.Ui/Views/Modals/WorkerConnectionModalView.axaml src/ClaudeDo.Ui/Views/Modals/WorkerConnectionModalView.axaml.cs
|
||||||
|
git commit -m "feat(ui): add worker connection help modal"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 8: Shell hook, command, grace timer + decision gate
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs`
|
||||||
|
- Test: `tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing test for the decision gate**
|
||||||
|
|
||||||
|
`tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs`:
|
||||||
|
```csharp
|
||||||
|
using ClaudeDo.Ui.ViewModels;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Tests;
|
||||||
|
|
||||||
|
public class ConnectionPromptGateTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Shows_once_when_offline()
|
||||||
|
{
|
||||||
|
var vm = new IslandsShellViewModel();
|
||||||
|
Assert.True(vm.DecideShowConnectionPrompt(isOffline: true));
|
||||||
|
Assert.False(vm.DecideShowConnectionPrompt(isOffline: true)); // not a second time
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Does_not_show_when_connected_before_grace()
|
||||||
|
{
|
||||||
|
var vm = new IslandsShellViewModel();
|
||||||
|
Assert.False(vm.DecideShowConnectionPrompt(isOffline: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter ConnectionPromptGateTests`
|
||||||
|
Expected: FAIL — `DecideShowConnectionPrompt` does not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add the hook, command, gate, and grace timer**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs`:
|
||||||
|
|
||||||
|
Add a hook property near the other `Show*Modal` hooks (after line 52):
|
||||||
|
```csharp
|
||||||
|
// Set by MainWindow to open the worker-connection help dialog.
|
||||||
|
public Func<Modals.WorkerConnectionModalViewModel, Task>? ShowWorkerConnectionModal { get; set; }
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the gate field + method and the open command (place near `OpenAbout`, around line 271):
|
||||||
|
```csharp
|
||||||
|
private bool _connectionPromptShown;
|
||||||
|
|
||||||
|
internal bool DecideShowConnectionPrompt(bool isOffline)
|
||||||
|
{
|
||||||
|
if (!isOffline) return false;
|
||||||
|
if (_connectionPromptShown) return false;
|
||||||
|
_connectionPromptShown = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenWorkerConnectionHelpAsync()
|
||||||
|
{
|
||||||
|
var vm = new Modals.WorkerConnectionModalViewModel(_workerLocator, _installerLocator);
|
||||||
|
if (ShowWorkerConnectionModal is not null) await ShowWorkerConnectionModal(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private Task OpenWorkerConnectionHelp() => OpenWorkerConnectionHelpAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the grace timer field near `_clearTimer` (line 74):
|
||||||
|
```csharp
|
||||||
|
private readonly System.Timers.Timer _connectTimer = new(12_000) { AutoReset = false };
|
||||||
|
```
|
||||||
|
|
||||||
|
Wire and start it inside the **public** constructor (after the `_primeStatusTimer.Elapsed` wiring, near line 222 — NOT in the parameterless test constructor):
|
||||||
|
```csharp
|
||||||
|
_connectTimer.Elapsed += (_, _) => Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
if (DecideShowConnectionPrompt(IsOffline)) _ = OpenWorkerConnectionHelpAsync();
|
||||||
|
});
|
||||||
|
_connectTimer.Start();
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests to verify they pass**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter ConnectionPromptGateTests`
|
||||||
|
Expected: PASS (2 tests).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Build the app**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: Build succeeded.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs
|
||||||
|
git commit -m "feat(ui): prompt once on worker connection failure with grace timer"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 9: Wire the modal in MainWindow + clickable status pill
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Views/MainWindow.axaml.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Views/MainWindow.axaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Wire the dialog hook**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Ui/Views/MainWindow.axaml.cs`, inside `OnDataContextChanged`, after the existing `vm.ShowRepoImportModal = ...` block (line 70), add:
|
||||||
|
```csharp
|
||||||
|
vm.ShowWorkerConnectionModal = async (connVm) =>
|
||||||
|
{
|
||||||
|
var dlg = new WorkerConnectionModalView { DataContext = connVm };
|
||||||
|
connVm.CloseAction = () => dlg.Close();
|
||||||
|
await dlg.ShowDialog(this);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
(`ClaudeDo.Ui.Views.Modals` is already imported at line 10.)
|
||||||
|
|
||||||
|
- [ ] **Step 2: Make the status pill a button**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Ui/Views/MainWindow.axaml`, replace the left "connection pill" `StackPanel` (lines 190-202) with a `Button` wrapping the same content:
|
||||||
|
```xml
|
||||||
|
<!-- Left: connection pill (click to open worker help) -->
|
||||||
|
<Button DockPanel.Dock="Left"
|
||||||
|
Command="{Binding OpenWorkerConnectionHelpCommand}"
|
||||||
|
Background="Transparent" BorderThickness="0" Padding="0"
|
||||||
|
Cursor="Hand" VerticalAlignment="Center">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
||||||
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusRunningBrush}"
|
||||||
|
IsVisible="{Binding Worker.IsConnected}"/>
|
||||||
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusReviewBrush}"
|
||||||
|
IsVisible="{Binding Worker.IsReconnecting}"/>
|
||||||
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusErrorBrush}"
|
||||||
|
IsVisible="{Binding IsOffline}"/>
|
||||||
|
<TextBlock Classes="eyebrow"
|
||||||
|
Text="{Binding ConnectionText, Converter={StaticResource UpperCase}}"
|
||||||
|
LetterSpacing="1.4"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build the app**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: Build succeeded.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Manual verification**
|
||||||
|
|
||||||
|
Start the worker (or leave it stopped) and run the App:
|
||||||
|
- Worker stopped → after ~12s the "WORKER NOT REACHABLE" dialog appears once. **Start Worker** launches it (footer pill turns ONLINE); **Rerun Installer** launches the installer and exits; **Dismiss** closes and does not reappear automatically.
|
||||||
|
- Click the footer status pill anytime → the dialog reopens.
|
||||||
|
- Worker running before launch → no dialog appears.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/Views/MainWindow.axaml.cs src/ClaudeDo.Ui/Views/MainWindow.axaml
|
||||||
|
git commit -m "feat(ui): wire worker connection modal and make status pill clickable"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 10: Full build + test sweep
|
||||||
|
|
||||||
|
- [ ] **Step 1: Build the touched projects**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj
|
||||||
|
dotnet build src/ClaudeDo.Installer/ClaudeDo.Installer.csproj
|
||||||
|
```
|
||||||
|
Expected: both Build succeeded.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the affected test suites**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
dotnet test tests/ClaudeDo.Installer.Tests
|
||||||
|
dotnet test tests/ClaudeDo.Ui.Tests
|
||||||
|
```
|
||||||
|
Expected: all pass; no references to the deleted `ScheduledTaskXml`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Final commit (if any stragglers)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore: worker lifecycle redesign cleanup" || echo "nothing to commit"
|
||||||
|
```
|
||||||
983
docs/superpowers/plans/2026-06-02-prime-recurring-weekdays.md
Normal file
983
docs/superpowers/plans/2026-06-02-prime-recurring-weekdays.md
Normal file
@@ -0,0 +1,983 @@
|
|||||||
|
# Prime Recurring Weekday Schedule — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Replace the Prime schedule's date-range model with a recurring weekday model — pick a set of weekdays plus a time, and the ping fires on the next eligible day the worker is running.
|
||||||
|
|
||||||
|
**Architecture:** A `[Flags] PrimeDays` weekday bitmask stored as a single `days_of_week` int column replaces `StartDate`/`EndDate`/`WorkdaysOnly`. `NextDueCalculator` walks forward to the next selected weekday; the existing 30-minute catch-up and already-fired-today logic are untouched. UI swaps the range picker + Mon–Fri checkbox for seven toggle buttons. Both SignalR DTO copies carry a single `int Days`.
|
||||||
|
|
||||||
|
**Tech Stack:** .NET 8, EF Core (SQLite), Avalonia 12 (CommunityToolkit.Mvvm), SignalR, xUnit.
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-06-02-prime-recurring-weekdays-design.md`
|
||||||
|
|
||||||
|
**Build/test note:** `dotnet build ClaudeDo.slnx` needs .NET 9; on .NET 8 build individual csproj. Commands in this plan use the per-project form.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
- `src/ClaudeDo.Data/Models/PrimeDays.cs` — **new**, `[Flags]` enum.
|
||||||
|
- `src/ClaudeDo.Data/Models/PrimeScheduleEntity.cs` — swap fields.
|
||||||
|
- `src/ClaudeDo.Data/Configuration/PrimeScheduleEntityConfiguration.cs` — column mapping.
|
||||||
|
- `src/ClaudeDo.Data/Migrations/*` — new migration + snapshot.
|
||||||
|
- `src/ClaudeDo.Data/Repositories/PrimeScheduleRepository.cs` — upsert fields + ordering.
|
||||||
|
- `src/ClaudeDo.Worker/Prime/PrimeScheduleDto.cs` — `int Days`.
|
||||||
|
- `src/ClaudeDo.Worker/Prime/NextDueCalculator.cs` — weekday eligibility.
|
||||||
|
- `src/ClaudeDo.Worker/Prime/PrimeScheduler.cs` — `ToDto` mapping.
|
||||||
|
- `src/ClaudeDo.Worker/Hub/WorkerHub.cs` — list/upsert mapping.
|
||||||
|
- `src/ClaudeDo.Ui/Services/PrimeScheduleDto.cs` — `int Days`.
|
||||||
|
- `src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeScheduleRowViewModel.cs` — 7 day bools.
|
||||||
|
- `src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeClaudeTabViewModel.cs` — defaults + validation.
|
||||||
|
- `src/ClaudeDo.Ui/Design/IslandStyles.axaml` — `day-toggle` style class.
|
||||||
|
- `src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml` — row template.
|
||||||
|
- Tests: `NextDueCalculatorTests`, `PrimeSchedulerTests`, `PrimeScheduleRepositoryTests`, `PrimeClaudeTabViewModelTests`.
|
||||||
|
- Docs: `src/ClaudeDo.Data/CLAUDE.md`, root `CLAUDE.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: PrimeDays enum + entity + configuration
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ClaudeDo.Data/Models/PrimeDays.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Data/Models/PrimeScheduleEntity.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Data/Configuration/PrimeScheduleEntityConfiguration.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the flags enum**
|
||||||
|
|
||||||
|
`src/ClaudeDo.Data/Models/PrimeDays.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum PrimeDays
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Monday = 1,
|
||||||
|
Tuesday = 2,
|
||||||
|
Wednesday = 4,
|
||||||
|
Thursday = 8,
|
||||||
|
Friday = 16,
|
||||||
|
Saturday = 32,
|
||||||
|
Sunday = 64,
|
||||||
|
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday, // 31
|
||||||
|
All = Weekdays | Saturday | Sunday, // 127
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Swap entity fields**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Data/Models/PrimeScheduleEntity.cs`, remove `StartDate`, `EndDate`, `WorkdaysOnly` and add `Days`. Result:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
public sealed class PrimeScheduleEntity
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
public PrimeDays Days { get; set; } = PrimeDays.Weekdays;
|
||||||
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
public DateTimeOffset? LastRunAt { get; set; }
|
||||||
|
public string? PromptOverride { get; set; }
|
||||||
|
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update entity configuration**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Data/Configuration/PrimeScheduleEntityConfiguration.cs`, replace the `start_date`/`end_date`/`workdays_only` property lines with a `days_of_week` mapping (EF maps the enum to INTEGER automatically):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
builder.Property(s => s.Days).HasColumnName("days_of_week")
|
||||||
|
.IsRequired().HasDefaultValue(PrimeDays.Weekdays);
|
||||||
|
builder.Property(s => s.TimeOfDay).HasColumnName("time_of_day").IsRequired();
|
||||||
|
builder.Property(s => s.Enabled).HasColumnName("enabled").IsRequired().HasDefaultValue(true);
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave `Id`, `LastRunAt`, `PromptOverride`, `CreatedAt` mappings unchanged. Add `using ClaudeDo.Data.Models;` if not present (it already is).
|
||||||
|
|
||||||
|
- [ ] **Step 4: Build the Data project**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.Data/ClaudeDo.Data.csproj`
|
||||||
|
Expected: FAILS — `PrimeScheduleRepository`, snapshot, etc. still reference removed fields. That is expected; Tasks 2–3 fix it. (If you prefer a clean build gate, proceed to Task 2 before building.)
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Data/Models/PrimeDays.cs src/ClaudeDo.Data/Models/PrimeScheduleEntity.cs src/ClaudeDo.Data/Configuration/PrimeScheduleEntityConfiguration.cs
|
||||||
|
git commit -m "feat(data): model Prime schedule as weekday bitmask"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: Repository
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Data/Repositories/PrimeScheduleRepository.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update `ListAsync` ordering**
|
||||||
|
|
||||||
|
The old ordering used `StartDate`. Order by `TimeOfDay`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public async Task<IReadOnlyList<PrimeScheduleEntity>> ListAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var rows = await _context.PrimeSchedules.AsNoTracking()
|
||||||
|
.OrderBy(s => s.TimeOfDay)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update `UpsertAsync` field copy**
|
||||||
|
|
||||||
|
Replace the three removed-field assignments with `Days`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existing.Days = entity.Days;
|
||||||
|
existing.TimeOfDay = entity.TimeOfDay;
|
||||||
|
existing.Enabled = entity.Enabled;
|
||||||
|
existing.PromptOverride = entity.PromptOverride;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave `GetAsync`, `DeleteAsync`, `UpdateLastRunAsync` unchanged.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit** (build verified after migration in Task 3)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Data/Repositories/PrimeScheduleRepository.cs
|
||||||
|
git commit -m "feat(data): persist weekday bitmask in prime schedule repo"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: EF migration
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ClaudeDo.Data/Migrations/<timestamp>_PrimeWeekdays.cs` (generated)
|
||||||
|
- Modify: `src/ClaudeDo.Data/Migrations/ClaudeDoDbContextModelSnapshot.cs` (generated)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Generate the migration**
|
||||||
|
|
||||||
|
Run from repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet ef migrations add PrimeWeekdays --project src/ClaudeDo.Data/ClaudeDo.Data.csproj --startup-project src/ClaudeDo.Worker/ClaudeDo.Worker.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: a new `*_PrimeWeekdays.cs` file and an updated snapshot. (If `dotnet ef` is unavailable, hand-write the migration using the body below.)
|
||||||
|
|
||||||
|
- [ ] **Step 2: Replace the generated `Up` body with an explicit backfill**
|
||||||
|
|
||||||
|
EF's auto-generated drop/add would discard existing schedules' weekday intent. Edit the new migration's `Up` to add the column, backfill from `workdays_only`, then drop the old columns:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "days_of_week",
|
||||||
|
table: "prime_schedules",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 31);
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"UPDATE prime_schedules SET days_of_week = CASE WHEN workdays_only = 1 THEN 31 ELSE 127 END;");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(name: "start_date", table: "prime_schedules");
|
||||||
|
migrationBuilder.DropColumn(name: "end_date", table: "prime_schedules");
|
||||||
|
migrationBuilder.DropColumn(name: "workdays_only", table: "prime_schedules");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Replace the generated `Down` body**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateOnly>(
|
||||||
|
name: "start_date", table: "prime_schedules",
|
||||||
|
type: "TEXT", nullable: false, defaultValue: new DateOnly(2000, 1, 1));
|
||||||
|
migrationBuilder.AddColumn<DateOnly>(
|
||||||
|
name: "end_date", table: "prime_schedules",
|
||||||
|
type: "TEXT", nullable: false, defaultValue: new DateOnly(2099, 12, 31));
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "workdays_only", table: "prime_schedules",
|
||||||
|
type: "INTEGER", nullable: false, defaultValue: true);
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"UPDATE prime_schedules SET workdays_only = CASE WHEN days_of_week = 127 THEN 0 ELSE 1 END;");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(name: "days_of_week", table: "prime_schedules");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `using System;` at the top of the migration file if `DateOnly` defaults require it (the existing AddPrimeSchedules migration already imports `System`).
|
||||||
|
|
||||||
|
- [ ] **Step 4: Build the Data project**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.Data/ClaudeDo.Data.csproj`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Data/Migrations
|
||||||
|
git commit -m "feat(data): migrate prime schedules to days_of_week bitmask"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Worker DTO + NextDueCalculator (TDD)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Worker/Prime/PrimeScheduleDto.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Worker/Prime/NextDueCalculator.cs`
|
||||||
|
- Test: `tests/ClaudeDo.Worker.Tests/Prime/NextDueCalculatorTests.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update the Worker DTO**
|
||||||
|
|
||||||
|
`src/ClaudeDo.Worker/Prime/PrimeScheduleDto.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
public sealed record PrimeScheduleDto(
|
||||||
|
Guid Id,
|
||||||
|
int Days,
|
||||||
|
TimeSpan TimeOfDay,
|
||||||
|
bool Enabled,
|
||||||
|
DateTimeOffset? LastRunAt,
|
||||||
|
string? PromptOverride);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Rewrite the calculator tests**
|
||||||
|
|
||||||
|
Replace the entire body of `tests/ClaudeDo.Worker.Tests/Prime/NextDueCalculatorTests.cs`. Note: 2026-05-05 is a Tuesday; 2026-05-08 is a Friday; 2026-05-09/10 are Sat/Sun; 2026-05-11 is a Monday.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Tests.Prime;
|
||||||
|
|
||||||
|
public class NextDueCalculatorTests
|
||||||
|
{
|
||||||
|
private static PrimeScheduleDto Schedule(
|
||||||
|
PrimeDays days, TimeSpan time,
|
||||||
|
bool enabled = true, DateTimeOffset? lastRun = null) =>
|
||||||
|
new(Guid.NewGuid(), (int)days, time, enabled, lastRun, null);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Disabled_Schedule_Returns_Null()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
|
||||||
|
var s = Schedule(PrimeDays.All, new(7, 0, 0), enabled: false);
|
||||||
|
Assert.Null(NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void No_Days_Selected_Returns_Null()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
|
||||||
|
var s = Schedule(PrimeDays.None, new(7, 0, 0));
|
||||||
|
Assert.Null(NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Future_Same_Day_Returns_Today_At_Target()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2)); // Tue
|
||||||
|
var s = Schedule(PrimeDays.All, new(7, 0, 0));
|
||||||
|
var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
|
||||||
|
Assert.NotNull(r);
|
||||||
|
Assert.Equal(new DateTimeOffset(2026, 5, 5, 7, 0, 0, now.Offset), r!.At);
|
||||||
|
Assert.False(r.FireImmediately);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Within_CatchUp_Window_Fires_Immediately()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 5, 7, 15, 0, TimeSpan.FromHours(2));
|
||||||
|
var s = Schedule(PrimeDays.All, new(7, 0, 0));
|
||||||
|
var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
|
||||||
|
Assert.NotNull(r);
|
||||||
|
Assert.True(r!.FireImmediately);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Past_CatchUp_Window_Skips_To_Next_Eligible_Day()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 5, 9, 0, 0, TimeSpan.FromHours(2)); // Tue
|
||||||
|
var s = Schedule(PrimeDays.All, new(7, 0, 0));
|
||||||
|
var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
|
||||||
|
Assert.NotNull(r);
|
||||||
|
Assert.Equal(new DateOnly(2026, 5, 6), DateOnly.FromDateTime(r!.At.LocalDateTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Weekdays_Only_Skips_Weekend()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 8, 8, 0, 0, TimeSpan.FromHours(2)); // Fri, past catch-up
|
||||||
|
var s = Schedule(PrimeDays.Weekdays, new(7, 0, 0));
|
||||||
|
var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
|
||||||
|
Assert.NotNull(r);
|
||||||
|
Assert.Equal(DayOfWeek.Monday, r!.At.LocalDateTime.DayOfWeek);
|
||||||
|
Assert.Equal(new DateOnly(2026, 5, 11), DateOnly.FromDateTime(r.At.LocalDateTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Single_Day_Schedule_Targets_That_Weekday()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 5, 8, 0, 0, TimeSpan.FromHours(2)); // Tue, past catch-up
|
||||||
|
var s = Schedule(PrimeDays.Friday, new(7, 0, 0));
|
||||||
|
var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
|
||||||
|
Assert.NotNull(r);
|
||||||
|
Assert.Equal(DayOfWeek.Friday, r!.At.LocalDateTime.DayOfWeek);
|
||||||
|
Assert.Equal(new DateOnly(2026, 5, 8), DateOnly.FromDateTime(r.At.LocalDateTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Already_Fired_Today_Skips_To_Next_Eligible_Day()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
|
||||||
|
var lastRun = new DateTimeOffset(2026, 5, 5, 7, 1, 0, TimeSpan.FromHours(2));
|
||||||
|
var s = Schedule(PrimeDays.All, new(7, 0, 0), lastRun: lastRun);
|
||||||
|
var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
|
||||||
|
Assert.NotNull(r);
|
||||||
|
Assert.Equal(new DateOnly(2026, 5, 6), DateOnly.FromDateTime(r!.At.LocalDateTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Multiple_Schedules_Returns_Earliest()
|
||||||
|
{
|
||||||
|
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
|
||||||
|
var early = Schedule(PrimeDays.All, new(7, 0, 0));
|
||||||
|
var late = Schedule(PrimeDays.All, new(9, 0, 0));
|
||||||
|
var r = NextDueCalculator.Compute(new[] { late, early }, now, TimeSpan.FromMinutes(30));
|
||||||
|
Assert.NotNull(r);
|
||||||
|
Assert.Equal(early.Id, r!.Schedule.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run the tests to verify they fail**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Worker.Tests --filter FullyQualifiedName~NextDueCalculatorTests`
|
||||||
|
Expected: FAIL — `PrimeScheduleDto` no longer has `StartDate`/`EndDate`/`workdaysOnly`, and the calculator still references them (compile errors).
|
||||||
|
|
||||||
|
- [ ] **Step 4: Rewrite the calculator**
|
||||||
|
|
||||||
|
Replace the entire body of `src/ClaudeDo.Worker/Prime/NextDueCalculator.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
public sealed record NextDue(PrimeScheduleDto Schedule, DateTimeOffset At, bool FireImmediately);
|
||||||
|
|
||||||
|
public static class NextDueCalculator
|
||||||
|
{
|
||||||
|
public static NextDue? Compute(
|
||||||
|
IEnumerable<PrimeScheduleDto> schedules,
|
||||||
|
DateTimeOffset now,
|
||||||
|
TimeSpan catchUp)
|
||||||
|
{
|
||||||
|
NextDue? best = null;
|
||||||
|
foreach (var s in schedules)
|
||||||
|
{
|
||||||
|
if (!s.Enabled) continue;
|
||||||
|
var due = ComputeFor(s, now, catchUp);
|
||||||
|
if (due is null) continue;
|
||||||
|
if (best is null || due.At < best.At) best = due;
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NextDue? ComputeFor(PrimeScheduleDto s, DateTimeOffset now, TimeSpan catchUp)
|
||||||
|
{
|
||||||
|
if ((PrimeDays)s.Days == PrimeDays.None) return null;
|
||||||
|
|
||||||
|
var todayLocal = DateOnly.FromDateTime(now.LocalDateTime);
|
||||||
|
var alreadyFiredToday = s.LastRunAt is { } last &&
|
||||||
|
DateOnly.FromDateTime(last.LocalDateTime) == todayLocal;
|
||||||
|
|
||||||
|
if (!alreadyFiredToday && IsEligibleDay(s, todayLocal))
|
||||||
|
{
|
||||||
|
var todayTarget = ToOffset(todayLocal, s.TimeOfDay, now.Offset);
|
||||||
|
if (todayTarget >= now)
|
||||||
|
return new NextDue(s, todayTarget, false);
|
||||||
|
if (now <= todayTarget + catchUp)
|
||||||
|
return new NextDue(s, now, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var d = todayLocal.AddDays(1);
|
||||||
|
for (int i = 0; i < 7; i++)
|
||||||
|
{
|
||||||
|
if (IsEligibleDay(s, d))
|
||||||
|
return new NextDue(s, ToOffset(d, s.TimeOfDay, now.Offset), false);
|
||||||
|
d = d.AddDays(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsEligibleDay(PrimeScheduleDto s, DateOnly d) =>
|
||||||
|
((PrimeDays)s.Days & ToFlag(d.DayOfWeek)) != PrimeDays.None;
|
||||||
|
|
||||||
|
private static PrimeDays ToFlag(DayOfWeek dow) => dow switch
|
||||||
|
{
|
||||||
|
DayOfWeek.Monday => PrimeDays.Monday,
|
||||||
|
DayOfWeek.Tuesday => PrimeDays.Tuesday,
|
||||||
|
DayOfWeek.Wednesday => PrimeDays.Wednesday,
|
||||||
|
DayOfWeek.Thursday => PrimeDays.Thursday,
|
||||||
|
DayOfWeek.Friday => PrimeDays.Friday,
|
||||||
|
DayOfWeek.Saturday => PrimeDays.Saturday,
|
||||||
|
DayOfWeek.Sunday => PrimeDays.Sunday,
|
||||||
|
_ => PrimeDays.None,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static DateTimeOffset ToOffset(DateOnly day, TimeSpan time, TimeSpan offset) =>
|
||||||
|
new(day.Year, day.Month, day.Day, time.Hours, time.Minutes, time.Seconds, offset);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run the calculator tests**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Worker.Tests --filter FullyQualifiedName~NextDueCalculatorTests`
|
||||||
|
Expected: still FAILS to build — `PrimeScheduler.ToDto` and `WorkerHub` mappings reference removed fields. Proceed to Tasks 5–6, then re-run.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Worker/Prime/PrimeScheduleDto.cs src/ClaudeDo.Worker/Prime/NextDueCalculator.cs tests/ClaudeDo.Worker.Tests/Prime/NextDueCalculatorTests.cs
|
||||||
|
git commit -m "feat(worker): compute prime due-time from weekday bitmask"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: PrimeScheduler.ToDto + scheduler tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Worker/Prime/PrimeScheduler.cs:104-105`
|
||||||
|
- Test: `tests/ClaudeDo.Worker.Tests/Prime/PrimeSchedulerTests.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update the `ToDto` mapping**
|
||||||
|
|
||||||
|
Replace the `ToDto` method in `PrimeScheduler.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private static PrimeScheduleDto ToDto(Data.Models.PrimeScheduleEntity e) =>
|
||||||
|
new(e.Id, (int)e.Days, e.TimeOfDay, e.Enabled, e.LastRunAt, e.PromptOverride);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update scheduler test fixtures**
|
||||||
|
|
||||||
|
In `tests/ClaudeDo.Worker.Tests/Prime/PrimeSchedulerTests.cs`, every `new PrimeScheduleEntity { ... }` initializer sets `StartDate`/`EndDate`/`WorkdaysOnly`. Replace those three lines in each of the three initializers (lines ~48-52, ~89-94, ~131-136) with a single `Days` assignment. Each initializer becomes:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await new PrimeScheduleRepository(ctx).UpsertAsync(new PrimeScheduleEntity
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Days = PrimeDays.All,
|
||||||
|
TimeOfDay = new TimeSpan(7, 0, 0),
|
||||||
|
Enabled = true,
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `using ClaudeDo.Data.Models;` to the file's usings if not already present (it is, via line 1).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run scheduler + calculator tests**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Worker.Tests --filter "FullyQualifiedName~Prime"`
|
||||||
|
Expected: still build-fails until `WorkerHub` (Task 6) compiles. After Task 6, this command must PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Worker/Prime/PrimeScheduler.cs tests/ClaudeDo.Worker.Tests/Prime/PrimeSchedulerTests.cs
|
||||||
|
git commit -m "test(worker): adapt prime scheduler tests to weekday model"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: WorkerHub mapping + repository tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Worker/Hub/WorkerHub.cs:488-518`
|
||||||
|
- Test: `tests/ClaudeDo.Worker.Tests/Repositories/PrimeScheduleRepositoryTests.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update `ListPrimeSchedules`**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public async Task<List<PrimeScheduleDto>> ListPrimeSchedules()
|
||||||
|
{
|
||||||
|
using var ctx = _dbFactory.CreateDbContext();
|
||||||
|
var rows = await new PrimeScheduleRepository(ctx).ListAsync();
|
||||||
|
return rows.Select(e => new PrimeScheduleDto(
|
||||||
|
e.Id, (int)e.Days, e.TimeOfDay, e.Enabled, e.LastRunAt, e.PromptOverride)).ToList();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update `UpsertPrimeSchedule`**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public async Task<PrimeScheduleDto> UpsertPrimeSchedule(PrimeScheduleDto dto)
|
||||||
|
{
|
||||||
|
using var ctx = _dbFactory.CreateDbContext();
|
||||||
|
var repo = new PrimeScheduleRepository(ctx);
|
||||||
|
var existing = await repo.GetAsync(dto.Id);
|
||||||
|
var entity = new ClaudeDo.Data.Models.PrimeScheduleEntity
|
||||||
|
{
|
||||||
|
Id = dto.Id == Guid.Empty ? Guid.NewGuid() : dto.Id,
|
||||||
|
Days = (ClaudeDo.Data.Models.PrimeDays)dto.Days,
|
||||||
|
TimeOfDay = dto.TimeOfDay,
|
||||||
|
Enabled = dto.Enabled,
|
||||||
|
PromptOverride = dto.PromptOverride,
|
||||||
|
CreatedAt = existing?.CreatedAt ?? DateTimeOffset.UtcNow,
|
||||||
|
LastRunAt = existing?.LastRunAt,
|
||||||
|
};
|
||||||
|
await repo.UpsertAsync(entity);
|
||||||
|
_primeSignal.Signal();
|
||||||
|
return new PrimeScheduleDto(entity.Id, (int)entity.Days, entity.TimeOfDay,
|
||||||
|
entity.Enabled, entity.LastRunAt, entity.PromptOverride);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`DeletePrimeSchedule` is unchanged.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update repository tests**
|
||||||
|
|
||||||
|
In `tests/ClaudeDo.Worker.Tests/Repositories/PrimeScheduleRepositoryTests.cs`, replace each entity initializer's `StartDate`/`EndDate`/`WorkdaysOnly` lines with `Days = PrimeDays.Weekdays,` (drop them where only `StartDate`/`EndDate` appear). The three initializers become:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Upsert_Then_List_RoundTrips
|
||||||
|
await new PrimeScheduleRepository(ctx).UpsertAsync(new PrimeScheduleEntity
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Days = PrimeDays.Weekdays,
|
||||||
|
TimeOfDay = new TimeSpan(7, 0, 0),
|
||||||
|
Enabled = true,
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// UpdateLastRunAt_Persists
|
||||||
|
await new PrimeScheduleRepository(ctx).UpsertAsync(new PrimeScheduleEntity
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Days = PrimeDays.Weekdays,
|
||||||
|
TimeOfDay = new TimeSpan(7, 0, 0),
|
||||||
|
Enabled = true,
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Delete_Removes_Row
|
||||||
|
await new PrimeScheduleRepository(ctx).UpsertAsync(new PrimeScheduleEntity
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Days = PrimeDays.All,
|
||||||
|
TimeOfDay = TimeSpan.Zero,
|
||||||
|
Enabled = true,
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Add an assertion in `Upsert_Then_List_RoundTrips` after the existing time assertion:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Assert.Equal(PrimeDays.Weekdays, rows[0].Days);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Build worker + run all worker tests**
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj && dotnet test tests/ClaudeDo.Worker.Tests`
|
||||||
|
Expected: PASS (all Prime + repository tests green).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Worker/Hub/WorkerHub.cs tests/ClaudeDo.Worker.Tests/Repositories/PrimeScheduleRepositoryTests.cs
|
||||||
|
git commit -m "feat(worker): map prime schedule weekday bitmask over the hub"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 7: UI DTO + ViewModels + tests (TDD)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Services/PrimeScheduleDto.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeScheduleRowViewModel.cs`
|
||||||
|
- Modify: `src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeClaudeTabViewModel.cs`
|
||||||
|
- Test: `tests/ClaudeDo.Ui.Tests/ViewModels/PrimeClaudeTabViewModelTests.cs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update the UI DTO**
|
||||||
|
|
||||||
|
`src/ClaudeDo.Ui/Services/PrimeScheduleDto.cs` (keep `PrimeFiredEvent` unchanged):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
namespace ClaudeDo.Ui.Services;
|
||||||
|
|
||||||
|
public sealed record PrimeScheduleDto(
|
||||||
|
Guid Id,
|
||||||
|
int Days,
|
||||||
|
TimeSpan TimeOfDay,
|
||||||
|
bool Enabled,
|
||||||
|
DateTimeOffset? LastRunAt,
|
||||||
|
string? PromptOverride);
|
||||||
|
|
||||||
|
public sealed record PrimeFiredEvent(
|
||||||
|
Guid ScheduleId,
|
||||||
|
bool Success,
|
||||||
|
string Message,
|
||||||
|
DateTimeOffset FiredAt);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Rewrite the row VM**
|
||||||
|
|
||||||
|
Replace the body of `src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeScheduleRowViewModel.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using ClaudeDo.Ui.Services;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||||
|
|
||||||
|
public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private const int Mon = 1, Tue = 2, Wed = 4, Thu = 8, Fri = 16, Sat = 32, Sun = 64;
|
||||||
|
|
||||||
|
public Guid Id { get; }
|
||||||
|
public bool IsExisting { get; }
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _enabled;
|
||||||
|
[ObservableProperty] private bool _monday;
|
||||||
|
[ObservableProperty] private bool _tuesday;
|
||||||
|
[ObservableProperty] private bool _wednesday;
|
||||||
|
[ObservableProperty] private bool _thursday;
|
||||||
|
[ObservableProperty] private bool _friday;
|
||||||
|
[ObservableProperty] private bool _saturday;
|
||||||
|
[ObservableProperty] private bool _sunday;
|
||||||
|
[ObservableProperty] private TimeSpan _timeOfDay;
|
||||||
|
[ObservableProperty] private DateTimeOffset? _lastRunAt;
|
||||||
|
|
||||||
|
public string LastRunLabel => LastRunAt is { } v ? v.LocalDateTime.ToString("g") : "—";
|
||||||
|
|
||||||
|
partial void OnLastRunAtChanged(DateTimeOffset? value) => OnPropertyChanged(nameof(LastRunLabel));
|
||||||
|
|
||||||
|
public PrimeScheduleRowViewModel(PrimeScheduleDto dto, bool isExisting)
|
||||||
|
{
|
||||||
|
Id = dto.Id;
|
||||||
|
IsExisting = isExisting;
|
||||||
|
Enabled = dto.Enabled;
|
||||||
|
Monday = (dto.Days & Mon) != 0;
|
||||||
|
Tuesday = (dto.Days & Tue) != 0;
|
||||||
|
Wednesday = (dto.Days & Wed) != 0;
|
||||||
|
Thursday = (dto.Days & Thu) != 0;
|
||||||
|
Friday = (dto.Days & Fri) != 0;
|
||||||
|
Saturday = (dto.Days & Sat) != 0;
|
||||||
|
Sunday = (dto.Days & Sun) != 0;
|
||||||
|
TimeOfDay = dto.TimeOfDay;
|
||||||
|
LastRunAt = dto.LastRunAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DaysMask()
|
||||||
|
{
|
||||||
|
int m = 0;
|
||||||
|
if (Monday) m |= Mon;
|
||||||
|
if (Tuesday) m |= Tue;
|
||||||
|
if (Wednesday) m |= Wed;
|
||||||
|
if (Thursday) m |= Thu;
|
||||||
|
if (Friday) m |= Fri;
|
||||||
|
if (Saturday) m |= Sat;
|
||||||
|
if (Sunday) m |= Sun;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrimeScheduleDto ToDto() =>
|
||||||
|
new(Id, DaysMask(), TimeOfDay, Enabled, LastRunAt, null);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update the tab VM**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeClaudeTabViewModel.cs`, replace `Validate` and `AddSchedule`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public string? Validate()
|
||||||
|
{
|
||||||
|
foreach (var r in Rows)
|
||||||
|
{
|
||||||
|
if (r.DaysMask() == 0)
|
||||||
|
return $"Schedule {r.TimeOfDay:hh\\:mm}: select at least one day.";
|
||||||
|
if (r.TimeOfDay < TimeSpan.Zero || r.TimeOfDay >= TimeSpan.FromDays(1))
|
||||||
|
return "Time must be between 00:00 and 23:59.";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[RelayCommand]
|
||||||
|
private void AddSchedule()
|
||||||
|
{
|
||||||
|
var dto = new PrimeScheduleDto(
|
||||||
|
Id: Guid.NewGuid(),
|
||||||
|
Days: 31, // Mon–Fri
|
||||||
|
TimeOfDay: new TimeSpan(7, 0, 0),
|
||||||
|
Enabled: true,
|
||||||
|
LastRunAt: null,
|
||||||
|
PromptOverride: null);
|
||||||
|
Rows.Add(new PrimeScheduleRowViewModel(dto, isExisting: false));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`LoadAsync`, `SaveAsync`, `RemoveSchedule`, `ApplyFiredEvent` are unchanged.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Rewrite the tab VM tests**
|
||||||
|
|
||||||
|
Replace the body of `tests/ClaudeDo.Ui.Tests/ViewModels/PrimeClaudeTabViewModelTests.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using ClaudeDo.Ui.Services;
|
||||||
|
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Tests.ViewModels;
|
||||||
|
|
||||||
|
public class PrimeClaudeTabViewModelTests
|
||||||
|
{
|
||||||
|
private sealed class FakeApi : IPrimeScheduleApi
|
||||||
|
{
|
||||||
|
public List<PrimeScheduleDto> Stored { get; } = new();
|
||||||
|
public List<PrimeScheduleDto> Upserts { get; } = new();
|
||||||
|
public List<Guid> Deletes { get; } = new();
|
||||||
|
public Task<List<PrimeScheduleDto>> ListAsync() => Task.FromResult(Stored.ToList());
|
||||||
|
public Task<PrimeScheduleDto?> UpsertAsync(PrimeScheduleDto dto)
|
||||||
|
{
|
||||||
|
Upserts.Add(dto);
|
||||||
|
return Task.FromResult<PrimeScheduleDto?>(dto);
|
||||||
|
}
|
||||||
|
public Task DeleteAsync(Guid id) { Deletes.Add(id); return Task.CompletedTask; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PrimeScheduleDto Dto(Guid id, int days, TimeSpan time) =>
|
||||||
|
new(id, days, time, true, null, null);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Load_Populates_Rows()
|
||||||
|
{
|
||||||
|
var api = new FakeApi();
|
||||||
|
api.Stored.Add(Dto(Guid.NewGuid(), 31, new TimeSpan(7, 0, 0)));
|
||||||
|
var vm = new PrimeClaudeTabViewModel(api);
|
||||||
|
await vm.LoadAsync();
|
||||||
|
Assert.Single(vm.Rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddSchedule_Appends_Row_With_Defaults()
|
||||||
|
{
|
||||||
|
var vm = new PrimeClaudeTabViewModel(new FakeApi());
|
||||||
|
vm.AddScheduleCommand.Execute(null);
|
||||||
|
Assert.Single(vm.Rows);
|
||||||
|
Assert.True(vm.Rows[0].Enabled);
|
||||||
|
Assert.True(vm.Rows[0].Monday);
|
||||||
|
Assert.True(vm.Rows[0].Friday);
|
||||||
|
Assert.False(vm.Rows[0].Saturday);
|
||||||
|
Assert.Equal(new TimeSpan(7, 0, 0), vm.Rows[0].TimeOfDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Row_Decomposes_And_Recomposes_Days()
|
||||||
|
{
|
||||||
|
var vm = new PrimeClaudeTabViewModel(new FakeApi());
|
||||||
|
vm.AddScheduleCommand.Execute(null);
|
||||||
|
var row = vm.Rows[0];
|
||||||
|
Assert.Equal(31, row.DaysMask());
|
||||||
|
row.Saturday = true;
|
||||||
|
Assert.Equal(63, row.DaysMask());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Save_Diffs_New_And_Removed_Rows()
|
||||||
|
{
|
||||||
|
var api = new FakeApi();
|
||||||
|
var keptId = Guid.NewGuid();
|
||||||
|
var deletedId = Guid.NewGuid();
|
||||||
|
api.Stored.Add(Dto(keptId, 31, new TimeSpan(7, 0, 0)));
|
||||||
|
api.Stored.Add(Dto(deletedId, 31, new TimeSpan(8, 0, 0)));
|
||||||
|
|
||||||
|
var vm = new PrimeClaudeTabViewModel(api);
|
||||||
|
await vm.LoadAsync();
|
||||||
|
vm.RemoveScheduleCommand.Execute(vm.Rows.Single(r => r.Id == deletedId));
|
||||||
|
vm.AddScheduleCommand.Execute(null);
|
||||||
|
|
||||||
|
await vm.SaveAsync();
|
||||||
|
|
||||||
|
Assert.Contains(deletedId, api.Deletes);
|
||||||
|
Assert.Equal(2, api.Upserts.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_Reports_No_Days_Selected()
|
||||||
|
{
|
||||||
|
var vm = new PrimeClaudeTabViewModel(new FakeApi());
|
||||||
|
vm.AddScheduleCommand.Execute(null);
|
||||||
|
var row = vm.Rows[0];
|
||||||
|
row.Monday = row.Tuesday = row.Wednesday = row.Thursday = row.Friday = false;
|
||||||
|
Assert.NotNull(vm.Validate());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_Passes_With_One_Day()
|
||||||
|
{
|
||||||
|
var vm = new PrimeClaudeTabViewModel(new FakeApi());
|
||||||
|
vm.AddScheduleCommand.Execute(null);
|
||||||
|
Assert.Null(vm.Validate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run UI tests**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter FullyQualifiedName~PrimeClaudeTabViewModelTests`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/Services/PrimeScheduleDto.cs src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeScheduleRowViewModel.cs src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeClaudeTabViewModel.cs tests/ClaudeDo.Ui.Tests/ViewModels/PrimeClaudeTabViewModelTests.cs
|
||||||
|
git commit -m "feat(ui): drive prime schedule rows from weekday toggles"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 8: XAML — toggle-button row
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Design/IslandStyles.axaml`
|
||||||
|
- Modify: `src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add a `day-toggle` style class**
|
||||||
|
|
||||||
|
Append to `src/ClaudeDo.Ui/Design/IslandStyles.axaml` (inside the root `<Styles>` element, alongside the other style selectors). Uses existing dynamic-resource tokens — no hardcoded colors:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Style Selector="ToggleButton.day-toggle">
|
||||||
|
<Setter Property="MinWidth" Value="34"/>
|
||||||
|
<Setter Property="Padding" Value="6,4"/>
|
||||||
|
<Setter Property="Margin" Value="0"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Background" Value="{DynamicResource DeepBrush}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource LineBrush}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="CornerRadius" Value="4"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ToggleButton.day-toggle:checked /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||||
|
</Style>
|
||||||
|
```
|
||||||
|
|
||||||
|
If `AccentBrush` is not a defined token, use the brush the project uses for primary/selected affordances (check the `primary` button style in this file and reuse that brush). Final visual pass is the user's.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Replace the Prime row template**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml`, replace the `<Grid ...>` inside the Prime `DataTemplate` (currently columns `Auto,*,Auto,Auto,Auto,Auto` with the `ThemedDatePicker` and Mon–Fri checkbox) with:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Grid ColumnDefinitions="Auto,*,Auto,Auto,Auto" ColumnSpacing="8">
|
||||||
|
<CheckBox Grid.Column="0" IsChecked="{Binding Enabled, Mode=TwoWay}" VerticalAlignment="Center"/>
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
|
||||||
|
<ToggleButton Classes="day-toggle" Content="Mo" IsChecked="{Binding Monday, Mode=TwoWay}"/>
|
||||||
|
<ToggleButton Classes="day-toggle" Content="Tu" IsChecked="{Binding Tuesday, Mode=TwoWay}"/>
|
||||||
|
<ToggleButton Classes="day-toggle" Content="We" IsChecked="{Binding Wednesday, Mode=TwoWay}"/>
|
||||||
|
<ToggleButton Classes="day-toggle" Content="Th" IsChecked="{Binding Thursday, Mode=TwoWay}"/>
|
||||||
|
<ToggleButton Classes="day-toggle" Content="Fr" IsChecked="{Binding Friday, Mode=TwoWay}"/>
|
||||||
|
<ToggleButton Classes="day-toggle" Content="Sa" IsChecked="{Binding Saturday, Mode=TwoWay}"/>
|
||||||
|
<ToggleButton Classes="day-toggle" Content="Su" IsChecked="{Binding Sunday, Mode=TwoWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBox Grid.Column="2" Width="64"
|
||||||
|
Text="{Binding TimeOfDay, Mode=TwoWay, Converter={StaticResource TimeSpanToHhmm}}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Classes="meta" Grid.Column="3" Text="{Binding LastRunLabel}" VerticalAlignment="Center"
|
||||||
|
MinWidth="80"/>
|
||||||
|
<Button Classes="icon-btn" Grid.Column="4" Content="✕"
|
||||||
|
Command="{Binding $parent[ItemsControl].((vm:SettingsModalViewModel)DataContext).Prime.RemoveScheduleCommand}"
|
||||||
|
CommandParameter="{Binding}"/>
|
||||||
|
</Grid>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update the explainer text**
|
||||||
|
|
||||||
|
Replace the intro `TextBlock` Text in the Prime tab (`SettingsModalView.axaml`):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
Text="Prime your Claude usage window by firing a single non-interactive ping on the days you choose, at a chosen time. Only runs while ClaudeDo is open. If the app starts within 30 minutes of the target time, the ping fires immediately."/>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Remove the now-unused range converter (only if unreferenced)**
|
||||||
|
|
||||||
|
The `DateOnlyToDateTime` resource on line 23 was used only by the range picker. Grep the file: if `DateOnlyToDateTime` has no other reference, remove the `<conv:DateOnlyToDateTimeConverter x:Key="DateOnlyToDateTime"/>` line. Keep `TimeSpanToHhmm` (still used).
|
||||||
|
|
||||||
|
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Manual UI check**
|
||||||
|
|
||||||
|
Start the worker, then the app. Open Settings → Prime Claude. Verify: a row shows 7 toggle buttons with Mon–Fri lit by default; toggling Sat/Sun persists after Save+reopen; clearing all days shows the validation error on Save. (UI correctness can only be confirmed in the running app — state so explicitly if it cannot be run.)
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Ui/Design/IslandStyles.axaml src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml
|
||||||
|
git commit -m "feat(ui): replace prime date range with weekday toggle buttons"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 9: Docs
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ClaudeDo.Data/CLAUDE.md`
|
||||||
|
- Modify: `CLAUDE.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update the Data CLAUDE.md**
|
||||||
|
|
||||||
|
In `src/ClaudeDo.Data/CLAUDE.md`, the Models section has no PrimeSchedule line today; add one under Models, and confirm the `prime_schedules` table mention in the Schema section stays accurate:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- **PrimeScheduleEntity** — Id, Days (`[Flags] PrimeDays` weekday bitmask, stored as `days_of_week` int), TimeOfDay, Enabled, LastRunAt, PromptOverride, CreatedAt. Recurs on the selected weekdays; no date range.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update the root CLAUDE.md if Prime is described**
|
||||||
|
|
||||||
|
Grep `CLAUDE.md` for "Prime"; if there is a Prime description mentioning a date range, update it to "recurring weekday schedule". If there is no such line, make no change.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Full test sweep**
|
||||||
|
|
||||||
|
Run: `dotnet test tests/ClaudeDo.Worker.Tests && dotnet test tests/ClaudeDo.Ui.Tests`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/ClaudeDo.Data/CLAUDE.md CLAUDE.md
|
||||||
|
git commit -m "docs: describe recurring-weekday Prime schedule"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review Notes
|
||||||
|
|
||||||
|
- **Spec coverage:** data model (T1), scheduling logic (T4), UI toggles (T7–T8), migration+backfill (T3), both DTOs (T4/T7), tests (T4–T7), out-of-scope items excluded. ✓
|
||||||
|
- **Type consistency:** entity `PrimeDays Days`; both DTOs `int Days`; hub/scheduler cast `(int)`/`(PrimeDays)` at boundaries; calculator casts `(PrimeDays)s.Days`; row VM exposes 7 bools + `DaysMask()`. ✓
|
||||||
|
- **Build ripple:** a single type change breaks several projects at once, so some intermediate steps note expected build failures; the gating green builds are T3 Step 4 (Data), T6 Step 4 (Worker + tests), T8 Step 4 (App). ✓
|
||||||
|
```
|
||||||
96
docs/superpowers/specs/2026-05-30-ui-normalization-design.md
Normal file
96
docs/superpowers/specs/2026-05-30-ui-normalization-design.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# UI Normalization & Single Source of Truth — Design
|
||||||
|
|
||||||
|
Date: 2026-05-30
|
||||||
|
Status: Approved
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make working on the ClaudeDo UI simpler by establishing the design tokens as the single source of truth for **every** visual value, eliminating duplicated styles, and providing reusable helpers for the patterns that are currently copy-pasted across views. Accept minor visual shifts where current values don't match the token scale — consistency is the priority over pixel-preservation.
|
||||||
|
|
||||||
|
## Scope decisions (locked)
|
||||||
|
|
||||||
|
- **Lane C (full normalization)** — global defaults + shared helpers + tokenize every hardcoded font/spacing/radius/color.
|
||||||
|
- **Normalization strategy: B (snap to existing scale).** Stray values round to the nearest existing token; off-palette colors fold into the closest design brush. The token vocabulary stays small; the UI shifts slightly in places and is verified by human eyeball.
|
||||||
|
- Badge colors collapse to palette (option A): blue is dropped.
|
||||||
|
|
||||||
|
## 1. Global defaults — `src/ClaudeDo.App/App.axaml`
|
||||||
|
|
||||||
|
Add application-level default styles so unstyled controls inherit the intended look instead of falling back to FluentTheme's Segoe UI:
|
||||||
|
|
||||||
|
- Default `FontFamily` = `{DynamicResource SansFont}` (Inter Tight) for text-bearing controls (`TextBlock`, `TextBox`, `Button`, `ComboBox`, `CheckBox`, `NumericUpDown`, `TabItem`).
|
||||||
|
- Default `FontSize` baseline = `{StaticResource FontSizeBody}` (13) where a control has no more specific style.
|
||||||
|
- Controls that need mono (`MonoFont`) continue to opt in explicitly via their class/style.
|
||||||
|
|
||||||
|
This single change fixes the Settings modal font and every other bare-Segoe-UI label across the app.
|
||||||
|
|
||||||
|
## 2. Tokens = source of truth — `src/ClaudeDo.Ui/Design/Tokens.axaml`
|
||||||
|
|
||||||
|
### Fonts — snap to the existing scale
|
||||||
|
Existing tokens: Eyebrow=10, Mono=11, Micro=11, Body=13, TaskTitle=14, H3=18, H2=24, H1=32.
|
||||||
|
- `9 → 10` (FontSizeEyebrow)
|
||||||
|
- `12 → 13` (FontSizeBody)
|
||||||
|
- `16 → 18` (FontSizeH3)
|
||||||
|
- Every `FontSize="N"` literal across all views/styles becomes a `{StaticResource FontSize*}` reference. No new size tokens are added.
|
||||||
|
|
||||||
|
### Spacing / radius — snap to the existing scale
|
||||||
|
- Modal body padding `16` / `20 → 18` (SpaceXl); the vertical component `12` stays `SpaceMd`.
|
||||||
|
- Corner radius `4 → 6` (ButtonCornerRadius).
|
||||||
|
- Text inputs (TextBox) standardize on `InputCornerRadius` (8); the `6` currently on DetailsIslandView TextBoxes moves to 8.
|
||||||
|
|
||||||
|
### Colors — fold off-palette into the palette
|
||||||
|
Add semantic brushes where a recurring role genuinely needs one, but reuse existing palette brushes wherever possible:
|
||||||
|
|
||||||
|
- **Connection-status dots** (MainWindow): green `#4CAF50` → `StatusRunningBrush`; amber `#FFA726` → `StatusReviewBrush`; red `#EF5350` → `StatusErrorBrush`. Also applies to the `#EF5350` literals in WorktreesOverviewModal.
|
||||||
|
- **Planning/draft badges** (IslandStyles `DraftBadgeBrush`/`PlanningBadgeBrush`/`PlannedBadgeBrush`): re-point to palette — draft → `TextMuteBrush`, planning → `PeatBrush`, planned → `SageBrush`. Blue dropped.
|
||||||
|
- **Named-color literals:** `OrangeRed` / `Orange` → `BloodBrush`; `White` → `TextBrush` (or `DeepBrush` where it sits on an accent fill, e.g. primary button text).
|
||||||
|
- **Terminal background** `#FF080C0B` (terminal + task-live-tail) → `VoidBrush` (`#FF0A0E0C`).
|
||||||
|
- **Status alpha-tints:** the repeated `#1F<hue>` fills and `#4C<hue>` borders used by chips and agent-strips become named brushes defined once in Tokens (e.g. `RunningTintBrush` / `RunningTintBorderBrush`, and the same for review/error/queued), then referenced from IslandStyles. The `#26<hue>` worktree-badge tints and `#147C9166` agent-strip tints fold into the same named tint family (snap the alpha to one value per family).
|
||||||
|
- **Island hairline overlay** `#0DFFFFFF` → a named `HairlineOverlayBrush` token.
|
||||||
|
|
||||||
|
## 3. Shared helpers
|
||||||
|
|
||||||
|
### `src/ClaudeDo.Ui/Design/IslandStyles.axaml`
|
||||||
|
Promote the styles currently copy-pasted into modals into the shared stylesheet, then delete the per-modal copies:
|
||||||
|
- `Button.primary` — standardize on **one** definition: `AccentDimBrush` background + `AccentBrush` border + `TextBrush` foreground (matching the existing `Button.btn.primary` variant). Resolves the AccentBrush-vs-AccentDimBrush divergence.
|
||||||
|
- `Button.danger` — `BloodBrush` background + `TextBrush` foreground.
|
||||||
|
- `TextBlock.field-label` — FontSize Micro (11), `TextDimBrush`, bottom margin 4.
|
||||||
|
- `TextBlock.section-label` already exists in IslandStyles; remove the duplicate local copies.
|
||||||
|
|
||||||
|
### New control: `ModalShell` (`src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml`)
|
||||||
|
A reusable `TemplatedControl` / `UserControl` providing the chrome every modal re-implements:
|
||||||
|
- Title bar: mono uppercase title (FontSize Mono, LetterSpacing 1.4), draggable region, ✕ close button (`icon-btn`).
|
||||||
|
- Outer border (SurfaceBrush bg, LineBrush border, ModalCornerRadius).
|
||||||
|
- Content slot for the body.
|
||||||
|
- Optional footer slot for action buttons (right-aligned).
|
||||||
|
- Exposes: `Title` (string), `Body` content, `Footer` content, and a `CloseCommand`.
|
||||||
|
|
||||||
|
The 8 modal windows (Settings, ListSettings, Merge, About, UnfinishedPlanning, RepoImport, Diff, PlanningDiff, ConflictResolution) migrate to wrap their content in `ModalShell` instead of re-declaring titlebar/border/footer grids. Window-level concerns (Width/Height, KeyBindings, WindowDecorations) stay on the `Window`; only the inner chrome is replaced.
|
||||||
|
|
||||||
|
## 4. Bug fixes (folded into the migration)
|
||||||
|
|
||||||
|
- `TaskRowView.axaml` schedule flyout: `BorderBrush="{DynamicResource BorderBrush}"` → `{DynamicResource LineBrush}` (the `BorderBrush` key does not exist in Tokens; current runtime resource-not-found).
|
||||||
|
- `DiffModalView.axaml`, `PlanningDiffView.axaml`, `ConflictResolutionView.axaml`: convert all `{StaticResource <token>}` references to `{DynamicResource <token>}` to match the rest of the app and survive theme changes. (Style-internal `Setter` references that must stay `StaticResource` for Avalonia reasons are left as-is; only token lookups in element attributes are converted.)
|
||||||
|
|
||||||
|
## 5. Verification
|
||||||
|
|
||||||
|
- `dotnet build` per project (`.slnx` requires .NET 9 — build individual csproj):
|
||||||
|
- `src/ClaudeDo.App/ClaudeDo.App.csproj` (pulls in Ui + Data)
|
||||||
|
- `src/ClaudeDo.Worker/ClaudeDo.Worker.csproj`
|
||||||
|
- A clean build confirms XAML compiles and all resource keys resolve (compiled bindings + StaticResource keys are validated at build time).
|
||||||
|
- Human visual pass: launch the app and walk each view/modal against a per-view checklist (provided with the plan), since lane B intentionally shifts some values. The eyeball is the regression check.
|
||||||
|
|
||||||
|
## Sequencing
|
||||||
|
|
||||||
|
1. Tokens.axaml: add new named brushes (tints, status, hairline), re-point badge brushes. (No behavior change yet.)
|
||||||
|
2. App.axaml: global font/size defaults.
|
||||||
|
3. IslandStyles.axaml: promote shared styles (primary/danger/field-label), replace internal hardcoded values with token refs.
|
||||||
|
4. Per-view migration: replace every hardcoded FontSize/spacing/radius/color with token refs; snap stray values.
|
||||||
|
5. ModalShell control + migrate the 8 modals.
|
||||||
|
6. Bug fixes (BorderBrush key, Static→Dynamic in the three views).
|
||||||
|
7. Build all projects; produce visual-check checklist.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- No layout/structure redesign — only values and shared chrome.
|
||||||
|
- No new features.
|
||||||
|
- No changes to ViewModels or behavior (ModalShell migration is markup-only; existing `CancelCommand` etc. bind through unchanged).
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
# Waiting for Review — Task State — Design
|
||||||
|
|
||||||
|
**Date:** 2026-06-01
|
||||||
|
**Status:** Approved (brainstorming)
|
||||||
|
**Scope:** `ClaudeDo.Data` (TaskEntity, EF config + migration), `ClaudeDo.Worker` (TaskStateService, TaskRunner, QueueService, WorkerHub, ExternalMcpService), `ClaudeDo.Ui` (StatusColorConverter, TaskRowViewModel, views), CLAUDE.md docs
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
A successful task run currently transitions straight to `Done` and is considered complete. There is no gate for a human (or another agent) to review the result before it is accepted. We want review to be a mandatory step: after a successful run a task waits for an explicit approval, and a reviewer can send it back with feedback for another turn.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Add a `WaitingForReview` lifecycle state that a task enters automatically after a **successful** run.
|
||||||
|
- Reviewer can **approve** (→ `Done`), **reject-and-re-run** (→ `Queued`, resuming the same Claude session with required feedback), **reject-and-park** (→ `Idle`), or **cancel** (→ `Cancelled`).
|
||||||
|
- Reject-and-re-run reuses the existing session-resume mechanism so the agent continues with full context.
|
||||||
|
- Both the desktop UI and the external MCP surface can perform review actions.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- No change to the failure path: a **failed** run still goes straight to `Failed`, never to `WaitingForReview`.
|
||||||
|
- No change to planning-phase finalization. A planning parent that generates child tasks keeps its current behavior and does **not** route through review. Only ordinary executable runs (`Running` → success) are affected.
|
||||||
|
- No change to worktree state flow (`Active | Merged | Discarded | Kept`).
|
||||||
|
- No change to the in-run auto-retry-on-failure behavior; only the *final* successful completion routes to review.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### 1. State machine
|
||||||
|
|
||||||
|
Changed/added transitions in **bold**:
|
||||||
|
|
||||||
|
| From | To | Trigger |
|
||||||
|
|---|---|---|
|
||||||
|
| Idle | Queued | enqueue (unchanged) |
|
||||||
|
| Queued | Running | queue picker claim (unchanged) |
|
||||||
|
| Running | **WaitingForReview** | **successful run (was → Done)** |
|
||||||
|
| Running | Failed | failed run (unchanged) |
|
||||||
|
| Running | Cancelled | cancel during run (unchanged) |
|
||||||
|
| **WaitingForReview** | **Done** | **approve** |
|
||||||
|
| **WaitingForReview** | **Queued** | **reject + required feedback → resume re-run** |
|
||||||
|
| **WaitingForReview** | **Idle** | **reject → park for manual edit** |
|
||||||
|
| **WaitingForReview** | **Cancelled** | **abandon an almost-done task** |
|
||||||
|
| Done \| Failed \| Cancelled | Idle | reset (unchanged) |
|
||||||
|
|
||||||
|
### 2. Data model
|
||||||
|
|
||||||
|
`ClaudeDo.Data`:
|
||||||
|
|
||||||
|
- `TaskStatus` enum (`Models/TaskEntity.cs`): add `WaitingForReview` after `Running`.
|
||||||
|
- EF string converter (`Configuration/TaskEntityConfiguration.cs`): map `WaitingForReview` ⇄ `"waiting_for_review"` (TEXT column, no schema constraint to change).
|
||||||
|
- New nullable column **`ReviewFeedback : string?`** on `TaskEntity`. Holds the reviewer's rejection comment until the re-run consumes it, then it is cleared. Persisted so it survives a worker restart and is visible to the UI.
|
||||||
|
- One EF migration: add the `review_feedback` column. No backfill — the new status value and column are only written going forward.
|
||||||
|
|
||||||
|
### 3. Worker — status transitions (`State/TaskStateService.cs`)
|
||||||
|
|
||||||
|
`TaskStateService` remains the sole owner of status writes. New/changed methods:
|
||||||
|
|
||||||
|
- `SubmitForReviewAsync(taskId)` — `Running` → `WaitingForReview`. Sets `FinishedAt` and `Result` exactly as `CompleteAsync` does today. Called by `TaskRunner` on success **instead of** `CompleteAsync`. (`CompleteAsync` is retained for the approve path.)
|
||||||
|
- `ApproveReviewAsync(taskId)` — `WaitingForReview` → `Done`.
|
||||||
|
- `RejectToQueueAsync(taskId, feedback)` — `WaitingForReview` → `Queued`. Rejects empty/whitespace feedback with a failed `TransitionResult`. Stores `feedback` in `ReviewFeedback`. Wakes the queue.
|
||||||
|
- `RejectToIdleAsync(taskId)` — `WaitingForReview` → `Idle`. Parks for manual editing; leaves `Result` intact, clears `ReviewFeedback`.
|
||||||
|
- `CancelAsync` — extend the allowed source states to include `WaitingForReview`.
|
||||||
|
|
||||||
|
Each transition broadcasts `TaskUpdated` as today. Invalid source states return a failed `TransitionResult` (no throw), matching existing convention.
|
||||||
|
|
||||||
|
### 4. Resume-aware re-run (`Queue/QueueService.cs`)
|
||||||
|
|
||||||
|
The queue picker still atomically claims a `Queued`, unblocked task (`UPDATE … SET status='running' … RETURNING *`). The `RETURNING` row already carries `ReviewFeedback`. After a successful claim, `QueueService` branches:
|
||||||
|
|
||||||
|
1. **`ReviewFeedback` set + latest run has a `SessionId`** → `TaskRunner.ContinueAsync(task, feedback)` — `--resume {sessionId}` with `feedback` as the next-turn prompt.
|
||||||
|
2. **`ReviewFeedback` set, no prior `SessionId`** (edge case) → `TaskRunner.RunAsync` with the feedback appended to the task prompt, so the comment is not lost.
|
||||||
|
3. **No `ReviewFeedback`** → normal `TaskRunner.RunAsync` (fresh session).
|
||||||
|
|
||||||
|
`ReviewFeedback` is cleared once consumed (single UPDATE), so a later re-run does not re-apply stale feedback.
|
||||||
|
|
||||||
|
### 5. External MCP surface (`External/ExternalMcpService.cs`)
|
||||||
|
|
||||||
|
- New tool **`review_task(taskId, decision, feedback?)`**, `decision ∈ {approve, reject_rerun, reject_park, cancel}`. `feedback` is required when `decision = reject_rerun` (validation error otherwise). Maps onto the `TaskStateService` methods in §3. This lets automation / other agents act as reviewers.
|
||||||
|
- `get_task_status_values` — add `WaitingForReview` with a description covering the four exit actions.
|
||||||
|
- `list_tasks` status-filter parsing and validation message — include `WaitingForReview`.
|
||||||
|
- `get_task` lifecycle description text — update to `Idle → Queued → Running → WaitingForReview → Done | Failed | Cancelled`.
|
||||||
|
- `update_task_status` stays restricted to `Idle` and `Queued`; all review decisions go through `review_task` (keeps the "set status freely" affordance and the review affordance distinct).
|
||||||
|
|
||||||
|
### 6. Worker hub (`Hub/WorkerHub.cs` + `Hub/HubBroadcaster.cs`)
|
||||||
|
|
||||||
|
New hub methods called by the UI, each delegating to `TaskStateService`:
|
||||||
|
|
||||||
|
- `ApproveReview(taskId)`
|
||||||
|
- `RejectReviewToQueue(taskId, feedback)`
|
||||||
|
- `RejectReviewToIdle(taskId)`
|
||||||
|
|
||||||
|
Cancel already exists. No new broadcast events — `TaskUpdated` covers it.
|
||||||
|
|
||||||
|
### 7. UI (`ClaudeDo.Ui`)
|
||||||
|
|
||||||
|
- `Converters/StatusColorConverter.cs`: add a `waiting_for_review` case. Snap to an existing color token from the scale; final visual pass is left to the user (per project convention — centralize/tokenize, user does the visual pass).
|
||||||
|
- `ViewModels/Islands/TaskRowViewModel.cs`: add `IsWaitingForReview` computed property and commands **Approve**, **RejectRerun**, **RejectPark**, **Cancel** (the last reuses the existing cancel command). Commands are enabled only when `Status == WaitingForReview`.
|
||||||
|
- Reject-Rerun opens a small flyout/dialog with a required multi-line feedback text box; on confirm it calls `RejectReviewToQueue(taskId, feedback)`.
|
||||||
|
- Wire the commands to the new SignalR client methods.
|
||||||
|
|
||||||
|
### 8. Docs
|
||||||
|
|
||||||
|
Update the status flow in:
|
||||||
|
|
||||||
|
- root `CLAUDE.md` — "Task status flow" line.
|
||||||
|
- `src/ClaudeDo.Data/CLAUDE.md` — TaskEntity status list.
|
||||||
|
- `src/ClaudeDo.Worker/CLAUDE.md` — status-model transition table.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
`ClaudeDo.Worker.Tests` (real SQLite + real git, existing harness):
|
||||||
|
|
||||||
|
- `SubmitForReviewAsync`: a successful run lands in `WaitingForReview`, not `Done`.
|
||||||
|
- `ApproveReviewAsync`: `WaitingForReview` → `Done`.
|
||||||
|
- `RejectToQueueAsync`: empty feedback rejected; valid feedback stored in `ReviewFeedback` and status → `Queued`.
|
||||||
|
- `RejectToIdleAsync`: → `Idle`, `Result` preserved, `ReviewFeedback` cleared.
|
||||||
|
- `CancelAsync` from `WaitingForReview` → `Cancelled`.
|
||||||
|
- Invalid source states (e.g. approve from `Idle`) return a failed `TransitionResult`.
|
||||||
|
- Resume-aware re-run: a task with `ReviewFeedback` + a prior `SessionId`, when claimed, resumes the session with the feedback as the prompt and clears `ReviewFeedback`.
|
||||||
|
- `review_task` MCP tool: each decision maps to the correct transition; `reject_rerun` without feedback errors.
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
None outstanding. Planning-task exclusion (Non-Goals) is the one assumption to verify against the planning-finalization code path during implementation; if planning finalization shares `CompleteAsync`, route only the executable-run success site through `SubmitForReviewAsync`.
|
||||||
153
docs/superpowers/specs/2026-06-01-worker-lifecycle-design.md
Normal file
153
docs/superpowers/specs/2026-06-01-worker-lifecycle-design.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# Worker Lifecycle Redesign
|
||||||
|
|
||||||
|
**Date:** 2026-06-01
|
||||||
|
**Status:** Approved (design)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The worker process has multiple competing owners, which collide in development and
|
||||||
|
muddy production behavior:
|
||||||
|
|
||||||
|
- The App auto-spawns its own worker on startup (`EnsureWorkerRunningAsync`,
|
||||||
|
`IslandsShellViewModel.cs:310`, called at line 224) ~4s after launch if it isn't
|
||||||
|
yet connected. In the IDE "Start Everything" multilaunch — which already runs the
|
||||||
|
worker via the `http` launch profile (`dotnet run`) — this produces a *second*
|
||||||
|
worker that fails to bind to `127.0.0.1:47821` and dies, surfacing a stray console
|
||||||
|
with a "failed to bind to address" error.
|
||||||
|
- Production autostart uses a per-user logon **Scheduled Task** (`RegisterAutostartStep`
|
||||||
|
+ `ScheduledTaskXml`), which the user wants to replace with a simpler Startup-folder
|
||||||
|
shortcut.
|
||||||
|
- When the App can't reach the worker, the only feedback is a silent "Offline" pill in
|
||||||
|
the footer — no guidance to the user.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Establish a single owner for the worker lifecycle and make connection failures
|
||||||
|
actionable:
|
||||||
|
|
||||||
|
1. The worker is owned **externally** — a per-user **Startup-folder shortcut** in
|
||||||
|
production (replacing the Scheduled Task), or the IDE in development.
|
||||||
|
2. The App **only connects**; it never auto-spawns a worker.
|
||||||
|
3. When the App can't connect, it shows a one-time prompt offering **Start Worker**,
|
||||||
|
**Rerun Installer**, or **Dismiss**, plus a clickable Offline pill to reopen it.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- No change to the IDE dev setup. The "Start Everything" multilaunch keeps running the
|
||||||
|
worker via the `http` profile (console with live logs); the duplicate/bind-error
|
||||||
|
worker disappears purely because the App no longer auto-spawns. Rider run configs live
|
||||||
|
in `.idea/.../workspace.xml` (per-user, gitignored) and are out of scope.
|
||||||
|
- No change to the SignalR hub URL, port, reconnect policy, or the worker's
|
||||||
|
single-instance mutex.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### Component 1 — Installer: Scheduled Task → Startup-folder shortcut
|
||||||
|
|
||||||
|
**`RegisterAutostartStep`** (`src/ClaudeDo.Installer/Steps/RegisterAutostartStep.cs`)
|
||||||
|
- Replace the task-XML build + `schtasks /Create` with creation of a `.lnk` in the
|
||||||
|
per-user Startup folder (`Environment.SpecialFolder.Startup`) targeting
|
||||||
|
`{InstallDirectory}\worker\ClaudeDo.Worker.exe`. The worker is `WinExe`, so it launches
|
||||||
|
with no console window.
|
||||||
|
- **Migration:** keep the existing legacy Windows-service removal, and **add** removal of
|
||||||
|
the old scheduled task: `schtasks.exe /Delete /TN "ClaudeDoWorker" /F` (best-effort),
|
||||||
|
so existing installs migrate cleanly to the shortcut model.
|
||||||
|
|
||||||
|
**`StartWorkerStep`** (`src/ClaudeDo.Installer/Steps/StartWorkerStep.cs`)
|
||||||
|
- Replace `schtasks /Run /TN ClaudeDoWorker` with a direct
|
||||||
|
`Process.Start(new ProcessStartInfo(workerExe) { UseShellExecute = true })`.
|
||||||
|
|
||||||
|
**`StopWorkerStep`** (`src/ClaudeDo.Installer/Steps/StopWorkerStep.cs`)
|
||||||
|
- Drop the `schtasks /End` call. Keep the existing install-dir-scoped process kill, which
|
||||||
|
is the real stop mechanism.
|
||||||
|
|
||||||
|
**`UninstallRunner`** (`src/ClaudeDo.Installer/Core/UninstallRunner.cs`)
|
||||||
|
- Keep the existing `schtasks /Delete` and `sc delete` (migration/legacy cleanup).
|
||||||
|
- **Add** deletion of the Startup-folder `.lnk` alongside the existing Start Menu /
|
||||||
|
Desktop shortcut removal.
|
||||||
|
|
||||||
|
**Shared shortcut helper**
|
||||||
|
- Extract the `IShellLink` COM interop currently embedded in `CreateShortcutsStep` into a
|
||||||
|
shared `src/ClaudeDo.Installer/Core/ShortcutFactory.cs` (`CreateShortcut(path, target,
|
||||||
|
workingDir, description)`). Both `CreateShortcutsStep` and `RegisterAutostartStep` use it.
|
||||||
|
|
||||||
|
**Cleanup**
|
||||||
|
- Delete `src/ClaudeDo.Installer/Core/ScheduledTaskXml.cs` once unreferenced.
|
||||||
|
|
||||||
|
The autostart shortcut name and location: `ClaudeDo Worker.lnk` in
|
||||||
|
`Environment.SpecialFolder.Startup`, working directory `{InstallDirectory}\worker`.
|
||||||
|
|
||||||
|
### Component 2 — App: stop auto-spawning the worker
|
||||||
|
|
||||||
|
**`IslandsShellViewModel`** (`src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs`)
|
||||||
|
- Remove the `_ = EnsureWorkerRunningAsync();` call (line 224) and the
|
||||||
|
`EnsureWorkerRunningAsync` method + its `_ensureRunningAttempted` flag.
|
||||||
|
- Keep the worker-launch logic (`RestartWorkerService`, which finds the worker exe via
|
||||||
|
`WorkerLocator` and starts it) — it becomes the backing action for the prompt's
|
||||||
|
**Start Worker** button. The existing `RestartWorkerAsync` command stays.
|
||||||
|
|
||||||
|
### Component 3 — App: connection-failure prompt
|
||||||
|
|
||||||
|
**New dialog** `WorkerConnectionModalViewModel`
|
||||||
|
(`src/ClaudeDo.Ui/ViewModels/Modals/WorkerConnectionModalViewModel.cs`) +
|
||||||
|
`WorkerConnectionModalView` (`src/ClaudeDo.Ui/Views/Modals/`).
|
||||||
|
- Buttons: **Start Worker**, **Rerun Installer**, **Dismiss**.
|
||||||
|
- Uses the established dialog pattern: a `Func<WorkerConnectionModalViewModel, Task>`
|
||||||
|
hook on `IslandsShellViewModel` set by `MainWindow` (mirroring `ShowAboutModal`), and
|
||||||
|
the dialog resolves a `TaskCompletionSource` on button press.
|
||||||
|
- **Start Worker** → `WorkerLocator.Find()` + `Process.Start` (reuse the
|
||||||
|
`RestartWorkerService` path). **Rerun Installer** → `InstallerLocator.Find()` + launch
|
||||||
|
+ `Environment.Exit(0)` (same pattern as the existing `UpdateNow` command).
|
||||||
|
**Dismiss** → close.
|
||||||
|
|
||||||
|
**Trigger logic** (in `IslandsShellViewModel`)
|
||||||
|
- A one-shot grace timer (~12s) started on construction/startup. When it elapses, if the
|
||||||
|
worker is still offline (`IsOffline` — not connected and not reconnecting) and the
|
||||||
|
prompt hasn't been shown yet (`_connectionPromptShown`), show the dialog once and set
|
||||||
|
the flag.
|
||||||
|
- If the worker connects before the grace elapses, the prompt is never shown.
|
||||||
|
|
||||||
|
**Clickable Offline pill** (`src/ClaudeDo.Ui/Views/MainWindow.axaml`)
|
||||||
|
- Turn the footer status pill into a button bound to a command that opens the same dialog
|
||||||
|
on demand (independent of the one-shot flag), so the user can reopen guidance anytime
|
||||||
|
while offline.
|
||||||
|
|
||||||
|
### Component 4 — Dev
|
||||||
|
|
||||||
|
No code change (see Non-Goals).
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Startup (production):
|
||||||
|
Windows logon -> Startup-folder .lnk -> ClaudeDo.Worker.exe (WinExe, mutex-guarded)
|
||||||
|
App launches -> WorkerClient connects to 127.0.0.1:47821
|
||||||
|
connected within grace -> Online pill, no prompt
|
||||||
|
still offline after ~12s -> WorkerConnectionModal (once)
|
||||||
|
|
||||||
|
User clicks Offline pill (anytime offline) -> WorkerConnectionModal
|
||||||
|
Start Worker -> Process.Start(worker exe)
|
||||||
|
Rerun Installer -> Process.Start(installer), Environment.Exit(0)
|
||||||
|
Dismiss -> close
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- Worker exe / installer not found (`Locator.Find()` returns null): the corresponding
|
||||||
|
dialog button is a no-op (consistent with existing `UpdateNow` behavior); the dialog
|
||||||
|
stays open so the user can pick another action.
|
||||||
|
- Startup-shortcut creation failure in the installer: surfaced as a failed install step
|
||||||
|
(`StepResult.Fail`), same as the current task-registration failure path.
|
||||||
|
- Legacy scheduled-task deletion is best-effort and never fails the install.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- **`Installer.Tests`**: `RegisterAutostartStep` creates the Startup `.lnk` at the
|
||||||
|
expected path with the correct target, and issues the legacy-task delete command.
|
||||||
|
`UninstallRunner` removes the Startup `.lnk`.
|
||||||
|
- **`Ui.Tests`**: prompt trigger logic — grace elapsed while offline shows the prompt
|
||||||
|
exactly once; a connection established before grace suppresses it; the clickable-pill
|
||||||
|
command opens the dialog regardless of the one-shot flag. (Abstract the dialog-show
|
||||||
|
hook so it can be asserted without real UI.)
|
||||||
|
- **Manual**: dialog buttons (Start Worker / Rerun Installer / Dismiss) and the clickable
|
||||||
|
Offline pill in a running App.
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# Prime: recurring weekday schedule
|
||||||
|
|
||||||
|
**Date:** 2026-06-02
|
||||||
|
**Status:** Approved
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The Prime feature fires a single non-interactive "ping" prompt to warm up the
|
||||||
|
Claude usage window. Today a schedule is defined by a **date range**
|
||||||
|
(`StartDate`/`EndDate`) plus a `TimeOfDay` and a single `WorkdaysOnly` toggle.
|
||||||
|
This is awkward for the real use case: the user wants a *recurring* morning ping
|
||||||
|
on specific weekdays, not a bounded calendar window.
|
||||||
|
|
||||||
|
Desired behavior: pick the **days of the week** (e.g. Mon–Fri) and a **time**.
|
||||||
|
The schedule recurs forever. Whenever the worker is running and it is one of the
|
||||||
|
selected days, the ping fires at (or shortly after) the chosen time. Concretely:
|
||||||
|
the worker autostarts on login, detects it is an eligible day around the target
|
||||||
|
time, and fires the ping.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- **Catch-up window:** unchanged. Keep the existing 30-minute catch-up — if the
|
||||||
|
worker boots within 30 min after the target time, the ping fires immediately;
|
||||||
|
otherwise it waits for the next eligible day. (User chose "keep current 30 min".)
|
||||||
|
- **Day picker UI:** seven compact **toggle buttons** in one row (Mo Tu We Th Fr
|
||||||
|
Sa Su), highlighted when selected — not labeled checkboxes.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### 1. Data model
|
||||||
|
|
||||||
|
`PrimeScheduleEntity` (`ClaudeDo.Data/Models`):
|
||||||
|
|
||||||
|
- **Remove:** `StartDate`, `EndDate`, `WorkdaysOnly`
|
||||||
|
- **Add:** `Days` — a `[Flags] enum PrimeDays` (`Monday=1, Tuesday=2, Wednesday=4,
|
||||||
|
Thursday=8, Friday=16, Saturday=32, Sunday=64`), stored as a single
|
||||||
|
`days_of_week INTEGER` column.
|
||||||
|
- **Keep:** `TimeOfDay`, `Enabled`, `LastRunAt`, `PromptOverride`, `CreatedAt`.
|
||||||
|
|
||||||
|
Rationale for a bitmask over a CSV string or 7 bool columns: one column, trivial
|
||||||
|
EF mapping (int), and a clean eligibility check.
|
||||||
|
|
||||||
|
`PrimeScheduleEntityConfiguration`: drop the `start_date`/`end_date`/
|
||||||
|
`workdays_only` property mappings; map `Days` to `days_of_week` (int, required,
|
||||||
|
default 31 = Mon–Fri).
|
||||||
|
|
||||||
|
### 2. Scheduling logic — `NextDueCalculator`
|
||||||
|
|
||||||
|
- Drop all `StartDate`/`EndDate` gating (the `EndDate < today` early-out, the
|
||||||
|
`StartDate > today` clamps, and the bounds check in `IsEligibleDay`).
|
||||||
|
- `IsEligibleDay(s, d)` becomes: does `s.Days` contain the flag for
|
||||||
|
`d.DayOfWeek`? (Map `System.DayOfWeek` → `PrimeDays`.)
|
||||||
|
- The existing forward search (loops up to 8 days ahead) now simply walks to the
|
||||||
|
next selected weekday.
|
||||||
|
- `alreadyFiredToday` (compares `LastRunAt`'s local date to today) is unchanged.
|
||||||
|
- The 30-min catch-up (`FireImmediately`) is unchanged.
|
||||||
|
- A schedule with `Days == 0` (none selected) is never eligible. UI validation
|
||||||
|
prevents saving that state.
|
||||||
|
|
||||||
|
### 3. UI — `SettingsModalView.axaml` + `PrimeScheduleRowViewModel`
|
||||||
|
|
||||||
|
Row template changes:
|
||||||
|
- **Remove** the `ThemedDatePicker` (range) and the single "Mon–Fri" checkbox.
|
||||||
|
- **Add** a horizontal row of 7 `ToggleButton`s (Mo Tu We Th Fr Sa Su), styled
|
||||||
|
to highlight when checked, bound to seven bool properties on the row VM.
|
||||||
|
- Keep the enabled checkbox, the time `TextBox`, the last-run label, and the
|
||||||
|
remove button.
|
||||||
|
|
||||||
|
`PrimeScheduleRowViewModel`:
|
||||||
|
- Replace `StartDate`/`EndDate`/`WorkdaysOnly` with seven `[ObservableProperty]`
|
||||||
|
bools: `Monday`…`Sunday`.
|
||||||
|
- Constructor decomposes `dto.Days` into the seven bools.
|
||||||
|
- `ToDto()` composes the seven bools back into the `Days` int.
|
||||||
|
|
||||||
|
`PrimeClaudeTabViewModel`:
|
||||||
|
- `AddSchedule` default: Mon–Fri selected, time 07:00, enabled.
|
||||||
|
- `Validate`: replace the `StartDate > EndDate` check with "at least one day must
|
||||||
|
be selected"; keep the time-range (00:00–23:59) check.
|
||||||
|
|
||||||
|
Update the explainer `TextBlock` text to describe weekday recurrence (keep the
|
||||||
|
"fires immediately if started within 30 minutes of the target time" note).
|
||||||
|
|
||||||
|
### 4. Migration
|
||||||
|
|
||||||
|
New EF Core migration in `ClaudeDo.Data/Migrations`:
|
||||||
|
- Add `days_of_week INTEGER NOT NULL DEFAULT 31`.
|
||||||
|
- Backfill from existing rows: `workdays_only = 1` → `31` (Mon–Fri),
|
||||||
|
`workdays_only = 0` → `127` (all 7 days).
|
||||||
|
- Drop `start_date`, `end_date`, `workdays_only`.
|
||||||
|
- Update the model snapshot.
|
||||||
|
|
||||||
|
### 5. DTOs
|
||||||
|
|
||||||
|
Both copies of `PrimeScheduleDto` (Worker `ClaudeDo.Worker.Prime` and UI
|
||||||
|
`ClaudeDo.Ui.Services`) are passed over SignalR and must stay structurally
|
||||||
|
compatible. In both: remove `StartDate`, `EndDate`, `WorkdaysOnly`; add a single
|
||||||
|
`int Days` field (serializes cleanly as JSON; avoids sharing the enum across
|
||||||
|
projects). `PrimeScheduler.ToDto` maps `entity.Days` → `(int)`.
|
||||||
|
|
||||||
|
`PrimeScheduleRepository`: update `UpsertAsync` (copy `Days` instead of the three
|
||||||
|
removed fields) and `ListAsync` ordering (order by `TimeOfDay` instead of
|
||||||
|
`StartDate`).
|
||||||
|
|
||||||
|
### 6. Tests
|
||||||
|
|
||||||
|
- `NextDueCalculatorTests` — rewrite cases around weekday sets (e.g. Mon–Fri
|
||||||
|
skips weekend; single-day schedule; catch-up still fires; already-fired-today
|
||||||
|
skips to next eligible day).
|
||||||
|
- `PrimeSchedulerTests` — update fixture DTOs to the new shape.
|
||||||
|
- `PrimeScheduleRepositoryTests` — update entity construction and assertions.
|
||||||
|
- `PrimeClaudeTabViewModelTests` — update for the day-bool VM and new validation.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Per-schedule catch-up tuning (rejected; fixed 30 min).
|
||||||
|
- Multiple times per day, timezones, or holiday calendars.
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceInclude Source="avares://ClaudeDo.Ui/Design/Tokens.axaml" />
|
<ResourceInclude Source="avares://ClaudeDo.Ui/Design/Tokens.axaml" />
|
||||||
|
<ResourceInclude Source="avares://ClaudeDo.Ui/Views/Controls/ModalShell.axaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
<!-- Converters -->
|
<!-- Converters -->
|
||||||
@@ -31,6 +32,13 @@
|
|||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<FluentTheme />
|
||||||
<StyleInclude Source="avares://ClaudeDo.Ui/Design/IslandStyles.axaml" />
|
<StyleInclude Source="avares://ClaudeDo.Ui/Design/IslandStyles.axaml" />
|
||||||
|
<!-- Global defaults: every Window inherits Inter Tight + body size.
|
||||||
|
Controls that need mono opt in via their own class/style. -->
|
||||||
|
<Style Selector="Window">
|
||||||
|
<Setter Property="FontFamily" Value="{DynamicResource SansFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{DynamicResource FontSizeBody}" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" />
|
||||||
|
</Style>
|
||||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{DynamicResource AccentGlowBrush}"/>
|
<Setter Property="Background" Value="{DynamicResource AccentGlowBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ Shared data layer: models, repositories, SQLite infrastructure, and git operatio
|
|||||||
|
|
||||||
## Models
|
## Models
|
||||||
|
|
||||||
- **TaskEntity** — Id, ListId, Title, Description, Status (`Idle|Queued|Running|Done|Failed|Cancelled`), PlanningPhase (`None|Active|Finalized` — parent-only), BlockedByTaskId (nullable FK to predecessor in a chain), ScheduledFor, Result, LogPath, timestamps, CommitType, Model / SystemPrompt / AgentPath (nullable overrides), IsStarred, IsMyDay, Notes, ParentTaskId, PlanningSessionId, PlanningSessionToken, PlanningFinalizedAt, CreatedBy. Legacy values `Manual`/`Planning`/`Planned`/`Draft`/`Waiting` were retired; existing rows backfill automatically via the `RetireLegacyTaskStatus` migration.
|
- **TaskEntity** — Id, ListId, Title, Description, Status (`Idle|Queued|Running|WaitingForReview|Done|Failed|Cancelled`), PlanningPhase (`None|Active|Finalized` — parent-only), BlockedByTaskId (nullable FK to predecessor in a chain), ScheduledFor, Result, ReviewFeedback (nullable; reviewer's rejection comment, consumed and cleared by the runner on the next re-run), LogPath, timestamps, CommitType, Model / SystemPrompt / AgentPath (nullable overrides), IsStarred, IsMyDay, Notes, ParentTaskId, PlanningSessionId, PlanningSessionToken, PlanningFinalizedAt, CreatedBy. Legacy values `Manual`/`Planning`/`Planned`/`Draft`/`Waiting` were retired; existing rows backfill automatically via the `RetireLegacyTaskStatus` migration.
|
||||||
- **ListEntity** — Id, Name, WorkingDir, DefaultCommitType, CreatedAt
|
- **ListEntity** — Id, Name, WorkingDir, DefaultCommitType, CreatedAt
|
||||||
- **ListConfigEntity** — ListId (PK, 1:1 with list), Model, SystemPrompt, AgentPath (all nullable)
|
- **ListConfigEntity** — ListId (PK, 1:1 with list), Model, SystemPrompt, AgentPath (all nullable)
|
||||||
- **WorktreeEntity** — TaskId (PK, 1:1 with task), Path, BranchName, BaseCommit, HeadCommit, DiffStat, State (Active|Merged|Discarded|Kept)
|
- **WorktreeEntity** — TaskId (PK, 1:1 with task), Path, BranchName, BaseCommit, HeadCommit, DiffStat, State (Active|Merged|Discarded|Kept)
|
||||||
- **TaskRunEntity** — per-run record (session_id, tokens, turns, result, structured output, exit code, log path)
|
- **TaskRunEntity** — per-run record (session_id, tokens, turns, result, structured output, exit code, log path)
|
||||||
|
- **PrimeScheduleEntity** — Id, Days (`[Flags] PrimeDays` weekday bitmask, stored as `days_of_week` int), TimeOfDay, Enabled, LastRunAt, PromptOverride, CreatedAt. Recurs on the selected weekdays; no date range.
|
||||||
- **SubtaskEntity**, **AppSettingsEntity**, **AgentInfo** — existing helpers / settings / record for scanned agent files
|
- **SubtaskEntity**, **AppSettingsEntity**, **AgentInfo** — existing helpers / settings / record for scanned agent files
|
||||||
|
|
||||||
## Repositories
|
## Repositories
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using ClaudeDo.Data.Seeding;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
namespace ClaudeDo.Data;
|
namespace ClaudeDo.Data;
|
||||||
|
|
||||||
@@ -19,9 +20,24 @@ public class ClaudeDoDbContext : DbContext
|
|||||||
public DbSet<AppSettingsEntity> AppSettings => Set<AppSettingsEntity>();
|
public DbSet<AppSettingsEntity> AppSettings => Set<AppSettingsEntity>();
|
||||||
public DbSet<PrimeScheduleEntity> PrimeSchedules => Set<PrimeScheduleEntity>();
|
public DbSet<PrimeScheduleEntity> PrimeSchedules => Set<PrimeScheduleEntity>();
|
||||||
|
|
||||||
|
private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
|
||||||
|
new(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
private static readonly ValueConverter<DateTime?, DateTime?> UtcNullableConverter =
|
||||||
|
new(v => v, v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : null);
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ClaudeDoDbContext).Assembly);
|
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ClaudeDoDbContext).Assembly);
|
||||||
|
|
||||||
|
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||||
|
foreach (var property in entityType.GetProperties())
|
||||||
|
{
|
||||||
|
if (property.ClrType == typeof(DateTime) && property.GetValueConverter() == null)
|
||||||
|
property.SetValueConverter(UtcConverter);
|
||||||
|
else if (property.ClrType == typeof(DateTime?) && property.GetValueConverter() == null)
|
||||||
|
property.SetValueConverter(UtcNullableConverter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ public class AppSettingsEntityConfiguration : IEntityTypeConfiguration<AppSettin
|
|||||||
builder.Property(s => s.DefaultPermissionMode)
|
builder.Property(s => s.DefaultPermissionMode)
|
||||||
.HasColumnName("default_permission_mode").IsRequired().HasDefaultValue("bypassPermissions");
|
.HasColumnName("default_permission_mode").IsRequired().HasDefaultValue("bypassPermissions");
|
||||||
|
|
||||||
|
builder.Property(s => s.MaxParallelExecutions)
|
||||||
|
.HasColumnName("max_parallel_executions").IsRequired().HasDefaultValue(1);
|
||||||
|
|
||||||
builder.Property(s => s.WorktreeStrategy)
|
builder.Property(s => s.WorktreeStrategy)
|
||||||
.HasColumnName("worktree_strategy").IsRequired().HasDefaultValue("sibling");
|
.HasColumnName("worktree_strategy").IsRequired().HasDefaultValue("sibling");
|
||||||
builder.Property(s => s.CentralWorktreeRoot)
|
builder.Property(s => s.CentralWorktreeRoot)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ public class PrimeScheduleEntityConfiguration : IEntityTypeConfiguration<PrimeSc
|
|||||||
builder.HasKey(s => s.Id);
|
builder.HasKey(s => s.Id);
|
||||||
builder.Property(s => s.Id).HasColumnName("id").ValueGeneratedNever();
|
builder.Property(s => s.Id).HasColumnName("id").ValueGeneratedNever();
|
||||||
|
|
||||||
builder.Property(s => s.StartDate).HasColumnName("start_date").IsRequired();
|
builder.Property(s => s.Days).HasColumnName("days_of_week")
|
||||||
builder.Property(s => s.EndDate).HasColumnName("end_date").IsRequired();
|
.IsRequired().HasDefaultValue(PrimeDays.Weekdays);
|
||||||
builder.Property(s => s.TimeOfDay).HasColumnName("time_of_day").IsRequired();
|
builder.Property(s => s.TimeOfDay).HasColumnName("time_of_day").IsRequired();
|
||||||
builder.Property(s => s.WorkdaysOnly).HasColumnName("workdays_only").IsRequired().HasDefaultValue(true);
|
|
||||||
builder.Property(s => s.Enabled).HasColumnName("enabled").IsRequired().HasDefaultValue(true);
|
builder.Property(s => s.Enabled).HasColumnName("enabled").IsRequired().HasDefaultValue(true);
|
||||||
builder.Property(s => s.LastRunAt).HasColumnName("last_run_at");
|
builder.Property(s => s.LastRunAt).HasColumnName("last_run_at");
|
||||||
builder.Property(s => s.PromptOverride).HasColumnName("prompt_override");
|
builder.Property(s => s.PromptOverride).HasColumnName("prompt_override");
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|||||||
TaskStatus.Idle => "idle",
|
TaskStatus.Idle => "idle",
|
||||||
TaskStatus.Queued => "queued",
|
TaskStatus.Queued => "queued",
|
||||||
TaskStatus.Running => "running",
|
TaskStatus.Running => "running",
|
||||||
|
TaskStatus.WaitingForReview => "waiting_for_review",
|
||||||
TaskStatus.Done => "done",
|
TaskStatus.Done => "done",
|
||||||
TaskStatus.Failed => "failed",
|
TaskStatus.Failed => "failed",
|
||||||
TaskStatus.Cancelled => "cancelled",
|
TaskStatus.Cancelled => "cancelled",
|
||||||
@@ -26,6 +27,7 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|||||||
"idle" => TaskStatus.Idle,
|
"idle" => TaskStatus.Idle,
|
||||||
"queued" => TaskStatus.Queued,
|
"queued" => TaskStatus.Queued,
|
||||||
"running" => TaskStatus.Running,
|
"running" => TaskStatus.Running,
|
||||||
|
"waiting_for_review" => TaskStatus.WaitingForReview,
|
||||||
"done" => TaskStatus.Done,
|
"done" => TaskStatus.Done,
|
||||||
"failed" => TaskStatus.Failed,
|
"failed" => TaskStatus.Failed,
|
||||||
"cancelled" => TaskStatus.Cancelled,
|
"cancelled" => TaskStatus.Cancelled,
|
||||||
@@ -72,6 +74,7 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration<TaskEntity>
|
|||||||
builder.Property(t => t.BlockedByTaskId).HasColumnName("blocked_by_task_id");
|
builder.Property(t => t.BlockedByTaskId).HasColumnName("blocked_by_task_id");
|
||||||
builder.Property(t => t.ScheduledFor).HasColumnName("scheduled_for");
|
builder.Property(t => t.ScheduledFor).HasColumnName("scheduled_for");
|
||||||
builder.Property(t => t.Result).HasColumnName("result");
|
builder.Property(t => t.Result).HasColumnName("result");
|
||||||
|
builder.Property(t => t.ReviewFeedback).HasColumnName("review_feedback");
|
||||||
builder.Property(t => t.LogPath).HasColumnName("log_path");
|
builder.Property(t => t.LogPath).HasColumnName("log_path");
|
||||||
builder.Property(t => t.CreatedAt).HasColumnName("created_at").IsRequired();
|
builder.Property(t => t.CreatedAt).HasColumnName("created_at").IsRequired();
|
||||||
builder.Property(t => t.StartedAt).HasColumnName("started_at");
|
builder.Property(t => t.StartedAt).HasColumnName("started_at");
|
||||||
|
|||||||
482
src/ClaudeDo.Data/Migrations/20260416064948_InitialCreate.Designer.cs
generated
Normal file
482
src/ClaudeDo.Data/Migrations/20260416064948_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
// <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("20260416064948_InitialCreate")]
|
||||||
|
partial class InitialCreate
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
|
||||||
|
|
||||||
|
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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("lists", (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.TagEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("tags", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1L,
|
||||||
|
Name = "agent"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2L,
|
||||||
|
Name = "manual"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FinishedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("finished_at");
|
||||||
|
|
||||||
|
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>("Result")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
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("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
|
b.HasIndex("Status")
|
||||||
|
.HasDatabaseName("idx_tasks_status");
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("list_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("list_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("list_tags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("task_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("task_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("task_tags", (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.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("list_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("task_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
498
src/ClaudeDo.Data/Migrations/20260420075929_AddTaskFlagsAndNotes.Designer.cs
generated
Normal file
498
src/ClaudeDo.Data/Migrations/20260420075929_AddTaskFlagsAndNotes.Designer.cs
generated
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
// <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("20260420075929_AddTaskFlagsAndNotes")]
|
||||||
|
partial class AddTaskFlagsAndNotes
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
|
||||||
|
|
||||||
|
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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("lists", (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.TagEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("tags", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1L,
|
||||||
|
Name = "agent"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2L,
|
||||||
|
Name = "manual"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
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>("Result")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
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("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
|
b.HasIndex("Status")
|
||||||
|
.HasDatabaseName("idx_tasks_status");
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("list_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("list_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("list_tags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("task_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("task_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("task_tags", (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.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("list_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("task_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
572
src/ClaudeDo.Data/Migrations/20260421113614_AddAppSettings.Designer.cs
generated
Normal file
572
src/ClaudeDo.Data/Migrations/20260421113614_AddAppSettings.Designer.cs
generated
Normal file
@@ -0,0 +1,572 @@
|
|||||||
|
// <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("20260421113614_AddAppSettings")]
|
||||||
|
partial class AddAppSettings
|
||||||
|
{
|
||||||
|
/// <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<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 = 30,
|
||||||
|
DefaultModel = "sonnet",
|
||||||
|
DefaultPermissionMode = "bypassPermissions",
|
||||||
|
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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("lists", (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.TagEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("tags", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1L,
|
||||||
|
Name = "agent"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2L,
|
||||||
|
Name = "manual"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
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>("Result")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("scheduled_for");
|
||||||
|
|
||||||
|
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("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_id");
|
||||||
|
|
||||||
|
b.HasIndex("Status")
|
||||||
|
.HasDatabaseName("idx_tasks_status");
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("list_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("list_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("list_tags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("task_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("task_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("task_tags", (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.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("list_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("task_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
581
src/ClaudeDo.Data/Migrations/20260422120000_AddTaskSortOrder.Designer.cs
generated
Normal file
581
src/ClaudeDo.Data/Migrations/20260422120000_AddTaskSortOrder.Designer.cs
generated
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
// <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("20260422120000_AddTaskSortOrder")]
|
||||||
|
partial class AddTaskSortOrder
|
||||||
|
{
|
||||||
|
/// <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<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 = 30,
|
||||||
|
DefaultModel = "sonnet",
|
||||||
|
DefaultPermissionMode = "bypassPermissions",
|
||||||
|
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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("lists", (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.TagEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("tags", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1L,
|
||||||
|
Name = "agent"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2L,
|
||||||
|
Name = "manual"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
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>("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("ListId")
|
||||||
|
.HasDatabaseName("idx_tasks_list_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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("list_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("list_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("list_tags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("task_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("task_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("task_tags", (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.ListEntity", "List")
|
||||||
|
.WithMany("Tasks")
|
||||||
|
.HasForeignKey("ListId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("List");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("list_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("task_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.ListEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Config");
|
||||||
|
|
||||||
|
b.Navigation("Tasks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClaudeDo.Data.Models.TaskEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Runs");
|
||||||
|
|
||||||
|
b.Navigation("Subtasks");
|
||||||
|
|
||||||
|
b.Navigation("Worktree");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
609
src/ClaudeDo.Data/Migrations/20260423154708_AddPlanningSupport.Designer.cs
generated
Normal file
609
src/ClaudeDo.Data/Migrations/20260423154708_AddPlanningSupport.Designer.cs
generated
Normal file
@@ -0,0 +1,609 @@
|
|||||||
|
// <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("20260423154708_AddPlanningSupport")]
|
||||||
|
partial class AddPlanningSupport
|
||||||
|
{
|
||||||
|
/// <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<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 = 30,
|
||||||
|
DefaultModel = "sonnet",
|
||||||
|
DefaultPermissionMode = "bypassPermissions",
|
||||||
|
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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("lists", (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.TagEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("tags", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1L,
|
||||||
|
Name = "agent"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2L,
|
||||||
|
Name = "manual"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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>("CommitType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValue("chore")
|
||||||
|
.HasColumnName("commit_type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
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>("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("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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("list_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("list_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("list_tags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("task_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("task_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("task_tags", (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.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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("list_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("task_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
613
src/ClaudeDo.Data/Migrations/20260424212250_AddTaskCreatedBy.Designer.cs
generated
Normal file
613
src/ClaudeDo.Data/Migrations/20260424212250_AddTaskCreatedBy.Designer.cs
generated
Normal file
@@ -0,0 +1,613 @@
|
|||||||
|
// <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("20260424212250_AddTaskCreatedBy")]
|
||||||
|
partial class AddTaskCreatedBy
|
||||||
|
{
|
||||||
|
/// <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<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 = "bypassPermissions",
|
||||||
|
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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("lists", (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.TagEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("tags", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1L,
|
||||||
|
Name = "agent"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2L,
|
||||||
|
Name = "manual"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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>("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>("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("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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("list_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("list_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("list_tags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("task_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("task_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("task_tags", (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.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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("list_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("task_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
632
src/ClaudeDo.Data/Migrations/20260427082248_AddPlanningPhaseAndBlockedBy.Designer.cs
generated
Normal file
632
src/ClaudeDo.Data/Migrations/20260427082248_AddPlanningPhaseAndBlockedBy.Designer.cs
generated
Normal file
@@ -0,0 +1,632 @@
|
|||||||
|
// <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("20260427082248_AddPlanningPhaseAndBlockedBy")]
|
||||||
|
partial class AddPlanningPhaseAndBlockedBy
|
||||||
|
{
|
||||||
|
/// <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<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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("lists", (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.TagEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("tags", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1L,
|
||||||
|
Name = "agent"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2L,
|
||||||
|
Name = "manual"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("list_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("list_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("list_tags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("task_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("task_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("task_tags", (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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("list_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("task_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
632
src/ClaudeDo.Data/Migrations/20260427130058_RetireLegacyTaskStatus.Designer.cs
generated
Normal file
632
src/ClaudeDo.Data/Migrations/20260427130058_RetireLegacyTaskStatus.Designer.cs
generated
Normal file
@@ -0,0 +1,632 @@
|
|||||||
|
// <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("20260427130058_RetireLegacyTaskStatus")]
|
||||||
|
partial class RetireLegacyTaskStatus
|
||||||
|
{
|
||||||
|
/// <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<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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("lists", (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.TagEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("tags", (string)null);
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1L,
|
||||||
|
Name = "agent"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2L,
|
||||||
|
Name = "manual"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("list_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("list_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("list_tags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("task_id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("tag_id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("task_id", "tag_id");
|
||||||
|
|
||||||
|
b.HasIndex("tag_id");
|
||||||
|
|
||||||
|
b.ToTable("task_tags", (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("list_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.ListEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("list_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("task_tags", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TagEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("tag_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ClaudeDo.Data.Models.TaskEntity", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("task_id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
587
src/ClaudeDo.Data/Migrations/20260519044715_RemoveTags.Designer.cs
generated
Normal file
587
src/ClaudeDo.Data/Migrations/20260519044715_RemoveTags.Designer.cs
generated
Normal file
@@ -0,0 +1,587 @@
|
|||||||
|
// <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("20260519044715_RemoveTags")]
|
||||||
|
partial class RemoveTags
|
||||||
|
{
|
||||||
|
/// <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<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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
591
src/ClaudeDo.Data/Migrations/20260529142614_AddRepoImportFolders.Designer.cs
generated
Normal file
591
src/ClaudeDo.Data/Migrations/20260529142614_AddRepoImportFolders.Designer.cs
generated
Normal file
@@ -0,0 +1,591 @@
|
|||||||
|
// <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("20260529142614_AddRepoImportFolders")]
|
||||||
|
partial class AddRepoImportFolders
|
||||||
|
{
|
||||||
|
/// <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<string>("WorkingDir")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("working_dir");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
607
src/ClaudeDo.Data/Migrations/20260601133737_AddMaxParallelExecutions.Designer.cs
generated
Normal file
607
src/ClaudeDo.Data/Migrations/20260601133737_AddMaxParallelExecutions.Designer.cs
generated
Normal file
@@ -0,0 +1,607 @@
|
|||||||
|
// <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("20260601133737_AddMaxParallelExecutions")]
|
||||||
|
partial class AddMaxParallelExecutions
|
||||||
|
{
|
||||||
|
/// <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<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
|
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",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
|
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,36 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddMaxParallelExecutions : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "max_parallel_executions",
|
||||||
|
table: "app_settings",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 1);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "app_settings",
|
||||||
|
keyColumn: "id",
|
||||||
|
keyValue: 1,
|
||||||
|
column: "max_parallel_executions",
|
||||||
|
value: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "max_parallel_executions",
|
||||||
|
table: "app_settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
607
src/ClaudeDo.Data/Migrations/20260601140000_NormalizeListIdFormat.Designer.cs
generated
Normal file
607
src/ClaudeDo.Data/Migrations/20260601140000_NormalizeListIdFormat.Designer.cs
generated
Normal file
@@ -0,0 +1,607 @@
|
|||||||
|
// <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("20260601140000_NormalizeListIdFormat")]
|
||||||
|
partial class NormalizeListIdFormat
|
||||||
|
{
|
||||||
|
/// <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<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
|
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",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
|
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,52 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class NormalizeListIdFormat : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// SQLite: PRAGMA foreign_keys must run outside a transaction.
|
||||||
|
migrationBuilder.Sql("PRAGMA foreign_keys = OFF;", suppressTransaction: true);
|
||||||
|
|
||||||
|
// Normalize tasks.list_id: 32-char compact hex → 36-char dashed UUID
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
UPDATE tasks
|
||||||
|
SET list_id = substr(list_id,1,8)||'-'||substr(list_id,9,4)||'-'||substr(list_id,13,4)||'-'||substr(list_id,17,4)||'-'||substr(list_id,21,12)
|
||||||
|
WHERE length(list_id) = 32;
|
||||||
|
""");
|
||||||
|
|
||||||
|
// Normalize list_config.list_id (also the PK of that table)
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
UPDATE list_config
|
||||||
|
SET list_id = substr(list_id,1,8)||'-'||substr(list_id,9,4)||'-'||substr(list_id,13,4)||'-'||substr(list_id,17,4)||'-'||substr(list_id,21,12)
|
||||||
|
WHERE length(list_id) = 32;
|
||||||
|
""");
|
||||||
|
|
||||||
|
// Normalize lists.id (PK — must come last)
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
UPDATE lists
|
||||||
|
SET id = substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||substr(id,17,4)||'-'||substr(id,21,12)
|
||||||
|
WHERE length(id) = 32;
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("PRAGMA foreign_keys = ON;", suppressTransaction: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("PRAGMA foreign_keys = OFF;", suppressTransaction: true);
|
||||||
|
|
||||||
|
migrationBuilder.Sql("UPDATE tasks SET list_id = replace(list_id,'-','') WHERE length(list_id) = 36;");
|
||||||
|
migrationBuilder.Sql("UPDATE list_config SET list_id = replace(list_id,'-','') WHERE length(list_id) = 36;");
|
||||||
|
migrationBuilder.Sql("UPDATE lists SET id = replace(id,'-','') WHERE length(id) = 36;");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("PRAGMA foreign_keys = ON;", suppressTransaction: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
611
src/ClaudeDo.Data/Migrations/20260601150820_AddReviewFeedback.Designer.cs
generated
Normal file
611
src/ClaudeDo.Data/Migrations/20260601150820_AddReviewFeedback.Designer.cs
generated
Normal file
@@ -0,0 +1,611 @@
|
|||||||
|
// <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("20260601150820_AddReviewFeedback")]
|
||||||
|
partial class AddReviewFeedback
|
||||||
|
{
|
||||||
|
/// <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<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
|
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",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
|
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<string>("ReviewFeedback")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("review_feedback");
|
||||||
|
|
||||||
|
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,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddReviewFeedback : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "review_feedback",
|
||||||
|
table: "tasks",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "review_feedback",
|
||||||
|
table: "tasks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
603
src/ClaudeDo.Data/Migrations/20260602060000_PrimeWeekdays.Designer.cs
generated
Normal file
603
src/ClaudeDo.Data/Migrations/20260602060000_PrimeWeekdays.Designer.cs
generated
Normal file
@@ -0,0 +1,603 @@
|
|||||||
|
// <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("20260602060000_PrimeWeekdays")]
|
||||||
|
partial class PrimeWeekdays
|
||||||
|
{
|
||||||
|
/// <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<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
|
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",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
|
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<int>("Days")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(31)
|
||||||
|
.HasColumnName("days_of_week");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("enabled");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LastRunAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last_run_at");
|
||||||
|
|
||||||
|
b.Property<string>("PromptOverride")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("prompt_override");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("TimeOfDay")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("time_of_day");
|
||||||
|
|
||||||
|
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<string>("ReviewFeedback")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("review_feedback");
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/ClaudeDo.Data/Migrations/20260602060000_PrimeWeekdays.cs
Normal file
48
src/ClaudeDo.Data/Migrations/20260602060000_PrimeWeekdays.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class PrimeWeekdays : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "days_of_week",
|
||||||
|
table: "prime_schedules",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 31);
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"UPDATE prime_schedules SET days_of_week = CASE WHEN workdays_only = 1 THEN 31 ELSE 127 END;");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(name: "start_date", table: "prime_schedules");
|
||||||
|
migrationBuilder.DropColumn(name: "end_date", table: "prime_schedules");
|
||||||
|
migrationBuilder.DropColumn(name: "workdays_only", table: "prime_schedules");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateOnly>(
|
||||||
|
name: "start_date", table: "prime_schedules",
|
||||||
|
type: "TEXT", nullable: false, defaultValue: new DateOnly(2000, 1, 1));
|
||||||
|
migrationBuilder.AddColumn<DateOnly>(
|
||||||
|
name: "end_date", table: "prime_schedules",
|
||||||
|
type: "TEXT", nullable: false, defaultValue: new DateOnly(2099, 12, 31));
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "workdays_only", table: "prime_schedules",
|
||||||
|
type: "INTEGER", nullable: false, defaultValue: true);
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"UPDATE prime_schedules SET workdays_only = CASE WHEN days_of_week = 127 THEN 0 ELSE 1 END;");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(name: "days_of_week", table: "prime_schedules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,6 +54,12 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasDefaultValue("bypassPermissions")
|
.HasDefaultValue("bypassPermissions")
|
||||||
.HasColumnName("default_permission_mode");
|
.HasColumnName("default_permission_mode");
|
||||||
|
|
||||||
|
b.Property<int>("MaxParallelExecutions")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("max_parallel_executions");
|
||||||
|
|
||||||
b.Property<string>("RepoImportFolders")
|
b.Property<string>("RepoImportFolders")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("repo_import_folders");
|
.HasColumnName("repo_import_folders");
|
||||||
@@ -89,6 +95,7 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
DefaultMaxTurns = 100,
|
DefaultMaxTurns = 100,
|
||||||
DefaultModel = "sonnet",
|
DefaultModel = "sonnet",
|
||||||
DefaultPermissionMode = "auto",
|
DefaultPermissionMode = "auto",
|
||||||
|
MaxParallelExecutions = 1,
|
||||||
WorktreeAutoCleanupDays = 7,
|
WorktreeAutoCleanupDays = 7,
|
||||||
WorktreeAutoCleanupEnabled = false,
|
WorktreeAutoCleanupEnabled = false,
|
||||||
WorktreeStrategy = "sibling"
|
WorktreeStrategy = "sibling"
|
||||||
@@ -140,12 +147,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -159,16 +175,18 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("created_at");
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<int>("Days")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(31)
|
||||||
|
.HasColumnName("days_of_week");
|
||||||
|
|
||||||
b.Property<bool>("Enabled")
|
b.Property<bool>("Enabled")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasDefaultValue(true)
|
.HasDefaultValue(true)
|
||||||
.HasColumnName("enabled");
|
.HasColumnName("enabled");
|
||||||
|
|
||||||
b.Property<DateOnly>("EndDate")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("end_date");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("LastRunAt")
|
b.Property<DateTimeOffset?>("LastRunAt")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("last_run_at");
|
.HasColumnName("last_run_at");
|
||||||
@@ -177,20 +195,10 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("prompt_override");
|
.HasColumnName("prompt_override");
|
||||||
|
|
||||||
b.Property<DateOnly>("StartDate")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("start_date");
|
|
||||||
|
|
||||||
b.Property<TimeSpan>("TimeOfDay")
|
b.Property<TimeSpan>("TimeOfDay")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("time_of_day");
|
.HasColumnName("time_of_day");
|
||||||
|
|
||||||
b.Property<bool>("WorkdaysOnly")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasDefaultValue(true)
|
|
||||||
.HasColumnName("workdays_only");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("prime_schedules", (string)null);
|
b.ToTable("prime_schedules", (string)null);
|
||||||
@@ -327,6 +335,10 @@ namespace ClaudeDo.Data.Migrations
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("result");
|
.HasColumnName("result");
|
||||||
|
|
||||||
|
b.Property<string>("ReviewFeedback")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("review_feedback");
|
||||||
|
|
||||||
b.Property<DateTime?>("ScheduledFor")
|
b.Property<DateTime?>("ScheduledFor")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("scheduled_for");
|
.HasColumnName("scheduled_for");
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ public sealed class AppSettingsEntity
|
|||||||
public int DefaultMaxTurns { get; set; } = 100;
|
public int DefaultMaxTurns { get; set; } = 100;
|
||||||
public string DefaultPermissionMode { get; set; } = "auto";
|
public string DefaultPermissionMode { get; set; } = "auto";
|
||||||
|
|
||||||
|
public int MaxParallelExecutions { get; set; } = 1;
|
||||||
|
|
||||||
public string WorktreeStrategy { get; set; } = "sibling";
|
public string WorktreeStrategy { get; set; } = "sibling";
|
||||||
public string? CentralWorktreeRoot { get; set; }
|
public string? CentralWorktreeRoot { get; set; }
|
||||||
public bool WorktreeAutoCleanupEnabled { get; set; }
|
public bool WorktreeAutoCleanupEnabled { get; set; }
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
16
src/ClaudeDo.Data/Models/PrimeDays.cs
Normal file
16
src/ClaudeDo.Data/Models/PrimeDays.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace ClaudeDo.Data.Models;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum PrimeDays
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Monday = 1,
|
||||||
|
Tuesday = 2,
|
||||||
|
Wednesday = 4,
|
||||||
|
Thursday = 8,
|
||||||
|
Friday = 16,
|
||||||
|
Saturday = 32,
|
||||||
|
Sunday = 64,
|
||||||
|
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday, // 31
|
||||||
|
All = Weekdays | Saturday | Sunday, // 127
|
||||||
|
}
|
||||||
@@ -3,10 +3,8 @@ namespace ClaudeDo.Data.Models;
|
|||||||
public sealed class PrimeScheduleEntity
|
public sealed class PrimeScheduleEntity
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
public DateOnly StartDate { get; set; }
|
public PrimeDays Days { get; set; } = PrimeDays.Weekdays;
|
||||||
public DateOnly EndDate { get; set; }
|
|
||||||
public TimeSpan TimeOfDay { get; set; }
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
public bool WorkdaysOnly { get; set; } = true;
|
|
||||||
public bool Enabled { get; set; } = true;
|
public bool Enabled { get; set; } = true;
|
||||||
public DateTimeOffset? LastRunAt { get; set; }
|
public DateTimeOffset? LastRunAt { get; set; }
|
||||||
public string? PromptOverride { get; set; }
|
public string? PromptOverride { get; set; }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ public enum TaskStatus
|
|||||||
Idle,
|
Idle,
|
||||||
Queued,
|
Queued,
|
||||||
Running,
|
Running,
|
||||||
|
WaitingForReview,
|
||||||
Done,
|
Done,
|
||||||
Failed,
|
Failed,
|
||||||
Cancelled,
|
Cancelled,
|
||||||
@@ -28,6 +29,7 @@ public sealed class TaskEntity
|
|||||||
public string? BlockedByTaskId { get; set; }
|
public string? BlockedByTaskId { get; set; }
|
||||||
public DateTime? ScheduledFor { get; set; }
|
public DateTime? ScheduledFor { get; set; }
|
||||||
public string? Result { get; set; }
|
public string? Result { get; set; }
|
||||||
|
public string? ReviewFeedback { get; set; }
|
||||||
public string? LogPath { get; set; }
|
public string? LogPath { get; set; }
|
||||||
public required DateTime CreatedAt { get; init; }
|
public required DateTime CreatedAt { get; init; }
|
||||||
public DateTime? StartedAt { get; set; }
|
public DateTime? StartedAt { get; set; }
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public sealed class AppSettingsRepository
|
|||||||
row.DefaultMaxTurns = updated.DefaultMaxTurns;
|
row.DefaultMaxTurns = updated.DefaultMaxTurns;
|
||||||
row.DefaultPermissionMode = string.IsNullOrWhiteSpace(updated.DefaultPermissionMode)
|
row.DefaultPermissionMode = string.IsNullOrWhiteSpace(updated.DefaultPermissionMode)
|
||||||
? "auto" : updated.DefaultPermissionMode;
|
? "auto" : updated.DefaultPermissionMode;
|
||||||
|
row.MaxParallelExecutions = updated.MaxParallelExecutions < 1 ? 1 : updated.MaxParallelExecutions;
|
||||||
row.WorktreeStrategy = string.IsNullOrWhiteSpace(updated.WorktreeStrategy) ? "sibling" : updated.WorktreeStrategy;
|
row.WorktreeStrategy = string.IsNullOrWhiteSpace(updated.WorktreeStrategy) ? "sibling" : updated.WorktreeStrategy;
|
||||||
row.CentralWorktreeRoot = string.IsNullOrWhiteSpace(updated.CentralWorktreeRoot)
|
row.CentralWorktreeRoot = string.IsNullOrWhiteSpace(updated.CentralWorktreeRoot)
|
||||||
? null : updated.CentralWorktreeRoot;
|
? null : updated.CentralWorktreeRoot;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ public sealed class PrimeScheduleRepository
|
|||||||
|
|
||||||
public async Task<IReadOnlyList<PrimeScheduleEntity>> ListAsync(CancellationToken ct = default)
|
public async Task<IReadOnlyList<PrimeScheduleEntity>> ListAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var rows = await _context.PrimeSchedules.AsNoTracking()
|
var rows = await _context.PrimeSchedules.AsNoTracking().ToListAsync(ct);
|
||||||
.OrderBy(s => s.StartDate)
|
return rows.OrderBy(s => s.TimeOfDay).ToList();
|
||||||
.ToListAsync(ct);
|
|
||||||
return rows.OrderBy(s => s.StartDate).ThenBy(s => s.TimeOfDay).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PrimeScheduleEntity?> GetAsync(Guid id, CancellationToken ct = default) =>
|
public async Task<PrimeScheduleEntity?> GetAsync(Guid id, CancellationToken ct = default) =>
|
||||||
@@ -30,10 +28,8 @@ public sealed class PrimeScheduleRepository
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
existing.StartDate = entity.StartDate;
|
existing.Days = entity.Days;
|
||||||
existing.EndDate = entity.EndDate;
|
|
||||||
existing.TimeOfDay = entity.TimeOfDay;
|
existing.TimeOfDay = entity.TimeOfDay;
|
||||||
existing.WorkdaysOnly = entity.WorkdaysOnly;
|
|
||||||
existing.Enabled = entity.Enabled;
|
existing.Enabled = entity.Enabled;
|
||||||
existing.PromptOverride = entity.PromptOverride;
|
existing.PromptOverride = entity.PromptOverride;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public static class DefaultListsSeeder
|
|||||||
{
|
{
|
||||||
ctx.Lists.Add(new ListEntity
|
ctx.Lists.Add(new ListEntity
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid().ToString("N"),
|
Id = Guid.NewGuid().ToString(),
|
||||||
Name = name,
|
Name = name,
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -209,6 +209,8 @@ public partial class App : Application
|
|||||||
sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<DownloadAndExtractStep>());
|
sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<DownloadAndExtractStep>());
|
||||||
sc.AddSingleton<IInstallStep, WriteConfigStep>();
|
sc.AddSingleton<IInstallStep, WriteConfigStep>();
|
||||||
sc.AddSingleton<IInstallStep, InitDatabaseStep>();
|
sc.AddSingleton<IInstallStep, InitDatabaseStep>();
|
||||||
|
sc.AddSingleton<RegisterMcpStep>();
|
||||||
|
sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<RegisterMcpStep>());
|
||||||
sc.AddSingleton<RegisterAutostartStep>();
|
sc.AddSingleton<RegisterAutostartStep>();
|
||||||
sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<RegisterAutostartStep>());
|
sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<RegisterAutostartStep>());
|
||||||
sc.AddSingleton<IInstallStep, CreateShortcutsStep>();
|
sc.AddSingleton<IInstallStep, CreateShortcutsStep>();
|
||||||
|
|||||||
26
src/ClaudeDo.Installer/Core/AutostartShortcut.cs
Normal file
26
src/ClaudeDo.Installer/Core/AutostartShortcut.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
public static class AutostartShortcut
|
||||||
|
{
|
||||||
|
public const string FileName = "ClaudeDo Worker.lnk";
|
||||||
|
|
||||||
|
public static string DefaultStartupDir =>
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.Startup);
|
||||||
|
|
||||||
|
public static string PathIn(string startupDir) => Path.Combine(startupDir, FileName);
|
||||||
|
|
||||||
|
public static void Install(string startupDir, string workerExe)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(startupDir);
|
||||||
|
var workingDir = Path.GetDirectoryName(workerExe) ?? startupDir;
|
||||||
|
ShortcutFactory.CreateShortcut(PathIn(startupDir), workerExe, workingDir, "ClaudeDo background worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Remove(string startupDir)
|
||||||
|
{
|
||||||
|
var path = PathIn(startupDir);
|
||||||
|
if (File.Exists(path)) File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,4 +32,8 @@ public sealed class InstallContext
|
|||||||
|
|
||||||
// InstallPage
|
// InstallPage
|
||||||
public bool CreateDesktopShortcut { get; set; } = true;
|
public bool CreateDesktopShortcut { get; set; } = true;
|
||||||
|
|
||||||
|
// WelcomePage — register the external MCP endpoint with the Claude CLI.
|
||||||
|
public bool RegisterMcpWithClaude { get; set; } = true;
|
||||||
|
public int ExternalMcpPort { get; set; } = 47_822;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
using System.Security;
|
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Core;
|
|
||||||
|
|
||||||
public static class ScheduledTaskXml
|
|
||||||
{
|
|
||||||
public static string Build(string userId, string workerExePath, int restartIntervalMinutes)
|
|
||||||
{
|
|
||||||
var minutes = restartIntervalMinutes < 1 ? 1 : restartIntervalMinutes;
|
|
||||||
var user = SecurityElement.Escape(userId);
|
|
||||||
var cmd = SecurityElement.Escape(workerExePath);
|
|
||||||
return $"""
|
|
||||||
<?xml version="1.0" encoding="UTF-16"?>
|
|
||||||
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
||||||
<RegistrationInfo>
|
|
||||||
<Description>ClaudeDo background worker (per-user).</Description>
|
|
||||||
</RegistrationInfo>
|
|
||||||
<Triggers>
|
|
||||||
<LogonTrigger>
|
|
||||||
<Enabled>true</Enabled>
|
|
||||||
<UserId>{user}</UserId>
|
|
||||||
</LogonTrigger>
|
|
||||||
</Triggers>
|
|
||||||
<Principals>
|
|
||||||
<Principal id="Author">
|
|
||||||
<UserId>{user}</UserId>
|
|
||||||
<LogonType>InteractiveToken</LogonType>
|
|
||||||
<RunLevel>LeastPrivilege</RunLevel>
|
|
||||||
</Principal>
|
|
||||||
</Principals>
|
|
||||||
<Settings>
|
|
||||||
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
||||||
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
||||||
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
||||||
<AllowHardTerminate>true</AllowHardTerminate>
|
|
||||||
<StartWhenAvailable>true</StartWhenAvailable>
|
|
||||||
<Hidden>true</Hidden>
|
|
||||||
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
||||||
<RestartOnFailure>
|
|
||||||
<Interval>PT{minutes}M</Interval>
|
|
||||||
<Count>3</Count>
|
|
||||||
</RestartOnFailure>
|
|
||||||
</Settings>
|
|
||||||
<Actions Context="Author">
|
|
||||||
<Exec>
|
|
||||||
<Command>{cmd}</Command>
|
|
||||||
</Exec>
|
|
||||||
</Actions>
|
|
||||||
</Task>
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
49
src/ClaudeDo.Installer/Core/ShortcutFactory.cs
Normal file
49
src/ClaudeDo.Installer/Core/ShortcutFactory.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
public static class ShortcutFactory
|
||||||
|
{
|
||||||
|
public static void CreateShortcut(string shortcutPath, string targetPath, string workingDir, string description)
|
||||||
|
{
|
||||||
|
var link = (IShellLink)new ShellLink();
|
||||||
|
link.SetPath(targetPath);
|
||||||
|
link.SetWorkingDirectory(workingDir);
|
||||||
|
link.SetDescription(description);
|
||||||
|
link.SetIconLocation(targetPath, 0);
|
||||||
|
|
||||||
|
var file = (IPersistFile)link;
|
||||||
|
file.Save(shortcutPath, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||||
|
private class ShellLink { }
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||||
|
private interface IShellLink
|
||||||
|
{
|
||||||
|
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, int fFlags);
|
||||||
|
void GetIDList(out IntPtr ppidl);
|
||||||
|
void SetIDList(IntPtr pidl);
|
||||||
|
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||||
|
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||||
|
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||||
|
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||||
|
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
||||||
|
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||||
|
void GetHotkey(out short pwHotkey);
|
||||||
|
void SetHotkey(short wHotkey);
|
||||||
|
void GetShowCmd(out int piShowCmd);
|
||||||
|
void SetShowCmd(int iShowCmd);
|
||||||
|
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
||||||
|
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||||
|
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
||||||
|
void Resolve(IntPtr hwnd, int fFlags);
|
||||||
|
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,9 +34,10 @@ public sealed class UninstallRunner
|
|||||||
$"Cannot uninstall: worker did not stop cleanly. {stopResult.ErrorMessage} " +
|
$"Cannot uninstall: worker did not stop cleanly. {stopResult.ErrorMessage} " +
|
||||||
"Kill the worker manually and re-run uninstall.");
|
"Kill the worker manually and re-run uninstall.");
|
||||||
|
|
||||||
// 3) Unregister the autostart task, and best-effort remove any legacy service.
|
// 3) Best-effort removal of the legacy scheduled task and Windows service
|
||||||
progress.Report("Removing autostart task...");
|
// (older installs; current installs autostart via a Startup-folder shortcut).
|
||||||
await ProcessRunner.RunAsync("schtasks.exe", $"/Delete /TN \"{StopWorkerStep.TaskName}\" /F", null, progress, ct);
|
progress.Report("Removing legacy autostart task...");
|
||||||
|
await ProcessRunner.RunAsync("schtasks.exe", $"/Delete /TN \"{StopWorkerStep.LegacyTaskName}\" /F", null, progress, ct);
|
||||||
await ProcessRunner.RunAsync("sc.exe", "delete ClaudeDoWorker", null, progress, ct);
|
await ProcessRunner.RunAsync("sc.exe", "delete ClaudeDoWorker", null, progress, ct);
|
||||||
|
|
||||||
// 3b) Remove Apps & Features registry entry (best-effort).
|
// 3b) Remove Apps & Features registry entry (best-effort).
|
||||||
@@ -58,6 +59,7 @@ public sealed class UninstallRunner
|
|||||||
TryDeleteFile(Path.Combine(
|
TryDeleteFile(Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
|
||||||
"Programs", "ClaudeDo.lnk"));
|
"Programs", "ClaudeDo.lnk"));
|
||||||
|
TryDeleteFile(AutostartShortcut.PathIn(AutostartShortcut.DefaultStartupDir));
|
||||||
|
|
||||||
// 5) Delete install directory. Track success so we can report partial state.
|
// 5) Delete install directory. Track success so we can report partial state.
|
||||||
var failures = new List<string>();
|
var failures = new List<string>();
|
||||||
|
|||||||
@@ -84,6 +84,15 @@ public partial class InstallPageViewModel : ObservableObject, IInstallerPage
|
|||||||
{
|
{
|
||||||
if (IsInstalling) return;
|
if (IsInstalling) return;
|
||||||
|
|
||||||
|
// Reset per-step state so a re-run starts clean instead of appending
|
||||||
|
// output to the previous run's messages.
|
||||||
|
foreach (var s in Steps)
|
||||||
|
{
|
||||||
|
s.Messages.Clear();
|
||||||
|
s.Status = StepStatus.Pending;
|
||||||
|
s.IsExpanded = false;
|
||||||
|
}
|
||||||
|
|
||||||
IsInstalling = true;
|
IsInstalling = true;
|
||||||
IsComplete = false;
|
IsComplete = false;
|
||||||
HasErrors = false;
|
HasErrors = false;
|
||||||
@@ -96,6 +105,10 @@ public partial class InstallPageViewModel : ObservableObject, IInstallerPage
|
|||||||
var step = Steps.FirstOrDefault(s => s.Name == p.StepName);
|
var step = Steps.FirstOrDefault(s => s.Name == p.StepName);
|
||||||
if (step is null) return;
|
if (step is null) return;
|
||||||
|
|
||||||
|
// Status and output lines arrive on two separate Progress<T> channels, so a
|
||||||
|
// trailing "Running" line-message can be delivered after the step's terminal
|
||||||
|
// Done/Failed. Never let that downgrade a completed step back to Running.
|
||||||
|
if (!(step.Status is StepStatus.Done or StepStatus.Failed && p.Status is StepStatus.Running))
|
||||||
step.Status = p.Status;
|
step.Status = p.Status;
|
||||||
if (p.Message is not null)
|
if (p.Message is not null)
|
||||||
{
|
{
|
||||||
@@ -135,6 +148,7 @@ public partial class InstallPageViewModel : ObservableObject, IInstallerPage
|
|||||||
_serviceProvider.GetRequiredService<DownloadAndExtractStep>(),
|
_serviceProvider.GetRequiredService<DownloadAndExtractStep>(),
|
||||||
// Migrates the legacy service away and (re)registers the logon task.
|
// Migrates the legacy service away and (re)registers the logon task.
|
||||||
_serviceProvider.GetRequiredService<RegisterAutostartStep>(),
|
_serviceProvider.GetRequiredService<RegisterAutostartStep>(),
|
||||||
|
_serviceProvider.GetRequiredService<RegisterMcpStep>(),
|
||||||
_serviceProvider.GetRequiredService<StartWorkerStep>(),
|
_serviceProvider.GetRequiredService<StartWorkerStep>(),
|
||||||
_serviceProvider.GetRequiredService<WriteInstallManifestStep>(),
|
_serviceProvider.GetRequiredService<WriteInstallManifestStep>(),
|
||||||
// Refresh the bundled uninstaller exe + Add/Remove-Programs version so a
|
// Refresh the bundled uninstaller exe + Add/Remove-Programs version so a
|
||||||
|
|||||||
@@ -32,6 +32,14 @@
|
|||||||
<TextBlock Text="{Binding InstallError}" Foreground="{StaticResource ErrorBrush}" FontSize="11"
|
<TextBlock Text="{Binding InstallError}" Foreground="{StaticResource ErrorBrush}" FontSize="11"
|
||||||
Visibility="{Binding InstallError, Converter={StaticResource NullToCollapsedConverter}}"/>
|
Visibility="{Binding InstallError, Converter={StaticResource NullToCollapsedConverter}}"/>
|
||||||
|
|
||||||
|
<CheckBox Content="Register MCP server with Claude"
|
||||||
|
IsChecked="{Binding RegisterMcp}"
|
||||||
|
Margin="0,24,0,0"/>
|
||||||
|
<TextBlock Text="Runs 'claude mcp add' so Claude can view and manage your ClaudeDo tasks. You can change this later."
|
||||||
|
TextWrapping="Wrap" FontSize="11"
|
||||||
|
Foreground="{StaticResource TextSecondaryBrush}"
|
||||||
|
Margin="0,4,0,0"/>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public partial class WelcomePageViewModel : ObservableObject, IInstallerPage
|
|||||||
[ObservableProperty] private string _heading = "Install ClaudeDo";
|
[ObservableProperty] private string _heading = "Install ClaudeDo";
|
||||||
[ObservableProperty] private string _subheading = "Set the installation directory and continue.";
|
[ObservableProperty] private string _subheading = "Set the installation directory and continue.";
|
||||||
[ObservableProperty] private bool _installDirEditable = true;
|
[ObservableProperty] private bool _installDirEditable = true;
|
||||||
|
[ObservableProperty] private bool _registerMcp = true;
|
||||||
|
|
||||||
public WelcomePageViewModel(InstallContext context)
|
public WelcomePageViewModel(InstallContext context)
|
||||||
{
|
{
|
||||||
@@ -62,6 +63,7 @@ public partial class WelcomePageViewModel : ObservableObject, IInstallerPage
|
|||||||
public Task ApplyAsync()
|
public Task ApplyAsync()
|
||||||
{
|
{
|
||||||
_context.InstallDirectory = InstallDirectory;
|
_context.InstallDirectory = InstallDirectory;
|
||||||
|
_context.RegisterMcpWithClaude = RegisterMcp;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.InteropServices.ComTypes;
|
|
||||||
using System.Text;
|
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Steps;
|
namespace ClaudeDo.Installer.Steps;
|
||||||
@@ -23,7 +20,7 @@ public sealed class CreateShortcutsStep : IInstallStep
|
|||||||
"Programs");
|
"Programs");
|
||||||
Directory.CreateDirectory(startMenuDir);
|
Directory.CreateDirectory(startMenuDir);
|
||||||
var startMenuPath = Path.Combine(startMenuDir, "ClaudeDo.lnk");
|
var startMenuPath = Path.Combine(startMenuDir, "ClaudeDo.lnk");
|
||||||
CreateShortcut(startMenuPath, appExe, workingDir, "ClaudeDo Task Manager");
|
ShortcutFactory.CreateShortcut(startMenuPath, appExe, workingDir, "ClaudeDo Task Manager");
|
||||||
progress.Report($"Created Start Menu shortcut: {startMenuPath}");
|
progress.Report($"Created Start Menu shortcut: {startMenuPath}");
|
||||||
|
|
||||||
// Desktop shortcut (optional)
|
// Desktop shortcut (optional)
|
||||||
@@ -32,7 +29,7 @@ public sealed class CreateShortcutsStep : IInstallStep
|
|||||||
var desktopPath = Path.Combine(
|
var desktopPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory),
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory),
|
||||||
"ClaudeDo.lnk");
|
"ClaudeDo.lnk");
|
||||||
CreateShortcut(desktopPath, appExe, workingDir, "ClaudeDo Task Manager");
|
ShortcutFactory.CreateShortcut(desktopPath, appExe, workingDir, "ClaudeDo Task Manager");
|
||||||
progress.Report($"Created Desktop shortcut: {desktopPath}");
|
progress.Report($"Created Desktop shortcut: {desktopPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,48 +41,5 @@ public sealed class CreateShortcutsStep : IInstallStep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreateShortcut(string shortcutPath, string targetPath, string workingDir, string description)
|
|
||||||
{
|
|
||||||
var link = (IShellLink)new ShellLink();
|
|
||||||
link.SetPath(targetPath);
|
|
||||||
link.SetWorkingDirectory(workingDir);
|
|
||||||
link.SetDescription(description);
|
|
||||||
link.SetIconLocation(targetPath, 0);
|
|
||||||
|
|
||||||
var file = (IPersistFile)link;
|
|
||||||
file.Save(shortcutPath, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region COM Interop for IShellLink
|
|
||||||
|
|
||||||
[ComImport]
|
|
||||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
|
||||||
private class ShellLink { }
|
|
||||||
|
|
||||||
[ComImport]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
[Guid("000214F9-0000-0000-C000-000000000046")]
|
|
||||||
private interface IShellLink
|
|
||||||
{
|
|
||||||
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, int fFlags);
|
|
||||||
void GetIDList(out IntPtr ppidl);
|
|
||||||
void SetIDList(IntPtr pidl);
|
|
||||||
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
|
||||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
|
||||||
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
|
||||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
|
||||||
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
|
||||||
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
|
||||||
void GetHotkey(out short pwHotkey);
|
|
||||||
void SetHotkey(short wHotkey);
|
|
||||||
void GetShowCmd(out int piShowCmd);
|
|
||||||
void SetShowCmd(int iShowCmd);
|
|
||||||
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
|
||||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
|
||||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
|
||||||
void Resolve(IntPtr hwnd, int fFlags);
|
|
||||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Principal;
|
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Steps;
|
namespace ClaudeDo.Installer.Steps;
|
||||||
|
|
||||||
public sealed class RegisterAutostartStep : IInstallStep
|
public sealed class RegisterAutostartStep : IInstallStep
|
||||||
{
|
{
|
||||||
public const string TaskName = "ClaudeDoWorker";
|
public const string LegacyTaskName = "ClaudeDoWorker";
|
||||||
private const string LegacyServiceName = "ClaudeDoWorker";
|
private const string LegacyServiceName = "ClaudeDoWorker";
|
||||||
|
|
||||||
public string Name => "Register Autostart";
|
public string Name => "Register Autostart";
|
||||||
@@ -34,24 +33,19 @@ public sealed class RegisterAutostartStep : IInstallStep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Register (or replace) the per-user logon task.
|
// 2) Migrate away the legacy logon scheduled task if present (best-effort).
|
||||||
var userId = WindowsIdentity.GetCurrent().Name;
|
progress.Report("Removing legacy logon task...");
|
||||||
var minutes = Math.Max(1, ctx.RestartDelayMs / 60000);
|
await ProcessRunner.RunAsync("schtasks.exe", $"/Delete /TN \"{LegacyTaskName}\" /F", null, progress, ct);
|
||||||
var xml = ScheduledTaskXml.Build(userId, workerExe, minutes);
|
|
||||||
|
|
||||||
var xmlPath = Path.Combine(Path.GetTempPath(), $"ClaudeDoWorker-{Guid.NewGuid():N}.xml");
|
// 3) Register per-user autostart via a Startup-folder shortcut.
|
||||||
await File.WriteAllTextAsync(xmlPath, xml, new System.Text.UnicodeEncoding(false, true), ct);
|
progress.Report("Creating Startup shortcut...");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
progress.Report("Registering logon task...");
|
AutostartShortcut.Install(AutostartShortcut.DefaultStartupDir, workerExe);
|
||||||
var (exit, output) = await ProcessRunner.RunAsync(
|
|
||||||
"schtasks.exe", $"/Create /TN \"{TaskName}\" /XML \"{xmlPath}\" /F", null, progress, ct);
|
|
||||||
if (exit != 0)
|
|
||||||
return StepResult.Fail($"schtasks /Create failed (exit {exit}): {output}");
|
|
||||||
}
|
}
|
||||||
finally
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
try { File.Delete(xmlPath); } catch { /* best effort */ }
|
return StepResult.Fail($"Failed to create Startup shortcut: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return StepResult.Ok();
|
return StepResult.Ok();
|
||||||
|
|||||||
47
src/ClaudeDo.Installer/Steps/RegisterMcpStep.cs
Normal file
47
src/ClaudeDo.Installer/Steps/RegisterMcpStep.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Steps;
|
||||||
|
|
||||||
|
public sealed class RegisterMcpStep : IInstallStep
|
||||||
|
{
|
||||||
|
private const string ServerName = "claudedo";
|
||||||
|
|
||||||
|
public string Name => "Register MCP with Claude";
|
||||||
|
|
||||||
|
public async Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (!ctx.RegisterMcpWithClaude)
|
||||||
|
{
|
||||||
|
progress.Report("Skipped (not selected)");
|
||||||
|
return StepResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = $"http://127.0.0.1:{ctx.ExternalMcpPort}/mcp";
|
||||||
|
|
||||||
|
// Drop any prior registration first so a re-run (e.g. update, changed port)
|
||||||
|
// overwrites cleanly instead of erroring on a duplicate name.
|
||||||
|
progress.Report($"Removing existing '{ServerName}' MCP registration (if any)...");
|
||||||
|
await ProcessRunner.RunAsync(ctx.ClaudeBin, $"mcp remove --scope user {ServerName}", null, progress, ct);
|
||||||
|
|
||||||
|
progress.Report($"Registering '{ServerName}' MCP server at {url}...");
|
||||||
|
var (exit, output) = await ProcessRunner.RunAsync(
|
||||||
|
ctx.ClaudeBin,
|
||||||
|
$"mcp add --transport http --scope user {ServerName} {url}",
|
||||||
|
null, progress, ct);
|
||||||
|
|
||||||
|
// Non-fatal: a missing/old Claude CLI must never block the install. Surface the
|
||||||
|
// manual command so the user can register it themselves later.
|
||||||
|
if (exit != 0)
|
||||||
|
{
|
||||||
|
progress.Report(
|
||||||
|
$"Could not register MCP automatically (claude exited {exit}). " +
|
||||||
|
$"Run manually: claude mcp add --transport http --scope user {ServerName} {url}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
progress.Report("MCP server registered with Claude.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return StepResult.Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,28 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Steps;
|
namespace ClaudeDo.Installer.Steps;
|
||||||
|
|
||||||
public sealed class StartWorkerStep : IInstallStep
|
public sealed class StartWorkerStep : IInstallStep
|
||||||
{
|
{
|
||||||
public const string TaskName = "ClaudeDoWorker";
|
|
||||||
|
|
||||||
public string Name => "Start Worker";
|
public string Name => "Start Worker";
|
||||||
|
|
||||||
public async Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
public Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var workerExe = Path.Combine(ctx.InstallDirectory, "worker", "ClaudeDo.Worker.exe");
|
||||||
|
if (!File.Exists(workerExe))
|
||||||
|
return Task.FromResult(StepResult.Fail($"Worker executable not found: {workerExe}"));
|
||||||
|
|
||||||
progress.Report("Starting worker...");
|
progress.Report("Starting worker...");
|
||||||
var (exit, output) = await ProcessRunner.RunAsync("schtasks.exe", $"/Run /TN \"{TaskName}\"", null, progress, ct);
|
try
|
||||||
if (exit != 0)
|
{
|
||||||
return StepResult.Fail($"schtasks /Run failed (exit {exit}): {output}");
|
Process.Start(new ProcessStartInfo(workerExe) { UseShellExecute = true });
|
||||||
return StepResult.Ok();
|
return Task.FromResult(StepResult.Ok());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Task.FromResult(StepResult.Fail($"Failed to start worker: {ex.Message}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,22 @@ namespace ClaudeDo.Installer.Steps;
|
|||||||
|
|
||||||
public sealed class StopWorkerStep : IInstallStep
|
public sealed class StopWorkerStep : IInstallStep
|
||||||
{
|
{
|
||||||
public const string TaskName = "ClaudeDoWorker";
|
public const string LegacyTaskName = "ClaudeDoWorker";
|
||||||
public const string ProcessName = "ClaudeDo.Worker";
|
|
||||||
|
// Both must be stopped before the install dir is touched: a running app/worker
|
||||||
|
// exe locks its directory, so Directory.Move during extraction would otherwise
|
||||||
|
// fail with "Access to the path '...\app' is denied".
|
||||||
|
private static readonly string[] ProcessNames = { "ClaudeDo.Worker", "ClaudeDo.App" };
|
||||||
|
|
||||||
public string Name => "Stop Worker";
|
public string Name => "Stop Worker";
|
||||||
|
|
||||||
public async Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
public async Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
||||||
{
|
{
|
||||||
progress.Report("Stopping worker task (if running)...");
|
progress.Report("Stopping ClaudeDo processes (if running)...");
|
||||||
await ProcessRunner.RunAsync("schtasks.exe", $"/End /TN \"{TaskName}\"", null, progress, ct);
|
|
||||||
|
|
||||||
progress.Report("Stopping worker process (if running)...");
|
|
||||||
var installDir = ctx.InstallDirectory;
|
var installDir = ctx.InstallDirectory;
|
||||||
foreach (var p in Process.GetProcessesByName(ProcessName))
|
foreach (var name in ProcessNames)
|
||||||
|
{
|
||||||
|
foreach (var p in Process.GetProcessesByName(name))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -30,6 +33,7 @@ public sealed class StopWorkerStep : IInstallStep
|
|||||||
catch { /* process may have exited or be inaccessible */ }
|
catch { /* process may have exited or be inaccessible */ }
|
||||||
finally { p.Dispose(); }
|
finally { p.Dispose(); }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
return StepResult.Ok();
|
return StepResult.Ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public class StatusColorConverter : IValueConverter
|
|||||||
{
|
{
|
||||||
"queued" => Brushes.DodgerBlue,
|
"queued" => Brushes.DodgerBlue,
|
||||||
"running" => Brushes.Orange,
|
"running" => Brushes.Orange,
|
||||||
|
"waitingforreview" => Brushes.MediumPurple,
|
||||||
|
"waiting_for_review" => Brushes.MediumPurple,
|
||||||
"done" => Brushes.Green,
|
"done" => Brushes.Green,
|
||||||
"failed" => Brushes.Red,
|
"failed" => Brushes.Red,
|
||||||
"manual" => Brushes.Gray,
|
"manual" => Brushes.Gray,
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using Avalonia.Data.Converters;
|
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Converters;
|
|
||||||
|
|
||||||
public sealed class TimeSpanToHhmmConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
|
|
||||||
value is TimeSpan t ? $"{t.Hours:00}:{t.Minutes:00}" : "07:00";
|
|
||||||
|
|
||||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value is not string s) return new TimeSpan(7, 0, 0);
|
|
||||||
var parts = s.Split(':');
|
|
||||||
if (parts.Length == 2 &&
|
|
||||||
int.TryParse(parts[0], out var h) && h is >= 0 and <= 23 &&
|
|
||||||
int.TryParse(parts[1], out var m) && m is >= 0 and <= 59)
|
|
||||||
return new TimeSpan(h, m, 0);
|
|
||||||
return new TimeSpan(7, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<!-- Window control icons — filled geometries (PathIcon fills, not strokes) -->
|
<!-- Window control icons — filled geometries (PathIcon fills, not strokes) -->
|
||||||
<StreamGeometry x:Key="Icon.WinMin">M4 9 H16 V11 H4 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.WinMin">M4 9 H16 V11 H4 Z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icon.WinMax">M4 4 H16 V6 H4 Z M4 14 H16 V16 H4 Z M4 4 H6 V16 H4 Z M14 4 H16 V16 H14 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.WinMax">M4 4 H16 V6 H4 Z M4 14 H16 V16 H4 Z M4 4 H6 V16 H4 Z M14 4 H16 V16 H14 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="Icon.WinRestore">M4 7 H13 V9 H4 Z M4 14 H13 V16 H4 Z M4 7 H6 V16 H4 Z M11 7 H13 V16 H11 Z M7 4 H16 V6 H7 Z M14 4 H16 V13 H14 Z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icon.WinClose">M4 5 L5 4 L16 15 L15 16 Z M15 4 L16 5 L5 16 L4 15 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.WinClose">M4 5 L5 4 L16 15 L15 16 Z M15 4 L16 5 L5 16 L4 15 Z</StreamGeometry>
|
||||||
<!-- Brand check glyph — filled rounded square with inset tick -->
|
<!-- Brand check glyph — filled rounded square with inset tick -->
|
||||||
<StreamGeometry x:Key="Icon.BrandCheck">M3 3 H21 V21 H3 Z M6 12 L7 11 L10 14 L17 7 L18 8 L10 16 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.BrandCheck">M3 3 H21 V21 H3 Z M6 12 L7 11 L10 14 L17 7 L18 8 L10 16 Z</StreamGeometry>
|
||||||
@@ -85,9 +86,9 @@
|
|||||||
<StreamGeometry x:Key="Icon.Settings">M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65a.5.5 0 0 0 .12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1a7.03 7.03 0 0 0-1.69-.98l-.38-2.65a.5.5 0 0 0-.5-.42h-4a.5.5 0 0 0-.5.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1a.5.5 0 0 0-.61.22l-2 3.46a.5.5 0 0 0 .12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65a.5.5 0 0 0-.12.64l2 3.46a.5.5 0 0 0 .61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65a.5.5 0 0 0 .5.42h4a.5.5 0 0 0 .5-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1a.5.5 0 0 0 .61-.22l2-3.46a.5.5 0 0 0-.12-.64l-2.11-1.65z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.Settings">M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65a.5.5 0 0 0 .12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1a7.03 7.03 0 0 0-1.69-.98l-.38-2.65a.5.5 0 0 0-.5-.42h-4a.5.5 0 0 0-.5.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1a.5.5 0 0 0-.61.22l-2 3.46a.5.5 0 0 0 .12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65a.5.5 0 0 0-.12.64l2 3.46a.5.5 0 0 0 .61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65a.5.5 0 0 0 .5.42h4a.5.5 0 0 0 .5-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1a.5.5 0 0 0 .61-.22l2-3.46a.5.5 0 0 0-.12-.64l-2.11-1.65z</StreamGeometry>
|
||||||
|
|
||||||
<!-- Badge brushes -->
|
<!-- Badge brushes -->
|
||||||
<SolidColorBrush x:Key="DraftBadgeBrush" Color="#4A5568"/>
|
<SolidColorBrush x:Key="DraftBadgeBrush" Color="{StaticResource TextMuteColor}"/>
|
||||||
<SolidColorBrush x:Key="PlanningBadgeBrush" Color="#D69E2E"/>
|
<SolidColorBrush x:Key="PlanningBadgeBrush" Color="{StaticResource PeatColor}"/>
|
||||||
<SolidColorBrush x:Key="PlannedBadgeBrush" Color="#3182CE"/>
|
<SolidColorBrush x:Key="PlannedBadgeBrush" Color="{StaticResource SageColor}"/>
|
||||||
|
|
||||||
</Styles.Resources>
|
</Styles.Resources>
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@
|
|||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
<Style Selector="Border.island">
|
<Style Selector="Border.island">
|
||||||
<Setter Property="Background" Value="{StaticResource IslandBackgroundBrush}" />
|
<Setter Property="Background" Value="{StaticResource IslandBackgroundBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#0DFFFFFF" />
|
<Setter Property="BorderBrush" Value="{StaticResource HairlineOverlayBrush}" />
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="CornerRadius" Value="{StaticResource IslandCornerRadius}" />
|
<Setter Property="CornerRadius" Value="{StaticResource IslandCornerRadius}" />
|
||||||
<Setter Property="BoxShadow" Value="{StaticResource IslandShadow}" />
|
<Setter Property="BoxShadow" Value="{StaticResource IslandShadow}" />
|
||||||
@@ -146,30 +147,30 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.chip > TextBlock">
|
<Style Selector="Border.chip > TextBlock">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- Status variants — tint background 12% alpha of the status hue -->
|
<!-- Status variants — tint background 12% alpha of the status hue -->
|
||||||
<Style Selector="Border.chip.running">
|
<Style Selector="Border.chip.running">
|
||||||
<Setter Property="Background" Value="#1F7C9166" />
|
<Setter Property="Background" Value="{StaticResource RunningTintBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#4C7C9166" />
|
<Setter Property="BorderBrush" Value="{StaticResource RunningTintBorderBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.chip.running > TextBlock">
|
<Style Selector="Border.chip.running > TextBlock">
|
||||||
<Setter Property="Foreground" Value="{StaticResource StatusRunningBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource StatusRunningBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.chip.review">
|
<Style Selector="Border.chip.review">
|
||||||
<Setter Property="Background" Value="#1FD4A574" />
|
<Setter Property="Background" Value="{StaticResource ReviewTintBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#4CD4A574" />
|
<Setter Property="BorderBrush" Value="{StaticResource ReviewTintBorderBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.chip.review > TextBlock">
|
<Style Selector="Border.chip.review > TextBlock">
|
||||||
<Setter Property="Foreground" Value="{StaticResource StatusReviewBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource StatusReviewBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.chip.error">
|
<Style Selector="Border.chip.error">
|
||||||
<Setter Property="Background" Value="#1FC87060" />
|
<Setter Property="Background" Value="{StaticResource ErrorTintBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#4CC87060" />
|
<Setter Property="BorderBrush" Value="{StaticResource ErrorTintBorderBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.chip.error > TextBlock">
|
<Style Selector="Border.chip.error > TextBlock">
|
||||||
<Setter Property="Foreground" Value="{StaticResource StatusErrorBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource StatusErrorBrush}" />
|
||||||
@@ -177,13 +178,22 @@
|
|||||||
|
|
||||||
<!-- queued → Sage (#8B9D7A) -->
|
<!-- queued → Sage (#8B9D7A) -->
|
||||||
<Style Selector="Border.chip.queued">
|
<Style Selector="Border.chip.queued">
|
||||||
<Setter Property="Background" Value="#1F8B9D7A" />
|
<Setter Property="Background" Value="{StaticResource QueuedTintBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#4C8B9D7A" />
|
<Setter Property="BorderBrush" Value="{StaticResource QueuedTintBorderBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.chip.queued > TextBlock">
|
<Style Selector="Border.chip.queued > TextBlock">
|
||||||
<Setter Property="Foreground" Value="{StaticResource StatusQueuedBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource StatusQueuedBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- done → green (#6FA86B) -->
|
||||||
|
<Style Selector="Border.chip.done">
|
||||||
|
<Setter Property="Background" Value="{StaticResource DoneTintBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource DoneTintBorderBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.chip.done > TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource StatusDoneBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- idle → TextMute (#6B7973) -->
|
<!-- idle → TextMute (#6B7973) -->
|
||||||
<Style Selector="Border.chip.idle">
|
<Style Selector="Border.chip.idle">
|
||||||
<Setter Property="Background" Value="{StaticResource Surface2Brush}" />
|
<Setter Property="Background" Value="{StaticResource Surface2Brush}" />
|
||||||
@@ -203,7 +213,7 @@
|
|||||||
<Setter Property="CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
|
<Setter Property="CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
|
||||||
<Setter Property="Padding" Value="10,6" />
|
<Setter Property="Padding" Value="10,6" />
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="11" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
<Setter Property="Transitions">
|
<Setter Property="Transitions">
|
||||||
<Transitions>
|
<Transitions>
|
||||||
@@ -216,11 +226,6 @@
|
|||||||
<Setter Property="Background" Value="{StaticResource Surface3Brush}" />
|
<Setter Property="Background" Value="{StaticResource Surface3Brush}" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource LineBrightBrush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrightBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.btn.primary">
|
|
||||||
<Setter Property="Background" Value="{StaticResource AccentDimBrush}" />
|
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- Icon button: 24×24 square with hover surface -->
|
<!-- Icon button: 24×24 square with hover surface -->
|
||||||
<Style Selector="Button.icon-btn">
|
<Style Selector="Button.icon-btn">
|
||||||
@@ -248,7 +253,7 @@
|
|||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="CornerRadius" Value="{StaticResource InputCornerRadius}" />
|
<Setter Property="CornerRadius" Value="{StaticResource InputCornerRadius}" />
|
||||||
<Setter Property="Padding" Value="10,8" />
|
<Setter Property="Padding" Value="10,8" />
|
||||||
<Setter Property="FontSize" Value="13" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeBody}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
<Setter Property="CaretBrush" Value="{StaticResource AccentBrush}" />
|
<Setter Property="CaretBrush" Value="{StaticResource AccentBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
@@ -310,8 +315,9 @@
|
|||||||
<Setter Property="Cursor" Value="Hand" />
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
<Setter Property="Transitions">
|
<Setter Property="Transitions">
|
||||||
<Transitions>
|
<Transitions>
|
||||||
<BrushTransition Property="Background" Duration="0:0:0.10"/>
|
<BrushTransition Property="Background" Duration="0:0:0.12" />
|
||||||
<BrushTransition Property="BorderBrush" Duration="0:0:0.10"/>
|
<BrushTransition Property="BorderBrush" Duration="0:0:0.12" />
|
||||||
|
<ThicknessTransition Property="Margin" Duration="0:0:0.15" />
|
||||||
</Transitions>
|
</Transitions>
|
||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
@@ -358,22 +364,22 @@
|
|||||||
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.agent-strip.running">
|
<Style Selector="Border.agent-strip.running">
|
||||||
<Setter Property="Background" Value="#147C9166" />
|
<Setter Property="Background" Value="{StaticResource RunningTintBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#4C7C9166" />
|
<Setter Property="BorderBrush" Value="{StaticResource RunningTintBorderBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.agent-strip.review">
|
<Style Selector="Border.agent-strip.review">
|
||||||
<Setter Property="Background" Value="#14D4A574" />
|
<Setter Property="Background" Value="{StaticResource ReviewTintBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#4CD4A574" />
|
<Setter Property="BorderBrush" Value="{StaticResource ReviewTintBorderBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.agent-strip.error">
|
<Style Selector="Border.agent-strip.error">
|
||||||
<Setter Property="Background" Value="#14C87060" />
|
<Setter Property="Background" Value="{StaticResource ErrorTintBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#4CC87060" />
|
<Setter Property="BorderBrush" Value="{StaticResource ErrorTintBorderBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- queued → Sage tint -->
|
<!-- queued → Sage tint -->
|
||||||
<Style Selector="Border.agent-strip.queued">
|
<Style Selector="Border.agent-strip.queued">
|
||||||
<Setter Property="Background" Value="#148B9D7A" />
|
<Setter Property="Background" Value="{StaticResource QueuedTintBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="#4C8B9D7A" />
|
<Setter Property="BorderBrush" Value="{StaticResource QueuedTintBorderBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- idle → neutral (same as base, explicit for clarity) -->
|
<!-- idle → neutral (same as base, explicit for clarity) -->
|
||||||
@@ -386,7 +392,7 @@
|
|||||||
<!-- TERMINAL / LOG -->
|
<!-- TERMINAL / LOG -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
<Style Selector="Border.terminal">
|
<Style Selector="Border.terminal">
|
||||||
<Setter Property="Background" Value="#FF080C0B" />
|
<Setter Property="Background" Value="{StaticResource VoidBrush}" />
|
||||||
<Setter Property="CornerRadius" Value="8" />
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
<Setter Property="Padding" Value="12" />
|
<Setter Property="Padding" Value="12" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
||||||
@@ -394,7 +400,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.terminal TextBlock">
|
<Style Selector="Border.terminal TextBlock">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="11" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.terminal TextBlock[Tag=log-sys]">
|
<Style Selector="Border.terminal TextBlock[Tag=log-sys]">
|
||||||
@@ -449,7 +455,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.live-chip > StackPanel > TextBlock">
|
<Style Selector="Border.live-chip > StackPanel > TextBlock">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="9" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource AccentBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource AccentBrush}" />
|
||||||
<Setter Property="LetterSpacing" Value="1.2" />
|
<Setter Property="LetterSpacing" Value="1.2" />
|
||||||
</Style>
|
</Style>
|
||||||
@@ -471,7 +477,7 @@
|
|||||||
<!-- Terminal log-line timestamp column -->
|
<!-- Terminal log-line timestamp column -->
|
||||||
<Style Selector="TextBlock.log-ts">
|
<Style Selector="TextBlock.log-ts">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
||||||
<Setter Property="Width" Value="60" />
|
<Setter Property="Width" Value="60" />
|
||||||
<Setter Property="VerticalAlignment" Value="Top" />
|
<Setter Property="VerticalAlignment" Value="Top" />
|
||||||
@@ -480,7 +486,7 @@
|
|||||||
<!-- Kind marker column -->
|
<!-- Kind marker column -->
|
||||||
<Style Selector="TextBlock.log-kind">
|
<Style Selector="TextBlock.log-kind">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
||||||
<Setter Property="Width" Value="46" />
|
<Setter Property="Width" Value="46" />
|
||||||
<Setter Property="VerticalAlignment" Value="Top" />
|
<Setter Property="VerticalAlignment" Value="Top" />
|
||||||
@@ -554,7 +560,7 @@
|
|||||||
<!-- Count badge — larger, high contrast, brighter when the row is active -->
|
<!-- Count badge — larger, high contrast, brighter when the row is active -->
|
||||||
<Style Selector="TextBlock.list-count">
|
<Style Selector="TextBlock.list-count">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="12" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeBody}" />
|
||||||
<Setter Property="FontWeight" Value="Medium" />
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
@@ -565,17 +571,6 @@
|
|||||||
<Setter Property="FontWeight" Value="SemiBold" />
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ============================================================ -->
|
|
||||||
<!-- LIST SECTION HEADER -->
|
|
||||||
<!-- ============================================================ -->
|
|
||||||
<Style Selector="TextBlock.list-section-label">
|
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
||||||
<Setter Property="FontSize" Value="9" />
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
|
||||||
<Setter Property="Margin" Value="10,10,10,4" />
|
|
||||||
<Setter Property="LetterSpacing" Value="1.2" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
<!-- SEARCH BOX WRAPPER -->
|
<!-- SEARCH BOX WRAPPER -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
@@ -594,7 +589,7 @@
|
|||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
<Setter Property="Padding" Value="4,7" />
|
<Setter Property="Padding" Value="4,7" />
|
||||||
<Setter Property="FontSize" Value="12" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeBody}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
<Setter Property="CaretBrush" Value="{StaticResource AccentBrush}" />
|
<Setter Property="CaretBrush" Value="{StaticResource AccentBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
@@ -618,7 +613,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.kbd > TextBlock">
|
<Style Selector="Border.kbd > TextBlock">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
||||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
@@ -633,7 +628,7 @@
|
|||||||
<Setter Property="BorderThickness" Value="0" />
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
<Setter Property="Padding" Value="10,8" />
|
<Setter Property="Padding" Value="10,8" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
||||||
<Setter Property="FontSize" Value="12" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeBody}" />
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
<Setter Property="Cursor" Value="Hand" />
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
@@ -694,7 +689,7 @@
|
|||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
<Setter Property="Padding" Value="0" />
|
<Setter Property="Padding" Value="0" />
|
||||||
<Setter Property="FontSize" Value="14" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeTaskTitle}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
<Setter Property="CaretBrush" Value="{StaticResource AccentBrush}" />
|
<Setter Property="CaretBrush" Value="{StaticResource AccentBrush}" />
|
||||||
<Setter Property="MinHeight" Value="20" />
|
<Setter Property="MinHeight" Value="20" />
|
||||||
@@ -722,22 +717,6 @@
|
|||||||
<!-- TASK ROW — extensions (C2) -->
|
<!-- TASK ROW — extensions (C2) -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
|
|
||||||
<!-- Augment base task-row transitions to include Margin -->
|
|
||||||
<Style Selector="Border.task-row">
|
|
||||||
<Setter Property="Transitions">
|
|
||||||
<Transitions>
|
|
||||||
<BrushTransition Property="Background" Duration="0:0:0.12" />
|
|
||||||
<ThicknessTransition Property="Margin" Duration="0:0:0.15" />
|
|
||||||
</Transitions>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- Selected state: rely on the left accent bar from TaskRowView;
|
|
||||||
no heavy bg or perimeter border. -->
|
|
||||||
<Style Selector="Border.task-row.selected">
|
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource LineBrightBrush}" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- Left accent bar for selected row -->
|
<!-- Left accent bar for selected row -->
|
||||||
<Style Selector="Border.task-row-accent">
|
<Style Selector="Border.task-row-accent">
|
||||||
<Setter Property="Width" Value="2" />
|
<Setter Property="Width" Value="2" />
|
||||||
@@ -767,7 +746,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.task-row Border.chip > StackPanel > TextBlock">
|
<Style Selector="Border.task-row Border.chip > StackPanel > TextBlock">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
@@ -779,12 +758,12 @@
|
|||||||
<!-- Diff chip add/del coloring -->
|
<!-- Diff chip add/del coloring -->
|
||||||
<Style Selector="TextBlock.diff-add">
|
<Style Selector="TextBlock.diff-add">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource MossBrightBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource MossBrightBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TextBlock.diff-del">
|
<Style Selector="TextBlock.diff-del">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource BloodBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource BloodBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
@@ -807,7 +786,7 @@
|
|||||||
<!-- LIVE-TAIL PREVIEW ROW -->
|
<!-- LIVE-TAIL PREVIEW ROW -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
<Style Selector="Border.task-live-tail">
|
<Style Selector="Border.task-live-tail">
|
||||||
<Setter Property="Background" Value="#FF080C0B" />
|
<Setter Property="Background" Value="{StaticResource VoidBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="CornerRadius" Value="5" />
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
@@ -816,7 +795,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.task-live-tail TextBlock">
|
<Style Selector="Border.task-live-tail TextBlock">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="11" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
@@ -857,14 +836,23 @@
|
|||||||
<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) -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
<Style Selector="TextBlock.section-label">
|
<Style Selector="TextBlock.section-label">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="10" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
||||||
<Setter Property="LetterSpacing" Value="1.4" />
|
<Setter Property="LetterSpacing" Value="1.4" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TextBlock.section-label.overdue">
|
<Style Selector="TextBlock.section-label.overdue">
|
||||||
@@ -881,9 +869,9 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.badge > TextBlock">
|
<Style Selector="Border.badge > TextBlock">
|
||||||
<Setter Property="FontSize" Value="9"/>
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}"/>
|
||||||
<Setter Property="FontWeight" Value="Bold"/>
|
<Setter Property="FontWeight" Value="Bold"/>
|
||||||
<Setter Property="Foreground" Value="White"/>
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.badge.draft">
|
<Style Selector="Border.badge.draft">
|
||||||
@@ -898,4 +886,172 @@
|
|||||||
<Setter Property="Background" Value="{DynamicResource PlannedBadgeBrush}"/>
|
<Setter Property="Background" Value="{DynamicResource PlannedBadgeBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- SHARED MODAL STYLES (promoted from per-modal Window.Styles) -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Style Selector="TextBlock.field-label">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
|
<Setter Property="Margin" Value="0,0,0,4" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.path-mono">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
|
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Self-contained action buttons (same geometry as Button.btn, distinct color).
|
||||||
|
Used standalone, e.g. Classes="primary" / "danger". -->
|
||||||
|
<Style Selector="Button.primary">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentDimBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
|
||||||
|
<Setter Property="Padding" Value="10,6" />
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.danger">
|
||||||
|
<Setter Property="Background" Value="{StaticResource BloodBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource BloodBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
|
||||||
|
<Setter Property="Padding" Value="10,6" />
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- CANONICAL TYPOGRAPHY -->
|
||||||
|
<!-- One class per text role. Small text = 11 (eyebrow/meta/ -->
|
||||||
|
<!-- field-label/path-mono). Body = 13. Heading = 18. Display = 24.-->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
|
||||||
|
<!-- Small secondary mono text: timestamps, ids, hints, status, diffstat, age -->
|
||||||
|
<Style Selector="TextBlock.meta">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Default body / list values / descriptions -->
|
||||||
|
<Style Selector="TextBlock.body">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource SansFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeBody}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Item / task / detail titles -->
|
||||||
|
<Style Selector="TextBlock.title">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource SansFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeBody}" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Panel / section headings ("Lists", modal section titles) -->
|
||||||
|
<Style Selector="TextBlock.heading">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource SansFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeH3}" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Main board / island display title -->
|
||||||
|
<Style Selector="TextBlock.display">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource SansFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeH2}" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- SHARED CONTAINERS (promoted from per-view inline/local styles)-->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
|
||||||
|
<!-- Bordered card / settings section -->
|
||||||
|
<Style Selector="Border.section">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
|
||||||
|
<Setter Property="Padding" Value="14" />
|
||||||
|
<Setter Property="Background" Value="{StaticResource DeepBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Stacked content section with a bottom hairline (Details island) -->
|
||||||
|
<Style Selector="Border.section-divider">
|
||||||
|
<Setter Property="Padding" Value="18,12" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,0,1" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Inline danger confirm box -->
|
||||||
|
<Style Selector="Border.danger-box">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource BloodBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
|
||||||
|
<Setter Property="Padding" Value="12,10" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Left sidebar pane with vertical hairline (diff/planning views) -->
|
||||||
|
<Style Selector="Border.sidebar-pane">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,1,0" />
|
||||||
|
<Setter Property="Background" Value="{StaticResource DeepBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Diff line-number gutter column -->
|
||||||
|
<Style Selector="TextBlock.diff-lineno">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Terminal selectable log text (SelectableTextBlock doesn't inherit the TextBlock terminal style) -->
|
||||||
|
<Style Selector="Border.terminal SelectableTextBlock">
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Accent call-to-action button (Send to queue / Continue / Schedule) -->
|
||||||
|
<Style Selector="Button.accent">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentDimBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
|
||||||
|
<Setter Property="Padding" Value="10,6" />
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- DAY TOGGLE -->
|
||||||
|
<!-- Small ToggleButton for weekday pickers (Prime schedule row) -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Style Selector="ToggleButton.day-toggle">
|
||||||
|
<Setter Property="MinWidth" Value="34"/>
|
||||||
|
<Setter Property="Padding" Value="6,4"/>
|
||||||
|
<Setter Property="Margin" Value="0"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Background" Value="{DynamicResource DeepBrush}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource LineBrush}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="CornerRadius" Value="4"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ToggleButton.day-toggle:checked /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|||||||
@@ -83,6 +83,22 @@
|
|||||||
<SolidColorBrush x:Key="StatusErrorBrush" Color="{StaticResource StatusErrorColor}" />
|
<SolidColorBrush x:Key="StatusErrorBrush" Color="{StaticResource StatusErrorColor}" />
|
||||||
<SolidColorBrush x:Key="StatusQueuedBrush" Color="{StaticResource StatusQueuedColor}" />
|
<SolidColorBrush x:Key="StatusQueuedBrush" Color="{StaticResource StatusQueuedColor}" />
|
||||||
<SolidColorBrush x:Key="StatusIdleBrush" Color="{StaticResource StatusIdleColor}" />
|
<SolidColorBrush x:Key="StatusIdleBrush" Color="{StaticResource StatusIdleColor}" />
|
||||||
|
<SolidColorBrush x:Key="StatusDoneBrush" Color="#6FA86B" />
|
||||||
|
|
||||||
|
<!-- Subtle white overlay (island hairline border) -->
|
||||||
|
<SolidColorBrush x:Key="HairlineOverlayBrush" Color="#0DFFFFFF" />
|
||||||
|
|
||||||
|
<!-- Status tints (12% fill / 30% border of the status hue) — reused by chips & agent strips -->
|
||||||
|
<SolidColorBrush x:Key="RunningTintBrush" Color="#1F7C9166" />
|
||||||
|
<SolidColorBrush x:Key="RunningTintBorderBrush" Color="#4C7C9166" />
|
||||||
|
<SolidColorBrush x:Key="ReviewTintBrush" Color="#1FD4A574" />
|
||||||
|
<SolidColorBrush x:Key="ReviewTintBorderBrush" Color="#4CD4A574" />
|
||||||
|
<SolidColorBrush x:Key="ErrorTintBrush" Color="#1FC87060" />
|
||||||
|
<SolidColorBrush x:Key="ErrorTintBorderBrush" Color="#4CC87060" />
|
||||||
|
<SolidColorBrush x:Key="QueuedTintBrush" Color="#1F8B9D7A" />
|
||||||
|
<SolidColorBrush x:Key="QueuedTintBorderBrush" Color="#4C8B9D7A" />
|
||||||
|
<SolidColorBrush x:Key="DoneTintBrush" Color="#1F6FA86B" />
|
||||||
|
<SolidColorBrush x:Key="DoneTintBorderBrush" Color="#4C6FA86B" />
|
||||||
|
|
||||||
<!-- Window-body gradient layers (apply as LinearGradientBrush in the main content Border) -->
|
<!-- Window-body gradient layers (apply as LinearGradientBrush in the main content Border) -->
|
||||||
<LinearGradientBrush x:Key="DesktopBackgroundBrush" StartPoint="0%,0%" EndPoint="0%,100%">
|
<LinearGradientBrush x:Key="DesktopBackgroundBrush" StartPoint="0%,0%" EndPoint="0%,100%">
|
||||||
@@ -149,11 +165,11 @@
|
|||||||
<FontFamily x:Key="MonoFamily">avares://ClaudeDo.Ui/Assets/Fonts/#JetBrains Mono, IBM Plex Mono, Cascadia Mono, Consolas, monospace</FontFamily>
|
<FontFamily x:Key="MonoFamily">avares://ClaudeDo.Ui/Assets/Fonts/#JetBrains Mono, IBM Plex Mono, Cascadia Mono, Consolas, monospace</FontFamily>
|
||||||
|
|
||||||
<!-- Type scale -->
|
<!-- Type scale -->
|
||||||
<x:Double x:Key="FontSizeEyebrow">10</x:Double> <!-- uppercase label, 0.14em tracking -->
|
<x:Double x:Key="FontSizeEyebrow">11</x:Double> <!-- uppercase label, 0.14em tracking -->
|
||||||
<x:Double x:Key="FontSizeMono">11</x:Double> <!-- chips, log lines, filepaths -->
|
<x:Double x:Key="FontSizeMono">11</x:Double> <!-- chips, log lines, filepaths -->
|
||||||
<x:Double x:Key="FontSizeMicro">11</x:Double> <!-- meta rows -->
|
<x:Double x:Key="FontSizeMicro">11</x:Double> <!-- meta rows -->
|
||||||
<x:Double x:Key="FontSizeBody">13</x:Double>
|
<x:Double x:Key="FontSizeBody">13</x:Double>
|
||||||
<x:Double x:Key="FontSizeTaskTitle">14</x:Double>
|
<x:Double x:Key="FontSizeTaskTitle">13</x:Double>
|
||||||
<x:Double x:Key="FontSizeH3">18</x:Double>
|
<x:Double x:Key="FontSizeH3">18</x:Double>
|
||||||
<x:Double x:Key="FontSizeH2">24</x:Double> <!-- island titles ("My Day") -->
|
<x:Double x:Key="FontSizeH2">24</x:Double> <!-- island titles ("My Day") -->
|
||||||
<x:Double x:Key="FontSizeH1">32</x:Double>
|
<x:Double x:Key="FontSizeH1">32</x:Double>
|
||||||
@@ -162,7 +178,7 @@
|
|||||||
<Style x:Key="EyebrowText" Selector="TextBlock.eyebrow">
|
<Style x:Key="EyebrowText" Selector="TextBlock.eyebrow">
|
||||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||||
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
||||||
<Setter Property="LetterSpacing" Value="1.4" />
|
<Setter Property="LetterSpacing" Value="1.4" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ public interface IWorkerClient : INotifyPropertyChanged
|
|||||||
Task<ListConfigDto?> GetListConfigAsync(string listId);
|
Task<ListConfigDto?> GetListConfigAsync(string listId);
|
||||||
Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto);
|
Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto);
|
||||||
Task SetTaskStatusAsync(string taskId, TaskStatus status);
|
Task SetTaskStatusAsync(string taskId, TaskStatus status);
|
||||||
|
Task ApproveReviewAsync(string taskId);
|
||||||
|
Task RejectReviewToQueueAsync(string taskId, string feedback);
|
||||||
|
Task RejectReviewToIdleAsync(string taskId);
|
||||||
|
Task CancelReviewAsync(string taskId);
|
||||||
Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default);
|
Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default);
|
||||||
Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default);
|
Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default);
|
||||||
Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default);
|
Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default);
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ namespace ClaudeDo.Ui.Services;
|
|||||||
|
|
||||||
public sealed record PrimeScheduleDto(
|
public sealed record PrimeScheduleDto(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
DateOnly StartDate,
|
int Days,
|
||||||
DateOnly EndDate,
|
|
||||||
TimeSpan TimeOfDay,
|
TimeSpan TimeOfDay,
|
||||||
bool WorkdaysOnly,
|
|
||||||
bool Enabled,
|
bool Enabled,
|
||||||
DateTimeOffset? LastRunAt,
|
DateTimeOffset? LastRunAt,
|
||||||
string? PromptOverride);
|
string? PromptOverride);
|
||||||
|
|||||||
@@ -349,6 +349,26 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
await _hub.InvokeAsync("SetTaskStatus", taskId, status.ToString());
|
await _hub.InvokeAsync("SetTaskStatus", taskId, status.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ApproveReviewAsync(string taskId)
|
||||||
|
{
|
||||||
|
await _hub.InvokeAsync("ApproveReview", taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RejectReviewToQueueAsync(string taskId, string feedback)
|
||||||
|
{
|
||||||
|
await _hub.InvokeAsync("RejectReviewToQueue", taskId, feedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RejectReviewToIdleAsync(string taskId)
|
||||||
|
{
|
||||||
|
await _hub.InvokeAsync("RejectReviewToIdle", taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CancelReviewAsync(string taskId)
|
||||||
|
{
|
||||||
|
await _hub.InvokeAsync("CancelReview", taskId);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync(string? listId = null)
|
public Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync(string? listId = null)
|
||||||
=> TryInvokeAsync<WorktreeCleanupDto>("CleanupFinishedWorktrees", listId);
|
=> TryInvokeAsync<WorktreeCleanupDto>("CleanupFinishedWorktrees", listId);
|
||||||
|
|
||||||
@@ -450,6 +470,7 @@ public sealed record AppSettingsDto(
|
|||||||
string DefaultModel,
|
string DefaultModel,
|
||||||
int DefaultMaxTurns,
|
int DefaultMaxTurns,
|
||||||
string DefaultPermissionMode,
|
string DefaultPermissionMode,
|
||||||
|
int MaxParallelExecutions,
|
||||||
string WorktreeStrategy,
|
string WorktreeStrategy,
|
||||||
string? CentralWorktreeRoot,
|
string? CentralWorktreeRoot,
|
||||||
bool WorktreeAutoCleanupEnabled,
|
bool WorktreeAutoCleanupEnabled,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
|||||||
[ObservableProperty] private PlanningPhase _planningPhase;
|
[ObservableProperty] private PlanningPhase _planningPhase;
|
||||||
[ObservableProperty] private string? _branch;
|
[ObservableProperty] private string? _branch;
|
||||||
[ObservableProperty] private string? _diffStat;
|
[ObservableProperty] private string? _diffStat;
|
||||||
[ObservableProperty] private string? _liveTail;
|
|
||||||
[ObservableProperty] private DateTime? _scheduledFor;
|
[ObservableProperty] private DateTime? _scheduledFor;
|
||||||
[ObservableProperty] private int _diffAdditions;
|
[ObservableProperty] private int _diffAdditions;
|
||||||
[ObservableProperty] private int _diffDeletions;
|
[ObservableProperty] private int _diffDeletions;
|
||||||
@@ -64,27 +63,30 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
|||||||
public bool HasSteps => StepsCount > 0;
|
public bool HasSteps => StepsCount > 0;
|
||||||
public bool IsOverdue => ScheduledFor is { } d && d.Date < DateTime.Today && !Done;
|
public bool IsOverdue => ScheduledFor is { } d && d.Date < DateTime.Today && !Done;
|
||||||
public bool IsRunning => Status == TaskStatus.Running;
|
public bool IsRunning => Status == TaskStatus.Running;
|
||||||
|
public bool IsWaitingForReview => Status == TaskStatus.WaitingForReview;
|
||||||
public bool IsQueued => Status == TaskStatus.Queued && string.IsNullOrEmpty(BlockedByTaskId);
|
public bool IsQueued => Status == TaskStatus.Queued && string.IsNullOrEmpty(BlockedByTaskId);
|
||||||
public bool IsWaiting => Status == TaskStatus.Queued && !string.IsNullOrEmpty(BlockedByTaskId);
|
public bool IsWaiting => Status == TaskStatus.Queued && !string.IsNullOrEmpty(BlockedByTaskId);
|
||||||
public bool CanRemoveFromQueue => IsQueued || HasQueuedSubtasks;
|
public bool CanRemoveFromQueue => IsQueued || HasQueuedSubtasks;
|
||||||
public bool CanSendToQueue => !IsRunning && !IsQueued && !HasQueuedSubtasks
|
public bool CanSendToQueue => !IsRunning && !IsQueued && !IsWaitingForReview && !HasQueuedSubtasks
|
||||||
&& (!IsChild || ParentFinalized);
|
&& (!IsChild || ParentFinalized);
|
||||||
// Parent-level "send plan to queue" — only once the plan is finalized (children Planned).
|
// Parent-level "send plan to queue" — only once the plan is finalized (children Planned).
|
||||||
public bool CanQueuePlan => !IsChild && HasPlanningChildren
|
public bool CanQueuePlan => !IsChild && HasPlanningChildren
|
||||||
&& PlanningPhase == PlanningPhase.Finalized
|
&& PlanningPhase == PlanningPhase.Finalized
|
||||||
&& !HasQueuedSubtasks;
|
&& !HasQueuedSubtasks;
|
||||||
public bool HasSchedule => ScheduledFor.HasValue;
|
public bool HasSchedule => ScheduledFor.HasValue;
|
||||||
public bool HasLiveTail => IsRunning && !string.IsNullOrEmpty(LiveTail);
|
|
||||||
|
|
||||||
public string DiffAdditionsText => $"+{DiffAdditions}";
|
public string DiffAdditionsText => $"+{DiffAdditions}";
|
||||||
public string DiffDeletionsText => $"−{DiffDeletions}";
|
public string DiffDeletionsText => $"−{DiffDeletions}";
|
||||||
public string StepsText => $"{StepsCompleted}/{StepsCount} steps";
|
public string StepsText => $"{StepsCompleted}/{StepsCount} steps";
|
||||||
|
|
||||||
|
public string StatusLabel => Status == TaskStatus.WaitingForReview ? "Waiting for Review" : Status.ToString();
|
||||||
|
|
||||||
public string StatusChipClass => (Status, IsBlocked: !string.IsNullOrEmpty(BlockedByTaskId)) switch
|
public string StatusChipClass => (Status, IsBlocked: !string.IsNullOrEmpty(BlockedByTaskId)) switch
|
||||||
{
|
{
|
||||||
(TaskStatus.Running, _) => "running",
|
(TaskStatus.Running, _) => "running",
|
||||||
|
(TaskStatus.WaitingForReview, _) => "review",
|
||||||
(TaskStatus.Failed, _) => "error",
|
(TaskStatus.Failed, _) => "error",
|
||||||
(TaskStatus.Done, _) => "review",
|
(TaskStatus.Done, _) => "done",
|
||||||
(TaskStatus.Queued, true) => "waiting",
|
(TaskStatus.Queued, true) => "waiting",
|
||||||
(TaskStatus.Queued, false) => "queued",
|
(TaskStatus.Queued, false) => "queued",
|
||||||
_ => "idle",
|
_ => "idle",
|
||||||
@@ -93,10 +95,11 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
|||||||
partial void OnStatusChanged(TaskStatus value)
|
partial void OnStatusChanged(TaskStatus value)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(StatusChipClass));
|
OnPropertyChanged(nameof(StatusChipClass));
|
||||||
|
OnPropertyChanged(nameof(StatusLabel));
|
||||||
OnPropertyChanged(nameof(IsRunning));
|
OnPropertyChanged(nameof(IsRunning));
|
||||||
|
OnPropertyChanged(nameof(IsWaitingForReview));
|
||||||
OnPropertyChanged(nameof(IsQueued));
|
OnPropertyChanged(nameof(IsQueued));
|
||||||
OnPropertyChanged(nameof(IsWaiting));
|
OnPropertyChanged(nameof(IsWaiting));
|
||||||
OnPropertyChanged(nameof(HasLiveTail));
|
|
||||||
OnPropertyChanged(nameof(IsDraft));
|
OnPropertyChanged(nameof(IsDraft));
|
||||||
OnPropertyChanged(nameof(IsPlanned));
|
OnPropertyChanged(nameof(IsPlanned));
|
||||||
OnPropertyChanged(nameof(CanOpenPlanningSession));
|
OnPropertyChanged(nameof(CanOpenPlanningSession));
|
||||||
@@ -152,7 +155,6 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
partial void OnBranchChanged(string? value) => OnPropertyChanged(nameof(HasBranch));
|
partial void OnBranchChanged(string? value) => OnPropertyChanged(nameof(HasBranch));
|
||||||
partial void OnLiveTailChanged(string? value) => OnPropertyChanged(nameof(HasLiveTail));
|
|
||||||
partial void OnDoneChanged(bool value) => OnPropertyChanged(nameof(IsOverdue));
|
partial void OnDoneChanged(bool value) => OnPropertyChanged(nameof(IsOverdue));
|
||||||
partial void OnScheduledForChanged(DateTime? value)
|
partial void OnScheduledForChanged(DateTime? value)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,18 +56,11 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
_worker.TaskUpdatedEvent += OnWorkerTaskUpdated;
|
_worker.TaskUpdatedEvent += OnWorkerTaskUpdated;
|
||||||
_worker.WorktreeUpdatedEvent += OnWorkerTaskUpdated;
|
_worker.WorktreeUpdatedEvent += OnWorkerTaskUpdated;
|
||||||
_worker.TaskMessageEvent += OnWorkerTaskMessage;
|
|
||||||
_worker.ListUpdatedEvent += OnWorkerListUpdated;
|
_worker.ListUpdatedEvent += OnWorkerListUpdated;
|
||||||
_worker.ConnectionRestoredEvent += () => LoadForList(_currentList);
|
_worker.ConnectionRestoredEvent += () => LoadForList(_currentList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnWorkerTaskMessage(string taskId, string line)
|
|
||||||
{
|
|
||||||
var row = Items.FirstOrDefault(r => r.Id == taskId);
|
|
||||||
if (row is not null) row.LiveTail = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnWorkerListUpdated(string listId)
|
private async void OnWorkerListUpdated(string listId)
|
||||||
{
|
{
|
||||||
// Mirror the renamed list onto every task row that references it,
|
// Mirror the renamed list onto every task row that references it,
|
||||||
@@ -487,6 +480,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)
|
||||||
{
|
{
|
||||||
@@ -577,6 +602,42 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
|||||||
catch { /* worker offline; the broadcast will reconcile when it returns */ }
|
catch { /* worker offline; the broadcast will reconcile when it returns */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Review actions (visible when a task is WaitingForReview) ─────────────
|
||||||
|
// Each delegates to the worker hub, which performs the transition and
|
||||||
|
// broadcasts TaskUpdated; the row refreshes from that broadcast.
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ApproveReviewAsync(TaskRowViewModel? row)
|
||||||
|
{
|
||||||
|
if (row is null || !row.IsWaitingForReview || _worker is null) return;
|
||||||
|
try { await _worker.ApproveReviewAsync(row.Id); }
|
||||||
|
catch { /* offline; broadcast reconciles on return */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RejectReviewToQueueAsync(TaskRowViewModel row, string feedback)
|
||||||
|
{
|
||||||
|
if (!row.IsWaitingForReview || _worker is null) return;
|
||||||
|
if (string.IsNullOrWhiteSpace(feedback)) return;
|
||||||
|
try { await _worker.RejectReviewToQueueAsync(row.Id, feedback); }
|
||||||
|
catch { /* offline; broadcast reconciles on return */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task RejectReviewToIdleAsync(TaskRowViewModel? row)
|
||||||
|
{
|
||||||
|
if (row is null || !row.IsWaitingForReview || _worker is null) return;
|
||||||
|
try { await _worker.RejectReviewToIdleAsync(row.Id); }
|
||||||
|
catch { /* offline; broadcast reconciles on return */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task CancelReviewAsync(TaskRowViewModel? row)
|
||||||
|
{
|
||||||
|
if (row is null || !row.IsWaitingForReview || _worker is null) return;
|
||||||
|
try { await _worker.CancelReviewAsync(row.Id); }
|
||||||
|
catch { /* offline; broadcast reconciles on return */ }
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SetScheduledForAsync(TaskRowViewModel row, DateTime? when)
|
public async Task SetScheduledForAsync(TaskRowViewModel row, DateTime? when)
|
||||||
{
|
{
|
||||||
if (row is null) return;
|
if (row is null) return;
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ public sealed partial class IslandsShellViewModel : ViewModelBase
|
|||||||
// Set by MainWindow to open the global worktrees overview dialog.
|
// Set by MainWindow to open the global worktrees overview dialog.
|
||||||
public Func<WorktreesOverviewModalViewModel, Task>? ShowWorktreesOverviewModal { get; set; }
|
public Func<WorktreesOverviewModalViewModel, Task>? ShowWorktreesOverviewModal { get; set; }
|
||||||
|
|
||||||
|
// Set by MainWindow to open the worker-connection help dialog.
|
||||||
|
public Func<WorkerConnectionModalViewModel, Task>? ShowWorkerConnectionModal { get; set; }
|
||||||
|
|
||||||
[ObservableProperty] private bool _isUpdateBannerVisible;
|
[ObservableProperty] private bool _isUpdateBannerVisible;
|
||||||
[ObservableProperty] private string? _updateBannerLatestVersion;
|
[ObservableProperty] private string? _updateBannerLatestVersion;
|
||||||
[ObservableProperty] private string? _inlineUpdateStatus;
|
[ObservableProperty] private string? _inlineUpdateStatus;
|
||||||
@@ -72,6 +75,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase
|
|||||||
public bool ShowLists => WindowWidth >= 780;
|
public bool ShowLists => WindowWidth >= 780;
|
||||||
|
|
||||||
private readonly System.Timers.Timer _clearTimer = new(30_000) { AutoReset = false };
|
private readonly System.Timers.Timer _clearTimer = new(30_000) { AutoReset = false };
|
||||||
|
private readonly System.Timers.Timer _connectTimer = new(12_000) { AutoReset = false };
|
||||||
|
|
||||||
[ObservableProperty] private string? _primeStatus;
|
[ObservableProperty] private string? _primeStatus;
|
||||||
private readonly System.Timers.Timer _primeStatusTimer = new(5_000) { AutoReset = false };
|
private readonly System.Timers.Timer _primeStatusTimer = new(5_000) { AutoReset = false };
|
||||||
@@ -220,8 +224,12 @@ public sealed partial class IslandsShellViewModel : ViewModelBase
|
|||||||
};
|
};
|
||||||
_primeStatusTimer.Elapsed += (_, _) =>
|
_primeStatusTimer.Elapsed += (_, _) =>
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() => PrimeStatus = null);
|
Avalonia.Threading.Dispatcher.UIThread.Post(() => PrimeStatus = null);
|
||||||
|
_connectTimer.Elapsed += (_, _) => Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
if (DecideShowConnectionPrompt(IsOffline)) _ = OpenWorkerConnectionHelpAsync();
|
||||||
|
});
|
||||||
|
_connectTimer.Start();
|
||||||
_ = Lists.LoadAsync();
|
_ = Lists.LoadAsync();
|
||||||
_ = EnsureWorkerRunningAsync();
|
|
||||||
_updateCheck.PropertyChanged += (_, e) =>
|
_updateCheck.PropertyChanged += (_, e) =>
|
||||||
{
|
{
|
||||||
if (e.PropertyName == nameof(UpdateCheckService.LastCheckStatus))
|
if (e.PropertyName == nameof(UpdateCheckService.LastCheckStatus))
|
||||||
@@ -270,6 +278,25 @@ public sealed partial class IslandsShellViewModel : ViewModelBase
|
|||||||
if (ShowAboutModal is not null) await ShowAboutModal(vm);
|
if (ShowAboutModal is not null) await ShowAboutModal(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _connectionPromptShown;
|
||||||
|
|
||||||
|
internal bool DecideShowConnectionPrompt(bool isOffline)
|
||||||
|
{
|
||||||
|
if (!isOffline) return false;
|
||||||
|
if (_connectionPromptShown) return false;
|
||||||
|
_connectionPromptShown = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenWorkerConnectionHelpAsync()
|
||||||
|
{
|
||||||
|
var vm = new WorkerConnectionModalViewModel(_workerLocator, _installerLocator);
|
||||||
|
if (ShowWorkerConnectionModal is not null) await ShowWorkerConnectionModal(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private Task OpenWorkerConnectionHelp() => OpenWorkerConnectionHelpAsync();
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task OpenRepoImport()
|
private async Task OpenRepoImport()
|
||||||
{
|
{
|
||||||
@@ -305,20 +332,6 @@ public sealed partial class IslandsShellViewModel : ViewModelBase
|
|||||||
|
|
||||||
[ObservableProperty] private string? _restartWorkerStatus;
|
[ObservableProperty] private string? _restartWorkerStatus;
|
||||||
|
|
||||||
private bool _ensureRunningAttempted;
|
|
||||||
|
|
||||||
private async Task EnsureWorkerRunningAsync()
|
|
||||||
{
|
|
||||||
if (_ensureRunningAttempted) return;
|
|
||||||
_ensureRunningAttempted = true;
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(4));
|
|
||||||
if (Worker?.IsConnected == true) return;
|
|
||||||
var exe = _workerLocator.Find();
|
|
||||||
if (exe is null) return;
|
|
||||||
try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(exe) { UseShellExecute = true }); }
|
|
||||||
catch { /* logon task is the primary mechanism; this is a convenience */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task RestartWorkerAsync()
|
private async Task RestartWorkerAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
|
|||||||
[ObservableProperty] private string _defaultModel = ModelRegistry.DefaultAlias;
|
[ObservableProperty] private string _defaultModel = ModelRegistry.DefaultAlias;
|
||||||
[ObservableProperty] private int _defaultMaxTurns = 100;
|
[ObservableProperty] private int _defaultMaxTurns = 100;
|
||||||
[ObservableProperty] private string _defaultPermissionMode = PermissionModeRegistry.DefaultMode;
|
[ObservableProperty] private string _defaultPermissionMode = PermissionModeRegistry.DefaultMode;
|
||||||
|
[ObservableProperty] private int _maxParallelExecutions = 1;
|
||||||
|
|
||||||
public IReadOnlyList<string> Models { get; } = ModelRegistry.Aliases;
|
public IReadOnlyList<string> Models { get; } = ModelRegistry.Aliases;
|
||||||
public IReadOnlyList<string> PermissionModes { get; } = PermissionModeRegistry.Modes;
|
public IReadOnlyList<string> PermissionModes { get; } = PermissionModeRegistry.Modes;
|
||||||
@@ -17,6 +18,8 @@ public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
if (DefaultMaxTurns < 1 || DefaultMaxTurns > 200)
|
if (DefaultMaxTurns < 1 || DefaultMaxTurns > 200)
|
||||||
return "Max turns must be between 1 and 200.";
|
return "Max turns must be between 1 and 200.";
|
||||||
|
if (MaxParallelExecutions < 1 || MaxParallelExecutions > 20)
|
||||||
|
return "Max parallel executions must be between 1 and 20.";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ public sealed partial class PrimeClaudeTabViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
foreach (var r in Rows)
|
foreach (var r in Rows)
|
||||||
{
|
{
|
||||||
if (r.StartDate > r.EndDate)
|
if (r.DaysMask() == 0)
|
||||||
return $"Schedule {r.TimeOfDay:hh\\:mm}: start date is after end date.";
|
return $"Schedule {r.TimeOfDay:hh\\:mm}: select at least one day.";
|
||||||
if (r.TimeOfDay < TimeSpan.Zero || r.TimeOfDay >= TimeSpan.FromDays(1))
|
if (r.TimeOfDay < TimeSpan.Zero || r.TimeOfDay >= TimeSpan.FromDays(1))
|
||||||
return "Time must be between 00:00 and 23:59.";
|
return "Time must be between 00:00 and 23:59.";
|
||||||
}
|
}
|
||||||
@@ -52,13 +52,10 @@ public sealed partial class PrimeClaudeTabViewModel : ViewModelBase
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void AddSchedule()
|
private void AddSchedule()
|
||||||
{
|
{
|
||||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
|
||||||
var dto = new PrimeScheduleDto(
|
var dto = new PrimeScheduleDto(
|
||||||
Id: Guid.NewGuid(),
|
Id: Guid.NewGuid(),
|
||||||
StartDate: today,
|
Days: 31, // Mon–Fri
|
||||||
EndDate: today.AddDays(30),
|
|
||||||
TimeOfDay: new TimeSpan(7, 0, 0),
|
TimeOfDay: new TimeSpan(7, 0, 0),
|
||||||
WorkdaysOnly: true,
|
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
LastRunAt: null,
|
LastRunAt: null,
|
||||||
PromptOverride: null);
|
PromptOverride: null);
|
||||||
|
|||||||
@@ -5,14 +5,20 @@ namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
|
|||||||
|
|
||||||
public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
|
public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
private const int Mon = 1, Tue = 2, Wed = 4, Thu = 8, Fri = 16, Sat = 32, Sun = 64;
|
||||||
|
|
||||||
public Guid Id { get; }
|
public Guid Id { get; }
|
||||||
public bool IsExisting { get; }
|
public bool IsExisting { get; }
|
||||||
|
|
||||||
[ObservableProperty] private bool _enabled;
|
[ObservableProperty] private bool _enabled;
|
||||||
[ObservableProperty] private DateOnly _startDate;
|
[ObservableProperty] private bool _monday;
|
||||||
[ObservableProperty] private DateOnly _endDate;
|
[ObservableProperty] private bool _tuesday;
|
||||||
|
[ObservableProperty] private bool _wednesday;
|
||||||
|
[ObservableProperty] private bool _thursday;
|
||||||
|
[ObservableProperty] private bool _friday;
|
||||||
|
[ObservableProperty] private bool _saturday;
|
||||||
|
[ObservableProperty] private bool _sunday;
|
||||||
[ObservableProperty] private TimeSpan _timeOfDay;
|
[ObservableProperty] private TimeSpan _timeOfDay;
|
||||||
[ObservableProperty] private bool _workdaysOnly;
|
|
||||||
[ObservableProperty] private DateTimeOffset? _lastRunAt;
|
[ObservableProperty] private DateTimeOffset? _lastRunAt;
|
||||||
|
|
||||||
public string LastRunLabel => LastRunAt is { } v ? v.LocalDateTime.ToString("g") : "—";
|
public string LastRunLabel => LastRunAt is { } v ? v.LocalDateTime.ToString("g") : "—";
|
||||||
@@ -24,13 +30,30 @@ public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
|
|||||||
Id = dto.Id;
|
Id = dto.Id;
|
||||||
IsExisting = isExisting;
|
IsExisting = isExisting;
|
||||||
Enabled = dto.Enabled;
|
Enabled = dto.Enabled;
|
||||||
StartDate = dto.StartDate;
|
Monday = (dto.Days & Mon) != 0;
|
||||||
EndDate = dto.EndDate;
|
Tuesday = (dto.Days & Tue) != 0;
|
||||||
|
Wednesday = (dto.Days & Wed) != 0;
|
||||||
|
Thursday = (dto.Days & Thu) != 0;
|
||||||
|
Friday = (dto.Days & Fri) != 0;
|
||||||
|
Saturday = (dto.Days & Sat) != 0;
|
||||||
|
Sunday = (dto.Days & Sun) != 0;
|
||||||
TimeOfDay = dto.TimeOfDay;
|
TimeOfDay = dto.TimeOfDay;
|
||||||
WorkdaysOnly = dto.WorkdaysOnly;
|
|
||||||
LastRunAt = dto.LastRunAt;
|
LastRunAt = dto.LastRunAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int DaysMask()
|
||||||
|
{
|
||||||
|
int m = 0;
|
||||||
|
if (Monday) m |= Mon;
|
||||||
|
if (Tuesday) m |= Tue;
|
||||||
|
if (Wednesday) m |= Wed;
|
||||||
|
if (Thursday) m |= Thu;
|
||||||
|
if (Friday) m |= Fri;
|
||||||
|
if (Saturday) m |= Sat;
|
||||||
|
if (Sunday) m |= Sun;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
public PrimeScheduleDto ToDto() =>
|
public PrimeScheduleDto ToDto() =>
|
||||||
new(Id, StartDate, EndDate, TimeOfDay, WorkdaysOnly, Enabled, LastRunAt, null);
|
new(Id, DaysMask(), TimeOfDay, Enabled, LastRunAt, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
|||||||
General.DefaultModel = dto.DefaultModel ?? "sonnet";
|
General.DefaultModel = dto.DefaultModel ?? "sonnet";
|
||||||
General.DefaultMaxTurns = dto.DefaultMaxTurns;
|
General.DefaultMaxTurns = dto.DefaultMaxTurns;
|
||||||
General.DefaultPermissionMode = dto.DefaultPermissionMode ?? "auto";
|
General.DefaultPermissionMode = dto.DefaultPermissionMode ?? "auto";
|
||||||
|
General.MaxParallelExecutions = dto.MaxParallelExecutions;
|
||||||
Worktrees.WorktreeStrategy = dto.WorktreeStrategy ?? "sibling";
|
Worktrees.WorktreeStrategy = dto.WorktreeStrategy ?? "sibling";
|
||||||
Worktrees.CentralWorktreeRoot = dto.CentralWorktreeRoot;
|
Worktrees.CentralWorktreeRoot = dto.CentralWorktreeRoot;
|
||||||
Worktrees.WorktreeAutoCleanupEnabled = dto.WorktreeAutoCleanupEnabled;
|
Worktrees.WorktreeAutoCleanupEnabled = dto.WorktreeAutoCleanupEnabled;
|
||||||
@@ -69,6 +70,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
|||||||
General.DefaultModel ?? "sonnet",
|
General.DefaultModel ?? "sonnet",
|
||||||
General.DefaultMaxTurns,
|
General.DefaultMaxTurns,
|
||||||
General.DefaultPermissionMode ?? "auto",
|
General.DefaultPermissionMode ?? "auto",
|
||||||
|
General.MaxParallelExecutions,
|
||||||
Worktrees.WorktreeStrategy ?? "sibling",
|
Worktrees.WorktreeStrategy ?? "sibling",
|
||||||
string.IsNullOrWhiteSpace(Worktrees.CentralWorktreeRoot) ? null : Worktrees.CentralWorktreeRoot,
|
string.IsNullOrWhiteSpace(Worktrees.CentralWorktreeRoot) ? null : Worktrees.CentralWorktreeRoot,
|
||||||
Worktrees.WorktreeAutoCleanupEnabled,
|
Worktrees.WorktreeAutoCleanupEnabled,
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using ClaudeDo.Ui.Services;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.ViewModels.Modals;
|
||||||
|
|
||||||
|
public sealed partial class WorkerConnectionModalViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly WorkerLocator _workerLocator;
|
||||||
|
private readonly InstallerLocator _installerLocator;
|
||||||
|
|
||||||
|
public WorkerConnectionModalViewModel(WorkerLocator workerLocator, InstallerLocator installerLocator)
|
||||||
|
{
|
||||||
|
_workerLocator = workerLocator;
|
||||||
|
_installerLocator = installerLocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action? CloseAction { get; set; }
|
||||||
|
|
||||||
|
[RelayCommand] private void Close() => CloseAction?.Invoke();
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void StartWorker()
|
||||||
|
{
|
||||||
|
var exe = _workerLocator.Find();
|
||||||
|
if (exe is null) return;
|
||||||
|
try { Process.Start(new ProcessStartInfo(exe) { UseShellExecute = true }); }
|
||||||
|
catch { /* nothing useful to show */ }
|
||||||
|
CloseAction?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void RerunInstaller()
|
||||||
|
{
|
||||||
|
var path = _installerLocator.Find();
|
||||||
|
if (path is null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
catch { /* nothing useful to show */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml
Normal file
43
src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls">
|
||||||
|
<ControlTheme x:Key="{x:Type ctl:ModalShell}" TargetType="ctl:ModalShell">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<Border Background="{DynamicResource SurfaceBrush}"
|
||||||
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{DynamicResource ModalCornerRadius}"
|
||||||
|
ClipToBounds="True">
|
||||||
|
<DockPanel>
|
||||||
|
<Border Name="PART_TitleBar" DockPanel.Dock="Top" Height="36"
|
||||||
|
Background="{DynamicResource DeepBrush}"
|
||||||
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
|
BorderThickness="0,0,0,1">
|
||||||
|
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
|
||||||
|
<TextBlock Text="{TemplateBinding Title}"
|
||||||
|
FontFamily="{DynamicResource MonoFont}"
|
||||||
|
FontSize="{DynamicResource FontSizeMono}"
|
||||||
|
LetterSpacing="1.4"
|
||||||
|
Foreground="{DynamicResource TextBrush}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Button Grid.Column="1" Classes="icon-btn" Content="✕"
|
||||||
|
FontSize="{DynamicResource FontSizeBody}"
|
||||||
|
Command="{TemplateBinding CloseCommand}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<Border Name="PART_Footer" DockPanel.Dock="Bottom"
|
||||||
|
Background="{DynamicResource DeepBrush}"
|
||||||
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
|
BorderThickness="0,1,0,0"
|
||||||
|
IsVisible="{TemplateBinding Footer, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
|
<ContentPresenter Content="{TemplateBinding Footer}" Margin="16,8"/>
|
||||||
|
</Border>
|
||||||
|
<ContentPresenter Content="{TemplateBinding Content}"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</ControlTheme>
|
||||||
|
</ResourceDictionary>
|
||||||
70
src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml.cs
Normal file
70
src/ClaudeDo.Ui/Views/Controls/ModalShell.axaml.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Input;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Views.Controls;
|
||||||
|
|
||||||
|
/// <summary>Reusable modal chrome: titlebar (drag + close) wrapping a body and optional footer.</summary>
|
||||||
|
public class ModalShell : ContentControl
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<string?> TitleProperty =
|
||||||
|
AvaloniaProperty.Register<ModalShell, string?>(nameof(Title));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<object?> FooterProperty =
|
||||||
|
AvaloniaProperty.Register<ModalShell, object?>(nameof(Footer));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<ICommand?> CloseCommandProperty =
|
||||||
|
AvaloniaProperty.Register<ModalShell, ICommand?>(nameof(CloseCommand));
|
||||||
|
|
||||||
|
public string? Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||||
|
public object? Footer { get => GetValue(FooterProperty); set => SetValue(FooterProperty, value); }
|
||||||
|
public ICommand? CloseCommand { get => GetValue(CloseCommandProperty); set => SetValue(CloseCommandProperty, value); }
|
||||||
|
|
||||||
|
private Window? _window;
|
||||||
|
private PixelPoint _dragStartScreen;
|
||||||
|
private PixelPoint _dragStartPos;
|
||||||
|
private bool _dragging;
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
if (e.NameScope.Find<Border>("PART_TitleBar") is { } bar)
|
||||||
|
{
|
||||||
|
bar.PointerPressed += OnTitleBarPressed;
|
||||||
|
bar.PointerMoved += OnTitleBarMoved;
|
||||||
|
bar.PointerReleased += OnTitleBarReleased;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisualRoot is a TopLevelHost (not the Window) in Avalonia 12, so resolve the
|
||||||
|
// owning Window via TopLevel.GetTopLevel and drive the move manually — BeginMoveDrag
|
||||||
|
// and a VisualRoot-as-Window cast both fail here.
|
||||||
|
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||||
|
_window = TopLevel.GetTopLevel(this) as Window;
|
||||||
|
if (_window is null) return;
|
||||||
|
_dragStartScreen = _window.PointToScreen(e.GetPosition(_window));
|
||||||
|
_dragStartPos = _window.Position;
|
||||||
|
_dragging = true;
|
||||||
|
e.Pointer.Capture(sender as IInputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTitleBarMoved(object? sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_dragging || _window is null
|
||||||
|
|| !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||||
|
var cur = _window.PointToScreen(e.GetPosition(_window));
|
||||||
|
_window.Position = new PixelPoint(
|
||||||
|
_dragStartPos.X + (cur.X - _dragStartScreen.X),
|
||||||
|
_dragStartPos.Y + (cur.Y - _dragStartScreen.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTitleBarReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
_dragging = false;
|
||||||
|
e.Pointer.Capture(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<Setter Property="Foreground" Value="{DynamicResource TextDimBrush}"/>
|
<Setter Property="Foreground" Value="{DynamicResource TextDimBrush}"/>
|
||||||
<Setter Property="CornerRadius" Value="999"/>
|
<Setter Property="CornerRadius" Value="999"/>
|
||||||
<Setter Property="Padding" Value="10,3"/>
|
<Setter Property="Padding" Value="10,3"/>
|
||||||
<Setter Property="FontSize" Value="11"/>
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}"/>
|
||||||
<Setter Property="MinHeight" Value="22"/>
|
<Setter Property="MinHeight" Value="22"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.quick:pointerover /template/ ContentPresenter">
|
<Style Selector="Button.quick:pointerover /template/ ContentPresenter">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<Setter Property="Width" Value="32"/>
|
<Setter Property="Width" Value="32"/>
|
||||||
<Setter Property="Height" Value="32"/>
|
<Setter Property="Height" Value="32"/>
|
||||||
<Setter Property="CornerRadius" Value="999"/>
|
<Setter Property="CornerRadius" Value="999"/>
|
||||||
<Setter Property="FontSize" Value="12"/>
|
<Setter Property="FontSize" Value="{StaticResource FontSizeBody}"/>
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
<Setter Property="Padding" Value="0"/>
|
<Setter Property="Padding" Value="0"/>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.day.selected /template/ ContentPresenter">
|
<Style Selector="Button.day.selected /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{DynamicResource AccentBrush}"/>
|
<Setter Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||||
<Setter Property="TextElement.Foreground" Value="White"/>
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.day.selected:pointerover /template/ ContentPresenter">
|
<Style Selector="Button.day.selected:pointerover /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{DynamicResource AccentDimBrush}"/>
|
<Setter Property="Background" Value="{DynamicResource AccentDimBrush}"/>
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
<Style Selector="TextBlock.weekday">
|
<Style Selector="TextBlock.weekday">
|
||||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
<Setter Property="Foreground" Value="{DynamicResource TextMuteBrush}"/>
|
<Setter Property="Foreground" Value="{DynamicResource TextMuteBrush}"/>
|
||||||
<Setter Property="FontSize" Value="10"/>
|
<Setter Property="FontSize" Value="{StaticResource FontSizeEyebrow}"/>
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
</Style>
|
</Style>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
@@ -133,12 +133,9 @@
|
|||||||
|
|
||||||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,2,0,0">
|
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,2,0,0">
|
||||||
<Button Grid.Column="0" Click="OnPrevMonthClick" Classes="nav" Content="◀"/>
|
<Button Grid.Column="0" Click="OnPrevMonthClick" Classes="nav" Content="◀"/>
|
||||||
<TextBlock Grid.Column="1" x:Name="MonthHeader"
|
<TextBlock Grid.Column="1" x:Name="MonthHeader" Classes="title"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"/>
|
||||||
FontWeight="SemiBold"
|
|
||||||
FontSize="13"
|
|
||||||
Foreground="{DynamicResource TextBrush}"/>
|
|
||||||
<Button Grid.Column="2" Click="OnNextMonthClick" Classes="nav" Content="▶"/>
|
<Button Grid.Column="2" Click="OnNextMonthClick" Classes="nav" Content="▶"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,14 @@
|
|||||||
Classes.status-pulse="{Binding IsRunning}"
|
Classes.status-pulse="{Binding IsRunning}"
|
||||||
Margin="0,0,6,0"/>
|
Margin="0,0,6,0"/>
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="meta"
|
||||||
Text="{Binding AgentStatusLabel}"
|
Text="{Binding AgentStatusLabel}"
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
|
||||||
FontSize="10"
|
|
||||||
LetterSpacing="1.2"
|
LetterSpacing="1.2"
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="0,0,8,0"/>
|
Margin="0,0,8,0"/>
|
||||||
<TextBlock Grid.Column="2"
|
<TextBlock Grid.Column="2"
|
||||||
|
Classes="meta"
|
||||||
Text="{Binding Model}"
|
Text="{Binding Model}"
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
|
||||||
FontSize="10"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}"
|
Foreground="{DynamicResource TextFaintBrush}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
@@ -66,16 +63,15 @@
|
|||||||
<Grid ColumnDefinitions="Auto,*,Auto"
|
<Grid ColumnDefinitions="Auto,*,Auto"
|
||||||
IsVisible="{Binding WorktreePath, Converter={x:Static ObjectConverters.IsNotNull}}">
|
IsVisible="{Binding WorktreePath, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
|
Classes="eyebrow"
|
||||||
Text="WORKTREE"
|
Text="WORKTREE"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="9"
|
|
||||||
LetterSpacing="1.2"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}"
|
Foreground="{DynamicResource TextFaintBrush}"
|
||||||
|
LetterSpacing="1.2"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="0,0,8,0"/>
|
Margin="0,0,8,0"/>
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="meta"
|
||||||
Text="{Binding WorktreePath}"
|
Text="{Binding WorktreePath}"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="10"
|
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<Button Grid.Column="2"
|
<Button Grid.Column="2"
|
||||||
@@ -93,15 +89,14 @@
|
|||||||
<PathIcon Data="{StaticResource Icon.GitBranch}" Width="11" Height="11"
|
<PathIcon Data="{StaticResource Icon.GitBranch}" Width="11" Height="11"
|
||||||
Foreground="{DynamicResource AccentBrush}"
|
Foreground="{DynamicResource AccentBrush}"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<TextBlock Text="{Binding BranchLine}"
|
<TextBlock Classes="meta"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="10"
|
Text="{Binding BranchLine}"
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<Border Classes="chip"
|
<Border Classes="chip"
|
||||||
IsVisible="{Binding CommitsOnBranch}"
|
IsVisible="{Binding CommitsOnBranch}"
|
||||||
Padding="5,1" CornerRadius="4">
|
Padding="5,1" CornerRadius="6">
|
||||||
<TextBlock Text="{Binding CommitsOnBranch, StringFormat='{}{0}c'}"
|
<TextBlock Classes="meta"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="9"
|
Text="{Binding CommitsOnBranch, StringFormat='{}{0}c'}"
|
||||||
Foreground="{DynamicResource TextFaintBrush}"/>
|
Foreground="{DynamicResource TextFaintBrush}"/>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -109,10 +104,10 @@
|
|||||||
<!-- Row 4: DIFF label + +add −del + meter bar -->
|
<!-- Row 4: DIFF label + +add −del + meter bar -->
|
||||||
<Grid ColumnDefinitions="Auto,Auto,Auto,*">
|
<Grid ColumnDefinitions="Auto,Auto,Auto,*">
|
||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
|
Classes="eyebrow"
|
||||||
Text="DIFF"
|
Text="DIFF"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="9"
|
|
||||||
LetterSpacing="1.2"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}"
|
Foreground="{DynamicResource TextFaintBrush}"
|
||||||
|
LetterSpacing="1.2"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="0,0,8,0"/>
|
Margin="0,0,8,0"/>
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
|
|||||||
@@ -21,9 +21,8 @@
|
|||||||
Foreground="{DynamicResource BloodBrush}"/>
|
Foreground="{DynamicResource BloodBrush}"/>
|
||||||
</Button>
|
</Button>
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="meta"
|
||||||
Text="{Binding Task.CreatedAtFormatted}"
|
Text="{Binding Task.CreatedAtFormatted}"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="10"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}"
|
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<Button Grid.Column="2" Classes="icon-btn"
|
<Button Grid.Column="2" Classes="icon-btn"
|
||||||
@@ -49,12 +48,14 @@
|
|||||||
Cursor="Hand"/>
|
Cursor="Hand"/>
|
||||||
</Button>
|
</Button>
|
||||||
<StackPanel Grid.Column="1" Spacing="0">
|
<StackPanel Grid.Column="1" Spacing="0">
|
||||||
<TextBlock Text="{Binding TaskIdBadge}"
|
<TextBlock Classes="meta"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="10"
|
Text="{Binding TaskIdBadge}"
|
||||||
Foreground="{DynamicResource TextFaintBrush}"
|
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="14" FontWeight="Medium"
|
FontSize="{StaticResource FontSizeTaskTitle}" FontWeight="Medium"
|
||||||
BorderThickness="0" Background="Transparent"
|
BorderThickness="0" Background="Transparent"
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
@@ -77,30 +78,31 @@
|
|||||||
IsEnabled="{Binding IsAgentSectionEnabled}"
|
IsEnabled="{Binding IsAgentSectionEnabled}"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Margin="6,0,0,0">
|
Margin="6,0,0,0">
|
||||||
<TextBlock Text="⚙" FontSize="14"/>
|
<TextBlock Text="⚙" FontSize="{StaticResource FontSizeTaskTitle}"/>
|
||||||
<Button.Flyout>
|
<Button.Flyout>
|
||||||
<Flyout Placement="BottomEdgeAlignedRight" ShowMode="Standard">
|
<Flyout Placement="BottomEdgeAlignedRight" ShowMode="Standard">
|
||||||
<StackPanel Width="340" Spacing="10" Margin="4">
|
<StackPanel Width="340" Spacing="10" Margin="4">
|
||||||
<TextBlock Text="Agent settings (overrides)" FontWeight="SemiBold"/>
|
<TextBlock Text="Agent settings (overrides)" FontWeight="SemiBold"/>
|
||||||
|
|
||||||
<StackPanel Spacing="2">
|
<StackPanel Spacing="2">
|
||||||
<TextBlock Text="Model"/>
|
<TextBlock Classes="field-label" Text="Model"/>
|
||||||
<ComboBox ItemsSource="{Binding TaskModelOptions}"
|
<ComboBox ItemsSource="{Binding TaskModelOptions}"
|
||||||
SelectedItem="{Binding TaskModelSelection, Mode=TwoWay}"
|
SelectedItem="{Binding TaskModelSelection, Mode=TwoWay}"
|
||||||
HorizontalAlignment="Stretch"/>
|
HorizontalAlignment="Stretch"/>
|
||||||
<TextBlock Text="{Binding EffectiveModelHint, StringFormat='Effective if inherited: {0}'}"
|
<TextBlock Classes="meta"
|
||||||
Opacity="0.6" FontSize="11"/>
|
Text="{Binding EffectiveModelHint, StringFormat='Effective if inherited: {0}'}"
|
||||||
|
Opacity="0.6"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="2">
|
<StackPanel Spacing="2">
|
||||||
<TextBlock Text="System prompt (appended)"/>
|
<TextBlock Classes="field-label" Text="System prompt (appended)"/>
|
||||||
<TextBox Text="{Binding TaskSystemPrompt, Mode=TwoWay}"
|
<TextBox Text="{Binding TaskSystemPrompt, Mode=TwoWay}"
|
||||||
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="70"
|
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="70"
|
||||||
PlaceholderText="{Binding EffectiveSystemPromptHint}"/>
|
PlaceholderText="{Binding EffectiveSystemPromptHint}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="2">
|
<StackPanel Spacing="2">
|
||||||
<TextBlock Text="Agent file"/>
|
<TextBlock Classes="field-label" Text="Agent file"/>
|
||||||
<ComboBox ItemsSource="{Binding TaskAgentOptions}"
|
<ComboBox ItemsSource="{Binding TaskAgentOptions}"
|
||||||
SelectedItem="{Binding TaskSelectedAgent, Mode=TwoWay}"
|
SelectedItem="{Binding TaskSelectedAgent, Mode=TwoWay}"
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
@@ -110,8 +112,9 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ComboBox.ItemTemplate>
|
</ComboBox.ItemTemplate>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<TextBlock Text="{Binding EffectiveAgentHint, StringFormat='Effective if inherited: {0}'}"
|
<TextBlock Classes="meta"
|
||||||
Opacity="0.6" FontSize="11"/>
|
Text="{Binding EffectiveAgentHint, StringFormat='Effective if inherited: {0}'}"
|
||||||
|
Opacity="0.6"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Flyout>
|
</Flyout>
|
||||||
@@ -128,39 +131,33 @@
|
|||||||
<StackPanel Spacing="0">
|
<StackPanel Spacing="0">
|
||||||
|
|
||||||
<!-- Planning merge section — visible only for planning parent tasks -->
|
<!-- Planning merge section — visible only for planning parent tasks -->
|
||||||
<Border Padding="18,12,18,12"
|
<Border Classes="section-divider"
|
||||||
BorderBrush="{DynamicResource LineBrush}"
|
|
||||||
BorderThickness="0,0,0,1"
|
|
||||||
IsVisible="{Binding Task.IsPlanningParent}">
|
IsVisible="{Binding Task.IsPlanningParent}">
|
||||||
<StackPanel Spacing="8">
|
<StackPanel Spacing="8">
|
||||||
<TextBlock Classes="section-label" Text="MERGE" Margin="0,0,0,2"/>
|
<TextBlock Classes="section-label" Text="MERGE" Margin="0,0,0,2"/>
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Text="Merge target"
|
<TextBlock Classes="field-label" Text="Merge target"/>
|
||||||
FontSize="11"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}"/>
|
|
||||||
<ComboBox ItemsSource="{Binding MergeTargetBranches}"
|
<ComboBox ItemsSource="{Binding MergeTargetBranches}"
|
||||||
SelectedItem="{Binding SelectedMergeTarget, Mode=TwoWay}"
|
SelectedItem="{Binding SelectedMergeTarget, Mode=TwoWay}"
|
||||||
HorizontalAlignment="Stretch"/>
|
HorizontalAlignment="Stretch"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<Button Content="Review combined diff"
|
<Button Classes="btn" Content="Review combined diff"
|
||||||
Command="{Binding ReviewCombinedDiffCommand}"/>
|
Command="{Binding ReviewCombinedDiffCommand}"/>
|
||||||
<Button Content="Merge all subtasks"
|
<Button Classes="btn" Content="Merge all subtasks"
|
||||||
IsEnabled="{Binding CanMergeAll}"
|
IsEnabled="{Binding CanMergeAll}"
|
||||||
Command="{Binding MergeAllCommand}"
|
Command="{Binding MergeAllCommand}"
|
||||||
ToolTip.Tip="{Binding MergeAllDisabledReason}"/>
|
ToolTip.Tip="{Binding MergeAllDisabledReason}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TextBlock Text="{Binding MergeAllError}"
|
<TextBlock Text="{Binding MergeAllError}"
|
||||||
Foreground="OrangeRed"
|
Foreground="{DynamicResource BloodBrush}"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
IsVisible="{Binding MergeAllError, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
IsVisible="{Binding MergeAllError, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Steps section -->
|
<!-- Steps section -->
|
||||||
<Border Padding="18,12,18,12"
|
<Border Classes="section-divider">
|
||||||
BorderBrush="{DynamicResource LineBrush}"
|
|
||||||
BorderThickness="0,0,0,1">
|
|
||||||
<StackPanel Spacing="6">
|
<StackPanel Spacing="6">
|
||||||
<TextBlock Classes="section-label" Text="STEPS" Margin="0,0,0,2"/>
|
<TextBlock Classes="section-label" Text="STEPS" Margin="0,0,0,2"/>
|
||||||
<TextBox Text="{Binding NewSubtaskTitle, Mode=TwoWay}"
|
<TextBox Text="{Binding NewSubtaskTitle, Mode=TwoWay}"
|
||||||
@@ -169,7 +166,7 @@
|
|||||||
Background="{DynamicResource Surface2Brush}"
|
Background="{DynamicResource Surface2Brush}"
|
||||||
BorderBrush="{DynamicResource LineBrush}"
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="6">
|
CornerRadius="8">
|
||||||
<TextBox.KeyBindings>
|
<TextBox.KeyBindings>
|
||||||
<KeyBinding Gesture="Enter" Command="{Binding AddSubtaskCommand}"/>
|
<KeyBinding Gesture="Enter" Command="{Binding AddSubtaskCommand}"/>
|
||||||
</TextBox.KeyBindings>
|
</TextBox.KeyBindings>
|
||||||
@@ -192,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}"
|
||||||
FontSize="13"
|
IsVisible="{Binding !IsEditing}"
|
||||||
|
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>
|
||||||
@@ -208,9 +222,7 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Details (description) section -->
|
<!-- Details (description) section -->
|
||||||
<Border Padding="18,12,18,12"
|
<Border Classes="section-divider">
|
||||||
BorderBrush="{DynamicResource LineBrush}"
|
|
||||||
BorderThickness="0,0,0,1">
|
|
||||||
<StackPanel Spacing="6">
|
<StackPanel Spacing="6">
|
||||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||||
<Button Grid.Column="0"
|
<Button Grid.Column="0"
|
||||||
@@ -220,12 +232,12 @@
|
|||||||
Margin="0,0,6,2"
|
Margin="0,0,6,2"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
<TextBlock Text="▾" FontSize="10"
|
<TextBlock Classes="meta"
|
||||||
IsVisible="{Binding IsDescriptionExpanded}"
|
Text="▾"
|
||||||
Foreground="{DynamicResource TextDimBrush}"/>
|
IsVisible="{Binding IsDescriptionExpanded}"/>
|
||||||
<TextBlock Text="▸" FontSize="10"
|
<TextBlock Classes="meta"
|
||||||
IsVisible="{Binding !IsDescriptionExpanded}"
|
Text="▸"
|
||||||
Foreground="{DynamicResource TextDimBrush}"/>
|
IsVisible="{Binding !IsDescriptionExpanded}"/>
|
||||||
<TextBlock Classes="section-label" Text="DETAILS"/>
|
<TextBlock Classes="section-label" Text="DETAILS"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -239,19 +251,17 @@
|
|||||||
<PathIcon Data="{StaticResource Icon.Copy}" Width="11" Height="11"/>
|
<PathIcon Data="{StaticResource Icon.Copy}" Width="11" Height="11"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Column="3"
|
<Button Grid.Column="3"
|
||||||
Classes="icon-btn"
|
Classes="btn"
|
||||||
Command="{Binding ToggleEditDescriptionCommand}"
|
Command="{Binding ToggleEditDescriptionCommand}"
|
||||||
Padding="6,2"
|
Padding="8,3"
|
||||||
FontSize="10"
|
|
||||||
ToolTip.Tip="Toggle edit/preview"
|
ToolTip.Tip="Toggle edit/preview"
|
||||||
IsVisible="{Binding IsDescriptionEditorVisible}">
|
IsVisible="{Binding IsDescriptionEditorVisible}">
|
||||||
<TextBlock Text="Preview"/>
|
<TextBlock Text="Preview"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Column="3"
|
<Button Grid.Column="3"
|
||||||
Classes="icon-btn"
|
Classes="btn"
|
||||||
Command="{Binding ToggleEditDescriptionCommand}"
|
Command="{Binding ToggleEditDescriptionCommand}"
|
||||||
Padding="6,2"
|
Padding="8,3"
|
||||||
FontSize="10"
|
|
||||||
ToolTip.Tip="Toggle edit/preview"
|
ToolTip.Tip="Toggle edit/preview"
|
||||||
IsVisible="{Binding IsDescriptionPreviewVisible}">
|
IsVisible="{Binding IsDescriptionPreviewVisible}">
|
||||||
<TextBlock Text="Edit"/>
|
<TextBlock Text="Edit"/>
|
||||||
@@ -266,11 +276,11 @@
|
|||||||
PlaceholderText="Add task details (markdown supported)..."
|
PlaceholderText="Add task details (markdown supported)..."
|
||||||
Padding="8"
|
Padding="8"
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
FontFamily="{DynamicResource MonoFont}"
|
||||||
FontSize="12"
|
FontSize="{StaticResource FontSizeBody}"
|
||||||
Background="{DynamicResource Surface2Brush}"
|
Background="{DynamicResource Surface2Brush}"
|
||||||
BorderBrush="{DynamicResource LineBrush}"
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="6"
|
CornerRadius="8"
|
||||||
IsVisible="{Binding IsDescriptionEditorVisible}"/>
|
IsVisible="{Binding IsDescriptionEditorVisible}"/>
|
||||||
|
|
||||||
<ctl:MarkdownView Markdown="{Binding EditableDescription}"
|
<ctl:MarkdownView Markdown="{Binding EditableDescription}"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -4,20 +4,12 @@
|
|||||||
xmlns:converters="using:ClaudeDo.Ui.Converters"
|
xmlns:converters="using:ClaudeDo.Ui.Converters"
|
||||||
x:Class="ClaudeDo.Ui.Views.Islands.ListsIslandView"
|
x:Class="ClaudeDo.Ui.Views.Islands.ListsIslandView"
|
||||||
x:DataType="vm:ListsIslandViewModel">
|
x:DataType="vm:ListsIslandViewModel">
|
||||||
<UserControl.Resources>
|
|
||||||
<converters:UpperCaseConverter x:Key="UpperCase"/>
|
|
||||||
<converters:IconKeyConverter x:Key="IconKey"/>
|
|
||||||
<converters:DotBrushConverter x:Key="DotBrush"/>
|
|
||||||
</UserControl.Resources>
|
|
||||||
|
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
|
|
||||||
<!-- ── 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 Spacing="4">
|
||||||
<TextBlock FontFamily="{DynamicResource SansFamily}" FontSize="18"
|
<TextBlock Classes="heading" Text="Lists"/>
|
||||||
FontWeight="SemiBold" Foreground="{DynamicResource TextBrush}"
|
|
||||||
Text="Lists"/>
|
|
||||||
|
|
||||||
<!-- Search row -->
|
<!-- Search row -->
|
||||||
<Border Classes="search-wrap" Margin="0,8,0,12">
|
<Border Classes="search-wrap" Margin="0,8,0,12">
|
||||||
@@ -45,18 +37,16 @@
|
|||||||
<!-- Avatar circle -->
|
<!-- Avatar circle -->
|
||||||
<Border Grid.Column="0" Classes="avatar-circle"
|
<Border Grid.Column="0" Classes="avatar-circle"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
<TextBlock Text="{Binding UserInitials}"
|
<TextBlock Classes="eyebrow"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="10"
|
Text="{Binding UserInitials}"
|
||||||
FontWeight="SemiBold"
|
FontWeight="SemiBold"
|
||||||
Foreground="{DynamicResource DeepBrush}"
|
Foreground="{DynamicResource DeepBrush}"
|
||||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
</Border>
|
</Border>
|
||||||
<!-- Name + machine -->
|
<!-- Name + machine -->
|
||||||
<StackPanel Grid.Column="1" Margin="8,0" Spacing="1" VerticalAlignment="Center">
|
<StackPanel Grid.Column="1" Margin="8,0" Spacing="1" VerticalAlignment="Center">
|
||||||
<TextBlock Text="{Binding UserName}"
|
<TextBlock Classes="title" Text="{Binding UserName}"/>
|
||||||
FontSize="12" Foreground="{DynamicResource TextBrush}"/>
|
<TextBlock Classes="meta">
|
||||||
<TextBlock FontFamily="{DynamicResource MonoFont}" FontSize="10"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}">
|
|
||||||
<TextBlock.Text>
|
<TextBlock.Text>
|
||||||
<MultiBinding StringFormat="{}{0} / local">
|
<MultiBinding StringFormat="{}{0} / local">
|
||||||
<Binding Path="MachineName"/>
|
<Binding Path="MachineName"/>
|
||||||
@@ -80,7 +70,7 @@
|
|||||||
<StackPanel Margin="6,0,6,4">
|
<StackPanel Margin="6,0,6,4">
|
||||||
|
|
||||||
<!-- SMART LISTS section -->
|
<!-- SMART LISTS section -->
|
||||||
<TextBlock Classes="list-section-label" Text="SMART LISTS"/>
|
<TextBlock Classes="section-label" Text="SMART LISTS" Margin="10,10,10,4"/>
|
||||||
<ItemsControl ItemsSource="{Binding SmartLists}">
|
<ItemsControl ItemsSource="{Binding SmartLists}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate DataType="vm:ListNavItemViewModel">
|
<DataTemplate DataType="vm:ListNavItemViewModel">
|
||||||
@@ -90,10 +80,8 @@
|
|||||||
<!-- Left accent bar for active state -->
|
<!-- Left accent bar for active state -->
|
||||||
<Border Grid.Column="0" Grid.ColumnSpan="3"
|
<Border Grid.Column="0" Grid.ColumnSpan="3"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
CornerRadius="8" IsHitTestVisible="False">
|
CornerRadius="8" IsHitTestVisible="False"
|
||||||
<Border.IsVisible>
|
IsVisible="{Binding IsActive}">
|
||||||
<Binding Path="IsActive"/>
|
|
||||||
</Border.IsVisible>
|
|
||||||
<Border Width="2" Height="16"
|
<Border Width="2" Height="16"
|
||||||
Background="{DynamicResource AccentBrush}"
|
Background="{DynamicResource AccentBrush}"
|
||||||
CornerRadius="1"
|
CornerRadius="1"
|
||||||
@@ -110,7 +98,7 @@
|
|||||||
<TextBlock Grid.Column="1" Classes="list-label"
|
<TextBlock Grid.Column="1" Classes="list-label"
|
||||||
Text="{Binding Name}"
|
Text="{Binding Name}"
|
||||||
VerticalAlignment="Center" Margin="8,0"
|
VerticalAlignment="Center" Margin="8,0"
|
||||||
Foreground="{DynamicResource TextDimBrush}" FontSize="13"/>
|
Foreground="{DynamicResource TextDimBrush}" FontSize="{StaticResource FontSizeBody}"/>
|
||||||
<!-- Count -->
|
<!-- Count -->
|
||||||
<TextBlock Grid.Column="2" Classes="list-count"
|
<TextBlock Grid.Column="2" Classes="list-count"
|
||||||
Text="{Binding Count}"/>
|
Text="{Binding Count}"/>
|
||||||
@@ -121,12 +109,22 @@
|
|||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|
||||||
<!-- MY LISTS section -->
|
<!-- MY LISTS section -->
|
||||||
<TextBlock Classes="list-section-label" Text="MY LISTS"/>
|
<TextBlock Classes="section-label" Text="MY LISTS" Margin="10,10,10,4"/>
|
||||||
<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..."
|
||||||
@@ -135,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">
|
||||||
@@ -158,12 +165,18 @@
|
|||||||
<TextBlock Grid.Column="1" Classes="list-label"
|
<TextBlock Grid.Column="1" Classes="list-label"
|
||||||
Text="{Binding Name}"
|
Text="{Binding Name}"
|
||||||
VerticalAlignment="Center" Margin="8,0"
|
VerticalAlignment="Center" Margin="8,0"
|
||||||
Foreground="{DynamicResource TextDimBrush}" FontSize="13"/>
|
Foreground="{DynamicResource TextDimBrush}" FontSize="{StaticResource FontSizeBody}"/>
|
||||||
<!-- Count -->
|
<!-- Count -->
|
||||||
<TextBlock Grid.Column="2" Classes="list-count"
|
<TextBlock Grid.Column="2" Classes="list-count"
|
||||||
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>
|
||||||
@@ -177,7 +190,8 @@
|
|||||||
Width="13" Height="13"
|
Width="13" Height="13"
|
||||||
Foreground="{DynamicResource TextMuteBrush}"
|
Foreground="{DynamicResource TextMuteBrush}"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<TextBlock Text="New list" FontSize="12"
|
<TextBlock Classes="body"
|
||||||
|
Text="New list"
|
||||||
Foreground="{DynamicResource TextMuteBrush}"
|
Foreground="{DynamicResource TextMuteBrush}"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
Height="28">
|
Height="28">
|
||||||
<!-- Session label -->
|
<!-- Session label -->
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="meta"
|
||||||
Text="{Binding BranchLine, StringFormat='claude-session · {0}'}"
|
Text="{Binding BranchLine, StringFormat='claude-session · {0}'}"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="10"
|
|
||||||
LetterSpacing="0.8"
|
LetterSpacing="0.8"
|
||||||
Foreground="{DynamicResource TextMuteBrush}"
|
Foreground="{DynamicResource TextMuteBrush}"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
@@ -65,7 +65,6 @@
|
|||||||
<!-- Message text — selectable so the user can copy raw output -->
|
<!-- Message text — selectable so the user can copy raw output -->
|
||||||
<SelectableTextBlock Grid.Column="1"
|
<SelectableTextBlock Grid.Column="1"
|
||||||
Text="{Binding Text}" Tag="{Binding ClassName}"
|
Text="{Binding Text}" Tag="{Binding ClassName}"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="11"
|
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
Foreground="{DynamicResource TextDimBrush}"
|
||||||
TextWrapping="Wrap"/>
|
TextWrapping="Wrap"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
|
||||||
using ClaudeDo.Ui.ViewModels.Islands;
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Views.Islands;
|
namespace ClaudeDo.Ui.Views.Islands;
|
||||||
@@ -9,16 +8,29 @@ public partial class SessionTerminalView : UserControl
|
|||||||
{
|
{
|
||||||
public SessionTerminalView() { InitializeComponent(); }
|
public SessionTerminalView() { InitializeComponent(); }
|
||||||
|
|
||||||
|
private DetailsIslandViewModel? _boundVm;
|
||||||
|
|
||||||
protected override void OnDataContextChanged(EventArgs e)
|
protected override void OnDataContextChanged(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnDataContextChanged(e);
|
base.OnDataContextChanged(e);
|
||||||
if (DataContext is DetailsIslandViewModel vm)
|
if (_boundVm is not null)
|
||||||
vm.Log.CollectionChanged += OnLogChanged;
|
_boundVm.Log.CollectionChanged -= OnLogChanged;
|
||||||
|
_boundVm = DataContext as DetailsIslandViewModel;
|
||||||
|
if (_boundVm is not null)
|
||||||
|
_boundVm.Log.CollectionChanged += OnLogChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLogChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
private void OnLogChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Action != NotifyCollectionChangedAction.Add) return;
|
if (e.Action != NotifyCollectionChangedAction.Add) return;
|
||||||
Dispatcher.UIThread.Post(() => LogScroll.ScrollToEnd(), DispatcherPriority.Background);
|
// Scroll after the next layout pass so the freshly-added (wrapping) line
|
||||||
|
// is measured first — otherwise ScrollToEnd stops short and clips it.
|
||||||
|
EventHandler? handler = null;
|
||||||
|
handler = (_, _) =>
|
||||||
|
{
|
||||||
|
LogScroll.LayoutUpdated -= handler;
|
||||||
|
LogScroll.ScrollToEnd();
|
||||||
|
};
|
||||||
|
LogScroll.LayoutUpdated += handler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,9 +79,9 @@
|
|||||||
Width="18" Height="18"
|
Width="18" Height="18"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
<Panel>
|
<Panel>
|
||||||
<TextBlock Text="▾" FontSize="10" IsVisible="{Binding IsExpanded}"
|
<TextBlock Classes="meta" Text="▾" IsVisible="{Binding IsExpanded}"
|
||||||
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||||||
<TextBlock Text="▸" FontSize="10" IsVisible="{Binding !IsExpanded}"
|
<TextBlock Classes="meta" Text="▸" IsVisible="{Binding !IsExpanded}"
|
||||||
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
<Grid ColumnDefinitions="*,Auto" VerticalAlignment="Center">
|
<Grid ColumnDefinitions="*,Auto" VerticalAlignment="Center">
|
||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
Classes="task-title"
|
Classes="task-title"
|
||||||
Text="{Binding Title}" FontSize="14"
|
Text="{Binding Title}" FontSize="{StaticResource FontSizeTaskTitle}"
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
FontStyle="{Binding IsDraft, Converter={StaticResource BoolToItalic}}"
|
FontStyle="{Binding IsDraft, Converter={StaticResource BoolToItalic}}"
|
||||||
@@ -131,12 +131,30 @@
|
|||||||
<!-- Status chip -->
|
<!-- Status chip -->
|
||||||
<Border Classes="chip"
|
<Border Classes="chip"
|
||||||
Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
|
Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
|
||||||
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Done}"
|
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=WaitingForReview}"
|
||||||
|
Classes.done="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Done}"
|
||||||
Classes.error="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Failed}"
|
Classes.error="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Failed}"
|
||||||
Classes.queued="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Queued}">
|
Classes.queued="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Queued}">
|
||||||
<TextBlock Text="{Binding Status}"/>
|
<TextBlock Text="{Binding StatusLabel}"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<!-- Review actions (visible when WaitingForReview) -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4"
|
||||||
|
IsVisible="{Binding IsWaitingForReview}">
|
||||||
|
<Button Classes="btn" Content="Approve" MinWidth="0" Padding="8,2"
|
||||||
|
ToolTip.Tip="Approve — mark Done"
|
||||||
|
Click="OnApproveReviewClick"/>
|
||||||
|
<Button Classes="btn" Content="Reject" MinWidth="0" Padding="8,2"
|
||||||
|
ToolTip.Tip="Reject with feedback and re-run"
|
||||||
|
Click="OnRejectReviewClick"/>
|
||||||
|
<Button Classes="btn" Content="Park" MinWidth="0" Padding="8,2"
|
||||||
|
ToolTip.Tip="Send back to Idle for manual editing"
|
||||||
|
Click="OnParkReviewClick"/>
|
||||||
|
<Button Classes="btn" Content="Cancel" MinWidth="0" Padding="8,2"
|
||||||
|
ToolTip.Tip="Cancel this task"
|
||||||
|
Click="OnCancelReviewClick"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Dequeue button (visible when row is Queued, or planning parent has queued subtasks) -->
|
<!-- Dequeue button (visible when row is Queued, or planning parent has queued subtasks) -->
|
||||||
<Button Classes="icon-btn dequeue-btn"
|
<Button Classes="icon-btn dequeue-btn"
|
||||||
IsVisible="{Binding CanRemoveFromQueue}"
|
IsVisible="{Binding CanRemoveFromQueue}"
|
||||||
@@ -175,20 +193,6 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Live-tail row (visible when running + has tail) -->
|
|
||||||
<Border Classes="task-live-tail" IsVisible="{Binding HasLiveTail}">
|
|
||||||
<StackPanel Spacing="3">
|
|
||||||
<TextBlock Text="{Binding LiveTail}"
|
|
||||||
TextTrimming="CharacterEllipsis" MaxLines="1"/>
|
|
||||||
<Grid Height="3" HorizontalAlignment="Stretch">
|
|
||||||
<Rectangle Fill="{DynamicResource Surface3Brush}"
|
|
||||||
HorizontalAlignment="Stretch" RadiusX="1.5" RadiusY="1.5"/>
|
|
||||||
<Rectangle Fill="{DynamicResource MossBrush}"
|
|
||||||
HorizontalAlignment="Left" Width="60" RadiusX="1.5" RadiusY="1.5"/>
|
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Star toggle -->
|
<!-- Star toggle -->
|
||||||
@@ -218,24 +222,22 @@
|
|||||||
<Button.Flyout>
|
<Button.Flyout>
|
||||||
<Flyout Placement="Bottom" ShowMode="Standard">
|
<Flyout Placement="Bottom" ShowMode="Standard">
|
||||||
<Border Background="{DynamicResource Surface2Brush}"
|
<Border Background="{DynamicResource Surface2Brush}"
|
||||||
BorderBrush="{DynamicResource BorderBrush}"
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
BorderThickness="1" CornerRadius="10"
|
BorderThickness="1" CornerRadius="10"
|
||||||
Padding="16" Width="300">
|
Padding="16" Width="300">
|
||||||
<StackPanel Spacing="12">
|
<StackPanel Spacing="12">
|
||||||
<TextBlock Text="Schedule task"
|
<TextBlock Classes="title" Text="Schedule task"/>
|
||||||
FontWeight="SemiBold" FontSize="13"
|
|
||||||
Foreground="{DynamicResource TextBrush}"/>
|
|
||||||
|
|
||||||
<StackPanel Spacing="6">
|
<StackPanel Spacing="6">
|
||||||
<TextBlock Text="WHEN" FontSize="10" Opacity="0.6"
|
<TextBlock Classes="eyebrow" Text="WHEN"
|
||||||
Foreground="{DynamicResource TextDimBrush}"/>
|
Foreground="{DynamicResource TextDimBrush}" Opacity="0.6"/>
|
||||||
<ctl:ThemedDatePicker x:Name="ScheduleDate" ShowTime="True"
|
<ctl:ThemedDatePicker x:Name="ScheduleDate" ShowTime="True"
|
||||||
HorizontalAlignment="Stretch"/>
|
HorizontalAlignment="Stretch"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8"
|
<StackPanel Orientation="Horizontal" Spacing="8"
|
||||||
HorizontalAlignment="Right" Margin="0,4,0,0">
|
HorizontalAlignment="Right" Margin="0,4,0,0">
|
||||||
<Button Content="Cancel" Click="OnScheduleCancelClick" MinWidth="76"/>
|
<Button Classes="btn" Content="Cancel" Click="OnScheduleCancelClick" MinWidth="76"/>
|
||||||
<Button Content="Schedule" Classes="accent" Click="OnScheduleSetClick" MinWidth="76"/>
|
<Button Content="Schedule" Classes="accent" Click="OnScheduleSetClick" MinWidth="76"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -243,5 +245,36 @@
|
|||||||
</Flyout>
|
</Flyout>
|
||||||
</Button.Flyout>
|
</Button.Flyout>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<!-- Hidden reject-feedback anchor (its Flyout is shown from the Reject button) -->
|
||||||
|
<Button Grid.Row="1" x:Name="RejectAnchor"
|
||||||
|
Width="1" Height="1" Opacity="0"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||||
|
IsHitTestVisible="False" Focusable="False">
|
||||||
|
<Button.Flyout>
|
||||||
|
<Flyout Placement="Bottom" ShowMode="Standard">
|
||||||
|
<Border Background="{DynamicResource Surface2Brush}"
|
||||||
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
|
BorderThickness="1" CornerRadius="10"
|
||||||
|
Padding="16" Width="320">
|
||||||
|
<StackPanel Spacing="12">
|
||||||
|
<TextBlock Classes="title" Text="Reject & re-run"/>
|
||||||
|
<StackPanel Spacing="6">
|
||||||
|
<TextBlock Classes="eyebrow" Text="FEEDBACK FOR THE AGENT"
|
||||||
|
Foreground="{DynamicResource TextDimBrush}" Opacity="0.6"/>
|
||||||
|
<TextBox x:Name="RejectFeedback"
|
||||||
|
AcceptsReturn="True" TextWrapping="Wrap"
|
||||||
|
MinHeight="80" PlaceholderText="What should the agent fix?"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8"
|
||||||
|
HorizontalAlignment="Right" Margin="0,4,0,0">
|
||||||
|
<Button Classes="btn" Content="Cancel" Click="OnRejectCancelClick" MinWidth="76"/>
|
||||||
|
<Button Content="Re-run" Classes="accent" Click="OnRejectConfirmClick" MinWidth="76"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Flyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -88,6 +88,43 @@ public partial class TaskRowView : UserControl
|
|||||||
await vm.SetStatusOnRowAsync(row, status);
|
await vm.SetStatusOnRowAsync(row, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void OnApproveReviewClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
|
||||||
|
await vm.ApproveReviewCommand.ExecuteAsync(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnParkReviewClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
|
||||||
|
await vm.RejectReviewToIdleCommand.ExecuteAsync(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnCancelReviewClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
|
||||||
|
await vm.CancelReviewCommand.ExecuteAsync(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRejectReviewClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is not TaskRowViewModel) return;
|
||||||
|
RejectFeedback.Text = "";
|
||||||
|
RejectAnchor.Flyout?.ShowAt(RejectAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnRejectConfirmClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
RejectAnchor.Flyout?.Hide();
|
||||||
|
if (DataContext is not TaskRowViewModel row || FindTasksVm() is not { } vm) return;
|
||||||
|
var feedback = RejectFeedback.Text ?? "";
|
||||||
|
if (string.IsNullOrWhiteSpace(feedback)) return;
|
||||||
|
await vm.RejectReviewToQueueAsync(row, feedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRejectCancelClick(object? sender, RoutedEventArgs e)
|
||||||
|
=> RejectAnchor.Flyout?.Hide();
|
||||||
|
|
||||||
private void OnScheduleForClick(object? sender, RoutedEventArgs e)
|
private void OnScheduleForClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not TaskRowViewModel row) return;
|
if (DataContext is not TaskRowViewModel row) return;
|
||||||
|
|||||||
@@ -9,14 +9,13 @@
|
|||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<Border DockPanel.Dock="Top" Classes="island-header">
|
<Border DockPanel.Dock="Top" Classes="island-header">
|
||||||
<Grid ColumnDefinitions="*,Auto" Margin="18,14">
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<StackPanel Grid.Column="0" Spacing="4">
|
<StackPanel Grid.Column="0" Spacing="4">
|
||||||
<TextBlock Classes="eyebrow" Text="{Binding HeaderEyebrow}"/>
|
<TextBlock Classes="eyebrow" Text="{Binding HeaderEyebrow}"/>
|
||||||
<TextBlock FontFamily="{DynamicResource SansFamily}" FontSize="24"
|
<TextBlock Classes="display"
|
||||||
FontWeight="SemiBold" Foreground="{DynamicResource TextBrush}"
|
|
||||||
Text="{Binding HeaderTitle}"
|
Text="{Binding HeaderTitle}"
|
||||||
TextTrimming="CharacterEllipsis"/>
|
TextTrimming="CharacterEllipsis"/>
|
||||||
<TextBlock FontFamily="{DynamicResource MonoFamily}" FontSize="11"
|
<TextBlock Classes="meta"
|
||||||
Foreground="{DynamicResource TextMuteBrush}"
|
Foreground="{DynamicResource TextMuteBrush}"
|
||||||
Text="{Binding Subtitle}"
|
Text="{Binding Subtitle}"
|
||||||
TextTrimming="CharacterEllipsis"/>
|
TextTrimming="CharacterEllipsis"/>
|
||||||
@@ -127,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">
|
||||||
|
|||||||
@@ -38,24 +38,18 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||||
<!-- CLAUDEDO label -->
|
<!-- CLAUDEDO label -->
|
||||||
<TextBlock Classes="title-brand-name"
|
<TextBlock Classes="title-brand-name eyebrow"
|
||||||
Text="CLAUDEDO"
|
Text="CLAUDEDO"
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
|
||||||
FontSize="11"
|
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
LetterSpacing="1.4"
|
LetterSpacing="1.4"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<!-- separator dot -->
|
<!-- separator dot -->
|
||||||
<TextBlock Text="·"
|
<TextBlock Classes="meta"
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
Text="·"
|
||||||
FontSize="11"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}"
|
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<!-- current list name -->
|
<!-- current list name -->
|
||||||
<TextBlock Text="{Binding Lists.SelectedList.Name, Converter={StaticResource UpperCase}}"
|
<TextBlock Classes="meta"
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
Text="{Binding Lists.SelectedList.Name, Converter={StaticResource UpperCase}}"
|
||||||
FontSize="11"
|
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
|
||||||
LetterSpacing="1.4"
|
LetterSpacing="1.4"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<!-- Help menu -->
|
<!-- Help menu -->
|
||||||
@@ -63,7 +57,7 @@
|
|||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
<MenuItem Header="Help"
|
<MenuItem Header="Help"
|
||||||
FontSize="11"
|
FontSize="{StaticResource FontSizeMono}"
|
||||||
Foreground="{DynamicResource TextDimBrush}">
|
Foreground="{DynamicResource TextDimBrush}">
|
||||||
<MenuItem Header="Check for updates"
|
<MenuItem Header="Check for updates"
|
||||||
Command="{Binding CheckForUpdatesCommand}"/>
|
Command="{Binding CheckForUpdatesCommand}"/>
|
||||||
@@ -88,7 +82,7 @@
|
|||||||
<PathIcon Data="{StaticResource Icon.WinMin}" Width="10" Height="10"/>
|
<PathIcon Data="{StaticResource Icon.WinMin}" Width="10" Height="10"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Classes="title-ctrl" Click="OnToggleMax">
|
<Button Classes="title-ctrl" Click="OnToggleMax">
|
||||||
<PathIcon Data="{StaticResource Icon.WinMax}" Width="10" Height="10"/>
|
<PathIcon x:Name="MaxIcon" Data="{StaticResource Icon.WinMax}" Width="10" Height="10"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Classes="title-ctrl close" Click="OnClose">
|
<Button Classes="title-ctrl close" Click="OnClose">
|
||||||
<PathIcon Data="{StaticResource Icon.WinClose}" Width="10" Height="10"/>
|
<PathIcon Data="{StaticResource Icon.WinClose}" Width="10" Height="10"/>
|
||||||
@@ -107,21 +101,20 @@
|
|||||||
IsVisible="{Binding IsUpdateBannerVisible}">
|
IsVisible="{Binding IsUpdateBannerVisible}">
|
||||||
<Grid ColumnDefinitions="*,Auto,Auto">
|
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
VerticalAlignment="Center"
|
Classes="body"
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
VerticalAlignment="Center">
|
||||||
FontSize="12">
|
|
||||||
<Run Text="Update available: v"/>
|
<Run Text="Update available: v"/>
|
||||||
<Run Text="{Binding UpdateCheck.CurrentVersion}"/>
|
<Run Text="{Binding UpdateCheck.CurrentVersion}"/>
|
||||||
<Run Text=" → v"/>
|
<Run Text=" → v"/>
|
||||||
<Run Text="{Binding UpdateBannerLatestVersion}"/>
|
<Run Text="{Binding UpdateBannerLatestVersion}"/>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<Button Grid.Column="1"
|
<Button Grid.Column="1"
|
||||||
|
Classes="btn"
|
||||||
Margin="0,0,8,0"
|
Margin="0,0,8,0"
|
||||||
Padding="10,3"
|
|
||||||
Content="Update now"
|
Content="Update now"
|
||||||
Command="{Binding UpdateNowCommand}"/>
|
Command="{Binding UpdateNowCommand}"/>
|
||||||
<Button Grid.Column="2"
|
<Button Grid.Column="2"
|
||||||
Padding="10,3"
|
Classes="btn"
|
||||||
Content="Dismiss"
|
Content="Dismiss"
|
||||||
Command="{Binding DismissBannerCommand}"/>
|
Command="{Binding DismissBannerCommand}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -129,11 +122,10 @@
|
|||||||
|
|
||||||
<!-- Inline update status (appears at right of banner row when no banner) -->
|
<!-- Inline update status (appears at right of banner row when no banner) -->
|
||||||
<TextBlock Grid.Row="1"
|
<TextBlock Grid.Row="1"
|
||||||
|
Classes="meta"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="0,0,14,0"
|
Margin="0,0,14,0"
|
||||||
FontSize="11"
|
|
||||||
Foreground="{DynamicResource TextFaintBrush}"
|
|
||||||
Text="{Binding InlineUpdateStatus}"
|
Text="{Binding InlineUpdateStatus}"
|
||||||
IsVisible="{Binding InlineUpdateStatus, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
IsVisible="{Binding InlineUpdateStatus, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||||
|
|
||||||
@@ -194,39 +186,39 @@
|
|||||||
BorderBrush="{DynamicResource LineBrush}"
|
BorderBrush="{DynamicResource LineBrush}"
|
||||||
BorderThickness="0,1,0,0">
|
BorderThickness="0,1,0,0">
|
||||||
<DockPanel LastChildFill="True" Margin="14,0">
|
<DockPanel LastChildFill="True" Margin="14,0">
|
||||||
<!-- Left: connection pill -->
|
<!-- Left: connection pill (click to open worker help) -->
|
||||||
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" Spacing="7"
|
<Button DockPanel.Dock="Left"
|
||||||
VerticalAlignment="Center">
|
Command="{Binding OpenWorkerConnectionHelpCommand}"
|
||||||
<Ellipse Width="7" Height="7" Fill="#4CAF50"
|
Background="Transparent" BorderThickness="0" Padding="0"
|
||||||
|
Cursor="Hand" VerticalAlignment="Center">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
||||||
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusRunningBrush}"
|
||||||
IsVisible="{Binding Worker.IsConnected}"/>
|
IsVisible="{Binding Worker.IsConnected}"/>
|
||||||
<Ellipse Width="7" Height="7" Fill="#FFA726"
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusReviewBrush}"
|
||||||
IsVisible="{Binding Worker.IsReconnecting}"/>
|
IsVisible="{Binding Worker.IsReconnecting}"/>
|
||||||
<Ellipse Width="7" Height="7" Fill="#EF5350"
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusErrorBrush}"
|
||||||
IsVisible="{Binding IsOffline}"/>
|
IsVisible="{Binding IsOffline}"/>
|
||||||
<TextBlock Text="{Binding ConnectionText, Converter={StaticResource UpperCase}}"
|
<TextBlock Classes="eyebrow"
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
Text="{Binding ConnectionText, Converter={StaticResource UpperCase}}"
|
||||||
FontSize="10"
|
|
||||||
LetterSpacing="1.4"
|
LetterSpacing="1.4"
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<!-- Right: worker log line -->
|
<!-- Right: worker log line -->
|
||||||
<TextBlock DockPanel.Dock="Right"
|
<TextBlock DockPanel.Dock="Right"
|
||||||
|
Classes="meta"
|
||||||
Text="{Binding WorkerLogText}"
|
Text="{Binding WorkerLogText}"
|
||||||
IsVisible="{Binding IsWorkerLogVisible}"
|
IsVisible="{Binding IsWorkerLogVisible}"
|
||||||
Foreground="{Binding WorkerLogLevel, Converter={StaticResource WorkerLogLevelToBrush}}"
|
Foreground="{Binding WorkerLogLevel, Converter={StaticResource WorkerLogLevelToBrush}}"
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
|
||||||
FontSize="10"
|
|
||||||
LetterSpacing="1.4"
|
LetterSpacing="1.4"
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
|
|
||||||
<!-- Right: prime status notification -->
|
<!-- Right: prime status notification -->
|
||||||
<TextBlock DockPanel.Dock="Right"
|
<TextBlock DockPanel.Dock="Right"
|
||||||
|
Classes="meta"
|
||||||
Text="{Binding PrimeStatus}"
|
Text="{Binding PrimeStatus}"
|
||||||
Foreground="{DynamicResource TextDimBrush}"
|
|
||||||
FontSize="11"
|
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="12,0,0,0"
|
Margin="12,0,0,0"
|
||||||
IsVisible="{Binding PrimeStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{Binding PrimeStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ public partial class MainWindow : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
KeyDown += OnWindowKeyDown;
|
KeyDown += OnWindowKeyDown;
|
||||||
DataContextChanged += OnDataContextChanged;
|
DataContextChanged += OnDataContextChanged;
|
||||||
|
UpdateMaxIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
if (change.Property == WindowStateProperty)
|
||||||
|
UpdateMaxIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMaxIcon()
|
||||||
|
{
|
||||||
|
var key = WindowState == WindowState.Maximized ? "Icon.WinRestore" : "Icon.WinMax";
|
||||||
|
if (this.TryFindResource(key, out var geometry) && geometry is Geometry g)
|
||||||
|
MaxIcon.Data = g;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDataContextChanged(object? sender, EventArgs e)
|
private void OnDataContextChanged(object? sender, EventArgs e)
|
||||||
@@ -68,6 +83,12 @@ public partial class MainWindow : Window
|
|||||||
modal.CloseAction = () => dlg.Close();
|
modal.CloseAction = () => dlg.Close();
|
||||||
await dlg.ShowDialog(this);
|
await dlg.ShowDialog(this);
|
||||||
};
|
};
|
||||||
|
vm.ShowWorkerConnectionModal = async (connVm) =>
|
||||||
|
{
|
||||||
|
var dlg = new WorkerConnectionModalView { DataContext = connVm };
|
||||||
|
connVm.CloseAction = () => dlg.Close();
|
||||||
|
await dlg.ShowDialog(this);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
||||||
|
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
||||||
x:Class="ClaudeDo.Ui.Views.Modals.AboutModalView"
|
x:Class="ClaudeDo.Ui.Views.Modals.AboutModalView"
|
||||||
x:DataType="vm:AboutModalViewModel"
|
x:DataType="vm:AboutModalViewModel"
|
||||||
Title="About ClaudeDo"
|
Title="About ClaudeDo"
|
||||||
@@ -12,38 +13,24 @@
|
|||||||
<Window.KeyBindings>
|
<Window.KeyBindings>
|
||||||
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||||
</Window.KeyBindings>
|
</Window.KeyBindings>
|
||||||
<Border BorderBrush="{DynamicResource LineBrush}" BorderThickness="1">
|
|
||||||
<Grid RowDefinitions="36,*,52">
|
<ctl:ModalShell Title="ABOUT" CloseCommand="{Binding CloseCommand}">
|
||||||
<Border Grid.Row="0" Background="{DynamicResource DeepBrush}"
|
<!-- Body -->
|
||||||
BorderBrush="{DynamicResource LineBrush}" BorderThickness="0,0,0,1">
|
<ScrollViewer Padding="20,16" HorizontalScrollBarVisibility="Disabled">
|
||||||
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
|
|
||||||
<TextBlock Text="ABOUT" FontFamily="{DynamicResource MonoFont}" FontSize="11"
|
|
||||||
LetterSpacing="1.4" Foreground="{DynamicResource TextBrush}" VerticalAlignment="Center"/>
|
|
||||||
<Button Grid.Column="1" Classes="icon-btn" Content="✕" FontSize="12"
|
|
||||||
Command="{Binding CloseCommand}" VerticalAlignment="Center"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
<ScrollViewer Grid.Row="1" Padding="20,16" HorizontalScrollBarVisibility="Disabled">
|
|
||||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto" ColumnDefinitions="90,*,Auto" RowSpacing="10" ColumnSpacing="8">
|
<Grid RowDefinitions="Auto,Auto,Auto,Auto" ColumnDefinitions="90,*,Auto" RowSpacing="10" ColumnSpacing="8">
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Version" Foreground="{DynamicResource TextDimBrush}" VerticalAlignment="Center"/>
|
<TextBlock Classes="meta" Grid.Row="0" Grid.Column="0" Text="Version" VerticalAlignment="Center"/>
|
||||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding AppVersion}" FontFamily="{DynamicResource MonoFont}" VerticalAlignment="Center"/>
|
<TextBlock Classes="meta" Grid.Row="0" Grid.Column="1" Text="{Binding AppVersion}" VerticalAlignment="Center"/>
|
||||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Data" Foreground="{DynamicResource TextDimBrush}" VerticalAlignment="Center"/>
|
<TextBlock Classes="meta" Grid.Row="1" Grid.Column="0" Text="Data" VerticalAlignment="Center"/>
|
||||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding DataFolderPath}" FontFamily="{DynamicResource MonoFont}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
<TextBlock Classes="path-mono" Grid.Row="1" Grid.Column="1" Text="{Binding DataFolderPath}" VerticalAlignment="Center"/>
|
||||||
<Button Grid.Row="1" Grid.Column="2" Content="Open" Command="{Binding OpenPathCommand}" CommandParameter="{Binding DataFolderPath}"/>
|
<Button Classes="btn" Grid.Row="1" Grid.Column="2" Content="Open" Command="{Binding OpenPathCommand}" CommandParameter="{Binding DataFolderPath}"/>
|
||||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Logs" Foreground="{DynamicResource TextDimBrush}" VerticalAlignment="Center"/>
|
<TextBlock Classes="meta" Grid.Row="2" Grid.Column="0" Text="Logs" VerticalAlignment="Center"/>
|
||||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding LogsFolderPath}" FontFamily="{DynamicResource MonoFont}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
<TextBlock Classes="path-mono" Grid.Row="2" Grid.Column="1" Text="{Binding LogsFolderPath}" VerticalAlignment="Center"/>
|
||||||
<Button Grid.Row="2" Grid.Column="2" Content="Open" Command="{Binding OpenPathCommand}" CommandParameter="{Binding LogsFolderPath}"/>
|
<Button Classes="btn" Grid.Row="2" Grid.Column="2" Content="Open" Command="{Binding OpenPathCommand}" CommandParameter="{Binding LogsFolderPath}"/>
|
||||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="Config" Foreground="{DynamicResource TextDimBrush}" VerticalAlignment="Center"/>
|
<TextBlock Classes="meta" Grid.Row="3" Grid.Column="0" Text="Config" VerticalAlignment="Center"/>
|
||||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding WorkerConfigPath}" FontFamily="{DynamicResource MonoFont}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
<TextBlock Classes="path-mono" Grid.Row="3" Grid.Column="1" Text="{Binding WorkerConfigPath}" VerticalAlignment="Center"/>
|
||||||
<Button Grid.Row="3" Grid.Column="2" Content="Open" Command="{Binding OpenPathCommand}" CommandParameter="{Binding WorkerConfigPath}"/>
|
<Button Classes="btn" Grid.Row="3" Grid.Column="2" Content="Open" Command="{Binding OpenPathCommand}" CommandParameter="{Binding WorkerConfigPath}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
<Border Grid.Row="2" Background="{DynamicResource DeepBrush}"
|
|
||||||
BorderBrush="{DynamicResource LineBrush}" BorderThickness="0,1,0,0">
|
</ctl:ModalShell>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="16,0">
|
|
||||||
<Button Content="Close" Command="{Binding CloseCommand}" MinWidth="90"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
||||||
|
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
||||||
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="{StaticResource SurfaceBrush}">
|
Background="{DynamicResource SurfaceBrush}">
|
||||||
|
|
||||||
<Window.KeyBindings>
|
<Window.KeyBindings>
|
||||||
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||||
@@ -17,10 +20,10 @@
|
|||||||
<Window.Styles>
|
<Window.Styles>
|
||||||
<!-- diff line row tints via Tag selector (compiled-binding-friendly) -->
|
<!-- diff line row tints via Tag selector (compiled-binding-friendly) -->
|
||||||
<Style Selector="Border.diff-line[Tag=add]">
|
<Style Selector="Border.diff-line[Tag=add]">
|
||||||
<Setter Property="Background" Value="#1A4A6B4A"/>
|
<Setter Property="Background" Value="{StaticResource RunningTintBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.diff-line[Tag=del]">
|
<Style Selector="Border.diff-line[Tag=del]">
|
||||||
<Setter Property="Background" Value="#1AC87060"/>
|
<Setter Property="Background" Value="{StaticResource ErrorTintBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.diff-line[Tag=ctx]">
|
<Style Selector="Border.diff-line[Tag=ctx]">
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
@@ -45,44 +48,20 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Window.Styles>
|
</Window.Styles>
|
||||||
|
|
||||||
<!-- Outer container — rectangular so the OS window rectangle stays filled (no black corners) -->
|
<ctl:ModalShell Title="DIFF" CloseCommand="{Binding CloseCommand}">
|
||||||
<Border Background="{StaticResource SurfaceBrush}"
|
<ctl:ModalShell.Footer>
|
||||||
BorderBrush="{StaticResource LineBrush}"
|
<StackPanel Orientation="Horizontal" Spacing="8"
|
||||||
BorderThickness="1">
|
HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||||
<Grid RowDefinitions="36,*">
|
<Button Classes="btn" Content="Merge…" Command="{Binding MergeCommand}"/>
|
||||||
|
|
||||||
<!-- Title bar / drag handle -->
|
|
||||||
<Border Grid.Row="0"
|
|
||||||
x:Name="TitleBar"
|
|
||||||
Background="{StaticResource Surface2Brush}"
|
|
||||||
BorderBrush="{StaticResource LineBrush}"
|
|
||||||
BorderThickness="0,0,0,1"
|
|
||||||
PointerPressed="TitleBar_PointerPressed">
|
|
||||||
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
|
|
||||||
<TextBlock Text="Diff" VerticalAlignment="Center"
|
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
|
||||||
FontSize="12"
|
|
||||||
Foreground="{StaticResource TextDimBrush}"/>
|
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
|
|
||||||
<Button Content="Merge…"
|
|
||||||
Command="{Binding MergeCommand}"
|
|
||||||
Margin="0,0,4,0" />
|
|
||||||
<Button Classes="icon-btn"
|
|
||||||
Content="✕"
|
|
||||||
FontSize="12"
|
|
||||||
Command="{Binding CloseCommand}" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</ctl:ModalShell.Footer>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Body: sidebar + diff content -->
|
<!-- Body: sidebar + diff content -->
|
||||||
<Grid Grid.Row="1" ColumnDefinitions="240,*">
|
<Grid ColumnDefinitions="240,*">
|
||||||
|
|
||||||
<!-- File sidebar -->
|
<!-- File sidebar -->
|
||||||
<Border Grid.Column="0"
|
<Border Grid.Column="0"
|
||||||
BorderBrush="{StaticResource LineBrush}"
|
Classes="sidebar-pane">
|
||||||
BorderThickness="0,0,1,0"
|
|
||||||
Background="{StaticResource DeepBrush}">
|
|
||||||
<ListBox ItemsSource="{Binding Files}"
|
<ListBox ItemsSource="{Binding Files}"
|
||||||
SelectedItem="{Binding SelectedFile, Mode=TwoWay}"
|
SelectedItem="{Binding SelectedFile, Mode=TwoWay}"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
@@ -92,23 +71,16 @@
|
|||||||
<DataTemplate x:DataType="vm:DiffFileViewModel">
|
<DataTemplate x:DataType="vm:DiffFileViewModel">
|
||||||
<Border Padding="10,8" Background="Transparent">
|
<Border Padding="10,8" Background="Transparent">
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Text="{Binding Path}"
|
<TextBlock Classes="path-mono" Text="{Binding Path}"
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
|
||||||
FontSize="11"
|
|
||||||
Foreground="{StaticResource TextDimBrush}"
|
|
||||||
TextTrimming="PrefixCharacterEllipsis"/>
|
TextTrimming="PrefixCharacterEllipsis"/>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
<Border Classes="chip" Padding="5,2">
|
<Border Classes="chip" Padding="5,2">
|
||||||
<TextBlock Foreground="{StaticResource MossBrightBrush}"
|
<TextBlock Foreground="{DynamicResource MossBrightBrush}"
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
|
||||||
FontSize="10"
|
|
||||||
Text="{Binding Additions, StringFormat='+{0}'}"/>
|
Text="{Binding Additions, StringFormat='+{0}'}"/>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Classes="chip" Padding="5,2">
|
<Border Classes="chip" Padding="5,2">
|
||||||
<TextBlock Foreground="{StaticResource BloodBrush}"
|
<TextBlock Foreground="{DynamicResource BloodBrush}"
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
Text="{Binding Deletions, StringFormat='−{0}'}"/>
|
||||||
FontSize="10"
|
|
||||||
Text="{Binding Deletions, StringFormat='\u2212{0}'}"/>
|
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -119,14 +91,11 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Diff content -->
|
<!-- Diff content -->
|
||||||
<Grid Grid.Column="1" Background="{StaticResource VoidBrush}">
|
<Grid Grid.Column="1" Background="{DynamicResource VoidBrush}">
|
||||||
<TextBlock Text="{Binding StatusMessage}"
|
<TextBlock Classes="body" Text="{Binding StatusMessage}"
|
||||||
IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"/>
|
||||||
Foreground="{StaticResource TextDimBrush}"
|
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
|
||||||
FontSize="12"/>
|
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
||||||
VerticalScrollBarVisibility="Auto">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<ItemsControl ItemsSource="{Binding SelectedFile.Lines}">
|
<ItemsControl ItemsSource="{Binding SelectedFile.Lines}">
|
||||||
@@ -140,32 +109,26 @@
|
|||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
Text="{Binding OldNo}"
|
Text="{Binding OldNo}"
|
||||||
Classes="diff-lineno"
|
Classes="diff-lineno"
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
|
||||||
FontSize="11"
|
|
||||||
Foreground="{StaticResource TextFaintBrush}"
|
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Margin="0,0,8,0"/>
|
Margin="0,0,8,0"/>
|
||||||
<!-- New line number -->
|
<!-- New line number -->
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Text="{Binding NewNo}"
|
Text="{Binding NewNo}"
|
||||||
Classes="diff-lineno"
|
Classes="diff-lineno"
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
|
||||||
FontSize="11"
|
|
||||||
Foreground="{StaticResource TextFaintBrush}"
|
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Margin="0,0,8,0"/>
|
Margin="0,0,8,0"/>
|
||||||
<!-- Sign -->
|
<!-- Sign -->
|
||||||
<TextBlock Grid.Column="2"
|
<TextBlock Grid.Column="2"
|
||||||
Classes="diff-sign"
|
Classes="diff-sign"
|
||||||
Text="{Binding Sign}"
|
Text="{Binding Sign}"
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
FontFamily="{DynamicResource MonoFont}"
|
||||||
FontSize="11"/>
|
FontSize="{StaticResource FontSizeMono}"/>
|
||||||
<!-- Line text -->
|
<!-- Line text -->
|
||||||
<TextBlock Grid.Column="3"
|
<TextBlock Grid.Column="3"
|
||||||
Classes="diff-text"
|
Classes="diff-text"
|
||||||
Text="{Binding Text}"
|
Text="{Binding Text}"
|
||||||
FontFamily="{StaticResource MonoFamily}"
|
FontFamily="{DynamicResource MonoFont}"
|
||||||
FontSize="11"
|
FontSize="{StaticResource FontSizeMono}"
|
||||||
TextWrapping="NoWrap"/>
|
TextWrapping="NoWrap"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -175,6 +138,5 @@
|
|||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</ctl:ModalShell>
|
||||||
</Border>
|
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Avalonia;
|
|||||||
using Avalonia.Animation;
|
using Avalonia.Animation;
|
||||||
using Avalonia.Animation.Easings;
|
using Avalonia.Animation.Easings;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using ClaudeDo.Ui.ViewModels.Modals;
|
using ClaudeDo.Ui.ViewModels.Modals;
|
||||||
@@ -43,10 +42,4 @@ public partial class DiffModalView : Window
|
|||||||
Opacity = 1;
|
Opacity = 1;
|
||||||
RenderTransform = new ScaleTransform(1.0, 1.0);
|
RenderTransform = new ScaleTransform(1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TitleBar_PointerPressed(object? sender, PointerPressedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
|
||||||
BeginMoveDrag(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user