fix(client): bound the staged-attachment poll and list every client poll

The Coach polled a staged document every 2.5 s until its content index
existed, which for a format that is never indexed meant for as long as the
Coach stayed open. It now stops with the same upload processing window the
documents list uses. Every client poll, its interval and its stop
condition is listed beside the dashboard cadence constant.
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 06:49:23 +02:00
1 parent 9bc95713cb
commit 415da581f8
4 files changed
+120 -3

No files matched your search

@@ -22,6 +22,8 @@ import {
resolveBulkShareDocuments,
SHARE_LINK_MAX_DOCUMENTS,
vaultFiltersToSearch,
STAGED_ATTACHMENT_POLL_MS,
stagedAttachmentPollInterval,
} from "../vault-utils";
function doc(overrides: Partial<InboundDocumentDto> = {}): InboundDocumentDto {
@@ -541,3 +543,36 @@ describe("buildProcedureChoices", () => {
expect(buildProcedureChoices([], null, labels)).toEqual([]);
});
});
describe("stagedAttachmentPollInterval", () => {
const now = Date.parse("2026-10-20T10:00:00Z");
const at = (msAgo: number) => new Date(now - msAgo).toISOString();
it("polls a fresh upload until its index lands", () => {
expect(
stagedAttachmentPollInterval(
{ createdAt: at(30_000), hasContentIndex: false },
now,
),
).toBe(STAGED_ATTACHMENT_POLL_MS);
expect(
stagedAttachmentPollInterval(
{ createdAt: at(30_000), hasContentIndex: true },
now,
),
).toBe(false);
});
it("stops polling a document that is never indexed once the window passes", () => {
expect(
stagedAttachmentPollInterval(
{ createdAt: at(RECENT_UPLOAD_WINDOW_MS + 1), hasContentIndex: false },
now,
),
).toBe(false);
});
it("does not poll before the detail has loaded", () => {
expect(stagedAttachmentPollInterval(undefined, now)).toBe(false);
});
});
+20
View File
@@ -167,6 +167,26 @@ export function isDocumentProcessing(
return !doc.hasContentIndex && isRecentlyUploaded(doc.createdAt, now);
}
/**
* How often the Coach polls a staged attachment for its content index.
*
* The same "still processing" question the vault list asks, so the same
* bounded window answers it: a document that will never be indexed (an
* unsupported format, indexing disabled) used to be polled every 2.5 s for as
* long as the Coach stayed open. `false` once the index exists or the window
* has passed.
*/
export const STAGED_ATTACHMENT_POLL_MS = 2_500;
export function stagedAttachmentPollInterval(
doc: Pick<InboundDocumentDto, "createdAt" | "hasContentIndex"> | undefined,
now: number = Date.now(),
): number | false {
return doc && isDocumentProcessing(doc, now)
? STAGED_ATTACHMENT_POLL_MS
: false;
}
/**
* Whether ANY document in the list is still inside its processing window —
* drives the vault list query's poll-while-indexing `refetchInterval`.
@@ -21,7 +21,10 @@ import type { CoachLaunchScope } from "@/lib/insights/coach-launch-context";
import type { InboundDocumentDetailDto } from "@/lib/validations/inbound-documents";
import { MAX_COACH_ATTACHMENTS } from "@/lib/validations/inbound-documents";
import { useModuleEnabled } from "@/hooks/use-module-enabled";
import { parseUploadResponse } from "@/components/documents/vault-utils";
import {
parseUploadResponse,
stagedAttachmentPollInterval,
} from "@/components/documents/vault-utils";
import {
metricScopeLabelFallback,
scopeSourceMetricLabelKey,
@@ -399,10 +402,11 @@ export function CoachConversation({
queryFn: async () =>
apiGet<InboundDocumentDetailDto>(`/api/documents/inbound/${id}`),
staleTime: 15_000,
// Bounded by the upload processing window, like the vault list: a
// document that is never indexed stops being polled.
refetchInterval: (query: {
state: { data?: InboundDocumentDetailDto };
}) =>
query.state.data && !query.state.data.hasContentIndex ? 2500 : false,
}) => stagedAttachmentPollInterval(query.state.data),
})),
});
const stagedDetailById = new Map<