diff --git a/KisaniCal/ISSUES.md b/KisaniCal/ISSUES.md index ac4f555..c9af11d 100644 --- a/KisaniCal/ISSUES.md +++ b/KisaniCal/ISSUES.md @@ -1790,3 +1790,40 @@ Files: `KisaniCalWidgets/KisaniCalWidgets.swift`. Not build-verified (no iOS runtime here). To use: on the Lock Screen, tap the widget area → Add Widgets → Wenza → Progress Dots → long-press → Edit Widget → set Mode to This Day / This Week / This Year (This Month already configured). + +--- + +## KC-54 — Timed recurring tasks vanish from Today once their time passes + +**Status:** Fixed (not build-verified) +**Reported by:** User (screenshots: Calendar day view showed "Look into DPO +certificate" 11 AM and "Read Azure 104" 5 PM; neither appeared anywhere in the +Today tab — not in Today, not in Overdue, not in Completed) + +### Root cause +Two rules interacted badly for **recurring** tasks: +- `todayTasks` deliberately excludes timed tasks whose time has passed today + ("belongs in overdue, not today"). +- `overdueTasks` deliberately excludes ALL recurring tasks (`!recurs($0)`), + because it feeds `postpone`/`postponeAllOverdue`, which mutate a task's + `dueDate` directly by id — for a recurring task that field is the + **recurrence rule's anchor date**, not just "today's occurrence." Including + recurring occurrences there would let "Postpone All" shift/corrupt the whole + series. + +Net effect: once a recurring task's today-occurrence time passed, it was +excluded from Today (rule 1) but never picked up by Overdue (rule 2) — it fell +into a gap and disappeared from the Today tab entirely, while Calendar's day +view (`occurrences(on:)`, which has no time-of-day gating) kept showing it +normally. Not a display bug — real data was correctly stored, just not +surfaced. + +### Fix +`todayTasks`' "past time → excluded" rule now only applies to non-recurring +tasks (`!$0.isRecurring`). A recurring task's today-occurrence always stays in +Today regardless of time — matching what Calendar already shows, and avoiding +the postpone/recurrence-anchor risk entirely (recurring rows are never routed +through `overdueTasks`). One-line, additive filter change; `activeRows()` / +`occurrenceCopy` already preserve `isRecurring` on the occurrence copy. + +Files: `TaskItem.swift`. diff --git a/KisaniCal/Models/TaskItem.swift b/KisaniCal/Models/TaskItem.swift index 5c18b80..e117c6d 100644 --- a/KisaniCal/Models/TaskItem.swift +++ b/KisaniCal/Models/TaskItem.swift @@ -253,10 +253,15 @@ class TaskViewModel: ObservableObject { let cal = Calendar.current let start = cal.startOfDay(for: now) let end = cal.date(byAdding: .day, value: 1, to: start)! - // Timed tasks whose time has already passed belong in overdue, not today. + // Timed ONE-OFF tasks whose time has already passed belong in overdue, + // not today. Recurring occurrences always stay in Today regardless of + // time: overdueTasks intentionally excludes recurring tasks (postponing + // one would shift the recurrence rule's anchor date, not just today's + // occurrence), so without this a past-time recurring task would vanish + // from the app entirely instead of showing here like Calendar shows it. return activeRows().filter { guard let d = $0.dueDate else { return false } - if $0.hasTime && d < now { return false } + if $0.hasTime && d < now && !$0.isRecurring { return false } return d >= start && d < end } .sorted { ($0.dueDate ?? .distantFuture) < ($1.dueDate ?? .distantFuture) }