From 997acb190f0134d0154a6c6795df8687c616c40b Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sun, 3 May 2026 19:38:27 +0200 Subject: [PATCH] feat(ms-ai-architect): playground v3 report renderers (17 commands) [skip-docs] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 17 rapport-renderers per kanonisk routing-tabell (Step 12) gruppert i 4 sub-batches: - Regulatory (6): renderAiActPyramid, renderRequirements, renderTransparency, renderFria, renderConformity, renderDpia - Security (3): renderSecurity, renderRos, renderReview - Economy (2): renderCost, renderLicense - Documentation (6): renderMigrate, renderAdr, renderSummary, renderPoc, renderUtredning, renderCompare Felles helpers: renderError (parser-fail fallback), renderEmptyState, renderMatrixHtml (5x5/6x5 grid), renderRadarSvg, renderThreatsTable, renderFindingsBlock. Wired stub erstattet med PARSERS+RENDERERS routing: handlePasteImport(commandId, markdown) henter cmd fra CATALOG, ruter via PARSERS[archetype] og RENDERERS[cmd.renderer], serialiserer til [data-report-slot=...]. Verktøy-commands (produces_report=false) får empty-state. Parse-feil renderer error-summary med strukturerte feilmeldinger. RENDERERS routing-objekt eksponert som window.__RENDERERS. Verified: 17 fixtures roundtrip parser+renderer, classify produserer .pyramide .pyramide__tier--high (aria-current på matchende tier), adr produserer dl med Status/Date/Deciders. --- .../playground/ms-ai-architect-v3.html | 610 +++++++++++++++++- 1 file changed, 603 insertions(+), 7 deletions(-) diff --git a/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html b/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html index bd93c02..02219e1 100644 --- a/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html +++ b/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html @@ -2611,17 +2611,613 @@ window.__parseSections = parseSections; window.__extractField = extractField; - // ---- Paste-import stub (Step 12 erstatter med faktisk routing) ---- + // ============================================================ + // REPORT RENDERERS (Step 12) + // ============================================================ + // + // 17 renderers per kanonisk archetype-routing-tabell. Hver renderer + // tar parsed data + slot DOM-element, og fyller slot.innerHTML med + // markup som matcher design-system BEM-klasser (.pyramide, .matrix, + // .findings, .rights-matrix, .capability-matrix, .distribution, + // .verdict-block, .pipeline-cockpit, .diff, .aiact-timeline). + // + // Routing: RENDERERS[command.renderer] for oppslag i handlePasteImport + // (under). Verktøy-commands (produces_report=false) får ingen renderer. + + // ---- Felles helpers ---- + + function renderEmptyState() { + return '
' + + '' + + '

Ingen data å vise — tom eller ufullstendig parsing.

