mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:00:44 +00:00
fix(control-ui): allow configured chat message width
Adds validated gateway.controlUi.chatMessageMaxWidth support for grouped Control UI chat messages, carries it through the Gateway bootstrap payload into UI state, applies it as a CSS custom property, and documents the setting while preserving the existing default width.
Fixes #67935.
Validation:
- Targeted config, gateway, and Control UI tests passed locally.
- Config schema/docs checks passed.
- Testbox changed-file gate passed.
- GitHub CI and security checks are green on cea25a4ca9.
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
gap: 2px;
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
max-width: min(900px, 68%);
|
||||
max-width: var(--chat-message-max-width, min(900px, 68%));
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,15 @@ function readLayoutCss(): string {
|
||||
return readFileSync(cssPath!, "utf8");
|
||||
}
|
||||
|
||||
function readGroupedChatCss(): string {
|
||||
const cssPath = [
|
||||
resolve(process.cwd(), "ui/src/styles/chat/grouped.css"),
|
||||
resolve(process.cwd(), "..", "ui/src/styles/chat/grouped.css"),
|
||||
].find((candidate) => existsSync(candidate));
|
||||
expect(cssPath).toBeTruthy();
|
||||
return readFileSync(cssPath!, "utf8");
|
||||
}
|
||||
|
||||
describe("chat header responsive mobile styles", () => {
|
||||
it("keeps the chat header and session controls from clipping on narrow widths", () => {
|
||||
const css = readMobileCss();
|
||||
@@ -46,3 +55,11 @@ describe("sidebar menu trigger styles", () => {
|
||||
expect(css).toContain("display: none;");
|
||||
});
|
||||
});
|
||||
|
||||
describe("grouped chat width styles", () => {
|
||||
it("uses the config-fed CSS variable with the current fallback", () => {
|
||||
const css = readGroupedChatCss();
|
||||
|
||||
expect(css).toContain("max-width: var(--chat-message-max-width, min(900px, 68%));");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { html } from "lit";
|
||||
import { html, render } from "lit";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AppViewState } from "./app-view-state.ts";
|
||||
import type { QuickSettingsProps } from "./views/config-quick.ts";
|
||||
@@ -89,6 +89,7 @@ function createState(overrides: Partial<AppViewState> = {}): AppViewState {
|
||||
localMediaPreviewRoots: [],
|
||||
embedSandboxMode: "scripts",
|
||||
allowExternalEmbedUrls: false,
|
||||
chatMessageMaxWidth: null,
|
||||
sessionKey: "main",
|
||||
chatLoading: false,
|
||||
chatSending: false,
|
||||
@@ -227,4 +228,16 @@ describe("renderApp assistant avatar routing", () => {
|
||||
expect(quickSettingsProps.current?.assistantAvatarReason).toBeNull();
|
||||
expect(quickSettingsProps.current?.assistantAvatarOverride).toBe(dataUrl);
|
||||
});
|
||||
|
||||
it("applies the configured chat message width as a shell CSS variable", () => {
|
||||
const container = document.createElement("div");
|
||||
|
||||
render(
|
||||
renderApp(createState({ tab: "chat", chatMessageMaxWidth: "min(1280px, 82%)" })),
|
||||
container,
|
||||
);
|
||||
|
||||
const shell = container.querySelector<HTMLElement>(".shell");
|
||||
expect(shell?.style.getPropertyValue("--chat-message-max-width")).toBe("min(1280px, 82%)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { t } from "../i18n/index.ts";
|
||||
import { getSafeLocalStorage } from "../local-storage.ts";
|
||||
import { refreshChat } from "./app-chat.ts";
|
||||
@@ -1329,6 +1330,9 @@ export function renderApp(state: AppViewState) {
|
||||
: ""} ${navCollapsed ? "shell--nav-collapsed" : ""} ${navDrawerOpen
|
||||
? "shell--nav-drawer-open"
|
||||
: ""} ${state.onboarding ? "shell--onboarding" : ""}"
|
||||
style=${styleMap(
|
||||
state.chatMessageMaxWidth ? { "--chat-message-max-width": state.chatMessageMaxWidth } : {},
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -84,6 +84,7 @@ export type AppViewState = {
|
||||
localMediaPreviewRoots: string[];
|
||||
embedSandboxMode: EmbedSandboxMode;
|
||||
allowExternalEmbedUrls: boolean;
|
||||
chatMessageMaxWidth?: string | null;
|
||||
sessionKey: string;
|
||||
chatLoading: boolean;
|
||||
chatSending: boolean;
|
||||
|
||||
@@ -189,6 +189,7 @@ export class OpenClawApp extends LitElement {
|
||||
@state() localMediaPreviewRoots: string[] = [];
|
||||
@state() embedSandboxMode: "strict" | "scripts" | "trusted" = "scripts";
|
||||
@state() allowExternalEmbedUrls = false;
|
||||
@state() chatMessageMaxWidth: string | null = null;
|
||||
@state() serverVersion: string | null = null;
|
||||
|
||||
@state() sessionKey = this.settings.sessionKey;
|
||||
|
||||
@@ -20,6 +20,7 @@ describe("loadControlUiBootstrapConfig", () => {
|
||||
localMediaPreviewRoots: ["/tmp/openclaw"],
|
||||
embedSandbox: "scripts",
|
||||
allowExternalEmbedUrls: true,
|
||||
chatMessageMaxWidth: "min(1280px, 82%)",
|
||||
}),
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);
|
||||
@@ -35,6 +36,7 @@ describe("loadControlUiBootstrapConfig", () => {
|
||||
localMediaPreviewRoots: [],
|
||||
embedSandboxMode: "scripts" as const,
|
||||
allowExternalEmbedUrls: false,
|
||||
chatMessageMaxWidth: null,
|
||||
serverVersion: null,
|
||||
};
|
||||
|
||||
@@ -54,6 +56,7 @@ describe("loadControlUiBootstrapConfig", () => {
|
||||
expect(state.localMediaPreviewRoots).toEqual(["/tmp/openclaw"]);
|
||||
expect(state.embedSandboxMode).toBe("scripts");
|
||||
expect(state.allowExternalEmbedUrls).toBe(true);
|
||||
expect(state.chatMessageMaxWidth).toBe("min(1280px, 82%)");
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ export type ControlUiBootstrapState = {
|
||||
localMediaPreviewRoots: string[];
|
||||
embedSandboxMode: ControlUiEmbedSandboxMode;
|
||||
allowExternalEmbedUrls: boolean;
|
||||
chatMessageMaxWidth?: string | null;
|
||||
sessionKey?: string | null;
|
||||
hello?: { auth?: { deviceToken?: string | null } | null } | null;
|
||||
settings?: { token?: string | null } | null;
|
||||
@@ -130,6 +131,10 @@ export async function loadControlUiBootstrapConfig(
|
||||
? "strict"
|
||||
: "scripts";
|
||||
state.allowExternalEmbedUrls = parsed.allowExternalEmbedUrls === true;
|
||||
state.chatMessageMaxWidth =
|
||||
typeof parsed.chatMessageMaxWidth === "string" && parsed.chatMessageMaxWidth.trim()
|
||||
? parsed.chatMessageMaxWidth
|
||||
: null;
|
||||
} catch {
|
||||
// Ignore bootstrap failures; UI will update identity after connecting.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user