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

10 visuelle bugs identifisert av maintainer i nettleser etter v1.13.0
shipped. Patch-pakke som adresserer mismatch mellom playground-rendrere
og DS-konvensjoner som v1.13.0 ikke fanget opp.

- B7: classify "Forpliktelser" indent — lokal .report-meta CSS-reset
  (DL grid max-content+1fr, h4 uppercase+bold, ul padding-left space-5)
  for konsistent venstre-justering uavhengig av nestelse.
- B8a: requirement-expand handler missing — renderRequirements markup
  hadde data-action="requirement-expand" på hver expansion__head, men
  ingen ACTIONS-handler var registrert. R-01..R-09-radene i AI Act-krav
  var derfor ikke klikkbare. Fix: register ACTIONS['requirement-expand'].
- B8b: expansion title-main + title-sub kjørte sammen — DS' spans var
  inline. Lokal display:block så de stables vertikalt.
- B10: kanban-card tegnknekking — DS' word-break:break-all knekker midt
  i ord. Lokal override med break-word.
- B11: DPIA matrix-bobler ikke responderer — v1.13.0 click-handler
  matchet kun mot første-kolonne i Trusler-tabellen. DPIA-fixturer har
  full-tekst label i matrix_cells men T-001-id i threats-tabellen, så
  ingen match. Utvid til (Pass 1) exact first-cell + (Pass 2) substring-
  match mot enhver celle med 40-tegn-prefiks-toleranse.
- B12, B13, B15: defensive layout for top-risks/suppressed-panel/
  phase-detail/aiact-timeline — eksplisitt display:block; clear:both;
  width:100% mot grid-leak fra small-multiples/kanban-board/mat-ladder.
- B14: Migrate "skal vel være tabell" — phases-summary-tabell over
  phase-detail-seksjonene (Fase, Varighet, Milepæler-count, Suksesskriterier-
  count, Status). Samme tabell speilet i renderPoc for konsistens.

Verifisering:
- 23/23 smoke-test PASS (B7-B15 + 5 v1.13.0-regresjoner)
- 271/271 playground E2E PASS
- 219 plugin-validering PASS
- 42 KB-update PASS

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

Berører kun lokal CSS i <style>-blokk, ACTIONS-handler-registrering,
click-handler-utvidelse, og to renderer-funksjoner. Ingen modifisering
av playground/vendor/. Vendored DS' .kanban-card__name { word-break:
break-all } står — overstyres lokalt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-06 15:17:00 +02:00
commit 9f806469f3
7 changed files with 149 additions and 15 deletions

View file

