refactor(ui): remove retired time format bootstrap (#116097)

This commit is contained in:
Vincent Koc
2026-07-30 06:23:27 +08:00
committed by GitHub
parent 43945b836a
commit 925fdb023e
9 changed files with 34 additions and 112 deletions
-2
View File
@@ -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
-2
View File
@@ -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);
-1
View File
@@ -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,
-2
View File
@@ -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 {
-39
View File
@@ -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 <think>…</think> segments", () => {
const input = ["<think>", "secret", "</think>", "", "Hello"].join("\n");
+3 -30
View File
@@ -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 | null | undefined>): string {
@@ -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",
@@ -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");
+11 -8
View File
@@ -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:3017: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");
});
});