433 Commits

Author SHA1 Message Date
fee454fc33 Fix 503s behind reverse proxy: add uvicorn --proxy-headers
FastAPI trailing-slash redirects (307) were using http:// instead of
https:// because uvicorn wasn't reading X-Forwarded-Proto from the
reverse proxy. When Pangolin (TLS-terminating proxy) received the
http:// redirect it returned 503, breaking all list endpoints
(/events, /calendars, /settings, /projects, /people, /locations).

Adding --proxy-headers makes uvicorn honour X-Forwarded-Proto so
redirects use the correct scheme. --forwarded-allow-ips '*' trusts
headers from any IP since nginx sits on the Docker bridge network.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:17:39 +08:00
0c7d057654 Auto-derive COOKIE_SECURE from ENVIRONMENT setting
COOKIE_SECURE now defaults to None and auto-derives from ENVIRONMENT
(production → true, else false) via a Pydantic model_validator. Explicit
env var values are still respected as an override escape hatch. Adds a
startup log line showing the resolved value. Restructures .env.example
with clear sections and inline docs, removes redundant production
checklist block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:38:54 +08:00
21aa670a39 Extract real client IP from proxy headers instead of Docker bridge IP
Nginx already forwards X-Forwarded-For and X-Real-IP, but the backend
read request.client.host directly — always returning 172.18.0.x. Added
get_client_ip() helper to audit service; updated all 13 call sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:20:07 +08:00
f8c2df9328 Merge multi-user RBAC with login flow fixes, QA + pentest remediations
Multi-user RBAC (8 phases):
- user_id FKs on all 12 domain models (migrations 026-037)
- Per-user query scoping on all 14 routers
- Admin portal: IAM, system config, audit logs
- Registration flow with optional MFA enforcement
- Role-based authorization (admin/standard)

Login flow fixes:
- Block inactive user login (HTTP 403) before session creation
- Optimistic setQueryData eliminates login flicker
- Unified inline error alerts (401/403/423)
- Axios 401 interceptor excludes auth endpoints

Security hardening:
- Global CSRF middleware (X-Requested-With header check)
- extra="forbid" on all Pydantic input schemas
- max_length on all string fields, ge/le on path IDs
- Timing-safe login (M-02 dummy hash)
- Password reuse prevention on change-password
- Setup endpoint 500 fix (func.count vs scalar_one_or_none)

Verified: QA review (0 critical) + pentest (51+ test cases, 0 exploitable)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 03:02:28 +08:00
a313ce8b32 Update README for multi-user RBAC release
- Add multi-user RBAC, admin portal, and registration to features
- Update tech stack (37 migrations, CSRF middleware, RBAC)
- Expand security section with IDOR protection, CSRF, input validation,
  timing safety, inactive user blocking, password reuse prevention
- Update project structure (18 models, 13 schema modules, admin components)
- Add admin endpoints to API overview
- Note pentest verification (51+ test cases, 0 exploitable findings)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 02:58:52 +08:00
d269742aa2 Fix pentest findings: setup 500 error + password reuse prevention
- L-01: Setup endpoint used scalar_one_or_none() on unbounded User
  query, throwing 500 MultipleResultsFound when >1 user exists.
  Replaced with select(func.count()) for a reliable check.
- L-02: Change-password allowed reusing the same password, defeating
  must_change_password enforcement. Added equality check before
  accepting the new password.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 02:42:00 +08:00
1aeb725410 Fix issues from QA review: hash upgrade ordering, interceptor scope, guard
- W-01: Move is_active check before hash upgrade so disabled accounts
  don't get their password hash silently mutated on rejected login
