Skip to content

feat(perf): #22 wire Web Vitals through GoogleAnalytics + consolidate reporters#78

Merged
TortoiseWolfe merged 1 commit intomainfrom
fix/22-web-vitals-instrumentation
May 6, 2026
Merged

feat(perf): #22 wire Web Vitals through GoogleAnalytics + consolidate reporters#78
TortoiseWolfe merged 1 commit intomainfrom
fix/22-web-vitals-instrumentation

Conversation

@TortoiseWolfe
Copy link
Copy Markdown
Owner

Summary

Closes #22 (Mobile-First Design perf-test gap) by activating Web Vitals instrumentation that already half-existed in the codebase. Two parallel implementations consolidated onto one consent-aware path. Net −95 LOC (drops ~280 LOC of hand-rolled PerformanceObserver logic in favor of the battle-tested npm package).

Why this is a Phase 0 ticket (GrimGlow ThreeJS prep)

GrimGlow Phase 1a is browser R3F on top of ScriptHammer. Three.js scenes notoriously degrade Core Web Vitals — large LCP from canvas mount, CLS from late-bound geometry, INP from main-thread shader compilation. Landing the measurement infrastructure in template means every fork inherits it for free instead of re-deriving.

What changed

  • src/utils/web-vitals.ts is now a thin re-export of onCLS/onFCP/onINP/onLCP/onTTFB/Metric from the npm web-vitals package. Public API unchanged so /status consumer keeps working. Drops ~280 LOC of bespoke timing logic.
  • src/utils/performance.ts initWebVitals() was previously dead code; now it actually runs. Subscribes through the re-export and dispatches via consolidated sendToAnalytics, routed through the consent-gated analytics.ts trackWebVital.
  • src/lib/analytics/GoogleAnalytics calls initWebVitals() once when consent.analytics flips on. Ref-guarded so consent toggle off-then-on doesn't double-subscribe.
  • src/types/analytics.types.ts WebVitalEvent.metric_name drops the 'FID' literal — Chromium replaced FID with INP in 2024; web-vitals ^5 no longer exports onFID.

Wireframe sub-task

The audit also asked to "regenerate wireframes" for this feature. Re-validated both SVGs against the current validator v5.4 — both PASS with no errors. The audit's regen claim was stale (issues files dated Jan 2026 against validator v5.0/v5.2). Updated the issues files to record the re-validation rather than running speculative regeneration.

GA4 acceptance criteria (from features/foundation/004-mobile-first-design/spec.md)

  • AC-1: LCP under 2.5s on mobile — measured (not enforced)
  • AC-2: FID under 100ms — replaced by INP under 200ms (FID deprecated)
  • AC-3: CLS below 0.1 — measured (not enforced)

These are observation thresholds, not fix-it requirements — the deliverable is measurement infrastructure, not optimization.

Tests

7 new tests in src/utils/web-vitals.test.ts:

  • Re-exports forward to npm package callbacks
  • reportWebVitals subscribes to all 5 metrics
  • Regression pin: no onFID re-introduction (web-vitals ^5 dropped it)
  • sendToAnalytics consent-gating contract (forwards when enabled, suppresses when disabled)
  • sendToAnalytics does NOT pre-scale CLS (the older bespoke implementation did; the consolidated path delegates rounding to trackWebVital to avoid double-scaling)

Verification

  • pnpm run type-check: clean
  • pnpm run lint: clean
  • pnpm test: 3255/3255 pass (7 new tests, full suite green)
  • Husky pre-push (lint, type-check, unit, build): all green

Test plan

  • CI runs the full Chromium + Firefox + WebKit E2E suite
  • Smoke test: open the app in a browser with NEXT_PUBLIC_GA_MEASUREMENT_ID set, accept consent → DevTools Network → filter g/collect → see LCP / INP / CLS event payloads after the first navigation
  • Smoke test: localhost without GA ID set → initWebVitals short-circuits gracefully (no errors)

Phase 0 status after this lands

# Ticket Status
#24 SetupBanner clarity ✅ Closed (PR #77)
#69 node_modules volume permissions ✅ Closed (PR #70)
#22 Mobile-First perf instrumentation ✅ This PR
#21 WCAG AAA scope upgrade ⚠️ Open — next in plan
#73 Constitution v1.0.2 wireframe gate ⚠️ Open — sequenced before #21

After #73 + #21 close, Phase 0 is closed and Phase 0.5 (#48 Three.js Game) becomes the next real work.

Closes

Closes #22

