fix(ui): a warming-up app reads as "starting", not "App not reachable"
Demo images / Build & push demo images (push) Failing after 3m45s

A container that is up but hasn't answered its probe yet rendered the hard
failure overlay — padlock icon, "App not reachable", "the container is
stopped". Both bitcoind (RPC -28 for its whole warm-up) and lnd (unreachable
until the wallet unlocks) sit in that window on every boot, so the node
looked broken while it was working normally.

The retry machinery was already correct: 6 × 10s of automatic re-checks, and
the app appears on its own when it answers. Only the headline was wrong. While
those retries are in flight AND the package reports running/starting/restarting
(or health "starting"), the overlay now shows the app's own pulsing icon,
"<App> is starting…", and says the container is running. Once retries are
exhausted the failure is real again and the original copy returns.

Follows the ElectrumX sync-screen precedent already in this file, which
suppresses the same overlay for the same reason. The explicit blocked-reason
and must-open-new-tab paths are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-06 14:28:17 -04:00
co-authored by Claude Opus 5
parent ab69400956
commit c65ee03a5c
3 changed files with 100 additions and 4 deletions
+22 -1
View File
@@ -41,6 +41,7 @@
:refresh-key="refreshKey"
:blocked-reason="blockedReason"
:blocked-title="blockedTitle"
:warming-up="warmingUp"
:electrs-sync="electrsSync"
@iframe-load="onLoad"
@iframe-error="onError"
@@ -112,6 +113,7 @@ import {
initialDisplayMode, resolveAppUrl, resolveAppTitle,
} from './appSession/appSessionConfig'
import { launchBlockedReason, resolveAppIcon } from './apps/appsConfig'
import { PackageState } from '@/types/api'
import { useAppIdentity } from './appSession/useAppIdentity'
import { useNostrBridge } from './appSession/useNostrBridge'
import { openExternalUrl, openInAppOrNewTab } from '@/utils/openExternal'
@@ -168,6 +170,25 @@ const appIcon = computed(() =>
: `/assets/img/app-icons/${appId.value}.png`
)
const blockedReason = computed(() => launchBlockedReason(appId.value, packageEntry.value))
// A container that is up but not yet answering its probe is STARTING, not
// broken — bitcoind serves RPC error -28 for its whole warm-up and lnd is
// unreachable until the wallet unlocks, so both spent that window reading as
// a hard "App not reachable" failure. The retry machinery below already
// tolerates it (6 × 10s); this only makes the headline tell the truth while
// those retries are still in flight. Once they are exhausted, the failure is
// real again and the copy reverts.
const MAX_AUTO_RETRIES = 6
const warmingUp = computed(() =>
iframeBlocked.value &&
!mustOpenNewTab.value &&
!blockedReason.value &&
autoRetryCount.value < MAX_AUTO_RETRIES &&
(packageEntry.value?.state === PackageState.Running ||
packageEntry.value?.state === PackageState.Starting ||
packageEntry.value?.state === PackageState.Restarting ||
packageEntry.value?.health === 'starting')
)
const blockedTitle = computed(() => appId.value === 'fedimint' || appId.value === 'fedimintd' ? 'Waiting for Bitcoin sync' : 'App not ready')
// Reactive so the overlay/teleport/footer/animation decisions track the live
// viewport (and match the CSS `md` breakpoint) instead of a stale one-shot read.
@@ -350,7 +371,7 @@ function onError() {
isRefreshing.value = false
iframeBlocked.value = true
// Auto-retry up to 6 times (60s total) for apps that are still starting
if (!mustOpenNewTab.value && autoRetryCount.value < 6) {
if (!mustOpenNewTab.value && autoRetryCount.value < MAX_AUTO_RETRIES) {
autoRetryId = setTimeout(() => {
autoRetryCount.value++
refresh()