From 412b4561f5e89600a80a44187a8018b29f7461b0 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sun, 10 May 2026 21:25:01 +0200 Subject: [PATCH] fix(voyage): inline screenshot gallery loads docs/screenshots/ PNGs (31d28f65) --- .../voyage/docs/sc1-checklist-verification.md | 13 ++- .../voyage/playground/voyage-playground.html | 89 +++++++++++++++++- .../tests/e2e/voyage-playground-a11y.spec.mjs | 32 +++++++ .../fixtures/screenshot-project/brief.md | 11 +++ .../docs/screenshots/dashboard/sample.png | Bin 0 -> 70 bytes 5 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 plugins/voyage/tests/fixtures/screenshot-project/brief.md create mode 100644 plugins/voyage/tests/fixtures/screenshot-project/docs/screenshots/dashboard/sample.png diff --git a/plugins/voyage/docs/sc1-checklist-verification.md b/plugins/voyage/docs/sc1-checklist-verification.md index fe370e6..17867cd 100644 --- a/plugins/voyage/docs/sc1-checklist-verification.md +++ b/plugins/voyage/docs/sc1-checklist-verification.md @@ -14,7 +14,7 @@ | 3 | Theme bootstrap IIFE | PASS | Group A test SC1.3; `data-theme` + `prefers-color-scheme` IIFE i HTML head | | 4 | Onboarding-grid (redefinert) | PASS-redef | Group A test SC1.4; voyage bruker `fleet-grid` + `fleet-tile` istedenfor onboarding-grid (scope-guardian SC-GAP-1, Assumption #21) | | 5 | A11Y-panel | PASS | Group A test SC1.5; `guide-panel--info` + `key-stats` + `findings__items` (Wave 5 Step 22) | -| 6 | Screenshots-spor (redefinert) | PASS-redef | Group A test SC1.6; `window.__voyage` hooks + `docs/screenshots/README.md` istedenfor inline gallery (scope-guardian SC-GAP-2, Assumption #22) | +| 6 | Screenshots-spor | PASS | Group D test SC1.6; `loadProjectDirectory` leser `docs/screenshots/**/*.png` via `readAsDataURL` (2 MB cap) og `renderScreenshotGallery` mounter en `
`-grid i dashboardet. Erstatter scope-guardian SC-GAP-2 PASS-redef med faktisk inline gallery (v4.3 Step 8, finding 31d28f65). | | 7 | Body typografi | PASS | Group A test SC1.7; `var(--font-size-*)` + `var(--font-family-mono)` brukt gjennomgående | | 8 | Spacing rhythm | PASS | Group A test SC1.8; ≥5 distinkte `var(--space-N)` referanser | | 9 | Color-token fidelity | PASS | Group A test SC1.9; `badge--scope-voyage` + `--color-scope-voyage` brukt | @@ -57,13 +57,12 @@ - **Test:** `SC1.5 A11Y panel — guide-panel--info + key-stats + findings` - **Status:** PASS -### Element 6 — Screenshots-spor (REDEFINERT) +### Element 6 — Screenshots-spor - **Bokstavelig krav:** llm-security har inline gallery med thumbnail-grid for case-studies -- **Voyage-tolkning:** Inline gallery deferret. Wave 5 Step 23 leverer `window.__voyage` hooks-spor (`navigate`, `scheduleRender`, `getProjectArtifacts`) + `docs/screenshots/README.md` mappe-konvensjon -- **Implementering:** Hooks brukes av Playwright-spec (Step 30); manuell screenshot-prosedyre dokumentert i `docs/screenshots/README.md` -- **Test:** `SC1.6 screenshots-spor — window.__voyage hooks + docs convention` -- **Status:** PASS-redef (scope-guardian SC-GAP-2, Assumption #22 — operatør-sign-off ved sesjon-start) -- **Risk:** Samme som Element 4 — `/trekreview` kan kreve faktisk gallery-komponent. +- **Voyage-implementering (v4.3 Step 8, finding 31d28f65):** `loadProjectDirectory` detekterer `docs/screenshots/**/*.png` via path-prefix-match, leser dem via `FileReader.readAsDataURL()` med 2 MB per-bilde cap (overskridelse annonseres via aria-live). `renderScreenshotGallery(screenshots)` bygger en `
`-grid med `<filename>` som mountes under `fleet-grid` i `renderDashboard`. Erstatter den tidligere PASS-redef-tolkningen — voyage har nå et faktisk inline gallery. +- **Beholder:** `window.__voyage` hooks fra Wave 5 Step 23 (`navigate`, `scheduleRender`, `getProjectArtifacts`) + `docs/screenshots/README.md` mappe-konvensjon — fortsatt brukt av Playwright-spec og av operatør for manuell screenshot-prosedyre. +- **Test:** Group D Playwright-test `SC1.6 inline gallery — data:image PNGs rendered (31d28f65)` injiserer en fixture-artifact via `scheduleRender` og asserter `#voyage-dashboard img[src^="data:image/png"]` count > 0. +- **Status:** PASS (inline gallery implementert; scope-guardian SC-GAP-2 lukket). ### Element 7 — Body typografi - **Bokstavelig krav:** typografi-skala bruker DS-tokens, ikke literal pixel-verdier diff --git a/plugins/voyage/playground/voyage-playground.html b/plugins/voyage/playground/voyage-playground.html index a908825..8b62764 100644 --- a/plugins/voyage/playground/voyage-playground.html +++ b/plugins/voyage/playground/voyage-playground.html @@ -844,6 +844,42 @@ } .voyage-back-btn:hover { background: var(--color-bg-soft); } + /* v4.3 Step 8 — inline screenshot gallery (finding 31d28f65). + Renders below the fleet-grid in the dashboard.
grid with + responsive auto-fit columns at 240px min;
shows the + relative path so operators can correlate to docs/screenshots/. */ + .voyage-screenshot-gallery { margin-top: var(--space-4); } + .voyage-screenshot-gallery h3 { + margin: 0 0 var(--space-3) 0; + font-size: var(--font-size-lg); + color: var(--color-text-primary); + } + .voyage-screenshot-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: var(--space-3); + } + .voyage-screenshot { + margin: 0; + border: 1px solid var(--color-border-subtle); + border-radius: var(--radius-sm); + background: var(--color-surface); + overflow: hidden; + } + .voyage-screenshot img { + display: block; + width: 100%; + height: auto; + } + .voyage-screenshot figcaption { + padding: var(--space-2) var(--space-3); + font-family: var(--font-family-mono); + font-size: var(--font-size-xs); + color: var(--color-text-secondary); + border-top: 1px solid var(--color-border-subtle); + word-break: break-all; + } + /* v4.3 remediation: color-contrast fix for finding 09132940. The vendored DS sets `.key-stat__label` to var(--color-text-tertiary) which is #6E7781 in light theme — borderline 4.5:1 WCAG-AA contrast @@ -1455,6 +1491,10 @@ playground first-run shows a complete round-trip-able artifact. progress: null, research: [], architecture: { overview: null, gaps: null, looseFiles: [] }, + // v4.3 Step 8 — inline screenshot gallery (finding 31d28f65). + // `screenshots` is populated below from docs/screenshots/**/*.png + // entries. Each item: { path, dataUrl }. + screenshots: [], looseFiles: [] }; @@ -1480,13 +1520,38 @@ playground first-run shows a complete round-trip-able artifact. else if (parts.length === 2 && parts[0] === 'architecture' && name === 'overview.md') bucket = 'arch_overview'; else if (parts.length === 2 && parts[0] === 'architecture' && name === 'gaps.md') bucket = 'arch_gaps'; else if (parts.length === 2 && parts[0] === 'architecture' && /\.md$/.test(name)) bucket = 'arch_loose'; + // v4.3 Step 8 — docs/screenshots/**/*.png → inline gallery + else if (parts[0] === 'docs' && parts[1] === 'screenshots' && /\.png$/i.test(name)) bucket = 'screenshot'; else bucket = 'loose'; classified.push({ file: f, rel: inside, bucket: bucket }); } // Phase 2 — read content + parse frontmatter (async). + // v4.3 Step 8 — `screenshot` bucket uses readAsDataURL with a 2 MB + // per-image cap (finding 31d28f65); oversized PNGs are skipped + // with an aria-live announce so AT users know what was suppressed. + var SCREENSHOT_MAX_BYTES = 2 * 1024 * 1024; for (var j = 0; j < classified.length; j++) { var c = classified[j]; + if (c.bucket === 'screenshot') { + if (c.file && typeof c.file.size === 'number' && c.file.size > SCREENSHOT_MAX_BYTES) { + announce('Hopper over for stort screenshot: ' + c.rel); + continue; + } + var dataUrl = ''; + try { + dataUrl = await new Promise(function (resolve, reject) { + var reader = new FileReader(); + reader.onload = function () { resolve(String(reader.result || '')); }; + reader.onerror = function () { reject(reader.error); }; + reader.readAsDataURL(c.file); + }); + } catch (_) { dataUrl = ''; } + if (dataUrl) { + artifacts.screenshots.push({ path: c.rel, dataUrl: dataUrl }); + } + continue; + } var text = ''; try { text = await c.file.text(); } catch (_) { text = ''; } var fm = quickParseFrontmatter(text); @@ -1504,6 +1569,7 @@ playground first-run shows a complete round-trip-able artifact. } } artifacts.research.sort(function (a, b) { return a.path.localeCompare(b.path); }); + artifacts.screenshots.sort(function (a, b) { return a.path.localeCompare(b.path); }); // Phase 3 — deterministic storage-key from basePath (NOT URL params) // so draft-annotation state is isolated per project. @@ -1807,6 +1873,26 @@ playground first-run shows a complete round-trip-able artifact. return parts[parts.length - 1] || p; } + // v4.3 Step 8 — inline screenshot gallery (finding 31d28f65). + // Builds a
grid from the screenshots[] array populated by + // loadProjectDirectory. Each item renders as a data:image/png + // wrapped in a
/
. Returns an empty string when + // there are no screenshots so the dashboard layout stays clean. + function renderScreenshotGallery(screenshots) { + if (!screenshots || !screenshots.length) return ''; + var items = screenshots.map(function (s) { + var name = (s.path || '').split('/').pop() || s.path || ''; + return '
' + + '' + escapeHtml(name) + '' + + '
' + escapeHtml(s.path) + '
' + + '
'; + }).join(''); + return ''; + } + function renderDashboard(projectArtifacts, slot) { var host = slot || $('voyage-dashboard'); if (!host) return; @@ -1828,7 +1914,8 @@ playground first-run shows a complete round-trip-able artifact. }).join(''); var projectName = shortenBasePath(projectArtifacts.basePath); - var bodyHtml = '
' + tilesHtml + '
'; + var galleryHtml = renderScreenshotGallery(projectArtifacts.screenshots || []); + var bodyHtml = '
' + tilesHtml + '
' + galleryHtml; host.innerHTML = renderPageShell({ eyebrow: 'Project dashboard', title: projectName, diff --git a/plugins/voyage/tests/e2e/voyage-playground-a11y.spec.mjs b/plugins/voyage/tests/e2e/voyage-playground-a11y.spec.mjs index 067e14d..8e5cec1 100644 --- a/plugins/voyage/tests/e2e/voyage-playground-a11y.spec.mjs +++ b/plugins/voyage/tests/e2e/voyage-playground-a11y.spec.mjs @@ -145,6 +145,38 @@ test.describe('voyage-playground a11y (axe-core)', () => { expect(nodes, `color-contrast violations remain: ${JSON.stringify(nodes, null, 2)}`).toEqual([]); }); + // v4.3 Step 8 — inline screenshot gallery (finding 31d28f65). + // Injects a pre-built artifacts object with screenshots[] via the + // window.__voyage.scheduleRender hook (avoids webkitdirectory which + // is not programmatically triggerable). Asserts the dashboard renders + // at least one data:image PNG tag. + test('SC1.6 inline gallery — data:image PNGs rendered (31d28f65)', async ({ page }) => { + await page.goto('voyage-playground.html'); + await page.waitForLoadState('domcontentloaded'); + // 1×1 transparent PNG (same base64 as the fixture file) + const SAMPLE_DATA_URL = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='; + await page.evaluate((dataUrl) => { + window.__voyage.scheduleRender({ + artifacts: { + basePath: 'fixture-project', + storageKey: 'voyage_proj_fixture', + brief: { path: 'brief.md', content: '# Fixture', frontmatter: {} }, + plan: null, + review: null, + progress: null, + research: [], + architecture: { overview: null, gaps: null, looseFiles: [] }, + screenshots: [{ path: 'docs/screenshots/dashboard/sample.png', dataUrl: dataUrl }], + looseFiles: [], + }, + }); + }, SAMPLE_DATA_URL); + // The gallery is rendered inside #voyage-dashboard + const imgCount = await page.locator('#voyage-dashboard img[src^="data:image/png"]').count(); + expect(imgCount, 'expected at least one data:image/png in the gallery').toBeGreaterThan(0); + }); + test('pixel-diff smoke 1280×900 — light + dark within 2% threshold (SC1 backup)', async ({ page }) => { await page.setViewportSize({ width: 1280, height: 900 }); // Light theme baseline diff --git a/plugins/voyage/tests/fixtures/screenshot-project/brief.md b/plugins/voyage/tests/fixtures/screenshot-project/brief.md new file mode 100644 index 0000000..d52957a --- /dev/null +++ b/plugins/voyage/tests/fixtures/screenshot-project/brief.md @@ -0,0 +1,11 @@ +--- +task: Screenshot gallery fixture for Group D test +slug: screenshot-project +project_dir: tests/fixtures/screenshot-project +--- + +# Screenshot fixture brief + +Minimal brief.md so `loadProjectDirectory` reaches its render phase +without emitting the "brief.md mangler" warning. Real verification +is the data:image PNG count assertion in the Group D test. diff --git a/plugins/voyage/tests/fixtures/screenshot-project/docs/screenshots/dashboard/sample.png b/plugins/voyage/tests/fixtures/screenshot-project/docs/screenshots/dashboard/sample.png new file mode 100644 index 0000000000000000000000000000000000000000..0f2de3749df299a6b84bf6ff1a0b393a1c1fd22b GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9xZYBTuKYyVd1A7xwz3mB) Q_dp2-Pgg&ebxsLQ0NDZ%