From e839ba2a7a3819a56c2b337120cdd6c8dbe74879 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sun, 10 May 2026 17:58:56 +0200 Subject: [PATCH] feat(voyage): implement screenshots-spor convention (window.__hooks + docs/screenshots/) --- plugins/voyage/docs/screenshots/README.md | 89 +++++++++++++++++++ .../voyage/playground/voyage-playground.html | 59 ++++++++++++ .../playground/voyage-playground.test.mjs | 31 +++++++ 3 files changed, 179 insertions(+) create mode 100644 plugins/voyage/docs/screenshots/README.md diff --git a/plugins/voyage/docs/screenshots/README.md b/plugins/voyage/docs/screenshots/README.md new file mode 100644 index 0000000..96eac76 --- /dev/null +++ b/plugins/voyage/docs/screenshots/README.md @@ -0,0 +1,89 @@ +# Voyage playground screenshot convention (v4.3) + +This directory holds reference screenshots for the Voyage playground +(`playground/voyage-playground.html`). They serve as a visual baseline for +manual review of design changes and as fixtures for the Wave 7 axe-core +Playwright spec (`tests/e2e/voyage-playground-a11y.spec.mjs`). + +Screenshots are NOT auto-committed. The `tests/e2e/snapshots/` directory +(produced by Playwright in Wave 7) is the test-baseline; this directory +holds curated illustrative captures for documentation. + +## Mappestruktur + +``` +docs/screenshots/ + README.md ← this file + dashboard/ ← project-dashboard fleet-grid views + artifact-detail/ ← drill-down render of brief/plan/review + annotation/ ← gutter-badge + sidebar + active highlight states + dark-mode/ ← screenshots taken with html[data-theme="dark"] + light-mode/ ← screenshots taken with html[data-theme="light"] +``` + +Each subfolder contains PNGs named by feature + variant, e.g. +`dashboard/fleet-grid-with-progress-tile.png`, +`annotation/active-anchor-yellow-tint.png`. + +## Hooks (window.__voyage) + +The playground exposes three automation hooks for headless screenshot +scripts. They are namespaced under `window.__voyage` to avoid global +pollution: + +| Method | Purpose | +|--------|---------| +| `window.__voyage.navigate(surface)` | Switch surface. `surface ∈ ['dashboard', 'detail', 'render', 'a11y']` | +| `window.__voyage.scheduleRender(state)` | Pre-populate state. `state.markdown` triggers `mountRender`; `state.artifacts` triggers `renderDashboard` | +| `window.__voyage.getProjectArtifacts()` | Returns the last-loaded `ProjectArtifacts` object (or `null`) | + +The hook surface mirrors the pattern used in +`plugins/llm-security/playground/llm-security-playground.html` +(`window.__navigate`, `window.__scheduleRender`). + +## Producing screenshots — Playwright headless (Wave 7+) + +Wave 7 ships a Playwright spec +(`tests/e2e/voyage-playground-a11y.spec.mjs`). Reuse its setup for +screenshots: + +```js +// tests/e2e/snapshot-helper.mjs (NOT committed in v4.3 — illustrative) +import { test } from '@playwright/test'; + +test('dashboard with all artifacts', async ({ page }) => { + await page.goto('file://' + process.cwd() + '/playground/voyage-playground.html'); + await page.evaluate(() => { + window.__voyage.scheduleRender({ + artifacts: { + basePath: 'demo', + brief: { path: 'brief.md', content: '...' }, + plan: { path: 'plan.md', content: '...' }, + review: null, + research: [], + architecture: { overview: null, gaps: null, looseFiles: [] }, + progress: null, + looseFiles: [], + } + }); + }); + await page.screenshot({ path: 'docs/screenshots/dashboard/fleet-grid-demo.png' }); +}); +``` + +## Producing screenshots — manuelt + +1. Åpne `playground/voyage-playground.html` i Chrome/Firefox. +2. Hvis du trenger et bestemt UI-state, paste relevant artifact-innhold i + textarea og trykk Render. +3. macOS: `Cmd+Shift+4`, deretter `Space` for vindu-skjermbilde. +4. Lagre med beskrivende filnavn under riktig undermappe ovenfor. + +## Konvensjon for nye screenshots + +- Bruk PNG, ikke JPG (lossless for tekst-tunge UIer). +- Kjør i konsistent viewport (1280×800 anbefales for desktop-baseline). +- Ta dark- og light-mode-varianter parvis når komponenten har visuelle + forskjeller mellom temaene. +- Ikke commit screenshots med personidentifiserende info eller hemmelig + artifact-innhold. diff --git a/plugins/voyage/playground/voyage-playground.html b/plugins/voyage/playground/voyage-playground.html index 211dd3d..894e3b2 100644 --- a/plugins/voyage/playground/voyage-playground.html +++ b/plugins/voyage/playground/voyage-playground.html @@ -2840,6 +2840,65 @@ playground first-run shows a complete round-trip-able artifact. }); } + // ---- v4.3 Step 23 — screenshots-spor convention ------------------- + // Expose a minimal automation surface for headless screenshot scripts + // (Wave 7 axe-spec + manual screenshot tooling). Mirrors the + // llm-security-playground.html `window.__navigate` / + // `window.__scheduleRender` pattern but namespaced under + // `window.__voyage` to avoid global pollution. All three methods are + // intentionally read-only from the outside — they wrap existing + // functions; we never expose state-mutating internals directly. + // + // Methods: + // navigate(surface) — surface ∈ ['dashboard', 'detail', 'render', 'a11y'] + // scheduleRender(state) — state.markdown → mountRender; state.artifacts → renderDashboard + // getProjectArtifacts() — returns the last-loaded ProjectArtifacts object (or null) + // + // See docs/screenshots/README.md for the screenshot mappestruktur. + window.__voyage = { + navigate: function (surface) { + var s = String(surface || '').toLowerCase(); + if (s === 'dashboard') { + if (__voyageCurrentArtifacts && typeof renderDashboard === 'function') { + renderDashboard(__voyageCurrentArtifacts); + } + return s; + } + if (s === 'detail') { + // No-op without an artifact-key; renderArtifactDetail requires + // a key (brief|plan|review|progress|research:N|architecture:overview). + return s; + } + if (s === 'render') { + var ta = document.getElementById('voyage-paste-input'); + if (ta && typeof mountRender === 'function') mountRender(ta.value || ''); + return s; + } + if (s === 'a11y') { + var panel = document.getElementById('voyage-a11y-panel'); + if (panel) panel.hidden = false; + return s; + } + return null; + }, + scheduleRender: function (state) { + if (!state || typeof state !== 'object') return false; + if (typeof state.markdown === 'string' && typeof mountRender === 'function') { + var ta = document.getElementById('voyage-paste-input'); + if (ta) ta.value = state.markdown; + mountRender(state.markdown); + } + if (state.artifacts && typeof renderDashboard === 'function') { + __voyageCurrentArtifacts = state.artifacts; + renderDashboard(state.artifacts); + } + return true; + }, + getProjectArtifacts: function () { + return __voyageCurrentArtifacts || null; + }, + }; + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { diff --git a/plugins/voyage/tests/playground/voyage-playground.test.mjs b/plugins/voyage/tests/playground/voyage-playground.test.mjs index b8039a9..87753ce 100644 --- a/plugins/voyage/tests/playground/voyage-playground.test.mjs +++ b/plugins/voyage/tests/playground/voyage-playground.test.mjs @@ -382,3 +382,34 @@ test('voyage-playground.html declares wireA11yToggle JS function (v4.3 Step 22)' assert.match(text, /panel\.hidden\s*=\s*!willOpen/, 'panel.hidden toggle required'); assert.match(text, /setAttribute\('aria-expanded'/, 'aria-expanded update required'); }); + +// v4.3 Step 23 — screenshots-spor convention (window.__hooks + docs/screenshots/) +test('voyage-playground.html exposes window.__voyage automation hooks (v4.3 Step 23)', () => { + const text = readFileSync(HTML, 'utf-8'); + // window.__voyage must be assigned (object literal or assignment expression) + assert.match(text, /window\.__voyage\s*=\s*\{/, 'window.__voyage = { ... } assignment required'); +}); + +test('voyage-playground.html window.__voyage exposes navigate/scheduleRender/getProjectArtifacts (v4.3 Step 23)', () => { + const text = readFileSync(HTML, 'utf-8'); + // Each method must appear as a property of the exposed object. + assert.match(text, /navigate:\s*function/, 'navigate method required'); + assert.match(text, /scheduleRender:\s*function/, 'scheduleRender method required'); + assert.match(text, /getProjectArtifacts:\s*function/, 'getProjectArtifacts method required'); +}); + +test('docs/screenshots/README.md documents mappestruktur + hooks (v4.3 Step 23)', () => { + const path = join(ROOT, 'docs', 'screenshots', 'README.md'); + const text = readFileSync(path, 'utf-8'); + assert.match(text, /Mappestruktur/, 'Mappestruktur heading required'); + // Must list each documented subfolder + assert.match(text, /dashboard\//, 'dashboard/ subfolder documented'); + assert.match(text, /artifact-detail\//, 'artifact-detail/ subfolder documented'); + assert.match(text, /annotation\//, 'annotation/ subfolder documented'); + assert.match(text, /dark-mode\//, 'dark-mode/ subfolder documented'); + assert.match(text, /light-mode\//, 'light-mode/ subfolder documented'); + // Hooks documentation must reference all three methods + assert.match(text, /window\.__voyage\.navigate/, 'navigate hook documented'); + assert.match(text, /window\.__voyage\.scheduleRender/, 'scheduleRender hook documented'); + assert.match(text, /window\.__voyage\.getProjectArtifacts/, 'getProjectArtifacts hook documented'); +});