fix(ms-ai-architect): playground v1.13.0 — visuelle DS-bugs

Fix-pakke som speiler llm-security v7.6.1 (commit f9b555a). Samme klasse
visuelle bugs identifisert via parallell DS-analyse av playground-rendrere.

- B1: renderFindingsBlock + renderRequirements bytter <div class="findings">
  outer (DS grid 360px+1fr klemte indre struktur til 360px-kolonne, lot
  1fr-detail-panel-kolonnen stå tom) til <section class="report-meta">.
  BEM-strukturen findings__list > findings__group > findings__items uendret.
- B2: lokal .report-table CSS for 6+ rapporter (Trusler, Kostnadsoversikt,
  TCO, Risiko-tabell, Key Metrics) som manglet styling — DS implementerer
  ikke klassen. Speilet lokal styling fra llm-security v7.6.1.
- B3: ROS-matrise-bobler bytter <span> til <button type="button"
  data-threat-id="..." aria-label="..."> med document-level click-handler
  som scroller smooth til tilsvarende rad i Trusler-tabellen og
  highlighter raden i 1.6 sek. Lokal CSS for cursor:pointer, hover
  scale(1.15), :focus-visible outline.
- B4: renderRadarSvg bumpet 300x300 til 380x380, R fra 100 til 125,
  label-offset fra R+25 til R+28, dynamisk text-anchor basert på
  horisontal-posisjon for å unngå at bottom-labels overlapper hverandre
  ved 6+ akser (typisk for ROS-rapport med 7 risiko-dimensjoner).
- B5: lokal .recommendation-card__body { overflow-wrap: anywhere;
  word-break: break-word } for å forhindre at lange single-line tekster
  (URLer, owner-tags, dato) skubber innhold ut av viewport i grid-cellen.

tests/test-playground-v3.sh: DS-klasse-assertion oppdatert fra .findings
til .findings__list (BEM-list er fortsatt i bruk; outer grid-container
bevisst fjernet i B1).

Verifisering:
- 22/22 smoke-test PASS (B1-B5 grep-asserts)
- 271/271 playground E2E PASS (201 statisk-struktur + 70 parser-fixtures)
- 219 plugin-validering PASS
- 42 KB-update test PASS

Versjon: v1.12.0 -> v1.13.0 (plugin.json, README badge, README
version-history, CHANGELOG, ROADMAP, TODO, plugin CLAUDE.md
playground-header, root README plugin-list, root CLAUDE.md plugin-list).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-06 14:51:15 +02:00
commit 121c5cc677
8 changed files with 108 additions and 17 deletions

View file

