moeru-ai/airi · warning

useReplayable must be used within a Replayable component

Error message

useReplayable must be used within a Replayable component

What it means

useReplayable() calls Vue's inject('replayable'), which only resolves inside a component tree where Replayable.vue has run provide('replayable', ...). Outside that subtree the injection is undefined, so the composable warns and hands back inert stubs (registerReplay is a no-op disposer, isReplaying always false) instead of throwing. Replay registration silently does nothing.

Source

Thrown at packages/stage-ui/src/components/animations/use-replayable.ts:12

import { inject, onUnmounted } from 'vue'

interface ReplayableContext {
  registerReplayCallback: (callback: () => void | Promise<void>) => () => void
  isReplaying: () => boolean
}

export function useReplayable(replayFn?: () => void | Promise<void>) {
  const context = inject<ReplayableContext>('replayable')

  if (!context) {
    console.warn('useReplayable must be used within a Replayable component')

    return {
      registerReplay: () => () => {},
      isReplaying: () => false,
    }
  }

  const registerReplay = (callback: () => void | Promise<void>) => {
    return context.registerReplayCallback(callback)
  }

  // Auto-register if callback provided
  let unregister: (() => void) | undefined
  if (replayFn) {
    unregister = registerReplay(replayFn)
  }

  onUnmounted(() => {

View on GitHub (pinned to 677329427f)

Solutions

  1. Wrap the consuming component in <Replayable> from packages/stage-ui/src/components/animations/Replayable.vue.
  2. Verify the provide/inject key string ('replayable') matches between provider and composable.
  3. In tests, mount with a Replayable ancestor or global.provide stub for 'replayable'.

Example fix

<!-- before -->
<ConfettiBurst v-if="show" />

<!-- after -->
<Replayable>
  <ConfettiBurst v-if="show" />
</Replayable>
Defensive patterns

Strategy: validation

Validate before calling

// inside setup(): verify the ancestor exists
const ctx = inject<ReplayableContext | null>('replayable', null)
if (!ctx) { /* component is outside <Replayable>; skip replay wiring */ }

Type guard

function isReplayableContext(v: unknown): v is ReplayableContext {
  return !!v && typeof (v as ReplayableContext).registerReplayCallback === 'function'
}

Prevention

When it happens

Trigger: Calling useReplayable() in a component that is not a descendant of <Replayable> (packages/stage-ui/src/components/animations/Replayable.vue); using it in a composable invoked outside setup(); teleporting a child outside the provider subtree (injection follows the component tree, not DOM).

Common situations: Copying a component using useReplayable into a different page without its Replayable wrapper; restructuring layouts so the provider ancestor is lost; unit tests mounting the leaf component in isolation.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/307d30d174d71202. Report an issue: GitHub.