diff --git a/UI-DESIGN.md b/UI-DESIGN.md
index e045177..531e0c5 100644
--- a/UI-DESIGN.md
+++ b/UI-DESIGN.md
@@ -490,6 +490,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- 使用 `standard` 壳层和统一 `PageHeader`。
- 搜索、范围和时间筛选位于筛选工具栏。
- 行项目统一显示标题、范围、最近更新时间和必要状态。
+- 侧栏最近会话的更新时间在当天显示本地时间,非当天显示本地日期;非当年记录必须同时显示年份。悬停时间信息时提供完整日期和时间。
- 删除入口使用 `danger-ghost`,并按数据可恢复性执行确认或撤销策略。
### 13.3 知识库
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index 784be10..59299b4 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -181,7 +181,11 @@ import type { ReleaseNotesSnapshot } from '../../shared/release-notes-contracts'
import { ReleaseNotesDialog } from './ReleaseNotesDialog'
import { scheduleIdleRoutePreload } from './idle-route-preload'
import { createPreloadableComponent } from './preloadable-component'
-import { formatTime, type TimeFormatLocale } from './time-format'
+import {
+ formatConversationListTime,
+ type TimeFormatLocale
+} from './time-format'
+import { formatMediumDateTime } from './locale-formatters'
import { formatCompactTokens } from './token-format'
import {
pruneKeepAliveEntries,
@@ -6464,7 +6468,22 @@ function App(): React.JSX.Element {
/>
)}
- {formatTime(conversation.updatedAt, locale)}
+
+
+
{activeConversationIds.has(conversation.id) && (
{
afterEach(() => {
@@ -27,4 +27,47 @@ describe('formatTime', () => {
expect(secondChinese).not.toBe('')
expect(formatter).toHaveBeenCalledTimes(2)
})
+
+ it('shows the time for a conversation updated today', () => {
+ const timestamp = new Date(2026, 7, 17, 9, 5).getTime()
+ const referenceTimestamp = new Date(2026, 7, 17, 23, 30).getTime()
+
+ expect(
+ formatConversationListTime(timestamp, 'zh-CN', referenceTimestamp)
+ ).toBe(
+ new Intl.DateTimeFormat('zh-CN', {
+ hour: '2-digit',
+ minute: '2-digit'
+ }).format(timestamp)
+ )
+ })
+
+ it('shows the date after the local calendar day changes', () => {
+ const timestamp = new Date(2026, 7, 16, 23, 30).getTime()
+ const referenceTimestamp = new Date(2026, 7, 17, 0, 15).getTime()
+
+ expect(
+ formatConversationListTime(timestamp, 'zh-CN', referenceTimestamp)
+ ).toBe(
+ new Intl.DateTimeFormat('zh-CN', {
+ month: 'short',
+ day: 'numeric'
+ }).format(timestamp)
+ )
+ })
+
+ it('includes the year for a conversation from another year', () => {
+ const timestamp = new Date(2025, 11, 31, 23, 30).getTime()
+ const referenceTimestamp = new Date(2026, 0, 1, 0, 15).getTime()
+
+ expect(
+ formatConversationListTime(timestamp, 'en-US', referenceTimestamp)
+ ).toBe(
+ new Intl.DateTimeFormat('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric'
+ }).format(timestamp)
+ )
+ })
})
diff --git a/src/renderer/src/time-format.ts b/src/renderer/src/time-format.ts
index ccef369..b5548e4 100644
--- a/src/renderer/src/time-format.ts
+++ b/src/renderer/src/time-format.ts
@@ -4,6 +4,14 @@ const timeFormatters: Partial<
Record
> = {}
+const conversationDateFormatters: Partial<
+ Record
+> = {}
+
+const conversationDateWithYearFormatters: Partial<
+ Record
+> = {}
+
export function formatTime(
timestamp: number,
locale: TimeFormatLocale
@@ -16,3 +24,43 @@ export function formatTime(
}))
return formatter.format(timestamp)
}
+
+export function formatConversationListTime(
+ timestamp: number,
+ locale: TimeFormatLocale,
+ referenceTimestamp = Date.now()
+): string {
+ const date = new Date(timestamp)
+ const referenceDate = new Date(referenceTimestamp)
+ const isSameYear = date.getFullYear() === referenceDate.getFullYear()
+ const isToday =
+ isSameYear &&
+ date.getMonth() === referenceDate.getMonth() &&
+ date.getDate() === referenceDate.getDate()
+
+ if (isToday) {
+ return formatTime(timestamp, locale)
+ }
+
+ if (isSameYear) {
+ const formatter =
+ conversationDateFormatters[locale] ??
+ (conversationDateFormatters[locale] = new Intl.DateTimeFormat(locale, {
+ month: 'short',
+ day: 'numeric'
+ }))
+ return formatter.format(timestamp)
+ }
+
+ const formatter =
+ conversationDateWithYearFormatters[locale] ??
+ (conversationDateWithYearFormatters[locale] = new Intl.DateTimeFormat(
+ locale,
+ {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric'
+ }
+ ))
+ return formatter.format(timestamp)
+}