diff --git a/frontend/src/lib/components/DocumentMetadataDrawer.svelte b/frontend/src/lib/components/DocumentMetadataDrawer.svelte
index a24a27d1..fd896f1c 100644
--- a/frontend/src/lib/components/DocumentMetadataDrawer.svelte
+++ b/frontend/src/lib/components/DocumentMetadataDrawer.svelte
@@ -3,6 +3,7 @@ import { m } from '$lib/paraglide/messages.js';
import { formatDate } from '$lib/utils/date';
import { formatDocumentStatus } from '$lib/utils/documentStatusLabel';
import { getInitials, personAvatarColor } from '$lib/utils/personFormat';
+import RelationshipBadge from '$lib/components/RelationshipBadge.svelte';
type Person = { id: string; firstName?: string | null; lastName: string; displayName: string };
type Tag = { id: string; name: string };
@@ -14,9 +15,18 @@ type Props = {
sender: Person | null;
receivers: Person[];
tags: Tag[];
+ inferredRelationship?: { labelFromA: string; labelFromB: string } | null;
};
-let { documentDate, location, status, sender, receivers, tags }: Props = $props();
+let {
+ documentDate,
+ location,
+ status,
+ sender,
+ receivers,
+ tags,
+ inferredRelationship = null
+}: Props = $props();
const VISIBLE_RECEIVER_LIMIT = 5;
@@ -112,6 +122,12 @@ function getFullName(person: Person): string {
{/if}
{/if}
+ {#if inferredRelationship}
+
+ {/if}
{:else}
{m.doc_details_no_persons()}
diff --git a/frontend/src/lib/components/DocumentTopBar.svelte b/frontend/src/lib/components/DocumentTopBar.svelte
index f352364a..15cadd6b 100644
--- a/frontend/src/lib/components/DocumentTopBar.svelte
+++ b/frontend/src/lib/components/DocumentTopBar.svelte
@@ -30,9 +30,16 @@ type Props = {
canWrite: boolean;
fileUrl: string;
transcribeMode: boolean;
+ inferredRelationship?: { labelFromA: string; labelFromB: string } | null;
};
-let { doc, canWrite, fileUrl, transcribeMode = $bindable() }: Props = $props();
+let {
+ doc,
+ canWrite,
+ fileUrl,
+ transcribeMode = $bindable(),
+ inferredRelationship = null
+}: Props = $props();
let detailsOpen = $state(false);
@@ -275,6 +282,7 @@ let mobileMenuOpen = $state(false);
sender={doc.sender ?? null}
receivers={doc.receivers ? [...doc.receivers] : []}
tags={doc.tags ? [...doc.tags] : []}
+ inferredRelationship={inferredRelationship}
/>
{/if}
diff --git a/frontend/src/lib/components/RelationshipBadge.svelte b/frontend/src/lib/components/RelationshipBadge.svelte
new file mode 100644
index 00000000..8de6473f
--- /dev/null
+++ b/frontend/src/lib/components/RelationshipBadge.svelte
@@ -0,0 +1,26 @@
+
+
+
+
+ {m.doc_details_field_relationship()}
+
+
+
{labelFromA}
+
+
{labelFromB}
+
+
diff --git a/frontend/src/lib/relationshipLabels.ts b/frontend/src/lib/relationshipLabels.ts
new file mode 100644
index 00000000..ff2edd44
--- /dev/null
+++ b/frontend/src/lib/relationshipLabels.ts
@@ -0,0 +1,44 @@
+import * as m from '$lib/paraglide/messages.js';
+
+/**
+ * Maps a backend inferred-label key (parent, uncle_aunt, ...) to its
+ * localised string. Unknown keys fall back to "distant".
+ */
+export function inferredRelationshipLabel(key: string): string {
+ switch (key) {
+ case 'parent':
+ return m.relation_inferred_parent();
+ case 'child':
+ return m.relation_inferred_child();
+ case 'spouse':
+ return m.relation_inferred_spouse();
+ case 'sibling':
+ return m.relation_inferred_sibling();
+ case 'grandparent':
+ return m.relation_inferred_grandparent();
+ case 'grandchild':
+ return m.relation_inferred_grandchild();
+ case 'great_grandparent':
+ return m.relation_inferred_great_grandparent();
+ case 'great_grandchild':
+ return m.relation_inferred_great_grandchild();
+ case 'uncle_aunt':
+ return m.relation_inferred_uncle_aunt();
+ case 'niece_nephew':
+ return m.relation_inferred_niece_nephew();
+ case 'great_uncle_aunt':
+ return m.relation_inferred_great_uncle_aunt();
+ case 'great_niece_nephew':
+ return m.relation_inferred_great_niece_nephew();
+ case 'inlaw_parent':
+ return m.relation_inferred_inlaw_parent();
+ case 'inlaw_child':
+ return m.relation_inferred_inlaw_child();
+ case 'sibling_inlaw':
+ return m.relation_inferred_sibling_inlaw();
+ case 'cousin_1':
+ return m.relation_inferred_cousin_1();
+ default:
+ return m.relation_inferred_distant();
+ }
+}
diff --git a/frontend/src/routes/documents/[id]/+page.server.ts b/frontend/src/routes/documents/[id]/+page.server.ts
index 5fb27114..e601dd44 100644
--- a/frontend/src/routes/documents/[id]/+page.server.ts
+++ b/frontend/src/routes/documents/[id]/+page.server.ts
@@ -1,6 +1,7 @@
import { error, redirect } from '@sveltejs/kit';
import { createApiClient } from '$lib/api.server';
import { getErrorMessage } from '$lib/errors';
+import { inferredRelationshipLabel } from '$lib/relationshipLabels';
export async function load({ params, fetch }) {
const { id } = params;
@@ -15,5 +16,38 @@ export async function load({ params, fetch }) {
throw error(docResult.response.status, getErrorMessage(code));
}
- return { document: docResult.data! };
+ const document = docResult.data!;
+ const inferredRelationship = await loadInferredRelationship(api, document);
+
+ return { document, inferredRelationship };
+}
+
+async function loadInferredRelationship(
+ api: ReturnType,
+ document: {
+ sender?: { id: string; familyMember?: boolean } | null;
+ receivers?: { id: string; familyMember?: boolean }[];
+ }
+): Promise<{ labelFromA: string; labelFromB: string } | null> {
+ const sender = document.sender;
+ const receivers = document.receivers ?? [];
+
+ // The badge is shown only when both endpoints are family members and the
+ // document has exactly one receiver.
+ if (!sender?.familyMember) return null;
+ if (receivers.length !== 1) return null;
+ const receiver = receivers[0];
+ if (!receiver?.familyMember) return null;
+
+ const result = await api.GET('/api/persons/{aId}/relationship-to/{bId}', {
+ params: { path: { aId: sender.id, bId: receiver.id } }
+ });
+
+ if (result.response.status === 404) return null;
+ if (!result.response.ok || !result.data) return null;
+
+ return {
+ labelFromA: inferredRelationshipLabel(result.data.labelFromA),
+ labelFromB: inferredRelationshipLabel(result.data.labelFromB)
+ };
}
diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte
index 9c52faa6..5c780a54 100644
--- a/frontend/src/routes/documents/[id]/+page.svelte
+++ b/frontend/src/routes/documents/[id]/+page.svelte
@@ -395,6 +395,7 @@ onMount(() => {
canWrite={canWrite}
fileUrl={fileLoader.fileUrl}
bind:transcribeMode={transcribeMode}
+ inferredRelationship={data.inferredRelationship}
/>