When adaptive timing shifts a late-night dose past midnight (e.g. 23:00
→ 00:42), the scheduler would create a new pending schedule on the next
day even if the dose was already taken. The proximity window was too
narrow to match the take log against the shifted time.
- Skip creating schedules for doses already taken/skipped (checks
today + yesterday logs against base_time)
- Fix midnight wraparound in proximity check for should_send_nag
- Display base_time (actual dose time) in reminders instead of the
internal adjusted_time
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bot was sending days_of_week/times but API expects days/time, so
bot-scheduled routines never got reminders. Also handle NULL frequency
from pre-migration rows and add detailed logging to routine reminder
checks for diagnosing further issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix double notifications: remove redundant check_medication_reminders()
call, use adaptive path as primary with basic as fallback
- Fix nag firing immediately: require nag_interval minutes after scheduled
dose time before first nag
- Fix missing schedules: create on-demand if midnight window was missed
- Fix wrong timezone: use user_now_for() instead of request-context
user_now() in calculate_adjusted_times()
- Fix immutable schedules: recalculate pending schedules on wake event
detection so adaptive timing actually adapts
- Fix take/skip not updating schedule: API endpoints now call
mark_med_taken/skipped so nags stop after logging a dose
- Fix skipped doses still triggering reminders: check both taken and
skipped in adaptive reminder and log queries
- Update README with tasks, AI step generation, auth refresh tokens,
knowledge base improvements, and current architecture
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Convert created_at from UTC to user's local timezone before comparing dates
- Add scheduled_time check in adaptive reminders (was only checking if any dose was taken today)
- Prevents duplicate reminders when user is in a different timezone than UTC
Make last_nag_at timezone-aware before subtracting from the tz-aware
current_time, and store future nag timestamps with timezone.utc to
prevent the mismatch recurring.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Multi-med creation form: add any number of medication cards in one session, each with independent name/dosage/unit/frequency/times settings
- Submit button labels dynamically (Add 1 / Add N Medications)
- Removed all schedule conflict checking — medications can now coexist at the same time slot as each other and as routines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The schedule editor on /dashboard/routines/new was missing the
Weekly/Every N Days toggle that was added to the edit page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scheduler: check_nagging() now calls _is_med_due_today() before creating
on-demand schedules or processing existing ones — prevents nagging
for specific_days / every_n_days meds on days they are not scheduled.
Web client: add Edit button (pencil icon) on each medication card linking
to /dashboard/medications/[id]/edit — new page pre-populates the full
form (name, dosage, unit, frequency, times, days, interval, notes)
and submits PUT /api/medications/:id on save.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB: tasks table with scheduled_datetime, reminder_minutes_before, advance_notified, status
- API: CRUD routes GET/POST /api/tasks, PATCH/DELETE /api/tasks/<id>
- Scheduler: check_task_reminders() fires advance + at-time notifications, tracks advance_notified to prevent double-fire
- Bot: handle_task() with add/list/done/cancel/delete actions + datetime resolution helper
- AI: task interaction type + examples added to command_parser
- Web: task list page with overdue/notified color coding + new task form with datetime-local picker
- Nav: replaced Templates with Tasks in bottom nav
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Web client: trustDevice now defaults to true so a refresh token is always
issued on login, preventing deauth after the 1-hour access token expiry.
Users can still uncheck the box on shared devices.
Bot: cache file path is now env-configurable (BOT_CACHE_FILE) and
defaults to /app/cache/user_cache.pkl. Docker Compose mounts a named
volume at /app/cache so the session cache survives container restarts.
saveCache() now creates the directory if it doesn't exist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users can now describe a goal and have AI auto-generate 4-7 ADHD-friendly
steps, which they can review and modify before saving.
- ai/ai_config.json: Add step_generator prompt and ai_compose examples
to command_parser so bot recognises vague task descriptions
- api/routes/ai.py: New POST /api/ai/generate-steps endpoint — calls
LLM via ai_parser, validates and sanitises returned steps
- api/main.py: Register new ai_routes module
- bot/commands/routines.py: Add ai_compose action — generates steps,
shows numbered list with durations, uses existing yes/no confirm flow
- synculous-client/src/lib/api.ts: Add api.ai.generateSteps(goal)
- synculous-client/src/app/dashboard/routines/new/page.tsx: Add
Generate with AI panel with collapsible textarea, loading spinner,
and inline error; generated steps slot into existing editable list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The command parser was returning needs_clarification for advice/how-to
questions that didn't explicitly reference a book. Updated ai_config.json
to classify any question that isn't about meds or routines as a knowledge
query, added book hints for DBT vs ADHD, and added concrete examples
(e.g. "how do I do things I don't want to do?").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
should_send_nag() was iterating all pending schedules for today without
verifying the scheduled time had actually passed. A dose scheduled for
18:00 would get nagged at 13:15. Add an early return when current_time
is before the scheduled dose time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The medication_schedules.adjusted_time column is a TIME type, but
med_logs.scheduled_time is VARCHAR. When adjusted_time values were
passed into queries against med_logs, PostgreSQL rejected the
comparison. Add _normalize_time() to convert datetime.time objects
to "HH:MM" strings at the entry points of should_send_nag,
record_nag_sent, and mark_med_taken.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix TIME object vs string comparison in scheduler preventing adaptive med
reminders from ever firing (#12, #6)
- Add frequency filtering to midnight schedule creation for every_n_days meds
- Require start_date and interval_days for every_n_days medications
- Add refresh token support (30-day) to API and bot for persistent sessions (#13)
- Add "trusted device" checkbox to frontend login for long-lived sessions (#7)
- Auto-refresh expired tokens in both bot (apiRequest) and frontend (api.ts)
- Restore bot sessions from cache on restart using refresh tokens
- Duration-aware routine scheduling conflict detection (#11)
- Add conflict check when starting routine sessions against medication times
- Add diagnostic logging to notification delivery channels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both handlers were building update_data with defaults for every field,
so a partial save (e.g. toggling one toggle) would silently reset all
other settings back to their defaults. Now only fields explicitly
present in the request body are written to the DB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. config/schema.sql — Added timezone_name VARCHAR(100) to user_preferences table + an ALTER TABLE migration at the bottom for existing DBs.
2. core/tz.py — Rewrote with dual-path timezone support:
- Request context: _get_request_tz() now checks X-Timezone-Name header (IANA) first, falls back to X-Timezone-Offset
- Background jobs: New tz_for_user(user_uuid) and user_now_for(user_uuid) read stored timezone_name from prefs, fall back to numeric offset, then UTC
- All existing function signatures (user_now(), user_today()) preserved for backward compat
3. scheduler/daemon.py — Fixed 3 bugs:
- _user_now_for() now delegates to tz.user_now_for() which uses IANA timezone names (DST-safe)
- check_nagging() — replaced datetime.utcnow() with _user_now_for(user_uuid) so nags evaluate in user's timezone
- poll_callback() — replaced single UTC midnight check with _check_per_user_midnight_schedules() that iterates users and creates daily schedules at their local midnight
4. api/routes/preferences.py — Added "timezone_name" to allowed PUT fields.
5. synculous-client/src/lib/api.ts — Added X-Timezone-Name header to every request + added timezone_name to preferences update type.
6. synculous-client/src/app/dashboard/layout.tsx — Now syncs both timezone_offset and timezone_name (via Intl.DateTimeFormat().resolvedOptions().timeZone) on session start.
Add a visual status indicator showing:
- Online/offline status with colored dot indicator
- Last seen timestamp
- Typical wake time (if available)
The indicator now displays whenever Discord notifications are enabled,
not just when presence tracking is active.
The API requires adaptive_mode when adaptive_timing_enabled is true,
but the frontend was only sending the enabled flag. This caused 400
errors when users tried to toggle adaptive timing on.
Now the toggle sends both fields when enabling, satisfying the API
validation requirements.
- #11: Add validation to prevent simultaneous scheduling of routines and medications
- Added _check_schedule_conflicts() in routines.py
- Added _check_med_schedule_conflicts() in medications.py
- Returns HTTP 409 with descriptive error on conflict
- #10: Fix medication reminders not being sent
- Added call to check_adaptive_medication_reminders() in daemon poll loop
- #9: Fix can't enable adaptive timing
- Added proper error handling and logging in update_adaptive_settings()
- Returns meaningful error message on database failures
- #8: Fix nagging not working
- Added debug logging for missing settings
- Auto-create medication schedules if they don't exist
- Improved error logging (warning -> error)