mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(ui): preserve Settings navigation selection and workspace fragments (#129888)
* fix(ui): preserve Settings navigation selection and workspace fragments * test(ui): scope mobile session menu proof to active chat pane
This commit is contained in:
committed by
GitHub
parent
ab9b764d68
commit
48e026161e
@@ -23,7 +23,7 @@ type ShellNavigationState = {
|
||||
};
|
||||
|
||||
type ShellSettingsEscapeState = ShellKeyboardState & {
|
||||
lastWorkspaceLocation: { routeId: "usage"; pathname: string; search: string };
|
||||
lastWorkspaceLocation: { routeId: "usage"; pathname: string; search: string; hash: string };
|
||||
navDrawerOpen: boolean;
|
||||
routeState: { routeId: "appearance" };
|
||||
};
|
||||
@@ -115,6 +115,35 @@ describe("OpenClaw native shell", () => {
|
||||
expect(navigate).toHaveBeenCalledWith("appearance", undefined);
|
||||
});
|
||||
|
||||
it("restores the complete prior workspace URL when Escape leaves Settings", () => {
|
||||
const navigate = vi.fn();
|
||||
const shell = document.createElement(
|
||||
"openclaw-app-shell",
|
||||
) as unknown as ShellSettingsEscapeState;
|
||||
shell.runtime = {
|
||||
context: {
|
||||
navigate,
|
||||
overlays: { snapshot: { devicePairSetupOpen: false } },
|
||||
} as unknown as ApplicationContext,
|
||||
};
|
||||
shell.lastWorkspaceLocation = {
|
||||
routeId: "usage",
|
||||
pathname: "/usage",
|
||||
search: "?agent=main",
|
||||
hash: "#queue",
|
||||
};
|
||||
shell.navDrawerOpen = false;
|
||||
shell.routeState = { routeId: "appearance" };
|
||||
|
||||
shell.handleDocumentKeydown(new KeyboardEvent("keydown", { key: "Escape", cancelable: true }));
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith("usage", {
|
||||
pathname: "/usage",
|
||||
search: "?agent=main",
|
||||
hash: "#queue",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the raw config editor unchanged when Escape is pressed", () => {
|
||||
const navigate = vi.fn();
|
||||
const shell = document.createElement(
|
||||
@@ -126,7 +155,7 @@ describe("OpenClaw native shell", () => {
|
||||
overlays: { snapshot: { devicePairSetupOpen: false } },
|
||||
} as unknown as ApplicationContext,
|
||||
};
|
||||
shell.lastWorkspaceLocation = { routeId: "usage", pathname: "/usage", search: "" };
|
||||
shell.lastWorkspaceLocation = { routeId: "usage", pathname: "/usage", search: "", hash: "" };
|
||||
shell.navDrawerOpen = false;
|
||||
shell.routeState = { routeId: "appearance" };
|
||||
const rawField = document.body.appendChild(document.createElement("label"));
|
||||
@@ -162,7 +191,7 @@ describe("OpenClaw native shell", () => {
|
||||
overlays: { snapshot: { devicePairSetupOpen: false } },
|
||||
} as unknown as ApplicationContext,
|
||||
};
|
||||
shell.lastWorkspaceLocation = { routeId: "usage", pathname: "/usage", search: "" };
|
||||
shell.lastWorkspaceLocation = { routeId: "usage", pathname: "/usage", search: "", hash: "" };
|
||||
shell.navDrawerOpen = false;
|
||||
shell.routeState = { routeId: "appearance" };
|
||||
const container = document.body.appendChild(document.createElement("div"));
|
||||
|
||||
@@ -178,7 +178,7 @@ class OpenClawShell
|
||||
readonly navigationSidebar = document.createElement(APP_SIDEBAR_TAG) as AppSidebarElement;
|
||||
// Where "Back to app" / Escape leaves the settings takeover; falls back to
|
||||
// chat (the app default route) when settings was the entry point.
|
||||
lastWorkspaceLocation: { routeId: RouteId; pathname: string; search: string } | null = null;
|
||||
lastWorkspaceLocation: ShellNavigationHost["lastWorkspaceLocation"] = null;
|
||||
custodianMinimizeRequestId = 0;
|
||||
lastConcreteRouteId: RouteId | undefined;
|
||||
agentsListClient: GatewayBrowserClient | null = null;
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface ShellNavigationHost {
|
||||
readonly context: ApplicationContext<RouteId> | undefined;
|
||||
activeSessionKey: string;
|
||||
routeState: ShellRouteState;
|
||||
lastWorkspaceLocation: { routeId: RouteId; pathname: string; search: string } | null;
|
||||
lastWorkspaceLocation: ({ routeId: RouteId } & Required<ApplicationNavigationOptions>) | null;
|
||||
custodianMinimizeRequestId: number;
|
||||
lastConcreteRouteId: RouteId | undefined;
|
||||
didConsiderNativeRouteRestore: boolean;
|
||||
@@ -260,6 +260,7 @@ export class ShellNavigationOwner {
|
||||
routeId: routeState.routeId,
|
||||
pathname: routeState.location?.pathname ?? "",
|
||||
search: routeState.location?.search ?? "",
|
||||
hash: routeState.location?.hash ?? "",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -281,10 +282,8 @@ export class ShellNavigationOwner {
|
||||
exitSettings(): void {
|
||||
const previous = this.host.lastWorkspaceLocation;
|
||||
if (previous) {
|
||||
this.navigate(previous.routeId, {
|
||||
pathname: previous.pathname,
|
||||
...(previous.search ? { search: previous.search } : {}),
|
||||
});
|
||||
const { routeId, ...location } = previous;
|
||||
this.navigate(routeId, location);
|
||||
return;
|
||||
}
|
||||
this.navigate("chat");
|
||||
|
||||
@@ -183,7 +183,9 @@ describe("settings sidebar search", () => {
|
||||
),
|
||||
].map((item) => item.textContent?.trim());
|
||||
expect(resultLabels).toEqual(["MCP", "Appearance", "Language"]);
|
||||
expect(container.querySelector(".settings-sidebar__item--active")).toBeNull();
|
||||
const active = container.querySelector(".settings-sidebar__item--active");
|
||||
expect(active?.textContent).toContain("Appearance");
|
||||
expect(active?.getAttribute("aria-current")).toBe("page");
|
||||
|
||||
const language = container.querySelector<HTMLAnchorElement>(
|
||||
'.settings-sidebar__subitem[href="/settings/appearance?section=__appearance__#settings-language"]',
|
||||
|
||||
@@ -171,8 +171,7 @@ function filterSettingsNavigationGroups(
|
||||
}
|
||||
|
||||
function renderItem(props: SettingsSidebarProps, routeId: RouteId, label?: string) {
|
||||
const active =
|
||||
!props.searchQuery && settingsNavigationOwnerRoute(props.activeRouteId) === routeId;
|
||||
const active = settingsNavigationOwnerRoute(props.activeRouteId) === routeId;
|
||||
return html`
|
||||
<a
|
||||
href=${pathForRoute(routeId, props.basePath)}
|
||||
|
||||
@@ -172,9 +172,10 @@ suite.define(() => {
|
||||
sessionKey,
|
||||
});
|
||||
await page.goto(controlUiSessionUrl(suite.server.baseUrl, sessionKey));
|
||||
await page.getByText("Mobile session menu proof.", { exact: true }).waitFor();
|
||||
const activePane = page.locator("openclaw-chat-pane.chat-pane-cache__pane--active");
|
||||
await activePane.getByText("Mobile session menu proof.", { exact: true }).waitFor();
|
||||
|
||||
const menuTrigger = page.getByRole("button", {
|
||||
const menuTrigger = activePane.getByRole("button", {
|
||||
name: "Actions for Terminal continuation",
|
||||
});
|
||||
await menuTrigger.press("Enter");
|
||||
|
||||
@@ -471,9 +471,15 @@ suite.define(() => {
|
||||
await settingsSearch.fill("channel");
|
||||
await captureSettingsSidebarProof(settingsSidebar, "01e-settings-search-route.png");
|
||||
await holdUiProof(page);
|
||||
await settingsSidebar.getByRole("link", { name: "Channels" }).first().click();
|
||||
const channelsResult = settingsSidebar.getByRole("link", { name: "Channels" }).first();
|
||||
await channelsResult.click();
|
||||
await expect.poll(() => new URL(page.url()).pathname).toBe("/settings/channels");
|
||||
await expect.poll(() => settingsSearch.inputValue()).toBe("channel");
|
||||
await captureSettingsSidebarProof(
|
||||
settingsSidebar,
|
||||
`settings-navigation-${process.env.OPENCLAW_UI_PROOF_LABEL ?? "current"}.png`,
|
||||
);
|
||||
await expect.poll(() => channelsResult.getAttribute("aria-current")).toBe("page");
|
||||
await captureSettingsSidebarProof(settingsSidebar, "01f-settings-search-navigated.png");
|
||||
await holdUiProof(page);
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
Reference in New Issue
Block a user