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:
parent
8bae4f6beb
commit
520732a8f7
|
|
@ -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" }));
|
||||
});
|
||||
});
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue