42 Commits

Author SHA1 Message Date
72e00f3a69 Fix QA review #2: backup code flow, audit filters, schema hardening
C-01: verifyTotp now sends backup_code field when in backup mode
C-02: Backup code input filter allows alphanumeric chars (not digits only)
W-01: Audit log ACTION_TYPES aligned with actual backend action strings
W-02: Added extra="forbid" to SetupRequest and LoginRequest schemas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 04:59:29 +08:00
e57a5b00c9 Fix QA review findings: C-01 through C-04, W-01 through W-07, S-01/S-04/S-05/S-06
Critical fixes:
- C-01: Pass user_id to _mark_sent/_already_sent (ntfy crash)
- C-02: Align frontend HTTP methods with backend routes (PATCH->PUT,
  DELETE->POST, fix reset-password/enforce-mfa/disable-mfa paths)
- C-03: Add X-Requested-With to CORS allow_headers
- C-04: Replace scalar_one_or_none with func.count for auth/status

Warning fixes:
- W-01: Batch audit log into same transaction in create_user, setup, register
- W-02: Extract users array from UserListResponse wrapper in useAdminUsers
- W-03: Update password hint from "8 chars" to "12 chars" in CreateUserDialog
- W-04: Remove password input from reset flow, show returned temp password
- W-06: Remove unused actor_alias variable in admin_dashboard
- W-07: Resolve usernames in dashboard audit entries via JOIN, remove
  ip_address column from recent_logins (not tracked on User model)

Suggestions applied:
- S-01/S-06: Add extra="forbid" to all admin mutation schemas
- S-04: Add ondelete="SET NULL" to audit_log.actor_user_id FK
- S-05: Improve registration error message for better UX

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:19:04 +08:00
d8bdae8ec3 Implement multi-user RBAC: database, auth, routing, admin API (Phases 1-6)
Phase 1: Add role, mfa_enforce_pending, must_change_password to users table.
Create system_config (singleton) and audit_log tables. Migration 026.

Phase 2: Add user_id FK to all 8 data tables (todos, reminders, projects,
calendars, people, locations, event_templates, ntfy_sent) with 4-step
nullable→backfill→FK→NOT NULL pattern. Migrations 027-034.

Phase 3: Harden auth schemas (extra="forbid" on RegisterRequest), add
MFA enforcement token serializer with distinct salt, rewrite auth router
with require_role() factory and registration endpoint.

Phase 4: Scope all 12 routers by user_id, fix dependency type bugs,
bound weather cache (SEC-15), multi-user ntfy dispatch.

Phase 5: Create admin router (14 endpoints), admin schemas, audit
service, rate limiting in nginx. SEC-08 CSRF via X-Requested-With.

Phase 6: Update frontend types, useAuth hook (role/isAdmin/register),
App.tsx (AdminRoute guard), Sidebar (admin link), api.ts (XHR header).

Security findings addressed: SEC-01, SEC-02, SEC-03, SEC-04, SEC-05,
SEC-06, SEC-07, SEC-08, SEC-12, SEC-13, SEC-15.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:06:25 +08:00
5ad0a610bd Address all QA review warnings and suggestions for lock screen feature
- [C-1] Add rate limiting and account lockout to /verify-password endpoint
- [W-3] Add max length validator (128 chars) to VerifyPasswordRequest
- [W-1] Move activeMutations to ref in useLock to prevent timer thrashing
- [W-5] Add user_id field to frontend Settings interface
- [S-1] Export auth schemas from schemas registry
- [S-3] Add aria-label to LockOverlay password input

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:20:42 +08:00
b0af07c270 Add lock screen, auto-lock timeout, and login visual upgrade
- Backend: POST /verify-password endpoint for lock screen re-auth,
  auto_lock_enabled/auto_lock_minutes columns on Settings with migration 025
- Frontend: LockProvider context with idle detection (throttled activity
  listeners, pauses during mutations), Lock button in sidebar, full-screen
  LockOverlay with password re-entry and "Switch account" option
- Settings: Security card with auto-lock toggle and configurable timeout (1-60 min)
- Visual: Upgraded login screen with large title, animated floating gradient
  orbs (3 drift keyframes), subtle grid overlay, shared AmbientBackground component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 10:03:12 +08:00
