Fix share name toggle revert and stale table data for umbral contacts

Bug 1: _to_settings_response() was missing share_first_name and
share_last_name — the response always returned false (Pydantic default),
causing the frontend to sync toggles back to off after save.

Bug 2: Table column renderers read from stale Person record fields.
Added sf() helper that overlays shared_fields for umbral contacts,
applied to name, phone, email, role, and birthday columns. The table
now shows live shared profile data matching the detail panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-04 08:07:45 +08:00
parent 33aac72639
commit 4513227338
2 changed files with 34 additions and 16 deletions

View File

@ -48,6 +48,8 @@ def _to_settings_response(s: Settings) -> SettingsResponse:
# Social settings
accept_connections=s.accept_connections,
# Sharing defaults
share_first_name=s.share_first_name,
share_last_name=s.share_last_name,
share_preferred_name=s.share_preferred_name,
share_email=s.share_email,
share_phone=s.share_phone,

View File

@ -85,6 +85,14 @@ function sortPeople(people: Person[], key: string, dir: 'asc' | 'desc'): Person[
// ---------------------------------------------------------------------------
// Column definitions
// ---------------------------------------------------------------------------
/** Get a field value, preferring shared_fields for umbral contacts. */
function sf(p: Person, key: string): string | null | undefined {
if (p.is_umbral_contact && p.shared_fields && key in p.shared_fields) {
return p.shared_fields[key] as string | null;
}
return p[key as keyof Person] as string | null | undefined;
}
const columns: ColumnDef<Person>[] = [
{
key: 'name',
@ -92,7 +100,10 @@ const columns: ColumnDef<Person>[] = [
sortable: true,
visibilityLevel: 'essential',
render: (p) => {
const initialsName = getPersonInitialsName(p);
const firstName = sf(p, 'first_name') ?? p.first_name;
const lastName = sf(p, 'last_name') ?? p.last_name;
const liveName = [firstName, lastName].filter(Boolean).join(' ') || p.nickname || p.name;
const initialsName = liveName || getPersonInitialsName(p);
return (
<div className="flex items-center gap-2.5">
<div
@ -100,7 +111,7 @@ const columns: ColumnDef<Person>[] = [
>
{getInitials(initialsName)}
</div>
<span className="font-medium truncate">{p.nickname || p.name}</span>
<span className="font-medium truncate">{liveName}</span>
{p.is_umbral_contact && (
<Ghost className="h-3.5 w-3.5 text-violet-400 shrink-0" aria-label="Umbral contact" />
)}
@ -113,18 +124,21 @@ const columns: ColumnDef<Person>[] = [
label: 'Number',
sortable: false,
visibilityLevel: 'essential',
render: (p) => (
<span className="text-muted-foreground truncate">{p.mobile || p.phone || '—'}</span>
),
render: (p) => {
const mobile = sf(p, 'mobile') ?? p.mobile;
const phone = sf(p, 'phone') ?? p.phone;
return <span className="text-muted-foreground truncate">{mobile || phone || '—'}</span>;
},
},
{
key: 'email',
label: 'Email',
sortable: true,
visibilityLevel: 'essential',
render: (p) => (
<span className="text-muted-foreground truncate">{p.email || '—'}</span>
),
render: (p) => {
const email = sf(p, 'email') ?? p.email;
return <span className="text-muted-foreground truncate">{email || '—'}</span>;
},
},
{
key: 'job_title',
@ -132,10 +146,10 @@ const columns: ColumnDef<Person>[] = [
sortable: true,
visibilityLevel: 'filtered',
render: (p) => {
const parts = [p.job_title, p.company].filter(Boolean);
return (
<span className="text-muted-foreground truncate">{parts.join(', ') || '—'}</span>
);
const jobTitle = sf(p, 'job_title') ?? p.job_title;
const company = sf(p, 'company') ?? p.company;
const parts = [jobTitle, company].filter(Boolean);
return <span className="text-muted-foreground truncate">{parts.join(', ') || '—'}</span>;
},
},
{
@ -143,12 +157,14 @@ const columns: ColumnDef<Person>[] = [
label: 'Birthday',
sortable: true,
visibilityLevel: 'filtered',
render: (p) =>
p.birthday ? (
<span className="text-muted-foreground">{format(parseISO(p.birthday), 'MMM d')}</span>
render: (p) => {
const birthday = sf(p, 'birthday') ?? p.birthday;
return birthday ? (
<span className="text-muted-foreground">{format(parseISO(birthday), 'MMM d')}</span>
) : (
<span className="text-muted-foreground"></span>
),
);
},
},
{
key: 'category',