{"record":{"id":"f67a6dd8b1666b48","repo":"moeru-ai/airi","slug":"componentname-is-required-to-spawn-a-widget","errorCode":null,"errorMessage":"componentName is required to spawn a widget.","messagePattern":"componentName is required to spawn a widget\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/renderer/stores/tools/builtin/widgets.ts","lineNumber":255,"sourceCode":"\n  return normalizeWidgetWindowSize(componentProps.windowSize)\n}\n\nfunction sanitizeComponentPropsForDispatch(componentName: string | undefined, componentProps: Record<string, any>) {\n  if (componentName?.trim().toLowerCase() !== 'extension-ui')\n    return componentProps\n\n  return sanitizeExtensionUiDispatchProps(componentProps)\n}\n\nexport async function executeWidgetAction(input: WidgetActionInput, deps?: { invokers?: WidgetInvokers }) {\n  const invokers = resolveInvokers(deps?.invokers)\n  const normalizedId = input.id?.trim() || undefined\n\n  switch (input.action) {\n    case 'spawn': {\n      if (!input.componentName?.trim())\n        throw new Error('componentName is required to spawn a widget.')\n\n      const componentProps = normalizeComponentProps(input.componentProps)\n      const sanitizedComponentProps = sanitizeComponentPropsForDispatch(input.componentName, componentProps)\n      const windowSize = resolveWindowSize(input.componentName, sanitizedComponentProps, input.windowSize)\n      const ttlMs = input.ttlSeconds ? Math.floor(input.ttlSeconds * 1000) : 0\n      const id = await invokers.addWidget({\n        id: normalizedId,\n        componentName: input.componentName,\n        componentProps: sanitizedComponentProps,\n        size: input.size ?? 'm',\n        ...(input.alwaysOnTop === undefined ? {} : { alwaysOnTop: input.alwaysOnTop }),\n        ...(windowSize === undefined ? {} : { windowSize }),\n        ttlMs,\n      })\n\n      return `Spawned widget${id ? ` (${id})` : ''}.`\n    }\n    case 'update': {","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/apps/stage-tamagotchi/src/renderer/stores/tools/builtin/widgets.ts#L237-L273","documentation":"Thrown by executeWidgetAction in the Stage desktop widget manager when action='spawn' but componentName is missing, empty, or whitespace-only. The spawn action must render a concrete Vue component, so the guard at widgets.ts:255 rejects any input where input.componentName?.trim() is falsy. It is a pre-dispatch validation gate before normalizeComponentProps, sanitization, and the IPC addWidget call.","triggerScenarios":"Calling executeWidgetAction({ action: 'spawn' }) or the stage_widgets tool with componentName omitted, set to '', or set to '   '. Also reached when an LLM driving the stage_widgets tool emits a spawn turn without populating componentName because the JSON schema field defaulted to empty.","commonSituations":"An LLM tool-call omits componentName while choosing action='spawn'; a test fixture constructs a WidgetActionInput spawn variant without componentName; a caller copies an 'open' or 'clear' payload shape (where componentName is optional) into a spawn call.","solutions":["Pass a non-empty componentName such as 'weather' or 'extension-ui' when action is 'spawn'.","If building the call from an LLM tool result, validate componentName is present before dispatching, and fall back to prompting the model to supply it.","For programmatic callers, narrow on the discriminated union: the spawn variant of WidgetActionInput marks componentName as required string — satisfy the type checker and the runtime guard will pass."],"exampleFix":"// before\nawait executeWidgetAction({ action: 'spawn', id: 'w1', componentName: '', componentProps: {} })\n// after\nawait executeWidgetAction({ action: 'spawn', id: 'w1', componentName: 'weather', componentProps: { city: 'Tokyo' } })","handlingStrategy":"validation","validationCode":"function assertSpawnInput(input) {\n  if (input.action === 'spawn' && !input.componentName?.trim())\n    throw new Error('componentName is required to spawn a widget.')\n}\n// call before executeWidgetAction\nassertSpawnInput(input)","typeGuard":"function isSpawnReady(input) {\n  return input.action !== 'spawn' || Boolean(input.componentName?.trim())\n}","tryCatchPattern":"try {\n  await executeWidgetAction(input)\n} catch (e) {\n  if (e.message.includes('componentName is required'))\n    // prompt the LLM/caller for componentName\n  throw e\n}","preventionTips":["For LLM-driven calls, keep componentName in the tool schema as required and validate before dispatch.","Unit-test the spawn path with a missing componentName to confirm the guard fires deterministically."],"tags":["widget","validation","electron","stage-tamagotchi","ipc"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}