# Time Ago A "5 minutes ago" timestamp that updates itself. A `now` value on the template is reassigned every minute by a `setInterval`. Every binding that reads `now` re-runs, so every `timeAgo(date, now)` cell on the page advances without explicit refresh. The pattern is short, all client-side, and works for any UI that needs a value to drift over time. ## Page setup ```bash elements create page time-ago ``` The page does nothing on the server beyond rendering. All the reactivity is client-side via the template's `now` and the `setInterval` that updates it. `app/pages/time-ago/index.ts`: ```ts import timeAgo from "./template"; export default function route(req, res) { let serverNow = new Date(); let sampleDates = [ new Date(serverNow.getTime() - 30_000), new Date(serverNow.getTime() - 5 * 60_000), new Date(serverNow.getTime() - 2 * 60 * 60_000), new Date(serverNow.getTime() - 3 * 24 * 60 * 60_000), ]; return new timeAgo({ sampleDates }); } ``` The route passes a few sample timestamps anchored to the server's clock so the demo shows different "ago" ranges. The dates are static (computed once on render); the relative string against each one changes as the page's local `now` ticks. `app/pages/time-ago/template.html`: ```html import "./style.css"; function timeAgo(date: Date, now: Date): string { let seconds = Math.floor((now.getTime() - date.getTime()) / 1000); if (seconds < 5) { return "just now"; } if (seconds < 60) { return `${seconds} seconds ago`; } let minutes = Math.floor(seconds / 60); if (minutes < 60) { return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`; } let hours = Math.floor(minutes / 60); if (hours < 24) { return hours === 1 ? "1 hour ago" : `${hours} hours ago`; } let days = Math.floor(hours / 24); return days === 1 ? "1 day ago" : `${days} days ago`; } function onTick(now: { value: Date }) { now.value = new Date(); } setInterval(() => onTick(now), 60_000)}>

time ago

now: {now.value.toLocaleTimeString()}

``` Three things make the page tick: - `now` is template state, wrapped as `{ value: Date }` so the `onTick` handler can reassign it from a function parameter. Plain `now: Date` would only let the handler mutate properties; for a primitive replacement we need an object wrapper. - `oninit` on the template instance starts a `setInterval` that fires `onTick` every 60 seconds. The interval is created once per page mount. - Each binding that reads `now.value` (inside `timeAgo(d, now.value)` and inside the "now: {…}" cell) re-runs every time `now.value` is reassigned. The dependency tracking is automatic; the call to `timeAgo` doesn't need to know it's reactive. `timeAgo(date, now)` is a pure function: same inputs always produce the same output. Pure inside reactive bindings is the right shape: the function reads two reactive sources (`d`, `now.value`), produces a string, and the binding rerenders whenever either changes. Adding more "fuzz" tiers (seconds, weeks, months) is just adding branches to `timeAgo`; no plumbing changes. ## Routes Register the page in `index.ts`: ```ts import timeAgo from "#app/pages/time-ago"; // ... app.route("/time-ago", timeAgo); ``` ## Notes - **Tick cadence.** Sixty seconds matches the resolution of "minutes ago" labels. For an "X seconds ago" label that updates per second, switch to `setInterval(..., 1000)`. For sparse usage where the page is open for hours, a slower interval saves CPU at the cost of slightly stale labels. - **Stopping the interval.** This recipe doesn't clear the interval. Browsers garbage-collect interval handles when the page navigates away (because the closures referencing the template state are themselves collected). For long-lived single-page apps that keep the template alive across many navigations, capture the handle in `oninit` and clear it in `onremove`. - **Reactivity scope.** `now.value` is reactive within the template that declared it. To share the same `now` across multiple templates, lift it to a module-level reactive value (or a singleton service) so every consumer reads the same source. The recipe keeps it per-page because the tick interval should run only when at least one timestamp is visible. - **Server time vs browser time.** The dates the page receives from the route are anchored to the server's clock. The `now` the page computes is from `new Date()` on the browser, which can drift if the user's system clock is off. For most "X ago" UIs this drift is invisible; if it matters (clinical timestamps, billing), send the server's current time with the data and use that as the base instead of `new Date()`. - **Pluralization and i18n.** The simple `n === 1 ? ... : ...` branches in `timeAgo` cover English. For other languages, replace the function with a call into your i18n layer or use `Intl.RelativeTimeFormat` (browser-native, no dependency). The reactivity shape is unchanged; only the string formatting moves.