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

@ -11,7 +11,7 @@ plugins/
graceful-handoff/ v2.1.0 — Auto-trigger handoff via Stop hook (skill + JSON pipeline + 4-step model-aware context resolution)
linkedin-thought-leadership/ v1.2.0 — LinkedIn content pipeline + analytics
llm-security/ v6.0.0 — Security scanning, auditing, threat modeling
ms-ai-architect/ v1.13.0 — Microsoft AI architecture (Cosmo Skyberg persona) + manual KB-refresh slash command
ms-ai-architect/ v1.13.1 — Microsoft AI architecture (Cosmo Skyberg persona) + manual KB-refresh slash command
okr/ v1.0.0 — OKR guidance for Norwegian public sector
voyage/ v4.0.0 — Brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline (six-command universal pipeline + multi-session resumption + --gates autonomy chain)

View file

@ -164,7 +164,7 @@ Key command: `/graceful-handoff [topic-slug] [--no-commit] [--no-push] [--dry-ru
---
### [MS AI Architect — Azure AI and Microsoft Foundry](plugins/ms-ai-architect/) `v1.13.0` `🇳🇴 Norwegian`
### [MS AI Architect — Azure AI and Microsoft Foundry](plugins/ms-ai-architect/) `v1.13.1` `🇳🇴 Norwegian`
Microsoft AI solution architecture guidance for Norwegian public sector and enterprise.

View file

@ -1,6 +1,6 @@
{
"name": "ms-ai-architect",
"version": "1.13.0",
"version": "1.13.1",
"description": "Microsoft AI Solution Architect - structured architecture guidance for the full Microsoft AI stack",
"author": {
"name": "Kjell Tore Guttormsen"

View file

@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.13.1] - 2026-05-06
### Fixed — playground visuelle bugs (post-v1.13.0 manuell QA)
Patch-pakke som adresserer 10 visuelle bugs identifisert av maintainer i nettleser etter v1.13.0-shipped. Alle skyldtes mismatch mellom playground-rendrere og DS-konvensjoner som v1.13.0 ikke fanget opp.
- **B7: classify "Forpliktelser" indent.** `<dl>`+`<ul>` inne i `<section class="report-meta">` brukte browser-default margin/padding som ga uforutsigbare indents. Lokal `.report-meta`-reset (DL som CSS-grid `max-content + 1fr`, h4 uppercase + bold, ul med `padding-left: var(--space-5)`).
- **B8a: `requirement-expand` handler missing.** `renderRequirements` markup hadde `data-action="requirement-expand"` på hver `expansion__head`-knapp, men ingen handler var registrert i `ACTIONS`-mappen. Klikk på R-01..R-09-radene i AI Act-krav-rapporten gjorde derfor ingenting. Fix: register `ACTIONS['requirement-expand']` med samme toggle-mønster som `onboarding-toggle-group`.
- **B8b: expansion title-main + title-sub kjørte sammen.** DS-spans var inline (`display: inline`), så "dokumentertKilde: Art. 9" rendret uten linjebrytning. Lokal `.expansion__title-main, .expansion__title-sub { display: block }` så de stables vertikalt.
- **B10: kanban-card tegnknekking.** DS' `.kanban-card__name` har `word-break: break-all` som knekker midt i ord ("Tekn isk dokumen tasjon"). Lokal override `.kanban-card .kanban-card__name { word-break: break-word }`.
- **B11: DPIA matrix-bobler ikke responderer på klikk.** v1.13.0 click-handler matchet kun mot første-kolonne i Trusler-tabellen (T-001-mønster). Men DPIA-fixturer har full-tekst label i `matrix_cells`-data og T-001..T-005 i threats-tabellen — ingen match. Fix: utvid match-strategi til (Pass 1) exact first-cell, deretter (Pass 2) substring-match mot enhver celle med 40-tegn-prefiks-toleranse for å håndtere truncation.
- **B12, B13, B15: defensive layout for top-risks/suppressed-panel/phase-detail/aiact-timeline.** Eksplisitt `display: block; clear: both; width: 100%` på sibling-rapport-seksjoner som potensielt kunne lekkefra grid-elementer (`.small-multiples`, `.kanban-board`, `.mat-ladder`). `.phase-detail` får også `margin-top` + h3/h4-spacing-reset.
- **B14: Migrate "skal vel være tabell".** Lagt til phases-summary-tabell over phase-detail-seksjonene (Fase, Varighet, Milepæler-count, Suksesskriterier-count, Status). Samme tabell speilet i `renderPoc` for konsistens.
### Notes on 1.13.1
- B9 (transparensnotis spacing) var visuell preferanse, ikke konkret bug — utsatt.
- Smoke-test 23/23 PASS (`/private/tmp/claude-smoke-msarch-v1131.mjs`).
- Plugin-validering 219 PASS, KB-update 42 PASS, playground E2E 271/271 PASS.
- Patch 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.
## [1.13.0] - 2026-05-06
### Fixed — playground visuelle DS-bugs

View file

@ -187,7 +187,7 @@ claude --plugin ./plugins/ms-ai-architect
/architect:help
```
## Playground (v3 / v1.13.0)
## Playground (v3 / v1.13.1)
Interaktiv decision-builder + rapport-viewer for Microsoft AI-beslutninger. Erstatter v2 5-stegs-pipelinen med en multi-surface-app som persisterer state og visualiserer importerte rapporter inline. Spec: v3-arkitektur dokumentert under `.claude/projects/2026-05-03-playground-v3-architecture/`. v1.10.0-utvidelser dokumentert under `.claude/projects/2026-05-03-ms-ai-architect-v1-10-playground/`. v1.11.0 leverer design-system 100%-adoption (PARALLEL-CSS-migrasjon til DS-konvensjon, inline `<style>`-trim 37%, severity-coded card borders, app-header-restruktur, `.stack-lg` body spacing, AI Act-pyramide bredde-fix). v1.13.0 leverer fix-pakke som speiler llm-security v7.6.1 — 5 visuelle bugs fikset: `renderFindingsBlock`/`renderRequirements` outer-wrapper bytter `<div class="findings">` (DS grid 360px+1fr klemte indre struktur) til `<section class="report-meta">`; lokal `.report-table` CSS for 6+ rapporter; ROS-matrise-bobler bytter `<span>``<button>` med `data-threat-id` + click-handler som scroller til Trusler-tabell-rad; `renderRadarSvg` bumpet 300×300→380×380 med dynamisk `text-anchor` for å unngå label-overlap ved 6+ akser; `recommendation-card__body` overflow-wrap.

