From e8377b579ecd17d30746c5168e3807df55b1f7c6 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 31 May 2026 12:17:40 +0200 Subject: [PATCH] feat(themen): key reader tag visibility on the subtree rollup (#698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regenerate the TagTreeNodeDTO type with subtreeDocumentCount and switch hasAnyDocuments to read it directly — the backend rollup already includes all descendants, so the recursive children walk is no longer needed. Reader surfaces now hide a topic only when its whole subtree is empty. Co-Authored-By: Claude Opus 4.8 --- frontend/src/lib/generated/api.ts | 5 ++++ .../src/lib/shared/utils/tagUtils.test.ts | 27 ++++++++++--------- frontend/src/lib/shared/utils/tagUtils.ts | 8 +++++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/frontend/src/lib/generated/api.ts b/frontend/src/lib/generated/api.ts index 5b059dd5..8f9e303e 100644 --- a/frontend/src/lib/generated/api.ts +++ b/frontend/src/lib/generated/api.ts @@ -2230,6 +2230,11 @@ export interface components { color?: string; /** Format: int32 */ documentCount: number; + /** + * Format: int32 + * @description Distinct documents tagged with this tag or any descendant tag (subtree rollup) + */ + subtreeDocumentCount: number; children?: components["schemas"]["TagTreeNodeDTO"][]; /** * Format: uuid diff --git a/frontend/src/lib/shared/utils/tagUtils.test.ts b/frontend/src/lib/shared/utils/tagUtils.test.ts index 5fe4a874..87b33763 100644 --- a/frontend/src/lib/shared/utils/tagUtils.test.ts +++ b/frontend/src/lib/shared/utils/tagUtils.test.ts @@ -4,26 +4,29 @@ import type { components } from '$lib/generated/api'; type TagTreeNodeDTO = components['schemas']['TagTreeNodeDTO']; -function makeNode(documentCount: number, children: TagTreeNodeDTO[] = []): TagTreeNodeDTO { - return { id: 'id', name: 'name', documentCount, children }; +function makeNode( + documentCount: number, + subtreeDocumentCount: number, + children: TagTreeNodeDTO[] = [] +): TagTreeNodeDTO { + return { id: 'id', name: 'name', documentCount, subtreeDocumentCount, children }; } describe('hasAnyDocuments', () => { - it('returns false for a leaf node with documentCount=0', () => { - expect(hasAnyDocuments(makeNode(0))).toBe(false); + it('returns false for a node whose subtree holds no documents', () => { + expect(hasAnyDocuments(makeNode(0, 0))).toBe(false); }); - it('returns true for a leaf node with documentCount=3', () => { - expect(hasAnyDocuments(makeNode(3))).toBe(true); + it('returns true for a node whose subtree holds documents', () => { + expect(hasAnyDocuments(makeNode(3, 3))).toBe(true); }); - it('returns true for a root with documentCount=0 but a child with documentCount=5', () => { - const node = makeNode(0, [makeNode(5)]); - expect(hasAnyDocuments(node)).toBe(true); + it('keys on the subtree rollup, not direct documentCount: 0 direct but rollup 5 → true', () => { + // The rollup already includes descendants — a single field read, no recursion over children. + expect(hasAnyDocuments(makeNode(0, 5))).toBe(true); }); - it('returns false for a root with documentCount=0 and all children also 0', () => { - const node = makeNode(0, [makeNode(0), makeNode(0)]); - expect(hasAnyDocuments(node)).toBe(false); + it('keys on the subtree rollup, not direct documentCount: 5 direct but rollup 0 → false', () => { + expect(hasAnyDocuments(makeNode(5, 0))).toBe(false); }); }); diff --git a/frontend/src/lib/shared/utils/tagUtils.ts b/frontend/src/lib/shared/utils/tagUtils.ts index a7fe138d..89826e68 100644 --- a/frontend/src/lib/shared/utils/tagUtils.ts +++ b/frontend/src/lib/shared/utils/tagUtils.ts @@ -2,6 +2,12 @@ import type { components } from '$lib/generated/api'; type TagTreeNodeDTO = components['schemas']['TagTreeNodeDTO']; +/** + * Whether a tag's whole subtree holds any documents — keyed on the subtree rollup + * (`subtreeDocumentCount`), which the backend already computes across all descendants. + * Used by the reader surfaces (/themen page, dashboard ThemenWidget) to hide empty topics. + * A single field read: no recursion needed, the rollup is authoritative. + */ export function hasAnyDocuments(node: TagTreeNodeDTO): boolean { - return (node.documentCount ?? 0) > 0 || (node.children ?? []).some(hasAnyDocuments); + return (node.subtreeDocumentCount ?? 0) > 0; }