import type { Save } from "../mirror/queries.ts"; import { type Html, html } from "./html.ts"; import { formatDate } from "./layout.ts"; const DAY = 86400; // Kepr 2's labels for saves per day across the whole history. const PACE: [number, string][] = [ [1, "very active"], [0.3, "active"], [0.1, "occasional"], ]; /** * §21.4's dotted timeline as SVG, so it draws without JavaScript: one dot per save on a dashed * line, x by time (as a percentage, so it scales without squashing dots), brighter and fuller * where neighbours crowd within a fortieth of the span. */ export function timelineSvg(repo: string, saves: Save[]): Html { const first = saves[0]?.created_at ?? 0; const span = (saves[saves.length - 1]?.created_at ?? 0) - first; const reach = Math.max(span / 40, 3600); const times = saves.map((save) => save.created_at); const dots = saves.map((save) => { const x = span === 0 ? 50 : 1 + (98 * (save.created_at - first)) / span; // ponytail: O(n²) neighbour count; a sliding window over sorted times if a repo reaches ~5,000 saves. const near = times.filter((t) => Math.abs(t - save.created_at) <= reach).length; const level = near >= 8 ? 3 : near >= 4 ? 2 : near >= 2 ? 1 : 0; const meta = [formatDate(save.created_at), save.agent, save.session] .filter(Boolean) .join(" · "); const tip = `${save.id}\n${save.message.split("\n")[0]}\n${meta}`; return html`${tip}`; }); const perDay = saves.length / Math.max(span / DAY, 1); const pace = PACE.find(([min]) => perDay > min)?.[1] ?? "quiet"; return html` ${dots}

${pace} · density = activity · position = time

`; }