mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-26 01:34:51 +08:00
feat(ui): move to a sidebar shell with global pages and grouped settings
Replace the header toolbar and icon-only app switcher with the v7 shell: - Sidebar (200px, collapses to a 72px icon rail under 960px, Cmd/Ctrl+\ toggles and remembers the choice): the app list with mode tags and "needs attention" dots, then Usage (today's cost), Accounts (re-login dot), MCP, Skills, Prompts and Sessions pinned to the bottom, and an Apps / Settings footer. Settings swaps the sidebar for its own directory. - Every screen gets a 52px page header; Linux in-app window controls sit at its far right. macOS traffic lights move down to centre in the 44px sidebar top bar. - Usage, Accounts, MCP, Skills, Prompts (with an app picker) and Sessions become global pages instead of settings tabs or per-app toolbar buttons. OpenClaw and Hermes switch to their own pages with a segmented control; OpenClaw's env, tools and agents merge into one Config page. - New Apps page: per-app install / upgrade with path and install source, batch upgrade, conflict details, manual commands, and the "show in sidebar" switch that used to live in settings. Tool management moves out of About into a shared hook; a read-only list_tool_installations command feeds the page so it does not share calls with the upgrade preflight. - Settings regroup into General, App config, Local routing, Network, Data and About, using row/card layout. Path settings save per section; switches still save immediately. Skills storage and sync move to a sheet on the Skills page. - New opt-in "check app updates on startup" setting (off by default): when on, a background check puts a neutral dot on Apps. - Switch adopts the v7 track and action colour; env-conflict banner joins the page flow instead of overlaying the header. The old route / Stack / failover toggles stay in the provider page header until the mode tabs land.
This commit is contained in:
1 parent
947a8a8b06
commit
40aa2864a2
53 files changed
+5574
-4295
No files matched your search
@@ -1,6 +1,12 @@
|
||||
import { Suspense, type ComponentType } from "react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
fireEvent,
|
||||
within,
|
||||
} from "@testing-library/react";
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { providersApi } from "@/lib/api/providers";
|
||||
@@ -132,15 +138,8 @@ vi.mock("@/components/ConfirmDialog", () => ({
|
||||
) : null,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/AppSwitcher", () => ({
|
||||
AppSwitcher: ({ activeApp, onSwitch }: any) => (
|
||||
<div data-testid="app-switcher">
|
||||
<span>{activeApp}</span>
|
||||
<button onClick={() => onSwitch("claude")}>switch-claude</button>
|
||||
<button onClick={() => onSwitch("codex")}>switch-codex</button>
|
||||
<button onClick={() => onSwitch("openclaw")}>switch-openclaw</button>
|
||||
</div>
|
||||
),
|
||||
vi.mock("@/contexts/UpdateContext", () => ({
|
||||
useUpdate: () => ({ hasUpdate: false, updateInfo: null }),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/skills/UnifiedSkillsPanel", async () => {
|
||||
@@ -169,12 +168,6 @@ vi.mock("@/components/skills/UnifiedSkillsPanel", async () => {
|
||||
return { default: MockUnifiedSkillsPanel };
|
||||
});
|
||||
|
||||
vi.mock("@/components/UpdateBadge", () => ({
|
||||
UpdateBadge: ({ onClick }: any) => (
|
||||
<button onClick={onClick}>update-badge</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/mcp/McpPanel", () => ({
|
||||
default: ({ open, onOpenChange }: any) =>
|
||||
open ? (
|
||||
@@ -186,6 +179,14 @@ vi.mock("@/components/mcp/McpPanel", () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
/** 侧栏里的应用行(v7 侧栏取代了原来页头的应用切换器) */
|
||||
const sidebarApp = (name: string) =>
|
||||
// 首次启动提示是模态对话框,会把侧栏标成 aria-hidden,所以带上 hidden
|
||||
within(document.querySelector("nav") as HTMLElement).getByRole("button", {
|
||||
name,
|
||||
hidden: true,
|
||||
});
|
||||
|
||||
const renderApp = (AppComponent: ComponentType) => {
|
||||
const client = new QueryClient();
|
||||
return render(
|
||||
@@ -218,7 +219,7 @@ describe("App integration with MSW", () => {
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText("switch-codex"));
|
||||
fireEvent.click(sidebarApp("Codex"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("provider-list").textContent).toContain(
|
||||
"codex-1",
|
||||
@@ -291,7 +292,7 @@ describe("App integration with MSW", () => {
|
||||
providerScrollContainer!.scrollTop = 640;
|
||||
providerScrollContainer!.scrollLeft = 24;
|
||||
|
||||
fireEvent.click(screen.getByText("switch-codex"));
|
||||
fireEvent.click(sidebarApp("Codex"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("provider-list").textContent).toContain(
|
||||
@@ -369,7 +370,7 @@ describe("App integration with MSW", () => {
|
||||
const { default: App } = await import("@/App");
|
||||
renderApp(App);
|
||||
|
||||
fireEvent.click(screen.getByText("switch-openclaw"));
|
||||
fireEvent.click(sidebarApp("OpenClaw"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("provider-list").textContent).toContain(
|
||||
@@ -629,7 +630,7 @@ describe("App integration with MSW", () => {
|
||||
const { default: App } = await import("@/App");
|
||||
renderApp(App);
|
||||
|
||||
fireEvent.click(screen.getByText("switch-openclaw"));
|
||||
fireEvent.click(sidebarApp("OpenClaw"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("provider-list").textContent).toContain(
|
||||
|
||||
Reference in new issue
Block a user