moeru-ai/airi · warning
No toast root provided, cannot close toast
Error message
No toast root provided, cannot close toast
What it means
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.
Source
Thrown at packages/stage-ui/src/components/scenarios/toasters/toaster-pwa-update-ready.vue:14
<script setup lang="ts">
import { Button } from '@proj-airi/ui'
import { inject } from 'vue'
import { useI18n } from 'vue-i18n'
import { ToasterRootInjectionKey } from './constants'
const props = defineProps<{ id?: string }>()
const emits = defineEmits<{ (e: 'update'): void }>()
const { t } = useI18n()
const toastRoot = inject(ToasterRootInjectionKey, { close: (id: string) => console.warn('No toast root provided, cannot close toast', id) })
function handleUpdate() {
emits('update')
toastRoot.close(props.id || '')
}
function handleNotNow() {
toastRoot.close(props.id || '')
}
</script>
<template>
<div
:class="[
'px-4 py-3',
'backdrop-blur-md shadow-md',
'bg-neutral-100/80 dark:bg-neutral-800/80',
'w-full flex flex-col gap-1 rounded-2xl',View on GitHub (pinned to 677329427f)
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
Example fix
// before
const toastRoot = inject(ToasterRootInjectionKey, { close: (id: string) => console.warn('No toast root provided, cannot close toast', id) })
// after
const toastRoot = inject(ToasterRootInjectionKey, undefined)
function closeToast() {
if (!toastRoot || !props.id)
return
toastRoot.close(props.id)
} Defensive patterns
Strategy: type-guard
Validate before calling
// At the app root, always wrap toasts in the provider // <ToasterRoot> ... <ToasterPwaUpdateReady /> ... </ToasterRoot>
Type guard
const toastRoot = inject(ToasterRootInjectionKey, undefined)
function canCloseToast(id?: string): id is string {
return Boolean(toastRoot) && typeof id === 'string' && id.length > 0
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- useReplayable must be used within a Replayable component
- Electron ipcRenderer is not available. Pass it explicitly to
- Widgets element not found
- No Live2D model source provided.
- Cannot set motion: model not loaded
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/ffccfae01ab171be.
Report an issue: GitHub.