Skip to content

Commit 26458c9

Browse files
committed
feat: refine workspace editor and terminal UX
1 parent 1f8eeae commit 26458c9

46 files changed

Lines changed: 3876 additions & 289 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

e2e-ui/fixtures/scene-runner.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@ async function openSettingsSection(
3333
) {
3434
const sectionOrder = {
3535
general: 0,
36-
providers: 1,
37-
appearance: 2,
38-
shortcuts: 3,
36+
monitoring: 1,
37+
analysis: 2,
38+
providers: 3,
39+
appearance: 4,
40+
shortcuts: 5,
41+
about: 6,
3942
} as const;
4043

4144
const index = sectionOrder[section];

packages/web/src/app/providers.lifecycle.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "../atoms/activation";
1010
import { appearancePersonalizationAtom, authenticatedAtom, themeAtom } from "../atoms/app-ui";
1111
import { authEnabledAtom, connectionStatusAtom } from "../atoms/connection";
12+
import { providerListAtom } from "../atoms/providers";
1213
import { sessionsAtom } from "../atoms/sessions";
1314
import {
1415
activeWorkspaceIdAtom,
@@ -1136,6 +1137,17 @@ describe("AppProviders lifecycle recovery", () => {
11361137
store.set(activationStatusAtom, "active");
11371138
store.set(activationGenerationAtom, 1);
11381139
store.set(activationReasonAtom, null);
1140+
store.set(providerListAtom, [
1141+
{
1142+
id: "codex",
1143+
displayName: "Codex",
1144+
badge: "Codex",
1145+
kind: "built_in",
1146+
capability: "full",
1147+
capabilities: [],
1148+
requiredCommands: ["codex"],
1149+
},
1150+
]);
11391151
store.set(gitStateAtomFamily("ws-1"), {
11401152
branch: "feature/test",
11411153
ahead: 1,
@@ -1205,6 +1217,7 @@ describe("AppProviders lifecycle recovery", () => {
12051217
expect(store.get(worktreeListAtomFamily("ws-1")).items).toEqual([]);
12061218
expect(store.get(fileTreeStaleAtomFamily("ws-1"))).toBe(false);
12071219
expect(store.get(sessionsAtom)).toEqual({});
1220+
expect(store.get(providerListAtom).map((provider) => provider.id)).toEqual(["codex"]);
12081221
});
12091222
});
12101223

packages/web/src/app/providers.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
dispatchCommandAtom,
3131
isWriterAtom,
3232
lastReconnectAttemptAtom,
33+
providerListAtom,
3334
reconnectAttemptCountAtom,
3435
serverInfoAtom,
3536
sessionsAtom,
@@ -88,6 +89,7 @@ import {
8889
} from "../features/workspace/atoms";
8990
import { useActivation } from "../hooks/use-activation";
9091
import { getThemeById, resolveStoredThemeId } from "../theme";
92+
import { useSyncTerminalThemeBackground } from "../theme/use-sync-terminal-theme-background";
9193
import type { ConnectionStatus, EventListener } from "../ws";
9294
import { resolveWsUrl, WsClient } from "../ws";
9395

@@ -221,6 +223,34 @@ function resetServerProjectedState(store: Store): void {
221223
}
222224
}
223225

226+
function ProviderListBootstrapper() {
227+
const connectionStatus = useAtomValue(connectionStatusAtom);
228+
const dispatch = useAtomValue(dispatchCommandAtom);
229+
const providerList = useAtomValue(providerListAtom);
230+
const setProviderList = useSetAtom(providerListAtom);
231+
232+
useEffect(() => {
233+
if (connectionStatus !== "connected" || providerList.length > 0) {
234+
return;
235+
}
236+
237+
let cancelled = false;
238+
239+
void dispatch("provider.list", {}).then((result) => {
240+
if (cancelled || !result.ok || !Array.isArray(result.data)) {
241+
return;
242+
}
243+
setProviderList(result.data);
244+
});
245+
246+
return () => {
247+
cancelled = true;
248+
};
249+
}, [connectionStatus, dispatch, providerList.length, setProviderList]);
250+
251+
return null;
252+
}
253+
224254
function parseWorkspaceRefreshHint(
225255
topic: string,
226256
payload: unknown
@@ -302,6 +332,7 @@ export function AppProviders({ children }: AppProvidersProps) {
302332
const { claim } = useActivation();
303333

304334
useSessionNotifications();
335+
useSyncTerminalThemeBackground(dispatch, activeWorkspaceId, connectionStatus);
305336

306337
// Use refs to avoid stale closures in event handlers
307338
const wsClientRef = useRef<WsClient | null>(null);
@@ -1186,7 +1217,12 @@ export function AppProviders({ children }: AppProvidersProps) {
11861217
.catch(() => {});
11871218
}, [activeWorkspaceId, activationStatus, authEnabled, authenticated, connectionStatus]);
11881219

1189-
return <>{children}</>;
1220+
return (
1221+
<>
1222+
<ProviderListBootstrapper />
1223+
{children}
1224+
</>
1225+
);
11901226
}
11911227

11921228
function storeServerMetadata(
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { ProviderListItem, ProviderRuntimeStatusResponse } from "@coder-studio/core";
2+
import { atom } from "jotai";
3+
4+
export const providerListAtom = atom<ProviderListItem[]>([]);
5+
6+
export const providerRuntimeStatusAtom = atom<
7+
ProviderRuntimeStatusResponse["providers"] | undefined
8+
>(undefined);

packages/web/src/features/agent-panes/index.test.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Session } from "@coder-studio/core";
1+
import type { ProviderRuntimeStatusResponse, Session } from "@coder-studio/core";
22
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
33
import { createStore, Provider } from "jotai";
44
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -201,6 +201,45 @@ const mockEditorPaneCard = vi.fn(
201201
)
202202
);
203203

204+
const AVAILABLE_PROVIDER_RUNTIME_STATUS: ProviderRuntimeStatusResponse = {
205+
providers: {
206+
claude: {
207+
providerId: "claude",
208+
displayName: "Claude",
209+
badge: "Claude",
210+
kind: "built_in",
211+
stability: "stable",
212+
capability: "full",
213+
capabilities: [],
214+
requiredCommands: ["claude"],
215+
available: true,
216+
missingCommands: [],
217+
missingPrerequisites: [],
218+
autoInstallSupported: false,
219+
installReadiness: "ready",
220+
manualGuideKeys: [],
221+
docUrls: { provider: "", prerequisites: {} },
222+
},
223+
codex: {
224+
providerId: "codex",
225+
displayName: "Codex",
226+
badge: "Codex",
227+
kind: "built_in",
228+
stability: "stable",
229+
capability: "full",
230+
capabilities: [],
231+
requiredCommands: ["codex"],
232+
available: true,
233+
missingCommands: [],
234+
missingPrerequisites: [],
235+
autoInstallSupported: false,
236+
installReadiness: "ready",
237+
manualGuideKeys: [],
238+
docUrls: { provider: "", prerequisites: {} },
239+
},
240+
},
241+
};
242+
204243
vi.mock("./views/shared/editor-pane-card", () => ({
205244
EditorPaneCard: (props: {
206245
paneId: string;
@@ -1436,6 +1475,9 @@ describe("AgentPanes", () => {
14361475
it("disables provider buttons while session.create is in flight to prevent re-entry", async () => {
14371476
let resolveCreate: ((value: unknown) => void) | undefined;
14381477
const sendCommand = vi.fn().mockImplementation((op: string) => {
1478+
if (op === "provider.runtimeStatus") {
1479+
return AVAILABLE_PROVIDER_RUNTIME_STATUS;
1480+
}
14391481
if (op === "session.create") {
14401482
return new Promise((resolve) => {
14411483
resolveCreate = resolve;

0 commit comments

Comments
 (0)