Files
KisaniCal/KisaniCal/ISSUES.md
kutesir 508d321619 Calendar: force timeline fill (dead space) + long-press menu on blocks
KC-49: week/3-day dead space — the outer .frame only allowed fill; force the
timeline ScrollView greedy (.frame maxHeight .infinity) and pin each timeline
root VStack top, so the header pins to the top instead of floating mid-screen.

KC-50: timeline blocks now have a long-press context menu — Complete, Snooze
(15m/1h/tomorrow), and the shared TaskMenuItems (reschedule/move/priority/
category/edit/delete). Events get no menu. CVTimeBlock takes @ViewBuilder menu.

Logs KC-49 (supersedes KC-48) and KC-50 in ISSUES.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:57:13 +00:00

59 KiB
Raw Blame History

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

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.


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

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

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).

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.

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.


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.


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.


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

Status: In Progress (implemented & build-verified, pending device build) 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.

"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.


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).


KC-14 — Workout Stats (daily / weekly / monthly performance)

Status: In Progress (implemented & build-verified, pending device build) Reported by: User Area: Workout

Description

Per-day logs (KC-9) existed but weren't aggregated, so there was no way to compare daily/weekly/monthly performance.

Fix

  • WorkoutDayLog.volume (Σ weight×reps over done sets); StatPeriod/WorkoutStat/ StatBucket.
  • WorkoutViewModel: stat(period, offset:) (current vs previous), statInterval, statBuckets(period, count:) for charting.
  • New WorkoutStatsView (chart-bar button in the Workout header): Day/Week/Month selector, summary cards (Workouts / Sets / Volume) with %-delta vs the previous period, a metric chooser, and a Swift Charts bar trend (last 14 days / 8 weeks / 6 months). Empty state when no history.

Files: ExerciseModels.swift, WorkoutView.swift.

Note

Stats are computed from the app's own per-day logs. Health (steps/kcal/resting HR + workout dates) is already read on the Today dashboard and merged into the streak; a Health overlay on this screen could be added later.


KC-15 — Bars countdown widget + unified slate/orange widget theme

Status: In Progress (implemented & build-verified, pending device build) Reported by: User Area: Widgets

Description

Add a bar-meter event-countdown widget (per reference screenshot) and re-theme every widget to one look: dark slate background + brand orange, matching the app.

Fix

  • WidgetTheme (slate background RGB(0.224,0.255,0.31), brand orange accent).
  • BarGrid vertical-bar progress view; new EventBarsView + EventBarsWidget ("Event Countdown (Bars)", medium) reusing the existing event config/provider.
  • EventEntry.fineTimeLeft (days → "X hr, Y min left") for the bars label.
  • Re-themed all home-screen widgets to slate bg + orange: Day Progress, Tasks Done Today, Event Countdown (dots), My Tasks. Lock-screen accessory widgets stay on .thinMaterial (system-tinted on the Lock Screen).

Files: WidgetViews.swift, EventCountdownWidget.swift, MyTasksWidget.swift, KisaniCalWidgets.swift.


KC-16 — Workout check-in, missed state, and rest-day compensation

Status: In Progress (implemented & build-verified; notification actions need a real device) Reported by: User Area: Workout / Notifications

Description

Workout reminders should ask "Have you done this workout?" with Yes / No / Snooze. Saying No marks that day missed and offers to compensate on a rest day. Long-press on workout/exercise cards gives quick Done / Not Done / Compensate actions. Each scheduled day keeps its own completion state.

Fix

  • VM: missedDates + compensations (persisted + iCloud), workoutStatus(on:) (pending/done/missed), markWorkoutDone/markWorkoutMissed(on:) (per-date only), upcomingRestDays, scheduleCompensation, promptCompensation, checkPendingMissed. program(for:) honors compensations; a compensation day is not a rest day.
  • Notifications: WORKOUT_CONFIRM category now has "Yes, I completed it ✓", "No, I didn't" (→ marks missed + asks compensation on next open), and "Remind me in 1 hour" (snooze re-delivers). Daily reminder + check-in both use it. Compensation days get their own one-off reminder ("Missed workout recovery").
  • UI: CompensationSheet (rest-day picker, "No, keep as missed"); long-press menus on the calendar/Today workout row (Done / Not Done / Compensate) and on exercise cards (Mark as Done / Not Done); workout row subtitle shows Pending / Done / Missed and a "Compensation ·" prefix (missed shows accent color).
  • Daily per-date rules unchanged (KC-1/KC-10): completing Tuesday never completes Saturday.

Files: ExerciseModels.swift, NotificationManager.swift, ContentView.swift, WorkoutView.swift, CalendarView.swift, TodayView.swift.

How to test (device)

  1. Enable workout reminder/check-in in Settings; when it fires, use the actions.
  2. "No, I didn't" → open app → compensation sheet appears → pick a rest day → that day now shows the program labeled Compensation (and gets a reminder).
  3. Long-press the workout card in Today/Calendar → Done / Not Done / Compensate.
  4. Long-press an exercise card in Workout → Mark as Done / Not Done.

