2026-06-06 19:29:25 +03:00
|
|
|
# KisaniCal — Issue Tracker
|
|
|
|
|
|
|
|
|
|
Status legend: `Open` · `In Progress` · `Resolved`
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-1 — Daily workout log does not reset on a new day
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (fix implemented & tested locally, pending release)
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Workout
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
When the same workout program is scheduled on back-to-back days (e.g. Shoulders
|
|
|
|
|
on Monday **and** Tuesday), the new day does not start fresh. Completed sets from
|
|
|
|
|
the previous day stay checked, so the workout appears already done.
|
|
|
|
|
|
|
|
|
|
### Root cause
|
|
|
|
|
Set completion (`ExerciseSet.isDone`) was stored permanently inside the
|
|
|
|
|
`WorkoutProgram` and persisted via `save()`. There was no per-day reset, so a
|
|
|
|
|
program reused the next day carried over yesterday's checkmarks.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
Set completion is now a per-day state:
|
|
|
|
|
- `WorkoutViewModel.resetSetsForNewDayIfNeeded()` clears every set's `isDone`
|
|
|
|
|
across all programs when the calendar day changes (skips first launch, no-ops
|
|
|
|
|
when the day is unchanged).
|
|
|
|
|
- `toggleSet(...)` stamps the active day so on-screen state always belongs to today.
|
|
|
|
|
- Called on cold launch (`init`), `.onAppear`, and `scenePhase == .active`
|
|
|
|
|
(handles crossing midnight while the app is alive).
|
|
|
|
|
- Streak history (`workoutDates`) is untouched, so completed days still count.
|
|
|
|
|
|
|
|
|
|
Files: `KisaniCal/Models/ExerciseModels.swift`, `KisaniCal/ContentView.swift`
|
|
|
|
|
|
|
|
|
|
### Verification
|
2026-06-06 19:29:25 +03:00
|
|
|
Builds cleanly (Xcode 26.2 SDK, simulator). Verified in-process on the simulator
|
|
|
|
|
via the real `resetSetsForNewDayIfNeeded()` against a seeded completed set:
|
|
|
|
|
`before=[true] after=[false] day=2026-06-06 -> PASS`. Streak preserved.
|
|
|
|
|
Committed in `cdc46e9`.
|
2026-06-06 19:29:25 +03:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-2 — App logo update (new KisaniCal brand mark)
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (assets installed & build-verified, pending release)
|
|
|
|
|
**Area:** Branding / App Icon
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
Replace the app icon with the new KisaniCal logo (orange dot grid on dark) across
|
|
|
|
|
the App Store marketing icon and all home/lock screen / Spotlight / notification sizes.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
All 12 sizes in `AppIcon.appiconset` regenerated from the new source. Alpha
|
|
|
|
|
flattened onto the brand dark background `RGB(26,29,34)` so the 1024 App Store icon
|
|
|
|
|
is fully opaque (App Store Connect compliant) and device icons are full-bleed.
|
|
|
|
|
|
|
|
|
|
Files: `KisaniCal/Assets.xcassets/AppIcon.appiconset/`
|
|
|
|
|
|
|
|
|
|
### Verification
|
2026-06-06 19:29:25 +03:00
|
|
|
Builds cleanly (app + widget extension); `hasAlpha: no` on all icons. New logo
|
|
|
|
|
confirmed compiled into `Assets.car` (home, lock-screen notification, Spotlight,
|
|
|
|
|
Settings all derive from `AppIcon`). Committed in `cdc46e9`.
|
|
|
|
|
Note: device still shows old icon until the new build is installed (delete + reinstall).
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-3 — Today view: section order + RSS feeds leaking into Tasks
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (implemented & build-verified, pending release)
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Tasks / Today
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
Two changes to the Today view:
|
|
|
|
|
1. The Overdue card sat at the very top, above today's active tasks. Active tasks
|
|
|
|
|
should always be the top section.
|
|
|
|
|
2. Subscribed/RSS calendar feeds (F1 schedule, TV-show calendars) were appearing
|
|
|
|
|
in the Today/Tasks timeline. They belong in the Calendar tab only.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- Moved the `OverdueCard` to render directly below today's active tasks (above
|
|
|
|
|
Next 3 Days), so active tasks stay at the top.
|
|
|
|
|
- `todayCalEvents` now excludes events whose `calendar.type == .subscription`, so
|
|
|
|
|
RSS/ICS feeds are filtered out of the Today timeline. The Calendar view reads
|
|
|
|
|
`monthEvents` directly and is unaffected.
|
|
|
|
|
- Birthdays (`.birthday`) and personal/CalDAV calendars are intentionally left
|
|
|
|
|
visible; the existing per-calendar visibility toggle (`hiddenCalendarIDs`) lets
|
|
|
|
|
the user hide any of them. User-created tasks are never affected.
|
|
|
|
|
|
|
|
|
|
Files: `KisaniCal/Views/TodayView.swift`
|
|
|
|
|
|
|
|
|
|
### Verification
|
2026-06-06 19:46:39 +03:00
|
|
|
Builds cleanly. Committed in `f66f032` (re-authored `a92e9d4`).
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-4 — Unified task context menu + Live Activity + calendar event actions
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (implemented & build-verified; Live Activity pending on-device test)
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Tasks / Calendar / Widgets
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
Long-pressing a task (in any task list) should show a consistent menu:
|
|
|
|
|
Pin · Date · Move · Priority · Tags · Add to Live Activity · Delete — and the
|
|
|
|
|
actions must actually work. Long-pressing a non-subscription calendar event
|
|
|
|
|
should offer Edit / Delete only.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- New shared `TaskMenuItems` (KisaniCal/Views/TaskContextMenu.swift) used by the
|
|
|
|
|
Today timeline, Matrix (grid + Overdue/Later/Completed lists), and Calendar
|
|
|
|
|
day timeline. Added `TaskViewModel.setPriority`.
|
|
|
|
|
- Live Activity (ActivityKit): `TaskActivityAttributes` (shared app+widget),
|
|
|
|
|
`LiveActivityManager` (start/update/end/toggle, iOS 16.1+), `TaskLiveActivity`
|
|
|
|
|
widget (lock screen + Dynamic Island), `NSSupportsLiveActivities` via project.yml.
|
|
|
|
|
- Calendar events (non-subscription, writable only): Edit opens the system
|
|
|
|
|
`EKEventEditViewController` (`EventEditView`); Delete via `CalendarStore.deleteEvent`.
|
|
|
|
|
|
|
|
|
|
Files: `TaskContextMenu.swift`, `TaskActivityAttributes.swift`,
|
|
|
|
|
`LiveActivityManager.swift`, `TaskLiveActivity.swift`, `TaskItem.swift`,
|
|
|
|
|
`MatrixView.swift`, `CalendarView.swift`, `TodayView.swift`,
|
|
|
|
|
`KisaniCalWidgets.swift`, `project.yml`. Committed in `d0d982f`.
|
|
|
|
|
|
|
|
|
|
### How to test
|
|
|
|
|
1. **Menu parity** — long-press a task in Today, in a Matrix quadrant, in a
|
|
|
|
|
Matrix list (Overdue/Later), and in the Calendar day view. All show the same
|
|
|
|
|
7 items.
|
|
|
|
|
2. **Each action** — Pin (row pins), Date → Today/Tomorrow/Next Week/Remove
|
|
|
|
|
(due date changes), Move (task changes quadrant), Priority (flag changes),
|
|
|
|
|
Tags (category changes), Delete (removed).
|
|
|
|
|
3. **Live Activity** — tap "Add to Live Activity" → a banner appears on the Lock
|
|
|
|
|
Screen / Dynamic Island with the task title + countdown; tap again to end.
|
|
|
|
|
**Requires a real iOS 16.1+ device** (not visible in the Simulator).
|
|
|
|
|
4. **Calendar events** — long-press a personal (writable) event → Edit opens the
|
|
|
|
|
system editor; Delete removes it. A subscribed/RSS event (F1, TV) shows **no**
|
|
|
|
|
menu.
|
|
|
|
|
|
|
|
|
|
### Known limitations
|
|
|
|
|
- Live Activity visuals only render on a physical device; the design is a first
|
|
|
|
|
pass (orange accent + timer) and can be refined.
|
|
|
|
|
- Uses the iOS 16.1 `Activity.request(...contentState:...)` API (deprecation
|
|
|
|
|
warning on 16.2+, still functional).
|
2026-06-07 04:08:23 +03:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-5 — Calendar "Connect" toggle unreliable / inconsistent across screens
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (implemented & build-verified; full reliability win is device-only)
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Calendar / Permissions
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
The iPhone Calendar "Connect" sometimes needed repeated taps, onboarding
|
|
|
|
|
sometimes didn't reflect a granted calendar, and there was no place to see/manage
|
|
|
|
|
connection status from Settings.
|
|
|
|
|
|
|
|
|
|
### Root cause
|
|
|
|
|
Three independent calendar-permission states that never synced: `TodayView`,
|
|
|
|
|
`CalendarView` each had their own `CalendarStore` + `EKEventStore`, and
|
|
|
|
|
`OnboardingView` had its own raw `EKEventStore`. Granting in one left the others
|
|
|
|
|
showing stale cached status until they happened to re-read it.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- `CalendarStore.shared` singleton — one source of truth used by Today, Calendar,
|
|
|
|
|
Onboarding, and Settings. Connect once, every screen reflects it.
|
|
|
|
|
- `requestAccess()` now debounces in-flight requests and reads the **authoritative**
|
|
|
|
|
`EKEventStore.authorizationStatus` instead of trusting the callback bool;
|
|
|
|
|
already-decided states re-sync via `refreshStatus()`.
|
|
|
|
|
- Turning on "Show Calendar Events" while unauthorized now triggers the prompt.
|
|
|
|
|
- New **Settings → Data → Calendar** row showing ● Connected / Connect / Denied,
|
|
|
|
|
opening the connect sheet.
|
|
|
|
|
|
|
|
|
|
Files: `CalendarView.swift`, `TodayView.swift`, `OnboardingView.swift`,
|
|
|
|
|
`SettingsView.swift`.
|
|
|
|
|
|
|
|
|
|
### Verification
|
|
|
|
|
Builds cleanly. Cross-screen consistency + the Settings status row are testable in
|
|
|
|
|
the Simulator; the request-reliability fix is best confirmed on a device.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-6 — No notifications for calendar events
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (implemented & build-verified; fires on a real device with calendar access)
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Calendar / Notifications
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
Task reminders fired, but calendar events never produced a KisaniCal
|
|
|
|
|
notification — events with no iOS alert (e.g. subscribed F1/TV feeds) notified
|
|
|
|
|
the user of nothing.
|
|
|
|
|
|
|
|
|
|
### Root cause
|
|
|
|
|
`NotificationManager` only scheduled workout + per-task notifications. There was
|
|
|
|
|
no calendar-event path at all (`EKEvent`/`CalendarStore` were never involved).
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- `CalendarStore.upcomingEventNotifs(days:)` snapshots visible events for the next
|
|
|
|
|
7 days (respects enabled flag + hidden calendars) with start time + alarm offsets.
|
|
|
|
|
- `NotificationManager.scheduleCalendarEvents(...)` schedules a notification at the
|
|
|
|
|
event start ("on time") plus at each alarm the user set; all-day events fire at
|
|
|
|
|
9am morning-of. Capped at ~20 to stay under iOS's 64 pending-notification limit;
|
|
|
|
|
recurring instances kept unique via start-time in the id.
|
|
|
|
|
- Wired into `reschedule(...)` (runs on foreground / scene-active / task changes).
|
|
|
|
|
|
|
|
|
|
Files: `CalendarView.swift`, `NotificationManager.swift`.
|
|
|
|
|
|
|
|
|
|
### How to test (device, calendar access granted)
|
|
|
|
|
1. Create an event a few minutes out with **no alert** on a visible calendar.
|
|
|
|
|
2. Background the app (triggers a reschedule).
|
|
|
|
|
3. Notification fires at the event's start time.
|
|
|
|
|
|
|
|
|
|
### Known limitations / decisions
|
|
|
|
|
- "All visible events" was chosen, so personal events that already have an iOS
|
|
|
|
|
alert may double (system + KisaniCal). Feed events (F1/TV) are the main win.
|
|
|
|
|
- Tapping a calendar notification opens the Today view (no calendar deeplink).
|
|
|
|
|
- The 7-day window re-scans on each foreground; no background refresh beyond that.
|
2026-06-07 14:11:26 +03:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-7 — App Store upload: "iPad Multitasking must provide a launch screen"
|
|
|
|
|
|
|
|
|
|
**Status:** Resolved (fix in `72f4566`; re-archive required to ship)
|
|
|
|
|
**Reported by:** User (App Store / Transporter validation)
|
|
|
|
|
**Area:** Build / App Store
|
|
|
|
|
|
|
|
|
|
### Symptom
|
|
|
|
|
Upload fails validation:
|
|
|
|
|
> Invalid bundle. Apps that support Multitasking on iPad must provide the app's
|
|
|
|
|
> launch screen using an Xcode storyboard, or using UILaunchScreen … Verify that
|
|
|
|
|
> the UILaunchStoryboardName key is included in your com.kutesir.KisaniCal bundle.
|
|
|
|
|
|
|
|
|
|
### Root cause
|
|
|
|
|
The app targets iPad (`TARGETED_DEVICE_FAMILY = "1,2"`) with multitasking
|
|
|
|
|
(`UIApplicationSupportsMultipleScenes = true`), but `project.yml` had
|
|
|
|
|
`INFOPLIST_KEY_UILaunchStoryboardName: ""` (empty) and no `UILaunchScreen` —
|
|
|
|
|
so the bundle shipped no valid launch screen.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- Added `KisaniCal/LaunchScreen.storyboard` (blank, `systemBackgroundColor`).
|
|
|
|
|
- `project.yml`: `INFOPLIST_KEY_UILaunchStoryboardName: LaunchScreen`.
|
|
|
|
|
- `xcodegen generate`. Verified a clean **Release** build emits
|
|
|
|
|
`UILaunchStoryboardName = LaunchScreen` and ships `LaunchScreen.storyboardc`.
|
|
|
|
|
|
|
|
|
|
### ⚡ If it happens again — solve it fast
|
|
|
|
|
This error almost always recurs because a **stale archive** was uploaded, NOT
|
|
|
|
|
because the code is wrong. Checklist:
|
|
|
|
|
1. Confirm the fix is present:
|
|
|
|
|
`grep UILaunchStoryboardName project.yml` → must be `LaunchScreen` (not `""`).
|
|
|
|
|
`git log --oneline -- KisaniCal/LaunchScreen.storyboard` → should show `72f4566`.
|
|
|
|
|
2. If Xcode was open, **reload the project** (xcodegen rewrites `.xcodeproj`).
|
|
|
|
|
3. **Product → Clean Build Folder (⇧⌘K)** — a cached build folder re-emits the old
|
|
|
|
|
Info.plist. This is the step people skip.
|
|
|
|
|
4. **Bump `CURRENT_PROJECT_VERSION`** in `project.yml` + `xcodegen generate`
|
|
|
|
|
(a failed upload can "use up" the build number).
|
|
|
|
|
5. Re-Archive → Validate → Distribute.
|
|
|
|
|
6. Sanity check any archive:
|
|
|
|
|
`/usr/libexec/PlistBuddy -c "Print :UILaunchStoryboardName" <App>.app/Info.plist`
|
|
|
|
|
and `ls <App>.app | grep LaunchScreen` (expect `LaunchScreen.storyboardc`).
|
|
|
|
|
|
|
|
|
|
Committed: fix `72f4566`, build-number bump `345415e`.
|
2026-06-07 23:12:46 +03:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-8 — Workout doesn't follow the schedule; rest day shows a stale workout
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (implemented & build-verified, pending device build)
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Workout
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
The Workout tab kept showing the last-used program with its old completion,
|
|
|
|
|
ignoring the weekly schedule. On a rest day (no program assigned) it still
|
|
|
|
|
displayed a fully-checked workout from a previous day.
|
|
|
|
|
|
|
|
|
|
### Root cause
|
|
|
|
|
The daily reset (KC-1) cleared set completion but never changed the **active
|
|
|
|
|
program** — it stayed on whatever was last selected, regardless of the schedule.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- `applyScheduledProgramForToday()` (run from `resetSetsForNewDayIfNeeded` on a
|
|
|
|
|
new day) switches the active program to the one assigned to today.
|
|
|
|
|
- `isRestDayToday` (schedule exists but today has no entry) drives a new **Rest
|
|
|
|
|
Day** card in `WorkoutView` with a "Work out anyway" override; header reads
|
|
|
|
|
"Rest Day".
|
|
|
|
|
- Daily set reset (KC-1) unchanged.
|
|
|
|
|
|
|
|
|
|
Files: `ExerciseModels.swift`, `WorkoutView.swift`. Committed in `f9081cf`.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-9 — Per-day workout history
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (implemented & build-verified, pending device build)
|
|
|
|
|
**Reported by:** User ("store the data for each day")
|
|
|
|
|
**Area:** Workout
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
Set completion resets daily, so there was no way to look back at what was
|
|
|
|
|
actually logged on a past day.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- New `WorkoutDayLog` / `LoggedExercise` / `LoggedSet` models; stored as
|
|
|
|
|
`kisani.workout.history.<uid>` (and iCloud-synced).
|
|
|
|
|
- `snapshotDay(_:)` captures the active program's completed sets — called from
|
|
|
|
|
`logWorkoutCompleted` (same-day) and from the daily reset **before** wiping
|
|
|
|
|
(captures partial days). Only records days with ≥1 set done.
|
|
|
|
|
- New `WorkoutHistoryView` (clock button in the Workout header) lists past days
|
|
|
|
|
newest-first with program, sets done, and per-exercise breakdown.
|
|
|
|
|
|
|
|
|
|
Files: `ExerciseModels.swift`, `WorkoutView.swift`.
|
2026-06-08 00:29:48 +03:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-10 — Calendar marks future workouts complete (per-type, not per-date)
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (implemented & build-verified, pending device build)
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Calendar / Workout
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
In the calendar, completing one occurrence of a program (e.g. Tuesday Shoulders)
|
|
|
|
|
showed **every** occurrence of that program as completed — including **future**
|
|
|
|
|
days (Saturday Shoulders) — with a checkmark + strikethrough.
|
|
|
|
|
|
|
|
|
|
### Root cause
|
|
|
|
|
`DayTimelineView` computed the workout's completion from the program's **live**
|
|
|
|
|
shared set state: `w.doneSets == w.totalSets`. `program(for: date)` returns the
|
|
|
|
|
same program object for every date it's scheduled, so its in-progress completion
|
|
|
|
|
leaked onto all of those dates.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- Added `WorkoutViewModel.isWorkoutComplete(on:)` — checks `workoutDates` (the set
|
|
|
|
|
of dates a workout was actually finished/Health-detected), i.e. **per-date**.
|
|
|
|
|
- `DayTimelineView` now takes a `workoutDone: Bool` and uses it instead of the live
|
|
|
|
|
`doneSets`. Calendar passes `isWorkoutComplete(on: selectedDate)`, Today passes
|
|
|
|
|
`isWorkoutComplete(on: today)`.
|
|
|
|
|
- Result: a date shows the workout complete only if that **specific date** was
|
|
|
|
|
finished. Future occurrences are never auto-completed.
|
|
|
|
|
|
|
|
|
|
Files: `ExerciseModels.swift`, `CalendarView.swift`, `TodayView.swift`.
|
2026-06-09 01:41:15 +03:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-11 — Calendar appeared capped at 2026 (no year shown)
|
|
|
|
|
|
|
|
|
|
**Status:** Resolved
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Calendar
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
Users couldn't navigate/see dates beyond 2026.
|
|
|
|
|
|
|
|
|
|
### Root cause
|
|
|
|
|
Navigation was actually unbounded, but every header showed only the month name
|
|
|
|
|
(`"MMMM"`) — the Year view had no year label at all — so crossing a year boundary
|
|
|
|
|
was invisible and future months felt unreachable (compounded by empty future
|
|
|
|
|
months from the missing recurrence engine, KC-12).
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
Show the year: main header `"Month Year"`, date-picker grid `"MMMM yyyy"`, and a
|
|
|
|
|
`◀ year ▶` navigator + label in the Year view (`navigateYear`).
|
|
|
|
|
Files: `CalendarView.swift`, `TodayView.swift`. Committed in `7b2b61a`.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-12 — Recurring tasks don't appear on future dates
|
|
|
|
|
|
2026-06-09 10:47:13 +03:00
|
|
|
**Status:** In Progress (implemented & build-verified, pending device build)
|
2026-06-09 01:41:15 +03:00
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Tasks / Calendar
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
A task set to repeat (Daily/Weekly/Monthly/Yearly/Every-Weekday) only ever showed
|
|
|
|
|
on its single original `dueDate`. No future occurrences, no per-occurrence
|
|
|
|
|
completion.
|
|
|
|
|
|
|
|
|
|
### Root cause
|
|
|
|
|
There was no recurrence engine — tasks carry `isRecurring` + `recurrenceLabel` but
|
|
|
|
|
a single `dueDate`, and every surface matched by `isDate(dueDate, inSameDayAs:)`.
|
|
|
|
|
|
|
|
|
|
### Fix (core)
|
|
|
|
|
- Model: `recurrenceEnd` + `completedOccurrences` (optional → safe decode of old data).
|
|
|
|
|
- `TaskViewModel` engine: `isOccurrence(_:on:)`, `occurrenceComplete`,
|
|
|
|
|
`toggleOccurrence`, `occurrenceCopy`, `nextOccurrence`, `occurrences(on:)`.
|
|
|
|
|
- `toggle(_:)` routes recurring rows to per-occurrence completion (one day never
|
|
|
|
|
affects another).
|
|
|
|
|
- Date filters (`today`/`next3`/`upcoming`/`completedToday`) exclude recurring
|
|
|
|
|
masters and re-inject per-date occurrence copies; recurring never goes "overdue".
|
|
|
|
|
- Calendar dots + day detail use `occurrences(on:)`.
|
|
|
|
|
- Works across year boundaries; respects `recurrenceEnd` when set.
|
|
|
|
|
|
|
|
|
|
Files: `TaskItem.swift`, `CalendarView.swift`.
|
|
|
|
|
|
2026-06-09 10:47:13 +03:00
|
|
|
### "Repeat until" UI (done)
|
|
|
|
|
- Added `recurrenceEnd` through `NLParsed`, `TaskDatePickerSheet`, both add/edit
|
|
|
|
|
sheets, and `addTask`/`updateTask`.
|
|
|
|
|
- Date picker shows a "Repeat until" row (graphical date picker, default "Forever",
|
|
|
|
|
with Clear) whenever a recurrence is selected.
|
|
|
|
|
|
|
|
|
|
Files: `TaskItem.swift`, `CalendarView.swift`, `TodayView.swift`, `MatrixView.swift`.
|
2026-06-10 10:09:55 +03:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## KC-13 — Recurrence redesign: single occurrence, new sections, classic Matrix
|
|
|
|
|
|
|
|
|
|
**Status:** In Progress (implemented & build-verified, pending device build)
|
|
|
|
|
**Reported by:** User
|
|
|
|
|
**Area:** Tasks / Calendar / Matrix
|
|
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
The first recurrence pass used calendar-expansion in lists — a daily task showed
|
|
|
|
|
in Today, Tomorrow, Next 7 Days, and Upcoming simultaneously (planning noise).
|
|
|
|
|
Should behave like TickTick: one task, one next occurrence, one source of truth.
|
|
|
|
|
|
|
|
|
|
### Fix
|
|
|
|
|
- **Single active occurrence (P1/P5):** `nextActiveOccurrence` + `activeRows` — each
|
|
|
|
|
recurring task contributes ONE row (its next un-completed occurrence). Completing
|
|
|
|
|
rolls forward; no future duplicates in lists. Calendar grid still shows per-date.
|
|
|
|
|
- **Sections (P2):** replaced Today / Next 3 Days / Upcoming with
|
|
|
|
|
**Today / Tomorrow / Next 7 Days / Later** (`todayTasks`/`tomorrowTasks`/
|
|
|
|
|
`next7DaysTasks`/`laterTasks`; `UpcomingSection` gained a `title`).
|
|
|
|
|
- **Icons (P3):** ⏰ reminder + 🔁 recurring on the right of each row; fixed the
|
|
|
|
|
hardcoded "Annual" label to show the real frequency.
|
|
|
|
|
- **Matrix (P4):** classic Eisenhower — importance is user-assigned (row), urgency is
|
|
|
|
|
derived from the deadline (within `urgentWindowDays` = 7). `displayQuadrant` +
|
|
|
|
|
`matrixTasks` group by the computed quadrant; recurring rolls forward. Chosen over
|
|
|
|
|
the fully-auto "far = Q4" model so the user keeps control of importance.
|
|
|
|
|
|
|
|
|
|
Files: `TaskItem.swift`, `TodayView.swift`, `MatrixView.swift`.
|
|
|
|
|
Commits: `b4e36db` (lists + icons), `2f1bc5d` (Matrix).
|