refactor(ui): remove retired chat control styles (#130201)

* refactor(ui): remove retired chat control styles

* fix(ui): keep background config refreshes from blocking saves
This commit is contained in:
Peter Steinberger
2026-08-26 11:53:35 -07:00
committed by GitHub
parent adb3fb2967
commit a665de9c1d
9 changed files with 15 additions and 686 deletions
@@ -224,7 +224,7 @@ export async function executeConfigExternalMutation<T>(
export async function loadConfig(
state: RuntimeConfigState,
options: LoadConfigOptions = {},
options: LoadConfigOptions & { background?: boolean } = {},
isCurrentLoad: () => boolean = () => true,
): Promise<boolean> {
const client = state.client;
@@ -233,7 +233,9 @@ export async function loadConfig(
}
const connectionEpoch = currentConfigConnectionEpoch(state);
const version = nextRequestVersion(state, "config");
state.configLoading = true;
if (!options.background) {
state.configLoading = true;
}
state.lastError = null;
state.chatError = null;
try {
@@ -249,7 +251,10 @@ export async function loadConfig(
}
return false;
} finally {
if (isCurrentRequest(state, "config", version, client, connectionEpoch)) {
if (
!options.background &&
isCurrentRequest(state, "config", version, client, connectionEpoch)
) {
state.configLoading = false;
}
}
@@ -344,9 +344,12 @@ describe("runtime config capability", () => {
const { runtimeConfig } = createConfigCapabilityHarness(
request as GatewayBrowserClient["request"],
);
await runtimeConfig.ensureLoaded();
const initialLoad = runtimeConfig.ensureLoaded();
expect(runtimeConfig.state.configLoading).toBe(true);
await initialLoad;
await vi.advanceTimersByTimeAsync(250);
expect(runtimeConfig.state.configLoading).toBe(false);
runtimeConfig.patchForm(["count"], 2);
await vi.advanceTimersByTimeAsync(CONFIG_FORM_AUTO_SAVE_DEBOUNCE_MS);
expect(runtimeConfig.state.configSnapshot?.hash).toBe("hash-2");
@@ -142,7 +142,8 @@ export function createRuntimeConfigCapability(
state.connected &&
state.configNeedsApply &&
state.configSnapshot?.appliedConfigHash !== undefined,
refresh: (isCurrent) => loadOnce("config", () => loadConfig(state, {}, isCurrent)),
refresh: (isCurrent) =>
loadOnce("config", () => loadConfig(state, { background: true }, isCurrent)),
});
const refreshConnectionState = () => {
const config = run(() => loadConfig(state));
@@ -139,7 +139,6 @@ type ChatFixtureOptions = {
crowdedComposerFooter?: boolean;
direct?: boolean;
sessionRailBody?: string;
singleAgent?: boolean;
slashMenu?: boolean;
};
@@ -464,40 +463,6 @@ function chatFooterActionsHtml() {
`;
}
function chatControlsHtml(opts: { agent?: boolean } = {}) {
const showAgent = opts.agent !== false;
return `
<div class="chat-mobile-controls-wrapper">
<button class="btn btn--sm btn--icon chat-controls-mobile-toggle" aria-expanded="true" aria-controls="chat-mobile-controls-dropdown">${iconSvg()}</button>
<div id="chat-mobile-controls-dropdown" class="chat-controls-dropdown open">
<div class="chat-controls">
<div class="chat-controls__session-row${showAgent ? "" : " chat-controls__session-row--single-agent"}">
${
showAgent
? `<label class="field chat-controls__session chat-controls__agent">
<select data-chat-agent-filter="true" aria-label="Filter sessions by agent"><option>Alpha</option><option>Beta</option></select>
</label>`
: ""
}
<label class="field chat-controls__session chat-controls__session-picker">
<select data-chat-session-select="true" aria-label="Chat thread"><option>Daily planning</option></select>
</label>
<details class="chat-controls__session chat-controls__inline-select chat-controls__model">
<summary class="chat-controls__inline-select-trigger" data-chat-model-select="true" data-chat-thinking-select="true" data-chat-select-value="" data-chat-thinking-value="" aria-label="Chat model">gpt-5 · High</summary>
</details>
</div>
<div class="chat-controls__thinking">
<button class="btn btn--sm btn--icon active">${iconSvg()}</button>
<button class="btn btn--sm btn--icon active">${iconSvg()}</button>
<button class="btn btn--sm btn--icon">${iconSvg()}</button>
<button class="btn btn--sm btn--icon active">${iconSvg()}</button>
</div>
</div>
</div>
</div>
`;
}
function composerControlsHtml() {
return `
<div class="agent-chat__composer-controls">
@@ -543,38 +508,6 @@ function composerControlsHtml() {
`;
}
function chatHeaderControlsHtml(hidden = false) {
return `
<main class="content content--chat" data-chat-header-responsive-fixture>
<section class="content-header${hidden ? " content-header--chat-hidden" : ""}"${hidden ? ' inert aria-hidden="true"' : ""}>
<div>
<div class="chat-controls__session-row">
<label class="field chat-controls__session chat-controls__agent">
<select data-chat-agent-filter="true" aria-label="Filter sessions by agent"><option>Valentina</option></select>
</label>
<label class="field chat-controls__session chat-controls__session-picker">
<select data-chat-session-select="true" aria-label="Chat thread"><option>main</option></select>
</label>
<details class="chat-controls__session chat-controls__inline-select chat-controls__model">
<summary class="chat-controls__inline-select-trigger" data-chat-model-select="true" data-chat-thinking-select="true" data-chat-select-value="gpt-5.5" data-chat-thinking-value="" aria-label="Chat model">gpt-5.5 · High</summary>
</details>
</div>
</div>
<div class="page-meta">
<div class="chat-controls">
<button class="btn btn--sm btn--icon" aria-label="Refresh chat data">${iconSvg()}</button>
<span class="chat-controls__separator">|</span>
<button class="btn btn--sm btn--icon active" aria-label="Toggle assistant thinking">${iconSvg()}</button>
<button class="btn btn--sm btn--icon active" aria-label="Toggle tool calls">${iconSvg()}</button>
<button class="btn btn--sm btn--icon active" aria-label="Show cron sessions">${iconSvg()}</button>
</div>
</div>
</section>
<section class="card chat"></section>
</main>
`;
}
function chatHtml(opts: ChatFixtureOptions = {}, mobileNavLayout = false) {
return `
<div class="shell shell--chat${mobileNavLayout ? " shell--mobile-nav" : ""}" data-chat-responsive-fixture>
@@ -582,7 +515,6 @@ function chatHtml(opts: ChatFixtureOptions = {}, mobileNavLayout = false) {
<div class="topnav-shell">
<div class="topnav-shell__actions">
<button class="topbar-search">${iconSvg()}</button>
<div>${chatControlsHtml({ agent: !opts.singleAgent })}</div>
</div>
</div>
</header>
@@ -917,19 +849,6 @@ function rectsOverlap(
);
}
async function openHeaderFixture(width: number, height: number, opts: { hidden?: boolean } = {}) {
const page = await openBrowserPage(width, height);
try {
await page.setContent(
`<!doctype html><html><head><style>${readUiCss()}</style></head><body>${chatHeaderControlsHtml(Boolean(opts.hidden))}</body></html>`,
);
return page;
} catch (error) {
await closeBrowserPage(page);
throw error;
}
}
async function expectNoHorizontalOverflow(page: Page) {
const metrics = await page.evaluate(() => ({
body: document.body.scrollWidth,
@@ -2780,66 +2699,6 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => {
}
});
it.each([
[1120, 740],
[1366, 900],
[1440, 900],
] as const)("keeps desktop chat controls in one row at %sx%s", async (width, height) => {
const page = await openHeaderFixture(width, height);
try {
await expectNoHorizontalOverflow(page);
const controls = await page.evaluate(() => {
const rectFor = (selector: string) => {
const node = document.querySelector(selector);
const rect = node?.getBoundingClientRect();
return rect ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null;
};
return {
session: rectFor('[data-chat-session-select="true"]'),
agent: rectFor('[data-chat-agent-filter="true"]'),
model: rectFor('[data-chat-model-select="true"]'),
action: rectFor(".page-meta .btn--icon"),
};
});
const rowY = [
controls.session?.y,
controls.agent?.y,
controls.model?.y,
controls.action?.y,
].filter((value): value is number => typeof value === "number");
expect(rowY.length).toBe(4);
expect(Math.max(...rowY) - Math.min(...rowY)).toBeLessThanOrEqual(4);
const agent = expectControlRect(controls.agent, "agent");
const session = expectControlRect(controls.session, "session");
expect(agent.x).toBeLessThan(session.x);
expect(session.width / agent.width).toBeGreaterThan(1.25);
expect(session.width / agent.width).toBeLessThan(1.55);
} finally {
await closeBrowserPage(page);
}
});
it("collapses the desktop chat controls row when scroll state hides it", async () => {
const page = await openHeaderFixture(1366, 900, { hidden: true });
try {
const hiddenState = await page.evaluate(() => {
const header = document.querySelector(".content-header") as HTMLElement | null;
const rect = header?.getBoundingClientRect();
const style = header ? getComputedStyle(header) : null;
return {
height: rect?.height ?? -1,
opacity: style?.opacity ?? "",
pointerEvents: style?.pointerEvents ?? "",
};
});
expect(hiddenState.height).toBeLessThanOrEqual(1);
expect(hiddenState.opacity).toBe("0");
expect(hiddenState.pointerEvents).toBe("none");
} finally {
await closeBrowserPage(page);
}
});
it.each(VIEWPORTS)("keeps the chat shell inside the viewport at %sx%s", async (width, height) => {
const page = await openFixture(width, height);
try {
@@ -3022,73 +2881,6 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => {
},
);
it.each(["dark", "light"] as const)(
"keeps mobile controls inside the viewport with touch targets in %s mode",
async (themeMode) => {
const page = await openFixture(320, 568);
try {
await page.evaluate(
(mode) => document.documentElement.setAttribute("data-theme-mode", mode),
themeMode,
);
const dropdown = await getBoundingBox(page, ".chat-controls-dropdown.open");
expect(dropdown.x).toBeGreaterThanOrEqual(8);
expect(dropdown.x + dropdown.width).toBeLessThanOrEqual(312);
await expectNoHorizontalOverflow(page);
const mobileControls = await page.evaluate(() => {
const rectFor = (selector: string) => {
const node = document.querySelector(selector) as HTMLElement | null;
if (!node) {
return null;
}
const rect = node.getBoundingClientRect();
return {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
text: node.textContent?.trim() ?? "",
display: getComputedStyle(node).display,
};
};
return {
agent: rectFor('[data-chat-agent-filter="true"]'),
session: rectFor('[data-chat-session-select="true"]'),
model: rectFor('[data-chat-model-select="true"]'),
compactCount: document.querySelectorAll('[data-chat-thinking-select-compact="true"]')
.length,
};
});
const agent = expectControlRect(mobileControls.agent, "agent");
const session = expectControlRect(mobileControls.session, "session");
const model = expectControlRect(mobileControls.model, "model");
expect(session.y).toBe(agent.y);
expect(agent.x).toBeLessThan(session.x);
expect(session.width / agent.width).toBeGreaterThan(1.25);
expect(session.width / agent.width).toBeLessThan(1.55);
expect(model.display).not.toBe("none");
expect(model.text).toBe("gpt-5 · High");
expect(mobileControls.compactCount).toBe(0);
const sizes = await page
.locator(".chat-controls-mobile-toggle, .chat-controls-dropdown .btn--icon")
.evaluateAll((nodes) =>
nodes.map((node) => {
const rect = (node as HTMLElement).getBoundingClientRect();
return { width: rect.width, height: rect.height };
}),
);
expect(sizes.length).toBeGreaterThan(0);
for (const size of sizes) {
expect(size.width).toBeGreaterThanOrEqual(TOUCH_TARGET_MIN_PX);
expect(size.height).toBeGreaterThanOrEqual(TOUCH_TARGET_MIN_PX);
}
} finally {
await closeBrowserPage(page);
}
},
);
it("keeps primary composer actions desktop-sized on phones", async () => {
const page = await openFixture(320, 568);
try {
@@ -4450,20 +4242,6 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => {
}
});
it("uses the compact mobile grid when the agent filter is not rendered", async () => {
const page = await openFixture(320, 568, { singleAgent: true });
try {
await expectNoHorizontalOverflow(page);
expect(await page.locator('[data-chat-agent-filter="true"]').count()).toBe(0);
const session = await getBoundingBox(page, '[data-chat-session-select="true"]');
const model = await getBoundingBox(page, '[data-chat-model-select="true"]');
expect(model.y).toBeGreaterThan(session.y);
expect(model.width).toBe(session.width);
} finally {
await closeBrowserPage(page);
}
});
it("keeps the embedded side-chat composer trailing and flush with the tab strip", async () => {
const page = await openBrowserPage(1024, 768);
try {
+1 -229
View File
@@ -78,17 +78,6 @@ openclaw-chat-page {
}
}
.chat-error__dot {
flex-shrink: 0;
width: 7px;
height: 7px;
/* Optically centers the dot on the first text line when the message wraps. */
margin-top: 6px;
border-radius: var(--radius-full);
background: var(--danger);
box-shadow: 0 0 8px color-mix(in srgb, var(--danger) 55%, transparent);
}
.chat-error__content {
min-width: 0;
overflow-wrap: anywhere;
@@ -207,25 +196,12 @@ openclaw-chat-page {
color: var(--muted);
}
.chat-cloud-disk-space-notice__title {
display: flex;
align-items: center;
gap: 8px;
}
.chat-cloud-disk-space-notice__title svg {
width: 17px;
height: 17px;
flex: 0 0 auto;
}
.chat-workspace-conflict-notice__content p,
.chat-workspace-conflict-event p {
margin: 0;
color: var(--muted);
}
.chat-workspace-conflict-notice__title,
.chat-workspace-conflict-event__header {
display: flex;
align-items: center;
@@ -233,7 +209,6 @@ openclaw-chat-page {
color: color-mix(in srgb, var(--warn) 82%, var(--text-strong));
}
.chat-workspace-conflict-notice__title svg,
.chat-workspace-conflict-event__header svg {
width: 17px;
height: 17px;
@@ -3823,18 +3798,6 @@ button.chat-pr__diff {
text-overflow: clip;
}
.agent-chat__composer-controls .chat-controls {
display: flex;
justify-content: flex-end;
gap: 6px;
min-width: 0;
margin: 0;
}
.agent-chat__composer-controls .chat-controls__session-row {
min-width: 0;
}
.chat-composer-model-control {
display: inline-flex;
flex: 0 0 auto;
@@ -3848,14 +3811,6 @@ button.chat-pr__diff {
width: 100%;
}
.agent-chat__stt-interim {
padding: 10px 14px 0;
color: var(--muted);
font-size: var(--control-ui-text-sm);
line-height: 1.35;
overflow-wrap: anywhere;
}
.agent-chat__composer-input-row {
display: flex;
align-items: flex-end;
@@ -4098,24 +4053,6 @@ button.chat-pr__diff {
}
}
.agent-chat__input-btn--selected:not(:disabled) {
position: relative;
color: var(--accent);
background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.agent-chat__input-btn--selected::after {
position: absolute;
right: 5px;
bottom: 5px;
width: 5px;
height: 5px;
border: 1px solid var(--bg);
border-radius: 50%;
background: currentColor;
content: "";
}
.agent-chat__input-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
@@ -4416,11 +4353,6 @@ button.chat-pr__diff {
height: 100%;
}
.agent-chat__composer-error-text {
min-width: 0;
flex: 1;
}
.agent-chat__composer-error .callout__dismiss {
margin: -4px -4px -4px 0;
border-radius: var(--radius-full);
@@ -4697,12 +4629,6 @@ button.chat-pr__diff {
height: 13px;
}
@keyframes chat-run-status-spin {
to {
transform: rotate(360deg);
}
}
/* Send is the primary action of the whole surface, so it gets the same solid
accent treatment as .btn.primary; variants (--stop, --voice*) override the
fill instead of reintroducing borders. */
@@ -6103,13 +6029,6 @@ button.chat-pr__diff {
white-space: nowrap;
}
.chat-attachment-file__type {
color: var(--muted);
font-size: 10px;
line-height: 1.1;
text-transform: uppercase;
}
.chat-attachment-file--pasted-text .chat-attachment-file__name {
font-size: 0.86rem;
line-height: 1.2;
@@ -6162,50 +6081,11 @@ button.chat-pr__diff {
display: none;
}
/* Chat controls - moved to content-header area, left aligned */
.chat-controls {
display: inline-flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
flex-wrap: wrap;
min-height: 36px;
position: relative;
overflow: visible;
}
.chat-controls__session {
min-width: 0;
max-width: none;
}
.chat-controls__agent {
min-width: 0;
max-width: none;
grid-area: agent;
}
.chat-controls__session-row {
display: grid;
grid-template-columns: minmax(116px, 5fr) minmax(132px, 7fr) minmax(180px, 8fr);
grid-template-areas: "agent session model";
align-items: center;
gap: 6px;
width: 100%;
min-height: 36px;
min-width: 0;
}
.chat-controls__session-row--single-agent {
grid-template-columns: minmax(132px, 7fr) minmax(180px, 8fr);
grid-template-areas: "session model";
}
.chat-controls__session-picker {
grid-area: session;
position: relative;
}
.chat-controls__model {
grid-area: model;
min-width: 0;
@@ -6344,42 +6224,6 @@ button.chat-pr__diff {
transform: rotate(0deg);
}
.chat-controls__trigger-meta {
color: var(--muted);
flex: 0 0 auto;
font-size: 11px;
}
.chat-controls__trigger-provider-wrap {
position: relative;
display: inline-flex;
flex: 0 0 auto;
}
.chat-controls__model-capability-alert {
position: absolute;
top: -5px;
right: -1px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 11px;
height: 11px;
border-radius: var(--radius-full);
background: var(--chat-composer-surface);
color: color-mix(in srgb, var(--warn) 82%, var(--text));
}
.chat-controls__model-capability-alert svg {
width: 10px;
height: 10px;
}
.chat-controls__model-capability-alert:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 1px;
}
.chat-controls__model-trigger-skeleton {
display: block;
width: 100%;
@@ -7340,48 +7184,7 @@ button.chat-pr__diff {
flex: 0 0 auto;
}
.chat-controls__thinking {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
padding: 4px 10px;
background: rgba(255, 255, 255, 0.04);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
}
/* Controls separator: the visible divider is the background; any glyph content
is clipped by the 1px overflow-hidden box. */
.chat-controls__separator {
align-self: center;
flex: 0 0 1px;
width: 1px;
height: 22px;
margin: 0 3px;
overflow: hidden;
background: color-mix(in srgb, var(--border-strong) 72%, transparent);
color: var(--muted);
font-size: 12px;
line-height: 1;
font-weight: 300;
user-select: none;
}
.chat-controls .btn--icon {
width: 36px;
min-width: 36px;
height: 36px;
padding: 0;
border-radius: var(--radius-lg);
}
:root[data-theme-mode="light"] .chat-controls__separator {
background: rgba(16, 24, 40, 0.18);
}
.chat-controls__session select,
.chat-controls__agent select {
.chat-controls__session select {
height: 36px;
min-height: 36px;
padding: 0 34px 0 12px;
@@ -7395,12 +7198,6 @@ button.chat-pr__diff {
text-overflow: ellipsis;
}
/* Light theme thinking indicator override */
:root[data-theme-mode="light"] .chat-controls__thinking {
background: rgba(255, 255, 255, 0.9);
border-color: rgba(16, 24, 40, 0.15);
}
@media (max-width: 768px) {
.chat-main__conversation {
--chat-composer-top-margin: 6px;
@@ -7415,20 +7212,11 @@ button.chat-pr__diff {
max-width: none;
}
.chat-controls__agent {
min-width: 120px;
max-width: none;
}
.chat-controls__model {
min-width: 140px;
max-width: none;
}
.chat-controls {
gap: 8px;
}
.agent-chat__input-btn,
.chat-send-btn {
width: 44px;
@@ -7574,10 +7362,6 @@ button.chat-pr__diff {
padding-inline: 0;
}
.agent-chat__input--chat .context-ring__pct {
display: none;
}
.agent-chat__input--chat .context-ring__dial,
.agent-chat__input--chat .chat-controls__permission-icon,
.agent-chat__input--chat .chat-controls__permission-lock,
@@ -7761,15 +7545,6 @@ button.chat-pr__diff {
flex-direction: row;
}
.agent-chat__composer-controls .chat-controls {
justify-content: flex-start;
}
.chat-controls {
flex-wrap: wrap;
gap: 8px;
}
.chat-controls__session {
min-width: 120px;
}
@@ -8078,6 +7853,3 @@ button.chat-pr__diff {
min-height: 44px;
}
}
/* Mobile dropdown toggle — hidden on desktop */
/* Mobile gear toggle + dropdown are hidden by default in layout.css */
-7
View File
@@ -1487,13 +1487,6 @@ select.input {
color: var(--text);
}
:root[data-theme-mode="light"] .chat-controls .btn--icon.active {
border-color: var(--accent);
background: var(--accent-subtle);
color: var(--accent);
box-shadow: 0 0 0 1px var(--accent-subtle);
}
/* Ghost stays chromeless in light mode too: without the border-color reset,
the higher-specificity light .btn--icon override above re-adds a 1px border
that dark mode never shows. Chrome appears on hover only. */
-31
View File
@@ -4113,17 +4113,6 @@ html:not(.openclaw-native-macos):not(.openclaw-native-nav):not(.openclaw-native-
min-width: 0;
}
.content--chat .content-header.content-header--chat-hidden {
opacity: 0;
transform: translateY(-10px);
min-height: 0;
max-height: 0px;
padding-top: 0;
padding-bottom: 0;
overflow: hidden;
pointer-events: none;
}
.page-title {
font-size: 22px;
font-weight: 650;
@@ -4182,12 +4171,6 @@ html:not(.openclaw-native-macos):not(.openclaw-native-nav):not(.openclaw-native-
overflow: visible;
}
.content--chat .chat-controls {
flex-shrink: 0;
flex-wrap: nowrap;
gap: 6px;
}
/* ===========================================
Grid Utilities
=========================================== */
@@ -4243,20 +4226,6 @@ html:not(.openclaw-native-macos):not(.openclaw-native-nav):not(.openclaw-native-
}
}
/* Mobile chat controls — hidden on desktop, shown in layout.mobile.css */
.chat-mobile-controls-wrapper {
display: none;
}
.chat-controls-mobile-toggle {
display: none;
border-radius: var(--radius-full);
}
.chat-controls-dropdown {
display: none;
}
/* Standalone terminal/desktop documents: centered notice when the gateway
refuses or lacks the requested surface. */
.terminal-view-unavailable,
-174
View File
@@ -26,46 +26,17 @@
row-gap: 0;
}
.content--chat .chat-controls {
margin-left: 0;
justify-content: flex-end;
row-gap: 0;
}
.chat-controls__session-row {
grid-template-columns: minmax(96px, 5fr) minmax(112px, 7fr) minmax(164px, 8fr);
grid-template-areas: "agent session model";
align-items: center;
gap: 8px;
width: 100%;
}
.chat-controls__session-row--single-agent {
grid-template-columns: minmax(112px, 7fr) minmax(164px, 8fr);
grid-template-areas: "session model";
}
.chat-controls__agent {
grid-area: agent;
}
.chat-controls__session-picker {
grid-area: session;
}
.chat-controls__model {
grid-area: model;
}
.chat-controls__session,
.chat-controls__agent,
.chat-controls__model {
min-width: 0;
max-width: none;
}
.chat-controls__session select,
.chat-controls__agent select,
.chat-controls__inline-select-trigger {
width: 100%;
max-width: none;
@@ -339,134 +310,6 @@ html.openclaw-native-macos body .shell--mobile-nav .topnav-shell__actions {
padding-bottom: 0;
}
/* Show the mobile gear toggle (lives in topbar now) */
.chat-mobile-controls-wrapper {
display: flex;
position: relative;
}
.chat-mobile-controls-wrapper .chat-controls-mobile-toggle {
display: flex;
width: 44px;
min-width: 44px;
height: 44px;
}
/* The dropdown panel is viewport-clamped so narrow phones never crop controls. */
.chat-mobile-controls-wrapper .chat-controls-dropdown {
display: none;
position: fixed;
top: calc(var(--shell-topbar-height) + 8px);
right: max(8px, var(--safe-area-right));
left: auto;
z-index: 100;
width: min(420px, calc(100vw - var(--safe-area-left) - var(--safe-area-right) - 16px));
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 10px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
flex-direction: column;
gap: 8px;
min-width: 0;
max-height: min(72vh, 520px);
overflow: auto;
overscroll-behavior: contain;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown.open {
display: flex;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
width: 100%;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__session-row {
display: grid;
grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
grid-template-areas:
"agent session"
"model model";
gap: 8px;
width: 100%;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__session-row--single-agent {
grid-template-columns: minmax(0, 1fr);
grid-template-areas:
"session"
"model";
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__session {
min-width: unset;
max-width: unset;
width: 100%;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__agent {
grid-area: agent;
min-width: unset;
max-width: unset;
width: 100%;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__session-picker {
grid-area: session;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__model {
grid-area: model;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__inline-select-trigger {
display: flex;
width: 100%;
max-width: none;
min-height: 44px;
font-size: 14px;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__model-menu,
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__effort-menu {
width: 100%;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__session select {
width: 100%;
font-size: 14px;
min-height: 44px;
padding: 10px 12px;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__agent select {
width: 100%;
font-size: 14px;
min-height: 44px;
padding: 10px 12px;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__thinking {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
align-items: center;
justify-content: stretch;
gap: 8px;
width: 100%;
padding: 0;
}
.chat-mobile-controls-wrapper .chat-controls-dropdown .btn--icon {
width: 100%;
min-width: 0;
height: 44px;
}
.content {
padding: 4px 4px 16px;
gap: 12px;
@@ -791,23 +634,6 @@ html.openclaw-native-macos body .shell--mobile-nav .topnav-shell__actions {
max-width: 76px;
}
.sessions-table .session-status-badge {
max-width: 72px;
gap: 5px;
padding: 2px 6px;
}
.sessions-table .session-goal-chip {
grid-template-columns: minmax(0, 1fr);
max-width: 72px;
gap: 0;
padding: 2px 6px;
}
.sessions-table .session-goal-chip__objective {
display: none;
}
.data-table th,
.data-table td {
padding: 6px 4px;
-18
View File
@@ -246,24 +246,6 @@
min-width: min(280px, 100%);
}
.workboard-toggle {
display: inline-flex;
align-items: center;
gap: 7px;
min-height: var(--workboard-control-height);
color: var(--muted);
font-size: 0.8rem;
font-weight: 600;
line-height: 1.2;
white-space: nowrap;
}
.workboard-toggle input {
width: 15px;
height: 15px;
margin: 0;
}
.workboard-draft {
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;