KC-17 — Remove Event Countdown (dots) + Tasks Done Today widgets

Status: Resolved (build-verified) Reported by: User Area: Widgets

Description

The dot-grid "Event Countdown" and "Tasks Done Today" widgets were judged not up to standard and removed entirely from the gallery.

Removal

  • EventCountdownWidget struct (kind KisaniEventCountdown) + EventCountdownView.
  • TasksCompleteWidget (kind KisaniTasksComplete) + TasksCompleteView + its only consumer todayTaskCompletion() in WidgetData.swift.
  • Both deregistered from KisaniWidgetBundle.
  • Kept (shared by the surviving bars widget): EventConfigIntent, EventEntity, EventQuery, EventProvider, EventEntry, CycleCountdownUnitIntent, BarGrid; DotGrid kept (used by Day Progress).
  • Untouched: Event Countdown (Bars), My Tasks, Day Progress, Lock Screen rect + circular, Task Live Activity.

Files modified: KisaniCalWidgets.swift, EventCountdownWidget.swift, WidgetViews.swift, WidgetData.swift. No files deleted (shared code remains in EventCountdownWidget.swift). No app-side settings referenced these widgets.


KC-18 — Bars widget: progress bar never fills; date pickers felt broken

Status: In Progress (implemented & build-verified, pending device build) Reported by: User Area: Widgets

Symptoms

The Event Countdown (Bars) widget showed all bars dim regardless of time left, and the Event date / Counting from rows in the config appeared not to work.

Root causes

  1. Auto (upcoming-queue) mode anchored progress to Date() on every refresh, so elapsed time was always ~0 → empty bar forever.
  2. Custom mode defaulted both Event date and Counting from to the same instant (Date.now at config time) → zero-length span → progress 0. The raw second-precision defaults also made the picker rows look broken.
  3. The timeline only refreshed at midnight, so even a correct bar wouldn't move during an "X hr left" countdown.

Fix

  • Persistent per-event anchor (kisani.widget.anchor.<id> in the App Group): progress fills from when an event was first tracked and resets naturally when the tracked event changes. An explicit "Counting from" before the event wins.
  • targetDate/startDate are now optional — rows read "Choose" until set, and a missing custom date falls back to a 2-day stub window.
  • Timeline pre-renders an entry every 30 minutes (12 h horizon) so the bar and time-left label keep moving.

Files: KisaniCalWidgets/EventCountdownWidget.swift. Note: existing widget instances re-read their config; dates must be re-picked once (params changed to optional).


KC-19 — Widgets: exact logo palette everywhere

Status: In Progress (implemented & build-verified, pending device build) Reported by: User Area: Widgets

Description

All widgets should match the brand orange theme of the logo exactly.

Fix

  • WidgetTheme now uses the precise brand palette: accent RGB(232,98,42) (app AppColors.accent) on the logo's dark RGB(26,29,34) background — previously a near-miss orange on a slate background.
  • Every themed widget (Event Countdown Bars, My Tasks, Day Progress) inherits it.
  • Task Live Activity: hardcoded accent → WidgetTheme.accent; background tint → logo dark.
  • Lock-screen ring tints → brand accent (renders in color on StandBy/home).

Files: WidgetViews.swift, TaskLiveActivity.swift.


KC-20 — "Track Countdown": zero-config bars widget driven from the app

Status: In Progress (implemented & build-verified, pending device build) Reported by: User Area: Widgets / Tasks

Description

The bars widget should not need manual event entry: track any KisaniCal event from the app and the widget displays it automatically (title, date, time left, progress bars).

Fix

  • App: new CountdownTracker + a "Track Countdown" / "Stop Tracking Countdown" item in the shared task long-press menu. Stores kisani.widget.trackedEvent (+ trackedSince) in the App Group and reloads widget timelines.
  • Widget: the tracked event bypasses the widget configuration entirely.
    • Mode A (one-off): progress = elapsed/(event tracking date).
    • Mode B (recurring birthdays/anniversaries): progress across the occurrence span containing now (previous → next occurrence), derived from the task's recurrence rule (recurrenceSpan).
  • WidgetTask now decodes isRecurring/recurrenceLabel from the shared JSON.
  • No tracked event → existing configuration (upcoming queue / picked / custom) remains the fallback, exactly as before.
  • Bar rendering already maps progress → filled (accent) vs remaining (dimmed) bars; the screenshot showing static dim bars was the pre-KC-18 build.

Files: TaskContextMenu.swift, WidgetData.swift, EventCountdownWidget.swift.

How to test (device)

  1. Long-press any task with a due date → "Track Countdown".
  2. The bars widget switches to that event without touching its config.
  3. A yearly birthday shows Mode B: bar spans last year's → this year's date.
  4. Long-press the task again → "Stop Tracking Countdown" → widget falls back to its configured mode.

