tasks: fix timed recurring tasks vanishing from Today after their time passes (KC-54)
Some checks failed
CI / build-and-test (push) Has been cancelled
Some checks failed
CI / build-and-test (push) Has been cancelled
Real bug: todayTasks excludes timed tasks whose time has passed ('belongs in
overdue'), but overdueTasks deliberately excludes ALL recurring tasks (it feeds
postpone/postponeAllOverdue, which mutate dueDate by id -- for a recurring task
that's the recurrence anchor, not just today's occurrence; including them there
would let Postpone All corrupt the whole series). Recurring tasks fell into the
gap between the two rules and vanished from the Today tab entirely once their
time passed, while Calendar's day view (no time-of-day gating) kept showing
them fine.
Fix: todayTasks only excludes past-time NON-recurring tasks. Recurring
occurrences always stay in Today, matching Calendar, without ever touching the
overdue/postpone path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1790,3 +1790,40 @@ Files: `KisaniCalWidgets/KisaniCalWidgets.swift`.
|
|||||||
Not build-verified (no iOS runtime here). To use: on the Lock Screen, tap the
|
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 →
|
widget area → Add Widgets → Wenza → Progress Dots → long-press → Edit Widget →
|
||||||
set Mode to This Day / This Week / This Year (This Month already configured).
|
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`.
|
||||||
|
|||||||
@@ -253,10 +253,15 @@ class TaskViewModel: ObservableObject {
|
|||||||
let cal = Calendar.current
|
let cal = Calendar.current
|
||||||
let start = cal.startOfDay(for: now)
|
let start = cal.startOfDay(for: now)
|
||||||
let end = cal.date(byAdding: .day, value: 1, to: start)!
|
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 {
|
return activeRows().filter {
|
||||||
guard let d = $0.dueDate else { return false }
|
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
|
return d >= start && d < end
|
||||||
}
|
}
|
||||||
.sorted { ($0.dueDate ?? .distantFuture) < ($1.dueDate ?? .distantFuture) }
|
.sorted { ($0.dueDate ?? .distantFuture) < ($1.dueDate ?? .distantFuture) }
|
||||||
|
|||||||
Reference in New Issue
Block a user