{"record":{"id":"361116129ec59d66","repo":"koala73/worldmonitor","slug":"dashboard-is-no-longer-available-361116","errorCode":null,"errorMessage":"Dashboard is no longer available.","messagePattern":"Dashboard is no longer available\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/app/event-handlers.ts","lineNumber":1282,"sourceCode":"  openMissionPresetPickerForWebMcp(): boolean {\n    if (this.ctx.isDestroyed) return false;\n    const mobile = this.ctx.isMobile;\n    const anchor = document.getElementById(mobile ? 'mobileMenuMission' : 'missionPresetBtn');\n    this.openMissionPresetPopover(anchor, mobile, 'agent');\n    return this.missionPresetPopover !== null;\n  }\n\n  /**\n   * WebMCP entry: apply a bundled mission preset through the same path as the\n   * visible mission control. Snapshots prior dashboard state and restores it\n   * if the commit throws before completion.\n   */\n  applyMissionPresetForWebMcp(presetId: MissionPresetId): {\n    changed: boolean;\n    priorPresetId: string | null;\n  } {\n    if (this.ctx.isDestroyed) {\n      throw new Error('Dashboard is no longer available.');\n    }\n\n    const snapshot = this.snapshotMissionDashboardState();\n    try {\n      this.applyMissionPreset(presetId, 'agent');\n      const nextPresetId = loadStoredMissionPreset()?.id ?? null;\n      const mapState = this.ctx.map?.getState();\n      const changed = snapshot.presetId !== nextPresetId\n        || JSON.stringify(snapshot.panelSettings) !== JSON.stringify(this.ctx.panelSettings)\n        || JSON.stringify(snapshot.mapLayers) !== JSON.stringify(this.ctx.mapLayers)\n        || snapshot.mapView !== (mapState?.view ?? snapshot.mapView)\n        || snapshot.mapZoom !== (mapState?.zoom ?? snapshot.mapZoom)\n        || snapshot.timeRange !== (mapState?.timeRange ?? snapshot.timeRange);\n      return { changed, priorPresetId: snapshot.presetId };\n    } catch (error) {\n      this.restoreMissionDashboardState(snapshot);\n      throw error;\n    }","sourceCodeStart":1264,"sourceCodeEnd":1300,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/src/app/event-handlers.ts#L1264-L1300","documentation":"applyMissionPresetForWebMcp is a WebMCP/agent entry point on EventHandlerManager that applies a bundled mission preset to the live dashboard. Before doing any work it checks this.ctx.isDestroyed; if the dashboard context has already been torn down there is no live state to mutate, so it throws 'Dashboard is no longer available.' instead of operating on a dead context. It is a lifecycle guard, not a data error.","triggerScenarios":"Calling dashboard.applyMissionPresetForWebMcp(presetId) after the dashboard was destroyed — e.g. an automation/MCP agent session holding a stale reference to the EventHandlerManager while the page is navigating away, hot-reloading, or the dashboard's destroy() has already run.","commonSituations":"MCP/agent tools cached across page reloads; single-page-app navigation that tears down the dashboard while a queued agent command still runs; dev-mode hot module replacement replacing the app instance; calling the API from a setTimeout/promise that resolves after teardown.","solutions":["Check the destroyed flag before calling: only invoke applyMissionPresetForWebMcp while the dashboard context is alive.","Re-acquire a fresh dashboard/EventHandlerManager reference after navigation or reload instead of reusing a cached one.","Catch this error in agent tooling and treat it as a no-op 'page gone' result rather than a preset failure, then re-attach and retry on the new instance.","Debounce/queue agent commands so late-arriving preset commands are dropped once teardown begins."],"exampleFix":"// before\nconst result = cachedDashboard.applyMissionPresetForWebMcp('crisis-watch');\n// after\nif (!cachedDashboard || cachedDashboard.ctx.isDestroyed) {\n  cachedDashboard = attachDashboard(); // re-acquire after teardown\n}\nconst result = cachedDashboard.applyMissionPresetForWebMcp('crisis-watch');","handlingStrategy":"try-catch","validationCode":"if (dashboard && !dashboard.ctx.isDestroyed) {\n  // safe to call\n}\n","typeGuard":"function isDashboardAlive(d: { ctx: { isDestroyed: boolean } } | null | undefined): d is { ctx: { isDestroyed: false } } {\n  return !!d && !d.ctx.isDestroyed;\n}\n","tryCatchPattern":"try {\n  const r = dashboard.applyMissionPresetForWebMcp(presetId);\n  return r;\n} catch (e) {\n  if (e instanceof Error && e.message === 'Dashboard is no longer available.') {\n    return { changed: false, priorPresetId: null }; // treat as page-gone no-op\n  }\n  throw e;\n}\n","preventionTips":["Never cache the dashboard/EventHandlerManager reference across navigations or HMR boundaries","Check ctx.isDestroyed immediately before every agent entry-point call","Drop queued agent commands when a teardown event fires","Re-resolve the dashboard instance after any page-level reload"],"tags":["lifecycle","stale-reference","webmcp","dashboard"],"backgroundTag":"stale-object-reference-after-teardown","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}