From 925fdb023eca3631d27bb966135c219506296da2 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 30 Jul 2026 06:23:27 +0800 Subject: [PATCH] refactor(ui): remove retired time format bootstrap (#116097) --- src/gateway/control-ui-contract.ts | 2 - src/gateway/control-ui.http.test.ts | 2 - src/gateway/control-ui.ts | 1 - ui/src/app/config.ts | 2 - ui/src/lib/format.test.ts | 39 ------------------- ui/src/lib/format.ts | 33 ++-------------- .../chat/components/chat-message-timestamp.ts | 9 +---- .../chat/components/chat-message.test.ts | 39 +++++++++---------- ui/src/pages/plugin/logbook-view.test.ts | 19 +++++---- 9 files changed, 34 insertions(+), 112 deletions(-) diff --git a/src/gateway/control-ui-contract.ts b/src/gateway/control-ui-contract.ts index 5ff771f5f734..0e8e2241ba71 100644 --- a/src/gateway/control-ui-contract.ts +++ b/src/gateway/control-ui-contract.ts @@ -166,8 +166,6 @@ export type ControlUiBootstrapConfig = { embedSandbox?: ControlUiEmbedSandboxMode; allowExternalEmbedUrls?: boolean; seamColor?: string; - /** Resolved `agents.defaults.timeFormat`; "auto" keeps the browser locale default. */ - timeFormat?: "auto" | "12" | "24"; /** * Whether the operator terminal surface is enabled (`gateway.terminal.enabled`). * The Control UI hides the terminal entirely when false so a disabled kill diff --git a/src/gateway/control-ui.http.test.ts b/src/gateway/control-ui.http.test.ts index a8f683690f2d..6a5ab1adb580 100644 --- a/src/gateway/control-ui.http.test.ts +++ b/src/gateway/control-ui.http.test.ts @@ -139,7 +139,6 @@ describe("handleControlUiHttpRequest", () => { devGitBranch?: string; localMediaPreviewRoots?: string[]; seamColor?: string; - timeFormat?: "auto" | "12" | "24"; terminalEnabled: boolean; pluginFrameGrants?: ControlUiPluginFrameGrantAck[]; }; @@ -1303,7 +1302,6 @@ describe("handleControlUiHttpRequest", () => { expect(parsed.assistantAvatarReason).toBe("missing"); expect(parsed.assistantAgentId).toBe("roboclaw"); expect(parsed.seamColor).toBe("#1A2b3C"); - expect(parsed.timeFormat).toBe("auto"); expect(parsed.terminalEnabled).toBe(true); expect(parsed.devGitBranch).toBeUndefined(); expect(Array.isArray(parsed.localMediaPreviewRoots)).toBe(true); diff --git a/src/gateway/control-ui.ts b/src/gateway/control-ui.ts index 3d6cd28d72f9..606b3f5b1a6e 100644 --- a/src/gateway/control-ui.ts +++ b/src/gateway/control-ui.ts @@ -1097,7 +1097,6 @@ export async function handleControlUiHttpRequest( : "scripts", allowExternalEmbedUrls: config?.gateway?.controlUi?.allowExternalEmbedUrls === true, seamColor: config?.ui?.seamColor, - timeFormat: "auto", terminalEnabled, pluginFrameGrants: pluginFrameGrants.map(({ pluginId, path: grantPath, match }) => ({ pluginId, diff --git a/ui/src/app/config.ts b/ui/src/app/config.ts index b19eade93ccc..b698469a1f09 100644 --- a/ui/src/app/config.ts +++ b/ui/src/app/config.ts @@ -7,7 +7,6 @@ import { type ControlUiPluginFrameGrantAck, } from "../../../src/gateway/control-ui-contract.js"; import { normalizeAssistantIdentity } from "../lib/assistant-identity.ts"; -import { setUiTimeFormatPreference } from "../lib/format.ts"; import { resolveControlUiAuthCandidates } from "./control-ui-auth.ts"; type ApplicationConfigAuthSource = { @@ -213,7 +212,6 @@ async function loadApplicationConfig(params: { return null; } const parsed = (await res.json()) as ControlUiBootstrapConfig; - setUiTimeFormatPreference(parsed.timeFormat); applyControlUiSeamColor(parsed.seamColor); return normalizeApplicationConfig(parsed); } catch { diff --git a/ui/src/lib/format.test.ts b/ui/src/lib/format.test.ts index 61487103cdfa..aca4d60bf7cd 100644 --- a/ui/src/lib/format.test.ts +++ b/ui/src/lib/format.test.ts @@ -16,7 +16,6 @@ import { formatTokens, formatUnknownText, parseSessionKeyParts, - setUiTimeFormatPreference, truncateText, } from "./format.ts"; import { stripThinkingTags } from "./strip-thinking-tags.ts"; @@ -107,44 +106,6 @@ describe("date/time millisecond formatters", () => { }); }); -describe("agents.defaults.timeFormat preference", () => { - // 19:30 UTC: 24-hour renders "19:30", 12-hour renders "7:30 PM". - const ts = Date.UTC(2026, 0, 15, 19, 30); - const opts: Intl.DateTimeFormatOptions = { - hour: "2-digit", - minute: "2-digit", - timeZone: "UTC", - }; - - afterEach(() => { - setUiTimeFormatPreference("auto"); - }); - - it("forces a 24-hour clock when preference is 24", () => { - setUiTimeFormatPreference("24"); - expect(formatTimeMs(ts, opts, "")).toBe("19:30"); - }); - - it("forces a 12-hour clock when preference is 12", () => { - setUiTimeFormatPreference("12"); - const formatted = formatTimeMs(ts, opts, ""); - expect(formatted).toContain("7:30"); - expect(formatted).toMatch(/PM/i); - }); - - it("lets the caller override the resolved hour cycle", () => { - setUiTimeFormatPreference("24"); - expect(formatTimeMs(ts, { ...opts, hour12: true }, "")).toMatch(/PM/i); - }); - - it("leaves rendering to the browser locale default for auto", () => { - setUiTimeFormatPreference("auto"); - const auto = formatDateTimeMs(ts, opts, ""); - const native = new Date(ts).toLocaleString([], opts); - expect(auto).toBe(native); - }); -}); - describe("stripThinkingTags", () => { it("strips segments", () => { const input = ["", "secret", "", "", "Hello"].join("\n"); diff --git a/ui/src/lib/format.ts b/ui/src/lib/format.ts index 71f48b8e0285..d3d7d1119d5b 100644 --- a/ui/src/lib/format.ts +++ b/ui/src/lib/format.ts @@ -220,33 +220,12 @@ export function formatUnknownText( return Object.prototype.toString.call(value); } -type UiTimeFormatPreference = "auto" | "12" | "24"; - -// Resolved `agents.defaults.timeFormat`, threaded in once at bootstrap. "auto" -// (or unset) keeps the browser locale default so existing deployments render -// unchanged; only "12"/"24" force an hour cycle across Control UI timestamps. -let uiTimeFormatPreference: UiTimeFormatPreference = "auto"; - -export function setUiTimeFormatPreference(value: UiTimeFormatPreference | null | undefined): void { - uiTimeFormatPreference = value === "12" || value === "24" ? value : "auto"; -} - -export function resolveUiHourCycleOptions(): Intl.DateTimeFormatOptions { - if (uiTimeFormatPreference === "12") { - return { hour12: true }; - } - if (uiTimeFormatPreference === "24") { - return { hour12: false }; - } - return {}; -} - export function formatMs(ms?: number | null): string { const timestampMs = asDateTimestampMs(ms); if (timestampMs === undefined) { return t("common.na"); } - return new Date(timestampMs).toLocaleString(i18n.getLocale(), resolveUiHourCycleOptions()); + return new Date(timestampMs).toLocaleString(i18n.getLocale()); } export function formatDateMs( @@ -268,10 +247,7 @@ export function formatTimeMs( const timestampMs = asDateTimestampMs(ms); return timestampMs === undefined ? fallback - : new Date(timestampMs).toLocaleTimeString(i18n.getLocale(), { - ...resolveUiHourCycleOptions(), - ...options, - }); + : new Date(timestampMs).toLocaleTimeString(i18n.getLocale(), options); } export function formatDateTimeMs( @@ -282,10 +258,7 @@ export function formatDateTimeMs( const timestampMs = asDateTimestampMs(ms); return timestampMs === undefined ? fallback - : new Date(timestampMs).toLocaleString(i18n.getLocale(), { - ...resolveUiHourCycleOptions(), - ...options, - }); + : new Date(timestampMs).toLocaleString(i18n.getLocale(), options); } export function formatList(values?: Array): string { diff --git a/ui/src/pages/chat/components/chat-message-timestamp.ts b/ui/src/pages/chat/components/chat-message-timestamp.ts index a6b7193a4054..2e7c4163be13 100644 --- a/ui/src/pages/chat/components/chat-message-timestamp.ts +++ b/ui/src/pages/chat/components/chat-message-timestamp.ts @@ -1,11 +1,7 @@ import { html } from "lit"; import { t } from "../../../i18n/index.ts"; import type { MessageGroup } from "../../../lib/chat/chat-types.ts"; -import { - formatCompactTokenCount, - formatTimeAgo, - resolveUiHourCycleOptions, -} from "../../../lib/format.ts"; +import { formatCompactTokenCount, formatTimeAgo } from "../../../lib/format.ts"; type ChatTimestampDisplay = { label: string; @@ -23,10 +19,8 @@ function formatChatTimestampForDisplay(timestamp: number): ChatTimestampDisplay }; } - const hourCycle = resolveUiHourCycleOptions(); return { label: date.toLocaleString([], { - ...hourCycle, month: "short", day: "numeric", year: "numeric", @@ -35,7 +29,6 @@ function formatChatTimestampForDisplay(timestamp: number): ChatTimestampDisplay timeZoneName: "short", }), title: date.toLocaleString([], { - ...hourCycle, weekday: "long", month: "long", day: "numeric", diff --git a/ui/src/pages/chat/components/chat-message.test.ts b/ui/src/pages/chat/components/chat-message.test.ts index af4261498d06..32c8b57cda6b 100644 --- a/ui/src/pages/chat/components/chat-message.test.ts +++ b/ui/src/pages/chat/components/chat-message.test.ts @@ -4,7 +4,6 @@ import { html, render } from "lit"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import * as markdown from "../../../components/markdown.ts"; import type { MessageGroup } from "../../../lib/chat/chat-types.ts"; -import { setUiTimeFormatPreference } from "../../../lib/format.ts"; import { setAvatarGatewayOrigin } from "../../../lib/identity-avatar.ts"; import * as localStorageModule from "../../../local-storage.ts"; import * as chatAvatar from "../chat-avatar.ts"; @@ -606,7 +605,6 @@ afterEach(() => { element.remove(); }); clearDeleteConfirmSkip(); - setUiTimeFormatPreference("auto"); setAvatarGatewayOrigin(null); vi.useRealTimers(); vi.unstubAllGlobals(); @@ -1507,25 +1505,26 @@ describe("grouped chat rendering", () => { ); }); - it.each([ - { preference: "12" as const, expected: /AM|PM/i }, - { preference: "24" as const, expected: /^(?!.*(?:AM|PM))/i }, - ])( - "honors the $preference-hour clock preference in timestamp tooltips", - ({ preference, expected }) => { - setUiTimeFormatPreference(preference); - const container = document.createElement("div"); - renderAssistantMessage(container, { - role: "assistant", - content: "Done", - timestamp: Date.UTC(2026, 0, 15, 19, 30), - }); + it("uses the browser locale for timestamp tooltips", () => { + const timestamp = Date.UTC(2026, 0, 15, 19, 30); + const container = document.createElement("div"); + renderAssistantMessage(container, { + role: "assistant", + content: "Done", + timestamp, + }); - expect(container.querySelector("openclaw-tooltip")?.getAttribute("content")).toMatch( - expected, - ); - }, - ); + expect(container.querySelector("openclaw-tooltip")?.getAttribute("content")).toBe( + new Date(timestamp).toLocaleString([], { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "2-digit", + timeZoneName: "short", + }), + ); + }); it("omits streaming bubble class for completed stream segments", () => { const container = document.createElement("div"); diff --git a/ui/src/pages/plugin/logbook-view.test.ts b/ui/src/pages/plugin/logbook-view.test.ts index 94b17b5ba2bc..b5755f3c804d 100644 --- a/ui/src/pages/plugin/logbook-view.test.ts +++ b/ui/src/pages/plugin/logbook-view.test.ts @@ -1,16 +1,11 @@ import { render } from "lit"; -import { afterEach, describe, expect, it } from "vitest"; -import { setUiTimeFormatPreference } from "../../lib/format.ts"; +import { describe, expect, it } from "vitest"; +import { i18n } from "../../i18n/index.ts"; import { getLogbookState } from "./logbook-controller.ts"; import { renderLogbook } from "./logbook-view.ts"; describe("Logbook view", () => { - afterEach(() => { - setUiTimeFormatPreference("auto"); - }); - it("renders timeline clocks in the capture host timezone", () => { - setUiTimeFormatPreference("24"); const host = {}; const state = getLogbookState(host); state.day = "2026-01-01"; @@ -48,7 +43,15 @@ describe("Logbook view", () => { const container = document.createElement("div"); render(renderLogbook({ host, client: null, connected: false }), container); - expect(container.querySelector(".logbook-card__time")?.textContent?.trim()).toBe("16:30–17:30"); + const timeOptions = { + hour: "2-digit", + minute: "2-digit", + timeZone: "America/Los_Angeles", + } satisfies Intl.DateTimeFormatOptions; + const expectedTime = [Date.UTC(2026, 0, 2, 0, 30), Date.UTC(2026, 0, 2, 1, 30)] + .map((ms) => new Date(ms).toLocaleTimeString(i18n.getLocale(), timeOptions)) + .join("–"); + expect(container.querySelector(".logbook-card__time")?.textContent?.trim()).toBe(expectedTime); expect(container.querySelector(".logbook-card__duration")?.textContent?.trim()).toBe("1h"); }); });