mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(ui): keep limited-access banner clear of shell controls (#123128)
* fix(ui): keep limited-access banner clear of shell controls * fix(ui): avoid native notice gutters
This commit is contained in:
committed by
GitHub
parent
baf85e6868
commit
449ca204a6
@@ -59,7 +59,6 @@ const SCOPE_UPGRADE_BANNER_ELEMENT = {
|
||||
function renderScopeUpgradeBanner(
|
||||
host: ShellViewHost,
|
||||
snapshot: ApplicationContext["gateway"]["snapshot"],
|
||||
chromeOffset: boolean,
|
||||
) {
|
||||
const state = readScopeUpgradeAvailability(snapshot);
|
||||
if (state.phase === "hidden") {
|
||||
@@ -80,7 +79,6 @@ function renderScopeUpgradeBanner(
|
||||
return html`<openclaw-device-scope-upgrade-banner
|
||||
.props=${{
|
||||
snapshot,
|
||||
chromeOffset,
|
||||
}}
|
||||
></openclaw-device-scope-upgrade-banner>`;
|
||||
}
|
||||
@@ -506,7 +504,7 @@ export function renderApplicationShell(host: ShellViewHost) {
|
||||
: ""} ${activeRoute === "workboard" ? "content--workboard" : ""}"
|
||||
.tabIndex=${-1}
|
||||
>
|
||||
${renderScopeUpgradeBanner(host, gatewaySnapshot, !settingsTakeover && !mobileNavLayout)}
|
||||
${renderScopeUpgradeBanner(host, gatewaySnapshot)}
|
||||
${gatewaySnapshot.hello?.deviceAuthMigration?.pending === true
|
||||
? // The migration banner is registered by a rare-flow dynamic import after first render.
|
||||
customElements.get("openclaw-device-auth-migration-banner")
|
||||
|
||||
@@ -130,7 +130,6 @@ export class ScopeUpgradeController {
|
||||
|
||||
type ScopeUpgradeBannerProps = {
|
||||
snapshot: ApplicationGatewaySnapshot;
|
||||
chromeOffset: boolean;
|
||||
};
|
||||
|
||||
class ScopeUpgradeBanner extends OpenClawLightDomContentsElement {
|
||||
@@ -186,7 +185,6 @@ class ScopeUpgradeBanner extends OpenClawLightDomContentsElement {
|
||||
class="callout ${state.phase === "error" || state.phase === "rejected"
|
||||
? "danger"
|
||||
: "warn"} callout--action"
|
||||
style=${props.chromeOffset ? "margin-left: 72px" : nothing}
|
||||
role="status"
|
||||
>
|
||||
<span class="callout__content">${text}</span>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { chromium, type Browser, type BrowserContext, type Page } from "playwrig
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
||||
import {
|
||||
canRunPlaywrightChromium,
|
||||
controlUiBundledSettingsStorageKey,
|
||||
installMockGateway,
|
||||
resolvePlaywrightChromiumExecutablePath,
|
||||
startControlUiE2eServer,
|
||||
@@ -29,9 +30,24 @@ const SCOPE_UPGRADE_METHODS = [
|
||||
"device.scopes.requestUpgrade",
|
||||
"device.scopes.waitUpgrade",
|
||||
] as const;
|
||||
const HIDDEN_WEB_CHROME_HOSTS = [
|
||||
{ collapsed: false, label: "native web chrome", rootClass: "openclaw-native-web-chrome" },
|
||||
{ collapsed: true, label: "collapsed native navigation", rootClass: "openclaw-native-nav" },
|
||||
] as const;
|
||||
const MANUAL_UPGRADE_GUIDANCE =
|
||||
"This browser has limited access. Manage it with openclaw devices on the Gateway or from Devices on an admin browser.";
|
||||
|
||||
type BoundingBox = { x: number; y: number; width: number; height: number };
|
||||
|
||||
function boxesIntersect(left: BoundingBox, right: BoundingBox): boolean {
|
||||
return !(
|
||||
left.x + left.width <= right.x ||
|
||||
right.x + right.width <= left.x ||
|
||||
left.y + left.height <= right.y ||
|
||||
right.y + right.height <= left.y
|
||||
);
|
||||
}
|
||||
|
||||
let browser: Browser;
|
||||
let server: ControlUiE2eServer;
|
||||
const openContexts = new Set<BrowserContext>();
|
||||
@@ -122,6 +138,10 @@ describeControlUiE2e("Control UI live device scope upgrade", () => {
|
||||
await page.getByRole("button", { name: "Request admin" }).waitFor();
|
||||
|
||||
await page.locator("#new-session-project-trigger").click();
|
||||
const projectPopover = page.locator("wa-popover.new-session-page__project-popover");
|
||||
await expect
|
||||
.poll(() => projectPopover.evaluate((element) => element === document.activeElement))
|
||||
.toBe(true);
|
||||
const browse = page.getByRole("button", { name: "Browse folders" });
|
||||
await expect.poll(() => browse.isDisabled()).toBe(true);
|
||||
await browse.focus();
|
||||
@@ -187,13 +207,81 @@ describeControlUiE2e("Control UI live device scope upgrade", () => {
|
||||
});
|
||||
|
||||
await page.goto(`${server.baseUrl}chat`);
|
||||
await page.getByText(MANUAL_UPGRADE_GUIDANCE, { exact: true }).waitFor();
|
||||
const guidance = page.getByText(MANUAL_UPGRADE_GUIDANCE, { exact: true });
|
||||
await guidance.waitFor();
|
||||
|
||||
const guidanceBox = await guidance.boundingBox();
|
||||
const chromeControls = page.locator(".shell-chrome-controls__button");
|
||||
expect(guidanceBox).not.toBeNull();
|
||||
expect(await chromeControls.count()).toBe(2);
|
||||
for (let index = 0; index < (await chromeControls.count()); index += 1) {
|
||||
const control = chromeControls.nth(index);
|
||||
const controlBox = await control.boundingBox();
|
||||
expect(controlBox).not.toBeNull();
|
||||
if (guidanceBox && controlBox) {
|
||||
const label = await control.getAttribute("aria-label");
|
||||
expect(
|
||||
boxesIntersect(guidanceBox, controlBox),
|
||||
`guidance ${JSON.stringify(guidanceBox)} intersects ${label} ${JSON.stringify(controlBox)}`,
|
||||
).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
expect(await page.getByRole("button", { name: "Request admin" }).count()).toBe(0);
|
||||
expect(await gateway.getRequests("device.scopes.requestUpgrade")).toHaveLength(0);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(HIDDEN_WEB_CHROME_HOSTS)(
|
||||
"keeps manual guidance at the normal content inset with $label",
|
||||
async ({ collapsed, rootClass }) => {
|
||||
const context = await createContext();
|
||||
await context.addInitScript(
|
||||
({ hostClass, settingsKey, startCollapsed }) => {
|
||||
localStorage.setItem(settingsKey, JSON.stringify({ navCollapsed: startCollapsed }));
|
||||
const stamp = () =>
|
||||
document.documentElement.classList.add("openclaw-native-macos", hostClass);
|
||||
if (document.documentElement) {
|
||||
stamp();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", stamp);
|
||||
}
|
||||
},
|
||||
{
|
||||
hostClass: rootClass,
|
||||
settingsKey: controlUiBundledSettingsStorageKey(server.baseUrl),
|
||||
startCollapsed: collapsed,
|
||||
},
|
||||
);
|
||||
const page = await context.newPage();
|
||||
await installMockGateway(page, {
|
||||
featureMethods: ["chat.metadata", "chat.startup", "device.scopes.requestUpgrade"],
|
||||
operatorScopes: LIMITED_SCOPES,
|
||||
});
|
||||
|
||||
await page.goto(`${server.baseUrl}chat`);
|
||||
const guidance = page.getByText(MANUAL_UPGRADE_GUIDANCE, { exact: true });
|
||||
await guidance.waitFor();
|
||||
if (collapsed) {
|
||||
await expect
|
||||
.poll(() => page.locator(".shell").getAttribute("class"))
|
||||
.toContain("shell--nav-collapsed");
|
||||
}
|
||||
await expect.poll(() => page.locator(".shell-chrome-controls").isVisible()).toBe(false);
|
||||
|
||||
const contentBox = await page.locator(".content").boundingBox();
|
||||
const calloutBox = await page.locator("openclaw-update-banner .callout").boundingBox();
|
||||
const contentInset = await page
|
||||
.locator(".content")
|
||||
.evaluate((element) => Number.parseFloat(getComputedStyle(element).paddingLeft));
|
||||
expect(contentBox).not.toBeNull();
|
||||
expect(calloutBox).not.toBeNull();
|
||||
if (contentBox && calloutBox) {
|
||||
expect(calloutBox.x).toBe(contentBox.x + contentInset);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("does not misreport limited Custodian access as an outdated Gateway", async () => {
|
||||
const context = await createContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
var(--shell-chrome-control-size) + var(--shell-chrome-controls-gap) +
|
||||
var(--shell-chrome-controls-gap)
|
||||
);
|
||||
--shell-floating-update-card-left: calc(
|
||||
var(--shell-chrome-controls-inset) + var(--shell-chrome-controls-collapsed-width) + 8px
|
||||
--shell-chrome-safe-area-left: calc(
|
||||
var(--shell-chrome-controls-inset) + var(--shell-chrome-controls-collapsed-width) -
|
||||
var(--shell-chrome-control-size) - var(--shell-chrome-controls-gap) + 8px
|
||||
);
|
||||
/* Desktop has no topbar row — the sidebar owns navigation. Narrow viewports
|
||||
(layout.mobile.css) and the chat split toolbar restore the row height. */
|
||||
@@ -73,6 +74,9 @@
|
||||
.shell--nav-collapsed {
|
||||
grid-template-columns: 0 minmax(0, 1fr);
|
||||
--shell-nav-width: 0px;
|
||||
--shell-chrome-safe-area-left: calc(
|
||||
var(--shell-chrome-controls-inset) + var(--shell-chrome-controls-collapsed-width) + 8px
|
||||
);
|
||||
}
|
||||
|
||||
/* Drawer shells keep the nav mounted offscreen; desktop collapse removes it. */
|
||||
@@ -136,6 +140,23 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Reserve notice space only while the corresponding in-page chrome remains visible. */
|
||||
:is(
|
||||
html:not(.openclaw-native-nav, .openclaw-native-web-chrome)
|
||||
.shell:not(.shell--mobile-nav, .shell--settings),
|
||||
html.openclaw-native-nav:not(.openclaw-native-web-chrome)
|
||||
.shell:not(.shell--mobile-nav, .shell--nav-collapsed, .shell--settings)
|
||||
)
|
||||
.content
|
||||
> :is(
|
||||
openclaw-update-banner,
|
||||
openclaw-device-scope-upgrade-banner,
|
||||
openclaw-device-auth-migration-banner
|
||||
)
|
||||
.callout {
|
||||
margin-left: var(--shell-chrome-safe-area-left);
|
||||
}
|
||||
|
||||
html.openclaw-native-macos .shell-chrome-controls {
|
||||
top: 52px;
|
||||
}
|
||||
@@ -1150,8 +1171,8 @@ openclaw-settings-save-indicator {
|
||||
|
||||
/* Clear the collapsed three-button chrome cluster plus an 8px breathing gap. */
|
||||
.shell:not(.shell--mobile-nav) .sidebar-update-card--floating .sidebar-update-card {
|
||||
width: min(360px, calc(100% - var(--shell-floating-update-card-left) - 16px));
|
||||
margin-left: var(--shell-floating-update-card-left);
|
||||
width: min(360px, calc(100% - var(--shell-chrome-safe-area-left) - 16px));
|
||||
margin-left: var(--shell-chrome-safe-area-left);
|
||||
}
|
||||
|
||||
/* Native hosts hide the web cluster; onboarding never mounts it. */
|
||||
|
||||
Reference in New Issue
Block a user