9b261574ca Fix ImportError: remove stale SettingsCreate and ChangePinRequest from schemas registry
These were removed in the auth migration but schemas/__init__.py still imported them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 08:04:11 +08:00
4a98b67b0b Address all QA review warnings and suggestions for entity pages
- W1: Add ntfy_has_token property to Settings model for safe from_attributes usage
- W2: Eager-load event location and pass location_name to ntfy template builder
- W3: Add missing accent color swatches (red, pink, yellow) to match backend Literal
- W7: Cap IP rate-limit dict at 10k entries with stale-entry purge to prevent OOM
- W9: Include user_id in SettingsResponse for multi-user readiness

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 07:48:45 +08:00
fbc452a004 Implement Stage 6 Track A: PIN → Username/Password auth migration
- New User model (username, argon2id password_hash, totp fields, lockout)
- New UserSession model (DB-backed revocation, replaces in-memory set)
- New services/auth.py: Argon2id hashing, bcrypt→Argon2id upgrade path, URLSafeTimedSerializer session/MFA tokens
- New schemas/auth.py: SetupRequest, LoginRequest, ChangePasswordRequest with OWASP password strength validation
- Full rewrite of routers/auth.py: setup/login/logout/status/change-password with account lockout (10 failures → 30-min, HTTP 423), IP rate limiting retained as outer layer, get_current_user + get_current_settings dependencies replacing get_current_session
- Settings model: drop pin_hash, add user_id FK (nullable for migration)
- Schemas/settings.py: remove SettingsCreate, ChangePinRequest, _validate_pin_length
- Settings router: rewrite to use get_current_user + get_current_settings, preserve ntfy test endpoint
- All 11 consumer routers updated: auth-gate-only routers use get_current_user, routers reading Settings fields use get_current_settings
- config.py: add SESSION_MAX_AGE_DAYS, MFA_TOKEN_MAX_AGE_SECONDS, TOTP_ISSUER
- main.py: import User and UserSession models for Alembic discovery
- requirements.txt: add argon2-cffi>=23.1.0
- Migration 023: create users + user_sessions tables, migrate pin_hash → User row (admin), backfill settings.user_id, drop pin_hash

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 04:12:37 +08:00
67456c78dd Implement Track C: NTFY push notification integration
- Add ntfy columns to Settings model (server_url, topic, auth_token, enabled, per-type toggles, lead times)
- Create NtfySent dedup model to prevent duplicate notifications
- Create ntfy service with SSRF validation and async httpx send
- Create ntfy_templates service with per-type payload builders
- Create APScheduler background dispatch job (60s interval, events/reminders/todos/projects)
- Register scheduler in main.py lifespan with max_instances=1
- Update SettingsUpdate with ntfy validators (URL scheme, topic regex, lead time ranges)
- Update SettingsResponse with ntfy fields; ntfy_has_token computed, token never exposed
- Add POST /api/settings/ntfy/test endpoint
- Update GET/PUT settings to use explicit _to_settings_response() helper
- Add Alembic migration 022 for ntfy settings columns + ntfy_sent table
- Add httpx==0.27.2 and apscheduler==3.10.4 to requirements.txt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 04:04:23 +08:00
1806e15487 Address all QA review warnings and suggestions for entity pages
Warnings fixed:
- 3.1: _compute_display_name stale-data bug on all-names-clear
- 3.3: Location getValue unsafe type cast replaced with typed helper
- 3.5: Explicit updated_at timestamp refresh in locations router
- 3.6: Drop deprecated relationship column (migration 021, model, schema, TS type)

Suggestions fixed:
- 4.1: CategoryAutocomplete keyboard navigation (ArrowUp/Down, Enter, Escape)
- 4.2: Mobile detail panel backdrop click-to-close on both pages
- 4.3: PersonCreate whitespace bypass in require_some_name validator
- 4.5/4.6: Extract SortIcon, DataRow, SectionHeader from EntityTable render body
- 4.8: PersonForm sends null instead of empty string for birthday
- 4.10: Remove unnecessary executeDelete wrapper in EntityDetailPanel

Also includes previously completed fixes from prior session:
- 2.1: Remove Z suffix from naive timestamp in formatUpdatedAt
- 3.2: Drag-then-click conflict prevention in SortableCategoryChip
- 3.4: localStorage JSON shape validation in useCategoryOrder
- 4.4: Category chip styling consistency (both pages use inline hsl styles)
- 4.9: restrictToHorizontalAxis modifier on CategoryFilterBar drag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:04:20 +08:00
1b78dadf75 Fix bugs and action remaining QA suggestions
Bugs fixed:
- formatUpdatedAt treats naive UTC timestamps as UTC (append Z before parsing)
- PersonForm/LocationForm X button now inline with star toggle, matching panel style
- LocationForm contact placeholder changed from +44 to +61

