From 7388e8b007fb9959d326352f9db323741cc234a0 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 23 Apr 2026 14:28:13 -0400 Subject: [PATCH 1/3] refactor: replace date-fns with safe date formatting functions across deployment components --- apps/web/app/lib/date.ts | 55 +++++++++++++++++++ .../rule-results/DeploymentWindowDetail.tsx | 20 +++---- .../EnvironmentProgressionDetail.tsx | 11 +--- .../VerificationMetricStatus.tsx | 5 +- .../release-targets/argocd/ArgoCD.tsx | 6 +- .../release-targets/datadog/Datadog.tsx | 7 +-- .../release-targets/prometheus/Prometheus.tsx | 12 ++-- ...ntId.release-targets.$releaseTargetKey.tsx | 25 +++++---- 8 files changed, 92 insertions(+), 49 deletions(-) create mode 100644 apps/web/app/lib/date.ts diff --git a/apps/web/app/lib/date.ts b/apps/web/app/lib/date.ts new file mode 100644 index 0000000000..3f6138f693 --- /dev/null +++ b/apps/web/app/lib/date.ts @@ -0,0 +1,55 @@ +import { + formatDistanceStrict, + formatDistanceToNow, + formatDistanceToNowStrict, + type FormatDistanceStrictOptions, + type FormatDistanceToNowStrictOptions, +} from "date-fns"; + +function toValidDate(value: Date | string | null | undefined): Date | null { + if (value == null) return null; + const date = value instanceof Date ? value : new Date(value); + if (isNaN(date.getTime())) return null; + return date; +} + +export function safeFormatDistanceToNowStrict( + value: Date | string | null | undefined, + options?: FormatDistanceToNowStrictOptions, +): string | null { + const date = toValidDate(value); + if (date == null) return null; + return formatDistanceToNowStrict(date, options); +} + +export function safeFormatDistanceToNow( + value: Date | string | null | undefined, + options?: Parameters[1], +): string | null { + const date = toValidDate(value); + if (date == null) return null; + return formatDistanceToNow(date, options); +} + +export function safeFormatDistanceStrict( + a: Date | string | null | undefined, + b: Date | string | null | undefined, + options?: FormatDistanceStrictOptions, +): string | null { + const dateA = toValidDate(a); + const dateB = toValidDate(b); + if (dateA == null || dateB == null) return null; + return formatDistanceStrict(dateA, dateB, options); +} + +export function safeIsPast(value: Date | string | null | undefined): boolean { + const date = toValidDate(value); + if (date == null) return false; + return date.getTime() < Date.now(); +} + +export function safeIsFuture(value: Date | string | null | undefined): boolean { + const date = toValidDate(value); + if (date == null) return false; + return date.getTime() > Date.now(); +} diff --git a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/DeploymentWindowDetail.tsx b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/DeploymentWindowDetail.tsx index 35afc5b84f..e95989d40b 100644 --- a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/DeploymentWindowDetail.tsx +++ b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/DeploymentWindowDetail.tsx @@ -1,10 +1,5 @@ import type { AppRouter } from "@ctrlplane/trpc"; import { useMemo } from "react"; -import { - formatDistanceStrict, - formatDistanceToNowStrict, - isPast, -} from "date-fns"; import _ from "lodash"; import { CheckCircle2Icon, @@ -15,6 +10,11 @@ import { rrulestr } from "rrule"; import { trpc } from "~/api/trpc"; import { useWorkspace } from "~/components/WorkspaceProvider"; +import { + safeFormatDistanceStrict, + safeFormatDistanceToNowStrict, + safeIsPast, +} from "~/lib/date"; type DeploymentWindow = NonNullable< Awaited> @@ -111,14 +111,12 @@ function WindowRow({ const windowOpenedAt = isOpen ? new Date(nextEnd.getTime() - durationMs) : null; - const timeOpen = - windowOpenedAt != null ? formatDistanceToNowStrict(windowOpenedAt) : null; + const timeOpen = safeFormatDistanceToNowStrict(windowOpenedAt); const timeRemaining = - isOpen && !isPast(nextEnd) ? formatDistanceToNowStrict(nextEnd) : null; - const totalDuration = - windowOpenedAt != null - ? formatDistanceStrict(windowOpenedAt, nextEnd) + isOpen && !safeIsPast(nextEnd) + ? safeFormatDistanceToNowStrict(nextEnd) : null; + const totalDuration = safeFormatDistanceStrict(windowOpenedAt, nextEnd); const windowLabel = policyName ?? (isAllowWindow ? "Allow Window" : "Deny Window"); diff --git a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/EnvironmentProgressionDetail.tsx b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/EnvironmentProgressionDetail.tsx index 9feff5fdfa..18ad3cc391 100644 --- a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/EnvironmentProgressionDetail.tsx +++ b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/EnvironmentProgressionDetail.tsx @@ -1,6 +1,5 @@ import type { AppRouter } from "@ctrlplane/trpc"; import { useMemo } from "react"; -import { formatDistanceToNowStrict } from "date-fns"; import _ from "lodash"; import { CheckCircle2Icon, @@ -20,6 +19,7 @@ import { } from "~/components/ui/dialog"; import { Progress } from "~/components/ui/progress"; import { useWorkspace } from "~/components/WorkspaceProvider"; +import { safeFormatDistanceToNowStrict } from "~/lib/date"; import { cn } from "~/lib/utils"; type RuleEvaluation = NonNullable< @@ -41,15 +41,6 @@ type ProgressionDetails = { failed_environments?: number; }; -function safeFormatDistanceToNowStrict( - value: Date | string | null | undefined, -) { - if (value == null) return null; - const date = value instanceof Date ? value : new Date(value); - if (isNaN(date.getTime())) return null; - return formatDistanceToNowStrict(date); -} - function extractEnvDetails(details: Record): PerEnvDetail[] { const envDetails: PerEnvDetail[] = []; for (const [key, value] of Object.entries(details)) { diff --git a/apps/web/app/routes/ws/deployments/_components/release-targets/VerificationMetricStatus.tsx b/apps/web/app/routes/ws/deployments/_components/release-targets/VerificationMetricStatus.tsx index 5383ecfa3a..2271b37b51 100644 --- a/apps/web/app/routes/ws/deployments/_components/release-targets/VerificationMetricStatus.tsx +++ b/apps/web/app/routes/ws/deployments/_components/release-targets/VerificationMetricStatus.tsx @@ -1,6 +1,5 @@ -import { formatDistanceToNowStrict } from "date-fns"; - import { Skeleton } from "~/components/ui/skeleton"; +import { safeFormatDistanceToNowStrict } from "~/lib/date"; import { cn } from "~/lib/utils"; import { useMetricMeasurements } from "./useMetricMeasurements"; @@ -127,7 +126,7 @@ export function VerificationMetricStatus({ )[0]; const timeAgo = latestMeasurement - ? formatDistanceToNowStrict(new Date(latestMeasurement.measuredAt), { + ? safeFormatDistanceToNowStrict(latestMeasurement.measuredAt, { addSuffix: true, }) : null; diff --git a/apps/web/app/routes/ws/deployments/_components/release-targets/argocd/ArgoCD.tsx b/apps/web/app/routes/ws/deployments/_components/release-targets/argocd/ArgoCD.tsx index 3e8f988bc3..d23743b5ba 100644 --- a/apps/web/app/routes/ws/deployments/_components/release-targets/argocd/ArgoCD.tsx +++ b/apps/web/app/routes/ws/deployments/_components/release-targets/argocd/ArgoCD.tsx @@ -1,7 +1,7 @@ -import { formatDistanceToNowStrict } from "date-fns"; import { Check, Loader2, X } from "lucide-react"; import { Spinner } from "~/components/ui/spinner"; +import { safeFormatDistanceToNowStrict } from "~/lib/date"; import { cn } from "~/lib/utils"; import { useMetricMeasurements } from "../useMetricMeasurements"; import { @@ -140,9 +140,9 @@ export function ArgoCDVerificationDisplay({
Last Synced
- {formatDistanceToNowStrict(new Date(status.reconciledAt), { + {safeFormatDistanceToNowStrict(status.reconciledAt, { addSuffix: true, - })} + }) ?? "unknown"}
)} diff --git a/apps/web/app/routes/ws/deployments/_components/release-targets/datadog/Datadog.tsx b/apps/web/app/routes/ws/deployments/_components/release-targets/datadog/Datadog.tsx index 669df7ce97..a966bef039 100644 --- a/apps/web/app/routes/ws/deployments/_components/release-targets/datadog/Datadog.tsx +++ b/apps/web/app/routes/ws/deployments/_components/release-targets/datadog/Datadog.tsx @@ -1,11 +1,10 @@ -import { formatDistanceToNowStrict } from "date-fns"; - import { Tooltip, TooltipContent, TooltipTrigger, } from "~/components/ui/tooltip"; import { Spinner } from "~/components/ui/spinner"; +import { safeFormatDistanceToNowStrict } from "~/lib/date"; import { cn } from "~/lib/utils"; import { useMetricMeasurements } from "../useMetricMeasurements"; import { @@ -151,7 +150,7 @@ function MeasurementRow({ measurement }: { measurement: MetricMeasurement }) { const isPassed = measurement.status === "passed"; const isFailed = measurement.status === "failed"; - const timeAgo = formatDistanceToNowStrict(new Date(measurement.measuredAt), { + const timeAgo = safeFormatDistanceToNowStrict(measurement.measuredAt, { addSuffix: true, }); @@ -168,7 +167,7 @@ function MeasurementRow({ measurement }: { measurement: MetricMeasurement }) { !isPassed && !isFailed && "bg-muted-foreground", )} /> - {timeAgo} + {timeAgo ?? "—"}
{queryEntries.length > 0 ? ( diff --git a/apps/web/app/routes/ws/deployments/_components/release-targets/prometheus/Prometheus.tsx b/apps/web/app/routes/ws/deployments/_components/release-targets/prometheus/Prometheus.tsx index 93d3968e6b..8f8e0bd6ab 100644 --- a/apps/web/app/routes/ws/deployments/_components/release-targets/prometheus/Prometheus.tsx +++ b/apps/web/app/routes/ws/deployments/_components/release-targets/prometheus/Prometheus.tsx @@ -1,6 +1,5 @@ -import { formatDistanceToNowStrict } from "date-fns"; - import { Spinner } from "~/components/ui/spinner"; +import { safeFormatDistanceToNowStrict } from "~/lib/date"; import { cn } from "~/lib/utils"; import { useMetricMeasurements } from "../useMetricMeasurements"; import { @@ -115,10 +114,9 @@ function MeasurementRow({ measurement }: { measurement: MetricMeasurement }) { const isPassed = measurement.status === "passed"; const isFailed = measurement.status === "failed"; - const timeAgo = formatDistanceToNowStrict( - new Date(measurement.measuredAt), - { addSuffix: true }, - ); + const timeAgo = safeFormatDistanceToNowStrict(measurement.measuredAt, { + addSuffix: true, + }); return (
@@ -131,7 +129,7 @@ function MeasurementRow({ measurement }: { measurement: MetricMeasurement }) { !isPassed && !isFailed && "bg-muted-foreground", )} /> - {timeAgo} + {timeAgo ?? "—"}
{parsed?.value != null ? parsed.value : "—"} diff --git a/apps/web/app/routes/ws/deployments/page.$deploymentId.release-targets.$releaseTargetKey.tsx b/apps/web/app/routes/ws/deployments/page.$deploymentId.release-targets.$releaseTargetKey.tsx index 4372664e17..aa3a646f18 100644 --- a/apps/web/app/routes/ws/deployments/page.$deploymentId.release-targets.$releaseTargetKey.tsx +++ b/apps/web/app/routes/ws/deployments/page.$deploymentId.release-targets.$releaseTargetKey.tsx @@ -1,5 +1,4 @@ import { useMemo } from "react"; -import { formatDistanceToNow } from "date-fns"; import _ from "lodash"; import { AlertCircleIcon, @@ -17,6 +16,7 @@ import { Link, useParams } from "react-router"; import { toast } from "sonner"; import { trpc } from "~/api/trpc"; +import { safeFormatDistanceToNow } from "~/lib/date"; import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"; import { Badge } from "~/components/ui/badge"; import { @@ -245,18 +245,21 @@ function EvalRow({ evaluation }: { evaluation: Evaluation }) { Evaluated{" "} - {formatDistanceToNow(new Date(evaluation.evaluatedAt), { + {safeFormatDistanceToNow(evaluation.evaluatedAt, { addSuffix: true, - })} + }) ?? "unknown"} - {evaluation.satisfiedAt != null && ( - - Satisfied{" "} - {formatDistanceToNow(new Date(evaluation.satisfiedAt), { - addSuffix: true, - })} - - )} + {evaluation.satisfiedAt != null && + safeFormatDistanceToNow(evaluation.satisfiedAt, { + addSuffix: true, + }) != null && ( + + Satisfied{" "} + {safeFormatDistanceToNow(evaluation.satisfiedAt, { + addSuffix: true, + })} + + )}
{windowDetails != null && } {approvalDetails != null && } From 05afa450716b9128c0d3688b0ddece9562f68ab3 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 23 Apr 2026 14:29:02 -0400 Subject: [PATCH 2/3] refactor: update deployment components to use safe date functions for improved reliability --- .../rule-results/DeploymentWindowDetail.tsx | 10 +++---- .../rule-results/GradRolloutDetail.tsx | 27 +++++++++++++------ .../rule-results/VersionCooldownDetail.tsx | 16 +++++++---- .../web/app/routes/ws/settings/work-queue.tsx | 3 ++- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/DeploymentWindowDetail.tsx b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/DeploymentWindowDetail.tsx index e95989d40b..f5e6e3f087 100644 --- a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/DeploymentWindowDetail.tsx +++ b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/DeploymentWindowDetail.tsx @@ -146,15 +146,15 @@ function WindowRow({ {timeOpen != null && ` (${timeOpen}${totalDuration != null ? ` of ${totalDuration}` : ""})`} {timeRemaining != null && ` · closes in ${timeRemaining}`} - {isPast(nextEnd) && - ` · ended ${formatDistanceToNowStrict(nextEnd)} ago`} + {safeIsPast(nextEnd) && + ` · ended ${safeFormatDistanceToNowStrict(nextEnd) ?? "?"} ago`} ) : ( {windowLabel} Closed · next{" "} - {isPast(nextStart) - ? `${formatDistanceToNowStrict(nextStart)} ago` - : `in ${formatDistanceToNowStrict(nextStart)}`} + {safeIsPast(nextStart) + ? `${safeFormatDistanceToNowStrict(nextStart) ?? "?"} ago` + : `in ${safeFormatDistanceToNowStrict(nextStart) ?? "?"}`} )} diff --git a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/GradRolloutDetail.tsx b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/GradRolloutDetail.tsx index 6a5d7e5958..6982464019 100644 --- a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/GradRolloutDetail.tsx +++ b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/GradRolloutDetail.tsx @@ -1,6 +1,5 @@ import type { AppRouter } from "@ctrlplane/trpc"; import { useMemo } from "react"; -import { formatDistanceToNowStrict, isFuture } from "date-fns"; import _ from "lodash"; import { CheckCircle2Icon, @@ -12,6 +11,10 @@ import { import { trpc } from "~/api/trpc"; import { Progress } from "~/components/ui/progress"; import { useWorkspace } from "~/components/WorkspaceProvider"; +import { + safeFormatDistanceToNowStrict, + safeIsFuture, +} from "~/lib/date"; type RolloutEvaluation = NonNullable< Awaited> @@ -126,19 +129,27 @@ function RolloutRow({ {!isComplete && ( {isWaiting && "Waiting for other policies to pass"} - {!isWaiting && nextDeployment != null && isFuture(nextDeployment) && ( - <>next in {formatDistanceToNowStrict(nextDeployment)} - )} {!isWaiting && nextDeployment != null && - isFuture(nextDeployment) && + safeIsFuture(nextDeployment) && ( + <> + next in{" "} + {safeFormatDistanceToNowStrict(nextDeployment) ?? "?"} + + )} + {!isWaiting && + nextDeployment != null && + safeIsFuture(nextDeployment) && estimatedCompletion != null && - isFuture(estimatedCompletion) && + safeIsFuture(estimatedCompletion) && " · "} {!isWaiting && estimatedCompletion != null && - isFuture(estimatedCompletion) && ( - <>done in {formatDistanceToNowStrict(estimatedCompletion)} + safeIsFuture(estimatedCompletion) && ( + <> + done in{" "} + {safeFormatDistanceToNowStrict(estimatedCompletion) ?? "?"} + )} )} diff --git a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/VersionCooldownDetail.tsx b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/VersionCooldownDetail.tsx index 68df1c0a2a..f044502d54 100644 --- a/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/VersionCooldownDetail.tsx +++ b/apps/web/app/routes/ws/deployments/_components/environmentversiondecisions/rule-results/VersionCooldownDetail.tsx @@ -1,6 +1,5 @@ import type { AppRouter } from "@ctrlplane/trpc"; import { useMemo } from "react"; -import { formatDistanceToNowStrict, isFuture } from "date-fns"; import _ from "lodash"; import { CheckCircle2Icon, @@ -11,6 +10,10 @@ import { import { trpc } from "~/api/trpc"; import { useWorkspace } from "~/components/WorkspaceProvider"; +import { + safeFormatDistanceToNowStrict, + safeIsFuture, +} from "~/lib/date"; type CooldownEvaluation = NonNullable< Awaited> @@ -101,15 +104,18 @@ function CooldownDescription({ {details.time_remaining} remaining {details.required_interval != null && ` of ${details.required_interval}`} - {earliest != null && isFuture(earliest) && ( - <> · deploys in {formatDistanceToNowStrict(earliest)} + {earliest != null && safeIsFuture(earliest) && ( + <> + {" "} + · deploys in {safeFormatDistanceToNowStrict(earliest) ?? "?"} + )} ); } - if (earliest != null && isFuture(earliest)) - return <>next in {formatDistanceToNowStrict(earliest)}; + if (earliest != null && safeIsFuture(earliest)) + return <>next in {safeFormatDistanceToNowStrict(earliest) ?? "?"}; return null; } diff --git a/apps/web/app/routes/ws/settings/work-queue.tsx b/apps/web/app/routes/ws/settings/work-queue.tsx index ab3e8aac2a..b16ed65134 100644 --- a/apps/web/app/routes/ws/settings/work-queue.tsx +++ b/apps/web/app/routes/ws/settings/work-queue.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; -import { formatDistanceToNowStrict } from "date-fns"; + +import { safeFormatDistanceToNowStrict } from "~/lib/date"; import { trpc } from "~/api/trpc"; import { Badge } from "~/components/ui/badge"; From 75f8d3d4e5a8d5a324d93ee3b9c71b7bbd6fae60 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 23 Apr 2026 15:06:11 -0400 Subject: [PATCH 3/3] refactor: replace date-fns with safe date formatting functions in resource aggregates and workflow components --- apps/web/app/routes/ws/resource-aggregates.tsx | 7 ++++--- apps/web/app/routes/ws/settings/work-queue.tsx | 8 ++++---- .../routes/ws/workflows/_components/WorkflowRunsTable.tsx | 5 ++--- .../routes/ws/workflows/page.$workflowId.runs.$runId.tsx | 5 ++--- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/web/app/routes/ws/resource-aggregates.tsx b/apps/web/app/routes/ws/resource-aggregates.tsx index b54304d566..330adea6e8 100644 --- a/apps/web/app/routes/ws/resource-aggregates.tsx +++ b/apps/web/app/routes/ws/resource-aggregates.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; -import { formatDistanceToNow } from "date-fns"; + +import { safeFormatDistanceToNow } from "~/lib/date"; import { BarChart3, Plus, Search, Trash2 } from "lucide-react"; import { Link } from "react-router"; @@ -166,9 +167,9 @@ export default function ResourceAggregates() { )} Created{" "} - {formatDistanceToNow(new Date(agg.createdAt), { + {safeFormatDistanceToNow(agg.createdAt, { addSuffix: true, - })} + }) ?? "unknown"} diff --git a/apps/web/app/routes/ws/settings/work-queue.tsx b/apps/web/app/routes/ws/settings/work-queue.tsx index b16ed65134..eab3bc6526 100644 --- a/apps/web/app/routes/ws/settings/work-queue.tsx +++ b/apps/web/app/routes/ws/settings/work-queue.tsx @@ -237,14 +237,14 @@ function WorkScopeTable() { {scope.claimedBy ?? "-"} - {formatDistanceToNowStrict(new Date(scope.notBefore), { + {safeFormatDistanceToNowStrict(scope.notBefore, { addSuffix: true, - })} + }) ?? "—"} - {formatDistanceToNowStrict(new Date(scope.eventTs), { + {safeFormatDistanceToNowStrict(scope.eventTs, { addSuffix: true, - })} + }) ?? "—"} ))} diff --git a/apps/web/app/routes/ws/workflows/_components/WorkflowRunsTable.tsx b/apps/web/app/routes/ws/workflows/_components/WorkflowRunsTable.tsx index f1b72d6e5e..aea1eddf2b 100644 --- a/apps/web/app/routes/ws/workflows/_components/WorkflowRunsTable.tsx +++ b/apps/web/app/routes/ws/workflows/_components/WorkflowRunsTable.tsx @@ -1,5 +1,4 @@ import type { RouterOutputs } from "@ctrlplane/trpc"; -import { formatDistanceToNowStrict } from "date-fns"; import { useNavigate, useParams } from "react-router"; import { useWorkspace } from "~/components/WorkspaceProvider"; @@ -11,14 +10,14 @@ import { TableHeader, TableRow, } from "~/components/ui/table"; +import { safeFormatDistanceToNowStrict } from "~/lib/date"; import { WorkflowRunStatusBadge } from "./WorkflowRunStatusBadge"; type WorkflowRun = RouterOutputs["workflows"]["runs"]["list"][number]; function timeAgo(date: Date | string | null) { if (date == null) return "-"; - const d = typeof date === "string" ? new Date(date) : date; - return formatDistanceToNowStrict(d, { addSuffix: true }); + return safeFormatDistanceToNowStrict(date, { addSuffix: true }) ?? "-"; } function WorkflowRunRow({ run }: { run: WorkflowRun }) { diff --git a/apps/web/app/routes/ws/workflows/page.$workflowId.runs.$runId.tsx b/apps/web/app/routes/ws/workflows/page.$workflowId.runs.$runId.tsx index 28d9e3ad32..13627da9c4 100644 --- a/apps/web/app/routes/ws/workflows/page.$workflowId.runs.$runId.tsx +++ b/apps/web/app/routes/ws/workflows/page.$workflowId.runs.$runId.tsx @@ -1,6 +1,5 @@ import type { RouterOutputs } from "@ctrlplane/trpc"; import { Fragment } from "react"; -import { formatDistanceToNowStrict } from "date-fns"; import { ExternalLink } from "lucide-react"; import { Link, useParams } from "react-router"; @@ -26,6 +25,7 @@ import { TableRow, } from "~/components/ui/table"; import { useWorkspace } from "~/components/WorkspaceProvider"; +import { safeFormatDistanceToNowStrict } from "~/lib/date"; import { cn } from "~/lib/utils"; import { JobStatusBadge } from "../_components/JobStatusBadge"; @@ -33,8 +33,7 @@ type WorkflowRunJob = RouterOutputs["workflows"]["runs"]["get"]["jobs"][number]; function timeAgo(date: Date | string | null) { if (date == null) return "-"; - const d = typeof date === "string" ? new Date(date) : date; - return formatDistanceToNowStrict(d, { addSuffix: true }); + return safeFormatDistanceToNowStrict(date, { addSuffix: true }) ?? "-"; } function RunPageHeader({