const state = { snapshot: null, view: "overview", query: "", type: "all", harness: "all", selectedItem: null, lastFocused: null, }; const ui = {}; document.addEventListener("DOMContentLoaded", init); function init() { cacheUi(); bindControls(); loadSnapshot(); } function cacheUi() { ui.subtitle = document.getElementById("snapshot-subtitle"); ui.meta = document.getElementById("topbar-meta"); ui.content = document.getElementById("content"); ui.viewTitle = document.getElementById("view-title"); ui.viewNav = document.getElementById("view-nav"); ui.search = document.getElementById("search-input"); ui.type = document.getElementById("type-filter"); ui.harness = document.getElementById("harness-filter"); ui.reset = document.getElementById("reset-filters"); ui.dialog = document.getElementById("detail-dialog"); ui.detailKind = document.getElementById("detail-kind"); ui.detailTitle = document.getElementById("detail-title"); ui.detailSummary = document.getElementById("detail-summary"); ui.detailMeta = document.getElementById("detail-meta"); ui.detailRaw = document.getElementById("detail-raw"); ui.closeDialog = document.getElementById("close-dialog"); } function bindControls() { ui.viewNav.addEventListener("click", (event) => { const button = event.target.closest("[data-view]"); if (!button) return; state.view = button.dataset.view; render(); }); ui.search.addEventListener("input", (event) => { state.query = event.target.value.trim().toLowerCase(); render(); }); ui.type.addEventListener("change", (event) => { state.type = event.target.value; render(); }); ui.harness.addEventListener("change", (event) => { state.harness = event.target.value; render(); }); ui.reset.addEventListener("click", () => { state.query = ""; state.type = "all"; state.harness = "all"; ui.search.value = ""; ui.type.value = "all"; ui.harness.value = "all"; render(); }); ui.closeDialog.addEventListener("click", closeDialog); ui.dialog.addEventListener("click", (event) => { if (event.target === ui.dialog) closeDialog(); }); ui.dialog.addEventListener("cancel", closeDialog); } async function loadSnapshot() { ui.content.replaceChildren(emptyState("Daten werden geladen.")); try { const response = await fetch("data/harness-snapshot.json", { cache: "no-store" }); if (!response.ok) throw new Error(`HTTP ${response.status}`); state.snapshot = normalizeSnapshot(await response.json()); populateHarnessFilter(); render(); } catch (error) { renderMissingSnapshot(error); } } function renderMissingSnapshot(error) { ui.subtitle.textContent = "Kein lesbarer Snapshot."; ui.meta.replaceChildren(); ui.content.replaceChildren( emptyState( "Snapshot fehlt oder ist nicht lesbar.", "Führe im Ordner `sidebyside` den Befehl `python3 scripts/build_snapshot.py` aus und lade die Seite über einen lokalen HTTP-Server neu.", error ? `Technischer Hinweis: ${error.message}` : "", ), ); } function render() { if (!state.snapshot) return; const snapshot = state.snapshot; const harnesses = filterHarnesses(snapshot.harnesses); const items = filterItems(snapshot.items); const discoveries = filterDiscoveries(snapshot.discoveries); ui.subtitle.textContent = snapshot.meta.generated_at ? `Stand ${formatDate(snapshot.meta.generated_at)}` : "Stand unbekannt"; ui.meta.replaceChildren( statPill("Harnesses", snapshot.harnesses.length), statPill("Inventar", snapshot.items.length), statPill("Funde", snapshot.discoveries.length), statPill("Privat", snapshot.meta.privacy_mode || "lokal"), ); updateNav(); const titleMap = { overview: "Vergleich", usage: "Meine Nutzung", inventory: "Skills & Commands", discoveries: "Unentdeckt", sources: "Quellen", }; ui.viewTitle.textContent = titleMap[state.view] || "Vergleich"; const node = { overview: () => renderOverview(snapshot, harnesses, discoveries), usage: () => renderUsage(harnesses), inventory: () => renderInventory(items, snapshot.items.length), discoveries: () => renderDiscoveries(discoveries), sources: () => renderSources(harnesses, snapshot), }[state.view](); ui.content.replaceChildren(node); } function updateNav() { ui.viewNav.querySelectorAll("[data-view]").forEach((button) => { button.classList.toggle("active", button.dataset.view === state.view); }); } function populateHarnessFilter() { ui.harness.replaceChildren(option("all", "Alle Harnesses")); for (const harness of state.snapshot.harnesses) { ui.harness.append(option(harness.id, harness.name)); } } function normalizeSnapshot(raw) { const meta = asObject(raw.meta); const harnesses = asArray(raw.harnesses).map(normalizeHarness); const comparisonRows = asArray(raw.comparison).map(normalizeComparisonRow); const discoveries = dedupeDiscoveries([ ...asArray(raw.discoveries).map((entry) => normalizeDiscovery(entry, null)), ...harnesses.flatMap((harness) => harness.discoveries.map((entry) => normalizeDiscovery(entry, harness))), ]); const items = harnesses.flatMap(collectDetailItems); return { meta, harnesses, comparisonRows, discoveries, items }; } function normalizeHarness(raw) { const base = asObject(raw); const extensionObject = asObject(base.extensions); const skills = bucketEntries(extensionObject, "skills"); const commands = bucketEntries(extensionObject, "commands"); const agents = bucketEntries(extensionObject, "agents"); const extensions = flattenExtensionEntries(extensionObject).filter((item) => !["skills", "commands", "agents"].includes(item.bucket)); const harness = { id: textValue(base.id), name: textValue(base.name || base.id || "Unbenannt"), status: statusText(base.status), rawStatus: asObject(base.status), config: asObject(base.config), rawUsage: asObject(base.usage), instructionLayers: collectArrayLike(base.instruction_layers), discoveries: collectArrayLike(base.discoveries), sources: collectArrayLike(base.sources), warnings: collectArrayLike(base.warnings), skills, commands, agents, extensions, }; harness.metrics = metricsForHarness(harness); return harness; } function metricsForHarness(harness) { const status = harness.rawStatus; const usage = harness.rawUsage; const counts = asObject(status.counts); return { model: modelLabel(harness.config), sessions: firstNumber(status.thread_count, status.session_count, counts.project_transcripts, counts.sessions, usage.sessions?.db_count), tools: firstNumber(status.log_rows, usage.tools?.observed_count, counts.tools, usage.tools?.db_count), extensions: harness.skills.length + harness.commands.length + harness.agents.length + harness.extensions.length, layers: harness.instructionLayers.length, discoveries: harness.discoveries.length, sources: harness.sources.length, }; } function normalizeComparisonRow(raw) { const base = asObject(raw); return { label: textValue(base.label || base.metric || "Vergleich"), note: textValue(base.note || ""), values: asObject(base.values), }; } function normalizeDiscovery(raw, harness) { const base = asObject(raw); const ownerId = textValue(base.harness_id || base.harnessId || harness?.id || ""); const ownerName = textValue(base.harness || harness?.name || "Global"); const title = discoveryTitle(base); return { kind: "discovery", id: `${ownerId}:${title}:${textValue(base.path || base.table || "")}`, name: title, category: textValue(base.kind || base.category || "discovery"), path: textValue(base.path || base.table || ""), summary: textValue(base.summary || base.reason || base.candidate || base.message || ""), confidence: confidenceValue(base.confidence), score: numberValue(base.score), harnessId: ownerId, harnessName: ownerName, raw: base, }; } function discoveryTitle(base) { const kind = textValue(base.kind); const name = textValue(base.name); const table = textValue(base.table); if (kind === "memory-store") return "Codex Memories sind leer"; if (kind === "hooks") return "Codex Hooks sind leer"; if (kind === "sessions") return "Session Store ist leer"; if (kind === "plugin" && name === "github") return "Nur GitHub ist als Codex-Plugin aktiv"; if (kind === "plugin") return "Plugin-Marktplatz ist kaum genutzt"; if (kind === "database" && table) return `${table} ist leer`; if (kind === "dormant_surface") return "Plugin-System wirkt ungenutzt"; if (kind === "feature_gap") return "Changelog-Features ohne Nutzungsspur"; if (kind === "lightly_used_surface") return "Große Oberfläche, wenig belegte Nutzung"; if (kind === "sparse_history") return "Session-History ist dünn"; if (kind === "empty_channels") return "Hermes Channels sind leer"; if (kind === "gateway_startup_failed") return "Hermes Gateway startet nicht sauber"; if (kind.endsWith("_disabled")) return `${kind.replaceAll("_", " ")} ist deaktiviert`; if (kind === "many_skills_low_usage") return "Viele Hermes-Skills kaum genutzt"; return textValue(base.name || base.title || base.message || base.candidate || base.kind || "Fund"); } function collectDetailItems(harness) { return [ ...harness.skills.map((item) => detailItem(item, harness, "skill")), ...harness.commands.map((item) => detailItem(item, harness, "command")), ...harness.agents.map((item) => detailItem(item, harness, "agent")), ...harness.instructionLayers.map((item) => detailItem(item, harness, "layer")), ...harness.extensions.map((item) => detailItem(item, harness, "extension")), ...usageEntries(harness.rawUsage).map((item) => detailItem(item, harness, "usage")), ]; } function detailItem(raw, harness, kind) { const base = asObject(raw); return { kind, harnessId: harness.id, harnessName: harness.name, id: `${harness.id}:${kind}:${textValue(base.path || base.name || base.label || Math.random())}`, name: textValue(base.name || base.label || base.id || base.layer || base.kind || kind), category: textValue(base.category || base.group || base.bucket || base.kind || kind), path: textValue(base.path || base.file || base.location || ""), summary: textValue(base.summary || base.description || base.preview || base.detail || summarizeValue(base.value)), usage: base.usage || base.count || base.value || "", confidence: confidenceValue(base.confidence), raw: base, }; } function renderOverview(snapshot, harnesses, discoveries) { const wrapper = viewStack(); wrapper.append( sectionHeader("Direkter Vergleich", "Die drei Harnesses als Arbeitswerkzeuge, nicht als Rohdaten-Dump."), harnessOverviewGrid(harnesses), focusPanel(discoveries), comparisonTable(snapshot.comparisonRows, harnesses), ); return wrapper; } function harnessOverviewGrid(harnesses) { const grid = el("div", "harness-overview"); for (const harness of harnesses) { const card = el("article", `harness-tile ${harness.id}`); card.append( row( stack(title(harness.name, "h3"), muted(harness.metrics.model || "Modell unbekannt")), statusBadge(harness.status), ), metricStrip([ ["Sessions", harness.metrics.sessions], ["Tools", harness.metrics.tools], ["Inventar", harness.metrics.extensions], ["Funde", harness.metrics.discoveries], ]), conciseInsight(harness), ); grid.append(card); } return grid; } function conciseInsight(harness) { const firstDiscovery = normalizeDiscovery(harness.discoveries[0] || {}, harness); const box = el("div", "insight-line"); box.append( tag(harness.id), textSpan(firstDiscovery.name || "Keine priorisierte Lücke im Snapshot."), ); return box; } function focusPanel(discoveries) { const panel = el("section", "focus-panel"); panel.append(sectionHeader("Fokus zuerst", "Die wichtigsten unentdeckten oder untergenutzten Features.")); const list = el("div", "focus-list"); balancedFocus(discoveries).forEach((item, index) => { const button = el("button", "focus-item"); button.type = "button"; button.addEventListener("click", () => openDetail(item)); button.append( textSpan(String(index + 1), "rank"), stack(strong(item.name), muted(`${item.harnessName} · Score ${formatNumber(item.score)} · ${confidenceLabel(item.confidence)}`)), ); list.append(button); }); panel.append(list); return panel; } function balancedFocus(discoveries) { const byHarness = discoveries.reduce((acc, item) => { const key = item.harnessId || item.harnessName || "global"; acc[key] = acc[key] || []; acc[key].push(item); return acc; }, {}); const firstPass = Object.values(byHarness).map((items) => items[0]).filter(Boolean); const rest = discoveries.filter((item) => !firstPass.includes(item)); return [...firstPass, ...rest].slice(0, 5); } function comparisonTable(rows, harnesses) { const section = el("section", "panel-section"); section.append(sectionHeader("Matrix", "Normierte Kennzahlen aus dem lokalen Snapshot.")); const wrap = el("div", "table-wrap"); const table = el("table"); const head = el("tr"); head.append(th("Metrik"), ...harnesses.map((harness) => th(harness.name))); table.append(el("thead", "", head)); const body = el("tbody"); for (const rowData of rows) { const tr = el("tr"); const label = el("td"); label.append(strong(rowData.label), rowData.note ? muted(rowData.note) : ""); tr.append(label); harnesses.forEach((harness) => tr.append(td(formatComparisonValue(rowData.values[harness.id] ?? rowData.values[harness.name])))); body.append(tr); } table.append(body); wrap.append(table); section.append(wrap); return section; } function renderUsage(harnesses) { const wrapper = viewStack(); wrapper.append(sectionHeader("Meine Nutzung", "Was deine lokalen Spuren über deinen tatsächlichen Einsatz zeigen.")); const grid = el("div", "usage-grid"); harnesses.forEach((harness) => grid.append(usagePanel(harness))); wrapper.append(grid); return wrapper; } function usagePanel(harness) { const panel = el("article", `usage-panel ${harness.id}`); panel.append(row(stack(title(harness.name, "h3"), muted(harness.metrics.model)), statusBadge(harness.status))); panel.append(metricStrip([ ["Sessions", harness.metrics.sessions], ["Toolcalls", harness.metrics.tools], ["Layer", harness.metrics.layers], ["Quellen", harness.metrics.sources], ])); panel.append(toolBlock(harness), compactConfig(harness)); return panel; } function toolBlock(harness) { const tools = topTools(harness); const block = el("div", "data-block"); block.append(strong("Top-Tools")); if (!tools.length) { block.append(muted("Keine Tool-Nutzung im Snapshot gefunden.")); return block; } const max = Math.max(...tools.map((item) => numberValue(item.count)), 1); tools.slice(0, 8).forEach((item) => { const bar = el("div", "bar-row"); bar.append(textSpan(item.name), textSpan(formatNumber(item.count), "bar-value")); const meter = el("div", "bar-meter"); const fill = el("span"); fill.style.width = `${Math.max(6, (numberValue(item.count) / max) * 100)}%`; meter.append(fill); block.append(bar, meter); }); return block; } function compactConfig(harness) { const rows = [ ["Modell", harness.metrics.model], ["Status", harness.status], ["Skills", harness.skills.length], ["Commands", harness.commands.length], ["Agents", harness.agents.length], ]; const block = el("div", "data-block"); block.append(strong("Profil")); rows.forEach(([label, value]) => block.append(keyValue(label, formatAny(value)))); return block; } function renderInventory(items, total) { const wrapper = viewStack(); wrapper.append(sectionHeader("Skills & Commands", `${items.length} von ${total} Einträgen nach aktuellen Filtern.`)); const typeCounts = countBy(items, "kind"); wrapper.append(metricStrip([ ["Skills", typeCounts.skill || 0], ["Commands", typeCounts.command || 0], ["Agents", typeCounts.agent || 0], ["Layer", typeCounts.layer || 0], ["Erweiterungen", typeCounts.extension || 0], ])); const list = el("div", "inventory-list"); items.slice(0, 160).forEach((item) => list.append(inventoryRow(item))); if (items.length > 160) list.append(emptyState(`${items.length - 160} weitere Treffer ausgeblendet. Nutze Suche oder Filter.`)); wrapper.append(list); return wrapper; } function inventoryRow(item) { const button = el("button", "inventory-row"); button.type = "button"; button.addEventListener("click", () => openDetail(item)); button.append( tag(kindLabel(item.kind)), stack(strong(item.name), muted([item.harnessName, item.category, item.path].filter(Boolean).join(" · "))), textSpan(item.summary || "Keine Kurzbeschreibung.", "row-summary"), ); return button; } function renderDiscoveries(discoveries) { const wrapper = viewStack(); wrapper.append(sectionHeader("Unentdeckt", `${discoveries.length} priorisierte Hinweise aus deinen lokalen Harness-Spuren.`)); const list = el("div", "discovery-list"); discoveries.forEach((item, index) => { const card = el("button", "discovery-row"); card.type = "button"; card.addEventListener("click", () => openDetail(item)); card.append( textSpan(String(index + 1), "rank"), stack(strong(item.name), muted(item.summary || "Keine Beschreibung.")), stack(tag(item.harnessName), muted(`Score ${formatNumber(item.score)} · ${confidenceLabel(item.confidence)}`)), ); list.append(card); }); wrapper.append(list); return wrapper; } function renderSources(harnesses, snapshot) { const wrapper = viewStack(); wrapper.append( sectionHeader("Quellen & Datenschutz", "Gruppierte Quellen statt endloser Dateiliste."), privacyPanel(snapshot), ); harnesses.forEach((harness) => wrapper.append(sourceGroup(harness))); return wrapper; } function privacyPanel(snapshot) { const panel = el("section", "privacy-panel"); panel.append( keyValue("Privacy Mode", snapshot.meta.privacy_mode || "summaries-only"), keyValue("Ausgeschlossen", asArray(snapshot.meta.excluded_patterns).join(", ")), keyValue("Warnungen", asArray(snapshot.meta.warnings).length || "keine"), ); return panel; } function sourceGroup(harness) { const section = el("section", "source-group"); section.append(row(stack(title(harness.name, "h3"), muted(`${harness.sources.length} Quellen`)), statusBadge(harness.status))); const kinds = countBy(harness.sources.map(normalizeSource), "kind"); section.append(metricStrip(Object.entries(kinds).slice(0, 5).map(([kind, count]) => [kind, count]))); const list = el("div", "source-list"); harness.sources.slice(0, 22).forEach((source) => { const item = normalizeSource(source); list.append(keyValue(item.kind || "Quelle", item.path || item.name || "")); }); if (harness.sources.length > 22) list.append(muted(`${harness.sources.length - 22} weitere Quellen im Snapshot.`)); section.append(list); return section; } function filterHarnesses(harnesses) { return harnesses.filter((harness) => state.harness === "all" || harness.id === state.harness); } function filterItems(items) { return items.filter((item) => { if (state.harness !== "all" && item.harnessId !== state.harness) return false; if (state.type !== "all" && item.kind !== state.type) return false; return matchesQuery(item); }).sort((a, b) => `${a.harnessName}:${a.kind}:${a.name}`.localeCompare(`${b.harnessName}:${b.kind}:${b.name}`, "de")); } function filterDiscoveries(discoveries) { return discoveries .filter((item) => { if (state.harness !== "all" && item.harnessId !== state.harness) return false; if (state.type !== "all" && state.type !== "discovery") return false; return matchesQuery(item); }) .sort((a, b) => numberValue(b.score) - numberValue(a.score) || confidenceValue(b.confidence) - confidenceValue(a.confidence)); } function matchesQuery(item) { if (!state.query) return true; return [item.name, item.kind, item.category, item.path, item.summary, item.harnessName] .map((value) => textValue(value).toLowerCase()) .join(" ") .includes(state.query); } function openDetail(item) { state.selectedItem = item; state.lastFocused = document.activeElement; ui.detailKind.textContent = `${item.harnessName || "Global"} · ${kindLabel(item.kind)}`; ui.detailTitle.textContent = item.name; ui.detailSummary.textContent = item.summary || "Keine Kurzbeschreibung im Snapshot."; ui.detailMeta.replaceChildren( ...metaPair("Kategorie", item.category || "unbekannt"), ...metaPair("Pfad", item.path || "kein Pfad"), ...metaPair("Nutzung", formatAny(item.usage)), ...metaPair("Confidence", confidenceLabel(item.confidence)), ); ui.detailRaw.textContent = clippedJson(item.raw || item); ui.dialog.showModal ? ui.dialog.showModal() : ui.dialog.setAttribute("open", ""); } function closeDialog() { state.selectedItem = null; if (ui.dialog.open) ui.dialog.close(); if (state.lastFocused && state.lastFocused.focus) state.lastFocused.focus(); } function viewStack() { return el("div", "view-stack"); } function sectionHeader(titleText, copy) { const header = el("header", "section-header"); header.append(stack(title(titleText, "h2"), muted(copy))); return header; } function metricStrip(entries) { const strip = el("div", "metric-strip"); entries.forEach(([label, value]) => { const metric = el("div", "metric"); metric.append(textSpan(label, "metric-label"), textSpan(formatAny(value), "metric-value")); strip.append(metric); }); return strip; } function keyValue(key, value) { const rowNode = el("div", "kv-row"); rowNode.append(textSpan(key, "kv-key"), textSpan(formatAny(value), "kv-value")); return rowNode; } function statusBadge(status) { const badge = el("span", `status-badge ${statusState(status)}`); badge.textContent = status; return badge; } function statPill(label, value) { const pill = el("span", "stat-pill"); pill.append(textSpan(label), strong(formatAny(value))); return pill; } function tag(text) { const node = el("span", "tag"); node.textContent = text || "n/a"; return node; } function option(value, label) { const node = document.createElement("option"); node.value = value; node.textContent = label; return node; } function th(text) { const node = document.createElement("th"); node.textContent = text; return node; } function td(text) { const node = document.createElement("td"); node.textContent = text; return node; } function title(text, level = "h3") { const node = document.createElement(level); node.textContent = text; return node; } function strong(text) { const node = document.createElement("strong"); node.textContent = text; return node; } function muted(text) { const node = el("p", "muted"); node.textContent = text; return node; } function textSpan(text, className = "") { const node = el("span", className); node.textContent = text; return node; } function stack(...children) { return el("div", "stack", ...children.filter(Boolean)); } function row(...children) { return el("div", "row", ...children.filter(Boolean)); } function el(tagName, className = "", ...children) { const node = document.createElement(tagName); if (className) node.className = className; children.flat().forEach((child) => { if (child === null || child === undefined || child === "") return; node.append(child); }); return node; } function emptyState(titleText, copy = "", detail = "") { const node = el("div", "empty-state"); node.append(strong(titleText)); if (copy) node.append(muted(copy)); if (detail) node.append(muted(detail)); return node; } function bucketEntries(extensionObject, bucket) { return collectArrayLike(extensionObject[bucket]).map((entry) => { const base = asObject(entry); return { ...base, bucket, category: base.category || base.group || base.kind || bucket }; }); } function flattenExtensionEntries(extensionObject) { return Object.entries(extensionObject).flatMap(([bucket, value]) => ( collectArrayLike(value).map((entry) => ({ ...asObject(entry), bucket, category: asObject(entry).category || bucket })) )); } function usageEntries(usage) { return Object.entries(asObject(usage)).map(([key, value]) => ({ name: key, category: "usage", value, summary: summarizeValue(value), })); } function topTools(harness) { const usage = harness.rawUsage; if (Array.isArray(usage.tools?.top)) return usage.tools.top.map((item) => ({ name: item.name || item.target || "tool", count: item.count || item.n || 0 })); if (Array.isArray(usage.log_target_counts)) return usage.log_target_counts.map((item) => ({ name: item.target || item.name || "tool", count: item.count || item.n || 0 })); return []; } function modelLabel(config) { const model = config.model; if (model && typeof model === "object") { return [model.default || model.model, model.provider].filter(Boolean).join(" · "); } return textValue(model || config.default_model || config.provider || "unbekannt"); } function firstNumber(...values) { for (const value of values) { const number = numberValue(value); if (number > 0) return number; } return 0; } function countBy(items, key) { return items.reduce((acc, item) => { const value = textValue(item[key] || "unbekannt"); acc[value] = (acc[value] || 0) + 1; return acc; }, {}); } function dedupeDiscoveries(discoveries) { const seen = new Set(); return discoveries.filter((item) => { const key = `${item.harnessId}:${item.name}:${item.summary}`; if (seen.has(key)) return false; seen.add(key); return true; }); } function normalizeSource(source) { return typeof source === "string" ? { kind: "Quelle", path: source } : asObject(source); } function statusText(value) { if (typeof value === "string") return value || "unknown"; const base = asObject(value); if (base.gateway?.startup_failed || base.flags?.gateway_startup_failed) return "partial"; if (base.exists && Object.values(base.exists).some((entry) => entry === false)) return "partial"; if (Object.keys(base).length) return "active"; return "unknown"; } function statusState(status) { const text = textValue(status).toLowerCase(); if (text.includes("partial") || text.includes("warn")) return "warn"; if (text.includes("fail") || text.includes("error") || text.includes("missing")) return "bad"; return "ok"; } function kindLabel(kind) { return { skill: "Skill", command: "Befehl", agent: "Agent", layer: "Instruktion", extension: "Erweiterung", usage: "Nutzung", discovery: "Fund", }[kind] || kind; } function formatComparisonValue(value) { if (value === undefined || value === null || value === "") return "n/a"; if (typeof value === "number") return formatNumber(value); if (typeof value === "string" && /^\d+$/.test(value)) return formatNumber(value); return String(value); } function formatDate(value) { const date = new Date(value); if (Number.isNaN(date.getTime())) return textValue(value); return new Intl.DateTimeFormat("de-DE", { dateStyle: "medium", timeStyle: "short" }).format(date); } function formatNumber(value) { const number = Number(value); if (!Number.isFinite(number)) return "0"; return new Intl.NumberFormat("de-DE").format(number); } function formatAny(value) { if (value === null || value === undefined || value === "") return "n/a"; if (typeof value === "number") return formatNumber(value); if (typeof value === "boolean") return value ? "Ja" : "Nein"; if (typeof value === "string") return value; if (Array.isArray(value)) return `${value.length} Einträge`; if (typeof value === "object") return summarizeValue(value); return String(value); } function summarizeValue(value) { if (value === null || value === undefined) return ""; if (Array.isArray(value)) return `${value.length} Einträge`; if (typeof value === "object") { const keys = Object.keys(value).slice(0, 6); return keys.length ? `Felder: ${keys.join(", ")}` : "Leeres Objekt"; } return textValue(value); } function confidenceLabel(value) { const confidence = confidenceValue(value); return confidence ? `${Math.round(confidence * 100)}%` : "n/a"; } function confidenceValue(value) { if (typeof value === "number") return value > 1 ? value / 100 : value; const text = textValue(value).toLowerCase(); if (text.startsWith("high")) return 1; if (text.startsWith("medium")) return 0.72; if (text.startsWith("low")) return 0.45; return numberValue(value); } function numberValue(value) { const number = Number(value); return Number.isFinite(number) ? number : 0; } function textValue(value) { if (value === null || value === undefined) return ""; if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean") return String(value); if (Array.isArray(value)) return value.map(textValue).filter(Boolean).join(", "); if (typeof value === "object") return textValue(value.name || value.label || value.id || value.path || value.summary || value.description || ""); return String(value); } function clippedJson(value) { let text = ""; try { text = JSON.stringify(value, null, 2); } catch { text = String(value); } return text.length > 5000 ? `${text.slice(0, 5000)}\n... gekürzt ...` : text; } function collectArrayLike(value) { if (!value) return []; if (Array.isArray(value)) return value; if (typeof value === "object") return Object.values(value); return [value]; } function asArray(value) { return Array.isArray(value) ? value : []; } function asObject(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : {}; } function metaPair(label, value) { const dt = document.createElement("dt"); dt.textContent = label; const dd = document.createElement("dd"); dd.textContent = formatAny(value); return [dt, dd]; }