QA suggestions actioned:
- CategoryAutocomplete: replace blur setTimeout with onPointerDown preventDefault
- CategoryFilterBar: replace hardcoded 600px maxWidth with 100vw
- Location "other" category shows dash instead of styled badge
- Delete dead legacy constants files (people/constants.ts, locations/constants.ts)
- EntityTable rows: add tabIndex, Enter/Space keyboard navigation, focus ring
- Replace Record<string, unknown> casts with typed keyof accessors
- Add email validation (field_validator) to Person and Location schemas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:46:38 +08:00
8cbc95939a Fix issues from QA review: schema safety, search scope, clipboard handling, UX polish
- Remove `name` from PersonUpdate schema (always computed, prevents bypass)
- Auto-split legacy `name` into first/last on create when only name provided
- Expand backend search to cover first_name, last_name, nickname, email, company
- Add server_default=text('false') to is_favourite and is_frequent model columns
- Add .catch() to clipboard API call in CopyableField
- Extract duplicate renderHeader into shared function in PeoplePage
- Add Escape key handler to close mobile detail panel overlays
- Extract calculate() out of useTableVisibility effects to single function
- Guard getInitials against empty string input

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:19:22 +08:00
cb9f74a387 Entity pages enhancement: backend model extensions, shared components, Locations rebuild, panel animations
- Add migrations 019/020: extend Person (first/last name, nickname, is_favourite, company, job_title, mobile, category) and Location (is_frequent, contact_number, email)
- Update Person/Location models, schemas, and routers with new fields + name denormalisation
- Create shared component library: EntityTable, EntityDetailPanel, CategoryFilterBar, CopyableField, CategoryAutocomplete, useTableVisibility hook
- Rebuild LocationsPage: table layout with sortable columns, detail side panel, category filter bar, frequent pinned section
- Extend LocationForm with contact number, email, frequent toggle, category autocomplete
- Add animated panel transitions to ProjectDetail (55/45 split with cubic-bezier easing)
- Update TypeScript interfaces for Person and Location

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:10:26 +08:00
17f331477f Fix QA review issues: error handlers, validation, accessibility, cleanup
- C1: Add onError handlers to dismiss/snooze mutations in useAlerts
- C2: Clear snoozed_until when dismissing via update endpoint
- W1: Handle future dates in getRelativeTime
- W2+S3: Add Escape key, aria-expanded, role=menu to SnoozeDropdown
- W4: Remove redundant field_validator (Literal suffices)
- W7: Validate recurrence_rule with Literal['daily','weekly','monthly']
- S2: Clean up delete confirmation setTimeout on unmount
- S6: Cap AlertBanner height with scroll for many alerts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:52:28 +08:00
daf2a4d5f1 Fix snooze/due using container UTC instead of client local time
Docker container datetime.now() returns UTC, but all stored datetimes
are naive local time from the browser. Both /due and /snooze now
accept client_now from the frontend, ensuring snooze computes from
the user's actual current time, not the container's clock.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:12:31 +08:00
b7251b72c7 Address remaining QA items: index, validation, accessibility, guard
- S1: Add composite index (is_active, is_dismissed, remind_at) for
  /due query performance with multi-user scaling
- W3: Snooze endpoint rejects dismissed/inactive reminders (409)
- W4: Custom field_validator on ReminderSnooze for clear error message
- S2: aria-label on all snooze/dismiss buttons in banner and toasts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:02:19 +08:00
5080e23256 Add real-time reminder alerts with snooze/dismiss
- Backend: GET /api/reminders/due endpoint, PATCH snooze endpoint,
  snoozed_until column + migration
- Frontend: useAlerts hook polls every 30s, fires Sonner toasts on
  non-dashboard pages (max 3 + summary), renders AlertBanner on
  dashboard below stats row
- Dashboard Active Reminders card filters out items shown in banner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:15:56 +08:00
79b3097410 Fix QA review issues: transaction safety, validation, accessibility
Critical fixes:
- [C1] _reactivate_recurring_todos now uses flush + with_for_update
  instead of mid-request commit; get_todos commits the full transaction
