{"record":{"id":"d2b83600dacc0dbe","repo":"actualbudget/actual","slug":"widget-not-found-id","errorCode":null,"errorMessage":"Widget not found: ${id}","messagePattern":"Widget not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/dashboard/app.ts","lineNumber":241,"sourceCode":"async function removeDashboardWidget(widgetId: string) {\n  await db.delete_('dashboard', widgetId);\n}\n\nasync function copyDashboardWidget({\n  id,\n  targetDashboardPageId,\n}: {\n  id: string;\n  targetDashboardPageId: string;\n}) {\n  // Get the widget to copy\n  const widget = await db.first<db.DbDashboard>(\n    'SELECT * FROM dashboard WHERE id = ? AND tombstone = 0',\n    [id],\n  );\n\n  if (!widget) {\n    throw new Error(`Widget not found: ${id}`);\n  }\n\n  await batchMessages(async () => {\n    // Insert the widget to target dashboard\n    if (isWidgetType(widget.type)) {\n      const newWidget = {\n        type: widget.type,\n        width: widget.width,\n        height: widget.height,\n        meta: widget.meta ? JSON.parse(widget.meta) : {},\n        dashboard_page_id: targetDashboardPageId,\n      };\n      await addDashboardWidget(newWidget);\n    } else {\n      throw new Error(`Unsupported widget type: ${widget.type}`);\n    }\n  });\n}","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/dashboard/app.ts#L223-L259","documentation":"copyDashboardWidget looks up the source widget by id in the dashboard table (tombstone = 0). If no live row matches the supplied id, it throws `Widget not found: ${id}` before performing the copy.","triggerScenarios":"Calling the copy-dashboard-widget handler with an id that was deleted (tombstoned), a fabricated id, or an id from a different budget file.","commonSituations":"Scripts or plugins caching widget ids across budget resets; UI race where the widget was deleted before the copy request; copying ids between two budget files.","solutions":["Verify the widget id exists: run an AQL/SQL query on the dashboard table filtering tombstone = 0.","Re-fetch the current widget list (e.g. via get-dashboard or a query) and use a fresh id.","If the widget was tombstoned, re-create it rather than copying it."],"exampleFix":"// before\nawait send('dashboard-copy-widget', { id: staleId });\n// after\nconst widgets = await send('dashboard-get-widgets');\nif (widgets.some(w => w.id === id)) {\n  await send('dashboard-copy-widget', { id });\n}","handlingStrategy":"validation","validationCode":"const widgets = await send('dashboard-get-widgets');\nif (!widgets.some(w => w.id === id)) {\n  throw new Error(`Widget ${id} does not exist; refetch before copying.`);\n}","typeGuard":"function widgetExists(widgets: { id: string }[], id: string): boolean {\n  return widgets.some(w => w.id === id);\n}","tryCatchPattern":"try {\n  await send('dashboard-copy-widget', { id, targetDashboardPageId });\n} catch (e) {\n  if (e.message.startsWith('Widget not found:')) {\n    console.error(`Widget ${id} is gone; refresh the widget list.`);\n  }\n}","preventionTips":["Always fetch fresh widget ids right before copying; don't cache across sessions.","Never reuse widget ids across budget files.","Handle deleted-widget races by refetching and retrying once."],"tags":["not-found","dashboard","lookup"],"backgroundTag":"entity-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}