Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 197 additions & 1 deletion apps/web/src/components/settings/SettingsPanels.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import {
type DesktopBridge,
type DesktopUpdateChannel,
type DesktopUpdateState,
type EnvironmentApi,
type LocalApi,
type OrchestrationShellSnapshot,
ProjectId,
ProviderDriverKind,
ProviderInstanceId,
type ServerConfig,
type ServerProcessResourceHistoryResult,
type ServerProvider,
type SourceControlDiscoveryResult,
ThreadId,
} from "@t3tools/contracts";
import * as DateTime from "effect/DateTime";
import * as Option from "effect/Option";
Expand All @@ -31,14 +35,24 @@ import {
createRoute,
createRouter,
} from "@tanstack/react-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import { __resetLocalApiForTests } from "../../localApi";
import {
__resetEnvironmentApiOverridesForTests,
__setEnvironmentApiOverrideForTests,
} from "../../environmentApi";
import { AppAtomRegistryProvider, resetAppAtomRegistryForTests } from "../../rpc/atomRegistry";
import { resetServerStateForTests, setServerConfigSnapshot } from "../../rpc/serverState";
import { useStore } from "../../store";
import { useUiStateStore } from "../../uiStateStore";
import { ConnectionsSettings } from "./ConnectionsSettings";
import { DiagnosticsSettingsPanel } from "./DiagnosticsSettings";
import { GeneralSettingsPanel, ProviderSettingsPanel } from "./SettingsPanels";
import {
ArchivedThreadsPanel,
GeneralSettingsPanel,
ProviderSettingsPanel,
} from "./SettingsPanels";
import { SourceControlSettingsPanel } from "./SourceControlSettings";

function renderWithTestRouter(children: ReactNode) {
Expand Down Expand Up @@ -297,6 +311,71 @@ function createEmptyProcessResourceHistoryResult(): ServerProcessResourceHistory
};
}

const archivedPanelEnvironmentId = EnvironmentId.make("environment-archive-test");
const archivedPanelProjectId = ProjectId.make("project-docs");
const archivedPanelModelSelection = {
instanceId: ProviderInstanceId.make("codex"),
model: "gpt-5",
};

function createArchivedPanelSnapshot(
threads: OrchestrationShellSnapshot["threads"],
): OrchestrationShellSnapshot {
return {
snapshotSequence: 1,
projects: [
{
id: archivedPanelProjectId,
title: "Docs Portal",
workspaceRoot: "/work/clients/docs",
defaultModelSelection: archivedPanelModelSelection,
scripts: [],
createdAt: "2036-04-07T00:00:00.000Z",
updatedAt: "2036-04-07T00:00:00.000Z",
},
],
threads,
updatedAt: "2036-04-07T00:03:00.000Z",
};
}

function createArchivedPanelThread(input: {
readonly id: string;
readonly title: string;
readonly branch: string | null;
readonly worktreePath: string | null;
readonly createdAt: string;
readonly archivedAt: string;
}): OrchestrationShellSnapshot["threads"][number] {
return {
id: ThreadId.make(input.id),
projectId: archivedPanelProjectId,
title: input.title,
modelSelection: archivedPanelModelSelection,
runtimeMode: "full-access",
interactionMode: "default",
branch: input.branch,
worktreePath: input.worktreePath,
latestTurn: null,
createdAt: input.createdAt,
updatedAt: input.archivedAt,
archivedAt: input.archivedAt,
session: null,
latestUserMessageAt: null,
hasPendingApprovals: false,
hasPendingUserInput: false,
hasActionableProposedPlan: false,
};
}

function createArchivedPanelEnvironmentApi(snapshot: OrchestrationShellSnapshot): EnvironmentApi {
return {
orchestration: {
getArchivedShellSnapshot: vi.fn().mockResolvedValue(snapshot),
},
} as unknown as EnvironmentApi;
}

function makePairingLink(input: {
readonly id: string;
readonly credential: string;
Expand Down Expand Up @@ -1310,6 +1389,123 @@ describe("GeneralSettingsPanel observability", () => {
});
});

