- 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>
- 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>
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.
- Changed timer button to red/tomato themed pill button
- Added red background, border, and hover effects
- Shows remaining time when running
- Green pulsing indicator when active
- Made TomatoIcon slightly larger (22px) for visibility
- Added PomodoroTimer component with work/break modes
- Timer appears as icon in header, expands to full widget on click
- Supports 25m work, 5m short break, 15m long break cycles
- Shows progress bar, cycle dots, and mode switcher
- Plays sound when timer completes
- Can be minimized while running (shows pulsing indicator)
- Added 'category' column to routine_templates table
- Categorized all 12 templates into: Daily Routines, Getting Things Done, Health & Body, Errands
- Added /api/templates/categories endpoint to list unique categories
- Updated /api/templates to support filtering by category query param
- Redesigned templates page with collapsible accordion sections by category
- Categories are sorted in logical order (Daily → Work → Health → Errands)
- All categories expanded by default for easy browsing
- Dynamic start/end hours computed from actual events (+1h padding each
side) instead of hard-coded 5 AM–11 PM; falls back to 7 AM–10 PM
when no events are scheduled
- Lane algorithm (greedy interval scheduling) prevents overlapping events
from hiding each other; routines and med groups share the same lane
pool so conflicts split the column width side by side
- Outer container locked to h-screen overflow-hidden to eliminate the
page-level scrollbar; timeline inner scrollbar hidden via
[&::-webkit-scrollbar]:hidden + scrollbarWidth:none
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaces the flat routine card list with a day-oriented timeline showing
scheduled routines at their time slots, with week strip navigation and
a live "now" indicator. Adds bulk schedules API endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses the existing bot token to send DMs to users by their Discord user ID
instead of posting to a channel webhook.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.
- Add all 14 missing database tables (medications, med_logs, routines, etc.)
- Rewrite medication scheduling: support specific days, every N days, as-needed (PRN)
- Fix taken_times matching: match by created_at date, not scheduled_time string
- Fix adherence calculation: taken / expected doses, not taken / (taken + skipped)
- Add formatSchedule() helper for readable display
- Update client types and API layer
- Rename brilli-ins-client → synculous-client
- Make client PWA: add manifest, service worker, icons
- Bind dev server to 0.0.0.0 for network access
- Fix SVG icon bugs in Icons.tsx
- Add .dockerignore for client npm caching
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>