KC-21 — Eisenhower Matrix as a TickTick-style view (Priority × deadline)

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Tasks / Matrix

Problem

The Matrix treated importance as the task's stored quadrant — you placed a task in a row and only a manual "Move" changed it. TickTick instead treats the Matrix as a view: it auto-places every task from its Priority and due date, while still allowing quick drag/menu moves.

Fix (model — TaskItem.swift)

  • Replaced whole-quadrant override with urgencyOverride: Bool?.
    • nil = urgency is computed from due date.
    • true = force urgent column.
    • false = force not-urgent column.
  • Importance is now priority == .high (top row), not the stored quadrant. Medium/Low/None stay in the bottom row.
  • Urgency is deadline-driven:
    • default urgent window = 3 days
    • birthday/domain/annual/renewal/subscription/exam/deadline = 14 days Auto-placement: Q1 = High+urgent, Q2 = High+later, Q3 = not-High+urgent, Q4 = the rest.
  • moveToQuadrant updates real attributes:
    • vertical movement sets Priority (top → High; moving down demotes High → Medium)
    • horizontal movement sets urgencyOverride only if the target column disagrees with date-derived urgency.
  • Editing due date (setDate) or saving the editor (updateTask) clears urgencyOverride so the task re-places live.
  • Category changes, priority changes, and date changes refresh the legacy quadrant color/widget fallback to match computed placement.
  • addTask applies KisaniCal defaults when no priority is picked: birthday / domain / annual (+ title keywords exam, subscription, renew, expir) → High; workout / gym / exercise → Medium.

Fix (create UI — TodayView.swift)

  • AddTaskSheet no longer has a manual quadrant selector. It now exposes a Priority menu (flag icon). User picks Priority + Date only; placement is computed. Adding from a Matrix quadrant seeds High for the top row.
  • Fixed no-date creation: a task without an explicit parsed/picked date stays "No Date" instead of silently becoming today/urgent.

Fix (Matrix UI — MatrixView.swift, TaskContextMenu.swift)

  • Completed tasks are hidden from the 2×2 Matrix grid by default, but remain in the per-quadrant detail Completed section.
  • Rows with an urgency override show a small Manual badge.
  • Context menu includes Reset Matrix Urgency when an urgency override exists.
  • Move menu shows all four quadrants, with a checkmark on the current quadrant.

How to test

  1. New task, no priority, far date → lands in Q4; raise to High → jumps to Q2.
  2. New task with no date → stays in right column until given a due date/reminder.
  3. Drag Q2 → Q1 when due date is far out → Manual badge appears.
  4. Reset Matrix Urgency → task returns to its date-derived column.
  5. Drag Q1 → Q4 → High demotes to Medium and urgency is overridden if needed.
  6. Open the task, change its due date → urgency override clears, re-places by rule.
  7. Add a "Mum's birthday" with no priority → auto-High and uses 14-day urgency window.

KC-22 — Quick postpone / snooze from task menus and lock screen

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Tasks / Calendar / Matrix / Notifications

Problem

Long-pressing a task/event in Today, Matrix, or Calendar required going into the date editor or using only coarse "+1 day" actions. Lock-screen task reminders also only supported "Mark Complete". The user wanted standard task-manager snooze choices directly from the pressed item and notification actions.

Fix (app task menus)

  • Added a shared Postpone submenu to TaskMenuItems:
    • Snooze 15 Minutes
    • Snooze 30 Minutes
    • Snooze 1 Hour
    • Snooze 2 Hours
    • Tomorrow
  • Wired the submenu through:
    • Today timeline + overdue/upcoming sections
    • Matrix grid + quadrant detail sections
    • Calendar day timeline
  • TaskViewModel.postpone(_:minutes:) applies the nudge:
    • existing reminder → reminder moves forward
    • due date with no reminder → due date/time moves forward and becomes timed
    • no date/reminder → creates a reminder

Fix (lock screen notifications)

  • Task reminder notifications now include actions:
    • Snooze 15 min
    • Snooze 30 min
    • Snooze 1 hour
    • Snooze 2 hours
  • Notification response handler updates the stored task reminder and schedules a one-off snoozed notification for the selected delay.

Files: TaskContextMenu.swift, TaskItem.swift, TodayView.swift, MatrixView.swift, CalendarView.swift, NotificationManager.swift.

How to test

  1. Long-press a task in Today → Postpone → 15 Minutes; reminder/due time moves.
  2. Repeat from Matrix grid, Matrix detail, and Calendar day timeline.
  3. Open Move from the same menu → only the other three quadrants are listed (see KC-25).
  4. Trigger a task reminder notification on device → verify 15/30/60/120 minute lock-screen actions appear.
  5. Tap a lock-screen snooze action → task reminder updates and notification re-delivers after the selected interval.

KC-23 — "Pick Date" everywhere + sensible time-based reminders

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Tasks / Matrix / Calendar / Reminders

