{"record":{"id":"ffccfae01ab171be","repo":"moeru-ai/airi","slug":"no-toast-root-provided-cannot-close-toast","errorCode":null,"errorMessage":"No toast root provided, cannot close toast","messagePattern":"No toast root provided, cannot close toast","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/components/scenarios/toasters/toaster-pwa-update-ready.vue","lineNumber":14,"sourceCode":"<script setup lang=\"ts\">\nimport { Button } from '@proj-airi/ui'\nimport { inject } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { ToasterRootInjectionKey } from './constants'\n\nconst props = defineProps<{ id?: string }>()\n\nconst emits = defineEmits<{ (e: 'update'): void }>()\n\nconst { t } = useI18n()\n\nconst toastRoot = inject(ToasterRootInjectionKey, { close: (id: string) => console.warn('No toast root provided, cannot close toast', id) })\n\nfunction handleUpdate() {\n  emits('update')\n  toastRoot.close(props.id || '')\n}\n\nfunction handleNotNow() {\n  toastRoot.close(props.id || '')\n}\n</script>\n\n<template>\n  <div\n    :class=\"[\n      'px-4 py-3',\n      'backdrop-blur-md shadow-md',\n      'bg-neutral-100/80 dark:bg-neutral-800/80',\n      'w-full flex flex-col gap-1 rounded-2xl',","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/components/scenarios/toasters/toaster-pwa-update-ready.vue#L1-L32","documentation":"toaster-pwa-update-ready.vue injects ToasterRootInjectionKey with a default object whose close() only logs this warning. The real close() is provided by toaster-root.vue; if the toast renders outside that provider tree, both the Update and Not-now buttons no-op with this warning and the toast can never be dismissed by id.","triggerScenarios":"Mounting toaster-pwa-update-ready.vue without an ancestor <ToasterRoot> (the component calling provide(ToasterRootInjectionKey, { close })): using the toast in isolation, in stories/tests, or teleported/mounted to a root that is outside the toaster provider subtree.","commonSituations":"Storybook/Histoire usage without the provider; refactoring the toaster layout so the root wrapper is lost; mounting the toast on a separate Vue app instance.","solutions":["Render PWA update toasts inside the component tree that includes ToasterRoot, which provides the real close via ToasterRootInjectionKey","In stories/tests, provide a matching { close } implementation at the test root or assert against this warning","Prefer injecting without a default and disabling the buttons when the injection is absent, so misuse is explicit"],"exampleFix":"// before\nconst toastRoot = inject(ToasterRootInjectionKey, { close: (id: string) => console.warn('No toast root provided, cannot close toast', id) })\n\n// after\nconst toastRoot = inject(ToasterRootInjectionKey, undefined)\nfunction closeToast() {\n  if (!toastRoot || !props.id)\n    return\n  toastRoot.close(props.id)\n}","handlingStrategy":"type-guard","validationCode":"// At the app root, always wrap toasts in the provider\n// <ToasterRoot> ... <ToasterPwaUpdateReady /> ... </ToasterRoot>","typeGuard":"const toastRoot = inject(ToasterRootInjectionKey, undefined)\nfunction canCloseToast(id?: string): id is string {\n  return Boolean(toastRoot) && typeof id === 'string' && id.length > 0\n}","tryCatchPattern":null,"preventionTips":["Always render toast components inside ToasterRoot, which provides the real close()","Inject without a default so absence is detectable, and disable dismiss buttons when missing","In stories/tests, provide a stub { close } at the test root"],"tags":["vue","provide-inject","toaster","pwa"],"backgroundTag":"missing-injection-provider","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}