{"record":{"id":"db3de5d0aa221b3e","repo":"koala73/worldmonitor","slug":"app-destroyed-db3de5","errorCode":"app_destroyed","errorMessage":"Dashboard is no longer available.","messagePattern":"Dashboard is no longer available\\.","errorType":"exception","errorClass":"DashboardBindingError","httpStatus":null,"severity":"error","filePath":"src/app/webmcp-dashboard.ts","lineNumber":40,"sourceCode":"    ...result,\n    ok: false,\n    status: 'denied',\n    reason,\n    message: reason === 'viewport_superseded'\n      ? 'Map movement was superseded by a newer viewport action.'\n      : reason === 'renderer_changed'\n        ? 'Map renderer changed before the movement completed.'\n        : 'Map movement was interrupted before it completed.',\n    targets: result.targets.map((target) => ({ ...target, status: 'denied', reason })),\n  };\n}\n\nexport function getWebMcpDashboardContext(\n  ctx: AppContext,\n  variant: string,\n): DashboardContextSnapshot {\n  if (ctx.isDestroyed) {\n    throw new DashboardBindingError('app_destroyed', 'Dashboard is no longer available.');\n  }\n  if (!ctx.map) {\n    throw new DashboardBindingError('map_unavailable', 'Map is not available.');\n  }\n\n  const mapState = ctx.map.getState();\n  const center = ctx.map.getCenter();\n\n  return {\n    variant,\n    map: {\n      view: mapState.view,\n      center,\n      zoom: mapState.zoom,\n      timeRange: mapState.timeRange,\n      enabledLayers: Object.entries(mapState.layers)\n        .filter(([, enabled]) => enabled === true)\n        .map(([layer]) => layer),","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/app/webmcp-dashboard.ts#L22-L58","documentation":"DashboardBindingError 'app_destroyed' thrown by getWebMcpDashboardContext() when ctx.isDestroyed is true. The context snapshot builder must never read UI state from a destroyed app, so it refuses before touching ctx.panels or ctx.map. This is the snapshot-getter sibling of the tool-binding guards in App.ts.","triggerScenarios":"An agent calls get_dashboard_context after the App was destroyed — e.g. the tool call raced page unload, HMR teardown, or explicit app.destroy(). The first thing the function does is refuse dead contexts; note the very next guard throws 'map_unavailable' for a live app without a map, so hitting this message specifically means the app itself is gone.","commonSituations":"Agent sessions that outlive the dashboard (client keeps calling tools after the tab started unloading); tests exercising context snapshots against a destroyed fixture; stale tool registrations surviving an HMR boundary.","solutions":["Check ctx.isDestroyed before requesting a context snapshot and skip/queue the call","Catch DashboardBindingError, branch on code 'app_destroyed', and drop or re-derive the tool bindings against a new App instance","Unregister WebMCP tools in destroy() (the controller stored from registerWebMcpTools exists for exactly this) so agents cannot reach a dead context"],"exampleFix":"// before\nconst snapshot = getWebMcpDashboardContext(ctx, variant); // throws on destroyed ctx\n\n// after\nif (ctx.isDestroyed) {\n  return { ok: false, status: 'denied', reason: 'app_destroyed' } as const;\n}\nconst snapshot = getWebMcpDashboardContext(ctx, variant);","handlingStrategy":"type-guard","validationCode":"function isContextUsable(ctx: AppContext): boolean {\n  return !ctx.isDestroyed;\n}\nif (!isContextUsable(ctx)) return { ok: false, status: 'denied', reason: 'app_destroyed' };","typeGuard":"function hasLiveAppContext(ctx: AppContext): boolean { return !ctx.isDestroyed; }","tryCatchPattern":"try { return getWebMcpDashboardContext(ctx, variant); } catch (e) { if (e instanceof DashboardBindingError && e.code === 'app_destroyed') return DESTROYED_SNAPSHOT; throw e; }","preventionTips":["Check ctx.isDestroyed before reading any snapshot state","Unregister WebMCP tools in destroy() via the stored controller so agents cannot reach a dead app","Version snapshots by app instance so stale ones are dropped"],"tags":["webmcp","agent-tools","lifecycle"],"backgroundTag":"stale-app-instance","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}