Files
siftlode/frontend/src/components/ui/RelativeTime.tsx
T
peter e886890d6e perf(watch): a shared link loads the player, not the app
A /watch link is opened by whoever it was sent to — signed out, often on a phone on mobile data —
and it booted through main.tsx: every app provider, the app's i18n (every locale of every area),
the flag sprites, ~392 KB gzip before the player could start. It now has its own HTML entry,
watch.html → watchMain.tsx, which imports WatchPage and what it renders; the server hands every
/watch/ path that page (the OG block injected as before), and vite dev does the same.

Measured on the build: /watch JS 290 → 87 KB gzip, CSS 104 → 19 KB; the app's own bytes unchanged.
Two leaks had to go for that: lib/format imported the app's i18n for one "just now" string (the
relative-time formatters now take `t`, like the file's other t-formatters), and the boundary's
crash copy came from the same i18n (CrashBoundary takes its copy; ErrorBoundary is the app's
wrapper, the watch entry gives it the page's own strings). The page's popover tree went too: it
renders no popover.

Held by: a contract test walking the entry's runtime imports, naming the path to anything only the
app may load (red on a planted i18n import in lib/format), and holding watch.html's head equal to
index.html's; a DB-lane test that every /watch/ path gets the player's page with the link's OG tags
and app paths get the app (red on the old routing). Screenshots at 375 px, EN and HU, video / audio /
password / gone / wrong password / unlocked: identical to the old entry's, bar the video's buffered bar.
2026-10-05 20:51:39 +02:00

36 lines
1.3 KiB
TypeScript

import { useTranslation } from "react-i18next";
import { formatDateTime, formatDateTimeSec, relativeFromMs } from "../../lib/format";
// docs/DESIGN.md "Copy": a relative time ("3d ago") says how recent something is in a list, and the
// exact moment is one hover away — so the pair travels together here instead of every list
// remembering (or forgetting) its own `title`. `seconds` is the log form (the audit). An unreadable
// or missing moment renders `empty` ("—" in a table cell) or nothing.
export function RelativeTime({
at,
seconds = false,
empty = "",
className,
}: {
/** An ISO timestamp, or epoch millis (a client-side notification). */
at: string | number | null | undefined;
seconds?: boolean;
empty?: string;
className?: string;
}) {
const { t, i18n } = useTranslation();
const lang = i18n.language;
const ms = typeof at === "number" ? at : at ? new Date(at).getTime() : NaN;
const text = Number.isFinite(ms) ? relativeFromMs(ms, lang, t) : "";
if (!text) return empty ? <span className={className}>{empty}</span> : null;
const iso = new Date(ms).toISOString();
return (
<time
dateTime={iso}
title={seconds ? formatDateTimeSec(iso, lang) : formatDateTime(iso, lang)}
className={className}
>
{text}
</time>
);
}