diff --git a/frontend/src/screens/Inbox.jsx b/frontend/src/screens/Inbox.jsx
index 39d63b3..9ac144e 100644
--- a/frontend/src/screens/Inbox.jsx
+++ b/frontend/src/screens/Inbox.jsx
@@ -25,8 +25,8 @@ import { qk } from '../lib/queryKeys'
import { friendlyAuthError } from '../lib/errors'
import * as inboxApi from '../api/inbox'
import {
- atsRecommendationClass, avatarColor, fmtDate, fmtShort, initials as initialsOf,
- inboxSources, relTime, sourceMeta,
+ atsRecommendationClass, avatarColor, fmtDate, initials as initialsOf,
+ inboxSources, sourceMeta,
} from '../data/seed'
const TABS = ['All Applications', 'Unread', 'Processed', 'Rejected', 'Duplicates', 'Email']
@@ -57,9 +57,6 @@ const TAB_FILTERS = {
*/
const SERVER_SCOPED_TABS = new Set(['All Applications', 'Unread'])
-/** The prototype computed "time ago" against a fixed 2026-07-09T20:00. */
-const NOW = new Date('2026-07-09T20:00')
-
/**
* message_received_time / message_sent_time are plain string columns
* (backend/inbox/models.py:54-56), not timestamps. An unparseable value yields
@@ -72,6 +69,33 @@ function parseDate(value) {
return Number.isNaN(d.getTime()) ? null : d
}
+function startOfDay(d) {
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate())
+}
+
+/**
+ * Outlook-style list timestamps in the client's local timezone.
+ * Within 7 days: weekday + AM/PM time. Older: dd/mm/yyyy + AM/PM time.
+ */
+function outlookListTime(value) {
+ if (!value) return '—'
+ const d = value instanceof Date ? value : new Date(value)
+ if (Number.isNaN(d.getTime())) return '—'
+ const time = d.toLocaleTimeString(undefined, {
+ hour: 'numeric',
+ minute: '2-digit',
+ hour12: true,
+ })
+ const daysAgo = Math.round((startOfDay(new Date()) - startOfDay(d)) / 86400000)
+ if (daysAgo < 7) {
+ const weekday = d.toLocaleDateString(undefined, { weekday: 'short' })
+ return `${weekday} ${time}`
+ }
+ const dd = String(d.getDate()).padStart(2, '0')
+ const mm = String(d.getMonth() + 1).padStart(2, '0')
+ return `${dd}/${mm}/${d.getFullYear()} ${time}`
+}
+
/**
* `source` arrives as the raw To address, because that is where the board tag
* lands — careers-rozee@, employee-referral@, mustakbil@ and so on. Strip
@@ -487,7 +511,7 @@ function BulkReadBar({ rows, selection, onSetRead, onSetAllRead, busy, canEdit,
onClick={() => flashRead('selected', () => onSetRead(true))}
>
{ticked === 'selected' &&