174174 outline-offset : 3px ;
175175 }
176176
177+ .mount-fallback__retry-status {
178+ margin : 12px 0 0 ;
179+ color : # 9fb0bd ;
180+ font-size : 0.85rem ;
181+ line-height : 1.4 ;
182+ }
183+
177184 html [data-theme-mode = "light" ] body .openclaw-mount-fallback-active {
178185 color : # 151b21 ;
179186 background : # f5f7fa ;
194201 html [data-theme-mode = "light" ] .mount-fallback__panel a {
195202 color : # 0369a1 ;
196203 }
204+
205+ html [data-theme-mode = "light" ] .mount-fallback__retry-status {
206+ color : # 6b7785 ;
207+ }
197208 </ style >
198209 </ head >
199210 < body >
209220 < div class ="mount-fallback__panel " tabindex ="-1 ">
210221 < p class ="mount-fallback__eyebrow "> OpenClaw Control UI</ p >
211222 < h1 id ="openclaw-mount-fallback-title "> Control UI did not start</ h1 >
212- < p >
223+ < p id =" openclaw-mount-fallback-reason " >
213224 The browser loaded the static page, but the app bundle did not register the
214225 < code > openclaw-app</ code > web component. A browser extension or early content script may
215226 be blocking module execution.
216227 </ p >
217- < ul >
228+ < ul id =" openclaw-mount-fallback-steps " >
218229 < li > Try again in a clean browser profile or private window.</ li >
219230 < li > Disable extensions that run on all pages, then reload this dashboard.</ li >
220231 < li >
@@ -239,6 +250,7 @@ <h1 id="openclaw-mount-fallback-title">Control UI did not start</h1>
239250 Keep waiting
240251 </ button >
241252 </ div >
253+ < p id ="openclaw-mount-retry-status " class ="mount-fallback__retry-status " hidden > </ p >
242254 </ div >
243255 </ section >
244256 < script >
@@ -251,9 +263,18 @@ <h1 id="openclaw-mount-fallback-title">Control UI did not start</h1>
251263 var panel = fallback . querySelector ( ".mount-fallback__panel" ) ;
252264 var retry = document . getElementById ( "openclaw-mount-retry" ) ;
253265 var wait = document . getElementById ( "openclaw-mount-wait" ) ;
266+ var retryStatus = document . getElementById ( "openclaw-mount-retry-status" ) ;
267+ var titleEl = document . getElementById ( "openclaw-mount-fallback-title" ) ;
268+ var reasonEl = document . getElementById ( "openclaw-mount-fallback-reason" ) ;
269+ var stepsEl = document . getElementById ( "openclaw-mount-fallback-steps" ) ;
254270 var rawDelay = Number ( fallback . getAttribute ( "data-openclaw-mount-timeout-ms" ) ) ;
255271 var delay = Number . isFinite ( rawDelay ) && rawDelay > 0 ? rawDelay : 12000 ;
256272 var timer ;
273+ var retryCount = 0 ;
274+ var maxRetries = 5 ;
275+ var backoffSchedule = [ 2000 , 4000 , 8000 , 16000 , 32000 ] ;
276+ var retrying = false ;
277+ var probeResult = null ; // null = not probed, "reachable", "unreachable"
257278
258279 function appMounted ( ) {
259280 try {
@@ -284,20 +305,158 @@ <h1 id="openclaw-mount-fallback-title">Control UI did not start</h1>
284305 panel . focus ( ) ;
285306 }
286307 }
308+ // Probe gateway reachability to distinguish restart from extension blocking
309+ probeGateway ( ) ;
310+ }
311+
312+ function probeGateway ( ) {
313+ if ( probeResult !== null ) return ; // already probed
314+ try {
315+ fetch ( window . location . href , {
316+ method : "GET" ,
317+ cache : "no-store" ,
318+ headers : { "X-OpenClaw-Probe" : "1" } ,
319+ } ) . then (
320+ function ( res ) {
321+ probeResult = res && res . ok ? "reachable" : "unreachable" ;
322+ updateFallbackMessage ( ) ;
323+ } ,
324+ function ( ) {
325+ probeResult = "unreachable" ;
326+ updateFallbackMessage ( ) ;
327+ } ,
328+ ) ;
329+ } catch ( e ) {
330+ probeResult = "unreachable" ;
331+ updateFallbackMessage ( ) ;
332+ }
333+ }
334+
335+ function updateFallbackMessage ( ) {
336+ if ( probeResult === null || ! titleEl || ! reasonEl || ! stepsEl ) return ;
337+ if ( probeResult === "unreachable" ) {
338+ titleEl . textContent = "Control UI is starting up" ;
339+ reasonEl . textContent =
340+ "The gateway is restarting or temporarily unavailable. The app bundle will be retried automatically — please wait." ;
341+ stepsEl . innerHTML =
342+ '<li>The gateway may take a few seconds to come back online.</li>' +
343+ '<li>If this persists after 30 seconds, try reloading the page.</li>' +
344+ '<li>See <a href="https://docs.openclaw.ai/web/control-ui#blank-control-ui-page" target="_blank" rel="noopener noreferrer">Control UI troubleshooting</a>.</li>' ;
345+ } else {
346+ titleEl . textContent = "Control UI did not start" ;
347+ reasonEl . textContent =
348+ "The browser loaded the static page, but the app bundle did not register the " +
349+ "openclaw-app web component. A browser extension or early content script may " +
350+ "be blocking module execution." ;
351+ stepsEl . innerHTML =
352+ '<li>Try again in a clean browser profile or private window.</li>' +
353+ '<li>Disable extensions that run on all pages, then reload this dashboard.</li>' +
354+ '<li>See <a href="https://docs.openclaw.ai/web/control-ui#blank-control-ui-page" target="_blank" rel="noopener noreferrer">Control UI troubleshooting</a>.</li>' ;
355+ }
287356 }
288357
289358 function armFallbackTimer ( ) {
290359 window . clearTimeout ( timer ) ;
291360 timer = window . setTimeout ( showFallback , delay ) ;
292361 }
293362
363+ function fetchAppBundle ( ) {
364+ try {
365+ return fetch ( "/src/main.ts" , { cache : "no-store" } ) . then (
366+ function ( res ) {
367+ return res && res . ok ;
368+ } ,
369+ function ( ) {
370+ return false ;
371+ } ,
372+ ) ;
373+ } catch ( e ) {
374+ return Promise . resolve ( false ) ;
375+ }
376+ }
377+
378+ function showRetryStatus ( message ) {
379+ if ( ! retryStatus ) return ;
380+ retryStatus . textContent = message ;
381+ retryStatus . hidden = false ;
382+ }
383+
384+ function hideRetryStatus ( ) {
385+ if ( ! retryStatus ) return ;
386+ retryStatus . hidden = true ;
387+ retryStatus . textContent = "" ;
388+ }
389+
390+ function performRetry ( ) {
391+ if ( retrying || appMounted ( ) ) return ;
392+ if ( retryCount >= maxRetries ) {
393+ showRetryStatus (
394+ "Automatic retry limit reached. Use \u201cTry again\u201d to reload the page." ,
395+ ) ;
396+ return ;
397+ }
398+ retrying = true ;
399+ var attempt = retryCount + 1 ;
400+ var backoff = backoffSchedule [ retryCount ] || 32000 ;
401+ showRetryStatus ( "Retrying (attempt " + attempt + " of " + maxRetries + ")\u2026" ) ;
402+ fetchAppBundle ( ) . then ( function ( ok ) {
403+ retrying = false ;
404+ retryCount ++ ;
405+ if ( ok && appMounted ( ) ) {
406+ hideRetryStatus ( ) ;
407+ hideFallback ( ) ;
408+ return ;
409+ }
410+ if ( ok && ! appMounted ( ) ) {
411+ // Bundle is fetchable but component hasn\u2019t registered yet \u2014 give it more time
412+ showRetryStatus (
413+ "Bundle fetched; waiting for app to initialize\u2026" ,
414+ ) ;
415+ timer = window . setTimeout ( performRetry , backoff ) ;
416+ return ;
417+ }
418+ // Fetch failed \u2014 gateway likely still restarting
419+ if ( retryCount >= maxRetries ) {
420+ showRetryStatus (
421+ "Automatic retry limit reached. Use \u201cTry again\u201d to reload the page." ,
422+ ) ;
423+ } else {
424+ showRetryStatus (
425+ "Gateway still unavailable. Next retry in " + Math . round ( backoff / 1000 ) + "s\u2026" ,
426+ ) ;
427+ timer = window . setTimeout ( performRetry , backoff ) ;
428+ }
429+ } ) ;
430+ }
431+
432+ function startRetrySequence ( ) {
433+ hideFallback ( ) ;
434+ hideRetryStatus ( ) ;
435+ retryCount = 0 ;
436+ retrying = false ;
437+ window . clearTimeout ( timer ) ;
438+ // Reset probe so we re-check gateway status on the next fallback
439+ probeResult = null ;
440+ performRetry ( ) ;
441+ // Arm a safety timer in case retries don\u2019t trigger fallback show
442+ timer = window . setTimeout ( function ( ) {
443+ if ( ! appMounted ( ) ) {
444+ showFallback ( ) ;
445+ if ( retryCount < maxRetries && ! retrying ) {
446+ performRetry ( ) ;
447+ }
448+ }
449+ } , delay ) ;
450+ }
451+
294452 armFallbackTimer ( ) ;
295453
296454 if ( window . customElements && typeof window . customElements . whenDefined === "function" ) {
297455 window . customElements . whenDefined ( tagName ) . then (
298456 function ( ) {
299457 window . clearTimeout ( timer ) ;
300458 hideFallback ( ) ;
459+ hideRetryStatus ( ) ;
301460 } ,
302461 function ( ) { } ,
303462 ) ;
@@ -310,8 +469,7 @@ <h1 id="openclaw-mount-fallback-title">Control UI did not start</h1>
310469 }
311470 if ( wait ) {
312471 wait . addEventListener ( "click" , function ( ) {
313- hideFallback ( ) ;
314- armFallbackTimer ( ) ;
472+ startRetrySequence ( ) ;
315473 } ) ;
316474 }
317475 } ) ( ) ;
0 commit comments