164 Commits

Author SHA1 Message Date
6ad6056125 Stage 6 Phase 4-5: TOTP setup UI and ntfy integrations settings
Adds Authentication card (password change + TOTP 5-state setup flow) and
Integrations card (ntfy master toggle, connection config, per-type toggles,
test button) to SettingsPage right column in correct order.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 04:21:36 +08:00
b134ad9e8b Implement Stage 6 Track B: TOTP MFA (pyotp, Fernet-encrypted secrets, backup codes)
- models/totp_usage.py: replay-prevention table, unique on (user_id, code, window)
- models/backup_code.py: Argon2id-hashed recovery codes with used_at tracking
- services/totp.py: Fernet encrypt/decrypt, verify_totp_code returns actual window, QR base64, backup code generation
- routers/totp.py: setup (idempotent), confirm, totp-verify (mfa_token + TOTP or backup code), disable, regenerate, status
- alembic/024: creates totp_usage and backup_codes tables
- main.py: register totp router, import new models for Alembic discovery
- requirements.txt: add pyotp>=2.9.0, qrcode[pil]>=7.4.0, cryptography>=42.0.0
- jobs/notifications.py: periodic cleanup for totp_usage (5 min) and expired user_sessions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 04:18:05 +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
5a8819c4a5 Stage 6 Phase 2-3: LockScreen rewrite + SettingsPage restructure
- LockScreen: full rewrite — username/password auth (setup/login/TOTP states),
  ambient glow blobs, UMBRA wordmark in flex flow, animate-slide-up card,
  HTTP 423 lockout banner, Loader2 spinner, client-side password validation
- SettingsPage: two-column lg grid (Profile/Appearance/Weather left,
  Calendar/Dashboard right), fixed h-16 page header, icon-anchored CardHeaders,
  labeled accent swatch grid with aria-pressed, max-w-xs removed from name
  input, upcoming days onBlur save with NaN+no-op guard, Security card removed
- useSettings: remove deprecated changePin/isChangingPin stubs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 04:06:53 +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
7f0ae0b6ef Stage 6 Phase 0-1: Foundation — Switch component, new types, useAuth rewrite, useSettings cleanup
- Create switch.tsx: pure Tailwind Switch toggle, shadcn/ui pattern (forwardRef, cn(), role=switch, aria-checked)
- types/index.ts: extend Settings with all ntfy fields; add LoginSuccessResponse, LoginMfaRequiredResponse, LoginResponse discriminated union, TotpSetupResponse
- useAuth.ts: rewrite with username/password login, ephemeral mfaToken state, totpVerifyMutation, mfaRequired boolean, full mutation exports
- useSettings.ts: remove changePinMutation logic; keep deprecated changePin/isChangingPin stubs for compile compatibility until SettingsPage is rewritten in Phase 3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 04:02:55 +08:00
5feb67bf13 Merge Stage 5: Entity Pages Enhancement (People & Locations rebuild, shared components, CategoryFilterBar QOL) 2026-02-25 01:25:01 +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
1231c4b36d Polish entity pages: groups, favourite toggle, copy icon, initials, notes
- EntityTable: replace flat rows with grouped sections (label + rows),
  each group gets its own section header for visual clarity
- EntityDetailPanel: add favourite/frequent toggle star button in header,
  add multiline flag for notes with whitespace-pre-wrap
- CopyableField: use inline-flex to keep copy icon close to text
- PeoplePage: compute initials from first_name/last_name (not nickname),
  build row groups from active filters, add toggle favourite mutation
- LocationsPage: build row groups from active filters, add toggle
  frequent mutation, pass favourite props to detail panel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 00:21:23 +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
f4b1239904 UI refresh Stage 5: rebuild People page with table/panel layout
- Rewrote PeoplePage.tsx: EntityTable + EntityDetailPanel + CategoryFilterBar
  with favourites pinned section, stat bar with birthday list, animated side panel
- Rewrote PersonForm.tsx: expanded fields (first/last/nickname, mobile, company,
  job_title, birthday+age, category autocomplete, LocationPicker, favourite star)
