Intelligence
Artifacts
Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.
Repository
assignment-dispatch-button.tsxassignment-status-badge.tsxassignments-panel.tsxcreate-mission-form.tsxgovernance-panels.tsxmission-dispatch.tsxmission-edit.tsxmission-state-actions.tsxmission-status-badge.tsxobjective-edit.tsxobjective-status-badge.tsxobjective-status-select.tsxobjectives-panel.tsxreport-list.tsxwork-orders-panel.tsx
README.md
CONSTITUTION_COMPLIANCE_AUDIT_V1.mdREADME.md
repositories/aaf-holdings/hq01/components/sessions/runtime-clock.tsx
1.4 KB
"use client";
import { useEffect, useState } from "react";
/**
* Live runtime display. For a running session it ticks every second; for a
* finished session it shows the frozen elapsed time between start and stop.
*
* The clock initialises to null and only computes "now" after mount, so the
* server-rendered HTML and the first client render agree (no hydration drift).
*/
function format(ms: number): string {
const total = Math.max(0, Math.floor(ms / 1000));
const h = Math.floor(total / 3600);
const m = Math.floor((total % 3600) / 60);
const s = total % 60;
const pad = (n: number) => String(n).padStart(2, "0");
return `${pad(h)}:${pad(m)}:${pad(s)}`;
}
export function RuntimeClock({
startedAt,
endedAt,
running,
className,
}: {
startedAt: string | null;
endedAt: string | null;
running: boolean;
className?: string;
}) {
const [now, setNow] = useState<number | null>(null);
useEffect(() => {
setNow(Date.now());
if (!running) return;
const t = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(t);
}, [running]);
if (!startedAt) return <span className={className}>—</span>;
const start = Date.parse(startedAt);
const end = running
? (now ?? start)
: endedAt
? Date.parse(endedAt)
: (now ?? start);
return (
<span className={className} suppressHydrationWarning>
{format(end - start)}
</span>
);
}
root · /srv/aaf