feat(ui): follow the spec for preset versions, tray problems and skills storage

Presets: name the Tencent family "Tencent Cloud" (腾讯云), since it also
covers pay-as-you-go plans in some apps, and let a family list its
versions in design order (Kimi by region, Tencent Lite before Pro).
Families without an order keep the preset file order.

Tray: a failed tray switch no longer expires after ten minutes. It stays
until the app switches successfully or its page is opened in the main
window, reported through the new tray_app_page_seen command.

Skills storage and sync: turn the sheet into the spec's dialog with a
real three-way sync method (auto, symlink only, copy only; auto was
shown as symlink before), radio buttons for where the main copy lives,
the real CC Switch skills path with an Open button, and a visible line
explaining why "Move and switch" is unavailable.
This commit is contained in:
Jason committed 2026-10-03 23:28:58 +08:00
1 parent e9170adb65
commit b7a5ef2885
20 files changed
+727 -413

No files matched your search

+24
View File
@@ -3,6 +3,7 @@ import { http, HttpResponse } from "msw";
import { describe, expect, it, vi } from "vitest";
import {
parseTrayNavigation,
useTrayAppPageSeen,
useTrayNavigation,
} from "@/hooks/useTrayNavigation";
import { server } from "../msw/server";
@@ -88,3 +89,26 @@ describe("useTrayNavigation", () => {
);
});
});
describe("useTrayAppPageSeen", () => {
it("reports the app page when shown and when the window regains focus", async () => {
const seen: unknown[] = [];
server.use(
http.post(`${TAURI_ENDPOINT}/tray_app_page_seen`, async ({ request }) => {
seen.push(await request.json());
return HttpResponse.json(null);
}),
);
const { rerender } = renderHook(
({ app }: { app: "codex" | null }) => useTrayAppPageSeen(app),
{ initialProps: { app: null as "codex" | null } },
);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(seen).toEqual([]);
rerender({ app: "codex" });
await waitFor(() => expect(seen).toEqual([{ appType: "codex" }]));
window.dispatchEvent(new Event("focus"));
await waitFor(() => expect(seen).toHaveLength(2));
});
});