{"record":{"id":"b2e35c58dabd25cb","repo":"koala73/worldmonitor","slug":"failed-to-load-status","errorCode":null,"errorMessage":"Failed to load status","messagePattern":"Failed to load status","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/components/ServiceStatusPanel.ts","lineNumber":45,"sourceCode":"  private error: string | null = null;\n  private filter: CategoryFilter = 'all';\n  constructor() {\n    super({ id: 'service-status', title: t('panels.serviceStatus'), showCount: false, infoTooltip: t('components.serviceStatus.infoTooltip') });\n  }\n\n  private lastServicesJson = '';\n\n  public async fetchStatus(): Promise<boolean> {\n    let keptLastGood = false;\n    try {\n      const data = await fetchServiceStatuses();\n      if (!this.element?.isConnected) return false;\n      if (!data.success || data.services.length === 0) {\n        if (this.services.length > 0) {\n          keptLastGood = true;\n          return false;\n        }\n        throw new Error('Failed to load status');\n      }\n\n      const fingerprint = data.services.map(s => `${s.name}:${s.status}`).join(',');\n      const changed = fingerprint !== this.lastServicesJson;\n      this.lastServicesJson = fingerprint;\n      this.services = data.services;\n      this.error = null;\n      return changed;\n    } catch (err) {\n      if (this.isAbortError(err)) return false;\n      if (!this.element?.isConnected) return false;\n      this.error = t('common.failedToLoad');\n      console.error('[ServiceStatus] Fetch error:', err);\n      return true;\n    } finally {\n      this.loading = false;\n      if (this.element?.isConnected && !keptLastGood) {\n        this.render();","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/src/components/ServiceStatusPanel.ts#L27-L63","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","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.","Verify network reachability and correct base URL for the API in the environment (desktop: local sidecar running; web: same-origin /api).","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.","If data is intermittent, add a retry/backoff around fetchServiceStatuses before first successful render."],"exampleFix":"// before\nif (!data.success || data.services.length === 0) {\n  if (this.services.length > 0) { keptLastGood = true; return false; }\n  throw new Error('Failed to load status');\n}\n// after\nif (!data.success || data.services.length === 0) {\n  if (this.services.length > 0) { keptLastGood = true; return false; }\n  this.error = t('common.failedToLoad');\n  console.warn('[ServiceStatus] empty/unsuccessful status payload:', data);\n  return true; // render error state without throwing\n}","handlingStrategy":"fallback","validationCode":"const data = await fetchServiceStatuses();\nif (!data?.success || !Array.isArray(data.services) || data.services.length === 0) {\n  // render empty/error state before the panel throws\n}","typeGuard":"function isServiceStatusPayload(v: unknown): v is { success: true; services: Array<{ name: string; status: string; category: string }> } {\n  const d = v as any;\n  return !!d && d.success === true && Array.isArray(d.services) && d.services.length > 0;\n}","tryCatchPattern":"const ok = await panel.fetchStatus(); // method catches internally\nif (!ok && panelHasErrorState) renderErrorPlaceholder('Status unavailable');","preventionTips":["Verify the service-status endpoint returns success:true with non-empty services before mounting the panel","Keep last-good data in the panel so transient failures degrade gracefully instead of throwing on first load","Monitor the status worker so an empty services list is detected server-side","Add a retry with backoff before declaring first-load failure"],"tags":["ui","fetch","panel","fallback"],"backgroundTag":"api-error-response","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}