chore(ms-ai-architect): scrub identifying references from fixtures + remove screenshots

Removes:
- All 6 PNG screenshots (playground/screenshots/) and the capture script
  (scripts/screenshots/capture-playground.py).
- "Screenshots" section from plugin README.
- "Screenshot-suite" section from plugin CLAUDE.md.
- Screenshots bullet from marketplace root README's ms-ai-architect listing.

Scrubs the 17 synthetic fixtures + CHANGELOG/CLAUDE/README of identifying
references: organization names, government-agency names, agency-specific
terminology, sector-specific use cases. Replaced with generic placeholder
data ("Acme AS" / "Demosystem") that exercises the same parser archetypes.

Plugin's domain-target wording (Datatilsynet, offentlig sektor, offentlig
myndighet, rettshåndhevelse, NS 5814, Utredningsinstruksen, EU AI Act
Annex III categories) is intact — those describe the plugin's intended
audience, not any specific entity.

This is a cleanup commit. Earlier git history still contains the prior
references; force-push or rebase is required if scrubbing the history is
desired. That decision is out of scope here — please run it separately
if needed.

Verified post-scrub:
- bash tests/validate-plugin.sh -> 215/215 PASS
- bash tests/run-e2e.sh --playground -> 240/240 PASS (170 + 70)
This commit is contained in:
Kjell Tore Guttormsen 2026-05-03 20:53:49 +02:00
commit e57dee5a03
28 changed files with 89 additions and 469 deletions

View file

