mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 19:08:22 -06:00
fix: label browser-local read-only preferences
This commit is contained in:
@@ -197,6 +197,7 @@ export function renderApplicationShell(host: ShellViewHost) {
|
||||
terminalAvailable,
|
||||
catalogOpenTarget: normalizeCatalogOpenTarget(uiSettings.catalogOpenTarget),
|
||||
canPairDevice: gatewayConnected && (operatorAccess.canAdmin || operatorAccess.canPair),
|
||||
preferencesBrowserOnly: gatewayConnected && context.runtimeConfig.canPatch === false,
|
||||
sidebarEntries: navigationSnapshot.sidebarEntries,
|
||||
workboardBoards: host.sidebarWorkboardSnapshot.boards,
|
||||
workboardBoardsReady: host.sidebarWorkboardSnapshot.ready,
|
||||
|
||||
@@ -32,6 +32,7 @@ export abstract class AppSidebarBase extends OpenClawLightDomContentsElement {
|
||||
@property({ attribute: false }) terminalAvailable = false;
|
||||
@property({ attribute: false }) catalogOpenTarget: CatalogOpenTarget = "viewer";
|
||||
@property({ attribute: false }) canPairDevice = false;
|
||||
@property({ attribute: false }) preferencesBrowserOnly = false;
|
||||
@property({ attribute: false }) sessionKey = "";
|
||||
@property({ attribute: false }) sidebarEntries: readonly string[] = DEFAULT_SIDEBAR_ENTRIES;
|
||||
@property({ attribute: false }) workboardBoards: readonly SidebarWorkboardBoard[] = [];
|
||||
|
||||
@@ -234,6 +234,7 @@ export function renderSidebarMoreMenu(params: SidebarMoreMenuParams) {
|
||||
type SidebarCustomizeMenuParams = {
|
||||
position: SidebarMenuPosition | null;
|
||||
sidebarEntries: readonly string[];
|
||||
preferencesBrowserOnly: boolean;
|
||||
isRouteEnabled: (routeId: NavigationRouteId) => boolean;
|
||||
workboardBoards: readonly SidebarWorkboardBoard[];
|
||||
workboardRenderers?: SidebarWorkboardRenderers;
|
||||
@@ -284,6 +285,11 @@ export function renderSidebarCustomizeMenu(params: SidebarCustomizeMenuParams) {
|
||||
style="position: fixed; left: ${position.x}px; top: ${position.y}px; width: 1px; height: 1px; opacity: 0; pointer-events: none;"
|
||||
></button>
|
||||
<div class="sidebar-customize-menu__title">${t("nav.customize")}</div>
|
||||
${params.preferencesBrowserOnly
|
||||
? html`<div class="sidebar-customize-menu__provenance" role="note">
|
||||
${t("quickSettings.personal.browserOnly")}
|
||||
</div>`
|
||||
: nothing}
|
||||
${SIDEBAR_NAV_ROUTES.filter((routeId) => params.isRouteEnabled(routeId)).map((routeId) => {
|
||||
const visible = params.sidebarEntries.includes(
|
||||
serializeSidebarEntry({ type: "route", route: routeId }),
|
||||
|
||||
@@ -84,6 +84,7 @@ export interface SidebarMenusControllerHost
|
||||
readonly onUpdateSidebarEntries?: (entries: string[]) => void;
|
||||
readonly onPreloadRoute?: (routeId: NavigationRouteId) => Promise<void>;
|
||||
readonly pinnedAgentIds: readonly string[];
|
||||
readonly preferencesBrowserOnly: boolean;
|
||||
readonly selectedSessionKeys: ReadonlySet<string>;
|
||||
readonly sessionData: SessionOrganizerControllerHost["sessionData"] &
|
||||
Pick<
|
||||
|
||||
@@ -104,6 +104,7 @@ export function renderSidebarCustomizeMenuForController(controller: SidebarMenus
|
||||
return renderSidebarCustomizeMenu({
|
||||
position,
|
||||
sidebarEntries: host.sidebarEntries,
|
||||
preferencesBrowserOnly: host.preferencesBrowserOnly,
|
||||
isRouteEnabled: (routeId) => controller.isRouteEnabled(routeId),
|
||||
workboardBoards: host.workboardBoards,
|
||||
workboardRenderers: host.workboardRenderers,
|
||||
|
||||
@@ -487,7 +487,7 @@ describeControlUiE2e("Control UI Appearance defaults mocked Gateway E2E", () =>
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps read-only edits browser-local across reload and restores the server baseline", async () => {
|
||||
it("keeps every read-only preference surface browser-local across reload", async () => {
|
||||
const context = await browser.newContext({
|
||||
locale: "en-US",
|
||||
serviceWorkers: "block",
|
||||
@@ -545,6 +545,65 @@ describeControlUiE2e("Control UI Appearance defaults mocked Gateway E2E", () =>
|
||||
.not.toContain("Stored in this browser only");
|
||||
await page.waitForTimeout(100);
|
||||
expect(await gateway.getRequests("config.patch")).toHaveLength(0);
|
||||
|
||||
await page.goto(`${server.baseUrl}chat`);
|
||||
const viewMenuTrigger = page.locator(".chat-view-menu-trigger");
|
||||
await viewMenuTrigger.click();
|
||||
const viewMenu = page.locator("wa-dropdown.chat-view-menu");
|
||||
await expect
|
||||
.poll(() => viewMenu.locator(".chat-view-menu__provenance").textContent())
|
||||
.toContain("Stored in this browser only");
|
||||
const reasoning = viewMenu.getByRole("menuitemcheckbox", { name: "Reasoning" });
|
||||
await reasoning.click();
|
||||
await expect.poll(() => reasoning.getAttribute("aria-checked")).toBe("false");
|
||||
|
||||
const sidebar = page.locator("openclaw-app-sidebar");
|
||||
await sidebar.locator(".sidebar-nav__head-action").click();
|
||||
await sidebar
|
||||
.locator("wa-dropdown.sidebar-more-menu")
|
||||
.getByRole("menuitem", { name: "Edit pinned items" })
|
||||
.click();
|
||||
const customizeMenu = sidebar.locator(
|
||||
"wa-dropdown.sidebar-customize-menu:not(.sidebar-more-menu):not(.sidebar-agent-menu)",
|
||||
);
|
||||
await expect
|
||||
.poll(() => customizeMenu.locator(".sidebar-customize-menu__provenance").textContent())
|
||||
.toContain("Stored in this browser only");
|
||||
const tasks = customizeMenu.getByRole("menuitemcheckbox", { name: "Tasks" });
|
||||
await tasks.click();
|
||||
await expect.poll(() => tasks.getAttribute("aria-checked")).toBe("true");
|
||||
await page.waitForTimeout(100);
|
||||
expect(await gateway.getRequests("config.patch")).toHaveLength(0);
|
||||
|
||||
await page.reload();
|
||||
await viewMenuTrigger.click();
|
||||
await expect
|
||||
.poll(() => viewMenu.locator(".chat-view-menu__provenance").textContent())
|
||||
.toContain("Stored in this browser only");
|
||||
await expect
|
||||
.poll(() =>
|
||||
viewMenu
|
||||
.getByRole("menuitemcheckbox", { name: "Reasoning" })
|
||||
.getAttribute("aria-checked"),
|
||||
)
|
||||
.toBe("false");
|
||||
|
||||
await sidebar.locator(".sidebar-nav__head-action").click();
|
||||
await sidebar
|
||||
.locator("wa-dropdown.sidebar-more-menu")
|
||||
.getByRole("menuitem", { name: "Edit pinned items" })
|
||||
.click();
|
||||
await expect
|
||||
.poll(() => customizeMenu.locator(".sidebar-customize-menu__provenance").textContent())
|
||||
.toContain("Stored in this browser only");
|
||||
await expect
|
||||
.poll(() =>
|
||||
customizeMenu
|
||||
.getByRole("menuitemcheckbox", { name: "Tasks" })
|
||||
.getAttribute("aria-checked"),
|
||||
)
|
||||
.toBe("true");
|
||||
expect(await gateway.getRequests("config.patch")).toHaveLength(0);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ function createProps(overrides: Record<string, unknown> = {}): ChatControlsProps
|
||||
stream: null,
|
||||
},
|
||||
onboarding: false,
|
||||
preferencesBrowserOnly: false,
|
||||
settings: createSettings(),
|
||||
viewMenuOpen: true,
|
||||
onSettingsChange: () => undefined,
|
||||
@@ -101,6 +102,15 @@ describe("chat composer view menu", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("labels read-only preference changes as browser-local", () => {
|
||||
const container = document.createElement("div");
|
||||
render(renderChatControls(createProps({ preferencesBrowserOnly: true })), container);
|
||||
|
||||
expect(container.querySelector(".chat-view-menu__provenance")?.textContent?.trim()).toBe(
|
||||
t("quickSettings.personal.browserOnly"),
|
||||
);
|
||||
});
|
||||
|
||||
it("disables the rows and pins display state during onboarding", () => {
|
||||
const container = document.createElement("div");
|
||||
const onSettingsChange = vi.fn();
|
||||
|
||||
@@ -411,6 +411,9 @@ export class ChatPane extends ChatPaneHeader {
|
||||
selectedSession,
|
||||
agentDefaultModel,
|
||||
mutationAccess: mutationAccess.runtimePatch,
|
||||
preferencesBrowserOnly:
|
||||
this.context.runtimeConfig.state.connected &&
|
||||
this.context.runtimeConfig.canPatch === false,
|
||||
}),
|
||||
sessionWorkspace: catalogKey ? undefined : sessionWorkspace,
|
||||
backgroundTasks: catalogKey ? undefined : backgroundTasks,
|
||||
|
||||
@@ -39,8 +39,16 @@ export function renderChatPaneComposerControls(params: {
|
||||
selectedSession: GatewaySessionRow | undefined;
|
||||
agentDefaultModel: string | undefined;
|
||||
mutationAccess: SessionMethodAccess;
|
||||
preferencesBrowserOnly: boolean;
|
||||
}) {
|
||||
const { paneId, state, selectedSession, agentDefaultModel, mutationAccess } = params;
|
||||
const {
|
||||
paneId,
|
||||
state,
|
||||
selectedSession,
|
||||
agentDefaultModel,
|
||||
mutationAccess,
|
||||
preferencesBrowserOnly,
|
||||
} = params;
|
||||
const mutationAllowed = () => mutationAccess.allowed;
|
||||
return renderChatControls({
|
||||
paneId,
|
||||
@@ -74,6 +82,7 @@ export function renderChatPaneComposerControls(params: {
|
||||
: Promise.resolve(false),
|
||||
},
|
||||
onboarding: state.onboarding,
|
||||
preferencesBrowserOnly,
|
||||
settings: state.settings,
|
||||
viewMenuOpen: state.chatViewMenuOpen,
|
||||
onSettingsChange: state.applySettings,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Chat-owned composer display controls: the View menu plus model controls.
|
||||
import { html } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
import type { UiSettings } from "../../../app/settings.ts";
|
||||
import { icons } from "../../../components/icons.ts";
|
||||
import "../../../components/tooltip.ts";
|
||||
@@ -11,6 +11,7 @@ type ChatControlsProps = {
|
||||
paneId: string;
|
||||
model: ChatModelControlsProps;
|
||||
onboarding: boolean;
|
||||
preferencesBrowserOnly: boolean;
|
||||
settings: UiSettings;
|
||||
viewMenuOpen: boolean;
|
||||
onSettingsChange: (patch: Partial<UiSettings>) => void;
|
||||
@@ -105,6 +106,11 @@ export function renderChatControls(props: ChatControlsProps) {
|
||||
</wa-dropdown-item>
|
||||
`,
|
||||
)}
|
||||
${props.preferencesBrowserOnly
|
||||
? html`<div class="chat-view-menu__provenance" role="note">
|
||||
${t("quickSettings.personal.browserOnly")}
|
||||
</div>`
|
||||
: nothing}
|
||||
</wa-dropdown>
|
||||
</openclaw-tooltip>
|
||||
</div>
|
||||
|
||||
@@ -2569,6 +2569,15 @@ openclaw-chat-video-player {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-view-menu__provenance {
|
||||
margin: 4px 6px 2px;
|
||||
padding-top: 7px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--border) 72%, transparent);
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.agent-chat__stt-interim {
|
||||
padding: 10px 14px 0;
|
||||
color: var(--muted);
|
||||
|
||||
@@ -2346,6 +2346,13 @@ wa-dropdown.sidebar-customize-menu::part(menu) {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.sidebar-customize-menu__provenance {
|
||||
margin: 0 10px 6px;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.sidebar-customize-menu__group-title {
|
||||
margin: 6px 4px 2px;
|
||||
padding: 7px 6px 4px;
|
||||
|
||||
Reference in New Issue
Block a user