describe("ArchivedThreadsPanel", () => {
let mounted:
| (Awaited<ReturnType<typeof render>> & {
cleanup?: () => Promise<void>;
unmount?: () => Promise<void>;
})
| null = null;

beforeEach(() => {
resetAppAtomRegistryForTests();
__resetEnvironmentApiOverridesForTests();
document.body.innerHTML = "";
useStore.setState({
activeEnvironmentId: null,
environmentStateById: {},
});
});

afterEach(async () => {
if (mounted) {
const teardown = mounted.cleanup ?? mounted.unmount;
await teardown?.call(mounted).catch(() => {});
}
mounted = null;
document.body.innerHTML = "";
__resetEnvironmentApiOverridesForTests();
resetAppAtomRegistryForTests();
useStore.setState({
activeEnvironmentId: null,
environmentStateById: {},
});
});

it("searches archived threads and collapses archived projects", async () => {
useStore
.getState()
.syncServerShellSnapshot(createArchivedPanelSnapshot([]), archivedPanelEnvironmentId);

__setEnvironmentApiOverrideForTests(
archivedPanelEnvironmentId,
createArchivedPanelEnvironmentApi(
createArchivedPanelSnapshot([
createArchivedPanelThread({
id: "thread-docs-bug",
title: "Fix publishing bug",
branch: "docs-fix",
worktreePath: "/work/clients/docs/.worktrees/docs-fix",
createdAt: "2036-04-07T00:01:00.000Z",
archivedAt: "2036-04-07T00:04:00.000Z",
}),
createArchivedPanelThread({
id: "thread-docs-homepage",
title: "Rewrite homepage copy",
branch: null,
worktreePath: null,
createdAt: "2036-04-07T00:02:00.000Z",
archivedAt: "2036-04-07T00:03:00.000Z",
}),
]),
),
);

const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});

mounted = await renderWithTestRouter(
<QueryClientProvider client={queryClient}>
<AppAtomRegistryProvider>
<ArchivedThreadsPanel />
</AppAtomRegistryProvider>
</QueryClientProvider>,
);

await expect.element(page.getByText("Fix publishing bug")).toBeInTheDocument();
await expect.element(page.getByText("Rewrite homepage copy")).toBeInTheDocument();

await page.getByLabelText("Search archived threads").fill("homepage");

await expect.element(page.getByText("Rewrite homepage copy")).toBeInTheDocument();
await expect.element(page.getByText("Fix publishing bug")).not.toBeInTheDocument();

await page.getByLabelText("Search archived threads").fill("");
await expect.element(page.getByText("Fix publishing bug")).toBeInTheDocument();

await page.getByRole("button", { name: "Collapse Docs Portal", exact: true }).click();

await expect.element(page.getByText("Fix publishing bug")).not.toBeInTheDocument();
await expect.element(page.getByText("Rewrite homepage copy")).not.toBeInTheDocument();

await page.getByLabelText("Search archived threads").fill("homepage");

await expect.element(page.getByText("Rewrite homepage copy")).toBeInTheDocument();
await expect.element(page.getByText("Fix publishing bug")).not.toBeInTheDocument();

const searchExpandedToggle = page.getByRole("button", {
name: "Docs Portal expanded while searching",
exact: true,
});
await expect.element(searchExpandedToggle).toHaveAttribute("aria-expanded", "true");
await expect.element(searchExpandedToggle).toBeDisabled();

await page.getByLabelText("Search archived threads").fill("");

await expect.element(page.getByText("Fix publishing bug")).not.toBeInTheDocument();
await expect.element(page.getByText("Rewrite homepage copy")).not.toBeInTheDocument();

await page.getByRole("button", { name: "Expand Docs Portal", exact: true }).click();

await expect.element(page.getByText("Fix publishing bug")).toBeInTheDocument();
await expect.element(page.getByText("Rewrite homepage copy")).toBeInTheDocument();
});
});

describe("SourceControlSettingsPanel discovery states", () => {
let mounted:
| (Awaited<ReturnType<typeof render>> & {
Expand Down
91 changes: 91 additions & 0 deletions apps/web/src/components/settings/SettingsPanels.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,100 @@ import {
import { describe, expect, it } from "vite-plus/test";
import {
buildProviderInstanceUpdatePatch,
filterArchivedThreadGroups,
formatDiagnosticsDescription,
} from "./SettingsPanels.logic";

const archivedGroups = [
{
project: {
id: "project-docs",
name: "Docs Portal",
cwd: "/work/clients/docs",
},
threads: [
{
id: "thread-docs-bug",
title: "Fix publishing bug",
branch: "docs-fix",
worktreePath: "/work/clients/docs/.worktrees/docs-fix",
},
{
id: "thread-docs-copy",
title: "Rewrite homepage copy",
branch: null,
worktreePath: null,
},
],
},
{
project: {
id: "project-api",
name: "API Service",
cwd: "/work/services/api",
},
threads: [
{
id: "thread-api-cache",
title: "Tune cache invalidation",
branch: "cache-tuning",
worktreePath: "/work/services/api/.worktrees/cache-tuning",
},
],
},
];

describe("archive thread search helpers", () => {
it("returns all groups for an empty query", () => {
expect(filterArchivedThreadGroups(archivedGroups, " ")).toEqual(archivedGroups);
});

it("keeps all project threads when the project name matches", () => {
const filtered = filterArchivedThreadGroups(archivedGroups, "docs portal");

expect(filtered).toHaveLength(1);
expect(filtered[0]?.project.id).toBe("project-docs");
expect(filtered[0]?.threads.map((thread) => thread.id)).toEqual([
"thread-docs-bug",
"thread-docs-copy",
]);
});

it("keeps all project threads when the project cwd matches", () => {
const filtered = filterArchivedThreadGroups(archivedGroups, "services api");

expect(filtered).toHaveLength(1);
expect(filtered[0]?.project.id).toBe("project-api");
expect(filtered[0]?.threads.map((thread) => thread.id)).toEqual(["thread-api-cache"]);
});

it("keeps only matching threads when the thread title matches", () => {
const filtered = filterArchivedThreadGroups(archivedGroups, " HOMEpage ");

expect(filtered).toHaveLength(1);
expect(filtered[0]?.project.id).toBe("project-docs");
expect(filtered[0]?.threads.map((thread) => thread.id)).toEqual(["thread-docs-copy"]);
});

it("matches thread branch and worktree path", () => {
expect(
filterArchivedThreadGroups(archivedGroups, "cache-tuning")[0]?.threads.map(
(thread) => thread.id,
),
).toEqual(["thread-api-cache"]);

expect(
filterArchivedThreadGroups(archivedGroups, "worktrees docs-fix")[0]?.threads.map(
(thread) => thread.id,
),
).toEqual(["thread-docs-bug"]);
});

it("drops groups with no matches", () => {
expect(filterArchivedThreadGroups(archivedGroups, "not-here")).toEqual([]);
});
});

describe("formatDiagnosticsDescription", () => {
it("collapses trace and metric URLs that share the same OTEL base path", () => {
expect(
Expand Down
Loading
Loading