feat(ms-ai-architect): v1.15.0 — playground v3 project-view integration

Erstatter v2 project-surface (screen-tabs + category-tabs + per-command paste-cards)
med v3 renderProjectView (sidebar med 17 artifacts + main-area + import-modal overlay).
renderActive() ruter project-surface til renderProjectSurfaceV3() som wrapper
renderProjectView + topbar + app-shell.

V2-surface helt fjernet:
- renderProjectSurface (152 linjer)
- renderCommandSubCard (87 linjer)
- rehydratePasteImports (15 linjer)
- ACTIONS['project-screen'], currentProjectScreen
- 5 v2-CSS-klasser: .project-tabs, .project-tab*, .sub-zone, .paste-import-row, .project-header__*, .command-cards

Zombie-handlers beholdt for test-back-compat:
currentProjectTab, ACTIONS['project-tab'], ACTIONS['parse'],
handlePasteImport, window.__handlePasteImport. Unreachable fra v3 DOM
men nødvendige for test-playground-v3.sh + test-playground-parsers.sh.

2 fingerprint-gap lukket:
- requirements.headers: utvidet med "EU AI Act — Krav" pattern
- license.headers: utvidet med "Lisens-kapabilitetsmatrise" pattern
- KNOWN_GAP_FIXTURES = {} i test-playground-fingerprints.sh

migrateDataVersion utvidet med parserFor (3. arg):
- Demo-state med kun raw_markdown auto-parses til project.artifacts[cid]
- defaultParserFor(cmdId) resolverer PARSERS[archetypeFor(cmdId)]
- 3 bootstrap-callsites oppdatert (cold-load, import, load-demo)

Ship-QA bugfixes funnet via browser-dogfood:
- components-tier4-project-view.css lagt til i <link>-kjeden (var ikke loaded
  -> modal-overlay og two-column layout virket ikke)
- renderImportModal setter data-open="true" (DS-kontrakt for display: flex)

Bundler også sesjon 2-4 deliverables som ikke ble committed tidligere:
- shared/playground-design-system v0.6.0 (Tier 4 project-view CSS + 6 tokens)
- ms-ai-architect/playground/vendor/ re-sync til DS v0.6.0
- tests/test-playground-fingerprints.sh (sesjon 4 NY - 32 PASS)
- tests/test-playground-projectview.sh (sesjon 4 NY - 30 PASS)
- tests/test-playground-actions.sh (sesjon 4 NY - 19 PASS)
- tests/test-playground-migrations.sh utvidet (7 -> 16 PASS)
- tests/run-e2e.sh wirer alle 6 playground-suiter

Stats:
- bash tests/run-e2e.sh --playground: 386 PASS, 0 FAIL, 2 WARN (pre-eks)
- bash tests/run-e2e.sh (full): All E2E suites passed
- bash tests/validate-plugin.sh: 219 PASS

Screenshots regenerert til playground/screenshots/v1.15.0/ (24 PNG-er, 12
surfaces x 2 tema). Nye v3-surfaces: project-overview, project-artifact-*,
project-import-modal (viewport-only), project-search.

Docs oppdatert (3 nivåer): README.md (badge + version history),
CHANGELOG.md, CLAUDE.md (playground-seksjon + valideringstabell),
rot-README.md + rot-CLAUDE.md (marketplace-landingen + plugin-index).

.gitignore: ny pattern *.local.html + *.local.json for sesjon-state-filer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-16 20:58:51 +02:00
commit d8882f5220
47 changed files with 3722 additions and 409 deletions

View file

