{"version":3,"sources":["../src/qa-meter/mount.tsx","../src/qa-meter/i18n.ts","../src/qa-meter/resolver.ts","../src/qa-meter/styles.ts","../src/qa-meter/Pastille.tsx","../src/qa-meter/ui.ts","../src/qa-meter/TestPanel.tsx","../src/qa-meter/LaneIcons.tsx","../src/qa-meter/SitemapModal.tsx","../src/qa-meter/index.ts"],"sourcesContent":["import { h, render } from 'preact'\nimport { useCallback, useEffect, useState } from 'preact/hooks'\n\nimport type { QaColor, QaPageStatus, QaStatusArtifact } from './types'\nimport type { QaMeterHandle, QaMeterOptions } from './index'\nimport { resolveStrings } from './i18n'\nimport { resolvePage } from './resolver'\nimport { QA_METER_STYLES } from './styles'\nimport { Pastille } from './Pastille'\nimport { TestPanel } from './TestPanel'\nimport { SitemapModal } from './SitemapModal'\n\n/** The custom event the patched History API dispatches so the meter can\n *  re-resolve the current page on SPA navigations (pushState/replaceState\n *  don't fire `popstate`). Module-scoped name so every instance listens to\n *  the same channel. */\nconst LOCATION_CHANGE_EVENT = 'mqa:locationchange'\n\n// ── History API patch (module-level, ref-counted) ──────────────────────────\n// We monkey-patch pushState/replaceState ONCE across all instances so a\n// `mqa:locationchange` CustomEvent fires on SPA navigations. A ref-count lets\n// the LAST disposed instance restore the originals; guarding on `patched`\n// prevents a second instance from double-wrapping (which would dispatch twice\n// and, worse, capture an already-wrapped \"original\").\nlet historyPatched = false\nlet historyRefCount = 0\nlet originalPushState: History['pushState'] | null = null\nlet originalReplaceState: History['replaceState'] | null = null\n\nfunction patchHistory(): void {\n  historyRefCount++\n  if (historyPatched) return\n  historyPatched = true\n  originalPushState = history.pushState\n  originalReplaceState = history.replaceState\n  const fire = (): void => {\n    window.dispatchEvent(new CustomEvent(LOCATION_CHANGE_EVENT))\n  }\n  history.pushState = function patchedPushState(\n    this: History,\n    ...args: Parameters<History['pushState']>\n  ) {\n    const ret = originalPushState!.apply(this, args)\n    fire()\n    return ret\n  }\n  history.replaceState = function patchedReplaceState(\n    this: History,\n    ...args: Parameters<History['replaceState']>\n  ) {\n    const ret = originalReplaceState!.apply(this, args)\n    fire()\n    return ret\n  }\n}\n\nfunction unpatchHistory(): void {\n  if (historyRefCount > 0) historyRefCount--\n  if (historyRefCount > 0 || !historyPatched) return\n  historyPatched = false\n  if (originalPushState) history.pushState = originalPushState\n  if (originalReplaceState) history.replaceState = originalReplaceState\n  originalPushState = null\n  originalReplaceState = null\n}\n\n/** Default offset, matching the donor `qa-pastille.vue` (sits near the feedback\n *  FAB). `.qa-pastille` anchors bottom-right via the `--mqa-right`/`--mqa-bottom`\n *  custom properties, which default to `1.5rem` in the stylesheet. CSS custom\n *  properties inherit from the host element THROUGH the shadow boundary, so a\n *  host can nudge the pastille by setting those vars on the host (done below\n *  when `options.position` is provided) — setting `host.style.right/bottom`\n *  directly would NOT work, since `.qa-pastille` is itself `position: fixed`\n *  and anchors to the viewport, not the host. */\n\n// ── Format validation ───────────────────────────────────────────────────────\nconst QA_ARTIFACT_FORMAT = 1\n\nfunction isValidArtifact(value: unknown): value is QaStatusArtifact {\n  return (\n    typeof value === 'object' &&\n    value !== null &&\n    (value as { format?: unknown }).format === QA_ARTIFACT_FORMAT\n  )\n}\n\nlet warnedBadFormat = false\nfunction warnBadFormatOnce(): void {\n  if (warnedBadFormat) return\n  warnedBadFormat = true\n  if (typeof console !== 'undefined') {\n    console.warn('[mqa] QA Meter artifact has an unsupported `format` — refusing to mount.')\n  }\n}\n\nconst NOOP_HANDLE: QaMeterHandle = {\n  open() {},\n  close() {},\n  dispose() {},\n}\n\n/** Every path_pattern an artifact knows — `pages[].page?.path_pattern` ∪\n *  `sitemap[].path_pattern`. Feeds the default routerless resolver. */\nfunction patternsFromArtifact(artifact: QaStatusArtifact): string[] {\n  const set = new Set<string>()\n  for (const page of artifact.pages) {\n    if (page.page) set.add(page.page.path_pattern)\n  }\n  for (const node of artifact.sitemap) {\n    set.add(node.path_pattern)\n  }\n  return [...set]\n}\n\ninterface Resolved {\n  state: 'known' | 'no-tests' | 'unknown-page'\n  color: QaColor\n  pattern: string | null\n  page: QaPageStatus | null\n}\n\nconst UNKNOWN: Resolved = { state: 'unknown-page', color: 'gray', pattern: null, page: null }\n\n/** State machine (task spec): a matched `pages[]` entry → known/its color;\n *  a pattern present only in the sitemap → no-tests/gray; no match (or no\n *  artifact yet) → unknown-page/gray. Never throws. */\nfunction resolveCurrent(artifact: QaStatusArtifact | null, pattern: string | null): Resolved {\n  if (!artifact || !pattern) return UNKNOWN\n  const page = artifact.pages.find((p) => p.page && p.page.path_pattern === pattern)\n  if (page && page.page) {\n    return { state: 'known', color: page.color, pattern, page }\n  }\n  const inSitemap = artifact.sitemap.some((n) => n.path_pattern === pattern)\n  if (inSitemap) {\n    return { state: 'no-tests', color: 'gray', pattern, page: null }\n  }\n  return { state: 'unknown-page', color: 'gray', pattern, page: null }\n}\n\nexport function createQaMeter(options: QaMeterOptions): QaMeterHandle {\n  const { source } = options\n  const strings = resolveStrings(options.locale ?? 'fr')\n\n  // For an INLINE artifact we must validate synchronously at create time, so a\n  // wrong-format object never produces a host (the test asserts this after a\n  // single microtask).\n  const inlineArtifact = typeof source === 'object' ? source : null\n  if (inlineArtifact && !isValidArtifact(inlineArtifact)) {\n    warnBadFormatOnce()\n    return NOOP_HANDLE\n  }\n\n  // Loaded artifact cache + lazy loader state. Inline artifacts are ready now;\n  // string/function sources load lazily on first open/hover, exactly once.\n  let artifact: QaStatusArtifact | null = inlineArtifact\n  let loadStarted = false\n  let loadPromise: Promise<void> | null = null\n\n  function load(): void {\n    if (artifact || loadStarted) return\n    loadStarted = true\n    const got: Promise<unknown> =\n      typeof source === 'function'\n        ? source()\n        : typeof source === 'string'\n          ? fetch(source).then((r) => r.json())\n          : Promise.resolve(source)\n    loadPromise = got\n      .then((value) => {\n        if (isValidArtifact(value)) {\n          artifact = value\n          forceRender()\n        } else {\n          warnBadFormatOnce()\n        }\n      })\n      .catch(() => {\n        // Never throw to the host: stay gray.\n      })\n  }\n\n  // ── Host + shadow root + styles (mirrors widget/mount.tsx) ────────────────\n  const host = document.createElement('div')\n  host.setAttribute('data-mqa-host', '')\n  host.setAttribute('data-mqa-size', options.size ?? 'md')\n  // Offset knobs as CSS custom properties: they inherit through the shadow\n  // boundary down to `.qa-pastille`, which reads them with a 1.5rem fallback.\n  // Only set each when provided so the stylesheet default applies otherwise.\n  if (options.position?.right !== undefined) {\n    host.style.setProperty('--mqa-right', `${options.position.right}px`)\n  }\n  if (options.position?.bottom !== undefined) {\n    host.style.setProperty('--mqa-bottom', `${options.position.bottom}px`)\n  }\n  document.body.appendChild(host)\n\n  const shadow = host.attachShadow({ mode: 'open' })\n  const style = document.createElement('style')\n  style.textContent = QA_METER_STYLES\n  shadow.appendChild(style)\n  const mountPoint = document.createElement('div')\n  shadow.appendChild(mountPoint)\n\n  // Imperative re-render hook into the Preact tree. The container component\n  // registers its dispatcher here so the loader/navigation callbacks can poke\n  // it without React-ish prop threading.\n  let forceRender: () => void = () => {}\n\n  function getCurrentPattern(): string | null {\n    if (options.getCurrentPage) return options.getCurrentPage()\n    if (!artifact) return null\n    return resolvePage(window.location.pathname, patternsFromArtifact(artifact))\n  }\n\n  function Container() {\n    // A bump-counter is the simplest \"force re-render on external change\"\n    // primitive (loader resolves, navigation, open/close).\n    const [tick, setTick] = useState(0)\n    void tick\n    const bump = useCallback(() => setTick((n) => n + 1), [])\n    forceRender = bump\n\n    // open/close lives in module scope (so the handle can drive it) but we\n    // read it via a closure variable bumped through `forceRender`.\n    const resolved = resolveCurrent(artifact, getCurrentPattern())\n\n    const meta = artifact\n      ? {\n          generated_at: artifact.generated_at,\n          app_version: artifact.app_version,\n          environment: artifact.environment,\n        }\n      : { generated_at: '', app_version: '', environment: '' }\n\n    const onHover = useCallback(() => {\n      // Lazy-load on first hover too (not just open), so the panel shows real\n      // data before the user clicks.\n      load()\n    }, [])\n\n    const onOpenModal = useCallback(() => {\n      load()\n      uiOpen = true\n      bump()\n    }, [])\n\n    const closeModal = useCallback(() => {\n      uiOpen = false\n      bump()\n    }, [])\n\n    // Re-resolve current page on navigation. popstate covers back/forward;\n    // the patched History API covers SPA pushState/replaceState.\n    useEffect(() => {\n      const onNav = (): void => bump()\n      window.addEventListener('popstate', onNav)\n      window.addEventListener(LOCATION_CHANGE_EVENT, onNav)\n      return () => {\n        window.removeEventListener('popstate', onNav)\n        window.removeEventListener(LOCATION_CHANGE_EVENT, onNav)\n      }\n    }, [])\n\n    return h(\n      'div',\n      {\n        class: 'qa-pastille-wrap',\n        // Re-entering the wrap (the dot OR the panel above it — both live\n        // inside) cancels a pending close; leaving schedules a debounced close.\n        onMouseEnter: cancelHoverClose,\n        onMouseLeave: scheduleHoverClose,\n      },\n      uiHover\n        ? h(\n            'div',\n            { class: 'qa-panel-anchor' },\n            h(TestPanel, { page: resolved.page, state: resolved.state, meta, strings }),\n          )\n        : null,\n      h(Pastille, {\n        color: resolved.color,\n        state: resolved.state,\n        strings,\n        onHover: () => {\n          cancelHoverClose()\n          uiHover = true\n          onHover()\n          bump()\n        },\n        onOpenModal,\n      }),\n      uiOpen && artifact\n        ? h(SitemapModal, {\n            artifact,\n            currentPattern: resolved.pattern,\n            strings,\n            onClose: closeModal,\n          })\n        : null,\n    )\n  }\n\n  // UI-local flags kept in closure scope so the imperative handle (open/close)\n  // can flip them and `forceRender()` reflects the change. The container reads\n  // them on each render.\n  let uiOpen = false\n  let uiHover = false\n\n  // Closing the hover panel is debounced (rather than firing on the raw\n  // mouseleave) so sweeping the cursor across the small gap between the dot and\n  // the panel — or a jittery mouse at the dot's edge — doesn't unmount it\n  // mid-sweep. Any mouseenter back into the wrap cancels the pending close.\n  let hoverCloseTimer: ReturnType<typeof setTimeout> | null = null\n  const HOVER_CLOSE_MS = 140\n  function cancelHoverClose(): void {\n    if (hoverCloseTimer !== null) {\n      clearTimeout(hoverCloseTimer)\n      hoverCloseTimer = null\n    }\n  }\n  function scheduleHoverClose(): void {\n    cancelHoverClose()\n    hoverCloseTimer = setTimeout(() => {\n      hoverCloseTimer = null\n      if (disposed) return\n      uiHover = false\n      forceRender()\n    }, HOVER_CLOSE_MS)\n  }\n\n  function mountTree(): void {\n    render(h(Container, {}), mountPoint)\n  }\n\n  // Initial mount.\n  mountTree()\n\n  patchHistory()\n\n  let disposed = false\n\n  return {\n    open() {\n      if (disposed) return\n      load()\n      uiOpen = true\n      forceRender()\n    },\n    close() {\n      if (disposed) return\n      uiOpen = false\n      forceRender()\n    },\n    dispose() {\n      if (disposed) return\n      disposed = true\n      cancelHoverClose()\n      render(null, mountPoint)\n      host.remove()\n      unpatchHistory()\n      void loadPromise\n    },\n  }\n}\n","/**\n * Bundled FR/EN strings for the QA Meter widget.\n *\n * Keys are the donor's `qaMeter.*` keys with the `qaMeter.` prefix stripped.\n * Values are copied verbatim from the donor locale files.\n *\n * Two new keys not present in the donor are appended at the end of each record:\n *   - `panel.noTests`\n *   - `panel.unknownPage`\n */\n\nexport const QA_STRINGS_FR = {\n  'pastille.label': 'Suivi des tests',\n  'pastille.loading': 'Chargement…',\n  'pastille.unavailable': 'Statuts indisponibles',\n  'pastille.unknownPage': 'Page inconnue',\n  'pastille.unknownPageHint': \"Cette route n’est pas encore au plan du site des tests.\",\n  'panel.freshness': 'Statuts g\\xe9n\\xe9r\\xe9s le {date} \\xb7 version {version} \\xb7 env {env}',\n  'panel.noScenarios': 'Aucun sc\\xe9nario li\\xe9 \\xe0 cette page.',\n  'panel.openSitemap': 'Voir le plan du site',\n  'panel.sitemapLink': 'Plan du site',\n  'panel.scenarioCountOne': '{count} sc\\xe9nario',\n  'panel.scenarioCountMany': '{count} sc\\xe9narios',\n  'panel.laneAutomated': 'Automatis\\xe9',\n  'panel.laneDev': 'D\\xe9v',\n  'panel.laneClient': 'Client',\n  'lane.notTested': 'Non test\\xe9',\n  'lane.validatedBy': 'Valid\\xe9 par {by}',\n  'lane.rejectedBy': 'Rejet\\xe9 par {by}',\n  'lane.stale': 'Valid\\xe9 en v{version}, version actuelle v{current}',\n  'lane.clientPending': 'Non test\\xe9 — plateforme requise',\n  'lane.bot': 'Automatis\\xe9 (bot)',\n  'lane.code': 'D\\xe9veloppeur (code)',\n  'lane.human': 'Humain',\n  'laneState.ok': '\\xc0 jour',\n  'laneState.issue': 'Probl\\xe8me',\n  'laneState.none': 'Non fait / p\\xe9rim\\xe9',\n  'color.green': 'Valid\\xe9',\n  'color.yellow': 'En attente',\n  'color.red': 'En \\xe9chec',\n  'color.gray': 'Non couvert',\n  'automated.passing': 'R\\xe9ussi',\n  'automated.failing': 'En \\xe9chec',\n  'automated.flaky': 'Instable',\n  'automated.neverRun': 'Jamais ex\\xe9cut\\xe9',\n  'source.testgen': 'g\\xe9n\\xe9r\\xe9',\n  'source.testgenHint': 'Sc\\xe9nario g\\xe9n\\xe9r\\xe9 automatiquement (testgen)',\n  'suite.cases': '{count} cas',\n  'case.covered': 'auto',\n  'case.manual': 'manuel',\n  'case.blocked': 'bloqu\\xe9',\n  'case.openQuestion': 'question produit',\n  'grounding.verified': 'attendu v\\xe9rifi\\xe9',\n  'grounding.confirmLive': '\\xe0 confirmer en live',\n  'grounding.productQuestion': 'd\\xe9cision produit',\n  'modal.title': 'Plan du site — suivi des tests',\n  'modal.close': 'Fermer',\n  'modal.tabPages': 'Pages',\n  'modal.tabScenarios': 'Sc\\xe9narios',\n  'modal.youAreHere': 'vous \\xeates ici',\n  'modal.search': 'Filtrer les sc\\xe9narios…',\n  'modal.noMatch': 'Aucun sc\\xe9nario ne correspond au filtre.',\n  'modal.traversed': 'Travers\\xe9es',\n  'modal.appliesTo': '{count} pages',\n  'modal.kpiPagesCovered': 'Pages avec ≥ 1 sc\\xe9nario',\n  'modal.kpiScenariosPassing': 'Sc\\xe9narios passants',\n  // New keys (not in donor)\n  'panel.noTests': 'Aucun test ne couvre encore cette page.',\n  'panel.unknownPage': 'Page inconnue du registre QA.',\n} as const\n\nexport const QA_STRINGS_EN = {\n  'pastille.label': 'Test tracking',\n  'pastille.loading': 'Loading…',\n  'pastille.unavailable': 'Statuses unavailable',\n  'pastille.unknownPage': 'Unknown page',\n  'pastille.unknownPageHint': \"This route isn’t in the test sitemap yet.\",\n  'panel.freshness': 'Statuses generated on {date} \\xb7 version {version} \\xb7 env {env}',\n  'panel.noScenarios': 'No scenario linked to this page.',\n  'panel.openSitemap': 'View sitemap',\n  'panel.sitemapLink': 'Sitemap',\n  'panel.scenarioCountOne': '{count} scenario',\n  'panel.scenarioCountMany': '{count} scenarios',\n  'panel.laneAutomated': 'Automated',\n  'panel.laneDev': 'Dev',\n  'panel.laneClient': 'Client',\n  'lane.notTested': 'Not tested',\n  'lane.validatedBy': 'Validated by {by}',\n  'lane.rejectedBy': 'Rejected by {by}',\n  'lane.stale': 'Validated in v{version}, current version v{current}',\n  'lane.clientPending': 'Not tested — platform required',\n  'lane.bot': 'Automated (bot)',\n  'lane.code': 'Developer (code)',\n  'lane.human': 'Human',\n  'laneState.ok': 'Up to date',\n  'laneState.issue': 'Issue',\n  'laneState.none': 'Not done / stale',\n  'color.green': 'Validated',\n  'color.yellow': 'Pending',\n  'color.red': 'Failing',\n  'color.gray': 'Not covered',\n  'automated.passing': 'Passing',\n  'automated.failing': 'Failing',\n  'automated.flaky': 'Flaky',\n  'automated.neverRun': 'Never run',\n  'source.testgen': 'generated',\n  'source.testgenHint': 'Auto-generated scenario (testgen)',\n  'suite.cases': '{count} cases',\n  'case.covered': 'auto',\n  'case.manual': 'manual',\n  'case.blocked': 'blocked',\n  'case.openQuestion': 'product question',\n  'grounding.verified': 'expected verified',\n  'grounding.confirmLive': 'confirm live',\n  'grounding.productQuestion': 'product decision',\n  'modal.title': 'Sitemap — test tracking',\n  'modal.close': 'Close',\n  'modal.tabPages': 'Pages',\n  'modal.tabScenarios': 'Scenarios',\n  'modal.youAreHere': 'you are here',\n  'modal.search': 'Filter scenarios…',\n  'modal.noMatch': 'No scenario matches the filter.',\n  'modal.traversed': 'Traversed',\n  'modal.appliesTo': '{count} pages',\n  'modal.kpiPagesCovered': 'Pages with ≥ 1 scenario',\n  'modal.kpiScenariosPassing': 'Passing scenarios',\n  // New keys (not in donor)\n  'panel.noTests': 'No tests cover this page yet.',\n  'panel.unknownPage': 'Page unknown to the QA registry.',\n} as const\n\nexport type QaStringKey = keyof typeof QA_STRINGS_FR\nexport type QaStrings = Record<QaStringKey, string>\n\nexport function resolveStrings(locale: 'fr' | 'en'): QaStrings {\n  return locale === 'en' ? QA_STRINGS_EN : QA_STRINGS_FR\n}\n","/** Routerless page resolution: match window.location.pathname against the\n *  artifact's path_patterns. Most-specific match wins (static segments beat\n *  params; longer beats shorter). The host can override with a router-aware\n *  getCurrentPage — this is only the default. */\n\nexport function normalizePattern(pattern: string | undefined | null): string {\n  if (!pattern) return '/'\n  let n = pattern.startsWith('/') ? pattern : `/${pattern}`\n  n = n.replace(/\\/{2,}/g, '/')\n  if (n.length > 1) n = n.replace(/\\/+$/, '')\n  return n.length > 0 ? n : '/'\n}\n\nconst segments = (p: string): string[] => p.split('/').filter(Boolean)\n\nfunction matchesPattern(pathSegs: string[], patternSegs: string[]): boolean {\n  let required = patternSegs.length\n  while (required > 0) {\n    const last = patternSegs[required - 1]\n    if (last !== undefined && last.startsWith(':') && last.endsWith('?')) required--\n    else break\n  }\n  if (pathSegs.length < required || pathSegs.length > patternSegs.length) return false\n  return pathSegs.every((seg, i) => {\n    const pat = patternSegs[i]\n    return pat !== undefined && (pat.startsWith(':') || pat === seg)\n  })\n}\n\nexport function resolvePage(pathname: string, patterns: ReadonlyArray<string>): string | null {\n  const pathSegs = segments(normalizePattern(pathname))\n  let best: string | null = null\n  let bestScore = -1\n  for (const pattern of patterns) {\n    const patSegs = segments(pattern)\n    if (!matchesPattern(pathSegs, patSegs)) continue\n    const statics = patSegs.filter((s) => !s.startsWith(':')).length\n    const score = statics * 100 + patSegs.length\n    if (score > bestScore) { best = pattern; bestScore = score }\n  }\n  return best\n}\n","/**\n * QA Meter CSS styles — a `:host` block with CSS variables prefixed `--mqa-`.\n *\n * The QA Meter z-index is set one below the feedback widget's `2147483640`\n * so the feedback FAB wins overlaps.\n *\n * Color tokens (hardcoded to remove dependency on DaisyUI HSL variables):\n *   green  #16a34a\n *   yellow #eab308\n *   red    #dc2626\n *   gray   #9ca3af\n *\n * Selectors ported from qa-pastille.vue, qa-test-panel.vue, qa-lane-icons.vue,\n * and qa-sitemap-modal.vue — adapted for standalone Shadow DOM use.\n */\nexport const QA_METER_STYLES = `\n:host {\n  --mqa-z-index: 2147483639;\n  --mqa-z-panel: 2147483638;\n\n  /* Color tokens */\n  --mqa-green: #16a34a;\n  --mqa-yellow: #eab308;\n  --mqa-red: #dc2626;\n  --mqa-gray: #9ca3af;\n\n  /* Surface */\n  --mqa-bg: #ffffff;\n  --mqa-surface: #f9fafb;\n  --mqa-border: #e5e7eb;\n  --mqa-text: #111827;\n  --mqa-text-muted: #6b7280;\n  --mqa-primary: #3b82f6;\n  --mqa-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.12);\n  --mqa-shadow-xl: 0 24px 56px rgba(0,0,0,0.18), 0 8px 20px rgba(0,0,0,0.10);\n\n  /* Font */\n  --mqa-font: system-ui, -apple-system, sans-serif;\n  --mqa-radius: 8px;\n\n  all: initial;\n  font-family: var(--mqa-font);\n  color: var(--mqa-text);\n  position: fixed;\n  z-index: var(--mqa-z-index);\n}\n\n@media (prefers-color-scheme: dark) {\n  :host {\n    --mqa-bg: #1e293b;\n    --mqa-surface: #0f172a;\n    --mqa-border: #334155;\n    --mqa-text: #f8fafc;\n    --mqa-text-muted: #94a3b8;\n    --mqa-shadow-xl: 0 24px 56px rgba(0,0,0,0.55), 0 8px 20px rgba(0,0,0,0.40);\n  }\n}\n\n/* ── Semaphore modifier classes ─────────────────────────────────────────── */\n\n.qa--green  { background: var(--mqa-green); }\n.qa--yellow { background: var(--mqa-yellow); }\n.qa--red    { background: var(--mqa-red); }\n.qa--gray   { background: var(--mqa-gray); }\n\n/* Priority modifiers (color only — components set sizing). */\n.qa-prio--p0 { color: var(--mqa-red); }\n.qa-prio--p1 { color: var(--mqa-yellow); }\n.qa-prio--p2 { color: var(--mqa-green); }\n.qa-prio--p3 { color: var(--mqa-gray); }\n\n/* ── Pastille (FAB circle) ──────────────────────────────────────────────── */\n\n/* The wrap is the fixed, bottom-right anchor for the whole pastille cluster.\n   It MUST be a positioning context: the hover panel .qa-panel is\n   position:absolute and is a SIBLING of the pastille, so without a positioned\n   wrap it would anchor to :host (fixed, no offsets — i.e. the top-left of the\n   screen) instead of sitting above the FAB. The --mqa-right/--mqa-bottom knobs\n   (set on the host, inherited here) keep driving the on-screen position. The\n   fixed sitemap modal inside the wrap anchors to the viewport regardless, so\n   it's unaffected. */\n.qa-pastille-wrap {\n  position: fixed;\n  right: var(--mqa-right, 1.5rem);\n  bottom: var(--mqa-bottom, 1.5rem);\n  z-index: var(--mqa-z-index);\n}\n\n.qa-pastille {\n  position: relative;\n}\n\n.qa-pastille__dot {\n  width: 48px;\n  height: 48px;\n  border-radius: 9999px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  box-shadow: var(--mqa-shadow);\n  color: rgba(0, 0, 0, 0.5);\n  /* No idle animation — the pastille stays static until interacted with, like\n     the feedback FAB. (Previously pulsed on a perpetual keyframe loop.) Only\n     the hover transition below animates. */\n  transition: transform 0.15s ease, box-shadow 0.15s ease;\n  border: none;\n}\n\n.qa-pastille__dot:hover {\n  transform: scale(1.12);\n  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);\n}\n\n.qa-pastille__ecg {\n  width: 30px;\n  height: 15px;\n}\n\n/* Secondary size: half-scale pastille for the integrated second-FAB use. */\n:host([data-mqa-size=\"sm\"]) .qa-pastille__dot { width: 24px; height: 24px; }\n:host([data-mqa-size=\"sm\"]) .qa-pastille__ecg { width: 15px; height: 8px; }\n\n/* Color overrides: on colored backgrounds the ECG trace needs a dark stroke. */\n.qa-pastille__dot.qa--green,\n.qa-pastille__dot.qa--yellow,\n.qa-pastille__dot.qa--red {\n  color: rgba(0, 0, 0, 0.6);\n}\n\n.qa-pastille__dot.qa--gray {\n  background: rgba(156, 163, 175, 0.35);\n  color: rgba(0, 0, 0, 0.45);\n}\n\n.qa-fade-enter-active,\n.qa-fade-leave-active {\n  transition: opacity 0.15s ease, transform 0.15s ease;\n}\n.qa-fade-enter-from,\n.qa-fade-leave-to {\n  opacity: 0;\n  transform: translateY(4px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .qa-pastille__dot { transition: none; }\n}\n\n/* ── Test panel ─────────────────────────────────────────────────────────── */\n\n/* The hover panel anchors above the pastille inside the fixed\n   .qa-pastille-wrap. The ANCHOR (not the panel) is what's taken out of flow:\n   it MUST be absolute so the wrap shrink-wraps to the pastille dot — an in-flow\n   panel would stretch the wrap to 340px and either push the dot aside or (on a\n   tall, scrolled host page) drive a scrollbar-reflow feedback loop that makes\n   the FAB jump. (The donor's positioning lived on an orphaned\n   .qa-pastille__panel selector that matched nothing after the Vue→Preact port\n   renamed the root to .qa-panel.)\n\n   The anchor's padding-bottom renders the 0.5rem visual gap between the dot\n   and the panel AS PART of the hover region, so sweeping the cursor from the\n   dot up onto the panel never crosses dead space → never fires the wrap's\n   mouseleave → no flicker. (Paired with a small close-delay in mount.tsx.) */\n.qa-panel-anchor {\n  position: absolute;\n  bottom: 100%;\n  right: 0;\n  padding-bottom: 0.5rem;\n  z-index: var(--mqa-z-panel);\n}\n\n.qa-panel {\n  background: var(--mqa-bg);\n  border-radius: var(--mqa-radius);\n  border: 1px solid var(--mqa-border);\n  box-shadow: var(--mqa-shadow-xl);\n  width: 340px;\n  max-height: 60vh;\n  overflow: hidden auto;\n  font-size: 0.8125rem;\n  color: var(--mqa-text);\n}\n\n.qa-panel__empty {\n  padding: 0.75rem;\n  opacity: 0.7;\n}\n\n.qa-panel__list,\n.qa-panel__groups {\n  margin: 0;\n  padding: 0.25rem 0;\n  list-style: none;\n}\n\n.qa-panel__foot {\n  border-top: 1px solid var(--mqa-border);\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.5rem;\n  padding: 0.5rem 0.75rem;\n  position: sticky;\n  bottom: 0;\n  background: var(--mqa-bg);\n}\n\n.qa-panel__count {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  font-size: 0.6875rem;\n  opacity: 0.65;\n}\n\n.qa-panel__link {\n  flex: none;\n  font-weight: 600;\n  cursor: pointer;\n  white-space: nowrap;\n  color: var(--mqa-primary);\n  background: none;\n  border: none;\n  font: inherit;\n}\n\n.qa-panel__link:hover { text-decoration: underline; }\n\n/* ── Suite groups ───────────────────────────────────────────────────────── */\n\n.qa-suite + .qa-suite { border-top: 1px solid var(--mqa-border); }\n\n.qa-suite__head {\n  width: 100%;\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  padding: 0.5rem 0.75rem;\n  cursor: pointer;\n  text-align: left;\n  background: none;\n  border: none;\n  font: inherit;\n  color: inherit;\n}\n\n.qa-suite__head:hover { background: var(--mqa-surface); }\n\n.qa-suite__chev {\n  flex: none;\n  font-size: 0.625rem;\n  opacity: 0.6;\n  transition: transform 0.12s ease;\n}\n\n.qa-suite__chev.is-open { transform: rotate(90deg); }\n\n.qa-suite__key {\n  flex: none;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 0.6875rem;\n  font-weight: 700;\n  background: var(--mqa-surface);\n  border-radius: 4px;\n}\n\n.qa-suite__label {\n  flex: 1;\n  min-width: 0;\n  font-weight: 600;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.qa-suite__count {\n  flex: none;\n  font-size: 0.6875rem;\n  opacity: 0.55;\n}\n\n.qa-suite__list {\n  background: var(--mqa-surface);\n  margin: 0;\n  padding: 0 0 0.25rem;\n  list-style: none;\n}\n\n/* ── Scenario rows ──────────────────────────────────────────────────────── */\n\n.qa-row {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  padding: 0.5rem 0.75rem;\n}\n\n.qa-row + .qa-row { border-top: 1px solid var(--mqa-border); }\n\n.qa-row__title {\n  display: flex;\n  align-items: center;\n  gap: 0.375rem;\n  min-width: 0;\n}\n\n.qa-row__name {\n  flex: 1;\n  min-width: 0;\n  font-weight: 600;\n  line-height: 1.25;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.qa-suite__list .qa-row { padding-left: 1.5rem; }\n\n.qa-tag {\n  flex: none;\n  font-size: 0.625rem;\n  padding: 0 0.25rem;\n  text-transform: uppercase;\n  letter-spacing: 0.02em;\n  opacity: 0.8;\n  background: var(--mqa-surface);\n  border-radius: 9999px;\n}\n\n/* ── Lane icons ─────────────────────────────────────────────────────────── */\n\n.qa-lanes {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.3125rem;\n  flex: none;\n  padding: 0.1875rem 0.4375rem;\n  border-radius: 9999px;\n  background: rgba(17, 24, 39, 0.06);\n}\n\n.qa-lanes--sm { font-size: 0.875rem; }\n.qa-lanes--md { font-size: 1rem; }\n\n.qa-lanes__icon {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  color: rgba(17, 24, 39, 0.4);\n}\n\n.qa-lanes__svg {\n  width: 1em;\n  height: 1em;\n  display: block;\n}\n\n.qa-lanes__icon.qa--green  { color: var(--mqa-green); background: none; }\n.qa-lanes__icon.qa--yellow { color: var(--mqa-yellow); background: none; }\n.qa-lanes__icon.qa--gray   { color: rgba(17, 24, 39, 0.4); background: none; }\n\n/* ── Sitemap modal backdrop ─────────────────────────────────────────────── */\n\n.qa-modal__backdrop {\n  background-color: rgba(17, 24, 39, 0.4);\n  backdrop-filter: blur(1px);\n  position: fixed;\n  inset: 0;\n  z-index: var(--mqa-z-index);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 1rem;\n}\n\n/* ── Sitemap modal ──────────────────────────────────────────────────────── */\n\n.qa-modal {\n  background: var(--mqa-bg);\n  border-radius: var(--mqa-radius);\n  box-shadow: var(--mqa-shadow-xl);\n  border: 1px solid var(--mqa-border);\n  width: min(720px, 92vw);\n  max-height: 86vh;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  color: var(--mqa-text);\n}\n\n.qa-modal__head {\n  background: var(--mqa-primary);\n  color: #ffffff;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0.5rem 0.75rem;\n}\n\n.qa-modal__head h3 {\n  font-weight: 700;\n  font-size: 1rem;\n  margin: 0;\n}\n\n.qa-modal__close {\n  border: 1px solid rgba(255, 255, 255, 0.3);\n  background: transparent;\n  color: #ffffff;\n  width: 24px;\n  height: 24px;\n  padding: 0;\n  border-radius: var(--mqa-radius);\n  cursor: pointer;\n  display: grid;\n  place-items: center;\n}\n\n.qa-modal__close:hover { background: var(--mqa-red); }\n\n.qa-modal__freshness {\n  background: var(--mqa-surface);\n  padding: 0.375rem 0.75rem;\n  font-size: 0.6875rem;\n  opacity: 0.8;\n}\n\n.qa-modal__body {\n  overflow: auto;\n  padding: 0.5rem 0;\n}\n\n/* ── KPI strip ──────────────────────────────────────────────────────────── */\n\n.qa-kpi {\n  display: flex;\n  gap: 1.5rem;\n  padding: 0.625rem 0.75rem;\n}\n\n.qa-kpi__item {\n  display: flex;\n  flex-direction: column;\n}\n\n.qa-kpi__value {\n  font-size: 1.25rem;\n  font-weight: 700;\n  line-height: 1;\n}\n\n.qa-kpi__label {\n  font-size: 0.6875rem;\n  opacity: 0.7;\n}\n\n/* ── Tabs ───────────────────────────────────────────────────────────────── */\n\n.qa-tabs {\n  border-bottom: 1px solid var(--mqa-border);\n  display: flex;\n  gap: 0.25rem;\n  padding: 0 0.5rem;\n}\n\n.qa-tabs__tab {\n  padding: 0.375rem 0.75rem;\n  font-weight: 600;\n  font-size: 0.8125rem;\n  border-bottom: 2px solid transparent;\n  cursor: pointer;\n  opacity: 0.6;\n  background: none;\n  border-left: none;\n  border-right: none;\n  border-top: none;\n  font: inherit;\n  color: inherit;\n}\n\n.qa-tabs__tab--active {\n  color: var(--mqa-primary);\n  border-bottom-color: var(--mqa-primary);\n  opacity: 1;\n}\n\n/* ── Sitemap page tree ──────────────────────────────────────────────────── */\n\n.qa-tree {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n  font-size: 0.8125rem;\n}\n\n.qa-tree__row {\n  display: flex;\n  align-items: center;\n  gap: 0.375rem;\n  padding-top: 0.25rem;\n  padding-bottom: 0.25rem;\n  padding-right: 0.75rem;\n}\n\n.qa-tree__row--here { background: var(--mqa-surface); }\n\n.qa-tree__caret {\n  width: 1rem;\n  text-align: center;\n  cursor: pointer;\n  opacity: 0.6;\n  font-size: 0.6875rem;\n  background: none;\n  border: none;\n  font: inherit;\n  color: inherit;\n}\n\n.qa-tree__caret--leaf { cursor: default; }\n\n.qa-tree__title { flex: 1; }\n.qa-tree__title--structural { font-style: italic; opacity: 0.75; }\n\n.qa-tree__here {\n  color: var(--mqa-primary);\n  font-size: 0.625rem;\n  font-weight: 700;\n  text-transform: uppercase;\n}\n\n.qa-tree__count {\n  background: var(--mqa-surface);\n  border-radius: 9999px;\n  font-size: 0.6875rem;\n  padding: 0 0.375rem;\n  min-width: 1.25rem;\n  text-align: center;\n}\n\n/* ── Scenarios tab ──────────────────────────────────────────────────────── */\n\n.qa-scenarios { padding: 0 0.75rem; }\n\n.qa-scenarios__search {\n  border: 1px solid var(--mqa-border);\n  border-radius: var(--mqa-radius);\n  width: 100%;\n  height: 2rem;\n  padding: 0 0.75rem;\n  font-size: 0.8125rem;\n  margin-bottom: 0.5rem;\n  background: var(--mqa-bg);\n  color: var(--mqa-text);\n  font-family: inherit;\n  box-sizing: border-box;\n}\n\n.qa-scenarios__empty {\n  opacity: 0.7;\n  padding: 0.5rem 0;\n}\n\n.qa-scenarios__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n\n.qa-scenarios__item {\n  border-top: 1px solid var(--mqa-border);\n  padding: 0.5rem 0;\n}\n\n.qa-scenarios__line {\n  display: flex;\n  align-items: center;\n  gap: 0.375rem;\n}\n\n.qa-scenarios__title {\n  flex: 1;\n  font-weight: 600;\n  font-size: 0.8125rem;\n}\n\n.qa-scenarios__pages {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem;\n  font-size: 0.6875rem;\n  opacity: 0.7;\n  margin-top: 0.125rem;\n  padding-left: 1rem;\n}\n\n/* ── Scenario dot (color indicator) ────────────────────────────────────── */\n\n.qa-dot {\n  width: 0.625rem;\n  height: 0.625rem;\n  border-radius: 9999px;\n  flex: none;\n  background: rgba(17, 24, 39, 0.6);\n}\n\n/* Override modifier background for dot — dot uses background not text color. */\n.qa-dot.qa--green  { background: var(--mqa-green); }\n.qa-dot.qa--yellow { background: var(--mqa-yellow); }\n.qa-dot.qa--red    { background: var(--mqa-red); }\n.qa-dot.qa--gray   { background: var(--mqa-gray); }\n\n/* ── Suite key badge ────────────────────────────────────────────────────── */\n\n.qa-suitekey {\n  flex: none;\n  font-size: 0.625rem;\n  font-weight: 700;\n  padding: 0 0.3125rem;\n  line-height: 1.4;\n  background: var(--mqa-surface);\n  border-radius: 4px;\n}\n`\n","import { h } from 'preact'\n\nimport type { QaColor } from './types'\nimport type { QaStrings } from './i18n'\nimport { QA_COLOR_LABEL_KEY, QA_COLOR_MODIFIER } from './ui'\n\n/**\n * The heartbeat/ECG pastille — purely presentational. It renders the page color\n * as a semaphore modifier and emits hover/open callbacks; it owns NO state, NO\n * router, NO store. State (current page, fetched artifact) lives in the mount\n * container task that wraps this.\n *\n * Heartbeat/ECG SVG copied verbatim from the donor `qa-pastille.vue`.\n */\nexport interface PastilleProps {\n  color: QaColor\n  state: 'known' | 'no-tests' | 'unknown-page'\n  strings: QaStrings\n  onOpenModal: () => void\n  onHover: () => void\n}\n\nexport function Pastille({ color, state, strings, onOpenModal, onHover }: PastilleProps) {\n  // The color → class mapping is the same for every state; only the title text\n  // differs slightly so a hover hint can explain the no-tests / unknown-page cases.\n  const base = strings['pastille.label']\n  let detail = strings[QA_COLOR_LABEL_KEY[color]]\n  if (state === 'no-tests') {\n    detail = strings['panel.noTests']\n  } else if (state === 'unknown-page') {\n    detail = strings['pastille.unknownPage']\n  }\n  const label = `${base} — ${detail}`\n\n  // Mirror the donor's two-element structure: `.qa-pastille` is the fixed-position\n  // CONTAINER; `.qa-pastille__dot` carries the size/background/animation (the\n  // `.qa-pastille__dot.qa--*` rules in styles.ts are where color resolves).\n  return h(\n    'div',\n    { class: 'qa-pastille' },\n    h(\n      'button',\n      {\n        type: 'button',\n        'data-testid': 'qa-pastille',\n        class: `qa-pastille__dot ${QA_COLOR_MODIFIER[color]}`,\n        'aria-label': label,\n        title: label,\n        onClick: onOpenModal,\n        onMouseEnter: onHover,\n        onFocus: onHover,\n      },\n      h(\n        'svg',\n        { class: 'qa-pastille__ecg', viewBox: '0 0 24 12', 'aria-hidden': 'true' },\n        h('polyline', {\n          points: '1,6 7,6 9,2 12,10 15,4 17,6 23,6',\n          fill: 'none',\n          stroke: 'currentColor',\n          'stroke-width': '1.5',\n          'stroke-linejoin': 'round',\n          'stroke-linecap': 'round',\n        }),\n      ),\n    ),\n  )\n}\n","/**\n * Helpers de PRÉSENTATION du QA Meter (aucune logique de statut).\n *\n * La couleur d'un scénario / d'une page est calculée à la construction de\n * l'artefact (doc 03 §9.3) et arrive déjà décidée. Ici on ne fait que la\n * traduire en classe CSS + clé i18n, et formater les dates/versions.\n *\n * Ported from the donor `qa-meter.ui.ts`; type imports point to ./types,\n * i18n keys are unprefixed (no `qaMeter.` prefix), Vue-specific imports dropped.\n */\nimport type {\n  QaAutomatedLane,\n  QaAutomatedStatus,\n  QaCaseStatus,\n  QaColor,\n  QaGrounding,\n  QaHumanLane,\n  QaPriority,\n  QaTriColor,\n} from './types';\nimport type { QaStringKey } from './i18n';\n\n/** Classe modificatrice BEM appliquée aux pastilles/puces (cf. styles des composants). */\nexport const QA_COLOR_MODIFIER: Record<QaColor, string> = {\n  green: 'qa--green',\n  yellow: 'qa--yellow',\n  red: 'qa--red',\n  gray: 'qa--gray',\n};\n\n/** Clé i18n du libellé d'une couleur (référencée dynamiquement → présente dans les deux locales). */\nexport const QA_COLOR_LABEL_KEY: Record<QaColor, QaStringKey> = {\n  green: 'color.green',\n  yellow: 'color.yellow',\n  red: 'color.red',\n  gray: 'color.gray',\n};\n\n/** Clé i18n du libellé d'un statut automated. */\nexport const QA_AUTOMATED_LABEL_KEY: Record<QaAutomatedStatus, QaStringKey> = {\n  passing: 'automated.passing',\n  failing: 'automated.failing',\n  flaky: 'automated.flaky',\n  never_run: 'automated.neverRun',\n};\n\n/** Modificateur sémaphore d'un volet automated (le volet, pas le scénario). */\nexport const QA_AUTOMATED_MODIFIER: Record<QaAutomatedStatus, string> = {\n  passing: 'qa--green',\n  failing: 'qa--red',\n  flaky: 'qa--yellow',\n  never_run: 'qa--gray',\n};\n\n/**\n * Modificateur sémaphore d'un volet humain (dev/client) :\n *  - `rejected` → rouge ; non validé → gris ; validé mais périmé/contredit → jaune ; validé → vert.\n */\nexport function humanLaneModifier(lane: { validated: boolean; rejected?: boolean; stale: boolean }): string {\n  if (lane.rejected) {\n    return 'qa--red';\n  }\n  if (!lane.validated) {\n    return 'qa--gray';\n  }\n  return lane.stale ? 'qa--yellow' : 'qa--green';\n}\n\n/** Modificateur d'une puce de priorité (P0 = critique → rouge ; décroît vers neutre). */\nexport const QA_PRIORITY_MODIFIER: Record<QaPriority, string> = {\n  P0: 'qa-prio--p0',\n  P1: 'qa-prio--p1',\n  P2: 'qa-prio--p2',\n  P3: 'qa-prio--p3',\n};\n\n/** Clé i18n du tag de statut d'un cas (couvert / manuel / bloqué / question produit). */\nexport const QA_CASE_STATUS_LABEL_KEY: Record<QaCaseStatus, QaStringKey> = {\n  covered: 'case.covered',\n  manual: 'case.manual',\n  blocked: 'case.blocked',\n  'open-question': 'case.openQuestion',\n};\n\n/** Clé i18n de l'ancrage de l'attendu (vérifié / à confirmer / décision produit). */\nexport const QA_GROUNDING_LABEL_KEY: Record<QaGrounding, QaStringKey> = {\n  verified: 'grounding.verified',\n  'confirm-live': 'grounding.confirmLive',\n  'product-question': 'grounding.productQuestion',\n};\n\n// ── Voies code/bot/human (3 icônes indépendantes, couleur 3-états) ───────────\n\n/** Voie « bot » (exécution automatisée) : passe → vert ; échec/instable → jaune ; jamais exécuté → gris. */\nexport function botTriColor(lane: QaAutomatedLane): QaTriColor {\n  if (lane.status === 'passing') {\n    return 'green';\n  }\n  if (lane.status === 'failing' || lane.status === 'flaky') {\n    return 'yellow';\n  }\n  return 'gray';\n}\n\n/** Voie humaine (« code » = dev, « human » = client) : validé & frais → vert ; rejeté → jaune ; non validé/périmé → gris. */\nexport function humanTriColor(lane: QaHumanLane): QaTriColor {\n  if (lane.rejected) {\n    return 'yellow';\n  }\n  if (lane.validated) {\n    return lane.stale ? 'gray' : 'green';\n  }\n  return 'gray';\n}\n\n/** Agrège une voie sur un ensemble de cas : un jaune → jaune ; sinon tout vert → vert ; sinon gris. */\nexport function aggregateTri(colors: ReadonlyArray<QaTriColor>): QaTriColor {\n  if (colors.some((c) => c === 'yellow')) {\n    return 'yellow';\n  }\n  if (colors.length > 0 && colors.every((c) => c === 'green')) {\n    return 'green';\n  }\n  return 'gray';\n}\n\n/** Rang de gravité d'une couleur, pour résumer un groupe par son « pire » état. */\nconst QA_COLOR_RANK: Record<QaColor, number> = { red: 3, yellow: 2, green: 1, gray: 0 };\n\n/** Couleur résumé d'un groupe : rouge domine ; sinon jaune ; sinon vert ; sinon gris. */\nexport function worstColor(colors: ReadonlyArray<QaColor>): QaColor {\n  return colors.reduce<QaColor>((acc, c) => (QA_COLOR_RANK[c] > QA_COLOR_RANK[acc] ? c : acc), 'gray');\n}\n\n/** Priorité la plus haute (P0 < P1 < P2 < P3) parmi une liste, ou `undefined`. */\nexport function topPriority(priorities: ReadonlyArray<QaPriority | undefined>): QaPriority | undefined {\n  return priorities.filter((p): p is QaPriority => Boolean(p)).sort()[0];\n}\n\n/**\n * Formate une estampille ISO en date+heure locale courte (fr-CA → AAAA-MM-JJ HH:MM).\n * Retourne `''` pour une valeur absente/invalide (l'appelant masque alors le segment).\n */\nexport function formatQaDate(iso: string | undefined | null): string {\n  if (!iso) {\n    return '';\n  }\n  const date = new Date(iso);\n  if (Number.isNaN(date.getTime())) {\n    return '';\n  }\n  return new Intl.DateTimeFormat('fr-CA', {\n    year: 'numeric',\n    month: '2-digit',\n    day: '2-digit',\n    hour: '2-digit',\n    minute: '2-digit',\n  }).format(date);\n}\n\n/**\n * Version d'app = chaîne opaque (doc 03 §1.1, jamais parsée). Tronquée pour\n * l'affichage : un timestamp de build complet est illisible dans une puce.\n */\nexport function shortVersion(version: string | undefined | null): string {\n  if (!version) {\n    return '';\n  }\n  return version.length > 19 ? `${version.slice(0, 19)}…` : version;\n}\n","import { h } from 'preact'\nimport { useState } from 'preact/hooks'\n\nimport type { QaPageStatus, QaScenario, QaTriColor } from './types'\nimport type { QaStrings } from './i18n'\nimport { LaneIcons } from './LaneIcons'\nimport {\n  aggregateTri,\n  botTriColor,\n  formatQaDate,\n  humanTriColor,\n  QA_CASE_STATUS_LABEL_KEY,\n  QA_GROUNDING_LABEL_KEY,\n  QA_PRIORITY_MODIFIER,\n  shortVersion,\n} from './ui'\n\n/**\n * Preact port of the donor `qa-test-panel.vue`.\n *\n * Presentational (no data-fetching, router, or store). The container task feeds\n * it the resolved page-status, a `state` discriminator (so the two new empty\n * states can render even when `page` is null), and the run metadata for the\n * freshness banner. Local UI state (which suite groups are expanded) lives in a\n * `useState` hook — matching the donor's `expanded`/`toggleGroup` interaction.\n *\n * The donor's `<script setup>` computed properties (lane tri-color derivation and\n * suite grouping) are extracted here as the pure exported helpers `laneTriColor`\n * and `groupScenarios` so they're unit-testable and reusable by the mount task.\n */\nexport interface TestPanelProps {\n  page: QaPageStatus | null\n  state: 'known' | 'no-tests' | 'unknown-page'\n  meta: { generated_at: string; app_version: string; environment: string }\n  strings: QaStrings\n}\n\n/** The three independent lane tri-colors of a scenario — single source of the\n *  dev/bot/human derivation (donor `lanesFor`). The client lane with no event is\n *  gray (never green), because `humanTriColor` returns gray for an unvalidated lane. */\nexport interface ScenarioLanes {\n  dev: QaTriColor\n  bot: QaTriColor\n  human: QaTriColor\n}\n\nexport function laneTriColor(scenario: QaScenario): ScenarioLanes {\n  return {\n    dev: humanTriColor(scenario.dev),\n    bot: botTriColor(scenario.automated),\n    human: humanTriColor(scenario.client),\n  }\n}\n\nexport interface ScenarioGroupView {\n  key: string\n  label: string\n  scenarios: QaScenario[]\n}\n\n/** Group scenarios by `scenario.group.key` (donor `grouped`). Scenarios with no\n *  group fall into a `'·'` bucket. Keys are alphabetical with the inter-module\n *  `X` suite forced last, matching the donor ordering. */\nexport function groupScenarios(scenarios: ReadonlyArray<QaScenario>): ScenarioGroupView[] {\n  const map = new Map<string, QaScenario[]>()\n  for (const scenario of scenarios) {\n    const key = scenario.group?.key ?? '·'\n    const bucket = map.get(key) ?? []\n    bucket.push(scenario)\n    map.set(key, bucket)\n  }\n  const keys = [...map.keys()].sort((a, b) => (a === 'X' ? 1 : b === 'X' ? -1 : a.localeCompare(b)))\n  return keys.map((key) => {\n    const bucket = map.get(key)!\n    return { key, label: bucket[0]?.group?.label ?? '', scenarios: bucket }\n  })\n}\n\nfunction interpolate(template: string, params: Record<string, string | number>): string {\n  return template.replace(/\\{(\\w+)\\}/g, (_, k: string) => String(params[k] ?? ''))\n}\n\n/** One scenario row: title, priority chip, provenance + tags, and the three lanes. */\nfunction ScenarioRow({ scenario, strings }: { scenario: QaScenario; strings: QaStrings }) {\n  const lanes = laneTriColor(scenario)\n  const provenance = scenario.automated.provenance\n  return h(\n    'li',\n    { class: 'qa-row', 'data-testid': 'qa-scenario-row' },\n    h(\n      'span',\n      { class: 'qa-row__title' },\n      scenario.priority\n        ? h(\n            'span',\n            { class: `qa-tag ${QA_PRIORITY_MODIFIER[scenario.priority]}`, 'data-testid': 'qa-priority' },\n            scenario.priority,\n          )\n        : null,\n      h('span', { class: 'qa-row__name' }, scenario.title),\n      scenario.source === 'testgen'\n        ? h('span', { class: 'qa-tag', title: strings['source.testgenHint'] }, strings['source.testgen'])\n        : null,\n      scenario.caseStatus\n        ? h('span', { class: 'qa-tag' }, strings[QA_CASE_STATUS_LABEL_KEY[scenario.caseStatus]])\n        : null,\n      scenario.grounding\n        ? h('span', { class: 'qa-tag' }, strings[QA_GROUNDING_LABEL_KEY[scenario.grounding]])\n        : null,\n      provenance ? h('span', { class: 'qa-tag', 'data-testid': 'qa-provenance' }, provenance) : null,\n    ),\n    h(LaneIcons, { dev: lanes.dev, bot: lanes.bot, human: lanes.human, strings }),\n  )\n}\n\nexport function TestPanel({ page, state, meta, strings }: TestPanelProps) {\n  // Local UI state: which suite-group keys are expanded. Matches the donor's\n  // `expanded` ref, which starts empty → all groups COLLAPSED by default (so a\n  // heavily-loaded page folds into a few theme headers, per QaScenarioGroup).\n  const [expanded, setExpanded] = useState<ReadonlySet<string>>(new Set())\n  const toggleGroup = (key: string): void => {\n    setExpanded((prev) => {\n      const next = new Set(prev)\n      if (next.has(key)) {\n        next.delete(key)\n      } else {\n        next.add(key)\n      }\n      return next\n    })\n  }\n\n  const freshness = interpolate(strings['panel.freshness'], {\n    date: formatQaDate(meta.generated_at),\n    version: shortVersion(meta.app_version),\n    env: meta.environment,\n  })\n\n  let body\n  if (state === 'no-tests') {\n    body = h('p', { class: 'qa-panel__empty' }, strings['panel.noTests'])\n  } else if (state === 'unknown-page') {\n    body = h('p', { class: 'qa-panel__empty' }, strings['panel.unknownPage'])\n  } else {\n    const scenarios = page?.scenarios ?? []\n    const hasGroups = scenarios.some((s) => s.group)\n    if (scenarios.length === 0) {\n      body = h('p', { class: 'qa-panel__empty' }, strings['panel.noScenarios'])\n    } else if (!hasGroups) {\n      // Synthetic / ungrouped page (donor `hasGroups === false`): flat list, no\n      // suite header — avoids a spurious empty-label group wrapper.\n      body = h(\n        'ul',\n        { class: 'qa-panel__list' },\n        scenarios.map((scenario) => h(ScenarioRow, { key: scenario.id, scenario, strings })),\n      )\n    } else {\n      const groups = groupScenarios(scenarios)\n      body = h(\n        'ul',\n        { class: 'qa-panel__groups', 'data-testid': 'qa-suite-groups' },\n        groups.map((group) => {\n          const isOpen = expanded.has(group.key)\n          const agg = {\n            dev: aggregateTri(group.scenarios.map((s) => humanTriColor(s.dev))),\n            bot: aggregateTri(group.scenarios.map((s) => botTriColor(s.automated))),\n            human: aggregateTri(group.scenarios.map((s) => humanTriColor(s.client))),\n          }\n          return h(\n            'li',\n            { key: group.key, class: 'qa-suite', 'data-testid': 'qa-panel-group' },\n            h(\n              'button',\n              {\n                type: 'button',\n                class: 'qa-suite__head',\n                'aria-expanded': isOpen,\n                'data-testid': 'qa-suite-group',\n                onClick: () => toggleGroup(group.key),\n              },\n              h(\n                'span',\n                { class: `qa-suite__chev ${isOpen ? 'is-open' : ''}`, 'aria-hidden': 'true' },\n                '▸',\n              ),\n              h('span', { class: 'qa-suite__key' }, group.key),\n              h('span', { class: 'qa-suite__label' }, group.label),\n              h(\n                'span',\n                { class: 'qa-suite__count' },\n                interpolate(strings['suite.cases'], { count: group.scenarios.length }),\n              ),\n              h(LaneIcons, { dev: agg.dev, bot: agg.bot, human: agg.human, strings }),\n            ),\n            isOpen\n              ? h(\n                  'ul',\n                  { class: 'qa-suite__list' },\n                  group.scenarios.map((scenario) =>\n                    h(ScenarioRow, { key: scenario.id, scenario, strings }),\n                  ),\n                )\n              : null,\n          )\n        }),\n      )\n    }\n  }\n\n  return h(\n    'section',\n    { class: 'qa-panel', 'data-testid': 'qa-panel' },\n    body,\n    h(\n      'footer',\n      { class: 'qa-panel__foot' },\n      h(\n        'span',\n        { class: 'qa-panel__count', title: freshness, 'data-testid': 'qa-panel-freshness' },\n        freshness,\n      ),\n    ),\n  )\n}\n","import { h } from 'preact'\n\nimport type { QaTriColor } from './types'\nimport type { QaStrings } from './i18n'\n\n/**\n * A row of three independent lane chips for a scenario (or a suite aggregate):\n *  - dev (`code`)   = developer validation (the `</>` chevrons);\n *  - bot (`bot`)    = automated execution (Playwright);\n *  - client (`human`) = human/client validation.\n *\n * Each chip carries its OWN tri-color (green/yellow/gray) — they are independent:\n * a human may have validated while the bot never ran, etc. Pure presentational:\n * no hooks, no state, no fetching.\n *\n * SVG path data copied verbatim from the donor `qa-lane-icons.vue`.\n */\nexport interface LaneIconsProps {\n  dev: QaTriColor\n  bot: QaTriColor\n  human: QaTriColor\n  strings: QaStrings\n}\n\n/** Tri-color → BEM modifier (donor mapping: `qa--green|qa--yellow|qa--gray`). */\nconst TRI_MODIFIER: Record<QaTriColor, string> = {\n  green: 'qa--green',\n  yellow: 'qa--yellow',\n  gray: 'qa--gray',\n}\n\nexport function LaneIcons({ dev, bot, human, strings }: LaneIconsProps) {\n  return h(\n    'span',\n    { class: 'qa-lanes', 'data-testid': 'qa-lane-icons' },\n    // dev — chevrons </>\n    h(\n      'span',\n      {\n        class: `qa-lanes__icon qa-lane ${TRI_MODIFIER[dev]}`,\n        title: strings['lane.code'],\n        'aria-label': strings['lane.code'],\n        'data-testid': 'qa-lane-dev',\n        'data-state': dev,\n      },\n      h(\n        'svg',\n        { class: 'qa-lanes__svg', viewBox: '0 0 24 24', 'aria-hidden': 'true' },\n        h('path', {\n          d: 'M8 7l-5 5 5 5M16 7l5 5-5 5',\n          fill: 'none',\n          stroke: 'currentColor',\n          'stroke-width': '2.2',\n          'stroke-linecap': 'round',\n          'stroke-linejoin': 'round',\n        }),\n      ),\n    ),\n    // bot — robot head\n    h(\n      'span',\n      {\n        class: `qa-lanes__icon qa-lane ${TRI_MODIFIER[bot]}`,\n        title: strings['lane.bot'],\n        'aria-label': strings['lane.bot'],\n        'data-testid': 'qa-lane-bot',\n        'data-state': bot,\n      },\n      h(\n        'svg',\n        {\n          class: 'qa-lanes__svg',\n          viewBox: '0 0 24 24',\n          'aria-hidden': 'true',\n          fill: 'none',\n          stroke: 'currentColor',\n          'stroke-width': '2',\n          'stroke-linecap': 'round',\n          'stroke-linejoin': 'round',\n        },\n        h('rect', { x: '4.5', y: '8.5', width: '15', height: '10.5', rx: '2.5' }),\n        h('path', { d: 'M12 4.5v4' }),\n        h('circle', { cx: '12', cy: '3.4', r: '1.3', fill: 'currentColor', stroke: 'none' }),\n        h('path', { d: 'M2.5 12.5v3M21.5 12.5v3' }),\n        h('circle', { cx: '9', cy: '13.5', r: '1.2', fill: 'currentColor', stroke: 'none' }),\n        h('circle', { cx: '15', cy: '13.5', r: '1.2', fill: 'currentColor', stroke: 'none' }),\n      ),\n    ),\n    // client — person\n    h(\n      'span',\n      {\n        class: `qa-lanes__icon qa-lane ${TRI_MODIFIER[human]}`,\n        title: strings['lane.human'],\n        'aria-label': strings['lane.human'],\n        'data-testid': 'qa-lane-client',\n        'data-state': human,\n      },\n      h(\n        'svg',\n        {\n          class: 'qa-lanes__svg',\n          viewBox: '0 0 24 24',\n          'aria-hidden': 'true',\n          fill: 'none',\n          stroke: 'currentColor',\n          'stroke-width': '2',\n          'stroke-linecap': 'round',\n          'stroke-linejoin': 'round',\n        },\n        h('circle', { cx: '12', cy: '8', r: '3.6' }),\n        h('path', { d: 'M5 20c0-3.6 3.1-6 7-6s7 2.4 7 6' }),\n      ),\n    ),\n  )\n}\n","import { h } from 'preact'\nimport { useEffect, useMemo, useState } from 'preact/hooks'\n\nimport type { QaScenario, QaSitemapNode, QaStatusArtifact } from './types'\nimport type { QaStrings } from './i18n'\nimport { LaneIcons } from './LaneIcons'\nimport { normalizePattern } from './resolver'\nimport { botTriColor, formatQaDate, humanTriColor, QA_COLOR_MODIFIER, shortVersion } from './ui'\n\n/**\n * Preact port of the donor `qa-sitemap-modal.vue`.\n *\n * Presentational apart from the Escape-key listener (the one place a hook is\n * warranted — the donor used a `watch` + `window.addEventListener`). Data comes\n * in as the already-fetched artifact; this component owns no store or fetching.\n *\n * The donor's `<script setup>` tree reconstruction is extracted as the pure\n * exported `buildTree` helper so it's unit-testable and reusable by the mount\n * task.\n */\nexport interface SitemapModalProps {\n  artifact: QaStatusArtifact\n  currentPattern: string | null\n  strings: QaStrings\n  onClose: () => void\n}\n\n/** A node of the rebuilt sitemap tree, flattened depth-first for rendering. The\n *  flat `artifact.sitemap` links children to parents via `parent_id`; we rebuild\n *  the hierarchy and emit `depth` + `hasChildren` (donor `flatTree`). */\nexport interface TreeRow {\n  node: QaSitemapNode\n  depth: number\n  hasChildren: boolean\n}\n\nexport function buildTree(nodes: ReadonlyArray<QaSitemapNode>): TreeRow[] {\n  const childrenByParent = new Map<string | null, QaSitemapNode[]>()\n  for (const node of nodes) {\n    const bucket = childrenByParent.get(node.parent_id) ?? []\n    bucket.push(node)\n    childrenByParent.set(node.parent_id, bucket)\n  }\n  for (const bucket of childrenByParent.values()) {\n    bucket.sort((a, b) => a.order - b.order)\n  }\n  const out: TreeRow[] = []\n  const visit = (parentId: string | null, depth: number): void => {\n    for (const node of childrenByParent.get(parentId) ?? []) {\n      const children = childrenByParent.get(node.id) ?? []\n      out.push({ node, depth, hasChildren: children.length > 0 })\n      if (children.length > 0) {\n        visit(node.id, depth + 1)\n      }\n    }\n  }\n  visit(null, 0)\n  return out\n}\n\ninterface CaseEntry {\n  scenario: QaScenario\n  primaries: Set<string>\n  traversed: Set<string>\n}\n\n/** Dedupe scenarios across pages by `external_key ?? id` (donor `dedupedCases`):\n *  a cross-module/traversed case that appears on several pages lists ONCE, with\n *  its primary/traversed pattern sets merged. */\nfunction dedupeScenarios(artifact: QaStatusArtifact): CaseEntry[] {\n  const byKey = new Map<string, CaseEntry>()\n  for (const page of artifact.pages) {\n    for (const scenario of page.scenarios) {\n      const key = scenario.external_key ?? scenario.id\n      const entry = byKey.get(key) ?? { scenario, primaries: new Set<string>(), traversed: new Set<string>() }\n      for (const ref of scenario.pages ?? []) {\n        ;(ref.role === 'primary' ? entry.primaries : entry.traversed).add(ref.pattern)\n      }\n      byKey.set(key, entry)\n    }\n  }\n  const rank = (p?: string): number => (p ? Number(p.slice(1)) : 9)\n  return [...byKey.values()].sort((a, b) => {\n    const ga = a.scenario.group?.key ?? '~'\n    const gb = b.scenario.group?.key ?? '~'\n    if (ga !== gb) {\n      return ga.localeCompare(gb)\n    }\n    return rank(a.scenario.priority) - rank(b.scenario.priority) || a.scenario.title.localeCompare(b.scenario.title)\n  })\n}\n\nfunction interpolate(template: string, params: Record<string, string | number>): string {\n  return template.replace(/\\{(\\w+)\\}/g, (_, k: string) => String(params[k] ?? ''))\n}\n\n/** KPI formatting: the metric values are already whole percentages (0–100), as\n *  emitted by `qa build` (`cli/src/qa/build.ts` rounds `100*num/den`). Render\n *  them directly — do NOT multiply by 100, or a CLI artifact's `25` becomes\n *  \"2500%\". See the QaMetric contract in `types.ts`. */\nfunction formatPct(pct: number): string {\n  return `${Math.round(pct)}%`\n}\n\nexport function SitemapModal({ artifact, currentPattern, strings, onClose }: SitemapModalProps) {\n  const [activeTab, setActiveTab] = useState<'pages' | 'scenarios'>('pages')\n  const [search, setSearch] = useState('')\n\n  // The one place a hook is warranted: close on Escape, cleaned up on unmount.\n  useEffect(() => {\n    const onKeydown = (event: KeyboardEvent): void => {\n      if (event.key === 'Escape') {\n        onClose()\n      }\n    }\n    window.addEventListener('keydown', onKeydown)\n    return () => window.removeEventListener('keydown', onKeydown)\n  }, [onClose])\n\n  const here = currentPattern ? normalizePattern(currentPattern) : null\n  const tree = useMemo(() => buildTree(artifact.sitemap), [artifact])\n  const cases = useMemo(() => dedupeScenarios(artifact), [artifact])\n\n  const titleByPattern = useMemo(() => {\n    const map = new Map<string, string>()\n    for (const node of artifact.sitemap) {\n      map.set(node.path_pattern, node.title)\n    }\n    return map\n  }, [artifact])\n  const patternTitle = (pattern: string): string => titleByPattern.get(pattern) ?? pattern\n\n  const query = search.trim().toLowerCase()\n  const filteredCases = query\n    ? cases.filter(\n        (c) =>\n          c.scenario.title.toLowerCase().includes(query) ||\n          (c.scenario.group?.label ?? '').toLowerCase().includes(query),\n      )\n    : cases\n\n  const freshness = interpolate(strings['panel.freshness'], {\n    date: formatQaDate(artifact.generated_at),\n    version: shortVersion(artifact.app_version),\n    env: artifact.environment,\n  })\n\n  return h(\n    'div',\n    {\n      class: 'qa-modal__backdrop',\n      'data-testid': 'qa-sitemap-modal',\n      onClick: (e: MouseEvent) => {\n        if (e.target === e.currentTarget) onClose()\n      },\n    },\n    h(\n      'div',\n      { class: 'qa-modal', role: 'dialog', 'aria-modal': 'true' },\n      h(\n        'header',\n        { class: 'qa-modal__head' },\n        h('h3', null, strings['modal.title']),\n        h(\n          'button',\n          {\n            type: 'button',\n            class: 'qa-modal__close',\n            title: strings['modal.close'],\n            'aria-label': strings['modal.close'],\n            'data-testid': 'qa-modal-close',\n            onClick: onClose,\n          },\n          '×',\n        ),\n      ),\n      h('div', { class: 'qa-modal__freshness' }, freshness),\n      // Dual metric (doc 01 D12) — rendered as rounded percentages.\n      h(\n        'div',\n        { class: 'qa-kpi', 'data-testid': 'qa-kpi' },\n        h(\n          'div',\n          { class: 'qa-kpi__item' },\n          h('span', { class: 'qa-kpi__value' }, formatPct(artifact.metric.pages_with_scenarios_pct)),\n          h('span', { class: 'qa-kpi__label' }, strings['modal.kpiPagesCovered']),\n        ),\n        h(\n          'div',\n          { class: 'qa-kpi__item' },\n          h('span', { class: 'qa-kpi__value' }, formatPct(artifact.metric.scenarios_passing_pct)),\n          h('span', { class: 'qa-kpi__label' }, strings['modal.kpiScenariosPassing']),\n        ),\n      ),\n      h(\n        'nav',\n        { class: 'qa-tabs' },\n        h(\n          'button',\n          {\n            type: 'button',\n            class: `qa-tabs__tab ${activeTab === 'pages' ? 'qa-tabs__tab--active' : ''}`,\n            'data-testid': 'qa-tab-pages',\n            onClick: () => setActiveTab('pages'),\n          },\n          strings['modal.tabPages'],\n        ),\n        h(\n          'button',\n          {\n            type: 'button',\n            class: `qa-tabs__tab ${activeTab === 'scenarios' ? 'qa-tabs__tab--active' : ''}`,\n            'data-testid': 'qa-tab-scenarios',\n            onClick: () => setActiveTab('scenarios'),\n          },\n          strings['modal.tabScenarios'],\n        ),\n      ),\n      h(\n        'div',\n        { class: 'qa-modal__body' },\n        activeTab === 'pages'\n          ? h(\n              'ul',\n              { class: 'qa-tree' },\n              tree.map((row) => {\n                const isHere = here !== null && row.node.path_pattern === here\n                return h(\n                  'li',\n                  {\n                    key: row.node.id,\n                    class: `qa-tree__row ${isHere ? 'qa-tree__row--here' : ''}`,\n                    'data-testid': isHere ? 'qa-tree-current' : 'qa-sitemap-node',\n                    style: { paddingLeft: `${0.5 + row.depth * 1.1}rem` },\n                  },\n                  h(\n                    'span',\n                    {\n                      class: `qa-dot ${QA_COLOR_MODIFIER[row.node.color]}`,\n                      'aria-hidden': 'true',\n                    },\n                  ),\n                  h(\n                    'span',\n                    {\n                      class: `qa-tree__title ${row.node.structural ? 'qa-tree__title--structural' : ''}`,\n                    },\n                    row.node.title || row.node.path_pattern,\n                  ),\n                  isHere ? h('span', { class: 'qa-tree__here' }, ` ${row.node.path_pattern} `) : null,\n                  isHere ? h('span', { class: 'qa-tree__here' }, strings['modal.youAreHere']) : null,\n                  h('span', { class: 'qa-tree__count' }, String(row.node.counts.scenarios)),\n                )\n              }),\n            )\n          : h(\n              'div',\n              { class: 'qa-scenarios' },\n              h('input', {\n                type: 'search',\n                class: 'qa-scenarios__search',\n                placeholder: strings['modal.search'],\n                'data-testid': 'qa-scenarios-search',\n                value: search,\n                onInput: (e: Event) => setSearch((e.target as HTMLInputElement).value),\n              }),\n              filteredCases.length === 0\n                ? h('p', { class: 'qa-scenarios__empty' }, strings['modal.noMatch'])\n                : h(\n                    'ul',\n                    { class: 'qa-scenarios__list' },\n                    filteredCases.map((entry) => {\n                      const scenario = entry.scenario\n                      const lanes = {\n                        dev: humanTriColor(scenario.dev),\n                        bot: botTriColor(scenario.automated),\n                        human: humanTriColor(scenario.client),\n                      }\n                      const traversed = [...entry.traversed].map(patternTitle)\n                      return h(\n                        'li',\n                        {\n                          key: scenario.external_key ?? scenario.id,\n                          class: 'qa-scenarios__item',\n                          'data-testid': 'qa-scenario-row',\n                        },\n                        h(\n                          'div',\n                          { class: 'qa-scenarios__line' },\n                          h('span', { class: `qa-dot ${QA_COLOR_MODIFIER[scenario.color]}`, 'aria-hidden': 'true' }),\n                          scenario.group?.key\n                            ? h('span', { class: 'qa-suitekey' }, scenario.group.key)\n                            : null,\n                          h('span', { class: 'qa-scenarios__title' }, scenario.title),\n                          scenario.source === 'testgen'\n                            ? h('span', { class: 'qa-tag' }, strings['source.testgen'])\n                            : null,\n                          h(LaneIcons, { dev: lanes.dev, bot: lanes.bot, human: lanes.human, strings }),\n                        ),\n                        h(\n                          'div',\n                          { class: 'qa-scenarios__pages' },\n                          entry.primaries.size\n                            ? h(\n                                'span',\n                                { class: 'qa-scenarios__primary' },\n                                interpolate(strings['modal.appliesTo'], { count: entry.primaries.size }),\n                              )\n                            : null,\n                          traversed.length\n                            ? h(\n                                'span',\n                                { class: 'qa-scenarios__traversed' },\n                                `${strings['modal.traversed']}: ${traversed.join(', ')}`,\n                              )\n                            : null,\n                        ),\n                      )\n                    }),\n                  ),\n            ),\n      ),\n    ),\n  )\n}\n","import type { QaStatusArtifact } from './types'\nimport { createQaMeter as createQaMeterImpl } from './mount'\n\nexport interface QaMeterOptions {\n  /** Where the QA status artifact comes from. An inline object renders\n   *  immediately; a URL is `fetch`ed lazily on first open/hover; a function is\n   *  invoked lazily likewise. */\n  source: string | QaStatusArtifact | (() => Promise<QaStatusArtifact>)\n  /** Router-aware override. Defaults to a routerless resolver over the\n   *  artifact's path_patterns + `window.location.pathname`. */\n  getCurrentPage?: () => string | null\n  locale?: 'fr' | 'en'\n  /** Pastille size. 'md' (48px, default) for standalone; the feedback-widget\n   *  integration passes 'sm' (24px) so the QA FAB reads as secondary. */\n  size?: 'sm' | 'md'\n  position?: { right?: number; bottom?: number }\n}\n\nexport interface QaMeterHandle {\n  open(): void\n  close(): void\n  dispose(): void\n}\n\nexport function createQaMeter(options: QaMeterOptions): QaMeterHandle {\n  return createQaMeterImpl(options)\n}\n\nexport type {\n  QaStatusArtifact,\n  QaColor,\n  QaScenario,\n  QaPageStatus,\n  QaSitemapNode,\n} from './types'\n"],"mappings":";AAAA,SAAS,KAAAA,IAAG,cAAc;AAC1B,SAAS,aAAa,aAAAC,YAAW,YAAAC,iBAAgB;;;ACU1C,IAAM,gBAAgB;AAAA,EAC3B,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,wBAAwB;AAAA,EACxB,4BAA4B;AAAA,EAC5B,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,yBAAyB;AAAA,EACzB,6BAA6B;AAAA,EAC7B,eAAe;AAAA,EACf,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,6BAA6B;AAAA;AAAA,EAE7B,iBAAiB;AAAA,EACjB,qBAAqB;AACvB;AAEO,IAAM,gBAAgB;AAAA,EAC3B,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,wBAAwB;AAAA,EACxB,4BAA4B;AAAA,EAC5B,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,yBAAyB;AAAA,EACzB,6BAA6B;AAAA,EAC7B,eAAe;AAAA,EACf,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,6BAA6B;AAAA;AAAA,EAE7B,iBAAiB;AAAA,EACjB,qBAAqB;AACvB;AAKO,SAAS,eAAe,QAAgC;AAC7D,SAAO,WAAW,OAAO,gBAAgB;AAC3C;;;ACnIO,SAAS,iBAAiB,SAA4C;AAC3E,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,IAAI,QAAQ,WAAW,GAAG,IAAI,UAAU,IAAI,OAAO;AACvD,MAAI,EAAE,QAAQ,WAAW,GAAG;AAC5B,MAAI,EAAE,SAAS,EAAG,KAAI,EAAE,QAAQ,QAAQ,EAAE;AAC1C,SAAO,EAAE,SAAS,IAAI,IAAI;AAC5B;AAEA,IAAM,WAAW,CAAC,MAAwB,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AAErE,SAAS,eAAe,UAAoB,aAAgC;AAC1E,MAAI,WAAW,YAAY;AAC3B,SAAO,WAAW,GAAG;AACnB,UAAM,OAAO,YAAY,WAAW,CAAC;AACrC,QAAI,SAAS,UAAa,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG,EAAG;AAAA,QACjE;AAAA,EACP;AACA,MAAI,SAAS,SAAS,YAAY,SAAS,SAAS,YAAY,OAAQ,QAAO;AAC/E,SAAO,SAAS,MAAM,CAAC,KAAK,MAAM;AAChC,UAAM,MAAM,YAAY,CAAC;AACzB,WAAO,QAAQ,WAAc,IAAI,WAAW,GAAG,KAAK,QAAQ;AAAA,EAC9D,CAAC;AACH;AAEO,SAAS,YAAY,UAAkB,UAAgD;AAC5F,QAAM,WAAW,SAAS,iBAAiB,QAAQ,CAAC;AACpD,MAAI,OAAsB;AAC1B,MAAI,YAAY;AAChB,aAAW,WAAW,UAAU;AAC9B,UAAM,UAAU,SAAS,OAAO;AAChC,QAAI,CAAC,eAAe,UAAU,OAAO,EAAG;AACxC,UAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC,EAAE;AAC1D,UAAM,QAAQ,UAAU,MAAM,QAAQ;AACtC,QAAI,QAAQ,WAAW;AAAE,aAAO;AAAS,kBAAY;AAAA,IAAM;AAAA,EAC7D;AACA,SAAO;AACT;;;AC1BO,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["h","useEffect","useState","h","h","h","h","useState","interpolate","useState","h","useState","useEffect","h","createQaMeter"]}