From e56960eebd10617ef66bbe1689bbf03bf04e2d1e Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 2 Jun 2026 11:51:23 -0700 Subject: [PATCH] fix(tables): count dispatcher pre-stamps in "X running" during active dispatch The live SSE path counts pending pre-stamps (isExecInFlight) but countRunningCells excluded them, so each per-window refetch reset the badge from ~20 to 0 (visible flicker now that the control stays shown via hasActiveDispatch). Include unclaimed pre-stamps in byRowId when a dispatch is active; keep excluding them only in the no-dispatch fallback (orphan case). --- apps/sim/lib/table/dispatcher.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/sim/lib/table/dispatcher.ts b/apps/sim/lib/table/dispatcher.ts index 377758c9a5..e881102df3 100644 --- a/apps/sim/lib/table/dispatcher.ts +++ b/apps/sim/lib/table/dispatcher.ts @@ -197,8 +197,13 @@ export async function insertDispatch(input: { * * Hits the `(table_id, status)` partial index on table_row_executions. */ export async function countRunningCells( - tableId: string + tableId: string, + opts?: { includeUnclaimedPreStamps?: boolean } ): Promise<{ total: number; byRowId: Record }> { + // `pending` + null-executionId rows are unclaimed pre-stamps. With an active + // dispatch they're real queued work (include); with none they're abandoned + // orphans that would pin the badge above zero forever (exclude). + const excludeOrphanPreStamps = !opts?.includeUnclaimedPreStamps const rows = await db .select({ rowId: tableRowExecutions.rowId, @@ -209,9 +214,9 @@ export async function countRunningCells( and( eq(tableRowExecutions.tableId, tableId), inArray(tableRowExecutions.status, ['queued', 'running', 'pending']), - // Exclude orphan pre-stamps (`pending` + null executionId). De Morgan of - // NOT(pending AND null) — `status` is NOT NULL so `ne` is well-defined. - or(ne(tableRowExecutions.status, 'pending'), isNotNull(tableRowExecutions.executionId)) + excludeOrphanPreStamps + ? or(ne(tableRowExecutions.status, 'pending'), isNotNull(tableRowExecutions.executionId)) + : undefined ) ) .groupBy(tableRowExecutions.rowId) @@ -261,9 +266,10 @@ export async function countActiveRunCells( return rowsAhead * groupCount } - // One round-trip per dispatch + the sidecar count, all in parallel. + // Include pre-stamps so `byRowId` matches the live SSE count (which counts + // `pending`); otherwise the badge flickers 20→0 on each refetch. const [sidecar, perDispatch] = await Promise.all([ - countRunningCells(tableId), + countRunningCells(tableId, { includeUnclaimedPreStamps: true }), Promise.all(active.map(countRowsAhead)), ]) const total = perDispatch.reduce((sum, n) => sum + n, 0)