Replace fake relative time strings with ISO 8601 timestamps in API

The API was returning human-readable strings like "2h ago" and "in 4 hours"
instead of real timestamps. The frontend now formats these for display using
timeAgo() and a new timeUntil() helper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-06 13:35:36 -05:00
parent daf5f5e24d
commit 4976358260
10 changed files with 70 additions and 47 deletions

View file

@ -12,6 +12,20 @@ export function timeAgo(iso: string): string {
return `${days}d ago`;
}
/**
* Format an ISO 8601 timestamp as a relative future time string (e.g. "in 2h", "in 3d").
*/
export function timeUntil(iso: string): string {
const seconds = Math.floor((new Date(iso).getTime() - Date.now()) / 1000);
if (seconds < 60) return "in <1m";
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `in ${minutes}m`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `in ${hours}h`;
const days = Math.floor(hours / 24);
return `in ${days}d`;
}
/**
* Return a human-readable date label for grouping (e.g. "Today", "Yesterday", "Previous 7 days").
*/

View file

@ -1,6 +1,7 @@
import { Link, Outlet, useNavigate } from "react-router";
import { PlusIcon } from "@heroicons/react/24/outline";
import { apiJson } from "../api-client";
import { timeAgo } from "../lib/time";
import type { PaginatedSavedQueryList, PaginatedHistoryEntryList } from "@qltysh/arc-api-client";
import type { Route } from "./+types/insights";
@ -108,7 +109,7 @@ export default function InsightsLayout({ loaderData }: Route.ComponentProps) {
{entry.sql}
</span>
<span className="font-mono text-[10px] text-fg-muted">
{entry.timestamp} · {entry.rowsReturned} rows
{timeAgo(entry.timestamp)} · {entry.rowsReturned} rows
</span>
</button>
))}

View file

@ -56,6 +56,7 @@ import type {
VerificationStatus,
} from "../data/verifications";
import { apiJson } from "../api-client";
import { timeAgo } from "../lib/time";
import type { VerificationDetailResponse } from "@qltysh/arc-api-client";
import type { Route } from "./+types/verification-detail";
@ -317,7 +318,7 @@ export default function VerificationDetail({ loaderData }: Route.ComponentProps)
<ResultIcon result={run.result} />
</td>
<td className="py-2.5 px-3 text-fg-muted">{run.workflow}</td>
<td className="py-2.5 pl-3 pr-4 text-right text-fg-muted">{run.timestamp}</td>
<td className="py-2.5 pl-3 pr-4 text-right text-fg-muted">{timeAgo(run.timestamp)}</td>
</tr>
))}
</tbody>

View file

@ -14,6 +14,7 @@ import {
} from "@heroicons/react/24/outline";
import { Link } from "react-router";
import { apiJson } from "../api-client";
import { timeAgo, timeUntil } from "../lib/time";
import type { PaginatedWorkflowList } from "@qltysh/arc-api-client";
import type { Route } from "./+types/workflows";
@ -110,10 +111,10 @@ export async function loader({ request }: Route.LoaderArgs) {
name: w.name,
slug: w.slug,
filename: w.filename,
lastRun: w.last_run?.label ?? "never",
lastRun: w.last_run?.ran_at ? timeAgo(w.last_run.ran_at) : "never",
color: slugColorMap[w.slug] ?? "var(--color-teal-500)",
schedule: w.schedule?.expression,
nextRun: w.schedule?.next_run,
nextRun: w.schedule?.next_run ? timeUntil(w.schedule.next_run) : undefined,
}));
return { workflows };
}

View file