@ -1,14 +1,12 @@
#!/usr/bin/env node
// Capture playground screenshots for v1.14.0 documentation.
// Capture playground screenshots for v1.15.0 documentation.
//
// Opens the single-file playground HTML via file://, drives it through:
// - Initial onboarding (empty state)
// - "Last inn demo-data" → project surface with all 17 reports rehydrated
// - All 4 project screen-tabs (oversikt / rapporter / kontekst / eksport)
// - Each rapport-tab category (regulatory / security / economy / docs / tool)
// - Both themes (dark + light)
// v1.15.0: v2 project-surface (renderProjectSurface med screen-tabs +
// category-tabs) erstattet av renderProjectView (sidebar med 17 artifacts +
// main-area med per-artifact view + import-modal). Skjermbilder oppdatert
// til å fange v3-surfaces.
//
// Output: playground/screenshots/v1.14.0/<surface>-<theme>.png
// Output: playground/screenshots/v1.15.0/<surface>-<theme>.png
//
// Usage:
// cd tests/screenshot
@ -25,7 +23,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PLUGIN_ROOT = resolve(__dirname, '..', '..');
const HTML_PATH = join(PLUGIN_ROOT, 'playground', 'ms-ai-architect-playground.html');
const OUT_DIR = join(PLUGIN_ROOT, 'playground', 'screenshots', 'v1.14.0');
const OUT_DIR = join(PLUGIN_ROOT, 'playground', 'screenshots', 'v1.15.0');
const HTML_URL = 'file://' + HTML_PATH;
const VIEWPORT = { width: 1440, height: 900 };
@ -49,7 +47,6 @@ async function clearState(page) {
await page.evaluate(() => {
try { localStorage.clear(); } catch (e) {}
try {
// Best-effort: clear IndexedDB databases.
const dbs = ['ms-ai-architect-state-v1', 'ms-ai-architect-playground'];
dbs.forEach((n) => indexedDB.deleteDatabase(n));
} catch (e) {}
@ -61,9 +58,9 @@ async function loadDemo(page) {
const action = document.querySelector('[data-action="load-demo"]');
if (action) action.click();
});
// Wait for project surface to render + rehydrate paste-imports.
await page.waitForSelector('[data-surface="project"]:not([hidden])', { timeout: 5000 });
await page.waitForTimeout(800); // settle rehydrate microtasks
// Settle migrasjon (v2→v3 auto-parse) + render.
await page.waitForTimeout(1200);
}
async function clickAction(page, action) {
@ -71,28 +68,46 @@ async function clickAction(page, action) {
const el = document.querySelector('[data-action="' + a + '"]');
if (el) el.click();
}, action);
await page.waitForTimeout(300);
}
async function clickProjectTab(page, tabId) {
await page.evaluate((t) => {
const el = document.querySelector('[data-action="project-tab"][data-tab="' + t + '"]');
if (el) el.click();
}, tabId);
await page.waitForTimeout(400);
}
async function clickProjectScreen(page, screenId) {
await page.evaluate((s) => {
const el = document.querySelector('[data-action="project-screen"][data-screen="' + s + '"]');
async function selectArtifact(page, artifactId) {
await page.evaluate((id) => {
const el = document.querySelector('[data-action="project-select-artifact"][data-artifact-id="' + id + '"]');
if (el) el.click();
}, screenId);
}, artifactId);
await page.waitForTimeout(500);
}
async function openImportModal(page, prefillCmd) {
await page.evaluate((cid) => {
// Foretrukket: artifact-reimport-knappen (har eksisterende markdown).
if (cid) {
const el = document.querySelector('[data-action="artifact-reimport"][data-command="' + cid + '"]');
if (el) { el.click(); return; }
}
// Fallback: generisk "Importer rapport"-knapp.
const open = document.querySelector('[data-action="import-open"]');
if (open) open.click();
}, prefillCmd);
await page.waitForSelector('[data-import-modal]', { timeout: 3000 });
await page.waitForTimeout(400);
}
async function shoot(page, name) {
async function setSearchQuery(page, query) {
await page.evaluate((q) => {
const input = document.querySelector('[data-project-search]');
if (!input) return;
input.value = q;
input.dispatchEvent(new Event('input', { bubbles: true }));
}, query);
await page.waitForTimeout(400);
}
async function shoot(page, name, opts) {
const path = join(OUT_DIR, name + '.png');
await page.screenshot({ path, fullPage: FULL_PAGE });
const useFullPage = (opts && opts.fullPage != null) ? opts.fullPage : FULL_PAGE;
await page.screenshot({ path, fullPage: useFullPage });
console.log(' → ' + name + '.png');
}
@ -106,52 +121,58 @@ async function captureAllSurfaces(page, theme) {
await setTheme(page, theme);
await shoot(page, '01-onboarding-empty-' + theme);
// 2. Load demo → project surface (rapporter screen, regulatory tab default)
// 2. Load demo → project-view overview (default — ingen artifact valgt)
await loadDemo(page);
await setTheme(page, theme);
await shoot(page, '02-project-rapporter-regulatory-' + theme);
await shoot(page, '02-project-overview-' + theme);
// 3. Project tab cycle (5 categories)
const TABS = [
{ id: 'security', label: 'security' },
{ id: 'economy', label: 'economy' },
{ id: 'documentation', label: 'documentation' },
{ id: 'tool', label: 'tool' }
// 3-7. 5 sample artifacts som dekker arketype-bredden
const SAMPLE_ARTIFACTS = [
{ id: 'classify', label: 'classify' }, // AI Act-pyramide
{ id: 'security', label: 'security' }, // 6×5 sikkerhets-matrise
{ id: 'ros', label: 'ros' }, // ROS matrise + radar
{ id: 'cost', label: 'cost' }, // P10/P50/P90 distribusjon
{ id: 'summary', label: 'summary' } // Beslutningsnotat
];
for (const tab of TABS) {
await clickProjectTab(page, tab.id);
await page.waitForTimeout(500);
await shoot(page, '03-project-rapporter-' + tab.label + '-' + theme);
for (let i = 0; i < SAMPLE_ARTIFACTS.length; i++) {
const a = SAMPLE_ARTIFACTS[i];
await selectArtifact(page, a.id);
const num = String(3 + i).padStart(2, '0');
await shoot(page, num + '-project-artifact-' + a.label + '-' + theme);
}
// 4. Project screen-tabs (oversikt / kontekst / eksport)
await clickProjectScreen(page, 'oversikt');
await shoot(page, '04-project-oversikt-' + theme);
await clickProjectScreen(page, 'kontekst');
await shoot(page, '05-project-kontekst-' + theme);
await clickProjectScreen(page, 'eksport');
await shoot(page, '06-project-eksport-' + theme);
// 8. Import-modal åpen (med prefill fra eksisterende ros-artifact)
// Viewport-only (ikke fullPage) — modal er position:fixed; fullPage
// skroller forbi overlay-en og kaster bort kontekst.
await openImportModal(page, 'ros');
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(200);
await shoot(page, '08-project-import-modal-' + theme, { fullPage: false });
await clickAction(page, 'import-close');
await page.waitForTimeout(300);
// Back to rapporter for nav screenshots
await clickProjectScreen(page, 'rapporter');
// 9. Sidebar-søk aktivt (filtrer på "ros")
await setSearchQuery(page, 'ros');
await shoot(page, '09-project-search-' + theme);
await setSearchQuery(page, ''); // reset
// 5. Home surface
// 10. Home surface
await clickAction(page, 'goto-home');
await page.waitForSelector('[data-surface="home"]:not([hidden])');
await page.waitForTimeout(300);
await shoot(page, '07-home-' + theme);
await shoot(page, '10-home-' + theme);
// 6. Catalog surface
// 11. Catalog surface
await clickAction(page, 'goto-catalog');
await page.waitForSelector('[data-surface="catalog"]:not([hidden])');
await page.waitForTimeout(300);
await shoot(page, '08-catalog-' + theme);
await shoot(page, '11-catalog-' + theme);
// 7. Onboarding (with prefilled state from demo)
// 12. Onboarding prefilled (post-demo med org-felter fylt)
await clickAction(page, 'goto-onboarding');
await page.waitForSelector('[data-surface="onboarding"]:not([hidden])');
await page.waitForTimeout(300);
await shoot(page, '09-onboarding-prefilled-' + theme);
await shoot(page, '12-onboarding-prefilled-' + theme);
}
async function main() {
@ -160,7 +181,7 @@ async function main() {
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: VIEWPORT,
deviceScaleFactor: 2 // crisper screenshots for retina
deviceScaleFactor: 2
});
const page = await context.newPage();
page.on('console', (msg) => {