diff --git a/frontend/src/renderer/components/GlobalSettingsForm.test.tsx b/frontend/src/renderer/components/GlobalSettingsForm.test.tsx new file mode 100644 index 000000000..b33a38810 --- /dev/null +++ b/frontend/src/renderer/components/GlobalSettingsForm.test.tsx @@ -0,0 +1,111 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { GlobalSettingsForm } from "./GlobalSettingsForm"; + +const { getMock, postMock, getMigration, setMigration, getUpdate, setUpdate } = vi.hoisted(() => ({ + getMock: vi.fn(), + postMock: vi.fn(), + getMigration: vi.fn(), + setMigration: vi.fn(), + getUpdate: vi.fn(), + setUpdate: vi.fn(), +})); + +vi.mock("../lib/api-client", () => ({ + apiClient: { GET: getMock, POST: postMock }, + apiErrorMessage: (e: unknown, fb = "Request failed") => + e instanceof Error ? e.message : ((e as { message?: string })?.message ?? fb), +})); +vi.mock("../lib/bridge", () => ({ + aoBridge: { + appState: { getMigration, setMigration }, + updateSettings: { get: getUpdate, set: setUpdate }, + }, +})); + +function renderForm() { + const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + render( + + + , + ); + return qc; +} + +beforeEach(() => { + for (const m of [getMock, postMock, getMigration, setMigration, getUpdate, setUpdate]) m.mockReset(); + getMigration.mockResolvedValue({ status: "pending" }); + getMock.mockResolvedValue({ data: { available: true, legacyRoot: "/home/u/.agent-orchestrator" }, error: undefined }); + postMock.mockResolvedValue({ data: { report: { projectsImported: 2, projectsSkipped: 1 } }, error: undefined }); + setMigration.mockResolvedValue(undefined); + getUpdate.mockResolvedValue({ enabled: true, channel: "latest", nightlyAck: false }); + setUpdate.mockResolvedValue(undefined); +}); + +describe("GlobalSettingsForm", () => { + it("renders the Updates and Migration sections", async () => { + renderForm(); + expect(await screen.findByText("Updates")).toBeInTheDocument(); + expect(screen.getByText("Migration")).toBeInTheDocument(); + }); + + it("shows the nightly warning and saves the loaded channel", async () => { + getUpdate.mockResolvedValue({ enabled: true, channel: "nightly", nightlyAck: true }); + renderForm(); + expect(await screen.findByText(/Nightly builds are cut every day/i)).toBeInTheDocument(); + await userEvent.click(screen.getByRole("button", { name: "Save changes" })); + await waitFor(() => + expect(setUpdate).toHaveBeenCalledWith(expect.objectContaining({ channel: "nightly", enabled: true })), + ); + }); + + it("hides the nightly warning on the stable channel", async () => { + renderForm(); + await screen.findByText("Updates"); + expect(screen.queryByText(/Nightly builds are cut every day/i)).not.toBeInTheDocument(); + }); + + it("shows migration status and the available legacy root", async () => { + renderForm(); + expect(await screen.findByText("Not migrated yet")).toBeInTheDocument(); + expect(await screen.findByText("/home/u/.agent-orchestrator")).toBeInTheDocument(); + }); + + it("Run migration imports and marks completed", async () => { + renderForm(); + const btn = await screen.findByRole("button", { name: "Run migration" }); + await userEvent.click(btn); + await waitFor(() => expect(postMock).toHaveBeenCalledWith("/api/v1/import")); + expect(setMigration).toHaveBeenCalledWith(expect.objectContaining({ status: "completed" })); + expect(await screen.findByText("Migration complete.")).toBeInTheDocument(); + }); + + it("lets a declined user re-run the migration", async () => { + getMigration.mockResolvedValue({ status: "declined", lastAttemptAt: "2026-06-01T00:00:00.000Z" }); + renderForm(); + expect(await screen.findByText("Declined")).toBeInTheDocument(); + const btn = await screen.findByRole("button", { name: "Run migration" }); + expect(btn).toBeEnabled(); + await userEvent.click(btn); + await waitFor(() => expect(postMock).toHaveBeenCalledWith("/api/v1/import")); + }); + + it("disables Run when no legacy install is available", async () => { + getMock.mockResolvedValue({ data: { available: false, legacyRoot: "" }, error: undefined }); + renderForm(); + expect(await screen.findByText("None found")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Run migration" })).toBeDisabled(); + }); + + it("a failed import surfaces the error and marks failed", async () => { + postMock.mockResolvedValue({ data: undefined, error: { message: "disk full" } }); + renderForm(); + const btn = await screen.findByRole("button", { name: "Run migration" }); + await userEvent.click(btn); + expect(await screen.findByText(/disk full/i)).toBeInTheDocument(); + expect(setMigration).toHaveBeenCalledWith(expect.objectContaining({ status: "failed", error: "disk full" })); + }); +}); diff --git a/frontend/src/renderer/components/GlobalSettingsForm.tsx b/frontend/src/renderer/components/GlobalSettingsForm.tsx index 89e280e93..bdb6ddf32 100644 --- a/frontend/src/renderer/components/GlobalSettingsForm.tsx +++ b/frontend/src/renderer/components/GlobalSettingsForm.tsx @@ -1,12 +1,20 @@ import { DashboardSubhead } from "./DashboardSubhead"; +import { MigrationSection } from "./MigrationSection"; +import { UpdatesSection } from "./UpdatesSection"; -// App-wide settings, shown from the sidebar when no project is selected. The -// body is intentionally empty for now; it will be filled in incrementally. +// App-wide settings, shown from the sidebar when no project is selected. Each +// section is a self-contained card: Updates (auto-update channel, #2207) and +// Migration (re-run the legacy-AO import, #2205). export function GlobalSettingsForm() { return (
-
+
+
+ + +
+
); } diff --git a/frontend/src/renderer/components/MigrationSection.tsx b/frontend/src/renderer/components/MigrationSection.tsx new file mode 100644 index 000000000..04f32fd2e --- /dev/null +++ b/frontend/src/renderer/components/MigrationSection.tsx @@ -0,0 +1,185 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { Loader2 } from "lucide-react"; +import { apiClient, apiErrorMessage } from "../lib/api-client"; +import { aoBridge } from "../lib/bridge"; +import { migrationOfferQueryKey } from "../hooks/useMigrationOffer"; +import { workspaceQueryKey } from "../hooks/useWorkspaceQuery"; +import type { MigrationState, MigrationStatus } from "../../main/app-state"; +import { Button } from "./ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; + +export const migrationSettingsQueryKey = ["migration-settings"] as const; + +interface MigrationView { + migration: MigrationState; + available: boolean; + legacyRoot: string; +} + +// fetchMigrationSettings reads the persisted decision (app marker) and asks the +// daemon whether legacy data is present. Unlike useMigrationOffer it never +// short-circuits on a terminal status: Settings always shows the full state so a +// user who declined or already completed can re-run. A 501/unreachable daemon +// resolves to "not available", never an error. +async function fetchMigrationSettings(): Promise { + const migration = await aoBridge.appState.getMigration(); + const { data, error } = await apiClient.GET("/api/v1/import"); + return { + migration, + available: !error && (data?.available ?? false), + legacyRoot: data?.legacyRoot ?? "", + }; +} + +const STATUS_LABEL: Record = { + pending: "Not migrated yet", + completed: "Completed", + declined: "Declined", + failed: "Last attempt failed", +}; + +function statusClass(status: MigrationStatus): string { + switch (status) { + case "completed": + return "text-success"; + case "failed": + return "text-error"; + default: + return "text-muted-foreground"; + } +} + +function formatTime(iso?: string): string { + if (!iso) return ""; + const d = new Date(iso); + return Number.isNaN(d.getTime()) ? "" : d.toLocaleString(); +} + +// MigrationSection is a drop-in Settings card for re-running the legacy-AO +// import. It reads the persisted migration decision + the daemon's availability, +// shows the last report/error, and exposes a Run / Re-run button that calls the +// idempotent POST /api/v1/import (safe even when completed/declined/failed). +// Issue #2205. +export function MigrationSection() { + const queryClient = useQueryClient(); + const query = useQuery({ + queryKey: migrationSettingsQueryKey, + queryFn: fetchMigrationSettings, + }); + + const run = useMutation({ + mutationFn: async () => { + const nowIso = () => new Date().toISOString(); + const { data, error } = await apiClient.POST("/api/v1/import"); + if (error) { + const msg = apiErrorMessage(error); + await aoBridge.appState.setMigration({ status: "failed", lastAttemptAt: nowIso(), error: msg }); + throw new Error(msg); + } + const report = data?.report; + await aoBridge.appState.setMigration({ + status: "completed", + lastAttemptAt: nowIso(), + completedAt: nowIso(), + report: report + ? { projectsImported: report.projectsImported, projectsSkipped: report.projectsSkipped } + : undefined, + }); + }, + onSettled: () => { + void queryClient.invalidateQueries({ queryKey: migrationSettingsQueryKey }); + void queryClient.invalidateQueries({ queryKey: migrationOfferQueryKey }); + void queryClient.invalidateQueries({ queryKey: workspaceQueryKey }); + }, + }); + + const migration = query.data?.migration ?? { status: "pending" as MigrationStatus }; + const available = query.data?.available ?? false; + const legacyRoot = query.data?.legacyRoot ?? ""; + const report = migration.report; + const completed = migration.status === "completed"; + const buttonLabel = run.isPending + ? "Running…" + : completed + ? "Re-run migration" + : migration.status === "failed" + ? "Retry migration" + : "Run migration"; + + return ( + + + Migration + + +

+ Import projects and orchestrator sessions from an earlier Agent Orchestrator install. Your old files are + never modified, and this is safe to run more than once. +

+ +
+ + {STATUS_LABEL[migration.status]} + + {formatTime(migration.completedAt || migration.lastAttemptAt) && ( + + {formatTime(migration.completedAt || migration.lastAttemptAt)} + + )} + {report && ( + + + {report.projectsImported} imported, {report.projectsSkipped} already present + + + )} + + {query.isLoading ? ( + Checking… + ) : available ? ( + {legacyRoot || "found"} + ) : ( + None found + )} + +
+ + {migration.status === "failed" && migration.error && ( +

+ {migration.error}. Your legacy projects are untouched (nothing is ever deleted). +

+ )} + {run.isError && ( +

+ {run.error instanceof Error ? run.error.message : "Migration failed."} +

+ )} + {run.isSuccess && !run.isPending &&

Migration complete.

} + +
+ + {!available && !query.isLoading && ( + Nothing to import from a legacy install. + )} +
+
+
+ ); +} + +function Row({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+ {label} + {children} +
+ ); +} diff --git a/frontend/src/renderer/components/UpdatesSection.tsx b/frontend/src/renderer/components/UpdatesSection.tsx new file mode 100644 index 000000000..b4e1ab760 --- /dev/null +++ b/frontend/src/renderer/components/UpdatesSection.tsx @@ -0,0 +1,124 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; +import { aoBridge } from "../lib/bridge"; +import type { UpdateChannel, UpdateSettings } from "../../main/update-settings"; +import { Button } from "./ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; +import { Label } from "./ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; + +export const updateSettingsQueryKey = ["update-settings"] as const; + +const CHANNEL_OPTIONS: { value: UpdateChannel; label: string }[] = [ + { value: "latest", label: "Stable (latest release)" }, + { value: "nightly", label: "Nightly (pre-release)" }, +]; + +// UpdatesSection is the Global Settings card for the desktop auto-update channel +// (issue #2207). It reads/writes ~/.ao/update-settings.json via the main process +// (the same file auto-updater.ts consumes), letting a user pick Stable vs Nightly. +// Changes apply on the next launch / update check. +export function UpdatesSection() { + const queryClient = useQueryClient(); + const query = useQuery({ + queryKey: updateSettingsQueryKey, + queryFn: () => aoBridge.updateSettings.get(), + }); + + const [form, setForm] = useState({ enabled: false, channel: "latest", nightlyAck: false }); + const [savedAt, setSavedAt] = useState(null); + + // Seed the form once settings load (and on refetch). Keying off the loaded + // value keeps local edits responsive without a controlled-from-query loop. + useEffect(() => { + if (query.data) setForm(query.data); + }, [query.data]); + + const save = useMutation({ + mutationFn: async (next: UpdateSettings) => { + await aoBridge.updateSettings.set(next); + }, + onSuccess: () => { + setSavedAt(Date.now()); + void queryClient.invalidateQueries({ queryKey: updateSettingsQueryKey }); + }, + }); + + const setEnabled = (enabled: boolean) => { + setSavedAt(null); + setForm((f) => ({ ...f, enabled })); + }; + const setChannel = (channel: UpdateChannel) => { + setSavedAt(null); + // Selecting Nightly in Settings is itself the acknowledgement of the + // instability warning shown below; Stable clears it. + setForm((f) => ({ ...f, channel, nightlyAck: channel === "nightly" })); + }; + + return ( + + + Updates + + +
+ + +
+ +
+ + +
+ + {form.channel === "nightly" && form.enabled && ( +

+ Nightly builds are cut every day and can be unstable or lose data. Only use Nightly if you are comfortable + with that. +

+ )} + +
+ + {save.isError && ( + + {save.error instanceof Error ? save.error.message : "Save failed"} + + )} + {savedAt && !save.isPending && !save.isError && Saved.} +
+
+
+ ); +} + +function EnabledSelect({ id, value, onChange }: { id: string; value: boolean; onChange: (value: boolean) => void }) { + return ( + + ); +} diff --git a/frontend/src/renderer/routeTree.gen.ts b/frontend/src/renderer/routeTree.gen.ts index 26cb7f148..5add28aa8 100644 --- a/frontend/src/renderer/routeTree.gen.ts +++ b/frontend/src/renderer/routeTree.gen.ts @@ -8,204 +8,209 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { Route as rootRouteImport } from "./routes/__root"; -import { Route as ShellRouteImport } from "./routes/_shell"; -import { Route as ShellIndexRouteImport } from "./routes/_shell.index"; -import { Route as ShellSettingsRouteImport } from "./routes/_shell.settings"; -import { Route as ShellPrsRouteImport } from "./routes/_shell.prs"; -import { Route as ShellSessionsSessionIdRouteImport } from "./routes/_shell.sessions.$sessionId"; -import { Route as ShellProjectsProjectIdRouteImport } from "./routes/_shell.projects.$projectId"; -import { Route as ShellProjectsProjectIdSettingsRouteImport } from "./routes/_shell.projects.$projectId_.settings"; -import { Route as ShellProjectsProjectIdSessionsSessionIdRouteImport } from "./routes/_shell.projects.$projectId_.sessions.$sessionId"; +import { Route as rootRouteImport } from './routes/__root' +import { Route as ShellRouteImport } from './routes/_shell' +import { Route as ShellIndexRouteImport } from './routes/_shell.index' +import { Route as ShellSettingsRouteImport } from './routes/_shell.settings' +import { Route as ShellPrsRouteImport } from './routes/_shell.prs' +import { Route as ShellSessionsSessionIdRouteImport } from './routes/_shell.sessions.$sessionId' +import { Route as ShellProjectsProjectIdRouteImport } from './routes/_shell.projects.$projectId' +import { Route as ShellProjectsProjectIdSettingsRouteImport } from './routes/_shell.projects.$projectId_.settings' +import { Route as ShellProjectsProjectIdSessionsSessionIdRouteImport } from './routes/_shell.projects.$projectId_.sessions.$sessionId' const ShellRoute = ShellRouteImport.update({ - id: "/_shell", - getParentRoute: () => rootRouteImport, -} as any); + id: '/_shell', + getParentRoute: () => rootRouteImport, +} as any) const ShellIndexRoute = ShellIndexRouteImport.update({ - id: "/", - path: "/", - getParentRoute: () => ShellRoute, -} as any); + id: '/', + path: '/', + getParentRoute: () => ShellRoute, +} as any) const ShellSettingsRoute = ShellSettingsRouteImport.update({ - id: "/settings", - path: "/settings", - getParentRoute: () => ShellRoute, -} as any); + id: '/settings', + path: '/settings', + getParentRoute: () => ShellRoute, +} as any) const ShellPrsRoute = ShellPrsRouteImport.update({ - id: "/prs", - path: "/prs", - getParentRoute: () => ShellRoute, -} as any); + id: '/prs', + path: '/prs', + getParentRoute: () => ShellRoute, +} as any) const ShellSessionsSessionIdRoute = ShellSessionsSessionIdRouteImport.update({ - id: "/sessions/$sessionId", - path: "/sessions/$sessionId", - getParentRoute: () => ShellRoute, -} as any); + id: '/sessions/$sessionId', + path: '/sessions/$sessionId', + getParentRoute: () => ShellRoute, +} as any) const ShellProjectsProjectIdRoute = ShellProjectsProjectIdRouteImport.update({ - id: "/projects/$projectId", - path: "/projects/$projectId", - getParentRoute: () => ShellRoute, -} as any); -const ShellProjectsProjectIdSettingsRoute = ShellProjectsProjectIdSettingsRouteImport.update({ - id: "/projects/$projectId_/settings", - path: "/projects/$projectId/settings", - getParentRoute: () => ShellRoute, -} as any); -const ShellProjectsProjectIdSessionsSessionIdRoute = ShellProjectsProjectIdSessionsSessionIdRouteImport.update({ - id: "/projects/$projectId_/sessions/$sessionId", - path: "/projects/$projectId/sessions/$sessionId", - getParentRoute: () => ShellRoute, -} as any); + id: '/projects/$projectId', + path: '/projects/$projectId', + getParentRoute: () => ShellRoute, +} as any) +const ShellProjectsProjectIdSettingsRoute = + ShellProjectsProjectIdSettingsRouteImport.update({ + id: '/projects/$projectId_/settings', + path: '/projects/$projectId/settings', + getParentRoute: () => ShellRoute, + } as any) +const ShellProjectsProjectIdSessionsSessionIdRoute = + ShellProjectsProjectIdSessionsSessionIdRouteImport.update({ + id: '/projects/$projectId_/sessions/$sessionId', + path: '/projects/$projectId/sessions/$sessionId', + getParentRoute: () => ShellRoute, + } as any) export interface FileRoutesByFullPath { - "/": typeof ShellIndexRoute; - "/prs": typeof ShellPrsRoute; - "/settings": typeof ShellSettingsRoute; - "/projects/$projectId": typeof ShellProjectsProjectIdRoute; - "/sessions/$sessionId": typeof ShellSessionsSessionIdRoute; - "/projects/$projectId/settings": typeof ShellProjectsProjectIdSettingsRoute; - "/projects/$projectId/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute; + '/': typeof ShellIndexRoute + '/prs': typeof ShellPrsRoute + '/settings': typeof ShellSettingsRoute + '/projects/$projectId': typeof ShellProjectsProjectIdRoute + '/sessions/$sessionId': typeof ShellSessionsSessionIdRoute + '/projects/$projectId/settings': typeof ShellProjectsProjectIdSettingsRoute + '/projects/$projectId/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute } export interface FileRoutesByTo { - "/prs": typeof ShellPrsRoute; - "/settings": typeof ShellSettingsRoute; - "/": typeof ShellIndexRoute; - "/projects/$projectId": typeof ShellProjectsProjectIdRoute; - "/sessions/$sessionId": typeof ShellSessionsSessionIdRoute; - "/projects/$projectId/settings": typeof ShellProjectsProjectIdSettingsRoute; - "/projects/$projectId/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute; + '/prs': typeof ShellPrsRoute + '/settings': typeof ShellSettingsRoute + '/': typeof ShellIndexRoute + '/projects/$projectId': typeof ShellProjectsProjectIdRoute + '/sessions/$sessionId': typeof ShellSessionsSessionIdRoute + '/projects/$projectId/settings': typeof ShellProjectsProjectIdSettingsRoute + '/projects/$projectId/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute } export interface FileRoutesById { - __root__: typeof rootRouteImport; - "/_shell": typeof ShellRouteWithChildren; - "/_shell/prs": typeof ShellPrsRoute; - "/_shell/settings": typeof ShellSettingsRoute; - "/_shell/": typeof ShellIndexRoute; - "/_shell/projects/$projectId": typeof ShellProjectsProjectIdRoute; - "/_shell/sessions/$sessionId": typeof ShellSessionsSessionIdRoute; - "/_shell/projects/$projectId_/settings": typeof ShellProjectsProjectIdSettingsRoute; - "/_shell/projects/$projectId_/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute; + __root__: typeof rootRouteImport + '/_shell': typeof ShellRouteWithChildren + '/_shell/prs': typeof ShellPrsRoute + '/_shell/settings': typeof ShellSettingsRoute + '/_shell/': typeof ShellIndexRoute + '/_shell/projects/$projectId': typeof ShellProjectsProjectIdRoute + '/_shell/sessions/$sessionId': typeof ShellSessionsSessionIdRoute + '/_shell/projects/$projectId_/settings': typeof ShellProjectsProjectIdSettingsRoute + '/_shell/projects/$projectId_/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath; - fullPaths: - | "/" - | "/prs" - | "/settings" - | "/projects/$projectId" - | "/sessions/$sessionId" - | "/projects/$projectId/settings" - | "/projects/$projectId/sessions/$sessionId"; - fileRoutesByTo: FileRoutesByTo; - to: - | "/prs" - | "/settings" - | "/" - | "/projects/$projectId" - | "/sessions/$sessionId" - | "/projects/$projectId/settings" - | "/projects/$projectId/sessions/$sessionId"; - id: - | "__root__" - | "/_shell" - | "/_shell/prs" - | "/_shell/settings" - | "/_shell/" - | "/_shell/projects/$projectId" - | "/_shell/sessions/$sessionId" - | "/_shell/projects/$projectId_/settings" - | "/_shell/projects/$projectId_/sessions/$sessionId"; - fileRoutesById: FileRoutesById; + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/prs' + | '/settings' + | '/projects/$projectId' + | '/sessions/$sessionId' + | '/projects/$projectId/settings' + | '/projects/$projectId/sessions/$sessionId' + fileRoutesByTo: FileRoutesByTo + to: + | '/prs' + | '/settings' + | '/' + | '/projects/$projectId' + | '/sessions/$sessionId' + | '/projects/$projectId/settings' + | '/projects/$projectId/sessions/$sessionId' + id: + | '__root__' + | '/_shell' + | '/_shell/prs' + | '/_shell/settings' + | '/_shell/' + | '/_shell/projects/$projectId' + | '/_shell/sessions/$sessionId' + | '/_shell/projects/$projectId_/settings' + | '/_shell/projects/$projectId_/sessions/$sessionId' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { - ShellRoute: typeof ShellRouteWithChildren; + ShellRoute: typeof ShellRouteWithChildren } -declare module "@tanstack/react-router" { - interface FileRoutesByPath { - "/_shell": { - id: "/_shell"; - path: ""; - fullPath: "/"; - preLoaderRoute: typeof ShellRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/_shell/": { - id: "/_shell/"; - path: "/"; - fullPath: "/"; - preLoaderRoute: typeof ShellIndexRouteImport; - parentRoute: typeof ShellRoute; - }; - "/_shell/settings": { - id: "/_shell/settings"; - path: "/settings"; - fullPath: "/settings"; - preLoaderRoute: typeof ShellSettingsRouteImport; - parentRoute: typeof ShellRoute; - }; - "/_shell/prs": { - id: "/_shell/prs"; - path: "/prs"; - fullPath: "/prs"; - preLoaderRoute: typeof ShellPrsRouteImport; - parentRoute: typeof ShellRoute; - }; - "/_shell/sessions/$sessionId": { - id: "/_shell/sessions/$sessionId"; - path: "/sessions/$sessionId"; - fullPath: "/sessions/$sessionId"; - preLoaderRoute: typeof ShellSessionsSessionIdRouteImport; - parentRoute: typeof ShellRoute; - }; - "/_shell/projects/$projectId": { - id: "/_shell/projects/$projectId"; - path: "/projects/$projectId"; - fullPath: "/projects/$projectId"; - preLoaderRoute: typeof ShellProjectsProjectIdRouteImport; - parentRoute: typeof ShellRoute; - }; - "/_shell/projects/$projectId_/settings": { - id: "/_shell/projects/$projectId_/settings"; - path: "/projects/$projectId/settings"; - fullPath: "/projects/$projectId/settings"; - preLoaderRoute: typeof ShellProjectsProjectIdSettingsRouteImport; - parentRoute: typeof ShellRoute; - }; - "/_shell/projects/$projectId_/sessions/$sessionId": { - id: "/_shell/projects/$projectId_/sessions/$sessionId"; - path: "/projects/$projectId/sessions/$sessionId"; - fullPath: "/projects/$projectId/sessions/$sessionId"; - preLoaderRoute: typeof ShellProjectsProjectIdSessionsSessionIdRouteImport; - parentRoute: typeof ShellRoute; - }; - } +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_shell': { + id: '/_shell' + path: '' + fullPath: '/' + preLoaderRoute: typeof ShellRouteImport + parentRoute: typeof rootRouteImport + } + '/_shell/': { + id: '/_shell/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof ShellIndexRouteImport + parentRoute: typeof ShellRoute + } + '/_shell/settings': { + id: '/_shell/settings' + path: '/settings' + fullPath: '/settings' + preLoaderRoute: typeof ShellSettingsRouteImport + parentRoute: typeof ShellRoute + } + '/_shell/prs': { + id: '/_shell/prs' + path: '/prs' + fullPath: '/prs' + preLoaderRoute: typeof ShellPrsRouteImport + parentRoute: typeof ShellRoute + } + '/_shell/sessions/$sessionId': { + id: '/_shell/sessions/$sessionId' + path: '/sessions/$sessionId' + fullPath: '/sessions/$sessionId' + preLoaderRoute: typeof ShellSessionsSessionIdRouteImport + parentRoute: typeof ShellRoute + } + '/_shell/projects/$projectId': { + id: '/_shell/projects/$projectId' + path: '/projects/$projectId' + fullPath: '/projects/$projectId' + preLoaderRoute: typeof ShellProjectsProjectIdRouteImport + parentRoute: typeof ShellRoute + } + '/_shell/projects/$projectId_/settings': { + id: '/_shell/projects/$projectId_/settings' + path: '/projects/$projectId/settings' + fullPath: '/projects/$projectId/settings' + preLoaderRoute: typeof ShellProjectsProjectIdSettingsRouteImport + parentRoute: typeof ShellRoute + } + '/_shell/projects/$projectId_/sessions/$sessionId': { + id: '/_shell/projects/$projectId_/sessions/$sessionId' + path: '/projects/$projectId/sessions/$sessionId' + fullPath: '/projects/$projectId/sessions/$sessionId' + preLoaderRoute: typeof ShellProjectsProjectIdSessionsSessionIdRouteImport + parentRoute: typeof ShellRoute + } + } } interface ShellRouteChildren { - ShellPrsRoute: typeof ShellPrsRoute; - ShellSettingsRoute: typeof ShellSettingsRoute; - ShellIndexRoute: typeof ShellIndexRoute; - ShellProjectsProjectIdRoute: typeof ShellProjectsProjectIdRoute; - ShellSessionsSessionIdRoute: typeof ShellSessionsSessionIdRoute; - ShellProjectsProjectIdSettingsRoute: typeof ShellProjectsProjectIdSettingsRoute; - ShellProjectsProjectIdSessionsSessionIdRoute: typeof ShellProjectsProjectIdSessionsSessionIdRoute; + ShellPrsRoute: typeof ShellPrsRoute + ShellSettingsRoute: typeof ShellSettingsRoute + ShellIndexRoute: typeof ShellIndexRoute + ShellProjectsProjectIdRoute: typeof ShellProjectsProjectIdRoute + ShellSessionsSessionIdRoute: typeof ShellSessionsSessionIdRoute + ShellProjectsProjectIdSettingsRoute: typeof ShellProjectsProjectIdSettingsRoute + ShellProjectsProjectIdSessionsSessionIdRoute: typeof ShellProjectsProjectIdSessionsSessionIdRoute } const ShellRouteChildren: ShellRouteChildren = { - ShellPrsRoute: ShellPrsRoute, - ShellSettingsRoute: ShellSettingsRoute, - ShellIndexRoute: ShellIndexRoute, - ShellProjectsProjectIdRoute: ShellProjectsProjectIdRoute, - ShellSessionsSessionIdRoute: ShellSessionsSessionIdRoute, - ShellProjectsProjectIdSettingsRoute: ShellProjectsProjectIdSettingsRoute, - ShellProjectsProjectIdSessionsSessionIdRoute: ShellProjectsProjectIdSessionsSessionIdRoute, -}; + ShellPrsRoute: ShellPrsRoute, + ShellSettingsRoute: ShellSettingsRoute, + ShellIndexRoute: ShellIndexRoute, + ShellProjectsProjectIdRoute: ShellProjectsProjectIdRoute, + ShellSessionsSessionIdRoute: ShellSessionsSessionIdRoute, + ShellProjectsProjectIdSettingsRoute: ShellProjectsProjectIdSettingsRoute, + ShellProjectsProjectIdSessionsSessionIdRoute: + ShellProjectsProjectIdSessionsSessionIdRoute, +} -const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren); +const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren) const rootRouteChildren: RootRouteChildren = { - ShellRoute: ShellRouteWithChildren, -}; -export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes(); + ShellRoute: ShellRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/frontend/src/renderer/test/setup.ts b/frontend/src/renderer/test/setup.ts index 116d2cbb7..face68336 100644 --- a/frontend/src/renderer/test/setup.ts +++ b/frontend/src/renderer/test/setup.ts @@ -141,5 +141,9 @@ if (typeof window !== "undefined") { getMigration: async () => ({ status: "pending" }), setMigration: async () => undefined, }, + updateSettings: { + get: async () => ({ enabled: false, channel: "latest", nightlyAck: false }), + set: async () => undefined, + }, }; } // end if (typeof window !== "undefined")