docs: spec for recurring-weekday Prime schedules
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||||
Reference in New Issue
Block a user