Problem

The Date submenu offered "Pick Date" only in the Today view — the Matrix grid cards and the Calendar reschedule/move menu were missing it. Separately, the reminder lead-time options were day/week based ("2 days early", "1 week early"), which is unrealistic for timed events; the user wanted the standard time-based set.

Fix

  • "Pick Date" (and Edit) now appear in the Matrix grid cards and the Calendar day timeline menu — both open the shared TaskEditSheet via a new editingTask sheet (QuadrantCard gained an onEdit; Calendar passes onEdit into the task menu).
  • Reworked the reminder picker (TaskDatePickerSheet, shared by every entry point) from day-offsets to minutes before the event time: None, On time, 5 min, 30 min, 1 hour, 1 day, plus a Minutes/Hours/Days custom wheel. Existing absolute reminderDates migrate to the nearest minute offset on open.

Files: MatrixView.swift, CalendarView.swift, TodayView.swift.

How to test

  1. Long-press a task in Matrix and in Calendar → Date → "Pick Date" is present.
  2. Create/edit a task → reminder options read On time / 5 min / 30 min / 1 hour / 1 day / Custom, relative to the event time.
  3. Custom → Minutes/Hours/Days toggle + count wheel commits the right offset.

KC-24 — App rebrand to "Wenza"

Status: In Progress (display name + copy done; App Store name set in ASC) Reported by: User Area: Branding

Problem

The app is being rebranded from "Kisani Cal" to Wenza.

Fix

  • CFBundleDisplayName → "Wenza" (app) and "Wenza Widgets" (widget) in project.yml; permission prompts and in-app copy (sidebar, Auth, Onboarding, Settings, tutorial, widget description) updated to Wenza.
  • Deliberately unchanged to preserve App Store identity and user data: bundle ID com.kutesir.KisaniCal, App Group group.com.kutesir.KisaniCal, kisani.tasks.v2.* storage keys, team ID, and internal Xcode target/project names.
  • App Store listing name set separately in App Store Connect (ships with the next version).

Files: project.yml, ContentView.swift, TutorialManager.swift, AuthView.swift, OnboardingView.swift, SettingsView.swift, KisaniCalWidgets.swift.

How to test

  1. Build & run → home-screen label and all in-app titles read "Wenza".
  2. Confirm tasks/widget data survive (App Group + keys unchanged).

KC-25 — Matrix "Move" lists only the other quadrants

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Tasks / Matrix

Problem

The Move submenu listed all four quadrants with a checkmark on the current one, so the quadrant a task already sat in appeared as a no-op option.

Fix

  • TaskMenuItems Move submenu now excludes the task's current quadrant and lists only the other three, using the clear matrix labels (e.g. a task in Urgent & Important shows Not Urgent & Important, Urgent & Unimportant, Not Urgent & Unimportant). Shared across Today, Matrix, and Calendar.

Files: TaskContextMenu.swift.

How to test

  1. Long-press a task in any quadrant → Move shows only the three other quadrants.
  2. Pick one → task moves (priority/urgency updated per the TickTick model).

KC-26 — Bars widget: auto-select advances to the next event

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Widgets

Problem

With "Upcoming queue" on + "Nearest to finish", the Event Countdown (Bars) widget stayed stuck on an event after it expired (e.g. a passed deadline) instead of rolling to the next-nearest. The selection was frozen: the timeline computed the chosen event once and reused it for all 24 pre-rendered entries, only refreshing every 12 hours.

Fix

  • Each timeline entry now re-resolves the nearest-to-finish event as of that entry's own time (upcomingEvents(asOf:) filters dueDate > entryDate), so the moment the current event expires the next entry rolls to the next one.
  • The timeline refreshes the instant the current selection expires (currentSelectionExpiry), not 12 hours later, giving the next event a fresh full pre-render window.
  • Completing/closing a task already reloads widget timelines, and the recompute drops it and picks the next.

Follow-up — tracked events now advance too

The first pass left the tracked ("Track Countdown") path pinned: a completed or expired tracked event stayed on screen. Fixed with trackedTask(asOf:), which returns the tracked event only while it's live (not complete, and recurring or still future); once done/past (non-recurring) the widget falls through to the next-nearest. Recurring tracked events still roll forward, and the refresh boundary now includes the tracked event's expiry.

Files: EventCountdownWidget.swift.

How to test

  1. Queue on + Nearest to finish; let the nearest event's due time pass → widget rolls to the next-nearest without manual action.
  2. Complete the nearest event → widget advances to the next.
  3. Track an event, then complete it → widget advances to the next-nearest.

KC-27 — Today ⋯ menu: Background, Group & Sort, Select

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Tasks / Today

Problem

Three items in the Today ⋯ menu were empty placeholders: Background, Group & Sort, and Select.

