open-swe/ui/src/features/agents/components/messages/MessageTimestamp.tsx
Adam Moussa 4d51410dc8
refactor: move ui/src/{components,lib}/agents into features/ layout
Part of the domain-reorg adoption (build plan step C6): fork content,
upstream layout. git mv's all 80 pure-rename files from the move-map
(agent-scoped components/lib -> ui/src/features/{agents,automations,
reviews,settings}/...), including ported/ splitting into
features/agents/experiments/ (still-unintegrated desktop-host ports)
and features/agents/components/chat/ (CloudPromptBar, CodeBlock,
DiffView, Logo, Markdown, ReplyCard, ShellCommand, ToolExecution --
files actually wired into the live dashboard). Fork-diverged files
(PlanReview.tsx, WorkflowApprovalCard.tsx, AgentsSidebar.tsx,
SidebarFilterMenu.tsx, AgentGitPanel.tsx, AutomationEditor.tsx,
DiffView.tsx, CloudPromptBar.tsx) keep fork content -- verified via
diff that only import lines changed.

Rewrote @/components/agents and @/lib/agents imports across all 54
importer files plus the moved files' own internal imports (grep-driven,
85-entry alias map covering every old->new path pair). Two hazards
caught only by the build gate (not tsc, since both files sit under the
new experiments/** tsconfig exclude): ui/src/lib/notifications.ts had
a relative `./agents/types` import (no `@/` alias) that the grep missed;
and features/agents/experiments/index.ts's barrel re-export of the
messages module needed to switch from an `@/` alias to a relative
import (`../components/messages`) after landing inside the newly
tsconfig-excluded experiments/ directory -- vite-tsconfig-paths failed
to resolve it at build time even though tsc stayed silent.

AgentPromptBar.tsx (the 2-line CloudPromptBar re-export shim) moves to
features/agents/components/ with its export path retargeted at
CloudPromptBar's new chat/ location -- a D/A pair, not a content loss,
since the import-path edit drops it below git's rename-similarity
threshold. ported/index.ts is the same D/A story for the same reason.

Swapped the 8-file ported/ exclude lists in tsconfig.json and
eslint.config.js for the single `src/features/agents/experiments/**`
glob (upstream's simplification, no behavior change). Deleted
ui/pnpm-workspace.yaml (lockfile hygiene -- bun stays the toolchain).

Gates: bunx tsc --noEmit (clean), bun run build (clean after the two
notifications.ts / experiments-index.ts fixes above), bun run test
(33/33), and the full Playwright E2E suite against the real langgraph
dev server + built dashboard (9/9).
2026-07-17 14:51:12 -04:00

75 lines
1.8 KiB
TypeScript

type MessageTimestampProps = {
timestamp: string;
startedAt?: string;
align?: "left" | "right";
className?: string;
};
const timeFormatter = new Intl.DateTimeFormat(undefined, {
hour: "numeric",
minute: "2-digit",
second: "2-digit",
});
const datedTimeFormatter = new Intl.DateTimeFormat(undefined, {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
});
const fullFormatter = new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "medium",
});
function parseTimestamp(value?: string | null): Date | null {
if (!value) return null;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
}
function isSameLocalDay(a: Date, b: Date): boolean {
return (
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate()
);
}
function shortTimestamp(date: Date): string {
return isSameLocalDay(date, new Date())
? timeFormatter.format(date)
: datedTimeFormatter.format(date);
}
export function MessageTimestamp({
timestamp,
startedAt,
align = "left",
className = "",
}: MessageTimestampProps) {
const date = parseTimestamp(timestamp);
if (!date) return null;
const startDate = parseTimestamp(startedAt);
const title =
startDate && Math.abs(date.getTime() - startDate.getTime()) >= 1000
? `Started ${fullFormatter.format(startDate)} · Last updated ${fullFormatter.format(date)}`
: fullFormatter.format(date);
return (
<div
className={`flex ${align === "right" ? "justify-end" : "justify-start"} ${className}`}
>
<time
dateTime={date.toISOString()}
title={title}
className="text-[11px] leading-4 text-[color:var(--ui-text-dim)] tabular-nums select-none"
>
{shortTimestamp(date)}
</time>
</div>
);
}