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");
});
});