Fix

  • Background: sheet with 5 themes (Default/Warm/Cool/Mono/Paper) applied as a subtle gradient to the Today background; persisted via @AppStorage.
  • Group & Sort: group by Date (default)/Priority/Category/Quadrant and sort by Smart/Due/Priority/Title. Non-date groupings render as titled sections reusing UpcomingSection.
  • Select: multi-select sheet with Select All / Deselect All and a bulk action bar (Complete / Priority / Move / Delete).
  • New file TodayMenuFeatures.swift; added TaskViewModel.allActiveRows so grouping/selection use the same single-occurrence task set (incl. undated).

Files: TodayMenuFeatures.swift, TodayView.swift, TaskItem.swift.

How to test

  1. ⋯ → Background → pick a theme; Today background updates and persists.
  2. ⋯ → Group & Sort → Priority/Category/Quadrant regroups the list.
  3. ⋯ → Select → Select All → Complete marks all done.

KC-28 — Clearer Snooze labels on notifications and task menus

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Notifications / Tasks

Problem

The lock-screen reminder actions and the in-app Postpone submenu showed bare durations ("15 min", "30 min", …) that didn't say what they do. iOS notification buttons are a flat list and can't nest, so a single expanding "Snooze" isn't possible — the labels must carry the meaning.

Fix

  • Lock-screen actions: "Snooze 15 min / 30 min / 1 hour / 2 hours" (+ Mark Complete).
  • In-app Postpone submenu: "Snooze 15 Minutes / 30 Minutes / 1 Hour / 2 Hours".

Files: NotificationManager.swift, TaskContextMenu.swift.


KC-29 — Lock-screen Progress Dots widget legibility

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Widgets

Problem

The Progress Dots widget in the lock-screen accessoryRectangular family packed 100 tiny dots into a small pill over a custom dark background, which the lock screen's vibrant rendering washed out — it looked faint / not loaded.

Fix

  • accessoryRectangular: 28 larger dots (vs 100), .primary color for the vibrant tint, and AccessoryWidgetBackground() for the standard frosted pill. Home Screen small/medium/large keep the 100-dot orange-on-dark grid.

Files: WidgetViews.swift.


KC-30 — Period milestone notifications (day / week / month / year)

Status: In Progress (implemented & build-verified, pending device QA) Reported by: User Area: Notifications

Problem

The user wanted recurring "one more period passed" nudges (Pretty Progress style): day, week, month, and year, all at midnight.