@ -191,6 +191,47 @@
.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; }
/* v1.13.1 fix (B7): .report-meta er ikke definert i vendored DS — vi bruker
<section class="report-meta"> som outer-wrapper i flere rendrere. Browser-
defaults for <dl><dd><ul> gir uforutsigbare indents (Forpliktelser-bullet-
liste i renderAiActPyramid, expansion__body-dl i renderRequirements).
Lokal reset gir konsistent venstre-justering uavhengig av nestelse. */
.report-meta { display: block; margin-block: var(--space-4); }
.report-meta > h4 { margin: 0 0 var(--space-2); font-size: var(--font-size-md); font-weight: var(--font-weight-semibold); color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.04em; }
.report-meta dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-3); margin: 0; }
.report-meta dt { font-weight: var(--font-weight-semibold); color: var(--color-text-secondary); }
.report-meta dd { margin: 0; color: var(--color-text-primary); }
.report-meta > ul { padding-left: var(--space-5); margin: 0; }
.report-meta > ul > li { margin-bottom: var(--space-1); color: var(--color-text-primary); }
/* v1.13.1 fix (B8b): .expansion__title-main og __title-sub er <span>'ene som
DS lar flyte inline. Resultat: "dokumentertKilde: Art. 9" uten linjebrytning.
Tving block-display så de stables vertikalt med riktig spacing. */
.expansion__title-main, .expansion__title-sub { display: block; }
/* v1.13.1 fix (B10): DS' .kanban-card__name har word-break:break-all som knekker
midt i ord ("Tekn isk dokumen tasjon"). Erstatt med break-word så ordskjøt
respekteres. Override krever spesifisitet pga. cascade-orden. */
.kanban-card .kanban-card__name { word-break: break-word; }
/* v1.13.1 fix (B12, B13, B15): defensive block-layout for sibling-rapport-
seksjoner som kan ha overflow-issues mot foregående grid-elementer
(.small-multiples, .kanban-board, .mat-ladder). Sikrer eksplisitt
block-flow + clear så de ikke leker grid-celler eller flyter. */
.top-risks,
.suppressed-panel,
.phase-detail,
.aiact-timeline,
.small-multiples + .top-risks,
.kanban-board + .report-meta,
.mat-ladder + .phase-detail,
.phase-detail + .phase-detail { display: block; clear: both; width: 100%; }
.phase-detail { margin-top: var(--space-5); }
.phase-detail h3 { margin-bottom: var(--space-3); }
.phase-detail h4 { margin: var(--space-3) 0 var(--space-2); font-size: var(--font-size-sm); color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.04em; }
.phase-detail ul { padding-left: var(--space-5); margin: 0 0 var(--space-2); }
.phase-detail ul li { margin-bottom: var(--space-1); }
</style>
</head>
<body>
@ -4104,6 +4145,23 @@
'<span class="cycle-ribbon__msg">' + escapeHtml(cur.name) + '</span>' +
'</div>';
}
// v1.13.1 fix (B14): brukeren etterspurte tabell-visning. Legg til en
// phases-summary-tabell over phase-detail-seksjonene som gir oversikt
// (Fase, Varighet, Milepæler-count, Suksess-count, Status).
const phasesSummaryRows = phases.map(function (p, i) {
const state = stepStateFor(p, i);
const stateLabel = state === 'completed' ? 'Ferdig' : state === 'current' ? 'Pågår' : 'Planlagt';
return '<tr>' +
'<td>' + escapeHtml(p.name) + '</td>' +
'<td>' + escapeHtml(String(p.duration_weeks || '—')) + ' uker</td>' +
'<td>' + ((p.milestones || []).length) + '</td>' +
'<td>' + ((p.success_criteria || []).length) + '</td>' +
'<td>' + escapeHtml(stateLabel) + '</td>' +
'</tr>';
}).join('');
const phasesSummaryHtml = phasesSummaryRows
? '<table class="report-table"><thead><tr><th>Fase</th><th>Varighet</th><th>Milepæler</th><th>Suksesskriterier</th><th>Status</th></tr></thead><tbody>' + phasesSummaryRows + '</tbody></table>'
: '';
const detailsHtml = phases.map(function (p) {
const ms = (p.milestones || []).map(function (m) { return '<li>' + escapeHtml(m) + '</li>'; }).join('');
const sc = (p.success_criteria || []).map(function (s) { return '<li>' + escapeHtml(s) + '</li>'; }).join('');
@ -4119,7 +4177,7 @@
const risksHtml = risksRows
? '<table class="report-table"><thead><tr><th>Risiko</th><th>Sannsynlighet</th><th>Konsekvens</th><th>Tiltak</th></tr></thead><tbody>' + risksRows + '</tbody></table>'
: '';
const body = ribbonHtml + ladderHtml + detailsHtml + risksHtml;
const body = ribbonHtml + ladderHtml + phasesSummaryHtml + detailsHtml + risksHtml;
slot.innerHTML = renderPageShell({
eyebrow: 'MIGRASJON',
title: data.title || 'Migrasjonsplan',
@ -4281,6 +4339,23 @@
'</div>';
}).join('');
const ladderHtml = '<div class="mat-ladder">' + stepsHtml + '</div>';
// v1.13.1 fix (B15): phases-summary-tabell over phase-detail-seksjonene
// gir struktur og forhindrer at faseinfo flyter horisontalt mot risiko-
// tabellen i renderPoc. Samme mønster som renderMigrate.
const phasesSummaryRows = phases.map(function (p, i) {
const state = stepStateFor(p, i);
const stateLabel = state === 'completed' ? 'Ferdig' : state === 'current' ? 'Pågår' : 'Planlagt';
return '<tr>' +
'<td>' + escapeHtml(p.name) + '</td>' +
'<td>' + escapeHtml(String(p.duration_weeks || '—')) + ' uker</td>' +
'<td>' + ((p.milestones || []).length) + '</td>' +
'<td>' + ((p.success_criteria || []).length) + '</td>' +
'<td>' + escapeHtml(stateLabel) + '</td>' +
'</tr>';
}).join('');
const phasesSummaryHtml = phasesSummaryRows
? '<table class="report-table"><thead><tr><th>Fase</th><th>Varighet</th><th>Milepæler</th><th>Suksesskriterier</th><th>Status</th></tr></thead><tbody>' + phasesSummaryRows + '</tbody></table>'
: '';
// B5 traffic-light per success-kriterie. R15: uten eksplisitt status,
// bruk fasens state — done=go, active=warning, future=neutral.
const detailsHtml = phases.map(function (p, i) {
@ -4307,7 +4382,7 @@
const risksHtml = risksRows
? '<table class="report-table"><thead><tr><th>Risiko</th><th>Sannsynlighet</th><th>Konsekvens</th><th>Tiltak</th></tr></thead><tbody>' + risksRows + '</tbody></table>'
: '';
const body = ladderHtml + detailsHtml + risksHtml;
const body = ladderHtml + phasesSummaryHtml + detailsHtml + risksHtml;
// B1 verdict-pille: data.pocVerdict styrer (go/go-with-conditions/block).
// R15: hvis ikke satt, fall tilbake til risk-baserte heuristikk.
let verdict = data.verdict || data.pocVerdict;
@ -5283,28 +5358,54 @@
// 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.
// anker. Speilet fra llm-security v7.6.1 commit f9b555a.
//
// v1.13.1 fix (B11): DPIA-fixturer har full-tekst label i matrix_cells men
// T-001..T-005-id i threats-tabellen. Matchet kun mot første-kolonne ga
// klikk uten effekt. Utvid match-strategi: prøv first-cell exact, så
// any-cell substring-match (fuzzy). Også legg til normalisering for å
// håndtere truncation (escapeHtml + slice).
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 norm = function (s) { return String(s || '').trim().toLowerCase(); };
const target = norm(threatId);
const targetHead = target.slice(0, 40);
const tables = document.querySelectorAll('table.report-table');
// Pass 1: exact match på første-kolonne (T-001-mønster).
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);
if (firstCell && norm(firstCell.textContent) === target) {
highlightRow(rows[r]);
return;
}
}
}
// Pass 2: substring-match mot enhver celle i raden (label-baserte data-id).
for (let t = 0; t < tables.length; t++) {
const rows = tables[t].querySelectorAll('tbody tr');
for (let r = 0; r < rows.length; r++) {
const cells = rows[r].querySelectorAll('td');
for (let c = 0; c < cells.length; c++) {
const cellText = norm(cells[c].textContent);
if (cellText && (cellText.indexOf(targetHead) !== -1 || target.indexOf(cellText.slice(0, 40)) !== -1)) {
highlightRow(rows[r]);
return;
}
}
}
}
function highlightRow(row) {
row.scrollIntoView({ behavior: 'smooth', block: 'center' });
const orig = row.style.background;
row.style.background = 'var(--color-primary-100, var(--color-bg-soft))';
row.style.transition = 'background var(--duration-base) var(--ease-default)';
setTimeout(function () { row.style.background = orig; }, 1600);
}
});
ACTIONS['onboarding-toggle-group'] = function (ev, el) {
@ -5314,6 +5415,17 @@
exp.setAttribute('aria-expanded', open ? 'false' : 'true');
};
// v1.13.1 fix (B8a): renderRequirements bruker data-action="requirement-expand"
// på hver expansion__head-knapp, men handleren var aldri registrert. Klikk
// gjorde derfor ingenting på R-01..R-09-radene i AI Act-krav-rapporten.
// Samme toggle-mønster som onboarding/catalog.
ACTIONS['requirement-expand'] = function (ev, el) {
const exp = el.closest('.expansion');
if (!exp) return;
const open = exp.getAttribute('aria-expanded') === 'true';
exp.setAttribute('aria-expanded', open ? 'false' : 'true');
};
ACTIONS['onboarding-goto-group'] = function (ev, el) {
const groupId = el.dataset.group;
const root = getSurfaceEl('onboarding');