5.0 KiB
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 singledays_of_week INTEGERcolumn. - 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/EndDategating (theEndDate < todayearly-out, theStartDate > todayclamps, and the bounds check inIsEligibleDay). IsEligibleDay(s, d)becomes: doess.Dayscontain the flag ford.DayOfWeek? (MapSystem.DayOfWeek→PrimeDays.)- The existing forward search (loops up to 8 days ahead) now simply walks to the next selected weekday.
alreadyFiredToday(comparesLastRunAt'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
ToggleButtons (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/WorkdaysOnlywith seven[ObservableProperty]bools:Monday…Sunday. - Constructor decomposes
dto.Daysinto the seven bools. ToDto()composes the seven bools back into theDaysint.
PrimeClaudeTabViewModel:
AddScheduledefault: Mon–Fri selected, time 07:00, enabled.Validate: replace theStartDate > EndDatecheck 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.