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>
207 lines
7.1 KiB
JavaScript
207 lines
7.1 KiB
JavaScript
#!/usr/bin/env node
|
||
// Capture playground screenshots for v1.15.0 documentation.
|
||
//
|
||
// 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.15.0/<surface>-<theme>.png
|
||
//
|
||
// Usage:
|
||
// cd tests/screenshot
|
||
// npm install
|
||
// npx playwright install chromium # ~150MB download, one-time
|
||
// node run.mjs
|
||
|
||
import { chromium } from 'playwright';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { dirname, resolve, join } from 'node:path';
|
||
import { mkdirSync, existsSync } from 'node:fs';
|
||
|
||
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.15.0');
|
||
const HTML_URL = 'file://' + HTML_PATH;
|
||
|
||
const VIEWPORT = { width: 1440, height: 900 };
|
||
const FULL_PAGE = true;
|
||
|
||
function ensureOutDir() {
|
||
if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true });
|
||
}
|
||
|
||
async function setTheme(page, theme) {
|
||
await page.evaluate((t) => {
|
||
document.documentElement.setAttribute('data-theme', t);
|
||
try { localStorage.setItem('ms-ai-architect-theme', t); } catch (e) {}
|
||
const labels = document.querySelectorAll('[data-theme-label]');
|
||
for (const l of labels) l.textContent = t === 'dark' ? 'Mørk' : 'Lys';
|
||
}, theme);
|
||
await page.waitForTimeout(150);
|
||
}
|
||
|
||
async function clearState(page) {
|
||
await page.evaluate(() => {
|
||
try { localStorage.clear(); } catch (e) {}
|
||
try {
|
||
const dbs = ['ms-ai-architect-state-v1', 'ms-ai-architect-playground'];
|
||
dbs.forEach((n) => indexedDB.deleteDatabase(n));
|
||
} catch (e) {}
|
||
});
|
||
}
|
||
|
||
async function loadDemo(page) {
|
||
await page.evaluate(() => {
|
||
const action = document.querySelector('[data-action="load-demo"]');
|
||
if (action) action.click();
|
||
});
|
||
await page.waitForSelector('[data-surface="project"]:not([hidden])', { timeout: 5000 });
|
||
// Settle migrasjon (v2→v3 auto-parse) + render.
|
||
await page.waitForTimeout(1200);
|
||
}
|
||
|
||
async function clickAction(page, action) {
|
||
await page.evaluate((a) => {
|
||
const el = document.querySelector('[data-action="' + a + '"]');
|
||
if (el) el.click();
|
||
}, action);
|
||
await page.waitForTimeout(400);
|
||
}
|
||
|
||
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();
|
||
}, 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 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');
|
||
const useFullPage = (opts && opts.fullPage != null) ? opts.fullPage : FULL_PAGE;
|
||
await page.screenshot({ path, fullPage: useFullPage });
|
||
console.log(' → ' + name + '.png');
|
||
}
|
||
|
||
async function captureAllSurfaces(page, theme) {
|
||
console.log('\n[' + theme + ' theme]');
|
||
|
||
// 1. Onboarding (empty state)
|
||
await clearState(page);
|
||
await page.goto(HTML_URL);
|
||
await page.waitForSelector('[data-surface="onboarding"]:not([hidden])', { timeout: 5000 });
|
||
await setTheme(page, theme);
|
||
await shoot(page, '01-onboarding-empty-' + theme);
|
||
|
||
// 2. Load demo → project-view overview (default — ingen artifact valgt)
|
||
await loadDemo(page);
|
||
await setTheme(page, theme);
|
||
await shoot(page, '02-project-overview-' + theme);
|
||
|
||
// 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 (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);
|
||
}
|
||
|
||
// 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);
|
||
|
||
// 9. Sidebar-søk aktivt (filtrer på "ros")
|
||
await setSearchQuery(page, 'ros');
|
||
await shoot(page, '09-project-search-' + theme);
|
||
await setSearchQuery(page, ''); // reset
|
||
|
||
// 10. Home surface
|
||
await clickAction(page, 'goto-home');
|
||
await page.waitForSelector('[data-surface="home"]:not([hidden])');
|
||
await page.waitForTimeout(300);
|
||
await shoot(page, '10-home-' + theme);
|
||
|
||
// 11. Catalog surface
|
||
await clickAction(page, 'goto-catalog');
|
||
await page.waitForSelector('[data-surface="catalog"]:not([hidden])');
|
||
await page.waitForTimeout(300);
|
||
await shoot(page, '11-catalog-' + theme);
|
||
|
||
// 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, '12-onboarding-prefilled-' + theme);
|
||
}
|
||
|
||
async function main() {
|
||
ensureOutDir();
|
||
console.log('[screenshot] launching Chromium…');
|
||
const browser = await chromium.launch();
|
||
const context = await browser.newContext({
|
||
viewport: VIEWPORT,
|
||
deviceScaleFactor: 2
|
||
});
|
||
const page = await context.newPage();
|
||
page.on('console', (msg) => {
|
||
const t = msg.type();
|
||
if (t === 'error' || t === 'warning') {
|
||
console.warn(' [browser ' + t + '] ' + msg.text());
|
||
}
|
||
});
|
||
|
||
try {
|
||
for (const theme of ['dark', 'light']) {
|
||
await captureAllSurfaces(page, theme);
|
||
}
|
||
console.log('\n[screenshot] done — output: ' + OUT_DIR);
|
||
} finally {
|
||
await browser.close();
|
||
}
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error('[screenshot] FAILED:', err);
|
||
process.exit(1);
|
||
});
|