chore: commit remaining worktree changes

This commit is contained in:
2026-08-26 08:10:37 +02:00
parent ec061c42d4
commit f48196a57f
234 changed files with 146 additions and 61044 deletions
@@ -1,120 +0,0 @@
// frontend/src/shell/NewSessionDialog.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { server } from "../test/msw";
import { canonicalWorkspaceFixture, workspaceRevisionFixture, workspaceSummaryFixture } from "../test/workspace-fixtures";
import { NewSessionDialog } from "./NewSessionDialog";
import { workspacePreferences } from "../workspaces/preferences";
function renderDialog() {
const onCreated = vi.fn();
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(
<QueryClientProvider client={client}>
<NewSessionDialog onCreated={onCreated} />
</QueryClientProvider>,
);
return { onCreated };
}
test("the form has only a question field (no workspace/model/provider/thinking)", async () => {
renderDialog();
await userEvent.click(screen.getByRole("button", { name: /new/i }));
expect(await screen.findByLabelText(/question/i)).toBeInTheDocument();
expect(screen.queryByLabelText(/workspace/i)).not.toBeInTheDocument();
expect(screen.queryByLabelText(/model/i)).not.toBeInTheDocument();
expect(screen.queryByLabelText(/provider/i)).not.toBeInTheDocument();
expect(screen.queryByLabelText(/thinking/i)).not.toBeInTheDocument();
});
test("submitting includes ephemeral migrated preferences and calls onCreated", async () => {
localStorage.clear();
let body: unknown = null;
server.use(
http.get("/api/settings", () => HttpResponse.json({
workspace: "default", provider: "zai", model: "glm-5.2", thinking: "low",
})),
http.get("/api/workspaces", () => HttpResponse.json([{
...workspaceSummaryFixture("default", {
displayName: "Default",
revision: workspaceRevisionFixture("default"),
}),
}])),
http.get("/api/workspaces/default", () => HttpResponse.json({
workspace: canonicalWorkspaceFixture("default", ["zai/glm-5.2"], "zai/glm-5.2"),
revision: workspaceRevisionFixture("default"),
})),
http.post("/api/sessions", async ({ request }) => {
body = await request.json();
return HttpResponse.json({ id: "s1" });
}),
);
const { onCreated } = renderDialog();
await userEvent.click(screen.getByRole("button", { name: /new/i }));
await userEvent.type(await screen.findByLabelText(/question/i), "Quante vendite nel 2025?");
await userEvent.click(screen.getByRole("button", { name: /^create$/i }));
await waitFor(() => expect(body).toEqual({
question: "Quante vendite nel 2025?",
workspaceId: "default", provider: "zai", model: "glm-5.2", thinking: "low",
}));
await waitFor(() => expect(onCreated).toHaveBeenCalledWith("s1"));
});
test("first-run direct dialog creation waits for registry policy without a mounted footer", async () => {
let body: unknown;
let releasePolicy!: () => void;
let summaryRequestStarted = false;
let policyRequestStarted = false;
const policyMayFinish = new Promise<void>((resolve) => { releasePolicy = resolve; });
const revision = {
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/snapshot",
};
workspacePreferences.save({
workspaceId: "psd-clinical", provider: "deepseek", model: "deepseek-v4-pro", thinking: "medium",
});
server.use(
http.get("/api/workspaces", () => {
summaryRequestStarted = true;
return HttpResponse.json([{
...workspaceSummaryFixture("psd-clinical", { displayName: "PSD Clinical", revision: revision as any }),
}]);
}),
http.get("/api/workspaces/psd-clinical", async () => {
policyRequestStarted = true;
await policyMayFinish;
return HttpResponse.json({
workspace: canonicalWorkspaceFixture("psd-clinical", ["zai/glm-5.2"], "zai/glm-5.2"), revision,
});
}),
http.post("/api/sessions", async ({ request }) => {
body = await request.json();
return HttpResponse.json({ id: "s1" });
}),
);
const { onCreated } = renderDialog();
await userEvent.click(screen.getByRole("button", { name: /new/i }));
await userEvent.type(await screen.findByLabelText(/question/i), "q");
await userEvent.click(screen.getByRole("button", { name: /^create$/i }));
await waitFor(() => expect(summaryRequestStarted).toBe(true));
await waitFor(() => expect(policyRequestStarted).toBe(true));
expect(body).toBeUndefined();
expect(screen.getByRole("button", { name: /creating/i })).toBeDisabled();
releasePolicy();
await waitFor(() => expect(body).toEqual({
question: "q", workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "medium",
}));
await waitFor(() => expect(onCreated).toHaveBeenCalledWith("s1"));
});
test("empty question shows a validation error and does not submit", async () => {
renderDialog();
await userEvent.click(screen.getByRole("button", { name: /new/i }));
await userEvent.click(screen.getByRole("button", { name: /^create$/i }));
expect(await screen.findByRole("alert")).toHaveTextContent(/empty/i);
});
-115
View File
@@ -1,115 +0,0 @@
// frontend/src/shell/NewSessionDialog.tsx
import { useEffect, useRef, useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "../components/ui/dialog";
import { Button } from "../components/ui/button";
import { createSession } from "../api/sessions";
import { captureAuthOperation, isAuthOperationCurrent } from "../auth/authOperation";
interface Props {
onCreated: (id: string) => void;
}
export function NewSessionDialog({ onCreated }: Props) {
const [open, setOpen] = useState(false);
const [question, setQuestion] = useState("");
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
const operationEpochRef = useRef(0);
useEffect(() => () => { operationEpochRef.current += 1; }, []);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
if (!question.trim()) {
setError("The question cannot be empty.");
return;
}
const operation = captureAuthOperation({ disposalEpoch: operationEpochRef.current });
setBusy(true);
try {
const { id } = await createSession({ question: question.trim() }, operation ? {
operation,
isCurrent: () => isAuthOperationCurrent(operation, {
sessionId: null,
disposalEpoch: operationEpochRef.current,
}),
} : undefined);
if (operation && !isAuthOperationCurrent(operation, {
sessionId: null,
disposalEpoch: operationEpochRef.current,
})) return;
setOpen(false);
setQuestion("");
onCreated(id);
} catch (err) {
if (operation && !isAuthOperationCurrent(operation, {
sessionId: null,
disposalEpoch: operationEpochRef.current,
})) return;
setError(err instanceof Error ? err.message : "Failed to create the session.");
} finally {
if (!operation || isAuthOperationCurrent(operation, {
sessionId: null,
disposalEpoch: operationEpochRef.current,
})) setBusy(false);
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<Button
variant="default"
size="sm"
className="w-full"
onClick={() => setOpen(true)}
>
New session
</Button>
<DialogContent showCloseButton={false}>
<DialogHeader>
<DialogTitle>New session</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
<div>
<label className="mb-1.5 block text-xs font-medium text-muted-foreground" htmlFor="nsd-question">
Question
</label>
<textarea
id="nsd-question"
value={question}
onChange={(e) => setQuestion(e.target.value)}
rows={3}
className="w-full resize-none rounded-lg border border-input bg-card px-3 py-2 text-sm shadow-xs outline-none transition-[border-color,box-shadow] placeholder:text-muted-foreground focus:border-primary/50 focus:ring-3 focus:ring-ring/15"
placeholder="Enter your question…"
/>
</div>
{error && (
<p className="text-xs text-destructive" role="alert">
{error}
</p>
)}
<div className="flex justify-end gap-2 pt-1">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setOpen(false)}
>
Cancel
</Button>
<Button type="submit" size="sm" disabled={busy}>
{busy ? "Creating…" : "Create"}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
);
}
-24
View File
@@ -1,24 +0,0 @@
/** The rotating "assistant is working" icon. Spins while `spinning`; used as the
* inline activity trigger in CentralStatus. */
export function WorkingSpinner({
className,
spinning = true,
}: {
className?: string;
spinning?: boolean;
}) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
role="status"
aria-label="Assistant is working"
className={["size-4 text-primary", spinning ? "animate-spin" : "", className]
.filter(Boolean)
.join(" ")}
>
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="3" opacity="0.2" />
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
</svg>
);
}