View file

@ -6,7 +6,7 @@
*AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)*
![Version](https://img.shields.io/badge/version-1.13.0-blue)
![Version](https://img.shields.io/badge/version-1.13.1-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Docs](https://img.shields.io/badge/reference_docs-387-green)
![Agents](https://img.shields.io/badge/agents-12-orange)
@ -638,6 +638,7 @@ Category-to-skill routing is defined in `scripts/skill-gen/category-skill-map.js
| Version | Date | Highlights |
|---------|------|-----------|
| **1.13.1** | 2026-05-06 | Playground visual bugs patch — 10 bugs identifisert post-v1.13.0 av maintainer i nettleser. Fixet: (B7) classify Forpliktelser indent via `.report-meta` CSS-reset; (B8a) `requirement-expand` ACTIONS-handler manglet — R-01..R-09-rader i AI Act-krav var ikke klikkbare; (B8b) expansion title-main + title-sub display:block så de stables; (B10) kanban-card `word-break:break-word` override DS' break-all; (B11) DPIA matrix-bobler match by description (Pass 1 first-cell exact + Pass 2 any-cell substring); (B12, B13, B15) defensive `display:block; clear:both; width:100%` på top-risks/suppressed-panel/phase-detail/aiact-timeline; (B14) Migrate/POC phases-summary-tabell over phase-detail-seksjoner. 23/23 smoke + 271 E2E + 219 plugin-validering. |
| **1.13.0** | 2026-05-06 | Playground visual DS-fixes — 5 bugs identifisert og fikset i fix-pakke som speiler llm-security v7.6.1: (B1) `renderFindingsBlock` + `renderRequirements` outer-wrapper byttet fra `<div class="findings">` (DS grid 360px+1fr klemte indre struktur) til `<section class="report-meta">`; (B2) lokal `.report-table` CSS for 6+ rapporter (Trusler, Kostnadsoversikt, TCO, Risiko, Key Metrics) som manglet styling; (B3) ROS-matrise-bobler byttet `<span>``<button>` med `data-threat-id` + click-handler som scroller til Trusler-tabell-rad og highlighter; (B4) `renderRadarSvg` bumpet 300×300→380×380, R=125, dynamisk `text-anchor` for å unngå label-overlap ved 6+ akser; (B5) `recommendation-card__body` overflow-wrap. 22/22 smoke-test PASS. 219 plugin-validering. 272 E2E. |
| **1.12.0** | 2026-05-05 | Manuell KB-refresh-arbeidsflyt — ny `/architect:kb-update` slash-kommando som driver poll → endringsrapport → `microsoft_docs_fetch`-oppdatering → commit fra en aktiv Claude Code-sesjon. Schedulering er bevisst utenfor scope og overlatt til brukeren. Tidligere launchd/cron-arkitektur (Wave 3-5: install-kb-cron, weekly-kb-cron, plist/systemd/Windows-templates, auth-mode-validation, lock-file, cost-cap, kb-update-status surfacing i session-start-hook) fjernet — ~1500 linjer kode + 7 testmoduler ut. Holder pluginen klart innenfor Anthropic Consumer Terms § 3 (automated access only via API key or where explicitly permitted). Beholdte utilities (atomic-write, backup, cross-platform-paths, log-rotate) + run-weekly-update + report-changes + build-registry + discover-new-urls fortsatt fullt funksjonelle for change-detection-fasen. 42/42 KB-update-tester PASS. |
| **1.11.0** | 2026-05-04 | Design-system 100%-adoption — 13 generic components hoisted to shared playground-design-system v0.3.0, all PARALLEL CSS names migrated to DS conventions, inline `<style>` block trimmed 37% (202 → 127 lines), severity-coded card borders on report cards, app-header restructure with breadcrumb, `.stack-lg` body spacing across home/project/catalog, AI Act pyramide width fix. Demo state renamed to "Acme Kommune" + "Acme: Kunde-chatbot" for cross-fixture consistency. 24 v1.11.0 screenshots regenerated. 278/278 playground E2E PASS. |

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');