mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(ui): remove retired time format bootstrap (#116097)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user