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

18 KiB
Raw Permalink Blame History

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:

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.

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:

{
  "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
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
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
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

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
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
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
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
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.