- Stripped constants.ts to legacy-only getRelationshipColor, removed RELATIONSHIPS
- Deleted PersonCard.tsx (replaced by table rows + detail panel)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:07:26 +08:00
765f692304 UI refresh Stage 5: redesign People & Locations pages
- Add badge color constants for relationships and location categories
- Compact h-16 headers with segmented filters replacing dropdowns
- Stat cards (Total, Upcoming Birthdays, With Contact Info / Categories)
- People search expanded to match name, email, and relationship
- Avatar initials with deterministic color hash on PersonCard
- Two-click delete via useConfirmAction on both entity cards
- Error toasts use getErrorMessage for meaningful messages
- Query invalidation includes dashboard and upcoming keys
- PersonForm migrated from Dialog to Sheet with Select dropdown
- LocationForm: import CATEGORIES from constants, invalidate dashboard/upcoming
- Normalize badge colors to text-*-400 pattern (was text-*-500)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 18:53:44 +08:00
4d5667a78a Fix migration 018: use DROP INDEX IF EXISTS for safety
The index may not exist if migration 017 partially failed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:07:51 +08:00
937118a528 Remove .claude directory from repository tracking
Local-only Claude Code context — not part of the UMBRA application.
Files remain on disk, just untracked per .gitignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:05:19 +08:00
e1e546c50c Add future roadmap and key features to CLAUDE.md
Document multi-user planning intent, reminder alerts pattern,
useConfirmAction hook, and naive datetime contract.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:01:59 +08:00
8aa662c096 Consolidate toast effects, extract useConfirmAction, extend index
- W3: Merge route-change and new-alert effects into single unified effect
- W6: Migration 018 extends due_lookup index with snoozed_until column
- S1: Extract useConfirmAction hook from TodoItem/ReminderItem
- S7: Update summary toast count on dismiss/snooze instead of dismissing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:01:50 +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
758e794880 Open snooze dropdown downward in AlertBanner
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:39:55 +08:00
0c767317ab Fix snooze dropdown clipping in toasts, add Dismiss label to banner
- SnoozeDropdown: added direction prop (up/down), toasts use 'down'
  so dropdown opens below the button instead of clipping off-screen
- AlertBanner dismiss button now shows X icon + 'Dismiss' text to
  match toast style

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:37:13 +08:00
6cd5b71d1c Add Dismiss label to toast button, reactivate on remind_at change
- Toast dismiss button now shows X icon + 'Dismiss' text to match
  the snooze button style
- Updating remind_at on a dismissed reminder clears is_dismissed
  and snoozed_until, making the reminder active again

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:31:29 +08:00
38bce21ac3 Polish toast actions and extend delete confirm timeout
- Toast: snooze/dismiss buttons side-by-side on the right, dismiss
  uses X icon, snooze shows clock icon + 'Snooze' label
- SnoozeDropdown trigger now shows text label alongside icon
- Delete confirm 'Sure?' lingers 4 seconds instead of 2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:24:40 +08:00
c609e374e7 Show 'Sure?' text on delete confirmation instead of silent icon change
Trash icon swaps to a red 'Sure?' label on first click, making the
two-click delete pattern discoverable. Applied to both TodoItem and
ReminderItem. Resets after 2 seconds if not confirmed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:16:32 +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
42e0fff40c Revert ReminderForm to single datetime-local input
Matches EventForm pattern used across UMBRA. Remind At and Recurrence
now sit side-by-side in a 2-column grid. Empty optional fields send
null instead of empty string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:11:31 +08:00
89b4bd4870 Replace snooze button group with clock icon dropdown
Single clock icon opens a dropdown with 5/10/15 min options instead
of three inline buttons. Shared SnoozeDropdown component used in
both AlertBanner and toast notifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:09:17 +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
5b1b9cc5b7 Fix QA issues: single AlertsProvider, null safety, snooze cleanup
- C1: Replaced duplicate useAlerts() calls with AlertsProvider context
  wrapping AppLayout — single hook instance, no double polling/toasts
- C2: Added null guard on remind_at in Active Reminders card format()
- W2: Clear snoozed_until when dismissing a reminder
- W5: Extracted getRelativeTime to shared lib/date-utils.ts
- S3: Replaced inline SVG with Lucide Bell component in toasts
- S4: Clear snoozed_until when remind_at is updated via PUT

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:56:56 +08:00
b2e336ab4a Fix reminder alerts not firing and add AM/PM time picker
- Backend: /due endpoint now matches both NULL and empty string for
  recurrence_rule (form was sending '' not null, excluding all reminders)
- Form: sends null instead of '' for empty recurrence_rule
- ReminderForm: replaced datetime-local with date + hour/minute/AM-PM
  selects for 12-hour time format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:50:08 +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
