@@ -213,6 +213,86 @@ describe('SessionSettingsDialog supervision', () => {
213213 expect ( screen . getByText ( 'summaryMeta:supervision_decision_v1' ) ) . toBeDefined ( ) ;
214214 } ) ;
215215
216+ it ( 'persists customInstructionsOverride=true when user checks the override checkbox, and drops the global cache for that session' , async ( ) => {
217+ // Simulate a user who already has global custom instructions saved.
218+ fetchSupervisorDefaultsMock . mockResolvedValue ( {
219+ backend : 'codex-sdk' ,
220+ model : CODEX_MODEL_IDS [ 0 ] ,
221+ timeoutMs : 12_000 ,
222+ promptVersion : 'supervision_decision_v1' ,
223+ customInstructions : 'GLOBAL: always prefer tests' ,
224+ } ) ;
225+
226+ render (
227+ < SessionSettingsDialog
228+ serverId = "srv-1"
229+ sessionName = "deck_proj_brain"
230+ label = "Brain"
231+ description = "desc"
232+ cwd = "/proj"
233+ type = "codex-sdk"
234+ transportConfig = { null }
235+ onClose = { vi . fn ( ) }
236+ onSaved = { vi . fn ( ) }
237+ /> ,
238+ ) ;
239+
240+ // Wait for the async fetchSupervisorDefaults to resolve and the global
241+ // textarea to pre-populate. Both the "merged preview" gate and the
242+ // `globalCustomInstructions` cache-mirror field depend on this.
243+ await waitFor ( ( ) => {
244+ expect ( fetchSupervisorDefaultsMock ) . toHaveBeenCalled ( ) ;
245+ } ) ;
246+
247+ // Turn on supervised mode and the session body must become editable.
248+ fireEvent . change ( screen . getAllByRole ( 'combobox' ) [ 3 ] ! , { target : { value : 'supervised' } } ) ;
249+ fireEvent . change ( screen . getAllByRole ( 'combobox' ) [ 4 ] ! , { target : { value : 'codex-sdk' } } ) ;
250+ fireEvent . change ( screen . getAllByRole ( 'combobox' ) [ 5 ] ! , { target : { value : CODEX_MODEL_IDS [ 0 ] } } ) ;
251+
252+ // Session-level custom instructions — different text so we can confirm
253+ // the session layer vs global layer are kept distinct in the payload.
254+ fireEvent . input ( screen . getByPlaceholderText ( 'customInstructionsPlaceholder' ) , {
255+ target : { value : 'SESSION: block commits on failing tests' } ,
256+ } ) ;
257+
258+ // The override checkbox must be present and initially unchecked.
259+ const overrideCheckbox = screen . getByLabelText ( / c u s t o m I n s t r u c t i o n s O v e r r i d e L a b e l / i) as HTMLInputElement ;
260+ expect ( overrideCheckbox . checked ) . toBe ( false ) ;
261+
262+ // With override=false AND both layers non-empty, the merged preview is
263+ // shown — this proves the UI reads both layers.
264+ expect ( screen . getByTestId ( 'supervision-merged-preview' ) ) . toBeDefined ( ) ;
265+
266+ // Check override → session replaces global for this session.
267+ fireEvent . click ( overrideCheckbox ) ;
268+ expect ( overrideCheckbox . checked ) . toBe ( true ) ;
269+
270+ // Preview must hide when override is active (no ambiguity to preview).
271+ expect ( screen . queryByTestId ( 'supervision-merged-preview' ) ) . toBeNull ( ) ;
272+
273+ fireEvent . click ( screen . getByRole ( 'button' , { name : / s a v e / i } ) ) ;
274+
275+ await waitFor ( ( ) => {
276+ expect ( patchSessionMock ) . toHaveBeenCalledWith ( 'srv-1' , 'deck_proj_brain' , expect . objectContaining ( {
277+ transportConfig : expect . objectContaining ( {
278+ supervision : expect . objectContaining ( {
279+ mode : 'supervised' ,
280+ customInstructions : 'SESSION: block commits on failing tests' ,
281+ customInstructionsOverride : true ,
282+ // Cache mirror of the current global value is still written to the
283+ // snapshot so the daemon can re-read it next time override flips
284+ // back to false without needing another defaults fetch.
285+ globalCustomInstructions : 'GLOBAL: always prefer tests' ,
286+ } ) ,
287+ } ) ,
288+ } ) ) ;
289+ } ) ;
290+
291+ // User did not edit the global region → defaults endpoint must not be
292+ // hit. This proves the save-split handles override-only changes cleanly.
293+ expect ( saveSupervisorDefaultsMock ) . not . toHaveBeenCalled ( ) ;
294+ } ) ;
295+
216296 it ( 'persists custom supervision instructions in the session snapshot' , async ( ) => {
217297 render (
218298 < SessionSettingsDialog
0 commit comments