From e58a2330a6f0ae026c0dc19c036cfd42e3adf73e Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Fri, 27 Mar 2026 00:51:06 -0700 Subject: [PATCH] Address Greptile feedback: clear debounce timer, deduplicate util, fix completed-run display - Clear the lastOutputAt debounce timer on run completion to avoid stale writes after the run finishes - Replace local formatRelativeTime with existing relativeTime util - Fix completed-run display in LiveRunWidget to show finish time instead of unconditionally showing lastOutputAt Co-Authored-By: Paperclip --- server/src/services/heartbeat.ts | 6 +++++- ui/src/components/LiveRunWidget.tsx | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 996a724b..f9c205d6 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -2502,6 +2502,7 @@ export function heartbeatService(db: Db) { const currentUserRedactionOptions = await getCurrentUserRedactionOptions(); let lastOutputAtFlushPending = false; let lastOutputAtLatest: Date | null = null; + let lastOutputAtTimer: ReturnType | null = null; const onLog = async (stream: "stdout" | "stderr", chunk: string) => { const sanitizedChunk = redactCurrentUserText(chunk, currentUserRedactionOptions); if (stream === "stdout") stdoutExcerpt = appendExcerpt(stdoutExcerpt, sanitizedChunk); @@ -2516,8 +2517,9 @@ export function heartbeatService(db: Db) { await db.update(heartbeatRuns) .set({ lastOutputAt: lastOutputAtLatest, updatedAt: new Date() }) .where(eq(heartbeatRuns.id, runId)); - setTimeout(() => { + lastOutputAtTimer = setTimeout(() => { lastOutputAtFlushPending = false; + lastOutputAtTimer = null; if (!lastOutputAtLatest) return; db.update(heartbeatRuns) .set({ lastOutputAt: lastOutputAtLatest, updatedAt: new Date() }) @@ -2649,6 +2651,8 @@ export function heartbeatService(db: Db) { }, authToken: authToken ?? undefined, }); + // Clear the debounce timer now that the run is complete + if (lastOutputAtTimer) { clearTimeout(lastOutputAtTimer); lastOutputAtTimer = null; } const adapterManagedRuntimeServices = adapterResult.runtimeServices ? await persistAdapterManagedRuntimeServices({ db, diff --git a/ui/src/components/LiveRunWidget.tsx b/ui/src/components/LiveRunWidget.tsx index 68e82efd..2af427c7 100644 --- a/ui/src/components/LiveRunWidget.tsx +++ b/ui/src/components/LiveRunWidget.tsx @@ -3,7 +3,7 @@ import { Link } from "@/lib/router"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { heartbeatsApi, type LiveRunForIssue } from "../api/heartbeats"; import { queryKeys } from "../lib/queryKeys"; -import { formatDateTime } from "../lib/utils"; +import { formatDateTime, relativeTime } from "../lib/utils"; import { AlertTriangle, ExternalLink, Square } from "lucide-react"; import { Identity } from "./Identity"; import { StatusBadge } from "./StatusBadge"; @@ -24,14 +24,6 @@ function isRunActive(status: string): boolean { return status === "queued" || status === "running"; } -function formatRelativeTime(iso: string): string { - const ms = Date.now() - new Date(iso).getTime(); - if (ms < 60_000) return "just now"; - const min = Math.floor(ms / 60_000); - if (min < 60) return `${min}m ago`; - const hr = Math.floor(min / 60); - return `${hr}h ${min % 60}m ago`; -} export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) { const queryClient = useQueryClient(); @@ -133,12 +125,20 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) { Idle )} - {run.lastOutputAt && ( + {run.lastOutputAt && isRunActive(run.status) && ( - Last output {formatRelativeTime(run.lastOutputAt)} + Last output {relativeTime(run.lastOutputAt)} )} - {!run.lastOutputAt && {formatDateTime(run.startedAt ?? run.createdAt)}} + {run.finishedAt && !isRunActive(run.status) && ( + {formatDateTime(run.finishedAt)} + )} + {!run.lastOutputAt && isRunActive(run.status) && ( + {formatDateTime(run.startedAt ?? run.createdAt)} + )} + {!run.finishedAt && !isRunActive(run.status) && !run.lastOutputAt && ( + {formatDateTime(run.startedAt ?? run.createdAt)} + )}