e3ecc11a21 Redesign Reminders page to match Todos compact list pattern
- Compact h-16 header with segmented filter, search input
- Stat cards (Active/Overdue/Dismissed) with semantic colors
- New ReminderItem component: single-line rows with grouped sections
- Optimistic delete, 2-second confirm pattern, dismiss action
- Mark Stage 4 Reminders as completed in ui_refresh.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:40:29 +08:00
250cbd0239 Address remaining QA items: indexes, validation, UX improvements
Backend:
- [W1] Add server_default=func.now() on created_at/updated_at
- [W2] Add index on reset_at column (migration 016)
- [W7] Document weekly reset edge case in code comment

Frontend:
- [W4] Extract shared isTodoOverdue() utility in lib/utils.ts,
  used consistently across TodosPage, TodoItem, TodoList
- [W5] Delete requires double-click confirmation (button turns red
  for 2s, second click confirms) with optimistic removal
- [W6] Stat cards now reflect filtered counts, not global
- [S3] Optimistic delete with rollback on error
- [S4] Add "None" to priority segmented filter
- [S7] Sort todos within groups by due date ascending

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:24:59 +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
cd868bd6ea Compact todo items to single-line rows, update stylesheet
- TodoItem: flatten to single-line row layout — checkbox, title,
  pills, date/reset info, actions all inline. Use hover:bg-card-elevated
  instead of bordered card with shadow.
- TodoList: tighten spacing from space-y-2 to space-y-0.5
- stylesheet.md: document list-view vs card-view convention —
  list pages use single-line rows, grid pages use multi-line cards

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:19:20 +08:00
66ad8902d7 Calculate recurrence schedule on todo update, not just toggle
When editing an already-completed todo to add/change recurrence or
due_date, recalculate reset_at and next_due_date so the reset info
displays immediately without needing to uncomplete and re-complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:09:42 +08:00
07cfbabcf0 Show due time in todo reset info line
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:05:54 +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
aa6502b47b Refresh Todos page UI: compact header, stat cards, grouped list
- TodosPage: h-16 header with segmented priority filter, search, category
  dropdown, show-completed toggle, and stat cards (open/completed/overdue)
- TodoItem: hover glow, priority pills, category badges, overdue/today
  date coloring, edit + delete action buttons
- TodoList: grouped sections (overdue/today/upcoming/no date/completed)
  with section headers and EmptyState action button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 15:13:13 +08:00
78a6ec3e99 Update CLAUDE.md and ui_refresh.md to reflect current state
CLAUDE.md:
- UI components count 12→16, list updated with Sheet, LocationPicker, etc.
- Calendar section lists all 5 components (CalendarSidebar, TemplateForm, etc.)
- Projects section lists all 8 components (KanbanBoard, TaskDetailPanel, etc.)
- Hooks section adds useCalendars
- API routes table adds /calendars, /event-templates, /weather, /locations search
- Settings description adds first day of week
- Fix ui_refresh.md authority link path, remove stale progress.md link

ui_refresh.md:
- Stage 3 additions: event template UX fixes, Sheet conversion, auto-prefill,
  template creation title, LocationPicker mount guard
- Updated current status line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:58:08 +08:00
27003374e3 Fix QA review warnings: model server_default, calendar_id coercion
- EventTemplate model: add server_default=func.now() to created_at
  to match migration 011 and prevent autogenerate drift
- CalendarPage: use nullish coalescing for template calendar_id
  instead of || 0 which produced an invalid falsy ID

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:50:31 +08:00
f64e181fbe Fix template form: location picker auto-open and event title wording
- LocationPicker: skip initial mount effect so dropdown doesn't auto-open
  when form loads with an existing location value
- EventForm: separate templateData/templateName props from event prop so
  template-based creation shows "Create Event from X Template" title
  instead of "Edit Event", and correctly uses Create button + no Delete
- CalendarPage: pass templateName through to EventForm

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:10:31 +08:00
b21343601b Convert TemplateForm from Dialog to Sheet side panel
Matches the EventForm UI pattern for consistency — same slide-in panel,
same layout structure with scrollable content area and pinned footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:58:55 +08:00
4a8c44ab80 Widen template form dialog from max-w-xl to max-w-2xl
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:51:59 +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
87708ae195 Remove .claude directory from git tracking
Already in .gitignore but files were tracked from before.
Local files preserved, just untracked from the repository.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 03:13:51 +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
bb87bd3743 Fix issues from QA review: cache invalidation, model server_default, route comment
- Add ['tracked-tasks'] cache invalidation to toggle mutations in
  ProjectDetail and ProjectCard so dashboard widget stays fresh
- Add server_default=sa.false() to model for consistency with migration
- Add route ordering comment above /tracked-tasks endpoint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 03:07:06 +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