moeru-ai/airi · warning · Error

Widgets element not found

Error message

Widgets element not found

What it means

themeColorFromPropertyOf (packages/stage-layouts/src/composables/theme-color.ts) resolves a theme color by querying the DOM for a selector (e.g. the tinted settings card) and reading a computed style property. It wraps document.querySelector in withRetry with 10 attempts spaced 1 s apart; if the selector still matches nothing after ~10 s, 'Widgets element not found' is thrown, meaning the element that carries the color never mounted.

Source

Thrown at packages/stage-layouts/src/composables/theme-color.ts:23

import Color from 'colorjs.io'

import { withRetry } from '@moeru/std'
import { colorFromElement, patchThemeSamplingHtml2CanvasClone } from '@proj-airi/stage-ui/libs'
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
import { useTheme } from '@proj-airi/ui'
import { useDocumentVisibility, useIntervalFn } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { nextTick, watch } from 'vue'

import { BackgroundKind } from '../stores/background'

export function themeColorFromPropertyOf(colorFromClass: string, property: string): () => Promise<string> {
  return async () => {
    const fetchUntilWidgetMounted = withRetry(() => {
      const widgets = document.querySelector(colorFromClass) as HTMLDivElement | undefined
      if (!widgets)
        throw new Error('Widgets element not found')

      return widgets
    }, { retry: 10, retryDelay: 1000 })

    const widgets = await fetchUntilWidgetMounted()
    return window.getComputedStyle(widgets).getPropertyValue(property)
  }
}

export function themeColorFromValue(value: string | { light: string, dark: string }): () => Promise<string> {
  return async () => {
    if (typeof value === 'string') {
      return value
    }
    else {
      const { isDark: dark } = useTheme()
      return dark.value ? value.dark : value.light
    }

View on GitHub (pinned to 677329427f)

Solutions

  1. Verify the selector matches a real element in the current DOM (inspect the rendered widget container)
  2. If the element may legitimately be absent, use themeColorFromValue with literal light/dark colors instead
  3. Ensure the call happens after the owning component mounts (onMounted / after nextTick)
  4. Update the selector when the widget's class changes in the layout package

Example fix

// before
const color = themeColorFromPropertyOf('.settings-appearance-card', '--tailwind-border-spacing')
// after
import { themeColorFromValue } from '../composables/theme-color'
const color = themeColorFromValue({ light: '#e4e4e7', dark: '#27272a' })
Defensive patterns

Strategy: retry

Validate before calling

async function readThemeColor(selector: string, property: string, fallback: string): Promise<string> {
  for (let attempt = 0; attempt < 10; attempt++) {
    const el = document.querySelector<HTMLElement>(selector)
    if (el) {
      const value = window.getComputedStyle(el).getPropertyValue(property).trim()
      if (value) return value
    }
    await new Promise(r => setTimeout(r, 1000))
  }
  return fallback // literal color instead of throwing
}

Type guard

function isHTMLElement(value: Element | null): value is HTMLElement {
  return value instanceof HTMLElement
}

Try / catch

try {
  color = await themeColorFromPropertyOf(selector, property)()
}
catch (error) {
  if (error instanceof Error && error.message === 'Widgets element not found') {
    color = '#7c7c7c' // known default for this widget
  }
  else throw error
}

Prevention

When it happens

Trigger: Passing a selector/class that does not exist in the current layout or route, calling the composable before the target component mounts (10 s is not enough on very slow loads), or running in an environment where document/DOM is absent or the component is conditionally v-if'd away.

Common situations: Refactoring renames the widget class and the hardcoded selector goes stale, headless/screenshot captures that query before hydration, routes where the tinted widget simply is never rendered.

Related errors


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