Skip to content

Commit 5e4a025

Browse files
authored
🤖 Show humanized 'last used' time in idle workspace tooltip (#207)
Adds human-readable relative time to idle workspace tooltips, making it easier to identify workspace recency at a glance. **Changes:** - Idle workspaces now show `Idle • Last used 5 minutes ago` in tooltip - Never-used workspaces show just `Idle` - New `formatRelativeTime()` utility handles relative time formatting - Comprehensive test coverage for all time ranges **Examples:** - "just now" (< 1 minute) - "5 minutes ago" - "3 hours ago" - "2 days ago" - "3 weeks ago" - "6 months ago" - "2 years ago" Uses existing `lastUserMessageAt` from `WorkspaceState` (tracks most recent user message timestamp). _Generated with `cmux`_
1 parent 83852f8 commit 5e4a025

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

‎src/components/ProjectSidebar.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useDrag, useDrop, useDragLayer } from "react-dnd";
1212
import { sortProjectsByOrder, reorderProjects, normalizeOrder } from "@/utils/projectOrdering";
1313
import { matchesKeybind, formatKeybind, KEYBINDS } from "@/utils/ui/keybinds";
1414
import { abbreviatePath } from "@/utils/ui/pathAbbreviation";
15+
import { formatRelativeTime } from "@/utils/ui/dateTime";
1516
import { TooltipWrapper, Tooltip } from "./Tooltip";
1617
import { StatusIndicator } from "./StatusIndicator";
1718
// Removed: import { getModelName } from "@/utils/ai/models";
@@ -1070,6 +1071,8 @@ const ProjectSidebar: React.FC<ProjectSidebarProps> = ({
10701071
"Assistant is responding"
10711072
) : isUnread ? (
10721073
"Unread messages"
1074+
) : workspaceState.recencyTimestamp ? (
1075+
`Idle • Last used ${formatRelativeTime(workspaceState.recencyTimestamp)}`
10731076
) : (
10741077
"Idle"
10751078
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { formatRelativeTime } from "./dateTime";
3+
4+
describe("formatRelativeTime", () => {
5+
test("should return 'just now' for very recent timestamps", () => {
6+
const now = Date.now();
7+
expect(formatRelativeTime(now)).toBe("just now");
8+
expect(formatRelativeTime(now - 30 * 1000)).toBe("just now"); // 30 seconds ago
9+
});
10+
11+
test("should return 'just now' for future timestamps", () => {
12+
const future = Date.now() + 5000;
13+
expect(formatRelativeTime(future)).toBe("just now");
14+
});
15+
16+
test("should format minutes correctly", () => {
17+
const now = Date.now();
18+
expect(formatRelativeTime(now - 1 * 60 * 1000)).toBe("1 minute ago");
19+
expect(formatRelativeTime(now - 5 * 60 * 1000)).toBe("5 minutes ago");
20+
expect(formatRelativeTime(now - 59 * 60 * 1000)).toBe("59 minutes ago");
21+
});
22+
23+
test("should format hours correctly", () => {
24+
const now = Date.now();
25+
expect(formatRelativeTime(now - 1 * 60 * 60 * 1000)).toBe("1 hour ago");
26+
expect(formatRelativeTime(now - 3 * 60 * 60 * 1000)).toBe("3 hours ago");
27+
expect(formatRelativeTime(now - 23 * 60 * 60 * 1000)).toBe("23 hours ago");
28+
});
29+
30+
test("should format days correctly", () => {
31+
const now = Date.now();
32+
expect(formatRelativeTime(now - 1 * 24 * 60 * 60 * 1000)).toBe("1 day ago");
33+
expect(formatRelativeTime(now - 3 * 24 * 60 * 60 * 1000)).toBe("3 days ago");
34+
expect(formatRelativeTime(now - 6 * 24 * 60 * 60 * 1000)).toBe("6 days ago");
35+
});
36+
37+
test("should format weeks correctly", () => {
38+
const now = Date.now();
39+
expect(formatRelativeTime(now - 7 * 24 * 60 * 60 * 1000)).toBe("1 week ago");
40+
expect(formatRelativeTime(now - 14 * 24 * 60 * 60 * 1000)).toBe("2 weeks ago");
41+
expect(formatRelativeTime(now - 27 * 24 * 60 * 60 * 1000)).toBe("3 weeks ago");
42+
});
43+
44+
test("should format months correctly", () => {
45+
const now = Date.now();
46+
expect(formatRelativeTime(now - 30 * 24 * 60 * 60 * 1000)).toBe("1 month ago");
47+
expect(formatRelativeTime(now - 60 * 24 * 60 * 60 * 1000)).toBe("2 months ago");
48+
expect(formatRelativeTime(now - 180 * 24 * 60 * 60 * 1000)).toBe("6 months ago");
49+
});
50+
51+
test("should format years correctly", () => {
52+
const now = Date.now();
53+
expect(formatRelativeTime(now - 365 * 24 * 60 * 60 * 1000)).toBe("1 year ago");
54+
expect(formatRelativeTime(now - 730 * 24 * 60 * 60 * 1000)).toBe("2 years ago");
55+
});
56+
});

‎src/utils/ui/dateTime.ts‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,44 @@ export function formatFullTimestamp(timestamp: number): string {
5454
hour12: true,
5555
});
5656
}
57+
58+
/**
59+
* Formats a Unix timestamp (milliseconds) into a human-readable relative time string.
60+
* Examples: "2 minutes ago", "3 hours ago", "2 days ago", "3 weeks ago"
61+
*
62+
* @param timestamp Unix timestamp in milliseconds
63+
* @returns Humanized relative time string
64+
*/
65+
export function formatRelativeTime(timestamp: number): string {
66+
const now = Date.now();
67+
const diffMs = now - timestamp;
68+
69+
// Handle future timestamps
70+
if (diffMs < 0) {
71+
return "just now";
72+
}
73+
74+
const seconds = Math.floor(diffMs / 1000);
75+
const minutes = Math.floor(seconds / 60);
76+
const hours = Math.floor(minutes / 60);
77+
const days = Math.floor(hours / 24);
78+
const weeks = Math.floor(days / 7);
79+
const months = Math.floor(days / 30);
80+
const years = Math.floor(days / 365);
81+
82+
if (seconds < 60) {
83+
return "just now";
84+
} else if (minutes < 60) {
85+
return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
86+
} else if (hours < 24) {
87+
return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
88+
} else if (days < 7) {
89+
return days === 1 ? "1 day ago" : `${days} days ago`;
90+
} else if (weeks < 4) {
91+
return weeks === 1 ? "1 week ago" : `${weeks} weeks ago`;
92+
} else if (months < 12) {
93+
return months === 1 ? "1 month ago" : `${months} months ago`;
94+
} else {
95+
return years === 1 ? "1 year ago" : `${years} years ago`;
96+
}
97+
}

0 commit comments

Comments
 (0)