18 KiB
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/, updatedcontent/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.v1exactly
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.v1as 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.