Fix

  • schedulePeriodMilestones() schedules recurring UNCalendarNotificationTriggers, all firing at midnight (00:00):
    • Today — "One more day passed." — every day
    • This Week — "One more week passed." — Monday 00:00 (weekly)
    • This Month — "One more month passed." — 1st at 00:00 (monthly)
    • This Year — "One more year passed." — Jan 1 at 00:00 (yearly)
  • Fixed identifiers + remove-then-add keep them de-duplicated; runs via the existing reschedule(...) path when authorized; gated by the kisani.periodMilestones flag (default on).
  • Pending: no in-app Settings toggle yet (see KC-31 #2).

Files: NotificationManager.swift.

How to test

  1. Authorize notifications; at midnight a "One more day passed." notification fires (and the week/month/year equivalents on their boundaries).

KC-31 — Audit fixes: lock-screen sync, live urgency, batch postpone

Status: Fixed items build-verified; pending items remain open (see below) Reported by: User (code review) Area: Notifications / Tasks / Matrix

Reviewed items & outcomes

Fixed

  • Lock-screen Mark Complete / Snooze now sync + reload. markTaskComplete and snoozeTask previously wrote only to UserDefaults.kisani. They now also CloudSyncManager.shared.push(...) (→ iCloud / Apple Watch) and WidgetCenter.shared.reloadAllTimelines().
  • Notification copy uses live Matrix urgency, not stored quadrant. reschedule(...) snapshots urgent task IDs from the computed displayQuadrant and threads them into scheduleTasks / notifBody, so "Urgent — tap to mark complete" and the evening check-in reflect current urgency rather than a stale stored fallback.
  • Batch postponeAllOverdue matches single postpone. It now clears urgencyOverride and re-syncs the stored quadrant per task.

Files: NotificationManager.swift, TaskItem.swift.

Pending (not closed)

  • #2 — Period-milestone Settings toggle. Currently only the kisani.periodMilestones UserDefaults flag (default on); no in-app UI to turn the day/week/month/year notifications on/off. Pending.
  • #6 — Configurable Matrix thresholds & snooze durations. urgentWindowDays (3), categoryUrgentWindowDays (14), and the 15/30/60/120-min snooze actions are hardcoded constants — fine for now, but no settings for TickTick-style customization. Pending.
  • Note (#1) — Matrix "Move" lists only the other 3 quadrants. This is the intended KC-25 behavior, not a defect; left as-is unless reversed.

KC-32 — Calendar Day / 3-Day / Week / List views not showing tasks & events

Status: Fixed (build + unit-test verified) Reported by: User Area: Calendar

Description

Tasks/events only appeared in the Month view. Day and 3-Day showed an empty timeline; Week showed abstract colored bars with no titles; the List/task-list views missed recurring tasks.

Root cause

  • cvTimeItems(date) iterated raw taskVM.tasks (no recurrence) and dropped any untimed task via guard h != 0 || m != 0 (midnight = "no time" → discarded).
  • Week rendered eventDots (colors only); List and the task-list toggle used raw tasks instead of occurrences(on:).

Fix

  • cvTimeItems now uses occurrences(on:) + the real hasTime flag; added an all-day band (cvAllDayBand / cvAllDayItems) for untimed tasks, all-day events, and the day's workout.
  • Week lists real titles (up to 4/day + "N more") via shared cvAgendaItems.
  • List view and calDayTaskListView switched to occurrences(on:).

Files: CalendarView.swift.


KC-33 — Calendar month grid: weekday header & week numbers misaligned to firstWeekday

Status: Fixed (17 unit tests, UI-verified) Reported by: User (release-blocking, data integrity) Area: Calendar

Description

Dates appeared under the wrong weekday labels (e.g. June 1 2026 — a Monday — shown under "S"). A data-integrity bug, not visual.

Root cause

The grid is built from .weekOfMonth (respects Calendar.current.firstWeekday), but the weekday header was hardcoded Sunday-first (["S","M","T","W","T","F","S"]) and the week-number label was gated on isSunday (weekday == 1). On any non-Sunday-first locale / "Start Week On = Monday", every column was mislabeled by one and the W-number landed on the wrong column.

Fix

  • Extracted pure, testable date math into CalendarGrid (monthGrid + weekdaySymbols); gridDays() and the year-view mini-month dedupe to it.
  • Header now derived from firstWeekday, so it always matches the grid.
  • Week-number label gated on isWeekStart (weekday == firstWeekday). Documented that week numbers are locale weekOfYear (consistent with layout), not ISO.

Files: CalendarGrid.swift (new), CalendarView.swift, ContentView.swift (DEBUG-only KISANI_INITIAL_TAB launch hook, compiled out of Release).

Verification

New KisaniCalTests target — 17 tests passing on simulator: exact Sunday-first June 2026 grid, header↔grid alignment for firstWeekday 17, cell identity, month navigation (May/Jul/Feb 2026 + Feb 2028 leap + Dec 2026→Jan 2027), event-day bucketing (all-day / late-night / midnight-crossing) across 4 timezones, and recurring-task occurrence expansion. Verified in the running app.


KC-34 — Confirm before completing a future-dated task

Status: Fixed (build-verified) Reported by: User Area: Tasks

Description

Tapping the circle on a task in "Next 7 Days" / "Later" silently completed it — easy to do by accident.

Fix

TaskRowView now shows a confirmation dialog before completing a task whose due date is after today (isFutureTask). Today/overdue tasks still toggle instantly, and un-completing never prompts. Applies everywhere TaskRowView is used.

Files: TodayView.swift.


KC-35 — Visual cleanup: slimmer checkbox + remove decorative emoji

Status: Fixed (build-verified) Area: Today / Widget / Settings / Notifications

Fix

  • Task checkbox: 22pt filled donut → 18pt open ring (1.5pt), no fill — quieter, matches the restrained brand. Tap target kept at 36×44.
  • Removed decorative emoji: 🎉 (My Tasks widget empty state), 𝕏 👾 📸 (Settings → Follow Us), (notification action title + "Done" body).

Files: TodayView.swift, MyTasksWidget.swift, SettingsView.swift, NotificationManager.swift.


KC-36 — Version display reads from the bundle (no hardcoded "v1.0.0")

Status: Fixed (build-verified) Area: Settings / Build

Description

About row and About sheet hardcoded "v1.0.0" while the app was 2.0 — drift.

Fix

Added a Bundle extension (marketingVersion / buildNumber / versionDisplay); both spots now read from the bundle (driven by project.yml). Build set to 2.0 (9).

Files: SettingsView.swift, project.yml.


KC-37 — Quick-add recurrence: detect "everyday" and "every weekday"

Status: Fixed (6 parser tests) Reported by: User Area: Tasks / Natural-language parsing

Description

Typing "remind me to pray everyday" didn't set a Daily recurrence.

Root cause

parseRecurrence required a space (\bevery\s+day\b), so "everyday" (one word) was missed; "every weekday" wasn't handled.

Fix

\bevery\s*day\b now matches both "every day" and "everyday"; added "each day", and "Every Weekday" detection ("every weekday" / "weekdays") ahead of the broader weekly/daily patterns. Sub-day cadence ("every hour" / "hourly") is intentionally not matched — the occurrence engine is day-granular, so it stays a one-off rather than being mislabeled.

Files: TodayView.swift (NLTaskParser). 6 parser tests added.

Pending (follow-up)

  • Hourly / sub-day recurrence is unsupported by the day-granular occurrence engine. Would need an engine + repeating-notification extension. Pending.

KC-38 — Voice input: add tasks by speaking

Status: Implemented (committed eb7d563) Area: Tasks / Quick-add

Description

The quick-add mic button is now wired to live, on-device speech recognition so a task can be dictated instead of typed.

Implementation

  • New SpeechRecognizer (Speech + AVFoundation): SFSpeechRecognizer driven by AVAudioEngine, publishing transcript / isRecording / error. Requests speech + microphone authorization, then streams partial results.
  • TodayView quick-add: mic button calls speech.toggle() (mic → mic.fill, accent + pulse while recording); onChange(of: speech.transcript) binds the text into the task field, so dictation flows through the same NLTaskParser — spoken dates/recurrence (e.g. "remind me to pray everyday") parse exactly like typed input (see KC-37). Permission errors surface inline.
  • Usage strings added: NSMicrophoneUsageDescription, NSSpeechRecognitionUsageDescription.

Files: SpeechRecognizer.swift (new), TodayView.swift, project.yml.

Notes / follow-up

  • Recognition locale is .current; no manual language picker.
  • Contains print("[VOICE] …") debug logging — harmless, could be removed before release. Minor.

Follow-up fix — transcript never appeared in the field

Status: Fixed (simulator-verified)

The mic captured speech and the transcript reached rawText, but the text never showed in the Quick Add field. Root cause: NLTaskField.updateUIView (UIViewRepresentable over UITextView) had guard coordinator.isEditing else { …return } — so when the field wasn't focused (the normal case when tapping the mic), externally-set text (voice) was dropped and the placeholder stayed. Fixed to render non-empty text even when not editing (caret moved to end). Verified on simulator by pushing text through the real speech.transcript path: "Buy milk tomorrow" → field shows it, date chip = Tomorrow; "Call Romeo at 11 AM" → field shows it, chip = Today 11:00.

Not exercised on the sim (no microphone / no UI-tap automation): live mic capture and the literal Save tap. The save path is the same submit()/addTask used by all typed tasks, so it's covered by normal task creation.

Added DEBUG-only test hooks (KISANI_AUTOADD, KISANI_VOICE_SIM) to inject a transcript on the simulator; #if DEBUG, compiled out of Release.

Files: TodayView.swift (NLTaskField.updateUIView), ContentView.swift.


KC-39 — Workout state lost on reinstall/update (incomplete iCloud restore)

Status: Fixed (build-verified) Reported by: User Area: Workout / iCloud sync

Description

Workout state was lost after app updates / fresh installs.

Root cause

save() pushes 7 workout keys to iCloud KVS (programs, schedule, activePid, completedDates, history, missedDates, compensations), but WorkoutViewModel.init only restoreIfMissing(...) for the first four. So history, missed dates, and rest-day compensations were uploaded to iCloud but never restored on a fresh launch — silently lost on reinstall/update.

Fix

Added the three missing restoreIfMissing calls (history, missedDates, compensations) so restore is symmetric with save. (Per-day lastActiveDay is transient set-reset state and intentionally not synced.)

Files: ExerciseModels.swift.

Note

Cross-device live refresh while the app is open isn't wired (WorkoutViewModel doesn't observe kisaniCloudDataRefreshed) — out of scope; the restore-on-launch path is what preserves state across updates.


KC-40 — Workout streak resets on a missed day (should be a lifetime tally)

Status: Implemented (not build-verified — sim runtime unavailable) Reported by: User Area: Workout

Description

The profile showed a streak of 1 even after 5 workouts that week — a single skipped/rest day reset it. User wants the count to only ever grow: every workout achieved counts, a 4/5 week and a 5/5 week both count fully, misses never subtract.

Change

streakDays is now a lifetime tally of distinct workout days (Set(workoutDates).count, totalAchievedWorkoutDays). Removed the consecutive/weekly-goal chain. Settings copy updated (goal no longer "maintains" a breakable streak). Unit tests in StreakLogicTests (4/5+5/5=9, blank-week, duplicates, skipped-day, empty) — algorithm also verified via standalone harness.

Files: ExerciseModels.swift, SettingsView.swift, KisaniCalTests/StreakLogicTests.swift.


KC-41 — 7-day bars empty & "this week" = 0 despite a nonzero done rate

Status: Implemented (not build-verified) Reported by: User Area: Tasks / Profile stats

Description

The profile's "COMPLETED LAST 7 DAYS" bars stayed flat and "this week" showed 0 even with completed tasks, because only one-off completedAt tasks were counted — recurring occurrences were ignored.

Change

Added TaskViewModel.completionCount(on:) / completions(on:) that count recurring occurrences (completedOccurrences) plus one-offs. Profile bars, "this week", and a new "day streak" cell now use them.

Files: TaskItem.swift, SettingsView.swift.


KC-42 — No way to browse historical task/workout activity

Status: Implemented (not build-verified) Reported by: User ("we need to be able to dig through the data") Area: Tasks / Workout / History

Change

New ActivityHistoryView: a 90-day, day-by-day archive of completed tasks (with times, recurring markers), workout logs (sets + volume), and missed / made-up days. Opened via a "History" button on the profile Tasks card.

Files: ActivityHistoryView.swift (new), SettingsView.swift.


KC-43 — Today-list checkbox color duplicates the row accent

Status: Implemented (not build-verified) Reported by: User Area: Tasks / Today

Change

TaskRowView checkbox stroke changed from task.quadrant.color to neutral text3 — the row already carries its color on the left accent bar and the category chip. Calendar day-list checkbox left colored (no accent bar there).

Files: TodayView.swift.


KC-44 — Workout streak banner should show week, then year, then comparison

Status: Implemented (not build-verified) Reported by: User Area: Workout

Change

StreakBannerView is now a swipeable 3-page TabView: (1) this week + 7 day ticks, (2) this year + 12 month ticks + all-time total, (3) this-week-vs-last-week delta. Added VM breakdowns: workoutsThisWeek/LastWeek/ThisYear, currentWeekDays, currentYearMonths.

Files: WorkoutView.swift, ExerciseModels.swift.


KC-45 — Calendar year view should scroll continuously (TickTick-style)

Status: Implemented (not build-verified) Reported by: User Area: Calendar

Change

Replaced the single paged year (◀ 2026 ▶, dead space below December) with an infinite LazyVStack of years, anchored on the current year (accent-colored). The top header now follows the scrolled-to year via a YearTopKey preference (iOS 16-safe). Removed navigateYear + the year nav header.

Files: CalendarView.swift.


KC-46 — Calendar month view: TickTick bars + collapse (REVERTED)

Status: Reverted (user preferred the original) Reported by: User Area: Calendar

Notes

Implemented TickTick-style day bars, collapse-to-week on selection (tap/drag), and a full-column selection highlight (commits 2ec3278, bd43eec). User preferred the original month view, so both were reverted (commits 0a87769, e8d74d4). Month view is back to dots + full grid + agenda below.

Files: CalendarView.swift.


KC-47 — Calendar week view should be a TickTick-style timeline

Status: Implemented (not build-verified) Reported by: User (screen recording) Area: Calendar

Change

Replaced the mini-month + agenda-list split with a 7-day timeline (hours down the left, one column per weekday, events as positioned blocks) reusing cvTimeGrid/cvDayColumnHeader. Added:

  • Drag-to-reschedule: task blocks drag vertically to change time, snapped to 15 min (CVTimeBlock, rescheduleTask; events + recurring tasks stay fixed).
  • Layout toggle: timeline ⇄ grid of day-cards (weekGrid, weekGridLayout).
  • Week-swipe navigation (navigateWeek).

Known gaps: drag moves time only (not across days); calendar events not draggable.

Files: CalendarView.swift.


KC-48 — Week / 3-day views float to the bottom (dead space on top)

Status: Fixed (not build-verified) Reported by: User Area: Calendar / Layout

Root cause

The timeline views are a thin day-header + a greedy ScrollView (≈0 ideal height), so the top-level VStack shrank and the ZStack(alignment: .bottomTrailing) floated it to the bottom, leaving a large empty gap above the header. Month view has a tall fixed grid, so it never surfaced.

Fix

Pinned the top-level content VStack with .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top).

