Fix QA review findings: per-card responding state, preserve data on detach

C-01: ConnectionRequestCard now uses local isResponding state instead of
shared hook boolean, so accepting one card doesn't disable all others.

C-03: detach_umbral_contact no longer wipes person data (name, email,
phone, etc.) when a connection is severed. The person becomes a standard
contact with all data preserved, preventing data loss for pre-existing
contacts that were linked to connections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-05 22:40:24 +08:00
parent aeb30afbce
commit 3fe344c3a0
2 changed files with 11 additions and 11 deletions

View File

@ -126,19 +126,15 @@ def create_person_from_connection(
async def detach_umbral_contact(person: Person) -> None:
"""Convert an umbral contact back to a standard contact. Does NOT commit."""
"""Convert an umbral contact back to a standard contact. Does NOT commit.
Preserves all person data (name, email, phone, etc.) so the user does not
lose contact information when a connection is severed. Only unlinks the
umbral association the person becomes a standard contact.
"""
person.linked_user_id = None
person.is_umbral_contact = False
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."""

View File

@ -15,7 +15,8 @@ interface ConnectionRequestCardProps {
}
export default function ConnectionRequestCard({ request, direction }: ConnectionRequestCardProps) {
const { respond, isResponding, cancelRequest, isCancelling } = useConnections();
const { respond, cancelRequest, isCancelling } = useConnections();
const [isResponding, setIsResponding] = useState(false);
const [resolved, setResolved] = useState(false);
// Clean up invisible DOM element after fade-out transition
@ -29,6 +30,7 @@ export default function ConnectionRequestCard({ request, direction }: Connection
if (hidden) return null;
const handleRespond = async (action: 'accept' | 'reject') => {
setIsResponding(true);
try {
await respond({ requestId: request.id, action });
setResolved(true);
@ -41,6 +43,8 @@ export default function ConnectionRequestCard({ request, direction }: Connection
} else {
toast.error(getErrorMessage(err, 'Failed to respond'));
}
} finally {
setIsResponding(false);
}
};