/** * DOM utility helpers — safe element lookup with missing-element warnings and telemetry. */ import { telemetry } from './app-telemetry.js'; const warned = new Set(); export const warnOnce = (code, message, details = {}) => { const normalizedCode = String(code ?? '').trim() || 'UI_WARN'; const normalizedMessage = String(message ?? '').trim(); const key = `${normalizedCode}:${normalizedMessage}`; if (warned.has(key)) {return;} warned.add(key); console.warn(`[${normalizedCode}] ${normalizedMessage}`, details); const warnMeta = { source: 'warn_once', code: normalizedCode, }; if (details && typeof details === 'object' && !Array.isArray(details)) { if (typeof details.module === 'string' && details.module.trim() !== '') { warnMeta.module = details.module.trim(); } if (typeof details.component === 'string' && details.component.trim() !== '') { warnMeta.component = details.component.trim(); } if (typeof details.feature === 'string' && details.feature.trim() !== '') { warnMeta.feature = details.feature.trim(); } } telemetry.capture('frontend.warn_once', { severity: 'warning', message: `[${normalizedCode}] ${normalizedMessage}`, meta: warnMeta, }); }; export const requireEl = (selector, details = {}) => { const el = document.querySelector(selector); if (!el) { warnOnce('UI_EL_MISSING', `Missing element: ${selector}`, details); return null; } return el; }; export const requireAll = (selector, details = {}) => { const list = Array.from(document.querySelectorAll(selector)); if (!list.length) { warnOnce('UI_EL_MISSING', `Missing elements: ${selector}`, details); } return list; }; export const optionalEl = (selector) => document.querySelector(selector); /** * Resolve a mount root to a valid query host. * Components receive a `root` parameter that may be document, an element, or undefined. * Returns the root if it supports querySelectorAll, otherwise falls back to document. */ export const resolveHost = (root) => root && typeof root.querySelectorAll === 'function' ? root : document;