Skip to content

Commit 71c3456

Browse files
authored
fix: centralize staged relative time updates (#576)
1 parent 08fbdca commit 71c3456

3 files changed

Lines changed: 63 additions & 39 deletions

File tree

apps/staged/src/lib/features/projects/ProjectSection.svelte

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import { subscribeDragDrop } from '../branches/dragDrop';
4343
import { isImageFile } from '../branches/branchCardHelpers';
4444
import { createImage, createImageFromData, deleteImage, getImageData } from '../../api/commands';
45+
import { formatRelativeTime, minuteNow } from '../../shared/relativeTime.svelte';
4546
4647
interface Props {
4748
project: Project;
@@ -347,21 +348,6 @@
347348
let openNote = $state<{ title: string; content: string; sessionId?: string } | null>(null);
348349
let openSessionId = $state<string | null>(null);
349350
350-
function formatRelativeTime(timestampMs: number): string {
351-
const date = new Date(timestampMs);
352-
const now = new Date();
353-
const diffMs = now.getTime() - date.getTime();
354-
const diffMins = Math.floor(diffMs / 60000);
355-
const diffHours = Math.floor(diffMins / 60);
356-
const diffDays = Math.floor(diffHours / 24);
357-
358-
if (diffMins < 1) return 'just now';
359-
if (diffMins < 60) return `${diffMins}m ago`;
360-
if (diffHours < 24) return `${diffHours}h ago`;
361-
if (diffDays < 7) return `${diffDays}d ago`;
362-
return date.toLocaleDateString();
363-
}
364-
365351
// ── Lifecycle ──────────────────────────────────────────────────────────
366352
367353
onMount(() => {
@@ -563,6 +549,7 @@
563549

564550
<!-- Project notes -->
565551
{#if projectNotes.length > 0}
552+
{@const nowMs = minuteNow.now()}
566553
<div class="project-notes">
567554
<div class="notes-header">
568555
<FileText size={13} />
@@ -580,7 +567,9 @@
580567
: isFailed
581568
? 'Session finished — no note created'
582569
: note.title || 'Untitled note'}
583-
secondaryMeta={isRunning || isFailed ? undefined : formatRelativeTime(note.createdAt)}
570+
secondaryMeta={isRunning || isFailed
571+
? undefined
572+
: formatRelativeTime(note.createdAt, nowMs)}
584573
deleting={deletingNoteIds.has(note.id)}
585574
isLast={index === timelineNotes.length - 1}
586575
sessionId={note.sessionId ?? undefined}

apps/staged/src/lib/features/timeline/BranchTimeline.svelte

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
import type { BranchTimeline as BranchTimelineData } from '../../types';
1414
import TimelineRow from './TimelineRow.svelte';
1515
import type { TimelineItemType, TimelineBadge } from './TimelineRow.svelte';
16+
import {
17+
formatRelativeTime,
18+
formatRelativeTimeSeconds,
19+
minuteNow,
20+
} from '../../shared/relativeTime.svelte';
1621
import {
1722
collectRunningSessionIds,
1823
createLiveSessionHints,
@@ -222,6 +227,7 @@
222227
223228
// Merge commits, notes, and reviews into a single sorted list
224229
let items = $derived.by(() => {
230+
const nowMs = minuteNow.now();
225231
const all: DisplayItem[] = [];
226232
const deletingCommitIds = new Set(
227233
deletingItems.filter((item) => item.type === 'commit').map((item) => item.id)
@@ -260,7 +266,7 @@
260266
secondaryMeta = liveHint ?? 'Generating commit';
261267
} else {
262268
type = 'commit';
263-
secondaryMeta = formatRelativeTime(commit.timestamp);
269+
secondaryMeta = formatRelativeTimeSeconds(commit.timestamp, nowMs);
264270
}
265271
266272
all.push({
@@ -300,7 +306,7 @@
300306
secondaryMeta = liveHint ?? 'Generating note';
301307
} else {
302308
type = 'note';
303-
secondaryMeta = formatRelativeTimeMs(note.createdAt);
309+
secondaryMeta = formatRelativeTime(note.createdAt, nowMs);
304310
}
305311
306312
all.push({
@@ -357,7 +363,7 @@
357363
meta = liveHint ?? 'Generating review';
358364
} else {
359365
type = 'review';
360-
meta = formatRelativeTimeMs(review.createdAt);
366+
meta = formatRelativeTime(review.createdAt, nowMs);
361367
}
362368
363369
all.push({
@@ -382,7 +388,7 @@
382388
key: `image-${image.id}`,
383389
type: 'image' as TimelineItemType,
384390
title: image.filename,
385-
secondaryMeta: isDeleting ? 'Deleting...' : formatRelativeTimeMs(image.createdAt),
391+
secondaryMeta: isDeleting ? 'Deleting...' : formatRelativeTime(image.createdAt, nowMs),
386392
deleting: isDeleting,
387393
timestamp: Math.floor(image.createdAt / 1000),
388394
order: 0,
@@ -496,25 +502,6 @@
496502
onDeleteImage(item.imageId);
497503
}
498504
}
499-
500-
function formatRelativeTime(timestamp: number): string {
501-
const date = new Date(timestamp * 1000);
502-
const now = new Date();
503-
const diffMs = now.getTime() - date.getTime();
504-
const diffMins = Math.floor(diffMs / 60000);
505-
const diffHours = Math.floor(diffMins / 60);
506-
const diffDays = Math.floor(diffHours / 24);
507-
508-
if (diffMins < 1) return 'just now';
509-
if (diffMins < 60) return `${diffMins}m ago`;
510-
if (diffHours < 24) return `${diffHours}h ago`;
511-
if (diffDays < 7) return `${diffDays}d ago`;
512-
return date.toLocaleDateString();
513-
}
514-
515-
function formatRelativeTimeMs(timestamp: number): string {
516-
return formatRelativeTime(Math.floor(timestamp / 1000));
517-
}
518505
</script>
519506

520507
{#if items.length === 0 && !onNewNote && !onNewCommit && !onNewReview && pendingDropNotes.length === 0 && pendingItems.length === 0}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class MinuteNowStore {
2+
private value = $state(Date.now());
3+
4+
constructor() {
5+
if (typeof window !== 'undefined') {
6+
this.start();
7+
}
8+
}
9+
10+
now(): number {
11+
return this.value;
12+
}
13+
14+
private start(): void {
15+
this.value = Date.now();
16+
17+
const msUntilNextMinute = 60000 - (this.value % 60000);
18+
setTimeout(() => {
19+
this.value = Date.now();
20+
setInterval(() => {
21+
this.value = Date.now();
22+
}, 60000);
23+
}, msUntilNextMinute);
24+
}
25+
}
26+
27+
export const minuteNow = new MinuteNowStore();
28+
29+
export function formatRelativeTime(timestampMs: number, nowMs = minuteNow.now()): string {
30+
const date = new Date(timestampMs);
31+
const diffMs = nowMs - date.getTime();
32+
const diffMins = Math.floor(diffMs / 60000);
33+
const diffHours = Math.floor(diffMins / 60);
34+
const diffDays = Math.floor(diffHours / 24);
35+
36+
if (diffMins < 1) return 'just now';
37+
if (diffMins < 60) return `${diffMins}m ago`;
38+
if (diffHours < 24) return `${diffHours}h ago`;
39+
if (diffDays < 7) return `${diffDays}d ago`;
40+
return date.toLocaleDateString();
41+
}
42+
43+
export function formatRelativeTimeSeconds(
44+
timestampSeconds: number,
45+
nowMs = minuteNow.now()
46+
): string {
47+
return formatRelativeTime(timestampSeconds * 1000, nowMs);
48+
}

0 commit comments

Comments
 (0)