diff --git a/frontend/src/lib/components/TagParentPicker.svelte b/frontend/src/lib/components/TagParentPicker.svelte
index a67faef9..1d37902f 100644
--- a/frontend/src/lib/components/TagParentPicker.svelte
+++ b/frontend/src/lib/components/TagParentPicker.svelte
@@ -7,14 +7,27 @@ import { createTypeahead } from '$lib/hooks/useTypeahead.svelte';
type Tag = components['schemas']['Tag'];
+interface FlatTagRef {
+ id: string;
+ name: string;
+ parentId?: string;
+}
+
interface Props {
name: string;
value?: string;
excludeIds?: string[];
initialName?: string;
+ allTags?: FlatTagRef[];
}
-let { name, value = $bindable(''), excludeIds = [], initialName = '' }: Props = $props();
+let {
+ name,
+ value = $bindable(''),
+ excludeIds = [],
+ initialName = '',
+ allTags = []
+}: Props = $props();
// displayName must be both prop-derived AND locally writable (user typing), so $state +
// $effect is the correct pattern here — writable $derived is read-only and won't work.
@@ -135,7 +148,8 @@ function handleKeydown(e: KeyboardEvent) {
>
{tag.name}
{#if tag.parentId}
- {tag.parentId}
+ {@const parentName = allTags.find((t) => t.id === tag.parentId)?.name ?? tag.parentId}
+ {parentName}
{/if}
{/each}
diff --git a/frontend/src/lib/components/TagParentPicker.svelte.spec.ts b/frontend/src/lib/components/TagParentPicker.svelte.spec.ts
index e5311983..03c905a5 100644
--- a/frontend/src/lib/components/TagParentPicker.svelte.spec.ts
+++ b/frontend/src/lib/components/TagParentPicker.svelte.spec.ts
@@ -162,3 +162,35 @@ describe('TagParentPicker – ARIA combobox', () => {
expect(option.id).toBe('parentId-option-0');
});
});
+
+// ─── Parent name resolution ───────────────────────────────────────────────────
+
+describe('TagParentPicker – parent name subtitle', () => {
+ it('shows parent name instead of UUID when allTags is provided', async () => {
+ mockFetchWithTags([{ id: 't2', name: 'Keller', parentId: 't1' }]);
+ const allTags = [
+ { id: 't1', name: 'Haus', documentCount: 5 },
+ { id: 't2', name: 'Keller', parentId: 't1', documentCount: 2 }
+ ];
+ render(TagParentPicker, { name: 'parentId', allTags });
+
+ const input = page.getByRole('combobox');
+ await input.fill('K');
+ await vi.advanceTimersByTimeAsync(300);
+
+ await expect.element(page.getByText('Haus')).toBeInTheDocument();
+ });
+
+ it('shows nothing as subtitle when tag has no parentId', async () => {
+ mockFetchWithTags([{ id: 't1', name: 'Haus' }]);
+ const allTags = [{ id: 't1', name: 'Haus', documentCount: 5 }];
+ render(TagParentPicker, { name: 'parentId', allTags });
+
+ const input = page.getByRole('combobox');
+ await input.fill('H');
+ await vi.advanceTimersByTimeAsync(300);
+
+ // Only the tag name should appear (no subtitle)
+ await expect.element(page.getByRole('option', { name: 'Haus' })).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/routes/admin/tags/[id]/+page.svelte b/frontend/src/routes/admin/tags/[id]/+page.svelte
index 1e0e5174..daae6402 100644
--- a/frontend/src/routes/admin/tags/[id]/+page.svelte
+++ b/frontend/src/routes/admin/tags/[id]/+page.svelte
@@ -131,6 +131,7 @@ const colors = [
bind:value={parentId}
excludeIds={[data.tag.id]}
initialName={parentName}
+ allTags={data.tags}
/>