"""HTML UI for the WebPageBench leaderboard."""
from __future__ import annotations
import html
from pathlib import Path
from typing import Any
from src.bench_config import load_bench_config, task_count
from src.formatting import fmt_cost, fmt_duration, fmt_number, fmt_pct_value, fmt_steps, round2
from src.i18n import get_lang, section_desc_t, section_label_t, t, taxonomy_desc_t
from src.load_entries import sort_entries
from src.models import LeaderboardEntry
from src.token_display import fmt_tokens, format_model_usage_breakdown_html
_DETAIL_TAB_LABELS = {
"submission": "Details",
"sections": "Sections",
"taxonomy": "UI taxonomy",
"metrics": "Metrics",
}
def load_css() -> str:
return (Path(__file__).resolve().parent.parent / "assets" / "webpagebench.css").read_text(encoding="utf-8")
def score_tier(pct: float | None) -> str:
if pct is None:
return "danger"
if pct >= 70:
return "success"
if pct >= 40:
return "warning"
return "danger"
def fmt_pct(value: float | None) -> str:
return fmt_pct_value(value)
def fmt_pct_display(entry: LeaderboardEntry) -> str:
if entry.success_rate is None:
return "—"
return fmt_pct_value(entry.success_rate)
def provider_from_model(model: str, provider: str | None = None) -> str:
if provider:
return provider
if "/" in model:
return model.split("/", 1)[0]
return model.split("-", 1)[0]
def _badge_link(label: str, passed: object, total: object, css_class: str, title: str, nav: str) -> str:
stat_html = ""
try:
passed_i = int(passed) if passed is not None else None
total_i = int(total) if total is not None else None
except (TypeError, ValueError):
passed_i = total_i = None
if passed_i is not None and total_i is not None:
stat_html = f'{passed_i}/{total_i} '
return (
f''
f"{html.escape(label)}{stat_html} "
)
def _badge(label: str, css_class: str, title: str) -> str:
return (
f''
f"{html.escape(label)} "
)
def _row_badges(entry: LeaderboardEntry) -> str:
config = load_bench_config()
badges: list[str] = []
ranked_sections = sorted(
(
(stats.success_rate, stats.section_id, section_label_t(stats.section_id), stats.passed, stats.total)
for stats in entry.sections.values()
if stats.success_rate is not None
),
reverse=True,
)[:2]
for _, section_id, label, passed, total in ranked_sections:
desc = section_desc_t(section_id) or label
nav = f"sections:{section_id}"
badges.append(_badge_link(label, passed, total, "domain", str(desc), nav))
ranked_taxonomy = sorted(
(
(float(row.get("success_rate")), class_id)
for class_id, row in entry.ui_classes.items()
if isinstance(row.get("success_rate"), (int, float))
),
reverse=True,
)[:2]
if not ranked_taxonomy and entry.ui_badges:
for class_id in entry.ui_badges[:2]:
meta = config.get("ui_classes", {}).get(class_id, {})
label = str(meta.get("label") or class_id)
row = entry.ui_classes.get(class_id) or {}
nav = f"taxonomy:{class_id}"
badges.append(
_badge_link(
label,
row.get("passed"),
row.get("total"),
"taxonomy",
taxonomy_desc_t(class_id) or str(meta.get("description") or class_id),
nav,
)
)
else:
for _, class_id in ranked_taxonomy:
meta = config.get("ui_classes", {}).get(class_id, {})
row = entry.ui_classes.get(class_id) or {}
label = str(meta.get("label") or class_id)
nav = f"taxonomy:{class_id}"
badges.append(
_badge_link(
label,
row.get("passed"),
row.get("total"),
"taxonomy",
taxonomy_desc_t(class_id) or str(meta.get("description") or class_id),
nav,
)
)
return "".join(badges)
def top_section_badges(entry: LeaderboardEntry, limit: int = 2) -> list[str]:
ranked = [
(stats.success_rate, section_label_t(stats.section_id))
for stats in entry.sections.values()
if stats.success_rate is not None
]
ranked.sort(reverse=True)
return [label for _, label in ranked[:limit]]
def pick_winner(entries: list[LeaderboardEntry], metric: str, *, ascending: bool = False) -> LeaderboardEntry | None:
if not entries:
return None
values = [(entry, entry.metric(metric)) for entry in entries]
values = [(entry, value) for entry, value in values if value is not None]
if not values:
return None
return min(values, key=lambda pair: pair[1])[0] if ascending else max(values, key=lambda pair: pair[1])[0]
def _quick_card(label: str, headline: str, entry: LeaderboardEntry, subtitle: str) -> str:
return f"""
{html.escape(label)}
{html.escape(headline)}
{html.escape(entry.model)}
{html.escape(subtitle)} {html.escape(entry.harness)}
"""
def _pick_headline(entry: LeaderboardEntry, metric: str) -> str:
if metric == "success_rate" or metric == "full_bench_success_rate":
return fmt_pct_display(entry)
if metric == "avg_duration_seconds":
return fmt_duration(entry.avg_duration_seconds)
if metric == "total_duration_seconds":
return fmt_duration(entry.total_duration_seconds)
if metric in {"total_cost_usd", "avg_cost_per_task_usd"}:
return fmt_cost(entry.total_cost_usd)
if metric == "value_score":
value = entry.value_score
return fmt_number(value) if value is not None else "—"
if metric.startswith("domain:"):
return fmt_pct(entry.metric(metric))
return fmt_pct_display(entry)
_GLOBAL_HIGHLIGHTS: tuple[dict[str, object], ...] = (
{
"label": "best_overall",
"metric": "success_rate",
"ascending": False,
"subtitle": "best_overall_sub",
},
{
"label": "best_budget",
"metric": "total_cost_usd",
"ascending": True,
"subtitle": "best_budget_sub",
},
{
"label": "fastest",
"metric": "total_duration_seconds",
"ascending": True,
"subtitle": "fastest_sub",
},
)
def build_highlight_picks(entries: list[LeaderboardEntry]) -> str:
"""Top highlights shown above view tabs on every leaderboard page."""
cards: list[str] = []
for pick in _GLOBAL_HIGHLIGHTS:
metric = str(pick["metric"])
winner = pick_winner(entries, metric, ascending=bool(pick["ascending"]))
if winner is None:
continue
cards.append(
_quick_card(
t(str(pick["label"])),
_pick_headline(winner, metric),
winner,
t(str(pick["subtitle"])),
)
)
if not cards:
return ""
return (
'"
)
def build_quick_picks(entries: list[LeaderboardEntry], view_id: str) -> str:
config = load_bench_config()
picks = config["quick_picks"].get(view_id, [])
cards: list[str] = []
for pick in picks:
metric = pick["metric"]
ascending = metric in {"avg_duration_seconds", "total_duration_seconds", "total_cost_usd"}
winner = pick_winner(entries, metric, ascending=ascending)
if winner is None:
continue
cards.append(_quick_card(pick["label"], _pick_headline(winner, metric), winner, pick["subtitle"]))
if not cards:
return ""
return f'
{"".join(cards)}
'
def _input_badge(entry: LeaderboardEntry) -> str:
modality = entry.input_modality
css = modality.replace("+", "-plus-") if modality not in {"—", ""} else "unknown"
return (
f'{html.escape(modality)} '
)
def _strict_cell(entry: LeaderboardEntry) -> str:
if entry.strict_passed_tasks is None or entry.total_tasks <= 0:
return "—"
pct = entry.strict_success_pct or 0
tier = score_tier(pct)
return (
f'{html.escape(fmt_pct_value(entry.strict_success_rate))}
'
f'{entry.strict_passed_tasks}/{entry.total_tasks}
'
)
def _sort_value(value: float | None) -> str:
if value is None:
return ""
return f"{value:.6f}"
def _sort_th(label: str, key: str, *, active: bool = False, title: str = "") -> str:
active_cls = " is-active" if active else ""
direction = ' data-wab-dir="desc"' if active else ""
title_attr = f' title="{html.escape(title)}"' if title else ""
return (
f""
f''
f"{html.escape(label)} "
)
def _success_row(idx: int, entry: LeaderboardEntry) -> str:
pct = entry.success_pct or 0
tier = score_tier(pct)
badges = _row_badges(entry)
nav = html.escape(f"submission:{entry.entry_id}")
tasks_ratio = entry.passed_tasks / entry.total_tasks if entry.total_tasks else None
return f"""
{idx + 1}
{html.escape(entry.model)}
{badges}
{html.escape(entry.harness)}
{_input_badge(entry)}
{entry.passed_tasks}/{entry.total_tasks}
{html.escape(fmt_pct_display(entry))}
{_strict_cell(entry)}
"""
def _speed_row(idx: int, entry: LeaderboardEntry) -> str:
pct = entry.success_pct or 0
tier = score_tier(pct)
nav = html.escape(f"submission:{entry.entry_id}")
return f"""
{idx + 1}
{html.escape(entry.model)}
{html.escape(entry.harness)} {_input_badge(entry)}
{html.escape(fmt_duration(entry.total_duration_seconds))}
{html.escape(fmt_duration(entry.avg_duration_seconds))}
{html.escape(fmt_steps(entry.total_agent_steps))}
{html.escape(fmt_pct_display(entry))}
"""
def _cost_row(idx: int, entry: LeaderboardEntry) -> str:
pct = entry.success_pct or 0
tier = score_tier(pct)
value = entry.value_score
nav = html.escape(f"submission:{entry.entry_id}")
return f"""
{idx + 1}
{html.escape(entry.model)}
{html.escape(entry.harness)} {_input_badge(entry)}
{html.escape(fmt_cost(entry.total_cost_usd))}
{html.escape(fmt_cost(entry.avg_cost_per_task_usd))}
{html.escape(fmt_tokens(entry.total_tokens))}
{html.escape(fmt_tokens(entry.avg_tokens_per_task))}
{format_model_usage_breakdown_html(entry)}
{fmt_number(value) if value is not None else "—"}
{html.escape(fmt_pct_display(entry))}
"""
def build_table(entries: list[LeaderboardEntry], view_id: str) -> str:
sorted_entries = sort_entries(entries, view_id)
if view_id == "speed":
head = (
f"# {html.escape(t('model'))} {html.escape(t('total_time'))} "
f"{html.escape(t('avg_time'))} {html.escape(t('total_steps'))} "
f"{html.escape(t('success'))} "
)
rows = [_speed_row(i, entry) for i, entry in enumerate(sorted_entries)]
subtitle = t("speed_sub")
view_label = t("view_speed")
elif view_id == "cost":
head = (
f"# {html.escape(t('model'))} {html.escape(t('total_cost'))} "
f"{html.escape(t('per_task'))} {html.escape(t('total_tokens'))} "
f"{html.escape(t('tokens_per_task'))} {html.escape(t('model_usage'))} "
f"{html.escape(t('value'))} {html.escape(t('success'))} "
)
rows = [_cost_row(i, entry) for i, entry in enumerate(sorted_entries)]
subtitle = t("cost_sub")
view_label = t("view_cost")
else:
head = (
f"# {html.escape(t('model'))} {html.escape(t('harness'))} "
f"{html.escape(t('input'))} "
f"{_sort_th(t('tasks_col'), 'tasks')}"
f"{_sort_th(t('success'), 'success', active=True)}"
f"{_sort_th(t('done_pass'), 'strict', title=t('done_pass_title'))} "
)
rows = [_success_row(i, entry) for i, entry in enumerate(sorted_entries)]
subtitle = t("success_sub")
view_label = t("view_success")
body = (
"".join(rows)
if rows
else f'{html.escape(t("no_runs_filter"))} '
)
return f"""
{html.escape(t("by_model", label=view_label))}
{html.escape(subtitle)}
"""
_GITHUB_URL = "https://github.com/ai-forever/WebPageBench"
_GITHUB_ICON = (
''
' '
)
def build_shell_top(entries: list[LeaderboardEntry]) -> str:
"""Header with page menu and GitHub link. View switcher is a separate Gradio control."""
config = load_bench_config()
benchmark = config["benchmark"]
lang = get_lang()
en_active = " active" if lang != "ru" else ""
ru_active = " active" if lang == "ru" else ""
return f"""
{html.escape(benchmark["name"])}
{html.escape(t("tagline"))}
Leaderboard
About
{len(entries)} {html.escape(t("runs"))}
{task_count()} {html.escape(t("tasks"))}
{len(config["sections"])} {html.escape(t("sections_n"))}
EN
RU
{_GITHUB_ICON}
"""
def build_board(entries: list[LeaderboardEntry], view_id: str = "success") -> str:
return f"""
{build_table(entries, view_id)}
"""
def build_shell(entries: list[LeaderboardEntry], view_id: str = "success") -> str:
return build_shell_top(entries) + build_board(entries, view_id)
def build_nav_js() -> str:
"""Client-side navigation for badge deep links (passed to demo.load js=)."""
labels = ", ".join(f'"{key}": "{value}"' for key, value in _DETAIL_TAB_LABELS.items())
return f"""
() => {{
if (window.__wabNavInit) return [];
window.__wabNavInit = true;
const TAB_LABELS = {{{labels}}};
function setPayload(value) {{
const root = document.getElementById("wab-nav-payload");
if (!root) return;
const input = root.querySelector("textarea, input");
if (!input) return;
const proto = input.tagName === "TEXTAREA" ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
if (setter) setter.call(input, value);
else input.value = value;
input.dispatchEvent(new Event("input", {{ bubbles: true }}));
}}
window.wabOpenPage = function (label) {{
const root = document.getElementById("wab-page-tabs");
if (!root || !label) return;
const wrap = root.querySelector(":scope > .tab-wrapper") || root;
wrap.querySelectorAll("button").forEach((btn) => {{
if ((btn.textContent || "").trim() === label) btn.click();
}});
document.querySelectorAll("[data-wab-page]").forEach((el) => {{
el.classList.toggle("active", el.getAttribute("data-wab-page") === label);
}});
}};
const CHROME = {{
en: {{
Details: "Details",
Sections: "Sections",
"UI taxonomy": "UI taxonomy",
Metrics: "Metrics",
Filters: "Filters",
Run: "Run"
}},
ru: {{
Details: "Подробности",
Sections: "Разделы",
"UI taxonomy": "Таксономия UI",
Metrics: "Метрики",
Filters: "Фильтры",
Run: "Прогон"
}}
}};
function currentLang() {{
return window.__wabLang === "ru" ? "ru" : "en";
}}
function setHidden(id, value) {{
const root = document.getElementById(id);
if (!root) return false;
const input = root.matches("textarea, input") ? root : root.querySelector("textarea, input");
if (!input) return false;
const proto = input.tagName === "TEXTAREA" ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
if (setter) setter.call(input, value);
else input.value = value;
input.dispatchEvent(new Event("input", {{ bubbles: true }}));
input.dispatchEvent(new Event("change", {{ bubbles: true }}));
return true;
}}
function clickHidden(id) {{
const root = document.getElementById(id);
if (!root) return false;
const btn = root.matches("button") ? root : root.querySelector("button");
if (!btn) return false;
btn.click();
return true;
}}
let applyingChrome = false;
function applyChrome(lang) {{
const map = CHROME[lang] || CHROME.en;
if (applyingChrome) return;
applyingChrome = true;
try {{
const detail = document.getElementById("wab-detail-tabs");
if (detail) {{
detail.querySelectorAll('button[role="tab"]').forEach((btn) => {{
if (!btn.dataset.wabTabKey) btn.dataset.wabTabKey = (btn.textContent || "").trim();
const key = btn.dataset.wabTabKey;
if (map[key] && btn.textContent !== map[key]) btn.textContent = map[key];
}});
}}
const filters = document.getElementById("wab-filters");
if (filters) {{
const label = filters.querySelector(":scope > .label-wrap span, :scope > button span");
const node = label || filters.querySelector(":scope > .label-wrap, :scope > button");
if (node && map.Filters && (node.textContent.trim() === "Filters" || node.textContent.trim() === "Фильтры" || node.dataset.wabChrome === "filters")) {{
if (node.dataset.wabChrome !== "filters") node.dataset.wabChrome = "filters";
if (node.textContent.trim() !== map.Filters) node.textContent = map.Filters;
}}
}}
const run = document.getElementById("wab-submission-dropdown");
if (run) {{
const lab = run.querySelector("label span, label");
if (lab && map.Run) {{
const current = (lab.textContent || "").trim();
if (current === "Run" || current === "Прогон" || lab.dataset.wabChrome === "run") {{
if (lab.dataset.wabChrome !== "run") lab.dataset.wabChrome = "run";
if (current !== map.Run) lab.textContent = map.Run;
}}
}}
}}
}} finally {{
applyingChrome = false;
}}
}}
function syncPageMenu() {{
const root = document.getElementById("wab-page-tabs");
if (!root) return;
const panels = [...root.querySelectorAll(":scope > .tabitem")];
const visible = panels.findIndex((el) => getComputedStyle(el).display !== "none");
const label = visible === 1 ? "About" : "Leaderboard";
document.querySelectorAll("[data-wab-page]").forEach((el) => {{
el.classList.toggle("active", el.getAttribute("data-wab-page") === label);
}});
}}
window.wabOpenDetailTab = function (tabId) {{
const label = TAB_LABELS[tabId];
if (!label) return;
const root = document.getElementById("wab-detail-tabs") || document.querySelector(".wab-tabs");
if (!root) return;
const buttons = root.querySelectorAll('button[role="tab"]');
for (const btn of buttons) {{
const key = btn.dataset.wabTabKey || (btn.textContent || "").trim();
if (key === label) {{
btn.click();
return;
}}
}}
}};
function clickNavButton() {{
const root = document.getElementById("wab-nav-go");
if (!root) return false;
const btn = root.matches("button") ? root : root.querySelector("button");
if (!btn) return false;
btn.click();
return true;
}}
function scrollToDetail() {{
const anchor = document.getElementById("wab-detail-anchor");
if (anchor) anchor.scrollIntoView({{ behavior: "smooth", block: "start" }});
}}
window.wabNavigate = function (payload) {{
if (!payload) return;
window.__wabPendingNav = payload;
setPayload(payload);
const tab = payload.split(":", 1)[0] || "submission";
window.wabOpenDetailTab(tab);
scrollToDetail();
setTimeout(clickNavButton, 60);
}};
document.addEventListener("click", function (event) {{
const sortBtn = event.target.closest("[data-wab-sort]");
if (sortBtn) {{
event.preventDefault();
event.stopPropagation();
const table = sortBtn.closest("table");
const tbody = table && table.querySelector("tbody");
if (!tbody) return;
const key = sortBtn.getAttribute("data-wab-sort");
const attr = key === "tasks" ? "data-wab-tasks" : key === "strict" ? "data-wab-strict" : "data-wab-success";
const nextDir = sortBtn.classList.contains("is-active") && sortBtn.getAttribute("data-wab-dir") === "desc" ? "asc" : "desc";
table.querySelectorAll("[data-wab-sort]").forEach((el) => {{
el.classList.remove("is-active");
el.removeAttribute("data-wab-dir");
}});
sortBtn.classList.add("is-active");
sortBtn.setAttribute("data-wab-dir", nextDir);
const rows = [...tbody.querySelectorAll("tr.wab-row")];
rows.sort((a, b) => {{
const rawA = a.getAttribute(attr);
const rawB = b.getAttribute(attr);
const numA = rawA === null || rawA === "" ? null : Number(rawA);
const numB = rawB === null || rawB === "" ? null : Number(rawB);
if (numA === null && numB === null) return 0;
if (numA === null || Number.isNaN(numA)) return 1;
if (numB === null || Number.isNaN(numB)) return -1;
return nextDir === "asc" ? numA - numB : numB - numA;
}});
rows.forEach((row, index) => {{
tbody.appendChild(row);
const rank = row.querySelector(".wab-rank");
if (rank) rank.textContent = String(index + 1);
}});
return;
}}
const traceTab = event.target.closest("[data-wab-trace-tab]");
if (traceTab && traceTab.closest(".wab-trace-detail-view")) {{
event.preventDefault();
event.stopPropagation();
const root = traceTab.closest(".wab-trace-detail-view");
const tabId = traceTab.getAttribute("data-wab-trace-tab");
root.querySelectorAll("[data-wab-trace-tab]").forEach((el) => {{
el.classList.toggle("active", el === traceTab);
}});
root.querySelectorAll("[data-wab-trace-panel]").forEach((panel) => {{
panel.classList.toggle("active", panel.getAttribute("data-wab-trace-panel") === tabId);
}});
return;
}}
const stepBtn = event.target.closest("[data-wab-trace-step]");
if (stepBtn && stepBtn.closest(".wab-trace-detail-view")) {{
event.preventDefault();
event.stopPropagation();
const root = stepBtn.closest(".wab-trace-detail-view");
const stepId = stepBtn.getAttribute("data-wab-trace-step");
root.querySelectorAll("[data-wab-trace-step]").forEach((el) => {{
el.classList.toggle("active", el === stepBtn);
}});
root.querySelectorAll("[data-wab-trace-step-panel]").forEach((panel) => {{
panel.classList.toggle("active", panel.getAttribute("data-wab-trace-step-panel") === stepId);
}});
return;
}}
const exportBtn = event.target.closest("[data-wab-trace-export]");
if (exportBtn && exportBtn.closest(".wab-trace-detail-view")) {{
event.preventDefault();
event.stopPropagation();
const root = exportBtn.closest(".wab-trace-detail-view");
const dataEl = root.querySelector(".wab-trace-export-data");
if (!dataEl) return;
const blob = new Blob([dataEl.textContent], {{ type: "application/json" }});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = (root.querySelector(".wab-trace-crumb-current")?.textContent || "trace") + ".json";
a.click();
URL.revokeObjectURL(url);
return;
}}
const langBtn = event.target.closest("[data-wab-lang]");
if (langBtn) {{
event.preventDefault();
event.stopPropagation();
const lang = langBtn.getAttribute("data-wab-lang") === "ru" ? "ru" : "en";
window.__wabLang = lang;
try {{ localStorage.setItem("wab-lang", lang); }} catch (err) {{}}
document.querySelectorAll("[data-wab-lang]").forEach((el) => {{
el.classList.toggle("active", el.getAttribute("data-wab-lang") === lang);
}});
applyChrome(lang);
setHidden("wab-lang-value", lang);
setTimeout(() => clickHidden("wab-lang-go"), 30);
return;
}}
const pageLink = event.target.closest("[data-wab-page]");
if (pageLink) {{
event.preventDefault();
event.stopPropagation();
window.wabOpenPage(pageLink.getAttribute("data-wab-page"));
return;
}}
const jump = event.target.closest(".wab-jump-link");
if (jump) {{
event.preventDefault();
const href = jump.getAttribute("href") || "";
const target = href.startsWith("#") ? document.querySelector(href) : null;
if (target) target.scrollIntoView({{ behavior: "smooth", block: "start" }});
else scrollToDetail();
return;
}}
const badge = event.target.closest("[data-wab-nav]");
if (!badge) return;
event.preventDefault();
event.stopPropagation();
window.wabNavigate(badge.getAttribute("data-wab-nav"));
}});
document.addEventListener("keydown", function (event) {{
if (event.key !== "Enter" && event.key !== " ") return;
const badge = event.target.closest("[data-wab-nav]");
if (!badge) return;
event.preventDefault();
window.wabNavigate(badge.getAttribute("data-wab-nav"));
}});
function hidePageTablist() {{
const pageTabs = document.getElementById("wab-page-tabs");
const wrap = pageTabs && pageTabs.querySelector(":scope > .tab-wrapper");
if (wrap) wrap.style.display = "none";
}}
hidePageTablist();
setTimeout(hidePageTablist, 300);
let savedLang = "en";
try {{ savedLang = localStorage.getItem("wab-lang") === "ru" ? "ru" : "en"; }} catch (err) {{}}
window.__wabLang = savedLang;
applyChrome(savedLang);
if (savedLang === "ru") {{
const kickLang = (attempt) => {{
const shell = document.querySelector(".wab-shell");
if (shell && shell.getAttribute("data-wab-current-lang") === "ru") return;
setHidden("wab-lang-value", "ru");
const clicked = clickHidden("wab-lang-go");
if (attempt < 12 && (!clicked || !(shell && shell.getAttribute("data-wab-current-lang") === "ru"))) {{
setTimeout(() => kickLang(attempt + 1), 500);
}}
}};
setTimeout(() => kickLang(0), 400);
}}
let chromeQueued = false;
const chromeObserver = new MutationObserver(() => {{
if (applyingChrome || chromeQueued) return;
chromeQueued = true;
requestAnimationFrame(() => {{
chromeQueued = false;
if (applyingChrome) return;
syncPageMenu();
applyChrome(currentLang());
}});
}});
chromeObserver.observe(document.body, {{ childList: true, subtree: true }});
document.addEventListener("input", function (event) {{
const input = event.target.closest(".wab-trace-step-filter");
if (!input) return;
const root = input.closest(".wab-trace-detail-view");
if (!root) return;
const query = (input.value || "").trim().toLowerCase();
root.querySelectorAll("[data-wab-trace-step]").forEach((btn) => {{
const label = btn.getAttribute("data-wab-step-label") || "";
btn.style.display = !query || label.includes(query) ? "" : "none";
}});
}});
return [];
}}
"""