feat(settings): Updates (channel) + Migration sections in Global Settings

- UpdatesSection: pick Stable vs Nightly auto-update channel, persisted to
  ~/.ao/update-settings.json via the new IPC bridge. Closes #2207.
- MigrationSection: drop-in card showing migration status + last report/error
  and a Run/Re-run button hitting the idempotent POST /api/v1/import; works
  even after Don't Migrate. Closes #2205.
- Wire both into the (previously blank) GlobalSettingsForm; cover both with tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Harshit Singh Bhandari 2026-06-27 19:32:19 +05:30
parent 8bae4f6beb
commit 520732a8f7
No known key found for this signature in database
6 changed files with 613 additions and 176 deletions

View File

@ -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(
<QueryClientProvider client={qc}>
<GlobalSettingsForm />
</QueryClientProvider>,
);
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" }));
});
});

View File

@ -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 (
<div className="flex h-full min-h-0 flex-col bg-background text-foreground">
<DashboardSubhead title="Global settings" subtitle="Settings that apply across all projects" />
<div className="min-h-0 flex-1 overflow-y-auto p-[18px]" />
<div className="min-h-0 flex-1 overflow-y-auto p-[18px]">
<div className="mx-auto flex max-w-2xl flex-col gap-4">
<UpdatesSection />
<MigrationSection />
</div>
</div>
</div>
);
}

View File

@ -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<MigrationView> {
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<MigrationStatus, string> = {
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 (
<Card>
<CardHeader>
<CardTitle className="text-[13px]">Migration</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<p className="text-[12px] leading-5 text-muted-foreground">
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.
</p>
<div className="flex flex-col gap-2 text-[12px]">
<Row label="Status">
<span className={statusClass(migration.status)}>{STATUS_LABEL[migration.status]}</span>
</Row>
{formatTime(migration.completedAt || migration.lastAttemptAt) && (
<Row label={completed ? "Completed" : "Last attempt"}>
<span className="text-foreground">{formatTime(migration.completedAt || migration.lastAttemptAt)}</span>
</Row>
)}
{report && (
<Row label="Last report">
<span className="text-foreground">
{report.projectsImported} imported, {report.projectsSkipped} already present
</span>
</Row>
)}
<Row label="Legacy install">
{query.isLoading ? (
<span className="text-passive">Checking</span>
) : available ? (
<span className="font-mono text-[11px] text-foreground">{legacyRoot || "found"}</span>
) : (
<span className="text-passive">None found</span>
)}
</Row>
</div>
{migration.status === "failed" && migration.error && (
<p className="text-[12px] leading-5 text-error">
{migration.error}. Your legacy projects are untouched (nothing is ever deleted).
</p>
)}
{run.isError && (
<p className="text-[12px] leading-5 text-error">
{run.error instanceof Error ? run.error.message : "Migration failed."}
</p>
)}
{run.isSuccess && !run.isPending && <p className="text-[12px] leading-5 text-success">Migration complete.</p>}
<div className="flex items-center gap-3">
<Button
type="button"
variant="primary"
onClick={() => run.mutate()}
disabled={run.isPending || (!available && !completed)}
>
{run.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{buttonLabel}
</Button>
{!available && !query.isLoading && (
<span className="text-[12px] text-passive">Nothing to import from a legacy install.</span>
)}
</div>
</CardContent>
</Card>
);
}
function Row({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="w-28 shrink-0 text-passive">{label}</span>
<span className="min-w-0 flex-1 truncate">{children}</span>
</div>
);
}

View File

@ -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<UpdateSettings>({ enabled: false, channel: "latest", nightlyAck: false });
const [savedAt, setSavedAt] = useState<number | null>(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 (
<Card>
<CardHeader>
<CardTitle className="text-[13px]">Updates</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<Label htmlFor="updatesEnabled" className="text-[12px] text-muted-foreground">
Automatic updates
</Label>
<EnabledSelect id="updatesEnabled" value={form.enabled} onChange={setEnabled} />
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="updateChannel" className="text-[12px] text-muted-foreground">
Update channel
</Label>
<Select value={form.channel} onValueChange={(v) => setChannel(v as UpdateChannel)} disabled={!form.enabled}>
<SelectTrigger id="updateChannel" className="h-8 w-full text-[13px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
{CHANNEL_OPTIONS.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{form.channel === "nightly" && form.enabled && (
<p className="text-[12px] leading-5 text-warning">
Nightly builds are cut every day and can be unstable or lose data. Only use Nightly if you are comfortable
with that.
</p>
)}
<div className="flex items-center gap-3">
<Button type="button" variant="primary" onClick={() => save.mutate(form)} disabled={save.isPending}>
{save.isPending ? "Saving…" : "Save changes"}
</Button>
{save.isError && (
<span className="text-[12px] text-error">
{save.error instanceof Error ? save.error.message : "Save failed"}
</span>
)}
{savedAt && !save.isPending && !save.isError && <span className="text-[12px] text-success">Saved.</span>}
</div>
</CardContent>
</Card>
);
}
function EnabledSelect({ id, value, onChange }: { id: string; value: boolean; onChange: (value: boolean) => void }) {
return (
<Select value={value ? "on" : "off"} onValueChange={(v) => onChange(v === "on")}>
<SelectTrigger id={id} className="h-8 w-full text-[13px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="on">Enabled</SelectItem>
<SelectItem value="off">Disabled</SelectItem>
</SelectContent>
</Select>
);
}

View File

@ -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<FileRouteTypes>();
ShellRoute: ShellRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

View File

@ -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")