feat(ms-ai-architect): release v1.10.1 — demo system + screenshot gallery
Adds one-click demo and committed screenshots so forkers see what the plugin produces without running anything. Plugin contract unchanged. - Inline <script id="demo-state-v1"> block (37 KB) built by scripts/build-demo-state.mjs from playground/test-fixtures/*.md - "Last inn demo-data" button on onboarding (replaces all state with demo) - raw_markdown persistence on project.reports[id] with equal-value guard - rehydratePasteImports() auto-fills textareas + re-renders visualizations on project surface mount - tests/screenshot/ standalone Playwright runner (own package.json) - 24 committed screenshots in playground/screenshots/v1.10.0/ (12 surfaces x 2 themes, deviceScaleFactor 2 retina, fullPage) Tests: 215 + 201 + 70 + 7 = 493 PASS, no regressions. Docs updated per OBLIGATORISK three-level rule (plugin README, plugin CLAUDE, marketplace root README, CHANGELOG). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
67240f01f6
commit
e3378e9b9c
35 changed files with 670 additions and 4 deletions
2
plugins/ms-ai-architect/tests/screenshot/.gitignore
vendored
Normal file
2
plugins/ms-ai-architect/tests/screenshot/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
package-lock.json
|
||||
56
plugins/ms-ai-architect/tests/screenshot/README.md
Normal file
56
plugins/ms-ai-architect/tests/screenshot/README.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Playground screenshot tooling
|
||||
|
||||
Standalone Playwright runner that captures playground screenshots for documentation.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
cd tests/screenshot
|
||||
npm install
|
||||
npx playwright install chromium # one-time, ~150 MB
|
||||
node run.mjs
|
||||
```
|
||||
|
||||
Output goes to `../../playground/screenshots/v1.10.0/`.
|
||||
|
||||
## What it captures
|
||||
|
||||
For each theme (dark, light):
|
||||
|
||||
| # | Surface | Screen / tab |
|
||||
|---|---------|--------------|
|
||||
| 01 | Onboarding | Empty state |
|
||||
| 02 | Project | Rapporter / Regulatory (default) |
|
||||
| 03 | Project | Rapporter / each of 4 other tabs |
|
||||
| 04-06 | Project | Oversikt / Kontekst / Eksport |
|
||||
| 07 | Home | Project list with demo-prosjekt |
|
||||
| 08 | Catalog | All 5 expansion-grupper |
|
||||
| 09 | Onboarding | Prefilled from demo-state |
|
||||
|
||||
= ~18 PNGs, captured with `deviceScaleFactor: 2` (retina-crisp), `fullPage: true`.
|
||||
|
||||
## How the demo state works
|
||||
|
||||
The screenshot script clicks `[data-action="load-demo"]` which reads the
|
||||
inline `<script type="application/json" id="demo-state-v1">` block from the
|
||||
playground HTML. That block is generated by `scripts/build-demo-state.mjs`
|
||||
and includes one demo project ("Demo: Innbygger-chatbot for byggesak") with
|
||||
all 17 fixture markdowns pre-loaded as `raw_markdown`. After load, the
|
||||
project surface re-runs `handlePasteImport` for each report so the
|
||||
visualizations render automatically.
|
||||
|
||||
## Regenerating demo state
|
||||
|
||||
If `playground/test-fixtures/*.md` changes:
|
||||
|
||||
```bash
|
||||
node scripts/build-demo-state.mjs
|
||||
```
|
||||
|
||||
This rewrites the `<script id="demo-state-v1">` block in the playground HTML.
|
||||
|
||||
## Commit policy
|
||||
|
||||
- Commit `playground/screenshots/v1.10.0/*.png` so forkers see what the
|
||||
plugin looks like without running anything.
|
||||
- Don't commit `node_modules/` (gitignored).
|
||||
14
plugins/ms-ai-architect/tests/screenshot/package.json
Normal file
14
plugins/ms-ai-architect/tests/screenshot/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "ms-ai-architect-screenshot",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Playwright tooling for capturing playground screenshots. Standalone — no relation to plugin runtime.",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"install-browser": "playwright install chromium",
|
||||
"shoot": "node run.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright": "^1.49.0"
|
||||
}
|
||||
}
|
||||
186
plugins/ms-ai-architect/tests/screenshot/run.mjs
Normal file
186
plugins/ms-ai-architect/tests/screenshot/run.mjs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/env node
|
||||
// Capture playground screenshots for v1.10.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)
|
||||
//
|
||||
// Output: playground/screenshots/v1.10.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.10.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 {
|
||||
// Best-effort: clear IndexedDB databases.
|
||||
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();
|
||||
});
|
||||
// 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
|
||||
}
|
||||
|
||||
async function clickAction(page, action) {
|
||||
await page.evaluate((a) => {
|
||||
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 + '"]');
|
||||
if (el) el.click();
|
||||
}, screenId);
|
||||
await page.waitForTimeout(400);
|
||||
}
|
||||
|
||||
async function shoot(page, name) {
|
||||
const path = join(OUT_DIR, name + '.png');
|
||||
await page.screenshot({ path, fullPage: FULL_PAGE });
|
||||
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 surface (rapporter screen, regulatory tab default)
|
||||
await loadDemo(page);
|
||||
await setTheme(page, theme);
|
||||
await shoot(page, '02-project-rapporter-regulatory-' + 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' }
|
||||
];
|
||||
for (const tab of TABS) {
|
||||
await clickProjectTab(page, tab.id);
|
||||
await page.waitForTimeout(500);
|
||||
await shoot(page, '03-project-rapporter-' + tab.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);
|
||||
|
||||
// Back to rapporter for nav screenshots
|
||||
await clickProjectScreen(page, 'rapporter');
|
||||
|
||||
// 5. 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);
|
||||
|
||||
// 6. 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);
|
||||
|
||||
// 7. Onboarding (with prefilled state from demo)
|
||||
await clickAction(page, 'goto-onboarding');
|
||||
await page.waitForSelector('[data-surface="onboarding"]:not([hidden])');
|
||||
await page.waitForTimeout(300);
|
||||
await shoot(page, '09-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 // crisper screenshots for retina
|
||||
});
|
||||
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);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue