Skip to content

Commit 15eccee

Browse files
SamigosDimillian
andauthored
feat(composer): persist Codex params per thread (#380)
Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
1 parent 867be49 commit 15eccee

17 files changed

Lines changed: 1867 additions & 141 deletions

src/App.tsx

Lines changed: 248 additions & 38 deletions
Large diffs are not rendered by default.

src/features/app/hooks/usePersistComposerSettings.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/features/collaboration/hooks/useCollaborationModes.test.tsx

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,139 @@ describe("useCollaborationModes", () => {
130130
]),
131131
);
132132
});
133+
134+
it("resets to the workspace default when selectionKey changes and preferredModeId is null", async () => {
135+
vi.mocked(getCollaborationModes).mockResolvedValue(makeModesResponse());
136+
137+
const { result, rerender } = renderHook(
138+
({
139+
workspace,
140+
enabled,
141+
preferredModeId,
142+
selectionKey,
143+
}: {
144+
workspace: WorkspaceInfo | null;
145+
enabled: boolean;
146+
preferredModeId: string | null;
147+
selectionKey: string | null;
148+
}) =>
149+
useCollaborationModes({
150+
activeWorkspace: workspace,
151+
enabled,
152+
preferredModeId,
153+
selectionKey,
154+
}),
155+
{
156+
initialProps: {
157+
workspace: workspaceOne,
158+
enabled: true,
159+
preferredModeId: "default" as string | null,
160+
selectionKey: "thread-a",
161+
},
162+
},
163+
);
164+
165+
await waitFor(() => expect(result.current.selectedCollaborationModeId).toBe("default"));
166+
167+
act(() => {
168+
result.current.setSelectedCollaborationModeId("plan");
169+
});
170+
expect(result.current.selectedCollaborationModeId).toBe("plan");
171+
172+
// Thread switch with no stored override: preferredModeId is null.
173+
rerender({
174+
workspace: workspaceOne,
175+
enabled: true,
176+
preferredModeId: null,
177+
selectionKey: "thread-b",
178+
});
179+
180+
expect(result.current.selectedCollaborationModeId).toBe("default");
181+
});
182+
183+
it("falls back to the workspace default when the preferredModeId is stale", async () => {
184+
vi.mocked(getCollaborationModes).mockResolvedValue(makeModesResponse());
185+
186+
const { result, rerender } = renderHook(
187+
(props: {
188+
enabled: boolean;
189+
preferredModeId: string | null;
190+
selectionKey: string;
191+
}) =>
192+
useCollaborationModes({
193+
activeWorkspace: workspaceOne,
194+
enabled: props.enabled,
195+
preferredModeId: props.preferredModeId,
196+
selectionKey: props.selectionKey,
197+
}),
198+
{
199+
initialProps: {
200+
enabled: true,
201+
preferredModeId: "plan",
202+
selectionKey: "thread-a",
203+
},
204+
},
205+
);
206+
207+
await waitFor(() => {
208+
expect(result.current.collaborationModes.length).toBeGreaterThan(0);
209+
});
210+
expect(result.current.selectedCollaborationModeId).toBe("plan");
211+
212+
rerender({
213+
enabled: true,
214+
preferredModeId: "stale-mode-id",
215+
selectionKey: "thread-b",
216+
});
217+
218+
await waitFor(() => {
219+
expect(result.current.selectedCollaborationModeId).toBe("default");
220+
});
221+
});
222+
223+
it("reapplies preferred mode when collaboration is re-enabled on the same thread", async () => {
224+
vi.mocked(getCollaborationModes).mockResolvedValue(makeModesResponse());
225+
226+
const { result, rerender } = renderHook(
227+
(props: {
228+
enabled: boolean;
229+
preferredModeId: string | null;
230+
selectionKey: string;
231+
}) =>
232+
useCollaborationModes({
233+
activeWorkspace: workspaceOne,
234+
enabled: props.enabled,
235+
preferredModeId: props.preferredModeId,
236+
selectionKey: props.selectionKey,
237+
}),
238+
{
239+
initialProps: {
240+
enabled: true,
241+
preferredModeId: "plan",
242+
selectionKey: "thread-a",
243+
},
244+
},
245+
);
246+
247+
await waitFor(() => {
248+
expect(result.current.selectedCollaborationModeId).toBe("plan");
249+
});
250+
251+
rerender({
252+
enabled: false,
253+
preferredModeId: "plan",
254+
selectionKey: "thread-a",
255+
});
256+
expect(result.current.selectedCollaborationModeId).toBeNull();
257+
258+
rerender({
259+
enabled: true,
260+
preferredModeId: "plan",
261+
selectionKey: "thread-a",
262+
});
263+
264+
await waitFor(() => {
265+
expect(result.current.selectedCollaborationModeId).toBe("plan");
266+
});
267+
});
133268
});

src/features/collaboration/hooks/useCollaborationModes.ts

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,33 @@ import { getCollaborationModes } from "../../../services/tauri";
99
type UseCollaborationModesOptions = {
1010
activeWorkspace: WorkspaceInfo | null;
1111
enabled: boolean;
12+
preferredModeId?: string | null;
13+
selectionKey?: string | null;
1214
onDebug?: (entry: DebugEntry) => void;
1315
};
1416

17+
function pickWorkspaceDefaultModeId(modes: CollaborationModeOption[]): string | null {
18+
return (
19+
modes.find(
20+
(mode) =>
21+
mode.id.trim().toLowerCase() === "default" ||
22+
mode.mode.trim().toLowerCase() === "default",
23+
)?.id ??
24+
modes.find(
25+
(mode) =>
26+
mode.id.trim().toLowerCase() === "code" ||
27+
mode.mode.trim().toLowerCase() === "code",
28+
)?.id ??
29+
modes[0]?.id ??
30+
null
31+
);
32+
}
33+
1534
export function useCollaborationModes({
1635
activeWorkspace,
1736
enabled,
37+
preferredModeId = null,
38+
selectionKey = null,
1839
onDebug,
1940
}: UseCollaborationModesOptions) {
2041
const [modes, setModes] = useState<CollaborationModeOption[]>([]);
@@ -23,6 +44,8 @@ export function useCollaborationModes({
2344
const previousWorkspaceId = useRef<string | null>(null);
2445
const inFlight = useRef(false);
2546
const selectedModeIdRef = useRef<string | null>(null);
47+
const lastSelectionKey = useRef<string | null>(null);
48+
const lastEnabled = useRef(enabled);
2649

2750
const workspaceId = activeWorkspace?.id ?? null;
2851
const isConnected = Boolean(activeWorkspace?.connected);
@@ -136,26 +159,14 @@ export function useCollaborationModes({
136159
.filter((mode): mode is CollaborationModeOption => mode !== null);
137160
setModes(data);
138161
lastFetchedWorkspaceId.current = workspaceId;
139-
const preferredModeId =
140-
data.find(
141-
(mode) =>
142-
mode.id.trim().toLowerCase() === "default" ||
143-
mode.mode.trim().toLowerCase() === "default",
144-
)?.id ??
145-
data.find(
146-
(mode) =>
147-
mode.id.trim().toLowerCase() === "code" ||
148-
mode.mode.trim().toLowerCase() === "code",
149-
)?.id ??
150-
data[0]?.id ??
151-
null;
162+
const workspaceDefaultModeId = pickWorkspaceDefaultModeId(data);
152163
setSelectedModeId((currentSelection) => {
153164
const selection = currentSelection ?? selectedModeIdRef.current;
154165
if (!selection) {
155-
return preferredModeId;
166+
return workspaceDefaultModeId;
156167
}
157168
if (!data.some((mode) => mode.id === selection)) {
158-
return preferredModeId;
169+
return workspaceDefaultModeId;
159170
}
160171
return selection;
161172
});
@@ -176,6 +187,33 @@ export function useCollaborationModes({
176187
selectedModeIdRef.current = selectedModeId;
177188
}, [selectedModeId]);
178189

190+
useEffect(() => {
191+
const wasEnabled = lastEnabled.current;
192+
lastEnabled.current = enabled;
193+
if (!enabled) {
194+
return;
195+
}
196+
const enabledJustReenabled = !wasEnabled;
197+
if (!enabledJustReenabled && selectionKey === lastSelectionKey.current) {
198+
return;
199+
}
200+
lastSelectionKey.current = selectionKey;
201+
// When switching threads, prefer the per-thread override. If there is no stored override,
202+
// reset to the workspace default instead of carrying over the previous thread's selection.
203+
// Also validate that a stored override still exists; otherwise fall back to the workspace default
204+
// so collaboration payload generation remains enabled.
205+
setSelectedModeId(() => {
206+
if (!modes.length) {
207+
// If modes aren't loaded yet, keep the preferred ID (if any) until refresh validates it.
208+
return preferredModeId;
209+
}
210+
if (preferredModeId && modes.some((mode) => mode.id === preferredModeId)) {
211+
return preferredModeId;
212+
}
213+
return pickWorkspaceDefaultModeId(modes);
214+
});
215+
}, [enabled, modes, preferredModeId, selectionKey]);
216+
179217
useEffect(() => {
180218
if (previousWorkspaceId.current !== workspaceId) {
181219
previousWorkspaceId.current = workspaceId;

0 commit comments

Comments
 (0)