- [C2] recurrence_rule validated via Literal["daily","weekly","monthly"]
  in Pydantic schemas (rejects invalid values with 422)

Warnings fixed:
- [W3] Clear due_time when due_date is set to null in update endpoint

Suggestions applied:
- [S2] Wrap filteredTodos in useMemo for consistent memoization
- [S6] Add aria-labels to edit/delete icon buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:38:01 +08:00
8e0af3ce86 Add optional due time to todos, fix date not being optional
Backend:
- Add due_time (TIME, nullable) column to todos model + migration 015
- Add due_time to Create/Update/Response schemas

Frontend:
- Add due_time to Todo type
- TodoForm: add time input, convert empty strings to null before
  sending (fixes date appearing required — Pydantic rejected '' as date)
- TodoItem: display clock icon + time when due_time is set

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 19:59:38 +08:00
46d4c5e28b Implement todo recurrence logic with auto-reset scheduling
Backend:
- Add reset_at (datetime) and next_due_date (date) columns to todos
- Toggle endpoint calculates reset schedule when completing recurring todos:
  daily resets next day, weekly resets start of next week (respects
  first_day_of_week setting), monthly resets 1st of next month
- GET /todos auto-reactivates recurring todos whose reset_at has passed,
  updating due_date to next_due_date and clearing completion state
- Alembic migration 014

Frontend:
- Add reset_at and next_due_date to Todo type
- TodoItem shows recurrence badge (Daily/Weekly/Monthly) in purple
- Completed recurring todos display reset info:
  "Resets Mon 02/03/26 · Next due 06/03/26"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:04:12 +08:00
b1075d6ad4 Remove duration_minutes from event templates, auto-prefill event times
- Drop duration_minutes column from event_templates (model, schema, migration)
- Remove duration field from TemplateForm UI and TypeScript types
- EventForm now defaults start to current date/time and end to +1 hour
  when no initial values are provided (new events and template-based events)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:55:27 +08:00
c67567e186 Resolve remaining QA suggestions: shared constants, query tuning, cleanup
- Extract duplicate statusColors/statusLabels to projects/constants.ts
- Add staleTime + select to sidebar tracked projects query to reduce
  refetches and narrow data to only id/name
- Gate TrackedProjectsWidget query on settings being loaded
- Remove unnecessary from_attributes on TrackedTaskResponse schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 03:10:48 +08:00
819a4689b8 Add track project feature with sidebar nav and dashboard widget
Adds is_tracked boolean to projects, expandable tracked projects
in sidebar navigation, pin toggle on project cards/detail, and a
dashboard widget showing upcoming tasks from tracked projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 01:20:36 +08:00
b5ec38f4b8 Fix kanban subtask view, project statuses, column order
- Add blocked/review/on_hold to ProjectStatus (backend + frontend)
- ProjectForm: add new status options to dropdown
- ProjectDetail: add status colors/labels for new statuses
- KanbanBoard: reorder columns (review before completed)
- KanbanBoard: decouple subtask view from selectedTaskId via
  kanbanParentTaskId — closing task panel stays in subtask view,
  "Back to all tasks" button now works
- TaskDetailPanel: show status badge on subtask rows so kanban
  drag-and-drop status changes are visible

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:35:46 +08:00
80f3f3ed10 Calendar enhancements: scroll navigation, birthday color editing, event templates
- Add wheel scroll navigation in month view (debounced, prevents rapid scrolling)
- Allow editing color on system calendars (Birthdays) - name field disabled
- Event templates: full CRUD backend (model, schema, router, migration 011)
- Event templates: sidebar section with create/edit/delete, click to pre-fill EventForm
- Register event_templates router at /api/event-templates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:34:16 +08:00
a11fcbcbcc Projects enhancements: inline editing, extended statuses, subtask interactions, kanban subtask view
- Inline task editing in TaskDetailPanel (replaces sheet-based edit flow)
- Extended task statuses: blocked, review, on_hold with color maps everywhere
- Click subtasks to navigate, delete subtasks from detail pane
- Kanban shows subtasks when a task with subtasks is selected
- Subtask sorting follows parent sort mode (priority/due_date)
- Progress bar on task rows showing subtask completion
- Default due date inheritance from parent task or project
- New status options in TaskForm select dropdown

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:04:10 +08:00
4169c245c2 Global enhancements: none priority, optional remind_at, required labels, textarea flex, remove color picker
- Add "none" priority (grey) to task/todo schemas, types, and all priority color maps
- Make remind_at optional on reminders (schema, model, migration 010)
- Add required prop to Label component with red asterisk indicator
- Add invalid:ring-red-500 to Input, Select, Textarea base classes
- Mark mandatory fields with required labels across all forms
- Replace fixed textarea rows with min-h + flex-1 for auto-expand
- Remove color picker from ProjectForm
- Align TaskRow metadata into fixed-width columns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:58:19 +08:00
6e50089201 ProjectDetail overhaul: master-detail layout, comments, sorting, kanban
- Backend: TaskComment model + migration, comment CRUD endpoints,
  task reorder endpoint, updated selectinload for comments