' + + '
'; + } + + function renderError(errors, slot) { + const items = (errors || []).map(function (e) { + return '
  • ' + escapeHtml(e.section || 'feil') + ': ' + escapeHtml(e.reason || 'Ukjent') + '
  • '; + }).join(''); + slot.innerHTML = + ''; + } + + function renderThreatsTable(threats) { + if (!threats || !threats.length) return ''; + const rows = threats.map(function (t) { + return '' + escapeHtml(t.id || '') + '' + escapeHtml(t.description || '') + '' + escapeHtml(t.severity || '') + '' + escapeHtml(t.mitigation || '') + ''; + }).join(''); + return '' + rows + '
    IDBeskrivelseSeverityTiltak
    '; + } + + function renderFindingsBlock(findings, label) { + const items = findings.map(function (f) { + return '
  • ' + + '' + + '' + escapeHtml(f.id || '') + '' + + '' + escapeHtml(f.recommendation || '') + '' + + 'Lokasjon: ' + escapeHtml(f.location || '—') + ' · Severity: ' + escapeHtml(f.severity || '—') + '' + + '
  • '; + }).join(''); + return '
    ' + + '
    ' + + '
    ' + + '
    ' + escapeHtml(label) + '' + findings.length + '
    ' + + '
      ' + items + '
    ' + + '
    ' + + '
    ' + + '
    '; + } + + function renderMatrixHtml(data, cons_max) { + cons_max = cons_max || 5; + const cells = data.matrix_cells || []; + const byPC = {}; + cells.forEach(function (c) { + const k = c.prob + '_' + c.cons; + if (!byPC[k]) byPC[k] = []; + byPC[k].push(c); + }); + const probSize = 5; + let html = '
    Konsekvens
    '; + html += '
    '; + for (let cons = cons_max; cons >= 1; cons--) { + html += '
    ' + cons + '
    '; + for (let prob = 1; prob <= probSize; prob++) { + const score = prob * cons; + const items = byPC[prob + '_' + cons] || []; + const bubblesHtml = items.length + ? '
    ' + + items.slice(0, 3).map(function (it, i) { + return '' + (i + 1) + ''; + }).join('') + + (items.length > 3 ? '+' + (items.length - 3) + '' : '') + + '
    ' + : ''; + html += '
    ' + + '' + score + '' + bubblesHtml + + '
    '; + } + } + html += '
    '; + for (let prob = 1; prob <= probSize; prob++) { + html += '
    ' + prob + '
    '; + } + html += '
    '; + html += '
    Sannsynlighet
    '; + html += '
    '; + return html; + } + + function renderRadarSvg(axes) { + if (!axes || !axes.length) return ''; + const N = axes.length; + const cx = 150, cy = 150, R = 100; + const points = axes.map(function (a, i) { + const angle = (i / N) * 2 * Math.PI - Math.PI / 2; + const r = R * (Math.max(0, Math.min(5, a.score)) / 5); + return (cx + r * Math.cos(angle)).toFixed(1) + ',' + (cy + r * Math.sin(angle)).toFixed(1); + }).join(' '); + const labels = axes.map(function (a, i) { + const angle = (i / N) * 2 * Math.PI - Math.PI / 2; + const x = cx + (R + 25) * Math.cos(angle); + const y = cy + (R + 25) * Math.sin(angle); + return '' + escapeHtml(a.name) + ''; + }).join(''); + const spokes = axes.map(function (a, i) { + const angle = (i / N) * 2 * Math.PI - Math.PI / 2; + const x = cx + R * Math.cos(angle); + const y = cy + R * Math.sin(angle); + return ''; + }).join(''); + return '
    ' + + '' + + '' + + '' + + spokes + labels + + '' + + '' + + '
    '; + } + + // ---- Sub-batch A: Regulatory (6) ---- + + function renderAiActPyramid(data, slot) { + const norm = (data.risk_level || '').toLowerCase(); + let activeTier = 'minimal'; + if (/forbidden|uakseptabel|prohibited|unacceptable/.test(norm)) activeTier = 'forbidden'; + else if (/høy|high|hoy/.test(norm)) activeTier = 'high'; + else if (/begrenset|limited/.test(norm)) activeTier = 'limited'; + else if (/minimal|low/.test(norm)) activeTier = 'minimal'; + + const tiers = [ + { id: 'forbidden', label: 'Uakseptabel risiko (Art. 5)' }, + { id: 'high', label: 'Høyrisiko (Art. 6 + Annex III)' }, + { id: 'limited', label: 'Begrenset risiko (Art. 50)' }, + { id: 'minimal', label: 'Minimal risiko' } + ]; + const tiersHtml = tiers.map(function (t) { + const active = (t.id === activeTier); + const ariaCurrent = active ? ' aria-current="true"' : ''; + const marker = active ? ' ← klassifisert' : ''; + return '
    ' + + '
    ' + escapeHtml(t.label) + '
    ' + + marker + + '
    '; + }).join(''); + + const obligationsHtml = (data.obligations || []).length + ? '

    Forpliktelser

      ' + + data.obligations.map(function (o) { return '
    • ' + escapeHtml(o) + '
    • '; }).join('') + + '
    ' + : ''; + const meta = '
    ' + + '
    Rolle
    ' + escapeHtml(data.role || '—') + '
    ' + + (data.reasoning ? '
    Begrunnelse
    ' + escapeHtml(data.reasoning).slice(0, 800) + '
    ' : '') + + '
    '; + slot.innerHTML = '
    ' + tiersHtml + '
    ' + meta + obligationsHtml; + } + + function renderRequirements(data, slot) { + const sevForStatus = function (status) { + const s = (status || '').toLowerCase(); + if (s === 'met') return 'low'; + if (s === 'partial') return 'medium'; + if (s === 'missing') return 'critical'; + return 'info'; + }; + const items = (data.items || []).map(function (it, idx) { + return '
  • ' + + '' + + 'R-' + String(idx + 1).padStart(2, '0') + '' + + '' + escapeHtml(it.requirement) + '' + + 'Kilde: ' + escapeHtml(it.source_article || '—') + ' · Status: ' + escapeHtml(it.status || '—') + '' + + '
  • '; + }).join(''); + slot.innerHTML = + '
    ' + + '
    ' + + '
    ' + + '
    Krav' + (data.items || []).length + '
    ' + + '
      ' + items + '
    ' + + '
    ' + + '
    ' + + '
    '; + } + + function renderTransparency(data, slot) { + const sectionsHtml = (data.sections || []).map(function (s) { + return '

    ' + escapeHtml(s.heading) + '

    ' + escapeHtml(s.body).replace(/\n/g, '
    ') + '

    '; + }).join(''); + slot.innerHTML = '
    ' + sectionsHtml + '
    '; + } + + function renderFria(data, slot) { + const headerHtml = + '
    ' + + '
    Rettighet
    ' + + '
    Impact (0-5)
    ' + + '
    Tiltak
    ' + + '
    '; + const rowsHtml = (data.rights || []).map(function (r) { + return '
    ' + + '
    ' + escapeHtml(r.name) + '
    ' + + '
    ' + r.impact + '
    ' + + '
    ' + escapeHtml(r.mitigation) + '
    ' + + '
    '; + }).join(''); + slot.innerHTML = '
    ' + headerHtml + rowsHtml + '
    '; + } + + function renderConformity(data, slot) { + const stateOf = function (status) { + const s = (status || '').toLowerCase(); + if (s === 'passed' || s === 'met' || s === 'done') return 'passed'; + if (s === 'active' || s === 'partial' || s === 'in-progress') return 'active'; + return 'upcoming'; + }; + const dlList = data.deadlines || []; + let timelineHtml = ''; + if (dlList.length) { + const milestones = dlList.map(function (d, i) { + const left = ((i + 1) / (dlList.length + 1)) * 100; + return '
    ' + + '
    ' + + '
    ' + + '' + escapeHtml(d.date) + '' + + '' + escapeHtml(d.milestone) + '' + + '
    ' + + '
    '; + }).join(''); + timelineHtml = + '
    ' + + '
    ' + + '
    ' + + milestones + + '
    ' + + '
    '; + } + const sevForStatus = function (status) { + const s = (status || '').toLowerCase(); + if (s === 'met') return 'low'; + if (s === 'partial') return 'medium'; + if (s === 'missing') return 'critical'; + return 'info'; + }; + const items = (data.checklist || []).map(function (it, idx) { + return '
  • ' + + '' + + 'C-' + String(idx + 1).padStart(2, '0') + '' + + '' + escapeHtml(it.requirement) + '' + + 'Bevis: ' + escapeHtml(it.evidence || '—') + ' · ' + escapeHtml(it.status || '—') + '' + + '
  • '; + }).join(''); + const findingsHtml = + '
    ' + + '
    ' + + '
    ' + + '
    Sjekkliste' + (data.checklist || []).length + '
    ' + + '
      ' + items + '
    ' + + '
    ' + + '
    ' + + '
    '; + slot.innerHTML = timelineHtml + findingsHtml; + } + + function renderDpia(data, slot) { + slot.innerHTML = renderMatrixHtml(data, 5) + renderThreatsTable(data.threats); + } + + // ---- Sub-batch B: Security (3) ---- + + function renderSecurity(data, slot) { + const matrixHtml = renderMatrixHtml(data, 6); + const radarHtml = renderRadarSvg(data.dimensions || []); + const findingsHtml = renderFindingsBlock(data.findings || [], 'Sikkerhetsfunn'); + slot.innerHTML = matrixHtml + radarHtml + findingsHtml; + } + + function renderRos(data, slot) { + const matrixHtml = renderMatrixHtml(data, 5); + const radarHtml = renderRadarSvg(data.radar_axes || []); + slot.innerHTML = matrixHtml + radarHtml + renderThreatsTable(data.threats); + } + + function renderReview(data, slot) { + slot.innerHTML = renderFindingsBlock(data.findings || [], 'Funn'); + } + + // ---- Sub-batch C: Economy (2) ---- + + function renderCost(data, slot) { + const p10 = data.p10 ? data.p10.monthly : 0; + const p50 = data.p50 ? data.p50.monthly : 0; + const p90 = data.p90 ? data.p90.monthly : 0; + const max = Math.max(p10, p50, p90, 1); + const distRows = [ + { label: 'P10 (lavt)', value: p10 }, + { label: 'P50 (median)', value: p50 }, + { label: 'P90 (høyt)', value: p90 } + ].map(function (r) { + const w = (r.value / max) * 100; + return '
    ' + + '
    ' + escapeHtml(r.label) + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + r.value.toLocaleString('nb-NO') + ' NOK' + + '
    ' + + '
    ' + + '
    '; + }).join(''); + const distHtml = + '
    ' + distRows + + '
    ' + + '0' + Math.floor(max / 2).toLocaleString('nb-NO') + '' + max.toLocaleString('nb-NO') + ' NOK/mnd' + + '
    ' + + '
    '; + const breakdownRows = (data.monthly_breakdown || []).map(function (m) { + return '' + escapeHtml(m.component) + '' + m.cost.toLocaleString('nb-NO') + ' NOK'; + }).join(''); + const breakdownHtml = breakdownRows + ? '' + breakdownRows + '
    KomponentNOK/mnd
    ' + : ''; + const tcoHeaders = data.tco_headers || []; + const tcoHeader = tcoHeaders.map(function (h) { return '' + escapeHtml(h) + ''; }).join(''); + const tcoRows = (data.tco_table || []).map(function (r) { + const cells = tcoHeaders.map(function (h) { return '' + escapeHtml(r[h] || '') + ''; }).join(''); + return '' + cells + ''; + }).join(''); + const tcoHtml = tcoRows + ? '' + tcoHeader + '' + tcoRows + '
    ' + : ''; + slot.innerHTML = distHtml + breakdownHtml + tcoHtml; + } + + function renderLicense(data, slot) { + const licenses = data.licenses || []; + if (!licenses.length) { slot.innerHTML = renderEmptyState(); return; } + const headHtml = + '
    ' + + '
    Kapabilitet
    ' + + licenses.map(function (l) { + return '
    ' + escapeHtml(l.name) + '
    '; + }).join('') + + '
    '; + const capabilityNames = (licenses[0].capabilities || []).map(function (c) { return c.name; }); + const rowsHtml = capabilityNames.map(function (capName, capIdx) { + const cells = licenses.map(function (l) { + const cap = l.capabilities[capIdx]; + const status = (cap && cap.status) || 'missing'; + return '
    ' + + '
    ' + + '
    '; + }).join(''); + return '
    ' + + '
    ' + escapeHtml(capName) + '
    ' + + cells + + '
    '; + }).join(''); + slot.innerHTML = '
    ' + + headHtml + rowsHtml + '
    '; + } + + // ---- Sub-batch D: Documentation (6) ---- + + function renderMigrate(data, slot) { + const phases = data.phases || []; + if (!phases.length) { slot.innerHTML = renderEmptyState(); return; } + const milestones = phases.map(function (p, i) { + const left = ((i + 1) / (phases.length + 1)) * 100; + return '
    ' + + '
    ' + + '
    ' + + '' + (p.duration_weeks ? p.duration_weeks + ' uker' : '') + '' + + '' + escapeHtml(p.name) + '' + + '
    ' + + '
    '; + }).join(''); + const timelineHtml = + '
    ' + + '
    ' + + '
    ' + + milestones + + '
    ' + + '
    '; + const detailsHtml = phases.map(function (p) { + const ms = (p.milestones || []).map(function (m) { return '
  • ' + escapeHtml(m) + '
  • '; }).join(''); + const sc = (p.success_criteria || []).map(function (s) { return '
  • ' + escapeHtml(s) + '
  • '; }).join(''); + return '
    ' + + '

    ' + escapeHtml(p.name) + ' (' + (p.duration_weeks || '?') + ' uker)

    ' + + (ms ? '

    Milepæler

      ' + ms + '
    ' : '') + + (sc ? '

    Suksesskriterier

      ' + sc + '
    ' : '') + + '
    '; + }).join(''); + const risksRows = (data.risks || []).map(function (r) { + return '' + escapeHtml(r.risk || '') + '' + escapeHtml(r.probability || '') + '' + escapeHtml(r.consequence || '') + '' + escapeHtml(r.mitigation || '') + ''; + }).join(''); + const risksHtml = risksRows + ? '' + risksRows + '
    RisikoSannsynlighetKonsekvensTiltak
    ' + : ''; + slot.innerHTML = timelineHtml + detailsHtml + risksHtml; + } + + function renderAdr(data, slot) { + const meta = + '
    ' + + (data.status ? '
    Status
    ' + escapeHtml(data.status) + '
    ' : '') + + (data.date ? '
    Date
    ' + escapeHtml(data.date) + '
    ' : '') + + (data.deciders ? '
    Deciders
    ' + escapeHtml(data.deciders) + '
    ' : '') + + '
    '; + const sectionsHtml = (data.sections || []).map(function (s) { + return '

    ' + escapeHtml(s.heading) + '

    ' + escapeHtml(s.body).replace(/\n/g, '
    ') + '
    '; + }).join(''); + slot.innerHTML = + '
    ' + + '

    ' + escapeHtml(data.title || 'ADR') + '

    ' + + meta + + sectionsHtml + + '
    '; + } + + function renderSummary(data, slot) { + const verdictMap = { + block: { variant: 'block', label: 'BLOCK' }, + warning: { variant: 'warning', label: 'WARNING' }, + allow: { variant: 'allow', label: 'ALLOW' } + }; + const v = verdictMap[(data.verdict || '').toLowerCase()] || { variant: 'warning', label: (data.verdict || '?').toUpperCase() }; + const score = v.variant === 'block' ? 92 : v.variant === 'warning' ? 55 : 22; + const verdictHtml = + '
    ' + + '
    ' + + '
    ' + escapeHtml(v.label) + '
    ' + + '
    ' + escapeHtml(data.sub || 'AI-vurdering') + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + score + '' + + 'heuristisk score (0-100)' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + 'AllowNoticeWarningBlockCritical' + + '
    ' + + '
    ' + + '
    '; + const rationaleHtml = data.rationale + ? '

    Rationale

    ' + escapeHtml(data.rationale).replace(/\n/g, '
    ') + '

    ' + : ''; + let metricsHtml = ''; + if ((data.key_metrics || []).length) { + const headers = data.metrics_headers || Object.keys(data.key_metrics[0] || {}); + const headerRow = headers.map(function (h) { return '' + escapeHtml(h) + ''; }).join(''); + const rows = data.key_metrics.map(function (m) { + const cells = headers.map(function (h) { return '' + escapeHtml(m[h] || '') + ''; }).join(''); + return '' + cells + ''; + }).join(''); + metricsHtml = '

    Key Metrics

    ' + headerRow + '' + rows + '
    '; + } + const nextHtml = (data.next_steps || []).length + ? '

    Next Steps

      ' + data.next_steps.map(function (s) { return '
    • ' + escapeHtml(s) + '
    • '; }).join('') + '
    ' + : ''; + slot.innerHTML = verdictHtml + rationaleHtml + metricsHtml + nextHtml; + } + + function renderPoc(data, slot) { + const phases = data.phases || []; + if (!phases.length) { slot.innerHTML = renderEmptyState(); return; } + const stagesHtml = phases.map(function (p, i) { + const num = String(i + 1).padStart(2, '0'); + const isCurrent = (i === 0); + return '
    ' + + '
    ' + num + '
    ' + + '
    ' + escapeHtml(p.name) + '
    ' + + '
    ' + (p.duration_weeks || '?') + ' uker
    ' + + '
    '; + }).join(''); + const cockpitHtml = '
    ' + stagesHtml + '
    '; + const detailsHtml = phases.map(function (p) { + const ms = (p.milestones || []).map(function (m) { return '
  • ' + escapeHtml(m) + '
  • '; }).join(''); + const sc = (p.success_criteria || []).map(function (s) { return '
  • ' + escapeHtml(s) + '
  • '; }).join(''); + return '
    ' + + '

    ' + escapeHtml(p.name) + ' (' + (p.duration_weeks || '?') + ' uker)

    ' + + (ms ? '

    Milepæler

      ' + ms + '
    ' : '') + + (sc ? '

    Suksesskriterier

      ' + sc + '
    ' : '') + + '
    '; + }).join(''); + const risksRows = (data.risks || []).map(function (r) { + return '' + escapeHtml(r.risk || '') + '' + escapeHtml(r.probability || '') + '' + escapeHtml(r.consequence || '') + '' + escapeHtml(r.mitigation || '') + ''; + }).join(''); + const risksHtml = risksRows + ? '' + risksRows + '
    RisikoSannsynlighetKonsekvensTiltak
    ' + : ''; + slot.innerHTML = cockpitHtml + detailsHtml + risksHtml; + } + + function renderUtredning(data, slot) { + const tocHtml = (data.sections || []).map(function (s, i) { + return '
  • ' + escapeHtml(s.heading) + '
  • '; + }).join(''); + const sectionsHtml = (data.sections || []).map(function (s, i) { + return '

    ' + escapeHtml(s.heading) + '

    ' + escapeHtml(s.body).replace(/\n/g, '
    ') + '
    '; + }).join(''); + slot.innerHTML = + '
    ' + + '' + + '
    ' + + '

    ' + escapeHtml(data.title || 'Utredning') + '

    ' + + sectionsHtml + + '
    ' + + '
    '; + } + + function renderCompare(data, slot) { + const subjects = (data.subjects && data.subjects.length === 2) ? data.subjects : ['Subjekt 1', 'Subjekt 2']; + const firstWord = function (s) { return (s || '').toLowerCase().split(/\s+/)[0] || ''; }; + const fw1 = firstWord(subjects[0]); + const fw2 = firstWord(subjects[1]); + let count1 = 0, count2 = 0, lik = 0; + (data.rows || []).forEach(function (r) { + const w = (r.winner || '').toLowerCase(); + if (!w || /lik|begge|—|-/.test(w)) lik++; + else if (fw1 && w.indexOf(fw1) >= 0) count1++; + else if (fw2 && w.indexOf(fw2) >= 0) count2++; + else lik++; + }); + const summaryHtml = + '
    ' + + '
    ' + count1 + ' ' + escapeHtml(subjects[0]) + '
    ' + + '
    ' + count2 + ' ' + escapeHtml(subjects[1]) + '
    ' + + '
    ' + lik + ' Lik
    ' + + '
    '; + const headerHtml = + '
    ' + + '
    ' + escapeHtml(subjects[0]) + '
    ' + + '
    ' + escapeHtml(subjects[1]) + '
    ' + + '
    '; + const rowsHtml = (data.rows || []).map(function (r) { + const w = (r.winner || '').toLowerCase(); + let cls1 = 'diff__cell--unchanged', cls2 = 'diff__cell--unchanged'; + if (fw1 && w.indexOf(fw1) >= 0) cls1 = 'diff__cell--added'; + if (fw2 && w.indexOf(fw2) >= 0) cls2 = 'diff__cell--added'; + return '
    ' + + '
    ' + escapeHtml(r.aspect) + ': ' + escapeHtml(r.value1) + '
    ' + + '
    ' + escapeHtml(r.aspect) + ': ' + escapeHtml(r.value2) + '
    ' + + '
    '; + }).join(''); + slot.innerHTML = '
    ' + summaryHtml + headerHtml + rowsHtml + '
    '; + } + + // ---- RENDERERS routing-objekt (17 commands) ---- + + const RENDERERS = { + renderAiActPyramid: renderAiActPyramid, + renderRequirements: renderRequirements, + renderTransparency: renderTransparency, + renderFria: renderFria, + renderConformity: renderConformity, + renderDpia: renderDpia, + renderSecurity: renderSecurity, + renderRos: renderRos, + renderReview: renderReview, + renderCost: renderCost, + renderLicense: renderLicense, + renderMigrate: renderMigrate, + renderAdr: renderAdr, + renderSummary: renderSummary, + renderPoc: renderPoc, + renderUtredning: renderUtredning, + renderCompare: renderCompare + }; + window.__RENDERERS = RENDERERS; + + // ---- Paste-import: parser + renderer routing (replaces stub) ---- function handlePasteImport(commandId, markdown) { - // Stub: logger til konsoll for å verifisere DOM-kontrakten. Step 12 - // henter PARSERS[CATALOG[id].report_archetype] + RENDERERS[id], parser - // markdown og injiserer i [data-report-slot=""]. - console.log('parse-pending:', commandId, (markdown || '').slice(0, 80)); + const cmd = (CATALOG.commands || []).find(function (c) { return c.id === commandId; }); const slot = document.querySelector('[data-report-slot="' + commandId + '"]'); - if (slot) { - slot.innerHTML = '

    Markdown mottatt (' + (markdown || '').length + ' tegn). Parser+renderer kommer i Step 12.

    '; + if (!cmd || !cmd.produces_report) { + if (slot) slot.innerHTML = renderEmptyState(); + return; } + if (!slot) return; + const parser = PARSERS[cmd.report_archetype]; + const renderer = RENDERERS[cmd.renderer]; + if (!parser || !renderer) { + slot.innerHTML = '

    Routing-feil

    Mangler parser eller renderer for ' + escapeHtml(cmd.id) + '.

    '; + return; + } + const result = parser(markdown); + slot.innerHTML = ''; + if (result.ok) renderer(result.data, slot); + else renderError(result.errors, slot); } window.__handlePasteImport = handlePasteImport;