koala73/worldmonitor · warning

Failed to load status

Error message

Failed to load status

What it means

ServiceStatusPanel.fetchStatus throws 'Failed to load status' when fetchServiceStatuses() returns a payload that is not success or contains zero services AND the panel has no previously-loaded good data to fall back on (src/components/ServiceStatusPanel.ts:45). It represents the first-load failure case: there is nothing to render, so the panel raises to switch into its error rendering path (caught immediately in the same method, setting this.error).

Solutions

  1. Check the service-status API endpoint responds with { success: true, services: [...] } (curl the endpoint in the same environment; verify api/ route and any worker feeding it).
  2. Confirm backend/worker deployment health — an empty services list usually means the status collector hasn't run or returned nothing; check worker logs/seed keys.
  3. Verify network reachability and correct base URL for the API in the environment (desktop: local sidecar running; web: same-origin /api).
  4. Handle the panel's error state gracefully in UI — this throw is caught internally and rendered as 'Failed to load', so ensure callers of mountPanel/fetchStatus don't double-handle.
  5. If data is intermittent, add a retry/backoff around fetchServiceStatuses before first successful render.

Example fix

// before
if (!data.success || data.services.length === 0) {
  if (this.services.length > 0) { keptLastGood = true; return false; }
  throw new Error('Failed to load status');
}
// after
if (!data.success || data.services.length === 0) {
  if (this.services.length > 0) { keptLastGood = true; return false; }
  this.error = t('common.failedToLoad');
  console.warn('[ServiceStatus] empty/unsuccessful status payload:', data);
  return true; // render error state without throwing
}
Defensive patterns

Strategy: fallback

Validate before calling

const data = await fetchServiceStatuses();
if (!data?.success || !Array.isArray(data.services) || data.services.length === 0) {
  // render empty/error state before the panel throws
}

Type guard

function isServiceStatusPayload(v: unknown): v is { success: true; services: Array<{ name: string; status: string; category: string }> } {
  const d = v as any;
  return !!d && d.success === true && Array.isArray(d.services) && d.services.length > 0;
}

Try / catch

const ok = await panel.fetchStatus(); // method catches internally
if (!ok && panelHasErrorState) renderErrorPlaceholder('Status unavailable');

Prevention

When it happens

Trigger: Calling fetchStatus() on first mount (services array empty) when the /api service-status fetch returns { success: false } or an empty services list — e.g. the status endpoint is down, returns an error envelope, or the upstream status aggregator yields no services. If services.length > 0 the panel silently keeps last-good data instead of throwing.

Common situations: Panel mounted while the backend/API route is misconfigured or unreachable (404/500 wrapped as success:false); the status worker hasn't produced any services yet on a fresh deployment; network offline at first paint so the fetch helper resolves with a failure envelope rather than throwing.

Related errors


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22). Data as JSON: /api/errors/b2e35c58dabd25cb. Report an issue: GitHub.

Appendix: source

Thrown at src/components/ServiceStatusPanel.ts:45

  private error: string | null = null;
  private filter: CategoryFilter = 'all';
  constructor() {
    super({ id: 'service-status', title: t('panels.serviceStatus'), showCount: false, infoTooltip: t('components.serviceStatus.infoTooltip') });
  }

  private lastServicesJson = '';

  public async fetchStatus(): Promise<boolean> {
    let keptLastGood = false;
    try {
      const data = await fetchServiceStatuses();
      if (!this.element?.isConnected) return false;
      if (!data.success || data.services.length === 0) {
        if (this.services.length > 0) {
          keptLastGood = true;
          return false;
        }
        throw new Error('Failed to load status');
      }

      const fingerprint = data.services.map(s => `${s.name}:${s.status}`).join(',');
      const changed = fingerprint !== this.lastServicesJson;
      this.lastServicesJson = fingerprint;
      this.services = data.services;
      this.error = null;
      return changed;
    } catch (err) {
      if (this.isAbortError(err)) return false;
      if (!this.element?.isConnected) return false;
      this.error = t('common.failedToLoad');
      console.error('[ServiceStatus] Fetch error:', err);
      return true;
    } finally {
      this.loading = false;
      if (this.element?.isConnected && !keptLastGood) {
        this.render();

View on GitHub (pinned to e586b8b4b8)