fix(documents): source the condition chips from the usage endpoint

The filter bar derived its condition chips from the loaded corpus, so an
episode whose only linked documents sat pages deep in the timeline never
surfaced a chip — the condition route into an older document silently
disappeared at four-digit corpus sizes. The usage endpoint (already read
on page mount) now returns `linkedEpisodes` — every episode carrying at
least one live document link, one indexed grouped query, no blobs — and
the view unions it with the active-filter fallback. Additive DTO change;
OpenAPI regenerated.
This commit is contained in:
Marc-André Bombeck committed 2026-07-07 14:15:40 +02:00
1 parent af45899f89
commit d937991af4
6 files changed
+82 -13

No files matched your search

+15 -1
View File
@@ -25,13 +25,23 @@ export const GET = apiHandler(async () => {
const gate = await requireModuleEnabled(user.id, "inboundDocuments");
if (!gate.enabled) return gate.response;
const [limits, rows] = await Promise.all([
const [limits, rows, linkRows] = await Promise.all([
resolveDocumentLimits(user.id),
prisma.$queryRaw<Array<{ used: bigint }>>`
SELECT COALESCE(SUM(byte_size), 0)::bigint AS used
FROM inbound_documents
WHERE user_id = ${user.id}
`,
// Episodes that carry at least one LIVE document link — the filter
// bar's condition chips. Sourced here (not from the loaded corpus) so
// a chip exists even when its documents sit pages deep in the
// timeline; one indexed grouped query, no blobs.
prisma.documentConditionLink.findMany({
where: { userId: user.id, document: { deletedAt: null } },
select: { episodeId: true, episode: { select: { label: true } } },
distinct: ["episodeId"],
orderBy: { episodeId: "asc" },
}),
]);
const usedBytes = Number(rows[0]?.used ?? 0);
@@ -45,6 +55,10 @@ export const GET = apiHandler(async () => {
quotaBytes: limits.quotaBytes,
maxFileBytes: limits.maxFileBytes,
acceptedExtensions: [...DOCUMENT_ACCEPTED_EXTENSIONS],
linkedEpisodes: linkRows.map((row) => ({
episodeId: row.episodeId,
name: row.episode.label,
})),
};
return apiSuccess(payload);
});