Files: CalendarView.swift.


KC-49 — Week/3-day dead space persisted after KC-48

Status: Fixed (not build-verified) — supersedes KC-48 Reported by: User ("DEAD SPACE STILL EXISTS") Area: Calendar / Layout

Root cause (refined)

Pinning only the outer container (.frame(maxHeight: .infinity), KC-48) merely allows fill — it doesn't force it. The timeline ScrollView returned its content size instead of expanding, so the week/3-day root stayed short and the .bottomTrailing ZStack floated it down, leaving the day header stranded mid- screen. Day view happened to fill, so it never showed.

Fix

Force the timeline ScrollView to be greedy with .frame(maxWidth: .infinity, maxHeight: .infinity), and pin each timeline view's root VStack to fill + top-align. Applied to weekView and threeDayView.

Files: CalendarView.swift.


KC-50 — Long-press on a timeline event has no actions

Status: Implemented (not build-verified) Reported by: User Area: Calendar / Tasks

Description

Long-pressing a task block on the day/week/3-day timeline did nothing — it should offer the same actions as elsewhere (Complete, Snooze, reschedule, etc.).

Change

CVTimeBlock now takes @ViewBuilder menu content and shows it via .contextMenu. cvBlockMenu(for:) builds it for task-backed items: a Complete toggle, a Snooze submenu (15 min / 1 hr / tomorrow), then the shared TaskMenuItems (pin, reschedule, move, priority, category, edit, delete). Events (no taskID) get no menu.

Files: CalendarView.swift.