- Frontend: Two-panel master-detail layout with TaskRow (compact)
  and TaskDetailPanel (full details + comments section)
- Sort toolbar: manual (drag-and-drop via @dnd-kit), priority, due date
- Kanban board view with drag-and-drop between status columns
- Responsive: mobile falls back to overlay panel on task select

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 03:22:44 +08:00
c62f8bc2a2 Add first day of week setting and fix calendar header alignment
- Add first_day_of_week column to settings (0=Sunday, 1=Monday)
- Add Calendar section in Settings with toggle button
- Pass firstDay to FullCalendar from settings
- Align calendar toolbar and sidebar header to h-16 (matches UMBRA header)
- Remove border/padding wrapper from calendar grid for full-width layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:33:45 +08:00
5701e067dd Fix issues from QA review: critical bugs, warnings, and accessibility
- C1: Nominatim search already uses run_in_executor (non-blocking)
- C2: Ensure target event is deleted in "this_and_future" scope
- W3: Add Field constraints (ge/le) on RecurrenceRule fields
- W4: Add safety cleanup for body overflow on Sheet unmount
- W5: Block drag-drop/resize on recurring events (must use scope dialog)
- W6: Discard stale LocationPicker responses via request ID
- S8: Add role="dialog" and aria-modal to Sheet component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:22:57 +08:00
232bdd3ef2 Fix recurrence_rule validation + smoother Sheet animation
- Add field_validator to coerce recurrence_rule from legacy strings,
  empty strings, and JSON strings into RecurrenceRule or None
- Increase Sheet slide-in duration to 350ms with cubic-bezier(0.16, 1, 0.3, 1)
  for a more premium feel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:00:16 +08:00
d811890509 Add Sheet forms, recurrence UI, all-day fix, LocationPicker
- Sheet component: slide-in panel replacing Dialog for all forms
- EventForm: structured recurrence picker, all-day end-date offset fix,
  LocationPicker with OSM search integration
- CalendarPage: scope dialog for editing/deleting recurring events
- TodoForm/ReminderForm/LocationForm: migrated to Sheet with 2-col layouts
- LocationPicker: debounced search combining local DB + Nominatim results
- Backend: /locations/search endpoint with OSM proxy
- CSS: slimmer all-day event bars in calendar grid
- Types: RecurrenceRule interface, extended CalendarEvent fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 00:42:12 +08:00
d89758fedf Add materialized recurring events backend
- Migration 007: parent_event_id (self-ref FK CASCADE), is_recurring, original_start columns on calendar_events
- CalendarEvent model: three new Mapped[] columns for recurrence tracking
- RecurrenceRule Pydantic model: typed schema for every_n_days, weekly, monthly_nth_weekday, monthly_date
- CalendarEventCreate/Update: accept structured RecurrenceRule (router serializes to JSON string for DB)
- CalendarEventUpdate: edit_scope field (this | this_and_future)
- CalendarEventResponse: exposes parent_event_id, is_recurring, original_start
- recurrence.py service: generates unsaved child ORM objects from parent rule up to 365-day horizon
- GET /: excludes parent template rows (children are displayed instead)
- POST /: creates parent + bulk children when recurrence_rule provided
- PUT /: scope=this detaches occurrence; scope=this_and_future deletes future siblings and regenerates
- DELETE /: scope=this deletes one; scope=this_and_future deletes future siblings; no scope deletes all (CASCADE)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 00:37:21 +08:00
093bceed06 Add multi-calendar backend support with virtual birthday events
- New Calendar model and calendars table with system/default flags
- Alembic migration 006: creates calendars, seeds Personal+Birthdays, migrates existing events
- CalendarEvent model gains calendar_id FK and calendar_name/calendar_color properties
- Updated CalendarEventCreate/Response schemas to include calendar fields
- New /api/calendars CRUD router (blocks system calendar deletion/rename)
- Events router: selectinload on all queries, default-calendar assignment on POST, virtual birthday event generation from People with birthdays when Birthdays calendar is visible

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 19:07:35 +08:00
4b5649758a Address code review findings for weather coordinates feature
- CRIT-1: Add lat/lon validation ([-90,90] and [-180,180]) in Pydantic schema
- WARN-1: Replace deprecated get_event_loop() with get_running_loop()
- SUG-1: Add GeoSearchResult response model to /search endpoint
- SUG-2: Dashboard weather query enables on coordinates too, not just city
- SUG-3: Clean up debounce timer on component unmount
- SUG-4: Fix geocoding URL from HTTP to HTTPS
- SUG-5: Add fallback display when weather_city is null but coords exist

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:17:41 +08:00
1545da48e5 Add coordinate-based weather lookup with location search
Replace plain-text city input with geocoding search that resolves
lat/lon coordinates for accurate OpenWeatherMap queries. Users can
now search, see multiple results with state/country detail, and
select the exact location.

