feat(themen): key reader tag visibility on the subtree rollup (#698)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user