Here's a summary of all fixes:

Issue #4 — Skip not clearing med
  File: synculous-client/src/app/dashboard/medications/page.tsx                                           Root cause: Skipped medications were routed to the "Upcoming" section (status === 'skipped' in the
  upcoming condition), so they appeared as if still pending.
  Fix: Removed || status === 'skipped' from the grouping condition. Now skipped meds go to the "Due"
  section where they properly render with the "Skipped" label — same pattern as taken meds showing
  "Taken".

  Issue #5 — "Invested -359m in yourself"

  Files: api/routes/routines.py, synculous-client/src/app/dashboard/page.tsx,
  synculous-client/src/app/dashboard/stats/page.tsx
  Root cause: Session duration was calculated by comparing a naive UTC created_at from PostgreSQL with
  the user's local time (after stripping timezone). For users behind UTC (e.g., CST/UTC-6), this
  produced negative durations (~-359 minutes ≈ -6 hours offset).
  Fix: Added _make_aware_utc() helper that treats naive datetimes as UTC before comparison. Also clamped
   durations to max(0, ...) on both backend and frontend formatTime as a safety net.

  Issue #6 — Push notifications not working

  File: api/routes/notifications.py
  Root cause: Subscribing to push notifications created a push_subscriptions row but never set
  web_push_enabled: true in the notifications table. The scheduler daemon checks web_push_enabled before
   sending, so push notifications were always skipped.
  Fix: When subscribing, also call notifications.setNotificationSettings() to enable web_push_enabled.
  When unsubscribing (and no subscriptions remain), disable it.
This commit is contained in:
2026-02-15 00:48:43 -06:00
parent 3ccd11153c
commit ba8c6e9050
5 changed files with 30 additions and 21 deletions

View File

@@ -35,6 +35,14 @@ def _auth(request):
return user_uuid
def _make_aware_utc(dt):
"""Ensure a datetime is timezone-aware; assume naive datetimes are UTC."""
if dt.tzinfo is None:
from datetime import timezone as _tz
return dt.replace(tzinfo=_tz.utc)
return dt
def _record_step_result(session_id, step_id, step_index, result, session):
"""Record a per-step result (completed or skipped)."""
try:
@@ -51,11 +59,8 @@ def _record_step_result(session_id, step_id, step_index, result, session):
if last_completed:
if isinstance(last_completed, str):
last_completed = datetime.fromisoformat(last_completed)
# Make naive datetimes comparable with aware ones
if last_completed.tzinfo is None:
duration_seconds = int((now.replace(tzinfo=None) - last_completed).total_seconds())
else:
duration_seconds = int((now - last_completed).total_seconds())
last_completed = _make_aware_utc(last_completed)
duration_seconds = max(0, int((now - last_completed).total_seconds()))
else:
duration_seconds = None
else:
@@ -63,10 +68,8 @@ def _record_step_result(session_id, step_id, step_index, result, session):
if created_at:
if isinstance(created_at, str):
created_at = datetime.fromisoformat(created_at)
if created_at.tzinfo is None:
duration_seconds = int((now.replace(tzinfo=None) - created_at).total_seconds())
else:
duration_seconds = int((now - created_at).total_seconds())
created_at = _make_aware_utc(created_at)
duration_seconds = max(0, int((now - created_at).total_seconds()))
else:
duration_seconds = None
@@ -90,11 +93,8 @@ def _complete_session_with_celebration(session_id, user_uuid, session):
if created_at:
if isinstance(created_at, str):
created_at = datetime.fromisoformat(created_at)
# Handle naive vs aware datetime comparison
if created_at.tzinfo is None:
duration_minutes = round((now.replace(tzinfo=None) - created_at).total_seconds() / 60, 1)
else:
duration_minutes = round((now - created_at).total_seconds() / 60, 1)
created_at = _make_aware_utc(created_at)
duration_minutes = max(0, round((now - created_at).total_seconds() / 60, 1))
else:
duration_minutes = 0