@@ -181,6 +181,10 @@ const App: React.FC = () => {
181181 const autoCheckIntervalRef = useRef < number | null > ( null ) ;
182182 const isAutoCheckingRef = useRef ( false ) ;
183183 const [ updateReady , setUpdateReady ] = useState ( false ) ;
184+ const [ updateStatus , setUpdateStatus ] = useState < UpdateStatusMessage | null > ( null ) ;
185+ const [ isUpdateBannerVisible , setIsUpdateBannerVisible ] = useState ( false ) ;
186+ const [ autoInstallScheduled , setAutoInstallScheduled ] = useState ( false ) ;
187+ const autoInstallTimeoutRef = useRef < number | null > ( null ) ;
184188
185189 // New states for deeper VCS integration
186190 const [ detailedStatuses , setDetailedStatuses ] = useState < Record < string , DetailedStatus | null > > ( { } ) ;
@@ -254,6 +258,70 @@ const App: React.FC = () => {
254258 onCancel : ( ) => { } ,
255259 } ) ;
256260
261+ const cancelAutoInstall = useCallback ( ( ) => {
262+ if ( autoInstallTimeoutRef . current ) {
263+ window . clearTimeout ( autoInstallTimeoutRef . current ) ;
264+ autoInstallTimeoutRef . current = null ;
265+ }
266+ setAutoInstallScheduled ( false ) ;
267+ } , [ ] ) ;
268+
269+ const handleRestartAndUpdate = useCallback ( ( ) => {
270+ cancelAutoInstall ( ) ;
271+ if ( window . electronAPI ?. restartAndInstallUpdate ) {
272+ setToast ( { message : 'Restarting to install the latest update…' , type : 'info' } ) ;
273+ window . electronAPI . restartAndInstallUpdate ( ) ;
274+ } else {
275+ setToast ( { message : 'Could not restart. Please restart the app manually.' , type : 'error' } ) ;
276+ }
277+ } , [ cancelAutoInstall , setToast ] ) ;
278+
279+ const scheduleAutoInstall = useCallback ( ( ) => {
280+ cancelAutoInstall ( ) ;
281+ autoInstallTimeoutRef . current = window . setTimeout ( ( ) => {
282+ setAutoInstallScheduled ( false ) ;
283+ handleRestartAndUpdate ( ) ;
284+ } , 8000 ) ;
285+ setAutoInstallScheduled ( true ) ;
286+ } , [ cancelAutoInstall , handleRestartAndUpdate ] ) ;
287+
288+ const handleAutoInstallPreferenceChange = useCallback ( ( mode : GlobalSettings [ 'autoInstallUpdates' ] ) => {
289+ if ( mode === settings . autoInstallUpdates ) {
290+ return ;
291+ }
292+ saveSettings ( { ...settings , autoInstallUpdates : mode } ) ;
293+ if ( mode === 'manual' ) {
294+ cancelAutoInstall ( ) ;
295+ setToast ( {
296+ message : 'Automatic installation disabled. Use the Update Ready controls when you want to apply the update.' ,
297+ type : 'info' ,
298+ } ) ;
299+ } else {
300+ setToast ( {
301+ message : 'Updates will now install automatically as soon as they finish downloading.' ,
302+ type : 'success' ,
303+ } ) ;
304+ }
305+ } , [ cancelAutoInstall , saveSettings , settings , setToast ] ) ;
306+
307+ const handleDeferUpdate = useCallback ( ( ) => {
308+ if ( settings . autoInstallUpdates === 'auto' ) {
309+ handleAutoInstallPreferenceChange ( 'manual' ) ;
310+ }
311+ cancelAutoInstall ( ) ;
312+ setIsUpdateBannerVisible ( false ) ;
313+ setToast ( {
314+ message : 'Update postponed. Use the “Update Ready” control in the title bar to install whenever you are ready.' ,
315+ type : 'info' ,
316+ } ) ;
317+ } , [ cancelAutoInstall , handleAutoInstallPreferenceChange , settings . autoInstallUpdates , setToast ] ) ;
318+
319+ const handleShowUpdateDetails = useCallback ( ( ) => {
320+ if ( updateReady ) {
321+ setIsUpdateBannerVisible ( true ) ;
322+ }
323+ } , [ updateReady ] ) ;
324+
257325 useEffect ( ( ) => {
258326 if ( ! instrumentation ) {
259327 return ;
@@ -467,31 +535,74 @@ const App: React.FC = () => {
467535 // Effect for auto-updater
468536 useEffect ( ( ) => {
469537 const handleUpdateStatus = ( _event : any , data : UpdateStatusMessage ) => {
470- logger . info ( `Update status change received: ${ data . status } ` , data ) ;
471- switch ( data . status ) {
472- case 'available' :
473- setToast ( { message : data . message , type : 'info' } ) ;
474- break ;
475- case 'downloaded' :
476- setToast ( { message : data . message , type : 'success' } ) ;
477- setUpdateReady ( true ) ;
478- break ;
479- case 'error' :
480- setToast ( { message : data . message , type : 'error' } ) ;
481- break ;
482- }
538+ logger . info ( `Update status change received: ${ data . status } ` , data ) ;
539+ setUpdateStatus ( data ) ;
540+
541+ switch ( data . status ) {
542+ case 'checking' :
543+ cancelAutoInstall ( ) ;
544+ setUpdateReady ( false ) ;
545+ setIsUpdateBannerVisible ( false ) ;
546+ break ;
547+ case 'available' :
548+ setToast ( { message : data . message , type : 'info' } ) ;
549+ break ;
550+ case 'downloaded' :
551+ setUpdateReady ( true ) ;
552+ setIsUpdateBannerVisible ( true ) ;
553+ if ( settings . autoInstallUpdates === 'auto' ) {
554+ setToast ( { message : 'Update downloaded. Restarting automatically in a few seconds…' , type : 'info' } ) ;
555+ scheduleAutoInstall ( ) ;
556+ } else {
557+ cancelAutoInstall ( ) ;
558+ setToast ( { message : data . message , type : 'success' } ) ;
559+ }
560+ break ;
561+ case 'error' :
562+ cancelAutoInstall ( ) ;
563+ setUpdateReady ( false ) ;
564+ setIsUpdateBannerVisible ( false ) ;
565+ setToast ( { message : data . message , type : 'error' } ) ;
566+ break ;
567+ default :
568+ break ;
569+ }
483570 } ;
484571
485572 if ( window . electronAPI ?. onUpdateStatusChange ) {
486- window . electronAPI . onUpdateStatusChange ( handleUpdateStatus ) ;
573+ window . electronAPI . onUpdateStatusChange ( handleUpdateStatus ) ;
487574 }
488575
489576 return ( ) => {
490- if ( window . electronAPI ?. removeUpdateStatusChangeListener ) {
491- window . electronAPI . removeUpdateStatusChangeListener ( handleUpdateStatus ) ;
492- }
577+ if ( window . electronAPI ?. removeUpdateStatusChangeListener ) {
578+ window . electronAPI . removeUpdateStatusChangeListener ( handleUpdateStatus ) ;
579+ }
493580 } ;
494- } , [ logger ] ) ;
581+ } , [ logger , cancelAutoInstall , scheduleAutoInstall , settings . autoInstallUpdates , setToast ] ) ;
582+
583+ useEffect ( ( ) => {
584+ if ( ! updateReady ) {
585+ return ;
586+ }
587+
588+ if ( settings . autoInstallUpdates === 'auto' ) {
589+ if ( ! autoInstallScheduled ) {
590+ scheduleAutoInstall ( ) ;
591+ }
592+ } else if ( autoInstallScheduled ) {
593+ cancelAutoInstall ( ) ;
594+ }
595+ } , [
596+ updateReady ,
597+ settings . autoInstallUpdates ,
598+ autoInstallScheduled ,
599+ scheduleAutoInstall ,
600+ cancelAutoInstall ,
601+ ] ) ;
602+
603+ useEffect ( ( ) => ( ) => {
604+ cancelAutoInstall ( ) ;
605+ } , [ cancelAutoInstall ] ) ;
495606
496607 // Effect to check local paths
497608 useEffect ( ( ) => {
@@ -1319,15 +1430,6 @@ const App: React.FC = () => {
13191430 }
13201431 } , [ ] ) ;
13211432
1322- const handleRestartAndUpdate = useCallback ( ( ) => {
1323- if ( window . electronAPI ?. restartAndInstallUpdate ) {
1324- window . electronAPI . restartAndInstallUpdate ( ) ;
1325- } else {
1326- setToast ( { message : 'Could not restart. Please restart the app manually.' , type : 'error' } ) ;
1327- }
1328- } , [ ] ) ;
1329-
1330-
13311433 const latestLog = useMemo ( ( ) => {
13321434 const allLogs = Object . values ( logs ) . flat ( ) ;
13331435 if ( allLogs . length === 0 ) return null ;
@@ -1428,9 +1530,22 @@ const App: React.FC = () => {
14281530 isCheckingAll = { isCheckingAll }
14291531 onToggleAllCategories = { toggleAllCategoriesCollapse }
14301532 canCollapseAll = { canCollapseAll }
1533+ updateReady = { updateReady }
1534+ onInstallUpdate = { handleRestartAndUpdate }
1535+ onShowUpdateDetails = { handleShowUpdateDetails }
14311536 />
14321537 < div className = "flex-1 flex flex-col min-h-0" >
1433- { updateReady && < UpdateBanner onInstall = { handleRestartAndUpdate } /> }
1538+ { updateReady && isUpdateBannerVisible && updateStatus ?. status === 'downloaded' && (
1539+ < UpdateBanner
1540+ version = { updateStatus . version }
1541+ message = { updateStatus . message }
1542+ autoInstallMode = { settings . autoInstallUpdates }
1543+ autoInstallScheduled = { autoInstallScheduled }
1544+ onChangeMode = { handleAutoInstallPreferenceChange }
1545+ onInstallNow = { handleRestartAndUpdate }
1546+ onInstallLater = { handleDeferUpdate }
1547+ />
1548+ ) }
14341549 < main
14351550 className = { mainContentClass }
14361551 data-automation-id = "main-content"
0 commit comments