9 Commits

Author SHA1 Message Date
4e3fd35040 Phase 3: Frontend core for shared calendars
Types: CalendarPermission, SharedCalendarMembership, CalendarMemberInfo,
CalendarInvite, EventLockInfo. Calendar type extended with is_shared.

Hooks: useCalendars extended with shared calendar polling (5s).
useSharedCalendars for member CRUD, invite responses, color updates.
useConnections cascade invalidation on disconnect.

New components: PermissionBadge, CalendarMemberSearch,
CalendarMemberRow, CalendarMemberList, SharedCalendarSection,
SharedCalendarSettings (non-owner dialog with color, members, leave).

Modified: CalendarForm (sharing toggle, member management for owners),
CalendarSidebar (shared calendars section with localStorage visibility),
CalendarPage (shared calendar ID integration in event filtering),
NotificationToaster (calendar_invite toast with accept/decline),
NotificationsPage (calendar_invite inline actions + type icons).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 04:59:13 +08:00
1e736eb333 Fix connection accept regression: revert disabled gates, add 409 handling
Previous fix (2139ea8) caused regression: staleTime:0 nuked cache on
every mount, isLoadingIncoming disabled buttons during cold-cache fetch.

Changes:
- Remove staleTime:0 (keep refetchOnMount:'always' for background refresh)
- Revert button gate to pendingRequestIds.has() only (no is_read gate)
- Remove isLoadingIncoming from disabled prop and spinner condition
- Add 409-as-success handling to ConnectionRequestCard (People tab)
- Use axios.isAxiosError() instead of err:any in all 3 catch blocks
- Add markRead() call to 409 branch so notifications clear properly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 18:23:47 +08:00
2139ea8077 Fix connection accept: stale cache, hidden button, and false 409 error
- incomingQuery: staleTime:0 + refetchOnMount:'always' so pending
  requests are always fresh when components mount (was inheriting
  5-min global staleTime, causing empty pendingRequestIds on nav)
- NotificationsPage: show Accept button while incoming data loads
  (was hidden during async gap); disable with spinner until ready
- Both toast and page: treat 409 as success ("already accepted")
  instead of showing error (fixes race when both fire respond)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:37:21 +08:00
6b59d61bf3 Fix connection mutation delays: make all onSuccess fire-and-forget
sendRequestMutation and cancelMutation were awaiting query invalidation
in onSuccess, which blocked mutateAsync until 3+ network refetches
completed (~1s+ delay). This caused noticeable lag on send/link/cancel
operations. Now all mutations use sync fire-and-forget invalidation,
matching respondMutation's pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:22:51 +08:00
60281caa64 Unify toast accept path with notification center via useConnections
Toast now uses the same respond() from useConnections hook instead
of raw api.put, making both accept surfaces share identical code.
Also made respondMutation.onSuccess fire-and-forget to prevent
invalidation errors from surfacing as mutation failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:30:35 +08:00
14a77f0f11 Fix stale UI after accept: await invalidations, dismiss toasts
- Await all query invalidations in respondMutation/cancelMutation
  onSuccess so UI has fresh data before mutation promise resolves
- Use deterministic toast IDs (connection-request-{id}) for Sonner
  toasts so they can be dismissed from any accept surface
- Dismiss stale connection toasts in respondMutation.onSuccess
- Fix handleCancel setting resolved before API call completes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:43:22 +08:00
73cef1df55 Add umbral name header, preferred name field, and link button for contacts
- Inject umbral_name into shared_fields for umbral contacts (always visible)
- Show @umbralname subtitle in detail panel header
- Add preferred_name to panel fields with synced label for umbral contacts
- Add Link button on standard contacts to tie to umbral user via connection request
- Migration 046: person_id FK on connection_requests with index
- Validate person_id ownership on send, re-validate + convert on accept

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 08:37:01 +08:00
820ff46efa Fix QA W-01/W-05/W-06/W-08: cancel requests, detach umbral, notifications
W-08: Add CHECK constraint on notifications.type (migration 044) with
defensive pre-check and matching __table_args__ on model.

W-05: Auto-detach umbral contact before Person delete — nulls out
connection's person_id so the connection survives deletion.

W-01: Add PUT /requests/{id}/cancel endpoint with atomic UPDATE,
silent notification cleanup, and audit logging. Frontend: direction-aware
ConnectionRequestCard, cancel mutation, pending requests section on
PeoplePage with incoming/outgoing subsections.

W-06: Convert useNotifications to context provider pattern — single
subscription shared via NotificationProvider in AppLayout. Adds
refreshNotifications convenience function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 07:17:31 +08:00
3d22568b9c Add user connections, notification centre, and people integration
Implements the full User Connections & Notification Centre feature:

Phase 1 - Database: migrations 039-043 adding umbral_name to users,
profile/social fields to settings, notifications table, connection
request/user_connection tables, and linked_user_id to people.

Phase 2 - Notifications: backend CRUD router + service + 90-day purge,
frontend NotificationsPage with All/Unread filter, bell icon in sidebar
with unread badge polling every 60s.

Phase 3 - Settings: profile fields (phone, mobile, address, company,
job_title), social card with accept_connections toggle and per-field
sharing defaults, umbral name display with CopyableField.

Phase 4 - Connections: timing-safe user search, send/accept/reject flow
with atomic status updates, bidirectional UserConnection + Person records,
in-app + ntfy notifications, per-receiver pending cap, nginx rate limiting.

Phase 5 - People integration: batch-loaded shared profiles (N+1 prevention),
Ghost icon for umbral contacts, Umbral filter pill, split Add Person button,
shared field indicators (synced labels + Lock icons), disabled form inputs
for synced fields on umbral contacts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 02:10:16 +08:00