🤖 Generated with Claude Code

… reporters

Closes #22 Mobile-First Design's perf-test gap by activating Web Vitals
instrumentation that already half-existed in the codebase. Two parallel
implementations were unifying onto one consent-aware path:

- src/utils/performance.ts had initWebVitals() using the npm web-vitals
  package, but it was dead code — nobody imported it. Its sendToAnalytics
  bypassed the consent-aware analytics.ts pipeline and called gtag
  directly.
- src/utils/web-vitals.ts hand-rolled ~280 LOC of PerformanceObserver
  logic that duplicated the npm package, only consumed by /status
  diagnostic page.

Consolidation:

- src/utils/web-vitals.ts is now a thin re-export of onCLS/onFCP/onINP/
  onLCP/onTTFB/Metric from the npm web-vitals package. Public API
  unchanged so /status keeps working. Drops ~280 LOC of bespoke timing
  code in favor of battle-tested package implementation.
- src/utils/performance.ts initWebVitals now subscribes through that
  re-export and dispatches via the consolidated sendToAnalytics, which
  routes through analytics.ts trackWebVital (consent-gated).
- src/lib/analytics/GoogleAnalytics calls initWebVitals once when
  consent.analytics flips on. Ref-guarded so consent toggle off-then-on
  doesn't double-subscribe.
- src/types/analytics.types.ts WebVitalEvent.metric_name drops the
  'FID' literal — Chromium replaced FID with INP in 2024, the
  web-vitals ^5 package no longer exports onFID.

Also closes the wireframe sub-task: features/foundation/004-mobile-
first-design/wireframes/{01,02}.issues.md were stale (Jan 2026
validator v5.0/v5.2 entries). Re-validated against current v5.4 — both
SVGs PASS with no errors. The audit's regen claim was stale; updating
the issues files records the re-validation rather than running
speculative regeneration.

GA4 acceptance criteria from features/foundation/004-mobile-first-
design/spec.md:
- AC-1: LCP under 2.5s on mobile — measured (not enforced)
- AC-2: FID under 100ms — replaced by INP under 200ms
- AC-3: CLS below 0.1 — measured (not enforced)

Verification:
- pnpm run type-check: clean
- pnpm run lint: clean
- pnpm test: 3255/3255 pass (7 new web-vitals tests; consolidation
  test surface includes the consent-gating contract, the
  no-FID-regression pin, and the no-pre-scaling CLS contract)

Why this is a Phase 0 ticket (per gleaming-kitten plan):
GrimGlow Phase 1a is browser R3F on top of ScriptHammer. Three.js
scenes notoriously degrade Core Web Vitals (large LCP from canvas
mount, CLS from late-bound geometry, INP from main-thread shader
compilation). Landing the measurement infrastructure in template means
every fork inherits it for free instead of re-deriving.

Closes #22

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@TortoiseWolfe TortoiseWolfe merged commit ad9e7d3 into main May 6, 2026
28 checks passed
@TortoiseWolfe TortoiseWolfe deleted the fix/22-web-vitals-instrumentation branch May 6, 2026 05:56
TortoiseWolfe added a commit that referenced this pull request May 6, 2026
Phase 0 (template hygiene for forks) closed this session via 5 PRs:
  - #77 (#24) SetupBanner clarity
  - #70 (#69) Docker volume permissions
  - #78 (#22) Mobile-First Web Vitals instrumentation
  - #79 (#73) Constitution v1.0.2 wireframe gate
  - #81 (#21) WCAG AAA scope upgrade

STATUS.md updates:
  - Snapshot date: 2026-04-27 → 2026-05-06
  - Stability blurb: rewritten to reflect Phase 0 closure
  - 001 WCAG: [~] → [x] AAA standard, ContactForm green, overlay → #80
  - 004 Mobile-First: [~] → [x] Web Vitals through GoogleAnalytics
  - 006 Template Fork: [~] → [x] SetupBanner + Docker volume fix
  - 015 OAuth Display Name: [~] → [x] full cascade with GitHub fixtures
  - Summary table: Shipped count 18 → 22

Inventories regenerated via scripts/refresh-inventories.py:
  - skill-index.md (22 items)
  - workflow-status.md (7 items)
  - security-touchpoints.md (45 items)
  - screen-inventory.md (25 items)

Pure documentation; no code changes.

Co-authored-by: TurtleWolfe <TurtleWolfe@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Gap-Audit] 004 Mobile-First Design: regenerate wireframes + complete perf tests

2 participants