Show only the day's docket on the Today board and add ad hoc Now workouts

- Due-filter schedules: daily always, fixed days by weekday, one-offs on
  their date; rest days get an empty state
- Workouts with no due schedule row (ad hoc or off-day starts) now render
  as their own board rows
- The + sheet is now "New Workout" with a "When" picker; "Now" (offered
  when adding on today) skips the schedule and starts the workout directly
- Remove the times-per-week scheduling mode everywhere (enum, document,
  entity, mappers, planner, seeds, tests)
This commit is contained in:
2026-07-11 11:09:11 -04:00
parent 9dc9283b4c
commit 7cb2d6da26
14 changed files with 231 additions and 199 deletions
+1 -52
View File
@@ -43,14 +43,13 @@ struct ProgressPlannerTests {
goal: GoalKind? = .strength,
recurrence: ScheduleRecurrence,
weekdays: [Int]? = nil,
timesPerWeek: Int? = nil,
onceDate: Date? = nil,
createdAt: Date,
routineID: String = "R-1",
routineName: String = "Push Day"
) -> ScheduleFacts {
ScheduleFacts(id: id, goal: goal, recurrence: recurrence, weekdays: weekdays,
timesPerWeek: timesPerWeek, onceDate: onceDate, createdAt: createdAt,
onceDate: onceDate, createdAt: createdAt,
routineID: routineID, routineName: routineName)
}
@@ -149,37 +148,6 @@ struct ProgressPlannerTests {
#expect(current?.met == 2)
}
// MARK: - weekMark: frequency
@Test func weekMarkFrequency() {
let now = day("2026-07-08") // Wednesday; current week Mon 07-06 Sun 07-12
let currentWeek = day("2026-07-06")
// Expected is the full weekly target even mid-week NOT clamped down to the
// 3 days that have elapsed so far.
let twicePerWeek = schedule(recurrence: .frequency, timesPerWeek: 2, createdAt: day("2026-06-01"))
let untouched = ProgressPlanner.weekMark(schedule: twicePerWeek, doneDays: [],
weekStart: currentWeek, calendar: cal, now: now)
#expect(untouched?.expected == 2)
#expect(untouched?.met == 0)
// Met is capped at the target even when more sessions were logged.
let threeDone: Set<Date> = [day("2026-07-06"), day("2026-07-07"), day("2026-07-08")]
let overMet = ProgressPlanner.weekMark(schedule: twicePerWeek, doneDays: threeDone,
weekStart: currentWeek, calendar: cal, now: now)
#expect(overMet?.met == 2)
#expect(overMet?.expected == 2)
#expect(overMet?.hit == true)
// Target clamps to the days the schedule existed in its creation week: created
// Wednesday leaves only 5 days (WedSun) in that week, so a 7x/week target
// clamps down to 5, not 7.
let createdMidWeek = schedule(recurrence: .frequency, timesPerWeek: 7, createdAt: day("2026-07-08"))
let clamped = ProgressPlanner.weekMark(schedule: createdMidWeek, doneDays: [],
weekStart: currentWeek, calendar: cal, now: day("2026-07-12"))
#expect(clamped?.expected == 5)
}
// MARK: - weekMark: once
@Test func weekMarkOnce() {
@@ -247,25 +215,6 @@ struct ProgressPlannerTests {
weekStart: weekStart, calendar: cal, now: now))
}
@Test func currentWeekBrokenFrequencyDaysRemainingVsRequirement() {
let twicePerWeek = schedule(recurrence: .frequency, timesPerWeek: 2, createdAt: day("2026-06-01"))
let weekStart = day("2026-07-06")
let oneDone: Set<Date> = [day("2026-07-07")]
// Saturday: 2 days left (Sat, Sun). 1 done, 1 still needed achievable.
#expect(!ProgressPlanner.currentWeekBroken(schedule: twicePerWeek, doneDays: oneDone,
weekStart: weekStart, calendar: cal, now: day("2026-07-11")))
// Sunday: only 1 day left (today itself). 1 done, 1 still needed needs
// exactly 1 1 day left, so still achievable.
#expect(!ProgressPlanner.currentWeekBroken(schedule: twicePerWeek, doneDays: oneDone,
weekStart: weekStart, calendar: cal, now: day("2026-07-12")))
// Sunday again, but nothing done yet: 2 still needed with only 1 day left broken.
#expect(ProgressPlanner.currentWeekBroken(schedule: twicePerWeek, doneDays: [],
weekStart: weekStart, calendar: cal, now: day("2026-07-12")))
}
@Test func currentWeekBrokenFixedDaysCatchUpDaysRemainingNotBroken() {
// Mon/Wed/Fri required. Monday (a fixed day) was missed, but it's only
// Tuesday catch-up days (Wed, Fri, or any other day this week) still remain.