@ -168,6 +168,29 @@
.suppressed-panel__list { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-2) 0 0 0; }
.suppressed-panel__item { padding: var(--space-2) var(--space-3); background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-sm); font-size: var(--font-size-sm); color: var(--color-text-secondary); display: flex; gap: var(--space-3); align-items: baseline; }
.suppressed-panel__id { font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-text-tertiary); }
/* v1.13.0 fix (B2): .report-table — DS har ikke implementert denne klassen, men
playground-rendrere bruker den i 6+ rapporter (Trusler, Kostnadsoversikt,
TCO, Risiko-tabell, Key Metrics). Lokal styling som komplementerer DS-tokens.
Speilet fra llm-security v7.6.1 commit f9b555a. */
.report-table { width: 100%; border-collapse: collapse; margin: var(--space-3) 0; font-size: var(--font-size-sm); }
.report-table th { text-align: left; padding: 8px 12px; border-bottom: 2px solid var(--color-border-moderate); background: var(--color-bg-soft); font-weight: var(--font-weight-semibold); color: var(--color-text-secondary); text-transform: uppercase; font-size: 11px; letter-spacing: 0.04em; }
.report-table td { padding: 8px 12px; border-bottom: 1px solid var(--color-border-subtle); vertical-align: top; color: var(--color-text-primary); }
.report-table tr:last-child td { border-bottom: none; }
.report-table tbody tr:hover { background: var(--color-bg-soft); }
.report-table code { font-family: var(--font-family-mono); font-size: 12px; background: var(--color-surface-sunken); padding: 1px 6px; border-radius: var(--radius-sm); }
/* v1.13.0 fix (B5): recommendation-card body kan inneholde lange single-line
tekster (URLer, owner-tags, dato). Tving word-wrap så grid-celle (auto + 1fr)
ikke skubber innhold utenfor viewport. */
.recommendation-card__body { overflow-wrap: anywhere; word-break: break-word; }
/* v1.13.0 fix (B3): matrix-bobler i ROS-matrise skal være klikkbare.
DS har hover på cellene, men bobler er <span> uten cursor. Klikk-handler
scroller til tilsvarende rad i Trusler-tabellen. */
.matrix__bubble { cursor: pointer; transition: transform var(--duration-fast) var(--ease-default); }
.matrix__bubble:hover { transform: scale(1.15); }
.matrix__bubble:focus-visible { outline: 2px solid var(--color-primary-500); outline-offset: 2px; }
</style>
</head>
<body>
@ -3272,6 +3295,9 @@
}
function renderFindingsBlock(findings, label) {
// v1.13.0 fix (B1): DS' .findings er grid 360px+1fr (list+detail-panel).
// Vi har bare list-kolonnen; bruk <section class="report-meta"> som outer
// for å beholde findings__list-strukturen uten å bli klemt til 360px.
const items = findings.map(function (f) {
return '<li class="findings__item">' +
'<span class="findings__item-severity-dot" data-severity="' + escapeAttr(f.severity || 'info') + '"></span>' +
@ -3280,14 +3306,15 @@
'<span class="findings__item-meta">Lokasjon: ' + escapeHtml(f.location || '—') + ' · Severity: ' + escapeHtml(f.severity || '—') + '</span>' +
'</li>';
}).join('');
return '<div class="findings">' +
return '<section class="report-meta">' +
'<h4>' + escapeHtml(label) + '</h4>' +
'<div class="findings__list">' +
'<div class="findings__group">' +
'<div class="findings__group-header"><span>' + escapeHtml(label) + '</span><span>' + findings.length + '</span></div>' +
'<ul class="findings__items">' + items + '</ul>' +
'</div>' +
'</div>' +
'</div>';
'</section>';
}
function renderMatrixHtml(data, cons_max) {
@ -3307,12 +3334,14 @@
for (let prob = 1; prob <= probSize; prob++) {
const score = prob * cons;
const items = byPC[prob + '_' + cons] || [];
// v1.13.0 fix (B3): bobler er nå <button> så de er klikkbare og fokuserbare.
// data-threat-id mapper til rad i Trusler-tabellen via document-level handler.
const bubblesHtml = items.length
? '<div class="matrix__cell-bubbles">' +
items.slice(0, 3).map(function (it, i) {
return '<span class="matrix__bubble" title="' + escapeAttr(it.label || '') + '">' + (i + 1) + '</span>';
return '<button type="button" class="matrix__bubble" data-threat-id="' + escapeAttr(it.id || it.label || '') + '" title="' + escapeAttr(it.label || '') + '" aria-label="Trussel: ' + escapeAttr(it.label || it.id || '') + '">' + (i + 1) + '</button>';
}).join('') +
(items.length > 3 ? '<span class="matrix__bubble matrix__bubble--count">+' + (items.length - 3) + '</span>' : '') +
(items.length > 3 ? '<button type="button" class="matrix__bubble matrix__bubble--count" aria-label="' + (items.length - 3) + ' flere trusler">+' + (items.length - 3) + '</button>' : '') +
'</div>'
: '';
html += '<div class="matrix__cell" data-score="' + score + '">' +
@ -3332,8 +3361,13 @@
function renderRadarSvg(axes) {
if (!axes || !axes.length) return '';
// v1.13.0 fix (B4): bump SVG fra 300×300 til 380×380, R fra 100 til 125,
// label-offset fra R+25 til R+28, og dynamisk text-anchor basert på
// horisontal-posisjon. Forhindrer at bottom-labels overlapper ved 6+
// akser (typisk for ROS med 7 risiko-dimensjoner). Speilet fra
// llm-security v7.6.1 commit f9b555a.
const N = axes.length;
const cx = 150, cy = 150, R = 100;
const SIZE = 380, cx = SIZE / 2, cy = SIZE / 2, R = 125;
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);
@ -3341,9 +3375,11 @@
}).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 '<text class="radar__label" x="' + x.toFixed(1) + '" y="' + y.toFixed(1) + '" text-anchor="middle" dominant-baseline="middle">' + escapeHtml(a.name) + '</text>';
const x = cx + (R + 28) * Math.cos(angle);
const y = cy + (R + 28) * Math.sin(angle);
const dx = Math.cos(angle);
const anchor = Math.abs(dx) < 0.2 ? 'middle' : (dx > 0 ? 'start' : 'end');
return '<text class="radar__label" x="' + x.toFixed(1) + '" y="' + y.toFixed(1) + '" text-anchor="' + anchor + '" dominant-baseline="middle">' + escapeHtml(a.name) + '</text>';
}).join('');
const spokes = axes.map(function (a, i) {
const angle = (i / N) * 2 * Math.PI - Math.PI / 2;
@ -3352,7 +3388,7 @@
return '<line class="radar__axis" x1="' + cx + '" y1="' + cy + '" x2="' + x.toFixed(1) + '" y2="' + y.toFixed(1) + '"/>';
}).join('');
return '<div class="radar"><div class="radar__chart">' +
'<svg class="radar__svg" viewBox="0 0 300 300">' +
'<svg class="radar__svg" viewBox="0 0 ' + SIZE + ' ' + SIZE + '">' +
'<circle class="radar__grid-line" cx="' + cx + '" cy="' + cy + '" r="' + R + '" fill="none"/>' +
'<circle class="radar__grid-line" cx="' + cx + '" cy="' + cy + '" r="' + (R * 0.6) + '" fill="none"/>' +
spokes + labels +
@ -3467,7 +3503,9 @@
'</div>';
}).join('') : '';
const body = cardsHtml + (expansionsHtml ? '<div class="findings">' + expansionsHtml + '</div>' : '');
// v1.13.0 fix (B1): bytt .findings-outer til .report-meta-wrapper
// (DS' .findings er grid 360px+1fr, klemmer expansion-list til 360px).
const body = cardsHtml + (expansionsHtml ? '<section class="report-meta">' + expansionsHtml + '</section>' : '');
slot.innerHTML = renderPageShell({
eyebrow: 'KRAV',
title: data.title || 'AI Act-krav per risiko og rolle',
@ -5243,6 +5281,32 @@
if (handler) handler(ev, actionEl);
});
// v1.13.0 fix (B3): matrix-bobler klikkbare. Klikk scroller til tilsvarende
// rad i Trusler-tabellen og fremhever den kort. Bruker data-threat-id som
// anker (matchet mot første kolonne i tabellen). Speilet fra llm-security
// v7.6.1 commit f9b555a.
document.addEventListener('click', function (ev) {
const bubble = ev.target.closest('.matrix__bubble[data-threat-id]');
if (!bubble) return;
const threatId = bubble.getAttribute('data-threat-id');
if (!threatId) return;
const tables = document.querySelectorAll('table.report-table');
for (let t = 0; t < tables.length; t++) {
const rows = tables[t].querySelectorAll('tbody tr');
for (let r = 0; r < rows.length; r++) {
const firstCell = rows[r].querySelector('td');
if (firstCell && firstCell.textContent.trim() === threatId) {
rows[r].scrollIntoView({ behavior: 'smooth', block: 'center' });
const orig = rows[r].style.background;
rows[r].style.background = 'var(--color-primary-100, var(--color-bg-soft))';
rows[r].style.transition = 'background var(--duration-base) var(--ease-default)';
setTimeout(function () { rows[r].style.background = orig; }, 1600);
return;
}
}
}
});
ACTIONS['onboarding-toggle-group'] = function (ev, el) {
const exp = el.closest('.expansion');
if (!exp) return;