{"record":{"id":"fb435d62e6872f11","repo":"moeru-ai/airi","slug":"no-active-source-selected","errorCode":null,"errorMessage":"No active source selected","messagePattern":"No active source selected","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/renderer/composables/use-vision-screen-capture.ts","lineNumber":123,"sourceCode":"        ...source,\n        appIconURL: source.appIcon && source.appIcon.length > 0 ? createObjectUrlFromBytes(source.appIcon, 'image/png') : undefined,\n        thumbnailURL: source.thumbnail && source.thumbnail.length > 0 ? createObjectUrlFromBytes(source.thumbnail, 'image/jpeg') : undefined,\n      }))\n\n      const hasActiveSource = sources.value.some(source => source.id === activeSourceId.value)\n      const nextActiveSourceId = hasActiveSource ? activeSourceId.value : sources.value[0]?.id || ''\n      activeSourceId.value = nextActiveSourceId\n    }\n    finally {\n      isRefetching.value = false\n      hasFetchedOnce.value = true\n    }\n  }\n\n  async function startStream() {\n    const sourceId = activeSourceId.value\n    if (!sourceId)\n      throw new Error('No active source selected')\n\n    if (isActiveStream(activeStream.value) && activeStreamSourceId.value === sourceId)\n      return activeStream.value!\n\n    clearActiveStream()\n\n    const stream = await selectWithSource(\n      () => sourceId,\n      async () => await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false }),\n    )\n    if (!isActiveStream(stream)) {\n      stream.getTracks().forEach(track => track.stop())\n      throw new Error('Selected source did not provide a live video track')\n    }\n\n    activeStream.value = stream\n    activeStreamSourceId.value = sourceId\n    attachStreamLifecycle(stream, sourceId)","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/apps/stage-tamagotchi/src/renderer/composables/use-vision-screen-capture.ts#L105-L141","documentation":"Thrown by use-vision-screen-capture.startStream() when activeSourceId.value is empty. The composable requires a selected screen source before it can request a display-media stream; the guard fires before any getDisplayMedia call.","triggerScenarios":"startStream() called before fetchSources() resolved, or after sources came back empty, or when the UI never bound a selection to activeSourceId. Also if the user's permission prompt during enumeration returned zero sources.","commonSituations":"Race: component calls startStream on mount before the async source enumeration completes; user revoked screen-capture permission so sources list is empty; refetch failed silently leaving activeSourceId unset.","solutions":["Ensure fetchSources() has completed and sources.value is non-empty before enabling the start button.","Bind the source picker to activeSourceId and require a selection (or auto-select sources[0]) before startStream.","Gate startStream behind `if (!activeSourceId.value) return` or show a 'select a source' prompt.","Check that screen-capture permission was granted in the OS/browser."],"exampleFix":"// before\nasync function startStream() {\n  const sourceId = activeSourceId.value\n  if (!sourceId) throw new Error('No active source selected')\n}\n\n// after\nasync function startStream() {\n  if (!activeSourceId.value && sources.value[0])\n    activeSourceId.value = sources.value[0].id\n  const sourceId = activeSourceId.value\n  if (!sourceId) throw new Error('No active source selected')\n}","handlingStrategy":"validation","validationCode":"function canStartStream(): boolean {\n  return !!activeSourceId.value && sources.value.some(s => s.id === activeSourceId.value)\n}","typeGuard":"function hasActiveSource(id: string | undefined, sources: { id: string }[]): id is string {\n  return typeof id === 'string' && id.length > 0 && sources.some(s => s.id === id)\n}","tryCatchPattern":null,"preventionTips":["Await fetchSources() and confirm sources.value.length > 0 before enabling start.","Auto-select sources[0] when no source is chosen.","Bind the source picker to activeSourceId so a selection is always present."],"tags":["screen-capture","ui-state","precondition","electron"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}