diff --git a/backend/app/routers/connections.py b/backend/app/routers/connections.py index 2f90b3b..aa7de36 100644 --- a/backend/app/routers/connections.py +++ b/backend/app/routers/connections.py @@ -522,7 +522,11 @@ async def _respond_to_request_inner( # Extract ntfy config before commit (avoids detached SA object in background task) sender_ntfy = extract_ntfy_config(sender_settings) if sender_settings else None - await db.commit() + try: + await db.commit() + except IntegrityError: + await db.rollback() + raise HTTPException(status_code=409, detail="Connection already exists") # ntfy push in background background_tasks.add_task( diff --git a/backend/app/services/connection.py b/backend/app/services/connection.py index 33eeb31..e169e5a 100644 --- a/backend/app/services/connection.py +++ b/backend/app/services/connection.py @@ -129,11 +129,16 @@ async def detach_umbral_contact(person: Person) -> None: """Convert an umbral contact back to a standard contact. Does NOT commit.""" person.linked_user_id = None person.is_umbral_contact = False - # Clear shared field values but preserve locally-entered data - # If no first_name exists, fill from the old name - if not person.first_name: - person.first_name = person.name or None - + person.category = None + # Clear all shareable fields — they were populated from the connection + # Preserve first_name from existing name so the contact is not left blank + fallback_name = person.first_name or person.name or None + for field in SHAREABLE_FIELDS: + if hasattr(person, field): + setattr(person, field, None) + person.first_name = fallback_name + # Recompute display name + person.name = fallback_name or "Removed Contact" def extract_ntfy_config(settings: Settings) -> dict | None: """Extract ntfy config values into a plain dict safe for use after session close.""" diff --git a/frontend/src/components/people/PeoplePage.tsx b/frontend/src/components/people/PeoplePage.tsx index 302d8d5..4e52d41 100644 --- a/frontend/src/components/people/PeoplePage.tsx +++ b/frontend/src/components/people/PeoplePage.tsx @@ -100,8 +100,8 @@ const columns: ColumnDef[] = [ sortable: true, visibilityLevel: 'essential', render: (p) => { - const firstName = sf(p, 'first_name') ?? p.first_name; - const lastName = sf(p, 'last_name') ?? p.last_name; + const firstName = sf(p, 'first_name'); + const lastName = sf(p, 'last_name'); const liveName = [firstName, lastName].filter(Boolean).join(' ') || p.nickname || p.name; const initialsName = liveName || getPersonInitialsName(p); return ( @@ -125,8 +125,8 @@ const columns: ColumnDef[] = [ sortable: false, visibilityLevel: 'essential', render: (p) => { - const mobile = sf(p, 'mobile') ?? p.mobile; - const phone = sf(p, 'phone') ?? p.phone; + const mobile = sf(p, 'mobile'); + const phone = sf(p, 'phone'); return {mobile || phone || '—'}; }, }, @@ -136,7 +136,7 @@ const columns: ColumnDef[] = [ sortable: true, visibilityLevel: 'essential', render: (p) => { - const email = sf(p, 'email') ?? p.email; + const email = sf(p, 'email'); return {email || '—'}; }, }, @@ -146,8 +146,8 @@ const columns: ColumnDef[] = [ sortable: true, visibilityLevel: 'filtered', render: (p) => { - const jobTitle = sf(p, 'job_title') ?? p.job_title; - const company = sf(p, 'company') ?? p.company; + const jobTitle = sf(p, 'job_title'); + const company = sf(p, 'company'); const parts = [jobTitle, company].filter(Boolean); return {parts.join(', ') || '—'}; }, @@ -158,7 +158,7 @@ const columns: ColumnDef[] = [ sortable: true, visibilityLevel: 'filtered', render: (p) => { - const birthday = sf(p, 'birthday') ?? p.birthday; + const birthday = sf(p, 'birthday'); return birthday ? ( {format(parseISO(birthday), 'MMM d')} ) : ( diff --git a/frontend/src/hooks/useNotifications.ts b/frontend/src/hooks/useNotifications.ts index 0681773..28c85c4 100644 --- a/frontend/src/hooks/useNotifications.ts +++ b/frontend/src/hooks/useNotifications.ts @@ -49,7 +49,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) { return data.count; }, refetchInterval: 15_000, - refetchIntervalInBackground: true, staleTime: 10_000, });