@ -1,360 +0,0 @@
#!/usr/bin/env python3
"""
capture-playground.py Tar screenshots av Playground v3 i alle 4 surfaces × 2 themes.
Bruker Playwright Python (sync API). Åpner playground/ms-ai-architect-playground.html
direkte fra disk via file:// URL og populerer state programmatisk via window.__store
før hvert screenshot. Dette gir reproduserbare screenshots uten å klikke gjennom hele
brukerflyten manuelt.
Output: playground/screenshots/*.png
Kjøring:
python3 scripts/screenshots/capture-playground.py
Krav: Playwright + chromium installert. macOS:
pip3 install playwright
playwright install chromium
"""
import json
import sys
from pathlib import Path
from playwright.sync_api import sync_playwright, Page
PLUGIN_ROOT = Path(__file__).resolve().parent.parent.parent
HTML_PATH = PLUGIN_ROOT / "playground" / "ms-ai-architect-playground.html"
FIXTURES_DIR = PLUGIN_ROOT / "playground" / "test-fixtures"
SCREENSHOTS_DIR = PLUGIN_ROOT / "playground" / "screenshots"
VIEWPORT = {"width": 1440, "height": 900}
TALL_VIEWPORT = {"width": 1440, "height": 1200} # for surfaces med mer innhold
# Demo-data — Statens vegvesen ANPR-eksempelet (matcher fixtures).
DEMO_SHARED = {
"organization": {
"name": "Statens vegvesen",
"description": "Trafikketat ansvarlig for vegnett, kjøretøy og sjåføropplæring",
"sector": "Statlig",
"size": "2000-10000",
"regulatory_requirements": [
"Personopplysningsloven/GDPR",
"Sikkerhetsloven",
"Forvaltningsloven",
"Offentleglova",
],
},
"technology": {
"cloud_platform": ["Azure", "M365"],
"license_type": "E5",
"ai_services_in_use": ["Azure OpenAI", "Copilot for M365", "Azure AI Search"],
},
"security": {
"data_classification": ["Intern", "Fortrolig"],
"data_residency": "Norge",
"dpia_practice": "Systematisk",
"certifications": "ISO 27001, ISO/IEC 42001 (under etablering)",
},
"architecture": {
"preferred_platform": "Azure AI Foundry",
"integration_needs": ["M365", "SharePoint", "Fagsystemer", "REST API-er"],
"annual_ai_budget": "2M-10M",
},
"business": {
"governance_model": "Sentralisert",
"doc_format_preferences": ["Markdown", "SharePoint Wiki"],
"reference_architecture": "TOGAF + intern AI-styringsmodell",
},
}
DEMO_PROJECTS = [
{
"id": "proj-anpr-2026",
"name": "ANPR Trafikkanalyse",
"description": "Automatisk skiltgjenkjenning for trafikkflyt-analyse på E18.",
"createdAt": "2026-04-15T09:00:00Z",
"local": {
"system_name": "ANPR Trafikkanalyse",
"system_description": "Sanntids skiltgjenkjenning av kjøretøy på utvalgte strekninger på E18 for trafikkflyt-analyse.",
"interaction_type": "automatisering",
"users": "Vegtrafikkavdelingen + analytikere",
"risk_level_assumption": "Høy",
"risk_classification": "Høy",
"org_role": "Provider",
"data_sources": "Kameraer langs E18, kjøretøyregisteret (begrenset), værdata fra met.no",
},
"reports": {},
},
{
"id": "proj-saksbehandler-2026",
"name": "Saksbehandlerassistent",
"description": "Copilot-basert assistent for førerkortssøknader og kjøretøy-registrering.",
"createdAt": "2026-04-22T10:30:00Z",
"local": {
"system_name": "Saksbehandlerassistent v1",
"system_description": "M365 Copilot Studio-agent som hjelper saksbehandlere med førerkort-søknader, henter relevant lovverk og foreslår sakssvar.",
"interaction_type": "beslutningsstøtte",
"users": "Saksbehandlere ved 22 trafikkstasjoner",
"risk_level_assumption": "Begrenset",
"risk_classification": "Begrenset",
"org_role": "Deployer",
},
"reports": {},
},
{
"id": "proj-chatbot-2026",
"name": "Brukerstøtte chatbot",
"description": "Publikumsrettet chatbot på vegvesen.no for førerkort- og kjøretøyspørsmål.",
"createdAt": "2026-05-01T13:15:00Z",
"local": {},
"reports": {},
},
]
def read_fixture(name: str) -> str:
"""Les en av de 17 ANPR-fixture-filene."""
return (FIXTURES_DIR / f"{name}.md").read_text(encoding="utf-8")
def seed_state(page: Page, *, projects=True):
"""Populerer __store.state med demo-data og trigger render."""
payload = {
"shared": DEMO_SHARED,
"projects": DEMO_PROJECTS if projects else [],
}
page.evaluate(
"""({shared, projects}) => {
for (const k of Object.keys(shared)) {
const target = window.__store.state.shared[k];
for (const f of Object.keys(shared[k])) target[f] = shared[k][f];
}
if (projects.length > 0) {
window.__store.state.projects.length = 0;
for (const p of projects) window.__store.state.projects.push(p);
}
window.__scheduleRender();
}""",
payload,
)
page.wait_for_timeout(200)
def import_reports(page: Page, project_id: str, command_ids: list):
"""Kaller __handlePasteImport på et aktivt prosjekt etter at project-surface
er rendret. kalles ETTER navigate('project') siden handlePasteImport
rendrer direkte til DOM-slottet og scheduleRender ville slette det."""
page.evaluate(
"""({pid}) => { window.__store.state.activeProjectId = pid; }""",
{"pid": project_id},
)
for cmd in command_ids:
markdown = read_fixture(cmd)
page.evaluate(
"""({cmd, md}) => { window.__handlePasteImport(cmd, md); }""",
{"cmd": cmd, "md": markdown},
)
page.wait_for_timeout(150)
def navigate(page: Page, surface: str, project_id: str = None, project_tab: str = None):
"""Bytter aktiv surface (og evt. aktivt prosjekt + tab) og trigger render."""
page.evaluate(
"""({surface, pid}) => {
if (pid) window.__store.state.activeProjectId = pid;
window.__navigate(surface);
}""",
{"surface": surface, "pid": project_id},
)
page.wait_for_timeout(250)
# Project-tab er module-local (currentProjectTab) — må klikkes via faktisk knapp
if surface == "project" and project_tab:
page.evaluate(
"""(tab) => {
const btn = document.querySelector('[data-action=\"project-tab\"][data-tab=\"' + tab + '\"]');
if (btn) btn.click();
}""",
project_tab,
)
page.wait_for_timeout(200)
def set_theme(page: Page, theme: str):
"""Setter tema (light/dark) før screenshot."""
page.evaluate(
"""(theme) => {
document.documentElement.setAttribute('data-theme', theme);
try { localStorage.setItem('ms-ai-architect-theme', theme); } catch(e){}
// Re-render topbar slik at theme-toggle-label oppdateres
const labels = document.querySelectorAll('[data-theme-label]');
for (const l of labels) l.textContent = theme === 'dark' ? 'Mørk' : 'Lys';
}""",
theme,
)
page.wait_for_timeout(100)
def shoot(page: Page, name: str, *, full_page: bool = False, scroll_to: str = None):
"""Lagrer en screenshot. Default: viewport-only.
full_page=True: hele scroll-høyden
scroll_to: CSS-selektor som scrolles inn i view før shot
"""
out = SCREENSHOTS_DIR / f"{name}.png"
if scroll_to:
page.evaluate(
"""(sel) => {
const el = document.querySelector(sel);
if (el) el.scrollIntoView({block: 'start', behavior: 'instant'});
window.scrollBy(0, -20);
}""",
scroll_to,
)
page.wait_for_timeout(120)
page.screenshot(path=str(out), full_page=full_page)
print(f" [{name}.png] {out.relative_to(PLUGIN_ROOT)}")
def open_clean(browser, viewport=None):
"""Åpner playground med ren state og venter på bootstrap."""
context = browser.new_context(viewport=viewport or VIEWPORT)
page = context.new_page()
# Hopp til about:blank først for å rense localStorage/IDB fra forrige
# kontekst-kjøring (file:// kan dele storage på samme browser-instans).
page.goto("about:blank")
page.evaluate(
"""async () => {
try { localStorage.clear(); } catch(e){}
try { sessionStorage.clear(); } catch(e){}
if (typeof indexedDB !== 'undefined' && indexedDB.databases) {
try {
const dbs = await indexedDB.databases();
await Promise.all(dbs.map(d => new Promise(r => {
const req = indexedDB.deleteDatabase(d.name); req.onsuccess = req.onerror = req.onblocked = r;
})));
} catch(e){}
}
}"""
)
page.goto(f"file://{HTML_PATH}")
# Vent til __store er eksponert (bootstrap fullført)
page.wait_for_function("() => window.__store && window.__CATALOG", timeout=10_000)
# Skjul evt. error-banner som kan dukke opp fra bootstrap-kanter
page.evaluate(
"""() => {
const errs = document.querySelectorAll('[data-onboarding-errors]');
for (const e of errs) e.setAttribute('hidden', '');
}"""
)
return context, page
def main():
if not HTML_PATH.exists():
print(f"FEIL: {HTML_PATH} mangler", file=sys.stderr)
sys.exit(1)
SCREENSHOTS_DIR.mkdir(parents=True, exist_ok=True)
print(f"Lagrer screenshots til: {SCREENSHOTS_DIR.relative_to(PLUGIN_ROOT)}")
print()
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
# NB: light-mode tokens er ikke implementert i det vendrede
# design-systemet ennå (kun mørk-tema-vars). Theme-toggle bytter
# data-theme + label korrekt, men CSS-fargene endres ikke før
# tokens.css får [data-theme="light"]-overrides. Vi capture'r kun
# mørk-modus for nå. Når light-tokens kommer, endre listen til
# ("dark", "light") for å regenerere parsuffix-screenshots.
for theme in ("dark",):
suffix = "" if theme == "dark" else "-light"
print(f"--- Theme: {theme} ---")
# 1. Onboarding (utfylt) — TALL viewport for å vise sidebar + alle 5 grupper
ctx, page = open_clean(browser, viewport=TALL_VIEWPORT)
seed_state(page, projects=False)
navigate(page, "onboarding")
set_theme(page, theme)
page.evaluate(
"""() => {
const errs = document.querySelectorAll('[data-onboarding-errors]');
for (const e of errs) e.setAttribute('hidden', '');
}"""
)
page.wait_for_timeout(150)
shoot(page, f"01-onboarding{suffix}")
ctx.close()
# 2. Home (med 3 prosjekter)
ctx, page = open_clean(browser)
seed_state(page)
navigate(page, "home")
set_theme(page, theme)
page.wait_for_timeout(150)
shoot(page, f"02-home{suffix}")
ctx.close()
# 3. Catalog (alle grupper synlig) — utvid de første 2 gruppene
ctx, page = open_clean(browser)
seed_state(page)
navigate(page, "catalog")
set_theme(page, theme)
page.evaluate(
"""() => {
const exps = document.querySelectorAll('[data-action="catalog-toggle-group"]');
// Klikk de 2 første for å utvide
for (let i = 0; i < Math.min(2, exps.length); i++) exps[i].click();
}"""
)
page.wait_for_timeout(200)
shoot(page, f"03-catalog{suffix}")
ctx.close()
# 4. Project — classify pyramide (scroll til classify report-slot)
ctx, page = open_clean(browser)
seed_state(page)
navigate(page, "project", project_id="proj-anpr-2026", project_tab="regulatory")
set_theme(page, theme)
import_reports(page, "proj-anpr-2026", ["classify"])
shoot(
page,
f"04-project-classify-pyramide{suffix}",
scroll_to='[data-report-slot="classify"]',
)
ctx.close()
# 5. Project — ROS matrix
ctx, page = open_clean(browser)
seed_state(page)
navigate(page, "project", project_id="proj-anpr-2026", project_tab="security")
set_theme(page, theme)
import_reports(page, "proj-anpr-2026", ["ros"])
shoot(
page,
f"05-project-ros-matrix{suffix}",
scroll_to='[data-report-slot="ros"]',
)
ctx.close()
# 6. Project — Cost distribution
ctx, page = open_clean(browser)
seed_state(page)
navigate(page, "project", project_id="proj-anpr-2026", project_tab="economy")
set_theme(page, theme)
import_reports(page, "proj-anpr-2026", ["cost"])
shoot(
page,
f"06-project-cost-distribution{suffix}",
scroll_to='[data-report-slot="cost"]',
)
ctx.close()
browser.close()
print()
print(f"Ferdig. Screenshots i {SCREENSHOTS_DIR.relative_to(PLUGIN_ROOT)}/")
if __name__ == "__main__":
main()