Intelligence

Artifacts

Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.

Repository
.gitignoreDockerfilenext-env.d.tsnext.config.mjspackage-lock.jsonpackage.jsonpostcss.config.mjsREADME.mdtailwind.config.tstsconfig.jsontsconfig.tsbuildinfo
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