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:
parent
9664bf1b1c
commit
e57dee5a03
28 changed files with 89 additions and 469 deletions
|
|
@ -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. På 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. Må 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue