mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 02:45:38 -06:00
cca5b14785
* feat(ui): path-based session and dashboard URLs * docs(ui): document control UI URLs * fix(ui): finalize session path routing * feat(ui): anchor session URLs on stable keys * docs(ui): clarify stable session URL identity * fix(ui): resolve session prefixes with full prefix and pagination Query sessions.list with the full supplied prefix instead of an eight-char truncation, and paginate up to a bounded page count so longer disambiguation links resolve instead of being reported ambiguous. Zero strict-prefix matches now fall through to literal-key resolution rather than rendering an empty ambiguity view. Also document the ~dot/~dotdot segment escape: peer ids reach session keys trimmed and lowercased only (src/routing/session-key.ts), so a literal '.' or '..' segment is reachable and browsers would normalize it away. * fix(ui): synchronize committed session routes * test(ui): split native shell host coverage * fix(ui): thread configured mainKey through session URL builders Reserved-set disambiguation needs the operator-configured mainKey at runtime, so thread it from agentsList through every session path builder and the ClickClack control URL. Unambiguous non-hex single-segment rests now resolve literally while short-id-shaped rests still fail closed, which restores ClickClack channel compatibility detection and control-link reconciliation. Also bound prefix-resolution retries, preserve catalog thread identity, and keep draft state on ambiguous candidate links. * fix(ui): repair session URL CI integration * perf(ui): lazy-load session route resolution * perf(ui): isolate session prefix resolution * perf(ui): defer session path parsing * perf(ui): defer session navigation startup * fix(ui): preserve first-run and literal session navigation * fix(ui): satisfy session routing type and export gates * fix(clickclack): preserve unscoped control link agent * style(ui): satisfy chat page line limit * refactor(ui): move chat page helpers to owning modules * fix(ui): preserve destination session route identity * fix(ui): preserve agent identity in session routes * fix(ui): escape dots in literal session path segments encodeURIComponent leaves periods intact, so a literal key segment like channel:release.js produced /chat/main/channel/release.js. In-app navigation is intercepted by the SPA, but a refresh, an external link, or a ClickClack link would be served as a static asset request and never reach the app. pathForWorkboardBoard already escapes dots for this reason; mirror it in both the session URL contract and the ClickClack encoder, route the agent id through the same segment encoder, and pin the case in both shared vector tables. * fix(ui): stop bootstrap after teardown race * refactor(ui): centralize session navigation targets * fix(ui): consume bootstrap teardown abort * fix(ui): canonicalize configured main session routes * fix(ui): preserve distinct session references * fix(ui): redirect released session query links * fix(ui): make bootstrap teardown abort-safe
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
import { render } from "lit";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import type { GatewayBrowserClient } from "../api/gateway.ts";
|
|
import { buildCatalogSessionKey } from "../lib/sessions/catalog-key.ts";
|
|
import {
|
|
createContext,
|
|
createGateway,
|
|
createSessions,
|
|
TWO_AGENTS,
|
|
} from "../test-helpers/app-sidebar.ts";
|
|
import {
|
|
SidebarMenusController,
|
|
type SidebarMenusControllerHost,
|
|
} from "./sidebar-menus-controller.ts";
|
|
|
|
describe("SidebarMenusController session routes", () => {
|
|
it("keeps the current catalog session when switching either face", () => {
|
|
const sessionKey = buildCatalogSessionKey({
|
|
catalogId: "claude",
|
|
hostId: "gateway:local",
|
|
threadId: "thread-1",
|
|
});
|
|
const context = createContext(
|
|
createGateway({} as GatewayBrowserClient),
|
|
createSessions("main", [sessionKey]),
|
|
TWO_AGENTS,
|
|
);
|
|
Object.assign(context, { basePath: "" });
|
|
const onNavigate = vi.fn();
|
|
const host = {
|
|
activeRouteId: "chat",
|
|
activeWorkboardBoardId: "",
|
|
addController: vi.fn(),
|
|
basePath: "",
|
|
enabledRouteIds: ["chat", "dashboard"],
|
|
getRouteSessionKey: () => sessionKey,
|
|
onNavigate,
|
|
requestUpdate: vi.fn(),
|
|
sessionDataContext: context,
|
|
terminalAvailable: false,
|
|
} as unknown as SidebarMenusControllerHost;
|
|
const controller = new SidebarMenusController(host);
|
|
const container = document.createElement("div");
|
|
|
|
render(controller.renderRoute("chat"), container);
|
|
const chat = container.querySelector<HTMLAnchorElement>("a");
|
|
expect(chat?.getAttribute("href")).toBe(
|
|
"/chat/main?catalog=claude&host=gateway%3Alocal&thread=thread-1",
|
|
);
|
|
chat?.click();
|
|
expect(onNavigate).toHaveBeenLastCalledWith("chat", {
|
|
pathname: "/chat/main",
|
|
search: "?catalog=claude&host=gateway%3Alocal&thread=thread-1",
|
|
});
|
|
|
|
render(controller.renderRoute("dashboard"), container);
|
|
const dashboard = container.querySelector<HTMLAnchorElement>("a");
|
|
expect(dashboard?.getAttribute("href")).toBe(
|
|
"/dashboard/main?catalog=claude&host=gateway%3Alocal&thread=thread-1",
|
|
);
|
|
dashboard?.click();
|
|
expect(onNavigate).toHaveBeenLastCalledWith("dashboard", {
|
|
pathname: "/dashboard/main",
|
|
search: "?catalog=claude&host=gateway%3Alocal&thread=thread-1",
|
|
});
|
|
});
|
|
});
|