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
- Verify the selector matches a real element in the current DOM (inspect the rendered widget container)
- If the element may legitimately be absent, use themeColorFromValue with literal light/dark colors instead
- Ensure the call happens after the owning component mounts (onMounted / after nextTick)
- 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
- Use themeColorFromValue with literal colors when the widget may not render
- Keep selector strings in one constant next to the component's class definition
- Search the codebase for the selector when renaming widget classes
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
- useReplayable must be used within a Replayable component
- Electron ipcRenderer is not available. Pass it explicitly to
- The entrypoint you imported for ${name} doesn't have any exp
- version ${rawValue.value.version} doesn't satisfy the versio
- property key 'version' wasn't found in the value of key ${ke
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/43976293ce8bcccd.
Report an issue: GitHub.