diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json
index de673041..a7171a62 100644
--- a/src/ClaudeDo.Localization/locales/de.json
+++ b/src/ClaudeDo.Localization/locales/de.json
@@ -75,6 +75,8 @@
"addSchedule": "+ Zeitplan hinzufügen",
"removeScheduleTip": "Zeitplan entfernen",
"dailyPrepMaxTasks": "Max. Aufgaben pro Tag",
+ "promptOverrideLabel": "Prompt-Zusatz",
+ "promptOverridePlaceholder": "Zusätzliche Anweisungen, die für diesen Zeitplan an den Prime-Prompt angehängt werden (optional)",
"dayMo": "Mo",
"dayTu": "Di",
"dayWe": "Mi",
diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json
index d1b70106..037c98e0 100644
--- a/src/ClaudeDo.Localization/locales/en.json
+++ b/src/ClaudeDo.Localization/locales/en.json
@@ -75,6 +75,8 @@
"addSchedule": "+ Add schedule",
"removeScheduleTip": "Remove schedule",
"dailyPrepMaxTasks": "Max tasks per day",
+ "promptOverrideLabel": "Prompt addition",
+ "promptOverridePlaceholder": "Extra instructions appended to the daily prep prompt for this schedule (optional)",
"dayMo": "Mo",
"dayTu": "Tu",
"dayWe": "We",
diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeScheduleRowViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeScheduleRowViewModel.cs
index fe7e140c..b321253a 100644
--- a/src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeScheduleRowViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Modals/Settings/PrimeScheduleRowViewModel.cs
@@ -21,6 +21,7 @@ public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
[ObservableProperty] private bool _sunday;
[ObservableProperty] private TimeSpan _timeOfDay;
[ObservableProperty] private DateTimeOffset? _lastRunAt;
+ [ObservableProperty] private string? _promptOverride;
public string LastRunLabel => LastRunAt is { } v ? v.LocalDateTime.ToString("g") : "—";
@@ -58,6 +59,7 @@ public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
Sunday = (dto.Days & Sun) != 0;
TimeOfDay = dto.TimeOfDay;
LastRunAt = dto.LastRunAt;
+ PromptOverride = dto.PromptOverride;
}
public int DaysMask()
@@ -74,5 +76,6 @@ public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
}
public PrimeScheduleDto ToDto() =>
- new(Id, DaysMask(), TimeOfDay, Enabled, LastRunAt, null);
+ new(Id, DaysMask(), TimeOfDay, Enabled, LastRunAt,
+ string.IsNullOrWhiteSpace(PromptOverride) ? null : PromptOverride);
}
diff --git a/src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml b/src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml
index d42970ed..dd8975ce 100644
--- a/src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml
+++ b/src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml
@@ -354,28 +354,35 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ClaudeDo.Worker/Prime/DailyPrepPrompt.cs b/src/ClaudeDo.Worker/Prime/DailyPrepPrompt.cs
index 89ebd6a7..b0789682 100644
--- a/src/ClaudeDo.Worker/Prime/DailyPrepPrompt.cs
+++ b/src/ClaudeDo.Worker/Prime/DailyPrepPrompt.cs
@@ -16,12 +16,18 @@ public static class DailyPrepPrompt
"--allowedTools", CandidatesTool, SetMyDayTool,
];
- public static string BuildPrompt(int maxTasks, DateOnly today) =>
- ClaudeDo.Data.PromptFiles.Render(
+ public static string BuildPrompt(int maxTasks, DateOnly today, string? promptOverride = null)
+ {
+ var prompt = ClaudeDo.Data.PromptFiles.Render(
ClaudeDo.Data.PromptKind.DailyPrep,
new Dictionary
{
["date"] = today.ToString("yyyy-MM-dd"),
["maxTasks"] = maxTasks.ToString(),
});
+
+ // Additive by design: a schedule's override is appended, never substituted, so a user
+ // can't accidentally break the mcp__claudedo__get_daily_prep_candidates/set_my_day flow.
+ return string.IsNullOrWhiteSpace(promptOverride) ? prompt : $"{prompt}\n\n{promptOverride}";
+ }
}
diff --git a/src/ClaudeDo.Worker/Prime/PrimeRunner.cs b/src/ClaudeDo.Worker/Prime/PrimeRunner.cs
index f7b7790b..35d46bb6 100644
--- a/src/ClaudeDo.Worker/Prime/PrimeRunner.cs
+++ b/src/ClaudeDo.Worker/Prime/PrimeRunner.cs
@@ -56,7 +56,7 @@ public sealed class PrimeRunner : IPrimeRunner
}
var today = DateOnly.FromDateTime(_clock.Now.LocalDateTime);
- var prompt = DailyPrepPrompt.BuildPrompt(maxTasks, today);
+ var prompt = DailyPrepPrompt.BuildPrompt(maxTasks, today, schedule.PromptOverride);
var args = DailyPrepPrompt.BuildArgs(MaxTurns);
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
diff --git a/tests/ClaudeDo.Data.Tests/PrimeScheduleRepositoryTests.cs b/tests/ClaudeDo.Data.Tests/PrimeScheduleRepositoryTests.cs
new file mode 100644
index 00000000..fc2d5f6e
--- /dev/null
+++ b/tests/ClaudeDo.Data.Tests/PrimeScheduleRepositoryTests.cs
@@ -0,0 +1,67 @@
+using ClaudeDo.Data.Models;
+using ClaudeDo.Data.Repositories;
+using Microsoft.EntityFrameworkCore;
+
+namespace ClaudeDo.Data.Tests;
+
+public sealed class PrimeScheduleRepositoryTests : IDisposable
+{
+ private readonly string _dbPath;
+ private readonly ClaudeDoDbContext _ctx;
+ private readonly PrimeScheduleRepository _repo;
+
+ public PrimeScheduleRepositoryTests()
+ {
+ _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_prime_{Guid.NewGuid():N}.db");
+ var options = new DbContextOptionsBuilder()
+ .UseSqlite($"Data Source={_dbPath}")
+ .Options;
+ _ctx = new ClaudeDoDbContext(options);
+ _ctx.Database.EnsureCreated();
+ _repo = new PrimeScheduleRepository(_ctx);
+ }
+
+ public void Dispose()
+ {
+ _ctx.Dispose();
+ foreach (var suffix in new[] { "", "-wal", "-shm" })
+ try { File.Delete(_dbPath + suffix); } catch { }
+ }
+
+ [Fact]
+ public async Task UpsertAsync_then_GetAsync_roundtrips_the_prompt_override()
+ {
+ var entity = new PrimeScheduleEntity { PromptOverride = "Also prioritize anything tagged #urgent." };
+
+ await _repo.UpsertAsync(entity);
+ var found = await _repo.GetAsync(entity.Id);
+
+ Assert.NotNull(found);
+ Assert.Equal("Also prioritize anything tagged #urgent.", found!.PromptOverride);
+ }
+
+ [Fact]
+ public async Task UpsertAsync_with_null_override_persists_as_null()
+ {
+ var entity = new PrimeScheduleEntity { PromptOverride = null };
+
+ await _repo.UpsertAsync(entity);
+ var found = await _repo.GetAsync(entity.Id);
+
+ Assert.NotNull(found);
+ Assert.Null(found!.PromptOverride);
+ }
+
+ [Fact]
+ public async Task UpsertAsync_updates_the_override_on_an_existing_row()
+ {
+ var entity = new PrimeScheduleEntity { PromptOverride = "old override" };
+ await _repo.UpsertAsync(entity);
+
+ entity.PromptOverride = "new override";
+ await _repo.UpsertAsync(entity);
+
+ var found = await _repo.GetAsync(entity.Id);
+ Assert.Equal("new override", found!.PromptOverride);
+ }
+}
diff --git a/tests/ClaudeDo.Worker.Tests/Prime/DailyPrepPromptTests.cs b/tests/ClaudeDo.Worker.Tests/Prime/DailyPrepPromptTests.cs
index febda784..4c1b7185 100644
--- a/tests/ClaudeDo.Worker.Tests/Prime/DailyPrepPromptTests.cs
+++ b/tests/ClaudeDo.Worker.Tests/Prime/DailyPrepPromptTests.cs
@@ -15,6 +15,43 @@ public class DailyPrepPromptTests
Assert.Contains("preparing my workday", prompt);
}
+ [Fact]
+ public void Build_prompt_without_override_matches_the_plain_render()
+ {
+ var today = new DateOnly(2026, 6, 3);
+ var expected = ClaudeDo.Data.PromptFiles.Render(
+ ClaudeDo.Data.PromptKind.DailyPrep,
+ new Dictionary { ["date"] = "2026-06-03", ["maxTasks"] = "5" });
+
+ var prompt = DailyPrepPrompt.BuildPrompt(maxTasks: 5, today: today);
+
+ Assert.Equal(expected, prompt);
+ }
+
+ [Fact]
+ public void Build_prompt_appends_override_as_an_additional_paragraph()
+ {
+ var today = new DateOnly(2026, 6, 3);
+ var baseline = DailyPrepPrompt.BuildPrompt(maxTasks: 5, today: today);
+
+ var prompt = DailyPrepPrompt.BuildPrompt(maxTasks: 5, today: today,
+ promptOverride: "Also prioritize anything tagged #urgent.");
+
+ Assert.StartsWith(baseline, prompt);
+ Assert.Contains("Also prioritize anything tagged #urgent.", prompt);
+ }
+
+ [Fact]
+ public void Build_prompt_treats_whitespace_only_override_as_absent()
+ {
+ var today = new DateOnly(2026, 6, 3);
+ var baseline = DailyPrepPrompt.BuildPrompt(maxTasks: 5, today: today);
+
+ var prompt = DailyPrepPrompt.BuildPrompt(maxTasks: 5, today: today, promptOverride: " ");
+
+ Assert.Equal(baseline, prompt);
+ }
+
[Fact]
public void Build_args_allows_only_the_two_tools()
{