- Add GET /api/weather/search endpoint (OWM Geocoding API)
- Add weather_lat/weather_lon columns to settings model + migration
- Use lat/lon for weather API calls when available, fall back to city name
- Replace settings text input with debounced search + dropdown selector
- Show selected location as chip with clear button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:11:02 +08:00
ca8b654471 Dashboard Phase 2: weather widget, starred events, quick add, thinner events
- Add weather router with OpenWeatherMap integration and 1-hour cache
- Add is_starred column to calendar events with countdown widget
- Add weather_city setting with Settings page input
- Replace people/locations stats with open todos count + weather card
- Add quick-add dropdown (event/todo/reminder) to dashboard header
- Make CalendarWidget events single-line thin rows
- Add rain warnings to smart briefing when chance > 40%
- Invalidate dashboard/upcoming queries on form mutations
- Migration 004: is_starred + weather_city columns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:15:43 +08:00
1d21caaa62 Dashboard personalization: preferred name, colored dots, smart briefing
- Add preferred_name column to settings model/schema with migration
- Settings page gets Profile card with name input (saves on blur/enter)
- Dashboard greeting now shows "Good evening, Kyle." when name is set
- WeekTimeline dots use event's actual color when available
- New DayBriefing component shows time-of-day-aware contextual summary

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 07:37:43 +08:00
27c65ce40d Fix Round 2 code review findings: type safety, security, and correctness
Backend:
- Add Literal types for status/priority fields (project_task, todo, project schemas)
- Add AccentColor Literal validation to prevent CSS injection (settings schema)
- Add PIN max-length (72 char bcrypt limit) validation
- Fix event date filtering to use correct range overlap logic
- Add revocation check to auth_status endpoint for consistency
- Config: env-aware SECRET_KEY fail-fast, configurable COOKIE_SECURE

Frontend:
- Add withCredentials to axios for cross-origin cookie support
- Replace .toISOString() with local date formatter in DashboardPage
- Replace `as any` casts with proper indexed type access in forms
- Nginx: add CSP, Referrer-Policy headers; remove deprecated X-XSS-Protection
- Nginx: duplicate security headers in static asset location block

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 15:18:49 +08:00
1aaa2b3a74 Fix code review findings: security hardening and frontend fixes
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>
2026-02-16 07:49:21 +08:00
ccfbf6df96 Add subtasks feature to project tasks
Backend:
- Add self-referencing parent_task_id FK on project_tasks with CASCADE delete
- Add Alembic migration 002 for parent_task_id column + index
- Update schemas with parent_task_id in create, nested subtasks in response
- Chain selectinload for subtasks on all project queries
- Validate parent must be top-level task (single nesting level only)

Frontend:
- Add parent_task_id and subtasks[] to ProjectTask type
- ProjectDetail: expand/collapse chevrons, subtask progress bars, inline
  subtask rendering with accent left border, add/edit/delete subtask buttons
- TaskForm: accept parentTaskId prop, include in create payload, context-aware
  dialog title (New Task vs New Subtask)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:31:46 +08:00
1f6519635f Initial commit 2026-02-15 16:13:41 +08:00