From 738bc87dd836fd7d05ee3a21218b6b14997eed28 Mon Sep 17 00:00:00 2001 From: Karthick Mahalingam Date: Wed, 28 Oct 2026 09:03:39 -0700 Subject: [PATCH 01/23] feat(ui): add time and date format settings and message timestamps --- packages/types/src/global-settings.ts | 15 ++ packages/types/src/vscode-extension-host.ts | 3 + src/core/webview/ClineProvider.ts | 9 + .../__tests__/ContextWindowProgress.spec.tsx | 5 +- webview-ui/src/components/chat/ChatRow.tsx | 16 ++ .../src/components/chat/MessageTimestamp.tsx | 45 ++++ webview-ui/src/components/chat/TaskHeader.tsx | 8 +- .../chat/__tests__/ChatRow.timestamp.spec.tsx | 134 ++++++++++++ .../chat/__tests__/MessageTimestamp.spec.tsx | 76 +++++++ .../src/components/history/TaskItemFooter.tsx | 16 +- .../history/__tests__/TaskGroupItem.spec.tsx | 1 + .../history/__tests__/TaskItem.spec.tsx | 1 + .../history/__tests__/TaskItemFooter.spec.tsx | 33 +++ .../components/settings/LanguageSettings.tsx | 207 +++++++++++++++--- .../src/components/settings/SettingsView.tsx | 14 +- .../LanguageSettings.selects.spec.tsx | 48 ++++ .../LanguageSettings.timeFormat.spec.tsx | 128 +++++++++++ .../src/context/ExtensionStateContext.tsx | 6 + webview-ui/src/i18n/locales/ca/settings.json | 36 +++ webview-ui/src/i18n/locales/de/settings.json | 36 +++ webview-ui/src/i18n/locales/en/settings.json | 36 +++ webview-ui/src/i18n/locales/es/settings.json | 36 +++ webview-ui/src/i18n/locales/fr/settings.json | 36 +++ webview-ui/src/i18n/locales/hi/settings.json | 36 +++ webview-ui/src/i18n/locales/id/settings.json | 36 +++ webview-ui/src/i18n/locales/it/settings.json | 36 +++ webview-ui/src/i18n/locales/ja/settings.json | 36 +++ webview-ui/src/i18n/locales/ko/settings.json | 36 +++ webview-ui/src/i18n/locales/nl/settings.json | 36 +++ webview-ui/src/i18n/locales/pl/settings.json | 36 +++ .../src/i18n/locales/pt-BR/settings.json | 36 +++ webview-ui/src/i18n/locales/ru/settings.json | 36 +++ webview-ui/src/i18n/locales/tr/settings.json | 36 +++ webview-ui/src/i18n/locales/vi/settings.json | 36 +++ .../src/i18n/locales/zh-CN/settings.json | 36 +++ .../src/i18n/locales/zh-TW/settings.json | 36 +++ webview-ui/src/utils/__tests__/format.spec.ts | 148 ++++++++++++- webview-ui/src/utils/format.ts | 128 ++++++++++- 38 files changed, 1652 insertions(+), 37 deletions(-) create mode 100644 webview-ui/src/components/chat/MessageTimestamp.tsx create mode 100644 webview-ui/src/components/chat/__tests__/ChatRow.timestamp.spec.tsx create mode 100644 webview-ui/src/components/chat/__tests__/MessageTimestamp.spec.tsx create mode 100644 webview-ui/src/components/settings/__tests__/LanguageSettings.selects.spec.tsx create mode 100644 webview-ui/src/components/settings/__tests__/LanguageSettings.timeFormat.spec.tsx diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index eb51c1d67d..f5757a51c7 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -280,6 +280,21 @@ export const globalSettingsSchema = z.object({ * When unset (or `null`), the webview inherits VS Code's `--vscode-font-size`. */ chatFontSize: z.number().int().min(8).max(32).nullish(), + /** + * Clock format used when rendering timestamps. "auto" follows the display language's locale. + * @default "auto" + */ + timeFormat: z.enum(["auto", "12h", "24h"]).optional(), + /** + * How dates are written. "auto" follows the display language's locale. + * @default "auto" + */ + dateFormat: z.enum(["auto", "mdy", "dmy", "ymd"]).optional(), + /** + * Show relative times ("5 min ago") in task history; when off, show the absolute date/time. + * @default true + */ + showRelativeTimes: z.boolean().optional(), /** * Controls the keyboard behavior for sending messages in the chat input. * - "send": Enter sends message, Shift+Enter creates newline (default) diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index dc407325d9..0bf3348d8d 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -339,6 +339,9 @@ export type ExtensionState = Pick< | "includeTaskHistoryInEnhance" | "reasoningBlockCollapsed" | "chatFontSize" + | "timeFormat" + | "dateFormat" + | "showRelativeTimes" | "enterBehavior" | "includeCurrentTime" | "includeCurrentCost" diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 32b82feaaa..f40315bd30 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2860,6 +2860,9 @@ export class ClineProvider historyPreviewCollapsed, reasoningBlockCollapsed, chatFontSize, + timeFormat, + dateFormat, + showRelativeTimes, enterBehavior, cloudUserInfo, cloudIsAuthenticated, @@ -3041,6 +3044,9 @@ export class ClineProvider historyPreviewCollapsed: historyPreviewCollapsed ?? false, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, chatFontSize, + timeFormat: timeFormat ?? "auto", + dateFormat: dateFormat ?? "auto", + showRelativeTimes: showRelativeTimes ?? true, enterBehavior: enterBehavior ?? "send", cloudUserInfo, cloudIsAuthenticated: cloudIsAuthenticated ?? false, @@ -3274,6 +3280,9 @@ export class ClineProvider historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true, chatFontSize: stateValues.chatFontSize, + timeFormat: stateValues.timeFormat ?? "auto", + dateFormat: stateValues.dateFormat ?? "auto", + showRelativeTimes: stateValues.showRelativeTimes ?? true, enterBehavior: stateValues.enterBehavior ?? "send", cloudUserInfo, cloudIsAuthenticated, diff --git a/webview-ui/src/__tests__/ContextWindowProgress.spec.tsx b/webview-ui/src/__tests__/ContextWindowProgress.spec.tsx index f5c2ba1062..052d58267e 100644 --- a/webview-ui/src/__tests__/ContextWindowProgress.spec.tsx +++ b/webview-ui/src/__tests__/ContextWindowProgress.spec.tsx @@ -5,8 +5,9 @@ import { providerIdentifiers } from "@roo-code/types" import TaskHeader from "@src/components/chat/TaskHeader" -// Mock formatLargeNumber function -vi.mock("@/utils/format", () => ({ +// Keep the real format helpers (other components in the tree use them) and override only formatLargeNumber. +vi.mock("@/utils/format", async (importOriginal) => ({ + ...(await importOriginal()), formatLargeNumber: vi.fn((num) => num.toString()), })) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 77ae4529c0..596e30af65 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -38,6 +38,7 @@ import WarningRow from "./WarningRow" import McpResourceRow from "../mcp/McpResourceRow" import { Mention } from "./Mention" +import { MessageTimestamp } from "./MessageTimestamp" import { CheckpointSaved } from "./checkpoints/CheckpointSaved" import { FollowUpSuggest } from "./FollowUpSuggest" import { BatchFilePermission } from "./BatchFilePermission" @@ -196,6 +197,8 @@ export const ChatRowContent = ({ clineMessages, currentTaskItem, enableCheckpoints, + timeFormat, + dateFormat, } = useExtensionState() const { info: model } = useSelectedModel(apiConfiguration) const [isEditing, setIsEditing] = useState(false) @@ -1224,6 +1227,13 @@ export const ChatRowContent = ({ )} + {/* When the reply was sent. Held back while it streams, so the line does not appear and + shift as the text grows. */} + {!message.partial && ( +
+ +
+ )} ) case "user_feedback": @@ -1299,6 +1309,12 @@ export const ChatRowContent = ({ )} + {/* When the message was sent. Hidden while editing: the edit box replaces the bubble. */} + {!isEditing && ( +
+ +
+ )} ) case "user_feedback_diff": diff --git a/webview-ui/src/components/chat/MessageTimestamp.tsx b/webview-ui/src/components/chat/MessageTimestamp.tsx new file mode 100644 index 0000000000..a925eb910c --- /dev/null +++ b/webview-ui/src/components/chat/MessageTimestamp.tsx @@ -0,0 +1,45 @@ +import { Clock } from "lucide-react" + +import { cn } from "@src/lib/utils" +import { formatDateTime, formatMessageTime, type DateFormat, type TimeFormat } from "@/utils/format" + +import { StandardTooltip } from "../ui/standard-tooltip" + +interface MessageTimestampProps { + /** Epoch ms the message was sent. */ + ts: number + /** From the user's Language settings; both default to "auto". */ + timeFormat?: TimeFormat + dateFormat?: DateFormat + className?: string +} + +/** + * "🕒 Yesterday 11:43 AM" under a sent message. + * + * Purely presentational: the caller supplies the user's time/date formats, so + * the component has no dependency on extension state and renders anywhere. The + * tooltip always carries the full date and time. A semantic