Files
Portfolio/docs/superpowers/plans/2026-09-01-portfolio-phase-3-quality.md

352 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Portfolio Phase 3: Media, Accessibility, Resilience, and Performance Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace text-first media slots with cleared authentic project assets and prove that the complete experience meets its accessibility, resilience, responsive, and performance contracts.
**Architecture:** A deterministic asset-preparation script re-encodes approved inputs into safe generated filenames and a hash-bound manifest; original files and the private clearance register remain outside version control. Media components load only through the manifest and obey motion/data policies. Browser instrumentation and fault hooks are compiled into test builds only.
**Tech Stack:** Node.js, Sharp, ffmpeg, Tesseract.js, ZXing, Vue 3, Vitest, Playwright, axe-core, Lighthouse CLI
**Spec:** `docs/superpowers/specs/2026-09-01-portfolio-journey-design.md`
## Global Constraints
- Only authentic user-supplied project media and code-generated diagrams/world artwork may ship.
- Original filenames, private names, source paths, metadata, browser chrome, account data, QR/barcodes, and title screens never enter distributable output.
- Health-related assets use visibly fictional demo values only and require Luca’s manual approval after final re-encoding.
- Video uses `preload="none"`, no native inaccessible controls, no autoplay in reduced/static/data-saver modes, and an author-owned play/pause control.
- Every chart has a visible caption and adjacent structured description; exact data tables appear when a claim depends on exact values.
- Automated accessibility supports but never replaces the recorded WCAG 2.2 A/AA manual checklist.
- Release budgets and fault/browser matrix are hard gates; ordinary device memory-pressure observations remain advisory.
---
### Task 1: Build deterministic asset preparation and clearance checks
**Files:**
- Create: `scripts/prepare-assets.mjs`
- Create: `scripts/lib/asset-recipes.mjs`
- Create: `scripts/lib/hash-file.mjs`
- Create: `scripts/lib/privacy-scan.mjs`
- Create: `scripts/lib/video-frames.mjs`
- Create: `content/public/asset-recipes.json`
- Create: `docs/asset-clearance-template.md`
- Create: `tests/fixtures/assets/`
- Test: `tests/integration/prepare-assets.test.ts`
- Modify: `package.json`
**Interfaces:**
- Consumes: `--source-root <private path>`, approved recipe JSON, and a denylist file descriptor/path supplied only to the scanner process.
- Produces: `inputs/prepared-assets/`, updated `content/public/asset-manifest.json`, and a private scan report outside tracked paths.
- [ ] **Step 1: Install asset tooling into the exact lockfile**
Run:
```bash
npm install --save-dev sharp tesseract.js @zxing/library file-type lighthouse
```
Add `prepare:assets` and `verify:assets` scripts that pass explicit paths rather than discover user directories.
- [ ] **Step 2: Write failing deterministic-preparation tests**
Fixtures cover EXIF removal, generated safe filenames, byte-identical output across two runs, unexpected/extra output rejection, OCR denylist match, QR/barcode rejection, video poster/frame sampling, title-frame exclusion, malformed media, and command failure without leaking matched text.
```ts
expect(result.manifest.assets[0].filename).toMatch(/^project-[a-z0-9-]+\.[a-z0-9]+$/);
expect(result.reportText).not.toContain(secretCanary);
expect(firstOutputHash).toBe(secondOutputHash);
```
- [ ] **Step 3: Define exact public recipes**
The recipe file uses only generic asset IDs and generated outputs:
```json
{
"schema": "portfolio-asset-recipes/v1",
"assets": [
{ "id": "medication-mobile-overview", "kind": "image", "output": "project-medication-overview.webp", "maxWidth": 720 },
{ "id": "medication-mobile-settings", "kind": "image", "output": "project-medication-settings.webp", "maxWidth": 720 },
{ "id": "footy-detection-frame", "kind": "image", "output": "project-footy-detection.webp", "maxWidth": 1600 },
{ "id": "opinion-polarization-chart", "kind": "image", "output": "project-opinion-polarization.webp", "maxWidth": 1000 },
{ "id": "private-game-loop", "kind": "video", "output": "project-private-game-loop.webm", "startSeconds": 3, "durationSeconds": 6 },
{ "id": "healthcare-interface", "kind": "image", "output": "project-healthcare-interface.webp", "maxWidth": 1440 },
{ "id": "currency-converter-interface", "kind": "image", "output": "project-currency-converter.webp", "maxWidth": 1440 }
]
}
```
Additional opinion charts follow the same `opinion-<metric>-chart` naming. Source filenames/paths are supplied through an untracked private mapping and never copied into the recipe or manifest.
- [ ] **Step 4: Implement safe image/video preparation**
Images are decoded, resized without enlargement, flattened where needed, and re-encoded as WebP/PNG with metadata omitted. Video is decoded by ffmpeg in a resource-limited subprocess, cropped to approved gameplay time, stripped of audio/unexpected tracks unless separately cleared, scaled to the approved cap, encoded as VP9 WebM plus H.264 MP4 fallback, and given a cleared poster. Subprocess arguments are arrays, never shell-concatenated strings.
- [ ] **Step 5: Implement final-output scanning**
Run OCR and QR/barcode detection against every final image/poster and sampled video frames; run byte/text/metadata checks against every output and decompressed test container. Failure output contains only opaque rule ID and coordinate/frame. Unsupported/encrypted/malformed input fails closed.
- [ ] **Step 6: Require manual final review**
Generate a contact sheet for Luca showing every final derivative at full readable scale plus representative/all-changing video frames. Record approval in an untracked private clearance register; only approved hashes may be copied to `inputs/prepared-assets` and the public manifest.
- [ ] **Step 7: Run and commit only safe outputs/code**
```bash
npm run test:unit -- tests/integration/prepare-assets.test.ts
npm run prepare:assets -- --source-root /absolute/private/source --mapping /absolute/private/mapping.json --denylist /absolute/private/denylist.txt
git add scripts content/public/asset-recipes.json content/public/asset-manifest.json inputs/prepared-assets docs/asset-clearance-template.md tests package.json package-lock.json
git commit -m "feat: add deterministic cleared project media"
```
Before committing, `git diff --cached --name-only` must contain no original source name or private path.
---
### Task 2: Integrate project media, chart descriptions, and controlled video
**Files:**
- Create: `src/components/ProjectMedia.vue`
- Create: `src/components/AccessibleVideo.vue`
- Create: `src/components/ChartFigure.vue`
- Create: `src/composables/useLazyMedia.ts`
- Modify: `src/components/ProjectCaseStudy.vue`
- Modify: `src/components/ProjectCard.vue`
- Modify: `content/public/portfolio-content.json`
- Test: `tests/unit/ProjectMedia.test.ts`
- Test: `tests/browser/media-policy.spec.ts`
**Interfaces:**
- Consumes: approved asset IDs/copy, public manifest, motion policy.
- Produces: intrinsic-size figures, author-controlled video, lazy/detached media lifecycle, accessible fallbacks.
- [ ] **Step 1: Write failing component policy tests**
Assert manifest-only URLs, contextual alt from content, figure captions, chart descriptions, no native video controls, `preload="none"`, detached sources outside the threshold, and explicit Play behavior in reduced/static/data-saver modes.
- [ ] **Step 2: Implement manifest-bound media resolution**
```ts
export interface MediaPresentation {
assetId: string;
alt: string;
caption?: string;
longDescription?: string;
}
```
`ProjectMedia` rejects unknown IDs and never reads contextual wording from the manifest.
- [ ] **Step 3: Implement author-owned video controls**
`AccessibleVideo` uses a real Play/Pause button, visible state, poster, caption/description, time-preserving detach on offscreen pause, and failure text. It attaches sources within 300 CSS pixels only when automatic media policy permits; otherwise explicit Play attaches them.
- [ ] **Step 4: Implement chart figures**
Each chart renders `<figure>`, visible `<figcaption>`, structured variables/trend/conclusion text, and a table only when exact values support the claim. Color never carries the conclusion alone.
- [ ] **Step 5: Verify and commit**
```bash
npm run test:unit -- tests/unit/ProjectMedia.test.ts
npm run test:browser -- tests/browser/media-policy.spec.ts
git add src/components src/composables/useLazyMedia.ts content/public/portfolio-content.json tests
git commit -m "feat: present cleared accessible project media"
```
---
### Task 3: Enforce WCAG 2.2 A/AA and screen-reader workflows
**Files:**
- Create: `tests/browser/accessibility-axe.spec.ts`
- Create: `tests/browser/accessibility-keyboard.spec.ts`
- Create: `tests/browser/accessibility-reflow.spec.ts`
- Create: `tests/browser/accessibility-forced-colors.spec.ts`
- Create: `docs/accessibility/wcag-2.2-aa-checklist.md`
- Create: `docs/accessibility/screen-reader-scripts.md`
- Modify: `src/styles/base.css`
- Modify: `src/styles/components.css`
- Modify: `src/styles/world.css`
**Interfaces:**
- Consumes: all default/audio/static/reduced/media-failure/missing-social states.
- Produces: automated reports plus manual NVDA/Firefox and VoiceOver/Safari evidence.
- [ ] **Step 1: Write automated axe/state tests**
```ts
for (const state of ['default', 'audio-running', 'static', 'reduced-motion', 'media-failure', 'missing-social']) {
test(`${state} has no axe violations`, async ({ page }) => {
await enterState(page, state);
expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
});
}
```
- [ ] **Step 2: Test keyboard, focus, and target geometry**
Enumerate every enabled author-owned pointer target and require 44×44 CSS pixels except marked inline sentence links. Verify focus is visible, inside safe bounds, and not overlapped by fixed HUD/controls. Keyboard traversal reaches Contact in logical order.
- [ ] **Step 3: Test reflow and user styles**
Run at 320 CSS pixels and desktop 400% zoom/effective 320 width. Apply WCAG text-spacing overrides and forced colors; require no lost/clipped content, control, focus ring, or Contact action.
- [ ] **Step 4: Execute named screen-reader scripts**
Record versions/results for NVDA+Firefox on Windows and VoiceOver+Safari on macOS/iOS. Follow the seven literal tasks in the spec, including scene description, navigation focus, audio controls, Toolkit labels, chart descriptions, video controls, and contact links.
- [ ] **Step 5: Resolve every applicable criterion and commit evidence**
```bash
npm run test:browser -- tests/browser/accessibility-axe.spec.ts tests/browser/accessibility-keyboard.spec.ts tests/browser/accessibility-reflow.spec.ts tests/browser/accessibility-forced-colors.spec.ts
git add tests/browser/accessibility-* docs/accessibility src/styles
git commit -m "test: enforce accessible portfolio interaction"
```
No automated violation remains open; false positives require evidence in the checklist.
---
### Task 4: Implement deterministic fault injection and browser coverage
**Files:**
- Create: `src/testing/fault-hooks.ts`
- Create: `tests/browser/fault-matrix.spec.ts`
- Create: `tests/browser/context-loss.spec.ts`
- Create: `tests/browser/audio-interruption.spec.ts`
- Create: `tests/browser/media-failure.spec.ts`
- Create: `tests/helpers/test-build-auth.ts`
- Modify: `vite.config.ts`
- Modify: `playwright.config.ts`
**Interfaces:**
- Consumes: authenticated test-build secret generated per run.
- Produces: F1–F13 fault hooks only in test builds and cross-engine failure evidence.
- [ ] **Step 1: Write failing absence/authentication tests**
Production bundles must not contain `__PORTFOLIO_TEST_HOOKS__`. A test build rejects missing/wrong ephemeral token. Correct token exposes only named deterministic failures and state snapshots.
- [ ] **Step 2: Implement compile-time-eliminated hooks**
Hooks cover scene chunk, shader, model, texture, poster, video, hydration, pre/post-first-frame exception, context loss/restore, audio start rejection, and audio interruption. They cannot mutate content, URLs, approval data, or production configuration.
- [ ] **Step 3: Implement exact failure oracles**
Every case asserts H1, sections, anchors, contact, private-link omissions, focus behavior, one static fallback, no duplicate resources, and no unhandled exception/rejection.
- [ ] **Step 4: Expand the executable browser matrix**
Configure current Chromium/Firefox/WebKit/Edge equivalents and the previous stable majors available in CI containers. Record physical iOS Safari and Pixel Chromium separately; do not silently replace them with emulation.
- [ ] **Step 5: Verify and commit**
```bash
npm run build:preview
npm run test:browser -- tests/browser/fault-matrix.spec.ts tests/browser/context-loss.spec.ts tests/browser/audio-interruption.spec.ts tests/browser/media-failure.spec.ts
git add src/testing tests playwright.config.ts vite.config.ts
git commit -m "test: add deterministic resilience matrix"
```
---
### Task 5: Add performance instrumentation and enforce budgets
**Files:**
- Create: `src/testing/performance-instrumentation.ts`
- Create: `tests/performance/drive-trace.spec.ts`
- Create: `tests/performance/physical-drive.protocol.ts`
- Create: `tests/performance/budgets.test.ts`
- Create: `scripts/check-bundle-budgets.mjs`
- Create: `docs/performance/lab-runbook.md`
- Modify: `package.json`
- Modify: `vite.config.ts`
**Interfaces:**
- Consumes: instrumented test build, physical reference devices, pinned network profiles.
- Produces: five-run lab JSON, bundle report, physical protocol log, and pass/fail summary.
- [ ] **Step 1: Write unit tests for metric calculations**
Cover nearest-rank p95, LCP cutoff, CLS windows, pre-running TBT, post-running long tasks, event-to-next-paint eight-action oracle, scene continuity, lost-presentation share, and zero unexpected late shifts.
- [ ] **Step 2: Implement test-only instrumentation**
Use PerformanceObserver and marks around complete scene update+render. Strip instrumentation from production with a compile-time constant. Never collect GPU/audio identity or export telemetry.
- [ ] **Step 3: Implement `drive-trace.v1` exactly**
Use the offsets/actions in the spec, trusted CDP input, pinned High/Low tiers, five fresh profiles per reference class, precise timeouts, and the deterministic navigation-settlement barrier. Write one result JSON per run plus aggregation.
- [ ] **Step 4: Implement `physical-drive.v1` as a protocol module**
Generate the exact W3C `driveTouch`/`driveKeys` actions, safe-viewport coordinates, cycle direction, excluded 389/390-second cycles, terminal-Static branches, rotations, backgrounding, interruption, context loss, and Contact finish. The module outputs actions and assertions; platform runners consume it without changing timing.
- [ ] **Step 5: Enforce transfer/runtime budgets**
Read the exact byte/frame/bundle thresholds from §14 into named constants and fail the build report on any violation. Audit dynamic chunk separation so reduced motion downloads no Three.js/live-scene asset.
- [ ] **Step 6: Run lab gates and commit tooling/results**
```bash
npm run build:preview
node scripts/check-bundle-budgets.mjs dist
npx playwright test tests/performance/drive-trace.spec.ts
git add src/testing tests/performance scripts/check-bundle-budgets.mjs docs/performance package.json vite.config.ts
git commit -m "perf: enforce journey budgets and reference traces"
```
Physical-device result files are committed only after device metadata is reviewed for privacy.
---
### Task 6: Complete visual QA, flash analysis, and audience validation
**Files:**
- Create: `tests/browser/visual-regression.spec.ts`
- Create: `docs/accessibility/flash-analysis.md`
- Create: `docs/reviews/audience-validation.md`
- Create: `docs/reviews/visual-qa.md`
- Modify: scene/style/component files identified by the captured diffs
**Interfaces:**
- Consumes: final Phase 3 captures across viewports/states.
- Produces: approved visual baseline, flash evidence, and three-person audience results.
- [ ] **Step 1: Capture every required visual state**
Capture desktop widths 1280/1440/1920/2560, tablet 768, mobile portrait/landscape, reduced motion, Static, audio-running, media failure, missing social, and Contact arrival. Use deterministic journey/test state but production rendering code.
- [ ] **Step 2: Review composition against the product direction**
Check readable near-black panels, restrained cyan/amber, no content scanlines/glow, original car silhouette, stable chase composition, no AI/generated project art, balanced systems/software weight, and restrained mobile world density.
- [ ] **Step 3: Run flash analysis**
Analyze final scene/video captures in every current engine. Record that no content exceeds the three-flashes threshold and resolve any failure by reducing/removing the effect.
- [ ] **Step 4: Run the evidence-first audience check**
Three independent hiring/technical reviewers use Static or reduced motion. Record 30-second identity/role/contact and 60-second systems/software proof results plus professionalism/evidence-balance ratings. Revise content hierarchy if thresholds fail.
- [ ] **Step 5: Run the complete Phase 3 gate and commit**
```bash
npm run typecheck
npm run test:unit
npm run build:preview
npm run test:browser
node scripts/check-bundle-budgets.mjs dist
git add tests/browser/visual-regression.spec.ts docs/accessibility docs/reviews src
git commit -m "test: approve portfolio quality and audience evidence"
```
## Phase 3 completion checkpoint
The build is a launch candidate only after cleared final assets, WCAG records, cross-engine fault results, performance budgets, physical-device evidence, flash analysis, and audience thresholds all pass without exceptions hidden in CI logs.