@@ -10,6 +10,7 @@ import {
1010 workspacesAtom ,
1111 workspacesLoadStateAtom ,
1212} from "../atoms/workspaces" ;
13+ import { terminalPreferencesAtom } from "../features/terminal-panel/preferences" ;
1314import {
1415 fileTreeStaleAtomFamily ,
1516 gitBranchListAtomFamily ,
@@ -97,9 +98,11 @@ function seedWorkspaces(
9798describe ( "AppProviders lifecycle recovery" , ( ) => {
9899 const originalFetch = globalThis . fetch ;
99100 const originalVisibilityState = Object . getOwnPropertyDescriptor ( document , "visibilityState" ) ;
101+ const originalTerminalPreferences = localStorage . getItem ( "ui.terminalPreferences" ) ;
100102
101103 beforeEach ( ( ) => {
102104 resetAppProvidersSingletonsForTests ( ) ;
105+ localStorage . removeItem ( "ui.terminalPreferences" ) ;
103106 globalThis . fetch = vi . fn ( ) . mockResolvedValue ( {
104107 json : async ( ) => ( { authEnabled : false } ) ,
105108 } ) as unknown as typeof fetch ;
@@ -133,6 +136,11 @@ describe("AppProviders lifecycle recovery", () => {
133136 resetAppProvidersSingletonsForTests ( ) ;
134137 globalThis . fetch = originalFetch ;
135138 vi . restoreAllMocks ( ) ;
139+ if ( originalTerminalPreferences === null ) {
140+ localStorage . removeItem ( "ui.terminalPreferences" ) ;
141+ } else {
142+ localStorage . setItem ( "ui.terminalPreferences" , originalTerminalPreferences ) ;
143+ }
136144 if ( originalVisibilityState ) {
137145 Object . defineProperty ( document , "visibilityState" , originalVisibilityState ) ;
138146 } else {
@@ -374,6 +382,131 @@ describe("AppProviders lifecycle recovery", () => {
374382 } ) ;
375383 } ) ;
376384
385+ it ( "hydrates terminal copy-on-select preferences from settings.get once connected" , async ( ) => {
386+ const store = createStore ( ) ;
387+ setVisibilityState ( "visible" ) ;
388+
389+ const sendCommand = vi . fn ( ) . mockImplementation ( async ( op : string ) => {
390+ if ( op === "settings.get" ) {
391+ return {
392+ "appearance.terminalCopyOnSelect" : true ,
393+ } ;
394+ }
395+
396+ return undefined ;
397+ } ) ;
398+ wsState . client ! . sendCommand = sendCommand ;
399+
400+ renderProviders ( store ) ;
401+
402+ await vi . waitFor ( ( ) => {
403+ expect ( wsState . client ?. connect ) . toHaveBeenCalled ( ) ;
404+ } ) ;
405+
406+ act ( ( ) => {
407+ wsState . client ?. statusHandler ?.( "connected" ) ;
408+ } ) ;
409+
410+ await vi . waitFor ( ( ) => {
411+ expect ( store . get ( terminalPreferencesAtom ) ) . toEqual ( { copyOnSelect : true } ) ;
412+ } ) ;
413+
414+ expect ( sendCommand ) . toHaveBeenCalledWith ( "settings.get" , { } , undefined ) ;
415+ } ) ;
416+
417+ it ( "preserves a newer local terminal copy-on-select update when startup hydration resolves later" , async ( ) => {
418+ const store = createStore ( ) ;
419+ setVisibilityState ( "visible" ) ;
420+
421+ let resolveSettingsGet : ( ( value : Record < string , unknown > ) => void ) | undefined ;
422+ const settingsGetPromise = new Promise < Record < string , unknown > > ( ( resolve ) => {
423+ resolveSettingsGet = resolve ;
424+ } ) ;
425+ const sendCommand = vi . fn ( ) . mockImplementation ( async ( op : string ) => {
426+ if ( op === "settings.get" ) {
427+ return await settingsGetPromise ;
428+ }
429+
430+ return undefined ;
431+ } ) ;
432+ wsState . client ! . sendCommand = sendCommand ;
433+
434+ renderProviders ( store ) ;
435+
436+ await vi . waitFor ( ( ) => {
437+ expect ( wsState . client ?. connect ) . toHaveBeenCalled ( ) ;
438+ } ) ;
439+
440+ act ( ( ) => {
441+ wsState . client ?. statusHandler ?.( "connected" ) ;
442+ } ) ;
443+
444+ await vi . waitFor ( ( ) => {
445+ expect ( sendCommand ) . toHaveBeenCalledWith ( "settings.get" , { } , undefined ) ;
446+ } ) ;
447+
448+ act ( ( ) => {
449+ store . set ( terminalPreferencesAtom , { copyOnSelect : true } ) ;
450+ } ) ;
451+
452+ await act ( async ( ) => {
453+ resolveSettingsGet ?.( { } ) ;
454+ await settingsGetPromise ;
455+ } ) ;
456+
457+ expect ( store . get ( terminalPreferencesAtom ) ) . toEqual ( { copyOnSelect : true } ) ;
458+ } ) ;
459+
460+ it ( "preserves an ABA local terminal copy-on-select update when startup hydration resolves later" , async ( ) => {
461+ const store = createStore ( ) ;
462+ setVisibilityState ( "visible" ) ;
463+
464+ act ( ( ) => {
465+ store . set ( terminalPreferencesAtom , { copyOnSelect : true } ) ;
466+ } ) ;
467+
468+ let resolveSettingsGet : ( ( value : Record < string , unknown > ) => void ) | undefined ;
469+ const settingsGetPromise = new Promise < Record < string , unknown > > ( ( resolve ) => {
470+ resolveSettingsGet = resolve ;
471+ } ) ;
472+ const sendCommand = vi . fn ( ) . mockImplementation ( async ( op : string ) => {
473+ if ( op === "settings.get" ) {
474+ return await settingsGetPromise ;
475+ }
476+
477+ return undefined ;
478+ } ) ;
479+ wsState . client ! . sendCommand = sendCommand ;
480+
481+ renderProviders ( store ) ;
482+
483+ await vi . waitFor ( ( ) => {
484+ expect ( wsState . client ?. connect ) . toHaveBeenCalled ( ) ;
485+ } ) ;
486+
487+ act ( ( ) => {
488+ wsState . client ?. statusHandler ?.( "connected" ) ;
489+ } ) ;
490+
491+ await vi . waitFor ( ( ) => {
492+ expect ( sendCommand ) . toHaveBeenCalledWith ( "settings.get" , { } , undefined ) ;
493+ } ) ;
494+
495+ act ( ( ) => {
496+ store . set ( terminalPreferencesAtom , { copyOnSelect : false } ) ;
497+ store . set ( terminalPreferencesAtom , { copyOnSelect : true } ) ;
498+ } ) ;
499+
500+ await act ( async ( ) => {
501+ resolveSettingsGet ?.( {
502+ "appearance.terminalCopyOnSelect" : false ,
503+ } ) ;
504+ await settingsGetPromise ;
505+ } ) ;
506+
507+ expect ( store . get ( terminalPreferencesAtom ) ) . toEqual ( { copyOnSelect : true } ) ;
508+ } ) ;
509+
377510 it ( "marks the session authenticated when /auth/status confirms an existing server session" , async ( ) => {
378511 globalThis . fetch = vi . fn ( ) . mockResolvedValue ( {
379512 json : async ( ) => ( { authEnabled : true , authenticated : true } ) ,
0 commit comments