Skip to content

Commit e5d13e8

Browse files
committed
feat: hydrate terminal preferences at app startup
1 parent 6b91b25 commit e5d13e8

3 files changed

Lines changed: 82 additions & 0 deletions

File tree

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
workspacesAtom,
1111
workspacesLoadStateAtom,
1212
} from "../atoms/workspaces";
13+
import { terminalPreferencesAtom } from "../features/terminal-panel/preferences";
1314
import {
1415
fileTreeStaleAtomFamily,
1516
gitBranchListAtomFamily,
@@ -374,6 +375,38 @@ describe("AppProviders lifecycle recovery", () => {
374375
});
375376
});
376377

378+
it("hydrates terminal copy-on-select preferences from settings.get once connected", async () => {
379+
const store = createStore();
380+
setVisibilityState("visible");
381+
382+
const sendCommand = vi.fn().mockImplementation(async (op: string) => {
383+
if (op === "settings.get") {
384+
return {
385+
"appearance.terminalCopyOnSelect": true,
386+
};
387+
}
388+
389+
return undefined;
390+
});
391+
wsState.client!.sendCommand = sendCommand;
392+
393+
renderProviders(store);
394+
395+
await vi.waitFor(() => {
396+
expect(wsState.client?.connect).toHaveBeenCalled();
397+
});
398+
399+
act(() => {
400+
wsState.client?.statusHandler?.("connected");
401+
});
402+
403+
await vi.waitFor(() => {
404+
expect(store.get(terminalPreferencesAtom)).toEqual({ copyOnSelect: true });
405+
});
406+
407+
expect(sendCommand).toHaveBeenCalledWith("settings.get", {}, undefined);
408+
});
409+
377410
it("marks the session authenticated when /auth/status confirms an existing server session", async () => {
378411
globalThis.fetch = vi.fn().mockResolvedValue({
379412
json: async () => ({ authEnabled: true, authenticated: true }),

packages/web/src/app/providers.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ import { activeWorkspaceIdAtom } from "../atoms/workspaces";
3939
import { useSessionNotifications } from "../features/notifications";
4040
import { supervisorCyclesAtom, supervisorsAtom } from "../features/supervisor/atoms";
4141
import { terminalMetaAtomFamily } from "../features/terminal-panel/atoms";
42+
import {
43+
resolveTerminalCopyOnSelectSetting,
44+
terminalPreferencesAtom,
45+
} from "../features/terminal-panel/preferences";
4246
import {
4347
editorRefreshTokenAtomFamily,
4448
fileTreeStaleAtomFamily,
@@ -173,6 +177,7 @@ export function AppProviders({ children }: AppProvidersProps) {
173177
// Supervisor state atoms
174178
const setSupervisors = useSetAtom(supervisorsAtom);
175179
const setSupervisorCycles = useSetAtom(supervisorCyclesAtom);
180+
const setTerminalPreferences = useSetAtom(terminalPreferencesAtom);
176181

177182
// Get Jotai store for writing to atomFamily atoms
178183
const store = useStore();
@@ -197,6 +202,31 @@ export function AppProviders({ children }: AppProvidersProps) {
197202
dispatchRef.current = dispatch;
198203
}, [dispatch]);
199204

205+
useEffect(() => {
206+
if (connectionStatus !== "connected") {
207+
return;
208+
}
209+
210+
let cancelled = false;
211+
212+
const hydrateTerminalPreferences = async () => {
213+
const result = await dispatch<Record<string, unknown>>("settings.get", {});
214+
if (cancelled || !result.ok || !result.data) {
215+
return;
216+
}
217+
218+
setTerminalPreferences({
219+
copyOnSelect: resolveTerminalCopyOnSelectSetting(result.data),
220+
});
221+
};
222+
223+
void hydrateTerminalPreferences();
224+
225+
return () => {
226+
cancelled = true;
227+
};
228+
}, [connectionStatus, dispatch, setTerminalPreferences]);
229+
200230
useEffect(() => {
201231
activeWorkspaceIdRef.current = activeWorkspaceId;
202232
}, [activeWorkspaceId]);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { atomWithStorage } from "jotai/utils";
2+
3+
export interface TerminalPreferences {
4+
copyOnSelect: boolean;
5+
}
6+
7+
export const DEFAULT_TERMINAL_PREFERENCES: TerminalPreferences = {
8+
copyOnSelect: false,
9+
};
10+
11+
export function resolveTerminalCopyOnSelectSetting(settings: Record<string, unknown>): boolean {
12+
const value = settings["appearance.terminalCopyOnSelect"];
13+
return typeof value === "boolean" ? value : DEFAULT_TERMINAL_PREFERENCES.copyOnSelect;
14+
}
15+
16+
export const terminalPreferencesAtom = atomWithStorage<TerminalPreferences>(
17+
"ui.terminalPreferences",
18+
DEFAULT_TERMINAL_PREFERENCES
19+
);

0 commit comments

Comments
 (0)