C-01: Remove nginx rate limit on event invitations endpoint — was
blocking GET (invitee list) on rapid event switching. Backend
already caps at 20 invitations per event with connection validation.
C-02: respondingRef uses string prefixes (conn-, cal-, event-) instead
of fragile numeric offsets (+100000/+200000) to prevent collisions.
W-01: get_accessible_event_scope combined into single UNION ALL query
(3 DB round-trips → 1) for calendar IDs + invitation IDs.
W-02: Dashboard and upcoming endpoints now include is_invited,
invitation_status, and display_calendar_id on event items.
W-04: LeaveEventDialog closes on error (.finally) instead of staying
open when mutation rejects.
S-01: Migration 055 FK constraint gets explicit name for consistency.
S-02: InviteSearch dropdown dismisses on blur (150ms delay for clicks).
S-03: Display calendar picker shows only owned calendars, not shared.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C-02: flush invitations before creating notifications so invitation_id
is available in notification data; eliminates extra pending fetch
- C-03: skip RSVP notification when status hasn't changed
- C-01: add defensive comments on update/delete endpoints
- W-01: add ge=1, le=2147483647 per-element validation on user_ids
- W-04: deduplicate invited_event_ids query via get_invited_event_ids()
- W-06: replace Python False with sa_false() in or_() clauses
- Frontend: extract resolveInvitationId helper, prefer data.invitation_id
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full-stack implementation of event invitations allowing users to invite connected
contacts to calendar events. Invitees can respond Going/Tentative/Declined, with
per-occurrence overrides for recurring series. Invited events appear on the invitee's
calendar with a Users icon indicator. LeaveEventDialog replaces delete for invited events.
Backend: Migration 054 (2 tables + notification types), EventInvitation model with
lazy="raise", service layer, dual-router (events + event-invitations), cascade on
disconnect, events/dashboard queries extended with OR for invited events.
Frontend: Types, useEventInvitations hook, InviteeSection (view list + RSVP buttons +
invite search), LeaveEventDialog, event invite toast with 3 response buttons, calendar
eventContent render with Users icon for invited events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Starred events should appear in the countdown widget regardless of how
far in the future they are. The _not_parent_template filter still
excludes recurring parent templates while allowing starred children.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1: Recurrence safety — MAX_OCCURRENCES=730 hard cap, adaptive 90-day
horizon for daily events (interval<7), RecurrenceRule cross-field validation,
ID bounds on location_id/calendar_id schemas.
Phase 2: Dashboard correctness — shared calendar events now included in
/dashboard and /upcoming via get_accessible_calendar_ids helper. Project stats
consolidated into single GROUP BY query (saves 1 DB round-trip).
Phase 3: Write performance — bulk db.add_all() for child events, removed
redundant SELECT in this_and_future delete path.
Phase 4: Frontend query efficiency — staleTime: 30_000 on calendar events
query eliminates redundant refetches on mount/view switch. Backend LIMIT 2000
safety guard on events endpoint.
Phase 5: Rate limiting — nginx limit_req zone on /api/events (30r/m) to
prevent DB flooding via recurrence amplification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove migration 054 (duplicate of 035 which already has all 3 indexes,
including a superior partial index for starred events)
- Fix handleEventDidMount indentation and missing semicolons
- Replace eventClassNames arrow function with static UMBRA_EVENT_CLASSES array
- Correct misleading subquery comment in dashboard.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migration 054: three indexes on calendar_events table:
- (calendar_id, start_datetime) for range queries
- (parent_event_id) for recurrence bulk operations
- (calendar_id, is_starred, start_datetime) for starred widget
Dashboard: replaced correlated subquery with single materialized
list fetch for user_calendar_ids in both /dashboard and /upcoming
handlers — eliminates 2 redundant subquery evaluations per request.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Starred events query had no upper date bound — a starred recurring
event would fill all 5 countdown slots with successive occurrences
beyond the user's configured range. Now capped to upcoming_cutoff_dt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
C-01: Add 30-day lower bound on overdue todo/reminder queries to
prevent fetching entire history.
C-02: Remove dead include_past query param — past-event filtering
is handled client-side.
W-01: Add onError toast handlers to all three inline mutations.
W-02: Snooze dropdown opens upward (bottom-full) to avoid clipping
inside the ScrollArea overflow container.
S-06: Clamp getMinutesUntilTomorrowMorning() to max 1440 to stay
within ReminderSnooze schema bounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend: Include overdue todos and snoozed reminders in /upcoming response,
add end_datetime/snoozed_until/is_overdue fields, widen snooze schema to
accept 1-1440 minutes for 1h/3h/tomorrow options.
Frontend: Full UpcomingWidget rewrite with sticky day separators (Today
highlighted in accent), collapsible groups, past-event toggle, focus mode
(Today + Tomorrow), color-coded left borders, compact type pills, relative
time for today's items, item count badge, and inline quick actions (complete
todo, snooze/dismiss reminder on hover). Card fills available height with
no dead space. DashboardPage always renders widget (no duplicate empty state).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unused imports (UserCheck, Loader2, ShieldOff) and replace
non-existent SmartphoneOff icon with Smartphone in admin components.
Includes backend query fixes, performance indexes migration, and
admin page shared utilities extraction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C3: Register User, UserSession, NtfySent, TOTPUsage, BackupCode in models/__init__.py
- C4: Enforce settings.user_id NOT NULL after backfill in migration 023, update model
- W4: Rename misleading current_user → current_settings in dashboard.py
- W5: Match NtfySettingsSection initial state defaults to backend (true/1/2)
- W8: Clear lockout banner on username/password input change in LockScreen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- W1: Guard end_datetime null checks in DayBriefing (lines 48, 95, 112)
- W2: Include active reminders in pre-5AM night briefing fallback
- S1: Extract _not_parent_template filter constant in dashboard.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Some events have recurrence_rule set to "" (empty string) instead of
NULL. The IS NULL filter excluded these legitimate non-recurring events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parent template events (with recurrence_rule set) should only be visible
through their materialized children. The events router already filtered
them out, but dashboard and upcoming endpoints did not.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add lower-bound filter (>= today) for upcoming_todos in /dashboard
- Add lower-bound filter (>= today_start) for active_reminders
- Prevents DayBriefing summary from referencing stale items
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add lower-bound date filter (>= today) for todos and reminders in /upcoming endpoint
- Accept client_date param for timezone-correct filtering
- Pass client_date from frontend to /upcoming API call
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- UpcomingWidget: single-line rows with icon/title/date/type/priority
- CalendarWidget: whitespace-nowrap time ranges, no wrapping
- TodoWidget: compact dot + title + date + badge on one line
- Active Reminders: single-line with dot indicator
- CountdownWidget: supports array of starred events
- StatsWidget: shows city name in weather card
- Dashboard API: returns starred_events array (up to 5) instead of single
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- Add rate limiting to login (5 attempts / 5 min window)
- Add secure flag to session cookies with helper function
- Add PIN min-length validation via Pydantic field_validator
- Fix naive datetime usage in todos.py (datetime.now() not UTC)
- Disable SQLAlchemy echo in production
- Remove auto-commit from get_db to prevent double commits
- Add lower bound filter to upcoming events query
- Add SECRET_KEY default warning on startup
- Remove create_all from lifespan (Alembic handles migrations)
Frontend:
- Fix ReminderForm remind_at slice for datetime-local input
- Add window.confirm() dialogs on all destructive actions
- Redirect authenticated users away from login screen
- Replace error: any with getErrorMessage helper in LockScreen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>