ui update and some backend functionality adding in accordance with research on adhd and ux design

This commit is contained in:
2026-02-14 17:21:37 -06:00
parent 4d3a9fbd54
commit fb480eacb2
32 changed files with 9549 additions and 248 deletions

View File

@@ -8,6 +8,7 @@ that can be called from API routes, bot commands, or scheduler.
import uuid
from datetime import datetime, date
import core.postgres as postgres
import core.tz as tz
def start_session(routine_id, user_uuid):
@@ -159,9 +160,13 @@ def clone_template(template_id, user_uuid):
return routine
STREAK_MILESTONES = {3, 7, 14, 21, 30, 60, 90, 100, 365}
def _update_streak(user_uuid, routine_id):
"""Update streak after completing a session. Resets if day was missed."""
today = date.today()
"""Update streak after completing a session. Resets if day was missed.
Returns the updated streak dict with optional 'milestone' key."""
today = tz.user_today()
streak = postgres.select_one(
"routine_streaks",
@@ -169,6 +174,7 @@ def _update_streak(user_uuid, routine_id):
)
if not streak:
new_streak_val = 1
new_streak = {
"id": str(uuid.uuid4()),
"user_uuid": user_uuid,
@@ -177,7 +183,10 @@ def _update_streak(user_uuid, routine_id):
"longest_streak": 1,
"last_completed_date": today.isoformat(),
}
return postgres.insert("routine_streaks", new_streak)
result = postgres.insert("routine_streaks", new_streak)
if new_streak_val in STREAK_MILESTONES:
result["milestone"] = new_streak_val
return result
last_completed = streak.get("last_completed_date")
if last_completed:
@@ -187,24 +196,28 @@ def _update_streak(user_uuid, routine_id):
if days_diff == 0:
return streak
elif days_diff == 1:
new_streak = streak["current_streak"] + 1
new_streak_val = streak["current_streak"] + 1
else:
new_streak = 1
new_streak_val = 1
else:
new_streak = 1
new_streak_val = 1
longest = max(streak["longest_streak"], new_streak)
longest = max(streak["longest_streak"], new_streak_val)
postgres.update(
"routine_streaks",
{
"current_streak": new_streak,
"current_streak": new_streak_val,
"longest_streak": longest,
"last_completed_date": today.isoformat(),
},
{"id": streak["id"]}
)
return streak
result = {**streak, "current_streak": new_streak_val, "longest_streak": longest}
if new_streak_val in STREAK_MILESTONES:
result["milestone"] = new_streak_val
return result
def calculate_streak(user_uuid, routine_id):
@@ -217,8 +230,14 @@ def calculate_streak(user_uuid, routine_id):
def get_active_session(user_uuid):
"""Get user's currently active session."""
return postgres.select_one(
"""Get user's currently active or paused session."""
session = postgres.select_one(
"routine_sessions",
{"user_uuid": user_uuid, "status": "active"}
)
if not session:
session = postgres.select_one(
"routine_sessions",
{"user_uuid": user_uuid, "status": "paused"}
)
return session

39
core/tz.py Normal file
View File

@@ -0,0 +1,39 @@
"""
core/tz.py - Timezone-aware date/time helpers
The frontend sends X-Timezone-Offset (minutes from UTC, same sign as
JavaScript's getTimezoneOffset — positive means behind UTC).
These helpers convert server UTC to the user's local date/time.
"""
from datetime import datetime, date, timezone, timedelta
def _get_offset_minutes():
"""Read the timezone offset header from the current Flask request.
Returns 0 if outside a request context or header is absent."""
try:
import flask
return int(flask.request.headers.get("X-Timezone-Offset", 0))
except (ValueError, TypeError, RuntimeError):
# RuntimeError: outside of request context
return 0
def _offset_to_tz(offset_minutes):
"""Convert JS-style offset (positive = behind UTC) to a timezone object."""
return timezone(timedelta(minutes=-offset_minutes))
def user_now(offset_minutes=None):
"""Current datetime in the user's timezone.
If offset_minutes is provided, uses that instead of the request header."""
if offset_minutes is None:
offset_minutes = _get_offset_minutes()
tz = _offset_to_tz(offset_minutes)
return datetime.now(tz)
def user_today(offset_minutes=None):
"""Current date in the user's timezone."""
return user_now(offset_minutes).date()