diff --git a/src/Frontend/src/components/AdaptiveTimestamp.spec.ts b/src/Frontend/src/components/AdaptiveTimestamp.spec.ts
new file mode 100644
index 000000000..0a70f5890
--- /dev/null
+++ b/src/Frontend/src/components/AdaptiveTimestamp.spec.ts
@@ -0,0 +1,89 @@
+import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
+import { cleanup, render, screen } from "@testing-library/vue";
+import AdaptiveTimestamp from "@/components/AdaptiveTimestamp.vue";
+import { useTimestampZone } from "@/composables/timestampZone";
+
+const NOW = new Date("2026-09-03T15:00:00");
+
+function renderAt(date: Date) {
+ cleanup();
+ render(AdaptiveTimestamp, { props: { dateUtc: date.toISOString() } });
+ return {
+ absolute: screen.getByTestId("adaptive-absolute").textContent!,
+ relative: screen.getByTestId("adaptive-relative").textContent!,
+ };
+}
+
+describe("FEATURE: Age-adaptive timestamps", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(NOW);
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ useTimestampZone().zone.value = "local";
+ });
+
+ test("EXAMPLE: Today shows the time only", () => {
+ const date = new Date("2026-09-03T13:00:00");
+ const { absolute, relative } = renderAt(date);
+
+ expect(absolute).toBe(date.toLocaleTimeString());
+ expect(relative).toContain("2 hours ago");
+ });
+
+ test("EXAMPLE: Yesterday is labeled yesterday", () => {
+ const date = new Date("2026-09-02T13:00:00");
+ const { absolute, relative } = renderAt(date);
+
+ expect(absolute).toBe(`yesterday ${date.toLocaleTimeString()}`);
+ expect(relative).toContain("1 day ago");
+ });
+
+ test("EXAMPLE: Within the past week the weekday is named", () => {
+ const date = new Date("2026-08-31T13:00:00"); // Monday
+ const { absolute, relative } = renderAt(date);
+
+ expect(absolute).toBe(`${date.toLocaleDateString(undefined, { weekday: "long" })} ${date.toLocaleTimeString()}`);
+ expect(relative).toContain("3 days ago");
+ });
+
+ test("EXAMPLE: Older dates keep the regular browser format", () => {
+ const date = new Date("2026-08-20T13:00:00");
+ const { absolute, relative } = renderAt(date);
+
+ expect(absolute).toBe(date.toLocaleString());
+ expect(relative).toContain("2 weeks ago");
+ });
+
+ test("EXAMPLE: The part prop renders only one half, for split layouts", () => {
+ cleanup();
+ render(AdaptiveTimestamp, { props: { dateUtc: new Date("2026-09-03T13:00:00").toISOString(), part: "relative" } });
+ expect(screen.queryByTestId("adaptive-absolute")).not.toBeInTheDocument();
+ expect(screen.getByTestId("adaptive-relative").textContent).toBe("2 hours ago");
+
+ cleanup();
+ render(AdaptiveTimestamp, { props: { dateUtc: new Date("2026-09-03T13:00:00").toISOString(), part: "absolute" } });
+ expect(screen.queryByTestId("adaptive-relative")).not.toBeInTheDocument();
+ expect(screen.getByTestId("adaptive-absolute")).toBeInTheDocument();
+ });
+
+ test("EXAMPLE: In UTC mode the wall-clock time is UTC", () => {
+ const { zone } = useTimestampZone();
+ zone.value = "utc";
+
+ const date = new Date("2026-09-03T13:00:00");
+ const utcWall = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
+
+ // toContain: near the UTC midnight boundary the day prefix may differ from local mode
+ expect(renderAt(date).absolute).toContain(utcWall.toLocaleTimeString());
+ });
+
+ test("EXAMPLE: The coarse relative label covers moments to years", () => {
+ expect(renderAt(new Date("2026-09-03T14:59:50")).relative).toContain("moments ago");
+ expect(renderAt(new Date("2026-09-03T14:30:00")).relative).toContain("30 minutes ago");
+ expect(renderAt(new Date("2026-06-03T15:00:00")).relative).toContain("3 months ago");
+ expect(renderAt(new Date("2024-09-03T15:00:00")).relative).toContain("2 years ago");
+ });
+});
diff --git a/src/Frontend/src/components/AdaptiveTimestamp.vue b/src/Frontend/src/components/AdaptiveTimestamp.vue
new file mode 100644
index 000000000..9addc2a7f
--- /dev/null
+++ b/src/Frontend/src/components/AdaptiveTimestamp.vue
@@ -0,0 +1,37 @@
+
+
+
+
+ {{ absolute }}
+ {{ props.part === "relative" ? "" : " · " }}{{ relative }}
+
+
+
+
diff --git a/src/Frontend/src/components/ResultsCount.spec.ts b/src/Frontend/src/components/ResultsCount.spec.ts
index 20e695c3b..84255aaf8 100644
--- a/src/Frontend/src/components/ResultsCount.spec.ts
+++ b/src/Frontend/src/components/ResultsCount.spec.ts
@@ -23,6 +23,15 @@ describe("FEATURE: Results count", () => {
expect(screen.getByText("Showing 10 of 10 result(s) · took 320 ms")).toBeInTheDocument();
});
+ test("EXAMPLE: When the query ran is shown relatively with the timestamp as tooltip", () => {
+ const completedAt = new Date(Date.now() - 3 * 60 * 1000).toISOString();
+ render(ResultsCount, { props: { displayed: 10, total: 10, durationMs: 320, completedAt } });
+
+ const ran = screen.getByTestId("ran-ago");
+ expect(ran.textContent).toContain("minutes ago");
+ expect(ran.getAttribute("title")).toContain("(UTC)");
+ });
+
test("EXAMPLE: Zero results render plainly", () => {
render(ResultsCount, { props: { displayed: 0, total: 0 } });
diff --git a/src/Frontend/src/components/ResultsCount.vue b/src/Frontend/src/components/ResultsCount.vue
index c5eea31bc..5a90be26f 100644
--- a/src/Frontend/src/components/ResultsCount.vue
+++ b/src/Frontend/src/components/ResultsCount.vue
@@ -1,12 +1,25 @@