- W-02: Narrow interceptor exclusion to specific auth endpoints instead
  of blanket /auth/* prefix (future-proofs against new auth routes)
- W-03: Add null guard on optimistic setQueryData to handle undefined
  cache gracefully instead of spreading undefined
- S-01: Clear loginError when switching from register back to login mode
- S-03: Add detail dict to auth.login_blocked_inactive audit event

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 02:13:48 +08:00
c4c06be148 Fix login error vanishing: exclude auth endpoints from 401 interceptor
The global axios 401 interceptor was firing window.location.href =
'/login' on every 401 response, including POST /auth/login with wrong
credentials. This caused a full page reload to /login, which remounted
the entire React tree and reset all LockScreen state (loginError,
username, password) before the user could see the error alert.

Fix: skip the redirect for /auth/* endpoints, which legitimately
return 401 for invalid credentials. The interceptor still redirects
to /login for expired sessions on protected API calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:43:18 +08:00
5426657b2e Fix login error alert disappearing due to browser autofill
After a failed login, the browser's password manager fires onChange
events on the username/password inputs (clearing or resetting them).
The onChange handlers were calling setLoginError(null), which wiped
the error alert immediately after it appeared.

Fix: remove setLoginError(null) from input onChange handlers. The
error now clears at the start of the next submit attempt via the
existing setLoginError(null) in handleCredentialSubmit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:37:57 +08:00
b2d81f7015 Block inactive user login + fix login flicker + inline error alerts
- Backend: reject is_active=False users with HTTP 403 after password
  verification but before session creation (prevents last_login_at
  update, lockout reset, and MFA token issuance for disabled accounts)
- Frontend: optimistic setQueryData on successful login eliminates the
  form flash between mutation success and auth query refetch
- LockScreen: replace lockoutMessage + toast.error with unified
  loginError inline alert for 401/403/423 responses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:21:06 +08:00
c68fd69cdf Make temp password click-to-copy in reset password flow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:35:18 +08:00
8582b41b03 Add user profile fields + IAM search, email column, detail panel
Backend:
- Migration 037: add email, first_name, last_name to users table
- User model: add 3 profile columns
- Admin schemas: extend UserListItem/UserDetailResponse/CreateUserRequest
  with profile fields, email validator, name field sanitization
- _create_user_defaults: accept optional preferred_name kwarg
- POST /users: set profile fields, email uniqueness check, IntegrityError guard
- GET /users/{id}: join Settings for preferred_name, include must_change_password/locked_until

Frontend:
- AdminUser/AdminUserDetail types: add profile + detail fields
- useAdmin: add CreateUserPayload profile fields + useAdminUserDetail query
- CreateUserDialog: optional profile section (first/last name, email, preferred name)
- IAMPage: search bar filtering on username/email/name, email column in table,
  row click to select user with accent highlight
- UserDetailSection: two-column detail panel (User Info + Security & Permissions)
  with inline role editing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:40:20 +08:00
c3654dc069 Fix audit log target for deleted users + create user 500 error
1. Audit log: COALESCE target_username with detail JSON fallback so
   deleted users still show their username in the target column
   (target_user_id is SET NULL by FK cascade, but detail JSON
   preserves the username).

2. Create/get user: add exclude={"active_sessions"} to model_dump()
   calls — UserListItem defaults active_sessions=0, so model_dump()
   includes it, then the explicit active_sessions=N keyword argument
   causes a duplicate keyword TypeError. DB commit already happened,
   so the user exists but the response was a 500.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:44:10 +08:00
48e15fa677 UX polish for delete-user: username toast, hide self-delete
S-03: Delete toast now shows the deleted username from the API response
S-04: Delete button hidden for the current admin's own row (backend
still guards with 403, but no reason to show a dead button)

Adds username to auth status response so the frontend can identify
the current user.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:30:43 +08:00
e7cb6de7d5 Add admin delete-user with full cascade cleanup
Migration 036 adds ondelete rules to 5 transitive FKs that would
otherwise block user deletion (calendar_events via calendars,
project_tasks via projects, todos via projects, etc.).

DELETE /api/admin/users/{user_id} with self-action guard, last-admin
guard, session revocation, and audit logging. Frontend gets a red
two-click confirm button in the IAM actions menu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:20:47 +08:00
c56830ddb0 Fix SyntaxError in admin.py: add default to Request params
Three functions (update_user_role, toggle_mfa_enforce, toggle_user_active)
had `request: Request` without a default after Path params with defaults,
causing Python SyntaxError on import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:31:25 +08:00
1ebc41b9d7 L-03: Session 7-day sliding window with 30-day hard ceiling
Reduce session expiry from 30 days to 7 days of inactivity while
preserving a 30-day absolute token lifetime for itsdangerous:

- SESSION_MAX_AGE_DAYS=7: sliding window for DB expires_at + cookie
- SESSION_TOKEN_HARD_CEILING_DAYS=30: itsdangerous max_age (prevents
  rejecting renewed tokens whose creation timestamp exceeds 7 days)
- get_current_user: silently extends expires_at and re-issues cookie
  when >1 day has elapsed since last renewal
- Active users never notice; 7 days of inactivity forces re-login;
  30-day absolute ceiling forces re-login regardless of activity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:45:15 +08:00
8e27f2920b M-02: Timing-safe login prevents username enumeration
When a login targets a non-existent username, run Argon2id against a
pre-computed dummy hash so response time (~60ms) matches wrong-password
attempts. Also reorder the login flow to run verify_password_with_upgrade
BEFORE the lockout check, preventing timing side-channels that could
distinguish locked accounts from wrong passwords.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:44:27 +08:00
2f58282c31 M-01+M-03: Add input validation and extra=forbid to all request schemas
- Add max_length constraints to all string fields in request schemas,
  matching DB column limits (title:255, description:5000, etc.)
- Add min_length=1 to required name/title fields
- Add ConfigDict(extra="forbid") to all request schemas to reject
  unknown fields (prevents silent field injection)
- Add Path(ge=1, le=2147483647) to all integer path parameters across
  all routers to prevent integer overflow → 500 errors
- Add max_length to TOTP inline schemas (code:6, mfa_token:256, etc.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:43:55 +08:00
581efa183a H-01: Add global CSRF middleware for all mutating endpoints
Replace the admin-only verify_xhr dependency with a pure ASGI
CSRFHeaderMiddleware that validates X-Requested-With: XMLHttpRequest
on all POST/PUT/PATCH/DELETE requests globally. Pre-auth endpoints
(login, setup, register, totp-verify, enforce-setup/confirm) are
exempt. This closes the CSRF gap where non-admin routes accepted
requests without origin validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:39:42 +08:00
9f7bbbfcbb Add per-user active session counts to IAM user list
Move active_sessions field from UserDetailResponse into UserListItem
so GET /admin/users returns session counts. Uses a correlated subquery
to count non-revoked, non-expired sessions per user.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:26:32 +08:00
a128005ae5 Fix create-mode crash in detail panels (null entity access)
The desktop detail panels are pre-mounted (always in DOM, hidden with w-0).
useState(isCreating) only captures the initial value on mount (false), so
when isCreating later becomes true via props, isEditing stays false. The
view-mode branch then runs with a null entity, crashing on property access.

Fix: use (isEditing || isCreating) for all conditionals that gate between
edit/create form and view mode, ensuring the form always renders when
isCreating is true regardless of isEditing state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 09:57:19 +08:00
f07ce02576 Fix crash when creating new todo/reminder/event (null.priority)
All three DetailPanel components initialized isEditing=false even
when isCreating=true. The useEffect that flips it to true runs AFTER
the first render, so the view-mode branch executes with todo=null,
crashing on null.priority. Initialize isEditing from isCreating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:35:31 +08:00
0fc3f1a14b Allow dots in usernames (e.g. user.test)
Added . to the username character whitelist regex. No security
reason to exclude it — dots are standard in usernames.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:03:25 +08:00
e860723a2a Fix Edit Role submenu overflowing right edge of viewport
Submenu was positioned left-full (opening rightward) but the parent
dropdown is already at the right edge. Changed to right-full so it
opens leftward into available space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 06:35:03 +08:00
4fc85684ea Fix IAM user actions dropdown clipped by overflow-x-auto
The table wrapper's overflow-x-auto forced overflow-y to also clip,
hiding the 3-dot actions dropdown below the container boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 06:23:08 +08:00
2438cdcf25 Fix migration 034 failing on fresh DB: drop index not constraint
Migration 022 created a unique INDEX (ix_ntfy_sent_notification_key),
not a named unique CONSTRAINT. Migration 034 was trying to drop a
constraint name that only existed on upgraded DBs. Fixed to drop the
index instead, which works on both fresh and upgrade paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 06:06:13 +08:00
619e220622 Fix QA review #2: W-03/W-04, S-01 through S-04
W-03: Unify split transactions — _create_db_session() now uses flush()
      instead of commit(), callers own the final commit.
W-04: Time-bound dedup key fetch to 7-day purge window.
S-01: Type admin dashboard response with RecentLoginItem/RecentAuditItem.
S-02: Convert starred events index to partial index WHERE is_starred = true.
S-03: EventTemplate.created_at default changed to func.now() for consistency.
S-04: Add single-worker scaling note to weather cache.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 05:41:16 +08:00
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
72ac1d53fb Fix migration 030 failing on fresh DB with no admin user
Migration 006 seeds default calendar rows. On a fresh install, no users
exist when migration 030 runs, so the backfill SELECT returns NULL and
SET NOT NULL fails. Now deletes orphan calendars before enforcing the
constraint — account setup will recreate defaults for new users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 04:49:57 +08:00
cbf4663e8d Fix TS build errors and apply remaining QA fixes
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>
2026-02-27 04:42:23 +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
e5a7ce13e0 Merge multi-user RBAC implementation (Phases 1-8)
Complete multi-user integration for UMBRA:
- Database: role enum, system_config, audit_log, user_id FKs on all tables
- Auth: RBAC with require_role(), registration, MFA enforcement
- Routing: All 12 routers scoped by user_id
- Admin API: 14 endpoints for IAM, config, audit, dashboard
- Admin Portal: IAM page, config page, dashboard with stats
- Registration flow: LockScreen with register/MFA-enforce/password-change
- Security: SEC-01 through SEC-15 mitigations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:06:43 +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
2ec70d9344 Add Phase 7 admin portal frontend (IAM, Config, Dashboard)
Creates 7 files: useAdmin hook with TanStack Query v5, AdminPortal
layout with horizontal tab nav, IAMPage with user table + stat cards
+ system settings, UserActionsMenu with two-click confirms, CreateUserDialog,
ConfigPage with paginated audit log + action filter, AdminDashboardPage
with stats + recent logins/actions tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:40:16 +08:00
464b8b911f Phase 8: Registration flow & MFA enforcement UI
- backend: add POST /auth/totp/enforce-setup and /auth/totp/enforce-confirm
  endpoints that operate on mfa_enforce_token (not session cookie), generate
  TOTP secret/QR/backup codes, verify confirmation code, enable TOTP, clear
  mfa_enforce_pending flag, and issue a full session cookie
- frontend: expand LockScreen to five modes — login, first-run setup,
  open registration, TOTP challenge, MFA enforcement setup (QR -> verify ->
  backup codes), and forced password change; all modes share AmbientBackground
  and the existing card layout; registration visible only when
  authStatus.registration_open is true

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:39:18 +08:00
b2ecedbc94 Support X-Forwarded-Proto from upstream reverse proxy
Add nginx map directive to prefer X-Forwarded-Proto header from
Traefik/Pangolin when present, falling back to $scheme for direct
internal HTTP access. Applied to both nginx.conf and proxy-params.conf.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 03:25:14 +08:00
132226120b Merge security/pentest-remediation-2026-02-26: remediate F-01, F-02, F-06 2026-02-26 02:43:11 +08:00
92efeba2ec Fix QA review findings: update docs and comments
- W-01: Update README.md security section to reflect removed in-memory
  rate limiter and add /setup to nginx rate-limited endpoint list
- W-02: Replace misleading ALLOW_LAN_NTFY reference with actionable
  guidance to edit _BLOCKED_NETWORKS directly
- S-04: Add comment explaining burst=3 on /api/auth/setup vs burst=5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 02:42:59 +08:00
a0b50a2b13 Remediate pentest findings F-01, F-02, F-06
- Remove ineffective in-memory IP rate limiter from auth.py (F-01):
  nginx limit_req_zone handles real-IP throttling, DB lockout is the per-user guard
- Block RFC 1918 + IPv6 ULA ranges in ntfy SSRF guard (F-02):
  prevents requests to Docker-internal services via user-controlled ntfy URL
- Rate-limit /api/auth/setup at nginx with burst=3 (F-06)
- Document production deployment checklist in .env.example (F-03/F-04/F-05)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 02:25:37 +08:00
6fc134d113 Update EntityDetailPanel to use 2-column grid layout
Add fullWidth field option to PanelField interface. Short fields
render in a grid grid-cols-2 layout; fullWidth fields (address, notes)
render below at full width. Add icons to People and Locations fields
for consistency with other detail panels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:59:30 +08:00
87a7a4ae32 Reformat detail panel view modes to 2-column grid layout
Match TaskDetailPanel gold standard: short fields use grid grid-cols-2
with icon+label headers, full-width fields (description) below the grid.
All grid slots render with "—" fallback to keep alignment consistent.

- Todo: Priority, Category, Due Date, Recurrence in grid
- Reminder: Status, Recurrence, Remind At, Snoozed Until in grid
- Calendar: Calendar, Starred, Start, End, Location, Recurrence in grid
- Task: Add "Updated X ago" footer (was missing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:52:15 +08:00
2e2466bfa6 Fix People search: alignment, focus ring, and name matching
- Wrap CategoryFilterBar in flex-1 min-w-0 so search aligns right
- Add first_name, last_name, nickname to People search filter
- Add ring-inset to all header search inputs (People, Todos,
  Locations, Reminders, Calendar) to prevent focus ring clipping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:35:40 +08:00
05d04c131e Always show search input in CategoryFilterBar
Remove the collapsible search behavior that hid the input behind an
icon when ≥4 categories existed. Search should always be visible and
match the standard header pattern (w-52 input with icon).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:30:02 +08:00
1e225145c9 Fix Todo search alignment: wrap CategoryFilterBar in flex-1 container
CategoryFilterBar's internal flex-1 spacer needs room to push search
right. Wrapping in flex-1 min-w-0 matches the Locations/People pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:25:41 +08:00
057b43627b Smooth calendar resize during panel transitions
Replace single delayed updateSize() with requestAnimationFrame loop
that continuously resizes FullCalendar throughout the 300ms CSS
transition, eliminating the visual desync between panel and calendar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:13:40 +08:00
8945295e2a Replace Sheet overlays with inline detail panels across all pages
- Calendar: move view selector left, inline EventDetailPanel with view/edit/create
  modes, fix resize on panel close, remove all Sheet/Dialog usage
- Todos: add TodoDetailPanel with inline view/edit/create, replace CategoryFilterBar
  with shared component (drag-and-drop categories), 55/45 split layout
- Reminders: add ReminderDetailPanel with inline view/edit/create, 55/45 split layout
- Dashboard: all widget items now deep-link to destination page AND open the
  relevant item's detail panel (events, todos, reminders)
- Fix TS errors: unused imports, undefined→null coalescing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:43:06 +08:00
898ecc407a Stage 7: final polish — transitions, navigation, calendar panel
- Add animate-fade-in page transitions to all pages
- Persist sidebar collapsed state in localStorage
- Add two-click logout confirmation using useConfirmAction
- Restructure Todos header: replace <select> with pill filters, move search right
- Move Reminders search right-aligned with spacer
- Add event search dropdown + Create Event button to Calendar toolbar
- Add search input to Projects header with name/description filtering
- Fix CategoryFilterBar search focus ring clipping with ring-inset
- Create EventDetailPanel: read-only event view with copyable fields,
  recurrence display, edit/delete actions, location name resolution
- Refactor CalendarPage to 55/45 split-panel layout matching People/Locations
- Add mobile overlay panel for calendar event details
- Add navigation state handler for CalendarPage (date/view from dashboard)
- Add navigation state handler for ProjectsPage (status filter from dashboard)
- Make all dashboard widgets navigable: stat cards → pages, week timeline
  days → calendar day view, upcoming items → source pages, countdown items
  → calendar, today's events/todos/reminders → respective pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:08:08 +08:00
89e253843c Merge chore/qa-suggestions-and-docs: QA suggestions + documentation refresh 2026-02-25 20:36:26 +08:00
ad102c24ed Apply QA suggestions and update all documentation
Code changes (S-01, S-02, S-05):
- DRY nginx proxy blocks via shared proxy-params.conf include
- Add ENVIRONMENT and CORS_ORIGINS to .env.example
- Remove unused X-Requested-With from CORS allow_headers

Documentation updates:
- README.md: reflect auth upgrade, security hardening, production
  deployment guide with secret generation commands, updated architecture
  diagram, current project structure and feature list
- CLAUDE.md: codify established dev workflow (branch → implement →
  test → QA → merge), update auth/infra/stack sections, add authority
  links for progress.md and ntfy.md
- progress.md: add Phase 11 (auth upgrade) and Phase 12 (pentest
  remediation), update file inventory, fix outstanding items
- ui_refresh.md: update current status line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:36:12 +08:00