@ -1104,6 +1104,7 @@ mod usage {
}
mod workflows {
use super::ts;
use arc_types::*;
pub fn list_items() -> Vec<WorkflowListItem> {
@ -1112,28 +1113,28 @@ mod workflows {
name: "Fix Build".into(),
slug: "fix_build".into(),
filename: "fix_build.dot".into(),
last_run: Some(WorkflowLastRun { label: "2 hours ago".into() }),
last_run: Some(WorkflowLastRun { ran_at: ts("2025-09-15T12:00:00Z") }),
schedule: None,
},
WorkflowListItem {
name: "Implement Feature".into(),
slug: "implement".into(),
filename: "implement.dot".into(),
last_run: Some(WorkflowLastRun { label: "4 days ago".into() }),
last_run: Some(WorkflowLastRun { ran_at: ts("2025-09-11T10:00:00Z") }),
schedule: None,
},
WorkflowListItem {
name: "Sync Drift".into(),
slug: "sync_drift".into(),
filename: "sync_drift.dot".into(),
last_run: Some(WorkflowLastRun { label: "1 day ago".into() }),
last_run: Some(WorkflowLastRun { ran_at: ts("2025-09-14T14:00:00Z") }),
schedule: None,
},
WorkflowListItem {
name: "Expand Product".into(),
slug: "expand".into(),
filename: "expand.dot".into(),
last_run: Some(WorkflowLastRun { label: "2 weeks ago".into() }),
last_run: Some(WorkflowLastRun { ran_at: ts("2025-09-01T08:00:00Z") }),
schedule: None,
},
]
@ -1373,6 +1374,7 @@ disk = 10
}
mod verifications {
use super::ts;
use arc_types::*;
// ── Category definitions (name, question, controls) ─────────────────
@ -1417,35 +1419,35 @@ mod verifications {
run_title: "PR #312 \u{2014} Add OAuth2 PKCE flow",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "2h ago",
timestamp: "2025-09-15T12:00:00Z",
},
RecentResultDef {
run_id: "run-046",
run_title: "PR #311 \u{2014} Update rate limiter config",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "5h ago",
timestamp: "2025-09-15T09:00:00Z",
},
RecentResultDef {
run_id: "run-044",
run_title: "PR #309 \u{2014} Migrate to pnpm",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "1d ago",
timestamp: "2025-09-14T14:00:00Z",
},
RecentResultDef {
run_id: "run-042",
run_title: "PR #307 \u{2014} Fix session timeout",
workflow: "fix_build",
result: VerificationStatus::Pass,
timestamp: "2d ago",
timestamp: "2025-09-13T14:00:00Z",
},
RecentResultDef {
run_id: "run-040",
run_title: "PR #305 \u{2014} Add webhook retries",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "3d ago",
timestamp: "2025-09-12T14:00:00Z",
},
];
@ -1455,35 +1457,35 @@ mod verifications {
run_title: "PR #312 \u{2014} Add OAuth2 PKCE flow",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "2h ago",
timestamp: "2025-09-15T12:00:00Z",
},
RecentResultDef {
run_id: "run-046",
run_title: "PR #311 \u{2014} Update rate limiter config",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "5h ago",
timestamp: "2025-09-15T09:00:00Z",
},
RecentResultDef {
run_id: "run-044",
run_title: "PR #309 \u{2014} Migrate to pnpm",
workflow: "code_review",
result: VerificationStatus::Fail,
timestamp: "1d ago",
timestamp: "2025-09-14T14:00:00Z",
},
RecentResultDef {
run_id: "run-042",
run_title: "PR #307 \u{2014} Fix session timeout",
workflow: "fix_build",
result: VerificationStatus::Pass,
timestamp: "2d ago",
timestamp: "2025-09-13T14:00:00Z",
},
RecentResultDef {
run_id: "run-040",
run_title: "PR #305 \u{2014} Add webhook retries",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "3d ago",
timestamp: "2025-09-12T14:00:00Z",
},
];
@ -1493,35 +1495,35 @@ mod verifications {
run_title: "PR #312 \u{2014} Add OAuth2 PKCE flow",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "2h ago",
timestamp: "2025-09-15T12:00:00Z",
},
RecentResultDef {
run_id: "run-046",
run_title: "PR #311 \u{2014} Update rate limiter config",
workflow: "code_review",
result: VerificationStatus::Fail,
timestamp: "5h ago",
timestamp: "2025-09-15T09:00:00Z",
},
RecentResultDef {
run_id: "run-044",
run_title: "PR #309 \u{2014} Migrate to pnpm",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "1d ago",
timestamp: "2025-09-14T14:00:00Z",
},
RecentResultDef {
run_id: "run-042",
run_title: "PR #307 \u{2014} Fix session timeout",
workflow: "fix_build",
result: VerificationStatus::Pass,
timestamp: "2d ago",
timestamp: "2025-09-13T14:00:00Z",
},
RecentResultDef {
run_id: "run-040",
run_title: "PR #305 \u{2014} Add webhook retries",
workflow: "code_review",
result: VerificationStatus::Fail,
timestamp: "3d ago",
timestamp: "2025-09-12T14:00:00Z",
},
];
@ -1531,35 +1533,35 @@ mod verifications {
run_title: "PR #312 \u{2014} Add OAuth2 PKCE flow",
workflow: "code_review",
result: VerificationStatus::Fail,
timestamp: "2h ago",
timestamp: "2025-09-15T12:00:00Z",
},
RecentResultDef {
run_id: "run-046",
run_title: "PR #311 \u{2014} Update rate limiter config",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "5h ago",
timestamp: "2025-09-15T09:00:00Z",
},
RecentResultDef {
run_id: "run-044",
run_title: "PR #309 \u{2014} Migrate to pnpm",
workflow: "code_review",
result: VerificationStatus::Fail,
timestamp: "1d ago",
timestamp: "2025-09-14T14:00:00Z",
},
RecentResultDef {
run_id: "run-042",
run_title: "PR #307 \u{2014} Fix session timeout",
workflow: "fix_build",
result: VerificationStatus::Fail,
timestamp: "2d ago",
timestamp: "2025-09-13T14:00:00Z",
},
RecentResultDef {
run_id: "run-040",
run_title: "PR #305 \u{2014} Add webhook retries",
workflow: "code_review",
result: VerificationStatus::Pass,
timestamp: "3d ago",
timestamp: "2025-09-12T14:00:00Z",
},
];
@ -2027,7 +2029,7 @@ mod verifications {
run: RunReference { id: r.run_id.into(), title: r.run_title.into() },
workflow: WorkflowReference { slug: r.workflow.into() },
result: r.result,
timestamp: r.timestamp.into(),
timestamp: ts(r.timestamp),
})
.collect()
}
@ -2420,14 +2422,14 @@ mod insights {
HistoryEntry {
id: "h1".into(),
sql: "SELECT workflow_name, COUNT(*) FROM runs GROUP BY 1".into(),
timestamp: "2 min ago".into(),
timestamp: ts("2025-09-15T13:58:00Z"),
elapsed: 0.342,
row_count: 6,
},
HistoryEntry {
id: "h2".into(),
sql: "SELECT * FROM runs WHERE status = 'failed' LIMIT 100".into(),
timestamp: "8 min ago".into(),
timestamp: ts("2025-09-15T13:52:00Z"),
elapsed: 0.127,
row_count: 23,
},
@ -2435,7 +2437,7 @@ mod insights {
id: "h3".into(),
sql: "SELECT date_trunc('day', created_at) as d, COUNT(*) FROM runs GROUP BY 1"
.into(),
timestamp: "15 min ago".into(),
timestamp: ts("2025-09-15T13:45:00Z"),
elapsed: 0.531,
row_count: 30,
},

View file

@ -1796,19 +1796,21 @@ components:
example: "0 */6 * * *"
next_run:
type: string
description: Human-readable relative timestamp of the next run.
example: in 4 hours
format: date-time
description: ISO 8601 timestamp of the next scheduled run.
example: "2025-09-15T18:00:00Z"
WorkflowLastRun:
description: Information about a workflow's most recent run.
type: object
required:
- label
- ran_at
properties:
label:
ran_at:
type: string
description: Human-readable relative timestamp.
example: 2 hours ago
format: date-time
description: ISO 8601 timestamp of the most recent run.
example: "2025-09-15T12:00:00Z"
UsageStageRef:
description: Reference to a usage stage.
@ -2536,8 +2538,9 @@ components:
$ref: "#/components/schemas/VerificationStatus"
timestamp:
type: string
description: Human-readable relative timestamp of the evaluation.
example: 2h ago
format: date-time
description: ISO 8601 timestamp of the evaluation.
example: "2025-09-15T12:00:00Z"
SiblingControl:
description: Summary of a sibling verification control in the same category.
@ -2998,8 +3001,9 @@ components:
example: "SELECT workflow_name, COUNT(*) FROM runs GROUP BY 1"
timestamp:
type: string
description: Human-readable relative timestamp of execution.
example: 2 min ago
format: date-time
description: ISO 8601 timestamp of execution.
example: "2025-09-15T14:00:00Z"
elapsed:
type: number
description: Query execution time in seconds.

View file

@ -27,7 +27,7 @@ export interface HistoryEntry {
*/
'sql': string;
/**
* Human-readable relative timestamp of execution.
* ISO 8601 timestamp of execution.
*/
'timestamp': string;
/**

View file

@ -31,7 +31,7 @@ export interface RecentControlResult {
'workflow': WorkflowReference;
'result': VerificationStatus;
/**
* Human-readable relative timestamp of the evaluation.
* ISO 8601 timestamp of the evaluation.
*/
'timestamp': string;
}

View file

@ -19,8 +19,8 @@
*/
export interface WorkflowLastRun {
/**
* Human-readable relative timestamp.
* ISO 8601 timestamp of the most recent run.
*/
'label': string;
'ran_at': string;
}

View file

@ -23,7 +23,7 @@ export interface WorkflowSchedule {
*/
'expression': string;
/**
* Human-readable relative timestamp of the next run.
* ISO 8601 timestamp of the next scheduled run.
*/
'next_run'?: string;
}