Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions extensions/sessions/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,37 @@ export function formatTimestamp(date: Date): string {
return `${year}-${month}-${day} ${hours}:${minutes}`;
}

export function formatRelativeTime(date: Date): string {
const now = new Date();
export function formatRelativeTime(date: Date, now: Date = new Date()): string {
const diffMs = now.getTime() - date.getTime();
const diffSec = Math.floor(diffMs / 1000);
const diffSec = Math.max(0, Math.floor(diffMs / 1000));
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHour / 24);

if (diffSec < 60) return "Just now";
if (diffMin < 60) return `${diffMin}m ago`;
if (diffHour < 24) return `${diffHour}h ago`;
if (diffDay === 1) return "Yesterday";
return `${diffDay}d ago`;
// Within 24 hours: display clock time and relative duration (m/h ago)
// This avoids midnight flips showing "0d ago" or cross-year jumps in early January
if (diffHour < 24) {
const time = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
if (diffSec < 60) return `${time} (just now)`;
if (diffMin < 60) return `${time} (${diffMin}m ago)`;
return `${time} (${diffHour}h ago)`;
}

// Older than 24 hours: display calendar date and day/month/year distance
// If under a year, show MM-DD so year transitions in January stay readable
if (diffDay < 365) {
const dateStr = `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
if (diffDay < 60) {
return `${dateStr} (${diffDay}d ago)`;
}
const months = Math.max(1, Math.floor(diffDay / 30));
return `${dateStr} (${months}mo ago)`;
}

// Cross-year older than 365 days
const years = Math.max(1, Math.floor(diffDay / 365));
const dateStr = `${date.getFullYear()}-${pad(date.getMonth() + 1)}`;
return `${dateStr} (${years}y ago)`;
}

const cleanDisplayLine = (text: string) =>
Expand Down Expand Up @@ -127,6 +145,8 @@ export const buildSearchText = (session: SessionInfoLike): string =>
session.id,
session.cwd,
session.firstMessage ?? "",
formatTimestamp(session.modified),
formatRelativeTime(session.modified),
]
.join(" ")
.toLowerCase();
Expand Down
49 changes: 48 additions & 1 deletion tests/extensions/sessions/sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
buildSessionDescription,
buildSessionLabel,
buildSessionPreview,
buildSessionSearchEntries,
filterSessionEntries,
formatRelativeTime,
parseLimit,
selectSessionStatsWindow,
type SessionInfoLike,
selectSessionStatsWindow,
} from "../../../extensions/sessions/sessions.ts";

const session: SessionInfoLike = {
Expand Down Expand Up @@ -144,3 +146,48 @@ test("bounded preview reports omitted messages and content bytes", () => {
]);
assert.match(preview.subtitle, /100 messages/);
});

test("formatRelativeTime deterministically formats relative hours and calendar dates with frozen clocks", () => {
const baseNow = new Date("2026-09-21T12:00:00");
const tenMins = new Date("2026-09-21T11:50:00");
const twoHours = new Date("2026-09-21T10:00:00");
const threeDays = new Date("2026-09-18T12:00:00");
const twelveDays = new Date("2026-09-09T12:00:00");

assert.equal(formatRelativeTime(tenMins, baseNow), "11:50 (10m ago)");
assert.equal(formatRelativeTime(twoHours, baseNow), "10:00 (2h ago)");
assert.equal(formatRelativeTime(threeDays, baseNow), "09-18 (3d ago)");
assert.equal(formatRelativeTime(twelveDays, baseNow), "09-09 (12d ago)");

// Midnight boundary test case: 10 minutes ago across midnight (23:55 viewed at 00:05)
const midnightNow = new Date("2026-09-21T00:05:00");
const midnightTenMinsAgo = new Date("2026-09-20T23:55:00");
assert.equal(
formatRelativeTime(midnightTenMinsAgo, midnightNow),
"23:55 (10m ago)",
);

// Cross-year boundary test case: New Year 00:05 viewing New Year's Eve 23:55
const janNow = new Date("2027-01-01T00:05:00");
const decEve = new Date("2026-12-31T23:55:00");
assert.equal(formatRelativeTime(decEve, janNow), "23:55 (10m ago)");

// Cross-year within 60 days
const jan3rd = new Date("2027-01-03T12:00:00");
const dec30 = new Date("2026-12-30T12:00:00");
assert.equal(formatRelativeTime(dec30, jan3rd), "12-30 (4d ago)");

// Over 1 year ago
const twoYearsLater = new Date("2028-09-21T12:00:00");
assert.equal(formatRelativeTime(baseNow, twoYearsLater), "2026-09 (2y ago)");
});

test("session search matches formatted dates and years", () => {
const sample = {
...session,
modified: new Date("2026-09-06T10:00:00Z"),
};
const entries = buildSessionSearchEntries([sample]);
assert.equal(filterSessionEntries(entries, "2026").length, 1);
assert.equal(filterSessionEntries(entries, "09-06").length, 1);
});
Loading