From a4cf2208332b248c2a408dc9bab47d65cca45a88 Mon Sep 17 00:00:00 2001 From: Fable Integrator Date: Thu, 9 Jul 2026 23:02:36 -0400 Subject: [PATCH] =?UTF-8?q?chore:=20agent-legibility=20sweep=20=E2=80=94?= =?UTF-8?q?=20repo=20map,=20doc=20status=20headers,=20dead-code=20deletion?= =?UTF-8?q?,=20config+test=20hygiene?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NEW docs/REPO_MAP.md (route atlas both products + lifting + admin, canonical-idiom table with file:line anchors, traps list, agent pre-flight checklist) linked from AGENTS.md + CLAUDE.md. - 77 doc-status headers (STALE/SUPERSEDED/PARKED) across docs/; memory/registry.yml re-pointed off the January auth docs (YAML-verified). - 32 dead files deleted (19 DEAD-CERTAIN + verified extras incl. the golf-mockups .tsx shadowed by its newer .ts barrel); rotten unwired guard scripts reconciled: chart-palette.test.mjs deleted (5/6 subjects were already gone since W1; live coverage is in the vitest stat-visuals suites), drawers-mobile.test.mjs pruned of the deleted IntentDrawer case and re-run green (6/6). - Config residue: next-shadcn-dashboard-starter + helm-vid stripped from tsconfig/eslint excludes. - Test hygiene: team-detail .range mock-drift latent bug fixed; stale skip re-documented; env-var honesty pass on .env.example (RESERVED markers on unwired integrations). Gates at commit: targeted suites green; full gates run in PR CI. --no-verify: documented hook-hang bypass. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX --- .env.example | 30 +- AGENTS.md | 14 + CLAUDE.md | 1 + docs/ADMIN_DASHBOARD_UPGRADE_PLAN.md | 7 + docs/APPSTORE_MASTER_REMEDIATION_PLAN.md | 7 + docs/APPSTORE_REQUIREMENTS_CHECKLIST.md | 7 + docs/BASEBALL_DASHBOARD_AUDIT_REPORT.md | 7 + docs/BASEBALL_RLS_SECURITY_AUDIT.md | 7 + docs/CLEANSLATE_JOB_LIST.md | 7 + docs/COACHHELM_STATE_2026-05-26.md | 7 + docs/CODEBASE_MAP.md | 7 + docs/CoachHelm UI Audit Future.md | 7 + docs/DATABASE_FIXES.md | 7 + docs/END-OF-RUN-2026-05-26.md | 7 + docs/ERROR_HANDLING_FIXES.md | 7 + docs/FEATURE_AUDIT.md | 7 + ...E_VERCEL_COACHHELM_DEEP_DIVE_2026-05-27.md | 7 + docs/IOS_NATIVE_AUDIT.md | 7 + docs/LANDING_ENTRY_WORLD_DESIGN.md | 7 + docs/OVERNIGHT_BUILD_REPORT.md | 7 + docs/PERFORMANCE_FIX_PLAN.md | 7 + docs/PRIVACY_AUDIT.md | 7 + docs/PUSH_NOTIFICATION_AUDIT.md | 7 + docs/RECON_REPORT.md | 7 + docs/REPO_MAP.md | 397 ++++++++++ docs/ROUND_REVIEW_ACCURACY_REPORT.md | 7 + docs/SECURITY_AUDIT.md | 7 + docs/SESSION_REPORT_2026-05-27_PART2.md | 7 + docs/SHOT_TRACKING_DEEP_SCRUB.md | 7 + docs/UIUX_AUDIT.md | 7 + docs/UI_POLISH_REPORT.md | 7 + ...rchitectural-review-golfhelm-2026-02-22.md | 7 + .../COMPREHENSIVE_AUTH_SYSTEM_PLAN.md | 7 + docs/architecture/USER_ROLE_DATA_OWNERSHIP.md | 7 + docs/audit-college-coach-2026-02-22.md | 7 + docs/audit-db-security-2026-02-22.md | 7 + docs/audit-errors-flows-2026-02-22.md | 7 + docs/audit-ui-premium-2026-02-22.md | 7 + .../COACHHELM_TO_95_AUDIT_2026-06-08.md | 7 + docs/audits/COACH_DASHBOARD_AUDIT_REPORT.md | 7 + .../audits/COURSE_LIBRARY_AUDIT_2026-06-13.md | 7 + docs/audits/DATA_INTEGRITY_AUDIT.md | 7 + .../GOLFHELM_CALENDAR_AUDIT_2026-06-10.md | 7 + .../GOLFHELM_E2E_TAB_AUDIT_2026-06-20.md | 155 ++-- docs/audits/HELMV3_ISSUE_LEDGER_2026-06-30.md | 7 + docs/audits/RLS_SECURITY_AUDIT.md | 7 + docs/baseball/ENTRY_SCENES_DESIGN.md | 7 + .../PRODUCTION_READINESS_MASTER_PLAN.md | 7 + docs/coachhelm-phase2-deferred-EF-plan.md | 7 + docs/code-quality-review-2026-02-22.md | 7 + docs/fairway-baseballhelm-migration-plan.md | 7 + docs/fairway-coachhelm-insight-rebuild.md | 7 + docs/fairway-recruiting-plan.md | 7 + docs/fairway-shot-tracking-plan.md | 7 + docs/fairway-stats-redesign-plan.md | 7 + ...ENDAR_COMPREHENSIVE_IMPLEMENTATION_PLAN.md | 7 + .../coachhelm/COACHHELM_DISABLE_FEATURE.md | 7 + .../COACHHELM_IMPLEMENTATION_SPEC.md | 7 + .../COACHHELM_INTELLIGENCE_ENGINE.md | 7 + .../COACHHELM_V2_INTELLIGENCE_ENGINE.md | 7 + .../COACHHELM_V2_INTELLIGENCE_ENGINE_FULL.md | 7 + .../coachhelm/CURSOR_IMPLEMENTATION_GUIDE.md | 7 + .../FEATURE_1_COACH_PHILOSOPHY_SETTINGS.md | 7 + .../coachhelm/FEATURE_ROUND_REVIEW.md | 7 + docs/features/coachhelm/README.md | 7 + .../coachhelm/coachhelm-master-blueprint.md | 7 + docs/golfhelm-engine-upgrade-plan.md | 7 + ...-analysis-dashboard-timeouts-2026-03-09.md | 7 + docs/plans/ITEM2_TWO_SESSION_PLAN.md | 7 + .../README.md | 7 + docs/redesign-playbook.md | 7 + .../00-orchestration.md | 7 + .../00-design-contract.md | 7 + .../00-design-contract.md | 7 + ...26-05-27-helm-database-vercel-alignment.md | 7 + .../2026-05-28-ai-codebase-intelligence.md | 7 + docs/v3-compatibility-shims.md | 7 + docs/v3-master-plan.md | 7 + docs/v3-player-transfer-playbook.md | 7 + docs/v3-wave-sequence.md | 7 + e2e/golf-qualifier.spec.ts | 21 +- eslint.config.mjs | 1 - memory/registry.yml | 7 +- scripts/__tests__/chart-palette.test.mjs | 150 ---- scripts/__tests__/drawers-mobile.test.mjs | 12 +- src/app/soreness-preview/page.tsx | 45 -- .../analytics/GameVsPracticePanel.tsx | 560 -------------- .../analytics/PlayerPerformanceGrid.tsx | 289 ------- .../analytics/PressureGapIndicator.tsx | 209 ----- .../analytics/StatTypeFilter.tsx | 107 --- .../analytics/TeamBattingOverview.tsx | 215 ------ .../analytics/TrendAnalysisPanel.tsx | 317 -------- .../command-center/analytics/index.ts | 23 - src/components/demo/DemoEnterTracker.tsx | 9 +- src/components/golf/GolfTabBar.tsx | 145 ---- .../golf/calendar/PremiumCalendarClient.tsx | 3 +- .../coachhelm/analytics/MissPatternChart.tsx | 312 -------- .../analytics/ShotAnalyticsPanel.tsx | 461 ------------ .../coachhelm/analytics/ShotTypeBreakdown.tsx | 442 ----------- .../coachhelm/analytics/TrendIndicator.tsx | 249 ------ .../golf/coachhelm/analytics/index.ts | 8 - .../golf/coachhelm/v3/IntentDrawer/index.tsx | 211 ------ .../golf/coachhelm/v3/PuttHeatmap/index.tsx | 357 --------- src/components/golf/dashboard/index.ts | 22 - src/components/golf/layout/AnimatedPage.tsx | 3 +- .../golf/layout/LargeTitleHeader.tsx | 107 --- .../golf/layout/MobileNavHeader.tsx | 108 --- .../golf/messages/GolfNewMessageModal.tsx | 350 --------- .../golf/messages/GolfTeamBroadcastModal.tsx | 409 ---------- .../golf/qualifiers/QualifierBracket.tsx | 306 -------- .../golf/roster/JoinRequestAlert.tsx | 142 ---- .../golf/rounds/RoundLibraryClient.tsx | 712 ------------------ src/components/golf/rounds/Sparkline.tsx | 150 ---- .../golf/stats/sections/shared-primitives.tsx | 474 ------------ src/components/layout/mobile-bottom-nav.tsx | 166 ---- src/components/player/profile/PlayerCard.tsx | 230 ------ .../profile/PlayerProfileCoachActions.tsx | 85 --- .../products/golf-mockups/index.tsx | 508 ------------- src/components/ui/secondary-nav.tsx | 2 +- src/components/video/VideoClipper.tsx | 436 ----------- src/hooks/golf/use-team-context.ts | 159 ---- src/hooks/useAnimatedNumber.ts | 44 -- .../admin/data/__tests__/team-detail.test.ts | 12 + src/test/SKIPPED.md | 7 +- ...0_bridge_baseball_coach_lifting_revoke.sql | 2 +- tsconfig.json | 2 - 126 files changed, 1085 insertions(+), 8626 deletions(-) create mode 100644 docs/REPO_MAP.md delete mode 100644 scripts/__tests__/chart-palette.test.mjs delete mode 100644 src/app/soreness-preview/page.tsx delete mode 100644 src/components/baseball/command-center/analytics/GameVsPracticePanel.tsx delete mode 100644 src/components/baseball/command-center/analytics/PlayerPerformanceGrid.tsx delete mode 100644 src/components/baseball/command-center/analytics/PressureGapIndicator.tsx delete mode 100644 src/components/baseball/command-center/analytics/StatTypeFilter.tsx delete mode 100644 src/components/baseball/command-center/analytics/TeamBattingOverview.tsx delete mode 100644 src/components/baseball/command-center/analytics/TrendAnalysisPanel.tsx delete mode 100644 src/components/baseball/command-center/analytics/index.ts delete mode 100644 src/components/golf/GolfTabBar.tsx delete mode 100644 src/components/golf/coachhelm/analytics/MissPatternChart.tsx delete mode 100644 src/components/golf/coachhelm/analytics/ShotAnalyticsPanel.tsx delete mode 100644 src/components/golf/coachhelm/analytics/ShotTypeBreakdown.tsx delete mode 100644 src/components/golf/coachhelm/analytics/TrendIndicator.tsx delete mode 100644 src/components/golf/coachhelm/analytics/index.ts delete mode 100644 src/components/golf/coachhelm/v3/IntentDrawer/index.tsx delete mode 100644 src/components/golf/coachhelm/v3/PuttHeatmap/index.tsx delete mode 100644 src/components/golf/dashboard/index.ts delete mode 100644 src/components/golf/layout/LargeTitleHeader.tsx delete mode 100644 src/components/golf/layout/MobileNavHeader.tsx delete mode 100644 src/components/golf/messages/GolfNewMessageModal.tsx delete mode 100644 src/components/golf/messages/GolfTeamBroadcastModal.tsx delete mode 100644 src/components/golf/qualifiers/QualifierBracket.tsx delete mode 100644 src/components/golf/roster/JoinRequestAlert.tsx delete mode 100644 src/components/golf/rounds/RoundLibraryClient.tsx delete mode 100644 src/components/golf/rounds/Sparkline.tsx delete mode 100644 src/components/golf/stats/sections/shared-primitives.tsx delete mode 100644 src/components/layout/mobile-bottom-nav.tsx delete mode 100644 src/components/player/profile/PlayerCard.tsx delete mode 100644 src/components/player/profile/PlayerProfileCoachActions.tsx delete mode 100644 src/components/products/golf-mockups/index.tsx delete mode 100644 src/components/video/VideoClipper.tsx delete mode 100644 src/hooks/golf/use-team-context.ts delete mode 100644 src/hooks/useAnimatedNumber.ts diff --git a/.env.example b/.env.example index 3ab10c005..e2a0df2a9 100644 --- a/.env.example +++ b/.env.example @@ -23,14 +23,20 @@ SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here # Application Configuration (REQUIRED) # ----------------------------------------------------------------------------- NEXT_PUBLIC_APP_URL=http://localhost:3000 +# RESERVED — not read anywhere in src/ today (verified 2026-07-09). Safe to +# set, has no effect until a call site is added. NEXT_PUBLIC_APP_NAME=Helm Sports Labs -# Development Mode (Disable in production!) +# RESERVED — not read anywhere in src/ today (verified 2026-07-09). There is +# no dev-mode gate in the app; this var currently does nothing. NEXT_PUBLIC_DEV_MODE=false # ----------------------------------------------------------------------------- # Tambo AI (for GolfHelm generative UI) # ----------------------------------------------------------------------------- +# RESERVED — aspirational. No `tambo` package.json dependency and zero Tambo +# references anywhere in src/ (verified 2026-07-09); the generative-UI +# integration this section name implies has not been built. NEXT_PUBLIC_TAMBO_API_KEY=your-tambo-api-key-here # ----------------------------------------------------------------------------- @@ -146,7 +152,10 @@ GMAIL_DAILY_CAP=50 FROM_EMAIL=Helm Sports # From address for the weekly coach digest email (resend-client.ts). RESEND_COACH_DIGEST_FROM=Helm Sports -# From address for team-invite emails (team-invite.ts). +# RESERVED — not read anywhere in src/ today (verified 2026-07-09). No +# team-invite.ts file exists; only RESEND_COACH_DIGEST_FROM is currently +# read by src/lib/email/resend-client.ts. Safe to set, has no effect until +# a team-invite send path is built. RESEND_TEAM_INVITE_FROM=Helm Sports # ----------------------------------------------------------------------------- @@ -238,14 +247,17 @@ GITHUB_PR_AUTHOR_LOGINS=your-github-username # INNGEST_SIGNING_KEY=signkey-prod-your-key # ----------------------------------------------------------------------------- -# Mapbox (Maps — courses, travel, recruiting heat-maps) +# Mapbox (Maps — courses, travel, recruiting heat-maps) — RESERVED, NOT BUILT # ----------------------------------------------------------------------------- -# Free tier: 50K map loads/month for Web SDK, 25K monthly active users -# on Mobile (Capacitor). Sign up at https://account.mapbox.com → tokens -# page → create a public token scoped to styles:read, fonts:read, -# tilesets:read. The public token ships to the browser — fine since -# scoping prevents abuse. Restrict by URL (helmsportslabs.com, *.vercel.app) -# in the Mapbox dashboard. +# No `mapbox` package.json dependency and zero Mapbox references anywhere in +# src/ (verified 2026-07-09) — src/lib/mapbox/client.ts and +# src/components/maps/CourseMap.tsx do not exist. This section documents the +# intended future setup, not a live integration: free tier would be 50K map +# loads/month for Web SDK, 25K monthly active users on Mobile (Capacitor). +# If/when built: sign up at https://account.mapbox.com → tokens page → +# create a public token scoped to styles:read, fonts:read, tilesets:read. +# The public token ships to the browser — fine since scoping prevents abuse. +# Restrict by URL (helmsportslabs.com, *.vercel.app) in the Mapbox dashboard. # NEXT_PUBLIC_MAPBOX_TOKEN=pk.your-mapbox-public-token # ---- GolfHelm UI Screenshots ---- diff --git a/AGENTS.md b/AGENTS.md index afdd451db..bda3caca9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,19 @@ # AGENTS.md +## Repo map + +- `docs/REPO_MAP.md` is the structural map of this repo for agents: the + resolved route atlas for BaseballHelm/GolfHelm/Lift Lab/Admin (route + groups resolved, per-role rails), the canonical idioms table (action + wrappers, toast, data access, design tokens per product, nav + registries, error-boundary pattern — each with a file:line anchor), + the known traps list, and a before-you-write-code checklist. +- Read it before adding a new route, a new action wrapper/toast/data-access + call, or a new design-token consumer — it exists so you don't have to + re-derive these conventions by grepping from scratch. +- It maps *shape and convention*, not feature behavior — for feature + behavior, use the Feature awareness routing below. + ## Feature awareness - Treat `memory/registry.yml` as the feature routing table for AI work. diff --git a/CLAUDE.md b/CLAUDE.md index 966136c0f..638162877 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,6 +40,7 @@ For large changes or PR reviews, read `/tmp/helmv3-context-pack.md` after genera | **CoachHelm AI** (insights, patterns, predictions, reviews, philosophy) | `memory/context/coachhelm-ai.md` — V2 engine architecture, pipeline, components | | **Routes, actions, or file locations** ("where is the code for X?") | `memory/projects/golfhelm.md` — All routes, all action files, component directories | | **Baseball features** | `memory/context/baseballhelm-features.md` — feature-by-feature data flow, files, tables, gaps; `docs/audits/BASEBALLHELM_CANONICAL_SPEC.md` — canonical spec (source of truth for what baseball should be) | +| **Cross-product structure** (route trees, canonical action-wrapper/toast/data-access/design-token/nav-registry/error-boundary idioms, known traps) | `docs/REPO_MAP.md` — resolved route atlas for BaseballHelm/GolfHelm/Lift Lab/Admin, idioms table with file:line anchors, 7 traps, pre-code checklist | ### By Role Context diff --git a/docs/ADMIN_DASHBOARD_UPGRADE_PLAN.md b/docs/ADMIN_DASHBOARD_UPGRADE_PLAN.md index 3e09697a0..3cd869ac8 100644 --- a/docs/ADMIN_DASHBOARD_UPGRADE_PLAN.md +++ b/docs/ADMIN_DASHBOARD_UPGRADE_PLAN.md @@ -1,3 +1,10 @@ + + # Admin Dashboard Upgrade Plan > Compiled from 11 parallel research agents analyzing UI/UX design + data pipelines across all 5 admin tabs. diff --git a/docs/APPSTORE_MASTER_REMEDIATION_PLAN.md b/docs/APPSTORE_MASTER_REMEDIATION_PLAN.md index 13aac4c9f..1bac9c977 100644 --- a/docs/APPSTORE_MASTER_REMEDIATION_PLAN.md +++ b/docs/APPSTORE_MASTER_REMEDIATION_PLAN.md @@ -1,3 +1,10 @@ + + # App Store Master Remediation Plan **App:** Helm Sports Labs (com.helmsportslabs.golfhelm) diff --git a/docs/APPSTORE_REQUIREMENTS_CHECKLIST.md b/docs/APPSTORE_REQUIREMENTS_CHECKLIST.md index a1d5809ae..6baefeaa9 100644 --- a/docs/APPSTORE_REQUIREMENTS_CHECKLIST.md +++ b/docs/APPSTORE_REQUIREMENTS_CHECKLIST.md @@ -1,3 +1,10 @@ + + # Apple App Store Requirements Checklist **App:** Helm Sports Labs (com.helmsportslabs.golfhelm) diff --git a/docs/BASEBALL_DASHBOARD_AUDIT_REPORT.md b/docs/BASEBALL_DASHBOARD_AUDIT_REPORT.md index 9c5fa77ef..1c62efed5 100644 --- a/docs/BASEBALL_DASHBOARD_AUDIT_REPORT.md +++ b/docs/BASEBALL_DASHBOARD_AUDIT_REPORT.md @@ -1,3 +1,10 @@ + + # BaseballHelm Dashboard Audit Report ## Comprehensive Analysis: UI, UX, Features, Database, and Wiring diff --git a/docs/BASEBALL_RLS_SECURITY_AUDIT.md b/docs/BASEBALL_RLS_SECURITY_AUDIT.md index ca2cde374..3c7802878 100644 --- a/docs/BASEBALL_RLS_SECURITY_AUDIT.md +++ b/docs/BASEBALL_RLS_SECURITY_AUDIT.md @@ -1,3 +1,10 @@ + + # BaseballHelm RLS Security Audit **Generated:** 2026-02-21 diff --git a/docs/CLEANSLATE_JOB_LIST.md b/docs/CLEANSLATE_JOB_LIST.md index e77ff624a..08e22a368 100644 --- a/docs/CLEANSLATE_JOB_LIST.md +++ b/docs/CLEANSLATE_JOB_LIST.md @@ -1,3 +1,10 @@ + + # BaseballHelm Clean-Slate — Parallel Job List > Hand each **GROUP** below to a separate Claude session. Each group is file-disjoint from the others, diff --git a/docs/COACHHELM_STATE_2026-05-26.md b/docs/COACHHELM_STATE_2026-05-26.md index 60aef4ec6..e8fd245d9 100644 --- a/docs/COACHHELM_STATE_2026-05-26.md +++ b/docs/COACHHELM_STATE_2026-05-26.md @@ -1,3 +1,10 @@ + + # CoachHelm — Complete State, Plan, and Build Reference > **Snapshot date:** 2026-05-26 diff --git a/docs/CODEBASE_MAP.md b/docs/CODEBASE_MAP.md index a95e7adfd..a2cbbac79 100644 --- a/docs/CODEBASE_MAP.md +++ b/docs/CODEBASE_MAP.md @@ -1,3 +1,10 @@ + + --- last_mapped: 2026-01-13T00:00:00Z total_files: 1752 diff --git a/docs/CoachHelm UI Audit Future.md b/docs/CoachHelm UI Audit Future.md index a4d3180f3..54e844349 100644 --- a/docs/CoachHelm UI Audit Future.md +++ b/docs/CoachHelm UI Audit Future.md @@ -1,3 +1,10 @@ + + # CoachHelm UI Audit — Future > **What this is:** every CoachHelm UI surface mapped to its **real production data** (prod `qmnssrrolpinvwjjnufo`), plus a prioritized "what to fix / build next" plan. diff --git a/docs/DATABASE_FIXES.md b/docs/DATABASE_FIXES.md index 7fdd7750b..b5c374bfa 100644 --- a/docs/DATABASE_FIXES.md +++ b/docs/DATABASE_FIXES.md @@ -1,3 +1,10 @@ + + # Database & Backend Fixes Report > **Generated**: 2026-02-17 00:10 EST diff --git a/docs/END-OF-RUN-2026-05-26.md b/docs/END-OF-RUN-2026-05-26.md index e37e26d3a..98c9610c7 100644 --- a/docs/END-OF-RUN-2026-05-26.md +++ b/docs/END-OF-RUN-2026-05-26.md @@ -1,3 +1,10 @@ + + # GolfHelm v3 — End-of-run review (2026-05-26) > **Comprehensive map of every wave shipped tonight.** Generated after merging the last PR; mirrors the live state of prod + the codebase as of 2026-05-26 23:30 UTC. diff --git a/docs/ERROR_HANDLING_FIXES.md b/docs/ERROR_HANDLING_FIXES.md index 27b56e934..a23f2019d 100644 --- a/docs/ERROR_HANDLING_FIXES.md +++ b/docs/ERROR_HANDLING_FIXES.md @@ -1,3 +1,10 @@ + + # Error Handling Fixes Report > **Generated**: 2026-02-17 00:20 EST diff --git a/docs/FEATURE_AUDIT.md b/docs/FEATURE_AUDIT.md index 00d924fb1..a2bf3d391 100644 --- a/docs/FEATURE_AUDIT.md +++ b/docs/FEATURE_AUDIT.md @@ -1,3 +1,10 @@ + + # Feature & Functionality Audit for App Store Readiness **Date:** 2026-03-05 diff --git a/docs/HELM_DATABASE_VERCEL_COACHHELM_DEEP_DIVE_2026-05-27.md b/docs/HELM_DATABASE_VERCEL_COACHHELM_DEEP_DIVE_2026-05-27.md index 5c1fc0574..8dfe99e56 100644 --- a/docs/HELM_DATABASE_VERCEL_COACHHELM_DEEP_DIVE_2026-05-27.md +++ b/docs/HELM_DATABASE_VERCEL_COACHHELM_DEEP_DIVE_2026-05-27.md @@ -1,3 +1,10 @@ + + # Helm Database, Vercel, Supabase, and CoachHelm Deep Dive Date: 2026-05-27 diff --git a/docs/IOS_NATIVE_AUDIT.md b/docs/IOS_NATIVE_AUDIT.md index e08d9d428..dd00997cc 100644 --- a/docs/IOS_NATIVE_AUDIT.md +++ b/docs/IOS_NATIVE_AUDIT.md @@ -1,3 +1,10 @@ + + # iOS Native Integration & Capacitor Audit **Date:** 2026-03-05 diff --git a/docs/LANDING_ENTRY_WORLD_DESIGN.md b/docs/LANDING_ENTRY_WORLD_DESIGN.md index e79595db9..b3d0f0d36 100644 --- a/docs/LANDING_ENTRY_WORLD_DESIGN.md +++ b/docs/LANDING_ENTRY_WORLD_DESIGN.md @@ -1,3 +1,10 @@ + + # HELM — The Entry World ### Landing · Login · Onboarding as one continuous experience *Fable's design spec · 2026-07-02 · synthesized from 4-stream research (cinematic patterns / real-glass cookbook / art direction / scroll narrative) + Nick's brief + his 4 references. Companion doc: docs/baseball/ENTRY_SCENES_DESIGN.md (the painterly SVG scenes).* diff --git a/docs/OVERNIGHT_BUILD_REPORT.md b/docs/OVERNIGHT_BUILD_REPORT.md index c11f976e7..3b4a33839 100644 --- a/docs/OVERNIGHT_BUILD_REPORT.md +++ b/docs/OVERNIGHT_BUILD_REPORT.md @@ -1,3 +1,10 @@ + + # BaseballHelm Overnight Build Report > **Build Date**: 2026-02-17 diff --git a/docs/PERFORMANCE_FIX_PLAN.md b/docs/PERFORMANCE_FIX_PLAN.md index 781e19833..09fb1528e 100644 --- a/docs/PERFORMANCE_FIX_PLAN.md +++ b/docs/PERFORMANCE_FIX_PLAN.md @@ -1,3 +1,10 @@ + + # GolfHelm Performance Fix Architecture Plan **Created:** 2026-02-20 diff --git a/docs/PRIVACY_AUDIT.md b/docs/PRIVACY_AUDIT.md index 7791010fc..b58dc8c5c 100644 --- a/docs/PRIVACY_AUDIT.md +++ b/docs/PRIVACY_AUDIT.md @@ -1,3 +1,10 @@ + + # Privacy & Data Compliance Audit Report **App:** Helm Sports Labs (GolfHelm / BaseballHelm) diff --git a/docs/PUSH_NOTIFICATION_AUDIT.md b/docs/PUSH_NOTIFICATION_AUDIT.md index 87dee57e5..b9b846c0a 100644 --- a/docs/PUSH_NOTIFICATION_AUDIT.md +++ b/docs/PUSH_NOTIFICATION_AUDIT.md @@ -1,3 +1,10 @@ + + # Push Notification Audit Report **Date:** March 5, 2026 diff --git a/docs/RECON_REPORT.md b/docs/RECON_REPORT.md index 834395412..3ba1ab8b4 100644 --- a/docs/RECON_REPORT.md +++ b/docs/RECON_REPORT.md @@ -1,3 +1,10 @@ + + # BaseballHelm Overnight Build - Reconnaissance Report > **Generated**: 2026-02-16 22:10 EST diff --git a/docs/REPO_MAP.md b/docs/REPO_MAP.md new file mode 100644 index 000000000..cfdd403e1 --- /dev/null +++ b/docs/REPO_MAP.md @@ -0,0 +1,397 @@ +# Repo Map for Agents + +Structural map of helmv3: route atlas, canonical idioms (with file:line +anchors), known traps, and a pre-code checklist. This is a map of *shape and +convention*, not of feature behavior — for feature behavior use +`memory/registry.yml` and `memory/context/*.md` per `CLAUDE.md`'s context +routing table. This doc does not auto-regenerate; if a route count or +file:line anchor looks wrong, trust the source file over this doc and flag +the drift. + +Scope: `src/app/**` route trees for BaseballHelm, GolfHelm, Lift Lab, and +Admin/Helm Bridge; the canonical action-wrapper / toast / data-access / +design-token / nav-registry / error-boundary idioms; the 7 traps found by a +2026-07 sweep; a before-you-write-code checklist. + +--- + +## 1. Product & route atlas + +Route groups (`(auth)`, `(dashboard)`, etc.) resolve away — they don't add a +URL segment, they only partition intent/layout. Listed below with groups +resolved to the actual path. + +### BaseballHelm — `src/app/baseball/**` (106 `page.tsx`, 85 with a sibling `error.tsx`) + +- **`(auth)`** — `/baseball/login`, `/signup`, `/forgot-password`, + `/reset-password`, `/demo`, `/complete-signup` +- **`(onboarding)`** — `/baseball/coach`, `/coach-onboarding`, `/player` +- **`(dashboard)/dashboard/*`** (coach + shared rail, ~80 leaves) — + `/baseball/dashboard` plus: `academics`, `activate`, `analytics`, + `announcements`, `calendar`, `camps[+/[id]]`, `college-interest`, + `colleges`, `command-center`, `compare`, `comparisons`, `decision-room`, + `dev-plan`, `dev-plans[+/[id]]`, `discover`, `documents`, `events`, + `import`, `journey`, `lift[+/[sessionId]]`, `messages[+/[id]]`, + `my-stats`, `operations`, `organization`, + `performance(+/builder,/groups,/live,/players/[id],/programs[+/[programId]])`, + `pipeline`, `players/[id](+/passport,/scout-packet[+/preview],/stats)`, + `postgame`, `practice`, `practice-effectiveness`, `profile`, `program`, + `readiness`, `roster`, `scout-packets`, `scouting`, + `settings(+/ai,/appearance,/audit,/data-retention,/demo-mode,/guardian-access,/imports,/integrations,/notifications,/permissions,/philosophy,/player-access,/privacy,/program,/recruiting-preferences,/roles,/season,/showcase-profile,/staff,/teams)`, + `signals`, `stats(+/games[+/[gameId],/create],/upload)`, `stats-center`, + `tasks`, `team`, `teams`, `travel`, `videos`, `watchlist` +- **`(player-dashboard)`** (player-only rail) — `/baseball/player/today`, + `/practice`, `/timeline`, `/passport` +- **`(public)`** (no auth) — `/baseball/packet/[token]`, + `/baseball/player/[id]`, `/baseball/program/[id]`, `/baseball/team/[id]` +- **Misc top-level** — `/baseball/admin/demo-sessions`, + `/baseball/join/[code]`, `/baseball/staff/join/[code]` + +### GolfHelm — `src/app/golf/**` (64 `page.tsx`, 65 `error.tsx` — near 1:1) + +- **`(auth)`** — `/golf/login`, `/signup`, `/forgot-password`, + `/reset-password`, `/demo`, `/welcome` +- **`(onboarding)`** — `/golf/coach`, `/player` +- **`(dashboard)/dashboard/*`** — `/golf/dashboard` plus: `alerts`, + `analytics/coachhelm`, `announcements`, `calendar`, `classes`, + `coachhelm(+/chat,/genome/[playerId],/genome/compare,/qualifying/[id])`, + `courses`, `development`, `documents`, `hub`, `insights`, `intelligence`, + `messages`, `my-development`, `my-game-profile`, `my-insights`, + `my-qualifiers`, `my-standing`, `patterns`, `players/[playerId](+/game[+/print])`, + `qualifiers[+/[id],/new]`, `recruiting`, `roster[+/[id]]`, + `rounds[+/[id][+/review],/continue/[id],/new,/recover]`, + `settings(+/coaching-intelligence,/notifications)`, `stats[+/team]`, + `tasks`, `team`, `team-hub`, `travel`, `whats-new` +- **Join** — `/golf/join`, `/golf/join/[code]` +- **`/golf/admin/*`** — `/golf/admin`, `/golf/admin/crm[+/coach/[id]]`, + `/golf/admin/demo-sessions`. This is **GolfHelm's own** CRM/demo-sessions + console — a separate tree from the cross-product `/admin` Helm Bridge + below. Don't conflate the two "admin" trees. + +### Lift Lab — `src/app/lifting/**` (23 `page.tsx`) + +- **`(auth)`** — `/lifting/login`, `/signup`, `/forgot-password`, + `/reset-password` +- **`(onboarding)`** — `/lifting/coach` +- **`(dashboard)/dashboard/*`** — `/lifting/dashboard` plus: + `athletes[+/[athleteId]]`, `check-ins`, `command`, `exercises`, `groups`, + `import`, `lift[+/[sessionId]]`, `programs[+/[programId]]`, `readiness`, + `sessions[+/live]`, `settings`, `today` +- **Join** — `/lifting/join/[token]` +- **Gap**: only 2 `error.tsx` files exist for the whole product + (`dashboard/error.tsx`, `dashboard/programs/[programId]/error.tsx`) vs. + baseball's 85 and golf's 65. Adding a new lifting route should add its own + `error.tsx` to close this gap, matching baseball/golf convention — see + checklist item below. + +### Admin / Helm Bridge — `src/app/admin/**` (17 `page.tsx`, flat — no route groups) + +`/admin` (root/overview), `/activity`, `/auth`, `/baseball`, `/ben-leah`, +`/deploys`, `/errors[+/[fingerprint]]`, `/golf[+/tracer]`, `/health`, +`/jobs`, `/teams/[id]`, `/users[+/[id][+/view-as]]`, `/work`. + +Super-admin gated via `checkSuperAdminAccess()` in +`src/app/admin/layout.tsx` (per `src/app/admin/error.tsx` comment); +`SUPER_ADMIN_USER_IDS` is the env gate. This is the cross-product +super-admin console — distinct from `/golf/admin` and +`/baseball/admin/demo-sessions` (per-product panels). An agent searching +for "admin" needs all three named. + +### Root / marketing / legal / misc + +Top-level `src/app` pages (route groups resolved): `/`, `/about`, `/help`, +`/products`, `/support`, `/splash`, `/vizlab`, `/fairway-preview`, +`/soreness-preview`, plus `(legal)` → `/privacy`, `/terms`. + +**`helm-website-ui/`** is a second, wholly separate Next.js app +(`helm-website-ui/app/page.tsx`, `helm-website-ui/app/products/page.tsx`) — +its own build, not part of the `src/app` tree. Don't assume all marketing +routes live under `src/app`. + +--- + +## 2. Canonical idioms + +### Action wrappers + +| Wrapper | Canonical location | Usage | Notes | +|---|---|---|---| +| `withBaseballAction` | `src/lib/baseball/with-baseball-action.ts:248` | 98 sites | Auth + active-context resolution, server-side capability enforcement, `Sentry.withScope` (`sport=baseball`, `feature`, `feature_area`, `action`) + breadcrumb. `opts`: `featureArea` required; optional `requiredCapability` / `requiredPlayerAccess` / `teamFrom` / `requireActiveContext` / `demoSafe`. | +| `withLiftingAction` | `src/lib/lifting/with-lifting-action.ts:113` | 23 sites | Injects `LiftingActionContext {user, orgId, access}`. `opts`: `featureArea`, `requireEdit`, `orgFrom`. Same `Sentry.withScope(sport='lifting', ...)` shape as baseball. | +| `withAdminObserved` | `src/lib/admin/observed-action.ts:56` (comment 21-25, guard `isNextControlFlowError` at line 28) | 129 sites — most-used of the three | Explicitly generalizes the with-baseball/with-lifting idea for cross-sport use ("Helm Bridge capture class #2 — failed server actions"). Contract: never changes the wrapped function's resolve/reject; logging is fire-and-forget via `resolveObservedUser()` (self-swallowing) then `observeActionSoftFailure()`. `isNextControlFlowError()` guards `NEXT_REDIRECT`/`NEXT_NOT_FOUND` digests from being misreported as failures. | + +These three **compose, they don't compete**: a sport-specific wrapper owns +auth + capability + Sentry scoping; `withAdminObserved` is a cross-cutting +observability layer that can wrap *any* action — including one already +wrapped in `withBaseballAction`/`withLiftingAction` — purely to add +`admin_events` soft-failure capture. + +### Toast + +| Variant | Location | Status | +|---|---|---| +| Canonical `useToast()` / `addToast()` | `src/components/ui/sonner.tsx:212-222` (dispatch via `dispatchByType()` at line 195) | **Use this.** Returns `{showToast(message, type)` [legacy 2-arg], `addToast({type,title,description,action,duration})` [options object], `removeToast(id)}`, all routed through Sonner's `toast.success/error/warning/info`. `Toaster` mounted once in `src/app/layout.tsx`. | +| Direct `toast.error(...)` sonner import | 31 files, e.g. `src/app/golf/admin/crm/page.tsx:839,843,886,985,996` | Accepted legacy pattern, not broken. Don't "fix" these as a drive-by in an unrelated change; steer *new* code to `useToast()` instead. | +| Local `useToast()` / `ToastContext` collision | `src/app/golf/admin/crm/components/Toast.tsx:29` (`ToastContext` at line 27) | **Name collision, not a duplicate.** Own React Context, own `toast(message, type)` API — completely separate from and incompatible with the canonical sonner-backed `useToast()`. Importing the wrong one in a golf/admin/crm file throws (`useToast must be used within a ToastProvider`) or silently no-ops depending on which is in scope. Pick by **import path**, not by name. | + +### Data access + +| Primitive | Location | Usage | Rule | +|---|---|---|---| +| `fromUntyped(client, table)` | `src/lib/supabase/untyped.ts:69` (`UntypedTable` allowlist union, lines 15-61) | 159 sites | Centralizes the `as any` escape hatch for tables not yet in generated `database.ts` types. Extend the `UntypedTable` union when adding a new hand-typed table module — don't scatter raw `as any` casts. Graduate a table off this list once `db:types` regen picks it up. | +| `createAdminClient()` | `src/lib/supabase/admin.ts:4` | 200 sites — most-used data-access primitive in the repo | Throws if `NEXT_PUBLIC_SUPABASE_URL` or `SUPABASE_SERVICE_ROLE_KEY` is missing/placeholder; returns a service-role client (`autoRefreshToken:false, persistSession:false`) that **bypasses RLS**. Greptile rule `service-role-scope` (`.greptile/config.json`, severity high) hard-blocks any use outside `src/lib/supabase/admin*` and `src/app/api/**/admin/**` as a security incident — not a style nit. | +| `fetchAllRows` / `fetchAllRowsResult` | `src/lib/supabase/fetch-all-rows.ts` — `fetchAllRows` (line 60, throws on error), `fetchAllRowsResult` (line 103, `{data,error}` shape) | 69 sites | `DEFAULT_PAGE_SIZE=1000` (line 34), loop bounded at 1000 pages as a circuit-breaker (line 74). Caller **must** supply a stable `.order()` on a unique column (lines 10-19) or page boundaries drift. Optional `rlsCtx` threads into `maybeCaptureRlsDenial` (lines 22-29). This is the fix for the PostgREST 1000-row-cap trap below — treat the two as one unit. | +| `todayIsoInTz(tz, now)` / `resolveTeamTimezone(...)` | `src/lib/baseball/daily-contract/contract-day.ts:57` and `:193` | — | Canonical timezone-resolution primitives for any calendar/today/streak feature. Part of the "daily contract" module backing Greptile rule `calendar-timezone-safety` (store UTC, display in team/user timezone, no naive `new Date()` day-boundaries). Don't hand-roll `new Date()` day-boundary math. | +| `getAppBaseUrl()` | `src/lib/app-base-url.ts:32` | — | Canonical way to resolve the app's own base URL (email links, share links) instead of hardcoding a host or reading `NEXT_PUBLIC_SITE_URL` directly at each call site. | + +### Design tokens + +**BaseballHelm — "Living Annual" (`src/styles/baseball-living-annual.css`)** + +`:root` vars (lines 30-66): `--paper #F3EAD6`, `--paper-canvas #EAE1CE`, +`--hairline #D6CBB0`, `--team-ink #16A34A`, `--pursuit-ink #C2703D`, +`--pursuit-deep #7A2E22`, `--notice-error-ink #C2703D` (see rationale +below), `--grade-low/avg/plus` (20-80 scouting scale), `--clay/--chalk/--sodium` +(reserved dark-viz canvas). 43 atom/molecule/viz components under +`src/components/baseball/living-annual/`. `.living-annual` scope (lines +78-87) re-points `--fw-color-*` to warm cream for baseball descendants +only — golf never carries this class. Color law (file header, lines +21-27): green = team/development, clay = recruiting/pursuit, oxblood = +seals/stamps **only**, sodium = single PR/live flash **sparingly**, dark +clay canvas **only** inside `` — never a page/sidebar/card +background. + +*`--notice-error-ink` rationale (lines 41-55):* three "sage & cream" +entry-scope stylesheets (`baseball-auth.css .baseball-auth-panel` and two +`onboarding-entry.css` files scoping `.entry-onboarding-scope`) redefine +**both** `--grade-plus` and `--pursuit-ink` to the same `--fl-sage-deep` — +so an error indicator reading `--pursuit-ink` directly on those surfaces +would render identically to a success indicator. `--notice-error-ink` is a +byte-identical-but-independently-named var (same `#C2703D` outside recolor +scopes) that none of the three recolor files override, so it survives +scoping. Consumed by ``, snapshot-card error variants +(`src/components/baseball/player-profile/snapshot-cards/shared.tsx:117,199`), +and join/onboarding error UI +(`src/app/baseball/join/[code]/page.tsx:170-174`, +`src/app/baseball/staff/join/[code]/page.tsx:73-77`). **Never** swap a +`--notice-error-ink` read back to `--pursuit-ink` "for consistency" — it +would silently break error/success distinguishability on exactly those 3 +recolor-scoped auth/onboarding surfaces. + +**GolfHelm — Fairway `fw-*` tokens (`src/styles/design-tokens.css`)** + +Header comment (line 20): "All design-system properties are prefixed +`--fw-` so they can NEVER collide." Ramps: `--fw-color-accent-50..900` +(helm green, `#16A34A` = accent-500), `--fw-color-team-mens/womens`, +`--fw-color-warm-50..950`, surface tier (`canvas/surface/surface-tint/surface-sunken/elevated`), +text (`text-primary/secondary/tertiary/on-accent/on-dark` — tertiary +darkened per a WCAG P422 fix, line 87, to clear 4.5:1 AA), +border/status/nav tokens, `--fw-radius-*`, `--fw-shadow-*`. Consumed in +`src/components/fairway/{calendar,instrument,overlays,surfaces}/*.css` and +animation vars `--fw-dur-*`/`--fw-ease-*` in `globals.css` (lines +1767-1845) with inline fallbacks (e.g. `var(--fw-dur-base, 280ms)`). This +is a **separate token layer** scoped via a `.fairway-ds` class, coexisting +with — not replacing — the `tailwind.config.ts` tokens described in +`CLAUDE.md`'s color-family list (`primary-*`/`destructive`/`warm-*`/`cream-*`). + +### Nav registries + +Data-driven for baseball/golf; component-inline for lifting/admin — a real +asymmetry, not an oversight to "fix" by inventing a lifting registry. + +| Product | Location | Shape | +|---|---|---| +| Baseball | `src/lib/baseball/nav-registry.ts` (1278 lines) | `BASEBALL_NAV_REGISTRY: readonly BaseballNavEntry[]` (line 330) + role/visibility helpers `isBaseballNavEntryVisible`/`getVisibleBaseballNav`/`getPrimaryBaseballNav`/`getSecondaryBaseballNav`/`getBaseballNavEntry`/`getBaseballDefaultLandingHref`/`getBaseballTerminology` (lines 1109-1276). | +| Golf | `src/lib/golf/nav-registry.ts` (460 lines) | `GOLF_COACH_HUBS`/`GOLF_PLAYER_HUBS` (lines 201, 231) + `buildCoachRailSections`/`buildPlayerRailSections`/`buildCoachBottomNavItems`/`buildPlayerBottomNavItems` (lines 304-460) + `resolveActiveGolfHub` + `COACHHELM_COACH_CLUSTER_PREFIXES`/`COACHHELM_PLAYER_CLUSTER_PREFIXES` (lines 125-152). | +| Lift Lab | `src/components/lifting/shell/LabNav.tsx` | Component-inline. **No `LIFTING_NAV_REGISTRY` module exists** — a new lifting route edits `LabNav.tsx` directly. | +| Admin | `src/app/admin/_components/admin-nav.ts` (+ `admin-nav.test.ts`) | Own small registry + test, separate from baseball/golf's. | + +### Error-boundary pattern + +**Shared:** `src/components/errors/RouteErrorBoundary.tsx` is the +canonical per-route `error.tsx` implementation — detects stale-deployment +chunk-load errors (`isChunkLoadError`, lines 35-43), stale-server-action +errors (via `isStaleServerActionError`/`softReloadForStaleServerAction` +shared with `src/lib/error-logging` so global `unhandledrejection` +handlers and this boundary share one regex + one reload-once-per-session +guard), and transient/retryable errors (`isTransientError`, 503/502/504) +with optional auto-retry. 157 route-level `error.tsx` files repo-wide, each +a thin `'use client'` wrapper importing `RouteErrorBoundary` and passing +`{route, component, title, message, homePath}`. Root tier: `src/app/error.tsx`, +`src/app/global-error.tsx`. New routes should add a 4-line `error.tsx` +delegating to `RouteErrorBoundary` with route-specific copy — don't +hand-roll new boundary logic. + +**Admin has a 3rd, panel-scoped tier + a documented boundary-scope gap:** +`src/app/admin/error.tsx` wraps `RouteErrorBoundary` (reusing golf's +Fairway-tokened boundary per its own comment), but: + +1. Every Bridge panel already degrades via a **separate**, finer-grained + `PanelBoundary` (`src/app/admin/_components/PanelBoundary.tsx`) — one + upstream hiccup scopes to a single amber card, not the whole console. +2. **Documented, unfixed gap:** `error.tsx` does **not** catch throws in + `layout.tsx` of the *same* folder (`src/app/admin/layout.tsx`) — only in + nested layouts/pages below it. A genuine failure in + `checkSuperAdminAccess()` at the layout level still bubbles to the root + `src/app/error.tsx` (non-Bridge chrome). Known follow-up, not a bug to + silently "fix" in an unrelated change. +3. `src/app/golf/admin/components/AdminErrorBoundary.tsx` is an **older + class-component** boundary (`Component`/`componentDidCatch`, lines + 1-40) that predates and coexists with `RouteErrorBoundary` — a + genuinely different mechanism (React class boundary vs. Next.js + `error.tsx` file convention), not just a naming variant. + +--- + +## 3. Traps + +Seven traps found with concrete repo evidence (not assumed from memory). + +1. **Schema-drift — migration recorded-but-not-applied.** + `docs/audits/REPO_UNTANGLE_AND_CLEAN_BASE.md:128`: Supabase's + migration-history table can show a migration as applied without the DDL + having actually run against the target DB (or vice versa). Never trust + the history table alone before writing code that assumes a + column/table exists — run a live `information_schema.columns`/`.tables` + check first. + +2. **Matview/view/function recreate re-grants `anon`.** + `supabase/migrations/20260617000100_crm_engagement_invert_clicks.sql:79-85`: + a fresh `CREATE MATERIALIZED VIEW` in the `public` schema re-triggers + Supabase's `ALTER DEFAULT PRIVILEGES`, auto-granting `ALL` to `anon` + + `authenticated`. Must be followed by explicit `REVOKE ALL ... FROM anon` + (and `FROM authenticated` where the original ACL excluded it) in the + **same migration**. Generalizes beyond matviews to `CREATE OR REPLACE + VIEW` (`20260709010000_baseball_public_views_honor_visibility.sql:90,158`) + and `CREATE OR REPLACE FUNCTION` + (`20260701150000_run_integrity_checks_rpc.sql:56`, + `20260704130000_integrity_check_admin_readability_tripwire.sql:71`). A + dedicated backfill wave exists: + `supabase/migrations/20260625000050_baseball_anon_revoke_wave1.sql`. + Verify via `pg_class.relacl` / `has_table_privilege` after — not just by + reading the DDL. + +3. **PostgREST 1000-row cap silently truncates unpaginated queries.** + `src/lib/supabase/fetch-all-rows.ts:1-8`: PostgREST returns at most + 1000 rows per request by default; an unpaginated `.in('round_id', + roundIds)` on `golf_holes`/`golf_shots` silently truncates once a team + season exceeds 1000 rows, undercounting putts/GIR%/fairway%. `.limit(N)` + does **not** lift this cap — only `range()`-based pagination does. Any + new aggregate/analytics query that can plausibly exceed 1000 rows over a + season must go through `fetchAllRowsResult` with a stable `.order('id')`. + +4. **Glob-shaped token inside a CSS/JS block comment prematurely closes it.** + `docs/audits/REPO_UNTANGLE_AND_CLEAN_BASE.md:101` (real, shipped + incident, PR #650): `src/app/globals.css` lines 2-4 contained comment + text `--pursuit-*/` — the literal `*/` terminated the block comment + early, causing a postcss syntax error at 3:42 and a broken Next build. + Any `'*/'` substring closes a CSS/JS block comment regardless of prose + context. Fixed (verified current) by rewriting to `--pursuit-*,` (comma, + not slash). When writing doc comments that list namespaced + custom-property prefixes ending in `*` (`--fw-*`, `--pursuit-*`), never + let a `/` immediately follow the `*` inside a block comment. + +5. **Vercel one-deploy-per-milestone gate (Ignored Build Step).** + `vercel.json:6-11`: `"ignoreCommand": "bash scripts/vercel-ignore-build.sh"`, + `"git": {"deploymentEnabled": {"*": false}}` — git-triggered deploys are + disabled for every branch at the platform level. + `scripts/vercel-ignore-build.sh` skips every branch except `main` (Vercel + semantics: exit 0 = skip, exit 1 = proceed), fail-opening to building + when `VERCEL_ENV=production` even with an empty + `VERCEL_GIT_COMMIT_REF`. Vercel preview builds being off is **by + design** (cost control) — a red/skipped Vercel preview check on a PR is + expected, not a CI failure to chase. `docs/audits/REPO_UNTANGLE_AND_CLEAN_BASE.md:96` + notes CircleCI's `lighthouse-preview` job is red only because of this, + an easy misdiagnosis. Flipping the Ignored Build Step back on is a + manual, owner-gated step (line 127) — easy to forget, don't do it + unprompted. + +6. **Auth/session hooks stranding `loading: true` forever on unexpected throw.** + `src/hooks/use-auth.ts:12-28`: with multiple independent `useAuth()` + callers each running their own `fetchUser()`/`onAuthStateChange` against + a shared flag, mount-guard early-returns could skip `setLoading(false)`, + stranding `loading=true` forever (page stuck on skeleton). Fixed via one + deduped module-level fetch + subscription (`inFlight`/`subscriberCount`/ + `authSubscription`, lines 31-33) where loading is **always** resolved in + a `finally`. Same bug class independently fixed in + `src/hooks/use-baseball-auth.ts:132-140` (falls back to `{ok:false, + redirectTo:'/baseball/login'}` instead of hanging). Any new + session/auth-resolving hook must resolve loading/pending state on + **every** code path including unexpected throws (try/catch or + `.finally`) — an unguarded exception mid-hook leaves callers on an + infinite loading skeleton, not a visible error. + +7. **`noUncheckedIndexedAccess` non-null-assertion fallback idiom.** + `tsconfig.json:21`: `"noUncheckedIndexedAccess": true` — every `arr[i]` + read types as `T | undefined`, not `T`. Canonical fallback is a + documented, guard-then-assert `!`, **not** a silent `?? fallback`: + `src/lib/golf/nav-registry.ts:322,328,335,341,348,389,427,430` + (`team.tabs[0]!.href`), `src/lib/golf/strokes-gained.ts:122` + (`distances[0]!`), `src/lib/golf/progress-drivers.ts:145` + (`goals[0]!.player_id // non-empty (guarded above)`). Convention + confirmed in `docs/archive/2026-06/superpowers/plans/2026-06-07-coachhelm-to-90.md:927`. + When `noUncheckedIndexedAccess` forces a `T | undefined` at an index + you've already proven non-empty (length check, `.filter`, upstream + invariant), assert with `!` **and** leave a one-line comment naming the + guard. Do not silently swap in `?? someDefault` — that masks a real + off-by-one/empty-array bug instead of surfacing it as a thrown error. + +--- + +## 4. Before you write code — checklist + +- [ ] **Feature awareness first.** Run `npm run knowledge:map -- --files + ` and read the mapped `memory/context/*.md` / + `memory/features/*.md` docs before touching mapped code (per + `AGENTS.md`). This map doc is structural, not a substitute for that. +- [ ] **Route placement.** Confirm which product tree the route belongs + in (§1) and which route group (`(auth)`/`(onboarding)`/`(dashboard)`/ + `(player-dashboard)`/`(public)`) matches its access model. Adding a + lifting route: add a sibling `error.tsx` — the product-wide gap means + it won't inherit one. +- [ ] **Mutations.** Use the sport-appropriate wrapper + (`withBaseballAction`/`withLiftingAction`) for auth+capability+Sentry + scoping. Layer `withAdminObserved` on top only if the action also + needs `admin_events` soft-failure capture — don't pick one instead + of the other where both apply. +- [ ] **Toasts.** Import `useToast` from `@/components/ui/sonner` by path, + not by name — a same-named, incompatible `useToast` exists at + `src/app/golf/admin/crm/components/Toast.tsx`. +- [ ] **Data access.** New hand-typed table → extend `UntypedTable` in + `src/lib/supabase/untyped.ts`, don't scatter `as any`. + `createAdminClient()` only inside `src/lib/supabase/admin*` or + `src/app/api/**/admin/**` — anywhere else is a Greptile-blocked + security finding. Any query that can exceed 1000 rows over a season + → `fetchAllRowsResult` with a stable `.order()`, never a bare + `.select()`/`.in()`. Day-boundary logic → `todayIsoInTz`/ + `resolveTeamTimezone`, never naive `new Date()`. Own-origin URLs → + `getAppBaseUrl()`, never a hardcoded host. +- [ ] **Design tokens.** Baseball surfaces under `.living-annual` scope → + Living Annual vars per the color law (§2); never read + `--pursuit-ink` where `--notice-error-ink` is the correct var for an + error state. Golf/Fairway surfaces → `--fw-*` tokens, not + `tailwind.config.ts` tokens directly, inside `.fairway-ds` scope. +- [ ] **Nav.** Baseball/golf → update the data-driven registry + (`nav-registry.ts`). Lifting → edit `LabNav.tsx` directly, there is + no registry module to update. +- [ ] **Error boundaries.** New route → thin `error.tsx` delegating to + `RouteErrorBoundary`. New admin panel → also consider `PanelBoundary` + for panel-scoped degradation. Don't assume `/admin`'s `error.tsx` + catches a `layout.tsx`-level throw in the same folder — it doesn't. +- [ ] **Migrations.** Any `CREATE MATERIALIZED VIEW` / `CREATE OR REPLACE + VIEW` / `CREATE OR REPLACE FUNCTION` in `public` → add `REVOKE ALL + ... FROM anon` (and `authenticated` as needed) in the **same** + migration; verify with `pg_class.relacl`, not by reading the DDL. + Verify a migration actually ran via `information_schema`, not the + migration-history table. +- [ ] **CSS/JS comments.** Never let a namespaced-prefix-plus-`*` token + (`--fw-*`, `--pursuit-*`) sit immediately before a `/` inside a + block comment. +- [ ] **`arr[i]!`.** Only assert past `noUncheckedIndexedAccess` when + you've proven the index is safe, and say why in a comment. +- [ ] **Vercel.** A red/skipped Vercel preview check is expected (deploys + are intentionally off for non-`main` branches) — don't chase it as a + CI failure, and don't flip the Ignored Build Step yourself. +- [ ] **Session/auth hooks.** Resolve `loading`/pending state in a + `finally` (or equivalent) on every code path, including unexpected + throws. diff --git a/docs/ROUND_REVIEW_ACCURACY_REPORT.md b/docs/ROUND_REVIEW_ACCURACY_REPORT.md index 1f13e2985..3d9ac772f 100644 --- a/docs/ROUND_REVIEW_ACCURACY_REPORT.md +++ b/docs/ROUND_REVIEW_ACCURACY_REPORT.md @@ -1,3 +1,10 @@ + + # Round Review Accuracy & Enhancement Report > **Date:** February 2026 diff --git a/docs/SECURITY_AUDIT.md b/docs/SECURITY_AUDIT.md index e3c668f54..2abf14fc6 100644 --- a/docs/SECURITY_AUDIT.md +++ b/docs/SECURITY_AUDIT.md @@ -1,3 +1,10 @@ + + # Security Audit Report - Helm Sports Labs (GolfHelm iOS App) **Date:** 2026-03-05 diff --git a/docs/SESSION_REPORT_2026-05-27_PART2.md b/docs/SESSION_REPORT_2026-05-27_PART2.md index 9b8259e6e..653fcec1e 100644 --- a/docs/SESSION_REPORT_2026-05-27_PART2.md +++ b/docs/SESSION_REPORT_2026-05-27_PART2.md @@ -1,3 +1,10 @@ + + # Session Report — 2026-05-27 (Part 2: Codex Security review + cleanup) This continues `docs/SESSION_REPORT_2026-05-27.md`. Read that first if you want the full backstory; this file captures everything that happened **after** Codex Security ran its own review and produced `docs/security/CODEX_SECURITY_RELEASE_RESCUE_PLAN_2026-05-27.md`. diff --git a/docs/SHOT_TRACKING_DEEP_SCRUB.md b/docs/SHOT_TRACKING_DEEP_SCRUB.md index b98b314c1..bc9eb845f 100644 --- a/docs/SHOT_TRACKING_DEEP_SCRUB.md +++ b/docs/SHOT_TRACKING_DEEP_SCRUB.md @@ -1,3 +1,10 @@ + + # Shot Tracking System — Deep Scrub Report > Compiled from live production database queries + full codebase trace. March 14, 2026. diff --git a/docs/UIUX_AUDIT.md b/docs/UIUX_AUDIT.md index f70b244ef..71526a39c 100644 --- a/docs/UIUX_AUDIT.md +++ b/docs/UIUX_AUDIT.md @@ -1,3 +1,10 @@ + + # UI/UX Audit for App Store Compliance & iOS Mobile Quality **Date:** 2026-03-05 diff --git a/docs/UI_POLISH_REPORT.md b/docs/UI_POLISH_REPORT.md index 9c64d1749..be1708ace 100644 --- a/docs/UI_POLISH_REPORT.md +++ b/docs/UI_POLISH_REPORT.md @@ -1,3 +1,10 @@ + + # UI Polish Report > **Generated**: 2026-02-17 00:15 EST diff --git a/docs/architectural-review-golfhelm-2026-02-22.md b/docs/architectural-review-golfhelm-2026-02-22.md index 29cb343f9..5b73f7409 100644 --- a/docs/architectural-review-golfhelm-2026-02-22.md +++ b/docs/architectural-review-golfhelm-2026-02-22.md @@ -1,3 +1,10 @@ + + # GolfHelm Architectural Review **Date:** 2026-02-22 diff --git a/docs/architecture/COMPREHENSIVE_AUTH_SYSTEM_PLAN.md b/docs/architecture/COMPREHENSIVE_AUTH_SYSTEM_PLAN.md index 03d02d778..318f31e4f 100644 --- a/docs/architecture/COMPREHENSIVE_AUTH_SYSTEM_PLAN.md +++ b/docs/architecture/COMPREHENSIVE_AUTH_SYSTEM_PLAN.md @@ -1,3 +1,10 @@ + + # COMPREHENSIVE AUTH SYSTEM ANALYSIS & FIX PLAN **Created:** 2026-01-01 diff --git a/docs/architecture/USER_ROLE_DATA_OWNERSHIP.md b/docs/architecture/USER_ROLE_DATA_OWNERSHIP.md index d7c4a94ed..ccda16c6f 100644 --- a/docs/architecture/USER_ROLE_DATA_OWNERSHIP.md +++ b/docs/architecture/USER_ROLE_DATA_OWNERSHIP.md @@ -1,3 +1,10 @@ + + # USER ROLE & DATA OWNERSHIP MAPPING > PHASE 2 AUDIT REPORT > Generated: 2026-01-01 diff --git a/docs/audit-college-coach-2026-02-22.md b/docs/audit-college-coach-2026-02-22.md index 78d82f564..e3d7e1d6e 100644 --- a/docs/audit-college-coach-2026-02-22.md +++ b/docs/audit-college-coach-2026-02-22.md @@ -1,3 +1,10 @@ + + # BaseballHelm College Coach Dashboard — Full Audit Date: 2026-02-22 diff --git a/docs/audit-db-security-2026-02-22.md b/docs/audit-db-security-2026-02-22.md index 1efaa56c7..15b1ca538 100644 --- a/docs/audit-db-security-2026-02-22.md +++ b/docs/audit-db-security-2026-02-22.md @@ -1,3 +1,10 @@ + + # BaseballHelm DB & Security Audit **Date:** 2026-02-22 **Auditor:** DB Security Engineer (subagent) diff --git a/docs/audit-errors-flows-2026-02-22.md b/docs/audit-errors-flows-2026-02-22.md index d5e60ecbb..3e0746989 100644 --- a/docs/audit-errors-flows-2026-02-22.md +++ b/docs/audit-errors-flows-2026-02-22.md @@ -1,3 +1,10 @@ + + # BaseballHelm — Error & Flow Audit Date: 2026-02-22 Auditor: QA Sub-Agent (automated trace) diff --git a/docs/audit-ui-premium-2026-02-22.md b/docs/audit-ui-premium-2026-02-22.md index 78f679ce7..76e8c3a7a 100644 --- a/docs/audit-ui-premium-2026-02-22.md +++ b/docs/audit-ui-premium-2026-02-22.md @@ -1,3 +1,10 @@ + + # BaseballHelm College Coach — UI/Premium Design Audit Date: 2026-02-22 diff --git a/docs/audits/COACHHELM_TO_95_AUDIT_2026-06-08.md b/docs/audits/COACHHELM_TO_95_AUDIT_2026-06-08.md index 7752d0f5c..4cc33716c 100644 --- a/docs/audits/COACHHELM_TO_95_AUDIT_2026-06-08.md +++ b/docs/audits/COACHHELM_TO_95_AUDIT_2026-06-08.md @@ -1,3 +1,10 @@ + + # CoachHelm 55-to-95 Audit Date: 2026-06-08 diff --git a/docs/audits/COACH_DASHBOARD_AUDIT_REPORT.md b/docs/audits/COACH_DASHBOARD_AUDIT_REPORT.md index aef646e35..c8540c2fe 100644 --- a/docs/audits/COACH_DASHBOARD_AUDIT_REPORT.md +++ b/docs/audits/COACH_DASHBOARD_AUDIT_REPORT.md @@ -1,3 +1,10 @@ + + # BaseballHelm Coach Dashboard - Production Readiness Audit Report > **Audit Date:** January 25, 2026 diff --git a/docs/audits/COURSE_LIBRARY_AUDIT_2026-06-13.md b/docs/audits/COURSE_LIBRARY_AUDIT_2026-06-13.md index 6e8673ee8..99e0fd3a3 100644 --- a/docs/audits/COURSE_LIBRARY_AUDIT_2026-06-13.md +++ b/docs/audits/COURSE_LIBRARY_AUDIT_2026-06-13.md @@ -1,3 +1,10 @@ + + # GolfHelm Cloud Course Library — System Audit (2026-06-13) > Produced by a 7-agent parallel audit of the live repo before any implementation. diff --git a/docs/audits/DATA_INTEGRITY_AUDIT.md b/docs/audits/DATA_INTEGRITY_AUDIT.md index fa34c03f5..9a83cf56c 100644 --- a/docs/audits/DATA_INTEGRITY_AUDIT.md +++ b/docs/audits/DATA_INTEGRITY_AUDIT.md @@ -1,3 +1,10 @@ + + # PHASE 4: DATA INTEGRITY & FOREIGN KEY AUDIT **Generated:** 2026-01-01 diff --git a/docs/audits/GOLFHELM_CALENDAR_AUDIT_2026-06-10.md b/docs/audits/GOLFHELM_CALENDAR_AUDIT_2026-06-10.md index 2bf4aff3d..0c997bd04 100644 --- a/docs/audits/GOLFHELM_CALENDAR_AUDIT_2026-06-10.md +++ b/docs/audits/GOLFHELM_CALENDAR_AUDIT_2026-06-10.md @@ -1,3 +1,10 @@ + + # GolfHelm Shared Calendar Audit — 20/100 ## Verdict diff --git a/docs/audits/GOLFHELM_E2E_TAB_AUDIT_2026-06-20.md b/docs/audits/GOLFHELM_E2E_TAB_AUDIT_2026-06-20.md index 0b32fb19d..f5ce6f85e 100644 --- a/docs/audits/GOLFHELM_E2E_TAB_AUDIT_2026-06-20.md +++ b/docs/audits/GOLFHELM_E2E_TAB_AUDIT_2026-06-20.md @@ -1,8 +1,15 @@ + + # GolfHelm End-to-End Tab Audit — Master Report **Date:** 2026-06-20 **Scope:** 36 GolfHelm tab/feature units, each traced UI → server action → database, on the **live Fairway redesign** path (`NEXT_PUBLIC_REDESIGN=true`, prod-promoted from `main`). -**Per-unit detail:** `docs/audits/_e2e_tab_audit_2026-06-20/` (one md per unit, linked throughout). +**Per-unit detail:** `docs/archive/2026-06/audits/_e2e_tab_audit_2026-06-20/` (one md per unit, linked throughout). ## Executive summary @@ -54,42 +61,42 @@ Across 36 units we filed **204 findings**: **11 CRITICAL, 25 HIGH, 59 MEDIUM, 61 | Unit | Role | C | H | M | L | I | Section | |---|---|--:|--:|--:|--:|--:|---| -| Coach Dashboard home | coach | 0 | 0 | 2 | 2 | 1 | [coach-home.md](_e2e_tab_audit_2026-06-20/coach-home.md) | -| CoachHelm AI / Intelligence hub + Chat | coach | 0 | 1 | 1 | 1 | 2 | [coachhelm-hub.md](_e2e_tab_audit_2026-06-20/coachhelm-hub.md) | -| Insights | coach | 0 | 0 | 3 | 2 | 2 | [coachhelm-insights.md](_e2e_tab_audit_2026-06-20/coachhelm-insights.md) | -| Alerts | coach | 0 | 1 | 2 | 1 | 1 | [coachhelm-alerts.md](_e2e_tab_audit_2026-06-20/coachhelm-alerts.md) | -| Patterns | coach | 2 | 1 | 2 | 1 | 2 | [coachhelm-patterns.md](_e2e_tab_audit_2026-06-20/coachhelm-patterns.md) | -| CoachHelm Analytics | coach | 0 | 0 | 3 | 1 | 2 | [coachhelm-analytics.md](_e2e_tab_audit_2026-06-20/coachhelm-analytics.md) | -| Coaching Intelligence Settings | coach | 0 | 0 | 4 | 2 | 0 | [coaching-settings.md](_e2e_tab_audit_2026-06-20/coaching-settings.md) | -| Development Plans (coach) | coach | 0 | 1 | 3 | 2 | 0 | [development-plans.md](_e2e_tab_audit_2026-06-20/development-plans.md) | -| Recruiting HQ | coach | 0 | 1 | 1 | 1 | 0 | [recruiting-hq.md](_e2e_tab_audit_2026-06-20/recruiting-hq.md) | -| Qualifiers (create/manage) | coach | 0 | 1 | 0 | 3 | 1 | [qualifiers-coach.md](_e2e_tab_audit_2026-06-20/qualifiers-coach.md) | -| Team Stats | coach | 0 | 0 | 1 | 2 | 2 | [team-stats.md](_e2e_tab_audit_2026-06-20/team-stats.md) | -| Player detail / game view (coach) | coach | 0 | 0 | 1 | 2 | 1 | [player-detail-coach.md](_e2e_tab_audit_2026-06-20/player-detail-coach.md) | -| Player Dashboard home + Hub | player | 0 | 1 | 0 | 1 | 3 | [player-home.md](_e2e_tab_audit_2026-06-20/player-home.md) | -| Player CoachHelm | player | 0 | 2 | 1 | 1 | 1 | [player-coachhelm.md](_e2e_tab_audit_2026-06-20/player-coachhelm.md) | -| CoachHelm Genome + Compare | player | 0 | 0 | 1 | 2 | 1 | [coachhelm-genome.md](_e2e_tab_audit_2026-06-20/coachhelm-genome.md) | -| CoachHelm Qualifying predictions | player | 0 | 0 | 0 | 1 | 2 | [coachhelm-qualifying.md](_e2e_tab_audit_2026-06-20/coachhelm-qualifying.md) | -| My Development (player) | player | 1 | 0 | 3 | 1 | 1 | [my-development.md](_e2e_tab_audit_2026-06-20/my-development.md) | -| Round create / continue / recover | player | 0 | 0 | 1 | 2 | 2 | [round-create.md](_e2e_tab_audit_2026-06-20/round-create.md) | -| Round Review | player | 0 | 0 | 2 | 1 | 1 | [round-review.md](_e2e_tab_audit_2026-06-20/round-review.md) | -| My Qualifiers (player) | player | 0 | 0 | 1 | 3 | 1 | [my-qualifiers.md](_e2e_tab_audit_2026-06-20/my-qualifiers.md) | -| Classes | player | 0 | 2 | 2 | 1 | 2 | [classes.md](_e2e_tab_audit_2026-06-20/classes.md) | -| My Game Profile + My Standing | player | 1 | 1 | 0 | 2 | 2 | [player-profile-self.md](_e2e_tab_audit_2026-06-20/player-profile-self.md) | -| Rounds list | both | 0 | 0 | 2 | 2 | 1 | [rounds-list.md](_e2e_tab_audit_2026-06-20/rounds-list.md) | -| Calendar & Events | both | 0 | 1 | 3 | 2 | 2 | [calendar.md](_e2e_tab_audit_2026-06-20/calendar.md) | -| Roster + member detail | both | 1 | 1 | 1 | 2 | 0 | [roster.md](_e2e_tab_audit_2026-06-20/roster.md) | -| Messaging | both | 1 | 1 | 2 | 2 | 1 | [messages.md](_e2e_tab_audit_2026-06-20/messages.md) | -| Announcements | both | 0 | 0 | 1 | 3 | 2 | [announcements.md](_e2e_tab_audit_2026-06-20/announcements.md) | -| Tasks | both | 2 | 3 | 3 | 1 | 1 | [tasks.md](_e2e_tab_audit_2026-06-20/tasks.md) | -| Team Hub (player) + Team Info | both | 0 | 1 | 0 | 2 | 2 | [team-hub-and-info.md](_e2e_tab_audit_2026-06-20/team-hub-and-info.md) | -| Documents + Travel | both | 1 | 2 | 2 | 1 | 2 | [docs-travel.md](_e2e_tab_audit_2026-06-20/docs-travel.md) | -| Stats (personal) | both | 0 | 0 | 0 | 1 | 2 | [stats-personal.md](_e2e_tab_audit_2026-06-20/stats-personal.md) | -| Course Library + What's New | both | 0 | 0 | 1 | 3 | 0 | [courses-whatsnew.md](_e2e_tab_audit_2026-06-20/courses-whatsnew.md) | -| Settings + Notifications | both | 2 | 1 | 3 | 2 | 1 | [settings-core.md](_e2e_tab_audit_2026-06-20/settings-core.md) | -| Auth (login/signup/forgot/reset/demo) | both | 0 | 1 | 2 | 2 | 1 | [auth-flows.md](_e2e_tab_audit_2026-06-20/auth-flows.md) | -| Onboarding (coach + player) | both | 0 | 2 | 3 | 2 | 1 | [onboarding.md](_e2e_tab_audit_2026-06-20/onboarding.md) | -| Join team by code | both | 0 | 0 | 2 | 1 | 2 | [join-team.md](_e2e_tab_audit_2026-06-20/join-team.md) | +| Coach Dashboard home | coach | 0 | 0 | 2 | 2 | 1 | [coach-home.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coach-home.md) | +| CoachHelm AI / Intelligence hub + Chat | coach | 0 | 1 | 1 | 1 | 2 | [coachhelm-hub.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-hub.md) | +| Insights | coach | 0 | 0 | 3 | 2 | 2 | [coachhelm-insights.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-insights.md) | +| Alerts | coach | 0 | 1 | 2 | 1 | 1 | [coachhelm-alerts.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-alerts.md) | +| Patterns | coach | 2 | 1 | 2 | 1 | 2 | [coachhelm-patterns.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-patterns.md) | +| CoachHelm Analytics | coach | 0 | 0 | 3 | 1 | 2 | [coachhelm-analytics.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-analytics.md) | +| Coaching Intelligence Settings | coach | 0 | 0 | 4 | 2 | 0 | [coaching-settings.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coaching-settings.md) | +| Development Plans (coach) | coach | 0 | 1 | 3 | 2 | 0 | [development-plans.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/development-plans.md) | +| Recruiting HQ | coach | 0 | 1 | 1 | 1 | 0 | [recruiting-hq.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/recruiting-hq.md) | +| Qualifiers (create/manage) | coach | 0 | 1 | 0 | 3 | 1 | [qualifiers-coach.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/qualifiers-coach.md) | +| Team Stats | coach | 0 | 0 | 1 | 2 | 2 | [team-stats.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/team-stats.md) | +| Player detail / game view (coach) | coach | 0 | 0 | 1 | 2 | 1 | [player-detail-coach.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/player-detail-coach.md) | +| Player Dashboard home + Hub | player | 0 | 1 | 0 | 1 | 3 | [player-home.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/player-home.md) | +| Player CoachHelm | player | 0 | 2 | 1 | 1 | 1 | [player-coachhelm.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/player-coachhelm.md) | +| CoachHelm Genome + Compare | player | 0 | 0 | 1 | 2 | 1 | [coachhelm-genome.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-genome.md) | +| CoachHelm Qualifying predictions | player | 0 | 0 | 0 | 1 | 2 | [coachhelm-qualifying.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-qualifying.md) | +| My Development (player) | player | 1 | 0 | 3 | 1 | 1 | [my-development.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/my-development.md) | +| Round create / continue / recover | player | 0 | 0 | 1 | 2 | 2 | [round-create.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/round-create.md) | +| Round Review | player | 0 | 0 | 2 | 1 | 1 | [round-review.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/round-review.md) | +| My Qualifiers (player) | player | 0 | 0 | 1 | 3 | 1 | [my-qualifiers.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/my-qualifiers.md) | +| Classes | player | 0 | 2 | 2 | 1 | 2 | [classes.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/classes.md) | +| My Game Profile + My Standing | player | 1 | 1 | 0 | 2 | 2 | [player-profile-self.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/player-profile-self.md) | +| Rounds list | both | 0 | 0 | 2 | 2 | 1 | [rounds-list.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/rounds-list.md) | +| Calendar & Events | both | 0 | 1 | 3 | 2 | 2 | [calendar.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/calendar.md) | +| Roster + member detail | both | 1 | 1 | 1 | 2 | 0 | [roster.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/roster.md) | +| Messaging | both | 1 | 1 | 2 | 2 | 1 | [messages.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/messages.md) | +| Announcements | both | 0 | 0 | 1 | 3 | 2 | [announcements.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/announcements.md) | +| Tasks | both | 2 | 3 | 3 | 1 | 1 | [tasks.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/tasks.md) | +| Team Hub (player) + Team Info | both | 0 | 1 | 0 | 2 | 2 | [team-hub-and-info.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/team-hub-and-info.md) | +| Documents + Travel | both | 1 | 2 | 2 | 1 | 2 | [docs-travel.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/docs-travel.md) | +| Stats (personal) | both | 0 | 0 | 0 | 1 | 2 | [stats-personal.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/stats-personal.md) | +| Course Library + What's New | both | 0 | 0 | 1 | 3 | 0 | [courses-whatsnew.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/courses-whatsnew.md) | +| Settings + Notifications | both | 2 | 1 | 3 | 2 | 1 | [settings-core.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/settings-core.md) | +| Auth (login/signup/forgot/reset/demo) | both | 0 | 1 | 2 | 2 | 1 | [auth-flows.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/auth-flows.md) | +| Onboarding (coach + player) | both | 0 | 2 | 3 | 2 | 1 | [onboarding.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/onboarding.md) | +| Join team by code | both | 0 | 0 | 2 | 1 | 2 | [join-team.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/join-team.md) | | **Totals (36 units)** | | **11** | **25** | **59** | **61** | **48** | | Per-role rollup: **coach** = 12 units (2C/6H), **player** = 10 units (2C/6H), **shared (both)** = 14 units (7C/13H). @@ -192,52 +199,52 @@ The following confirmed CRITICAL/HIGH findings are flagged `needsLiveVerify=true ### Coach tabs -- **Coach Dashboard home** — [coach-home.md](_e2e_tab_audit_2026-06-20/coach-home.md). Clean role-gate, honest KPIs, auth-first data fetch, paginated rounds. Redesign drops the legacy Today-timeline + Action-Items regions (data computed, never rendered) and per-KPI trend arrows/sparklines (MEDIUM×2); JoinRequest banner has no realtime (LOW). No C/H. -- **CoachHelm AI / Intelligence hub + Chat** — [coachhelm-hub.md](_e2e_tab_audit_2026-06-20/coachhelm-hub.md). Real Sonnet tool-loop agent, coach-only mount, RLS-scoped persistence. **HIGH:** `create_goal_for_player` has no UI confirm gate despite the code claiming the UI is the real safety fence. -- **Insights** — [coachhelm-insights.md](_e2e_tab_audit_2026-06-20/coachhelm-insights.md). RLS+v3-visibility scoped, paginated, optimistic with rollback. MEDIUMs: stats-vs-list scope divergence in multi-coach programs, absent error state, dead create-focus-area path. No C/H. -- **Alerts** — [coachhelm-alerts.md](_e2e_tab_audit_2026-06-20/coachhelm-alerts.md). **HIGH:** default `urgent` preset never matches mapped `critical`, hiding top alerts. MEDIUMs: unreachable bulk-action bar (no selection control), badge↔feed count mismatch. -- **Patterns** — [coachhelm-patterns.md](_e2e_tab_audit_2026-06-20/coachhelm-patterns.md). **2 CRITICAL / 1 HIGH:** Validate + Address write non-existent columns → 42703 atomic failure (live-reproduced); Resolve latent. Plus silent optimistic rollback masking the failure (MEDIUM) and read-side dead controls. -- **CoachHelm Analytics** — [coachhelm-analytics.md](_e2e_tab_audit_2026-06-20/coachhelm-analytics.md). Real rollups (daily cron), honesty thresholds, no mocks. MEDIUMs: frozen 30-day summary cards vs range-aware panels, two divergent prediction-accuracy numbers, no SSR error state. No C/H. -- **Coaching Intelligence Settings** — [coaching-settings.md](_e2e_tab_audit_2026-06-20/coaching-settings.md). Priorities/sensitivity/thresholds/master-switch/SG-baseline genuinely take effect. MEDIUMs: 11 alert toggles, weight sliders, display prefs, and (LOW) bubble-zone are write-only dead controls; no player self-gate (infinite skeleton). No C/H. -- **Development Plans (coach)** — [development-plans.md](_e2e_tab_audit_2026-06-20/development-plans.md). **HIGH:** outcome-mix hero stuck on "Awaiting outcomes" (reads a non-existent/unselected field). MEDIUMs: provenance chips + per-area sparkline never render (columns not selected); `updateFocusArea` silent 0-row no-op for multi-coach. -- **Recruiting HQ** — [recruiting-hq.md](_e2e_tab_audit_2026-06-20/recruiting-hq.md). Strong RLS, private bucket, signed downloads, orphan rollback. **HIGH:** 2 MB Server Action body cap silently kills 2–25 MB uploads vs the advertised 25 MB. MEDIUM: async `window.open` download popup-blocked on Safari. -- **Qualifiers (create/manage)** — [qualifiers-coach.md](_e2e_tab_audit_2026-06-20/qualifiers-coach.md). Create/entries/leaderboard/RLS all correct. **HIGH:** `updateQualifierStatus` has no caller — qualifiers can never advance or close. -- **Team Stats** — [team-stats.md](_e2e_tab_audit_2026-06-20/team-stats.md). Correctly wired; prior FW/GIR/Putts bugs appear remediated. MEDIUM: un-chunked `.in(roundIds)` URL-length risk at scale. No C/H. -- **Player detail / game view (coach)** — [player-detail-coach.md](_e2e_tab_audit_2026-06-20/player-detail-coach.md). Honest sparse/awaiting states, paginated insights. MEDIUM: `/game`+`/print` use any-staffed-team access while the base page uses active-team — inconsistent scoping (not a leak). No C/H. +- **Coach Dashboard home** — [coach-home.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coach-home.md). Clean role-gate, honest KPIs, auth-first data fetch, paginated rounds. Redesign drops the legacy Today-timeline + Action-Items regions (data computed, never rendered) and per-KPI trend arrows/sparklines (MEDIUM×2); JoinRequest banner has no realtime (LOW). No C/H. +- **CoachHelm AI / Intelligence hub + Chat** — [coachhelm-hub.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-hub.md). Real Sonnet tool-loop agent, coach-only mount, RLS-scoped persistence. **HIGH:** `create_goal_for_player` has no UI confirm gate despite the code claiming the UI is the real safety fence. +- **Insights** — [coachhelm-insights.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-insights.md). RLS+v3-visibility scoped, paginated, optimistic with rollback. MEDIUMs: stats-vs-list scope divergence in multi-coach programs, absent error state, dead create-focus-area path. No C/H. +- **Alerts** — [coachhelm-alerts.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-alerts.md). **HIGH:** default `urgent` preset never matches mapped `critical`, hiding top alerts. MEDIUMs: unreachable bulk-action bar (no selection control), badge↔feed count mismatch. +- **Patterns** — [coachhelm-patterns.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-patterns.md). **2 CRITICAL / 1 HIGH:** Validate + Address write non-existent columns → 42703 atomic failure (live-reproduced); Resolve latent. Plus silent optimistic rollback masking the failure (MEDIUM) and read-side dead controls. +- **CoachHelm Analytics** — [coachhelm-analytics.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-analytics.md). Real rollups (daily cron), honesty thresholds, no mocks. MEDIUMs: frozen 30-day summary cards vs range-aware panels, two divergent prediction-accuracy numbers, no SSR error state. No C/H. +- **Coaching Intelligence Settings** — [coaching-settings.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coaching-settings.md). Priorities/sensitivity/thresholds/master-switch/SG-baseline genuinely take effect. MEDIUMs: 11 alert toggles, weight sliders, display prefs, and (LOW) bubble-zone are write-only dead controls; no player self-gate (infinite skeleton). No C/H. +- **Development Plans (coach)** — [development-plans.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/development-plans.md). **HIGH:** outcome-mix hero stuck on "Awaiting outcomes" (reads a non-existent/unselected field). MEDIUMs: provenance chips + per-area sparkline never render (columns not selected); `updateFocusArea` silent 0-row no-op for multi-coach. +- **Recruiting HQ** — [recruiting-hq.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/recruiting-hq.md). Strong RLS, private bucket, signed downloads, orphan rollback. **HIGH:** 2 MB Server Action body cap silently kills 2–25 MB uploads vs the advertised 25 MB. MEDIUM: async `window.open` download popup-blocked on Safari. +- **Qualifiers (create/manage)** — [qualifiers-coach.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/qualifiers-coach.md). Create/entries/leaderboard/RLS all correct. **HIGH:** `updateQualifierStatus` has no caller — qualifiers can never advance or close. +- **Team Stats** — [team-stats.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/team-stats.md). Correctly wired; prior FW/GIR/Putts bugs appear remediated. MEDIUM: un-chunked `.in(roundIds)` URL-length risk at scale. No C/H. +- **Player detail / game view (coach)** — [player-detail-coach.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/player-detail-coach.md). Honest sparse/awaiting states, paginated insights. MEDIUM: `/game`+`/print` use any-staffed-team access while the base page uses active-team — inconsistent scoping (not a leak). No C/H. ### Player tabs -- **Player Dashboard home + Hub** — [player-home.md](_e2e_tab_audit_2026-06-20/player-home.md). Hub reads the correct `golf_task_assignments` (the doc's dual-table bug is stale/fixed). **HIGH (→MEDIUM):** root dashboard reads tasks from never-populated `golf_tasks.assigned_to` → home shows zero tasks. -- **Player CoachHelm** — [player-coachhelm.md](_e2e_tab_audit_2026-06-20/player-coachhelm.md). Auth+pagination+honest empties all correct. **2 HIGH:** What-If panel always empty (`improvements` never returned) and the Simulate path is consequently dead. MEDIUM: `currentPrediction` never passed. -- **CoachHelm Genome + Compare** — [coachhelm-genome.md](_e2e_tab_audit_2026-06-20/coachhelm-genome.md). Ownership gate solid at app + RLS layers; honest maturity floors. MEDIUM: non-coach redirect targets a non-existent `/forbidden` route (bare 404). No C/H. -- **CoachHelm Qualifying predictions** — [coachhelm-qualifying.md](_e2e_tab_audit_2026-06-20/coachhelm-qualifying.md). Coach-only selection workspace; player correctly redirected; upserts non-destructive. LOW: error boundary "home" lands a coach on a player dead-end; INFO: "predictions" is a misnomer. No C/H. -- **My Development (player)** — [my-development.md](_e2e_tab_audit_2026-06-20/my-development.md). **CRITICAL:** Log-progress/Mark-complete blocked by missing player-self UPDATE RLS, returns `{success:true}` on 0 rows. MEDIUMs: dead sparkline, mis-targeted review source link, no legacy error state. -- **Round create / continue / recover** — [round-create.md](_e2e_tab_audit_2026-06-20/round-create.md). Exemplary: atomic RPCs, upsert+orphan-trim, beacon/keepalive unload save, optimistic locking. MEDIUM: `clearEmergencySave` over-removes the `_new` draft (narrow cross-draft loss). No C/H. -- **Round Review** — [round-review.md](_e2e_tab_audit_2026-06-20/round-review.md). Auto-gen + upsert (no delete-then-insert); 9-hole normalization sound. MEDIUMs: `teamAvg` never passed (team comparison silently dead); `golf_round_reviews` over-broad anon grant (RLS backstops). No C/H. -- **My Qualifiers (player)** — [my-qualifiers.md](_e2e_tab_audit_2026-06-20/my-qualifiers.md). Self-scoped, no N+1, deep-link consumed. MEDIUM: Fairway `formatDate` shows dates one day early in US timezones; LOWs: swallowed rounds error, fictional X/N denominator, hardcoded 18 holes/round. No C/H. -- **Classes** — [classes.md](_e2e_tab_audit_2026-06-20/classes.md). Calendar-sync server action well-built. **HIGH (→CRITICAL):** edit always opens a blank form. (The `credits` int/float HIGH was refuted.) MEDIUMs: swallowed add/edit/delete errors, edit re-sync fails on empty semester. -- **My Game Profile + My Standing** — [player-profile-self.md](_e2e_tab_audit_2026-06-20/player-profile-self.md). **CRITICAL:** `StandingStrip` ignores `pga_omitted` → misleading men's-PGA benchmark for women's players. **HIGH (→MEDIUM):** counterfactual line dropped in prod fork. +- **Player Dashboard home + Hub** — [player-home.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/player-home.md). Hub reads the correct `golf_task_assignments` (the doc's dual-table bug is stale/fixed). **HIGH (→MEDIUM):** root dashboard reads tasks from never-populated `golf_tasks.assigned_to` → home shows zero tasks. +- **Player CoachHelm** — [player-coachhelm.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/player-coachhelm.md). Auth+pagination+honest empties all correct. **2 HIGH:** What-If panel always empty (`improvements` never returned) and the Simulate path is consequently dead. MEDIUM: `currentPrediction` never passed. +- **CoachHelm Genome + Compare** — [coachhelm-genome.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-genome.md). Ownership gate solid at app + RLS layers; honest maturity floors. MEDIUM: non-coach redirect targets a non-existent `/forbidden` route (bare 404). No C/H. +- **CoachHelm Qualifying predictions** — [coachhelm-qualifying.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/coachhelm-qualifying.md). Coach-only selection workspace; player correctly redirected; upserts non-destructive. LOW: error boundary "home" lands a coach on a player dead-end; INFO: "predictions" is a misnomer. No C/H. +- **My Development (player)** — [my-development.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/my-development.md). **CRITICAL:** Log-progress/Mark-complete blocked by missing player-self UPDATE RLS, returns `{success:true}` on 0 rows. MEDIUMs: dead sparkline, mis-targeted review source link, no legacy error state. +- **Round create / continue / recover** — [round-create.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/round-create.md). Exemplary: atomic RPCs, upsert+orphan-trim, beacon/keepalive unload save, optimistic locking. MEDIUM: `clearEmergencySave` over-removes the `_new` draft (narrow cross-draft loss). No C/H. +- **Round Review** — [round-review.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/round-review.md). Auto-gen + upsert (no delete-then-insert); 9-hole normalization sound. MEDIUMs: `teamAvg` never passed (team comparison silently dead); `golf_round_reviews` over-broad anon grant (RLS backstops). No C/H. +- **My Qualifiers (player)** — [my-qualifiers.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/my-qualifiers.md). Self-scoped, no N+1, deep-link consumed. MEDIUM: Fairway `formatDate` shows dates one day early in US timezones; LOWs: swallowed rounds error, fictional X/N denominator, hardcoded 18 holes/round. No C/H. +- **Classes** — [classes.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/classes.md). Calendar-sync server action well-built. **HIGH (→CRITICAL):** edit always opens a blank form. (The `credits` int/float HIGH was refuted.) MEDIUMs: swallowed add/edit/delete errors, edit re-sync fails on empty semester. +- **My Game Profile + My Standing** — [player-profile-self.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/player-profile-self.md). **CRITICAL:** `StandingStrip` ignores `pga_omitted` → misleading men's-PGA benchmark for women's players. **HIGH (→MEDIUM):** counterfactual line dropped in prod fork. ### Shared tabs -- **Rounds list** — [rounds-list.md](_e2e_tab_audit_2026-06-20/rounds-list.md). Role-scoped queries, scoped delete, no N+1. MEDIUMs: coach team-member query missing `status='active'` (ex-roster rounds linger); 50-round hard cap with no "load more". No C/H. -- **Calendar & Events** — [calendar.md](_e2e_tab_audit_2026-06-20/calendar.md). Hardened by the 2026-06-10 audit; stable realtime, soft-cancel, additive attendee sync. **HIGH:** Restore-event button dead (`onRestore` never passed). MEDIUMs: all-day drag → 1-hour timed; status string inconsistency; availability-polling unimplemented. -- **Roster + member detail** — [roster.md](_e2e_tab_audit_2026-06-20/roster.md). **CRITICAL (→HIGH):** Injured/Redshirt status options write invalid enum values. **HIGH:** `updatePlayerStatus` not cookie-aware → fails on 2-team orgs. MEDIUM: roster query shows pending/removed. -- **Messaging** — [messages.md](_e2e_tab_audit_2026-06-20/messages.md). **CRITICAL:** `get_golf_conversations_with_details` is SECURITY DEFINER, anon/PUBLIC-executable, trusts `p_user_id` (IDOR, live-confirmed). **HIGH:** sent attachments never rendered/downloadable. MEDIUMs: soft-delete leaks into preview/unread; unread-badge staleness. -- **Announcements** — [announcements.md](_e2e_tab_audit_2026-06-20/announcements.md). Staff-strict reads, upsert acks against a real UNIQUE, CASCADE deletes. MEDIUM: delete authorized only by original author (co-coaches blocked). LOWs: misleading persisted push/email flags, player assignment-fetch relies on RLS scoping. No C/H. -- **Tasks** — [tasks.md](_e2e_tab_audit_2026-06-20/tasks.md). The worst-wired tab: **2 CRITICAL (→HIGH) + 3 HIGH** all stemming from read-model (`golf_tasks.assigned_to`) ≠ write-model (`golf_task_assignments`). Players see an empty tab, coaches see "0 of 0", completion never reflects, Hub stays pending. -- **Team Hub (player) + Team Info** — [team-hub-and-info.md](_e2e_tab_audit_2026-06-20/team-hub-and-info.md). Team edits persist (UPDATE RLS present); reuses Hub queries. **HIGH (→MEDIUM):** player "Head coach" hidden on multi-coach orgs (`.maybeSingle()` on >1 row). LOWs: missing `status='active'` filters. -- **Documents + Travel** — [docs-travel.md](_e2e_tab_audit_2026-06-20/docs-travel.md). **CRITICAL (→HIGH):** `golf_documents` SELECT RLS leaks coach-only docs to players. **2 HIGH:** Download uses public URL on a now-private bucket (Fairway + legacy). MEDIUMs: expense-splits + event-link dormant. -- **Stats (personal)** — [stats-personal.md](_e2e_tab_audit_2026-06-20/stats-personal.md). Cleanest unit: full pagination, per-action `verifyPlayerAccess`, correct SG/leak-map math, no fabricated data. LOW: 9-hole normalized score vs raw to-par mismatch in Recent rounds. No C/H. -- **Course Library + What's New** — [courses-whatsnew.md](_e2e_tab_audit_2026-06-20/courses-whatsnew.md). Soft-delete, stage-and-swap, UPDATE RLS present (the stale "no UPDATE policy" memory is corrected). MEDIUM: Course Library is in no nav/command-palette — undiscoverable. No C/H. -- **Settings + Notifications** — [settings-core.md](_e2e_tab_audit_2026-06-20/settings-core.md). **2 CRITICAL (→HIGH):** the per-category notification matrix has no delivery consumer (`routeNotification` dead); coaches have no notification UI at all. **HIGH (→MEDIUM):** push defaults disagree between delivery and UI. +- **Rounds list** — [rounds-list.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/rounds-list.md). Role-scoped queries, scoped delete, no N+1. MEDIUMs: coach team-member query missing `status='active'` (ex-roster rounds linger); 50-round hard cap with no "load more". No C/H. +- **Calendar & Events** — [calendar.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/calendar.md). Hardened by the 2026-06-10 audit; stable realtime, soft-cancel, additive attendee sync. **HIGH:** Restore-event button dead (`onRestore` never passed). MEDIUMs: all-day drag → 1-hour timed; status string inconsistency; availability-polling unimplemented. +- **Roster + member detail** — [roster.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/roster.md). **CRITICAL (→HIGH):** Injured/Redshirt status options write invalid enum values. **HIGH:** `updatePlayerStatus` not cookie-aware → fails on 2-team orgs. MEDIUM: roster query shows pending/removed. +- **Messaging** — [messages.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/messages.md). **CRITICAL:** `get_golf_conversations_with_details` is SECURITY DEFINER, anon/PUBLIC-executable, trusts `p_user_id` (IDOR, live-confirmed). **HIGH:** sent attachments never rendered/downloadable. MEDIUMs: soft-delete leaks into preview/unread; unread-badge staleness. +- **Announcements** — [announcements.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/announcements.md). Staff-strict reads, upsert acks against a real UNIQUE, CASCADE deletes. MEDIUM: delete authorized only by original author (co-coaches blocked). LOWs: misleading persisted push/email flags, player assignment-fetch relies on RLS scoping. No C/H. +- **Tasks** — [tasks.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/tasks.md). The worst-wired tab: **2 CRITICAL (→HIGH) + 3 HIGH** all stemming from read-model (`golf_tasks.assigned_to`) ≠ write-model (`golf_task_assignments`). Players see an empty tab, coaches see "0 of 0", completion never reflects, Hub stays pending. +- **Team Hub (player) + Team Info** — [team-hub-and-info.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/team-hub-and-info.md). Team edits persist (UPDATE RLS present); reuses Hub queries. **HIGH (→MEDIUM):** player "Head coach" hidden on multi-coach orgs (`.maybeSingle()` on >1 row). LOWs: missing `status='active'` filters. +- **Documents + Travel** — [docs-travel.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/docs-travel.md). **CRITICAL (→HIGH):** `golf_documents` SELECT RLS leaks coach-only docs to players. **2 HIGH:** Download uses public URL on a now-private bucket (Fairway + legacy). MEDIUMs: expense-splits + event-link dormant. +- **Stats (personal)** — [stats-personal.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/stats-personal.md). Cleanest unit: full pagination, per-action `verifyPlayerAccess`, correct SG/leak-map math, no fabricated data. LOW: 9-hole normalized score vs raw to-par mismatch in Recent rounds. No C/H. +- **Course Library + What's New** — [courses-whatsnew.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/courses-whatsnew.md). Soft-delete, stage-and-swap, UPDATE RLS present (the stale "no UPDATE policy" memory is corrected). MEDIUM: Course Library is in no nav/command-palette — undiscoverable. No C/H. +- **Settings + Notifications** — [settings-core.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/settings-core.md). **2 CRITICAL (→HIGH):** the per-category notification matrix has no delivery consumer (`routeNotification` dead); coaches have no notification UI at all. **HIGH (→MEDIUM):** push defaults disagree between delivery and UI. ### Entry flows -- **Auth (login/signup/forgot/reset/demo)** — [auth-flows.md](_e2e_tab_audit_2026-06-20/auth-flows.md). Lockout/rate-limits, safe-path guards, server-side demo sign-in. **HIGH (→MEDIUM):** `DemoEnterTracker` not mounted in the Fairway shell → demo funnel blind on prod. MEDIUMs: forgot-password bypasses the hardened action; reset-password recovery-session unverified. -- **Onboarding (coach + player)** — [onboarding.md](_e2e_tab_audit_2026-06-20/onboarding.md). Auth-first, compensating rollback, player resume. **HIGH (→MEDIUM):** player page uses `useSearchParams` with no Suspense boundary. (The coach role-escalation HIGH was refuted.) MEDIUMs: uploaded onboarding avatar discarded; orphan-coach on mid-flow throw; no coach resume. -- **Join team by code** — [join-team.md](_e2e_tab_audit_2026-06-20/join-team.md). Auth-before-read, case-insensitive lookup, self-join RLS correct, exact-team auto-join. MEDIUMs: a coach clicking an invite link gets a stray player profile; login-from-invite (vs signup) drops the join code. No C/H. +- **Auth (login/signup/forgot/reset/demo)** — [auth-flows.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/auth-flows.md). Lockout/rate-limits, safe-path guards, server-side demo sign-in. **HIGH (→MEDIUM):** `DemoEnterTracker` not mounted in the Fairway shell → demo funnel blind on prod. MEDIUMs: forgot-password bypasses the hardened action; reset-password recovery-session unverified. +- **Onboarding (coach + player)** — [onboarding.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/onboarding.md). Auth-first, compensating rollback, player resume. **HIGH (→MEDIUM):** player page uses `useSearchParams` with no Suspense boundary. (The coach role-escalation HIGH was refuted.) MEDIUMs: uploaded onboarding avatar discarded; orphan-coach on mid-flow throw; no coach resume. +- **Join team by code** — [join-team.md](../archive/2026-06/audits/_e2e_tab_audit_2026-06-20/join-team.md). Auth-before-read, case-insensitive lookup, self-join RLS correct, exact-team auto-join. MEDIUMs: a coach clicking an invite link gets a stray player profile; login-from-invite (vs signup) drops the join code. No C/H. --- -*Report path: `/Users/ricknini/Downloads/helmv3/docs/audits/GOLFHELM_E2E_TAB_AUDIT_2026-06-20.md`. Source of truth for detail: the 36 per-unit md files in `docs/audits/_e2e_tab_audit_2026-06-20/`.* +*Report path: `/Users/ricknini/Downloads/helmv3/docs/audits/GOLFHELM_E2E_TAB_AUDIT_2026-06-20.md`. Source of truth for detail: the 36 per-unit md files in `docs/archive/2026-06/audits/_e2e_tab_audit_2026-06-20/`.* diff --git a/docs/audits/HELMV3_ISSUE_LEDGER_2026-06-30.md b/docs/audits/HELMV3_ISSUE_LEDGER_2026-06-30.md index 31b9fdf01..7215c438d 100644 --- a/docs/audits/HELMV3_ISSUE_LEDGER_2026-06-30.md +++ b/docs/audits/HELMV3_ISSUE_LEDGER_2026-06-30.md @@ -1,3 +1,10 @@ + + # HelmV3 Issue Ledger — 2026-06-30 Validated ledger of all 145 open GitHub issues against `origin/main` and PR #421 (`fix/coderabbit-planned-issues`), adversarially re-verified per issue with direct code/git evidence (file:line references, `git diff origin/main...fix/coderabbit-planned-issues`, migration reads, and in a few cases live `semgrep`/`vitest`/`gh` command output). diff --git a/docs/audits/RLS_SECURITY_AUDIT.md b/docs/audits/RLS_SECURITY_AUDIT.md index 413cf32a7..5fe5a6862 100644 --- a/docs/audits/RLS_SECURITY_AUDIT.md +++ b/docs/audits/RLS_SECURITY_AUDIT.md @@ -1,3 +1,10 @@ + + # RLS POLICY & SECURITY AUDIT > PHASE 3 AUDIT REPORT > Generated: 2026-01-01 diff --git a/docs/baseball/ENTRY_SCENES_DESIGN.md b/docs/baseball/ENTRY_SCENES_DESIGN.md index f3aea21af..dbd0e0846 100644 --- a/docs/baseball/ENTRY_SCENES_DESIGN.md +++ b/docs/baseball/ENTRY_SCENES_DESIGN.md @@ -1,3 +1,10 @@ + + # Helm Entry Scenes — Baseball & Lift Lab ### Fable's design spec · 2026-07-02 · for the login/onboarding worlds *Sibling system to golf's `CoastalScene`/`CourseScene` (src/components/golf/scenes/) — same craft DNA, new sports, new moods. Painterly layered SVG, no photography, no people. This is the "svg type thing," grown up.* diff --git a/docs/baseball/PRODUCTION_READINESS_MASTER_PLAN.md b/docs/baseball/PRODUCTION_READINESS_MASTER_PLAN.md index ec9d4998d..917f2170d 100644 --- a/docs/baseball/PRODUCTION_READINESS_MASTER_PLAN.md +++ b/docs/baseball/PRODUCTION_READINESS_MASTER_PLAN.md @@ -1,3 +1,10 @@ + + + # CoachHelm Phase 2 — Deferred E/F plan (band reconciliation, standing, persistence) > **Status:** DESIGNED + prod-verified, NOT built. Parked under the "do only high diff --git a/docs/code-quality-review-2026-02-22.md b/docs/code-quality-review-2026-02-22.md index e80e42c2d..c51552803 100644 --- a/docs/code-quality-review-2026-02-22.md +++ b/docs/code-quality-review-2026-02-22.md @@ -1,3 +1,10 @@ + + # GolfHelm Code Quality Review **Date:** 2026-02-22 diff --git a/docs/fairway-baseballhelm-migration-plan.md b/docs/fairway-baseballhelm-migration-plan.md index ea6996257..318e41390 100644 --- a/docs/fairway-baseballhelm-migration-plan.md +++ b/docs/fairway-baseballhelm-migration-plan.md @@ -1,3 +1,10 @@ + + # Fairway UI Migration Plan — BaseballHelm > **Status:** PLAN / NOT STARTED. Authored 2026-06-30. diff --git a/docs/fairway-coachhelm-insight-rebuild.md b/docs/fairway-coachhelm-insight-rebuild.md index 562fbba7b..13f0a30a5 100644 --- a/docs/fairway-coachhelm-insight-rebuild.md +++ b/docs/fairway-coachhelm-insight-rebuild.md @@ -1,3 +1,10 @@ + + # Fairway CoachHelm Rebuild — Hierarchical Insight Model (saved plan) > **Status:** designed, NOT built. Parked for a future session ("when I have more tokens"). diff --git a/docs/fairway-recruiting-plan.md b/docs/fairway-recruiting-plan.md index 5057fc7d5..bd6953c08 100644 --- a/docs/fairway-recruiting-plan.md +++ b/docs/fairway-recruiting-plan.md @@ -1,3 +1,10 @@ + + # Fairway redesign plan — Recruiting HQ _Status: PLAN ONLY (not built). Locked 2026-05-31._ diff --git a/docs/fairway-shot-tracking-plan.md b/docs/fairway-shot-tracking-plan.md index a0c4d1f32..33e370d39 100644 --- a/docs/fairway-shot-tracking-plan.md +++ b/docs/fairway-shot-tracking-plan.md @@ -1,3 +1,10 @@ + + # Fairway redesign plan — Shot-tracking / round-entry flow _Status: PLAN ONLY (not built). Locked 2026-05-31._ diff --git a/docs/fairway-stats-redesign-plan.md b/docs/fairway-stats-redesign-plan.md index cbfab0f9b..3846fee80 100644 --- a/docs/fairway-stats-redesign-plan.md +++ b/docs/fairway-stats-redesign-plan.md @@ -1,3 +1,10 @@ + + # Fairway Comprehensive Stats — Redesign Plan _Status: PLAN — awaiting approval before engine edits. Drafted 2026-06-01._ diff --git a/docs/features/CALENDAR_COMPREHENSIVE_IMPLEMENTATION_PLAN.md b/docs/features/CALENDAR_COMPREHENSIVE_IMPLEMENTATION_PLAN.md index af1f00d21..f852f74f9 100644 --- a/docs/features/CALENDAR_COMPREHENSIVE_IMPLEMENTATION_PLAN.md +++ b/docs/features/CALENDAR_COMPREHENSIVE_IMPLEMENTATION_PLAN.md @@ -1,3 +1,10 @@ + + # COMPREHENSIVE CALENDAR SYSTEM IMPLEMENTATION PLAN > **Generated**: January 1, 2026 diff --git a/docs/features/coachhelm/COACHHELM_DISABLE_FEATURE.md b/docs/features/coachhelm/COACHHELM_DISABLE_FEATURE.md index a04ba428a..41c0dbbef 100644 --- a/docs/features/coachhelm/COACHHELM_DISABLE_FEATURE.md +++ b/docs/features/coachhelm/COACHHELM_DISABLE_FEATURE.md @@ -1,3 +1,10 @@ + + # CoachHelm System Settings — Enable/Disable Feature ## Overview diff --git a/docs/features/coachhelm/COACHHELM_IMPLEMENTATION_SPEC.md b/docs/features/coachhelm/COACHHELM_IMPLEMENTATION_SPEC.md index e79a6fe6d..cb23fe95c 100644 --- a/docs/features/coachhelm/COACHHELM_IMPLEMENTATION_SPEC.md +++ b/docs/features/coachhelm/COACHHELM_IMPLEMENTATION_SPEC.md @@ -1,3 +1,10 @@ + + # CoachHelm Implementation Specification ## Detailed Feature Specs for Cursor Implementation diff --git a/docs/features/coachhelm/COACHHELM_INTELLIGENCE_ENGINE.md b/docs/features/coachhelm/COACHHELM_INTELLIGENCE_ENGINE.md index 49788fa73..69e2d583f 100644 --- a/docs/features/coachhelm/COACHHELM_INTELLIGENCE_ENGINE.md +++ b/docs/features/coachhelm/COACHHELM_INTELLIGENCE_ENGINE.md @@ -1,3 +1,10 @@ + + # CoachHelm Intelligence Engine ## How the Brain Works diff --git a/docs/features/coachhelm/COACHHELM_V2_INTELLIGENCE_ENGINE.md b/docs/features/coachhelm/COACHHELM_V2_INTELLIGENCE_ENGINE.md index 2160cd986..06c1f0d9e 100644 --- a/docs/features/coachhelm/COACHHELM_V2_INTELLIGENCE_ENGINE.md +++ b/docs/features/coachhelm/COACHHELM_V2_INTELLIGENCE_ENGINE.md @@ -1,3 +1,10 @@ + + # CoachHelm V2 Intelligence Engine ## The Full Brain diff --git a/docs/features/coachhelm/COACHHELM_V2_INTELLIGENCE_ENGINE_FULL.md b/docs/features/coachhelm/COACHHELM_V2_INTELLIGENCE_ENGINE_FULL.md index 2160cd986..06c1f0d9e 100644 --- a/docs/features/coachhelm/COACHHELM_V2_INTELLIGENCE_ENGINE_FULL.md +++ b/docs/features/coachhelm/COACHHELM_V2_INTELLIGENCE_ENGINE_FULL.md @@ -1,3 +1,10 @@ + + # CoachHelm V2 Intelligence Engine ## The Full Brain diff --git a/docs/features/coachhelm/CURSOR_IMPLEMENTATION_GUIDE.md b/docs/features/coachhelm/CURSOR_IMPLEMENTATION_GUIDE.md index 19cdecec3..0e63b4a56 100644 --- a/docs/features/coachhelm/CURSOR_IMPLEMENTATION_GUIDE.md +++ b/docs/features/coachhelm/CURSOR_IMPLEMENTATION_GUIDE.md @@ -1,3 +1,10 @@ + + # CoachHelm V2 Intelligence Engine — Cursor Implementation Guide ## How to Use This Document diff --git a/docs/features/coachhelm/FEATURE_1_COACH_PHILOSOPHY_SETTINGS.md b/docs/features/coachhelm/FEATURE_1_COACH_PHILOSOPHY_SETTINGS.md index a73d26ab0..74b19b919 100644 --- a/docs/features/coachhelm/FEATURE_1_COACH_PHILOSOPHY_SETTINGS.md +++ b/docs/features/coachhelm/FEATURE_1_COACH_PHILOSOPHY_SETTINGS.md @@ -1,3 +1,10 @@ + + # CoachHelm Feature 1: Coach Philosophy Settings ## Complete Implementation Specification diff --git a/docs/features/coachhelm/FEATURE_ROUND_REVIEW.md b/docs/features/coachhelm/FEATURE_ROUND_REVIEW.md index 7573e1175..8327f645e 100644 --- a/docs/features/coachhelm/FEATURE_ROUND_REVIEW.md +++ b/docs/features/coachhelm/FEATURE_ROUND_REVIEW.md @@ -1,3 +1,10 @@ + + # CoachHelm Feature: Round Review ## Complete Implementation Specification diff --git a/docs/features/coachhelm/README.md b/docs/features/coachhelm/README.md index 8e90d09da..ed746096a 100644 --- a/docs/features/coachhelm/README.md +++ b/docs/features/coachhelm/README.md @@ -1,3 +1,10 @@ + + # CoachHelm Documentation Package ## What's Inside diff --git a/docs/features/coachhelm/coachhelm-master-blueprint.md b/docs/features/coachhelm/coachhelm-master-blueprint.md index 2f1293b53..c9eb6065d 100644 --- a/docs/features/coachhelm/coachhelm-master-blueprint.md +++ b/docs/features/coachhelm/coachhelm-master-blueprint.md @@ -1,3 +1,10 @@ + + # CoachHelm Master Blueprint v2.0 ## Intelligence-First Design with Deep Configurability diff --git a/docs/golfhelm-engine-upgrade-plan.md b/docs/golfhelm-engine-upgrade-plan.md index 94a92792d..019294e53 100644 --- a/docs/golfhelm-engine-upgrade-plan.md +++ b/docs/golfhelm-engine-upgrade-plan.md @@ -1,3 +1,10 @@ + + # GolfHelm / CoachHelm Engine Upgrade Plan **Make the engine (a) more golf-aware and (b) genuinely trained/learning on real golf data** diff --git a/docs/perf-analysis-dashboard-timeouts-2026-03-09.md b/docs/perf-analysis-dashboard-timeouts-2026-03-09.md index 44e8e0ca2..4c4497bf3 100644 --- a/docs/perf-analysis-dashboard-timeouts-2026-03-09.md +++ b/docs/perf-analysis-dashboard-timeouts-2026-03-09.md @@ -1,3 +1,10 @@ + + # GolfHelm Dashboard Timeout Analysis _Generated: 2026-03-09 (overnight cron)_ diff --git a/docs/plans/ITEM2_TWO_SESSION_PLAN.md b/docs/plans/ITEM2_TWO_SESSION_PLAN.md index 1843c5e69..c21ddca99 100644 --- a/docs/plans/ITEM2_TWO_SESSION_PLAN.md +++ b/docs/plans/ITEM2_TWO_SESSION_PLAN.md @@ -1,3 +1,10 @@ + + # Item-2 Workload-Sharing Plan — Two Sessions (2026-07-01) > Follows the BaseballHelm clean-slate landing: **all 53 PRs merged + 19 migrations applied to prod**. diff --git a/docs/qa/baseball-fairway-visual-audit-2026-07-04/README.md b/docs/qa/baseball-fairway-visual-audit-2026-07-04/README.md index cdcb0e9de..526e3290c 100644 --- a/docs/qa/baseball-fairway-visual-audit-2026-07-04/README.md +++ b/docs/qa/baseball-fairway-visual-audit-2026-07-04/README.md @@ -1,3 +1,10 @@ + + # BaseballHelm Fairway Visual QA Pack **Generated:** 2026-07-04 diff --git a/docs/redesign-playbook.md b/docs/redesign-playbook.md index e68617925..cbd6c9c71 100644 --- a/docs/redesign-playbook.md +++ b/docs/redesign-playbook.md @@ -1,3 +1,10 @@ + + # GolfHelm Redesign Playbook > The operating contract for the flag-gated "Fairway" redesign. Locked 2026-05-30. diff --git a/docs/superpowers/plans/2026-04-21-coachhelm-fix/00-orchestration.md b/docs/superpowers/plans/2026-04-21-coachhelm-fix/00-orchestration.md index f4c11e720..467eeef1c 100644 --- a/docs/superpowers/plans/2026-04-21-coachhelm-fix/00-orchestration.md +++ b/docs/superpowers/plans/2026-04-21-coachhelm-fix/00-orchestration.md @@ -1,3 +1,10 @@ + + # CoachHelm Fix — Multi-Team Orchestration > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement these plans. Each team plan uses checkbox (`- [ ]`) syntax for tracking. diff --git a/docs/superpowers/plans/2026-04-22-insight-delivery/00-design-contract.md b/docs/superpowers/plans/2026-04-22-insight-delivery/00-design-contract.md index d9e823949..3f850dd00 100644 --- a/docs/superpowers/plans/2026-04-22-insight-delivery/00-design-contract.md +++ b/docs/superpowers/plans/2026-04-22-insight-delivery/00-design-contract.md @@ -1,3 +1,10 @@ + + # Insight Delivery Phase — Design Contract > **Read this BEFORE writing any code.** Every team plan in this directory references this contract. diff --git a/docs/superpowers/plans/2026-04-22-insight-quality/00-design-contract.md b/docs/superpowers/plans/2026-04-22-insight-quality/00-design-contract.md index 83105e92f..4c9c2265f 100644 --- a/docs/superpowers/plans/2026-04-22-insight-quality/00-design-contract.md +++ b/docs/superpowers/plans/2026-04-22-insight-quality/00-design-contract.md @@ -1,3 +1,10 @@ + + # Insight Quality Phase — Design Contract > **Read this BEFORE writing any code.** Every team plan in this directory references this contract. Do not deviate from the shapes/rules below without coordinating. diff --git a/docs/superpowers/plans/2026-05-27-helm-database-vercel-alignment.md b/docs/superpowers/plans/2026-05-27-helm-database-vercel-alignment.md index 76a9b4725..c45010351 100644 --- a/docs/superpowers/plans/2026-05-27-helm-database-vercel-alignment.md +++ b/docs/superpowers/plans/2026-05-27-helm-database-vercel-alignment.md @@ -1,3 +1,10 @@ + + # Helm Database / Vercel / Supabase / CoachHelm Alignment 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. diff --git a/docs/superpowers/plans/2026-05-28-ai-codebase-intelligence.md b/docs/superpowers/plans/2026-05-28-ai-codebase-intelligence.md index 24625d90c..63edbd8cf 100644 --- a/docs/superpowers/plans/2026-05-28-ai-codebase-intelligence.md +++ b/docs/superpowers/plans/2026-05-28-ai-codebase-intelligence.md @@ -1,3 +1,10 @@ + + # Helmv3 AI Codebase Intelligence 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. diff --git a/docs/v3-compatibility-shims.md b/docs/v3-compatibility-shims.md index fc801fe3d..4aed4f225 100644 --- a/docs/v3-compatibility-shims.md +++ b/docs/v3-compatibility-shims.md @@ -1,3 +1,10 @@ + + # CoachHelm v3 — Compatibility Shim Registry > **Every shim has a kill date or the PR is rejected.** This file enforces Rule 10 from [`docs/v3-master-plan.md` Part I](./v3-master-plan.md). A shim is any temporary bridge that lets v2 and v3 coexist; without an explicit removal wave, shims become permanent debt. diff --git a/docs/v3-master-plan.md b/docs/v3-master-plan.md index 11865884b..4adf84f58 100644 --- a/docs/v3-master-plan.md +++ b/docs/v3-master-plan.md @@ -1,3 +1,10 @@ + + # CoachHelm v3 — The Final All-In-One Plan *Schema-verified · Decision-locked · Ultra-organized · Zero hand-waving* diff --git a/docs/v3-player-transfer-playbook.md b/docs/v3-player-transfer-playbook.md index a28334309..b4c5a31e3 100644 --- a/docs/v3-player-transfer-playbook.md +++ b/docs/v3-player-transfer-playbook.md @@ -1,3 +1,10 @@ + + # CoachHelm v3 — Player Transfer & Lifecycle Playbook > Edge cases the v3 engine and UI must handle correctly. Every wave that adds player-scoped data must read this and confirm its tables don't break these scenarios. If a wave introduces a new player-scoped table, add a row to the matrix at the bottom. diff --git a/docs/v3-wave-sequence.md b/docs/v3-wave-sequence.md index 4ed6fda4b..2c3399d91 100644 --- a/docs/v3-wave-sequence.md +++ b/docs/v3-wave-sequence.md @@ -1,3 +1,10 @@ + + # CoachHelm v3 — Wave Sequence Tracker > **Live ship-status doc.** The full per-wave spec lives in [`docs/v3-master-plan.md` Part XXIII](./v3-master-plan.md). This file is the running ledger of what has shipped, what is in flight, and what is next. Update it as part of every wave's PR. diff --git a/e2e/golf-qualifier.spec.ts b/e2e/golf-qualifier.spec.ts index c73008030..035c3ddf9 100644 --- a/e2e/golf-qualifier.spec.ts +++ b/e2e/golf-qualifier.spec.ts @@ -17,13 +17,22 @@ const GOLF_PASSWORD = process.env.E2E_GOLF_PASSWORD; const hasSeededAuth = Boolean(GOLF_EMAIL && GOLF_PASSWORD); test.describe('Golf Qualifier - Coach Flow', () => { - test.skip('should create a new qualifier', async ({ page }) => { - // Skip if coach login not available - // This test requires a coach account - + // Was `test.skip('should create a new qualifier', ...)` — Playwright's + // (name, fn) skip signature, which unconditionally skips forever + // regardless of env vars. That predates the env-gating documented in the + // file header above and never actually adopted it, unlike the Player + // Flow / Leaderboard blocks below. Converted to the same conditional + // skip so this runs whenever seeded creds are present. Verified + // 2026-07-09: E2E_GOLF_EMAIL/E2E_GOLF_PASSWORD are still unset in this + // repo's environment (no seeded-auth CI fixture exists yet), so this + // remains skipped today — the true current blocker is the missing env + // vars, not the test itself. + test.skip(!hasSeededAuth, 'Set E2E_GOLF_EMAIL and E2E_GOLF_PASSWORD (seeded golf coach) to run.'); + + test('should create a new qualifier', async ({ page }) => { await page.goto('/golf/login'); - await page.fill('input[type="email"]', GOLF_EMAIL ?? ''); - await page.fill('input[type="password"]', GOLF_PASSWORD ?? ''); + await page.fill('input[type="email"]', GOLF_EMAIL as string); + await page.fill('input[type="password"]', GOLF_PASSWORD as string); await page.click('button[type="submit"]'); // Navigate to qualifiers diff --git a/eslint.config.mjs b/eslint.config.mjs index 73c0f45d4..f7772896a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -39,7 +39,6 @@ export default tseslint.config( ...tseslint.configs.recommended, { ignores: [ - "next-shadcn-dashboard-starter/**", "node_modules/**", ".next/**", "eslint-rules/**", diff --git a/memory/registry.yml b/memory/registry.yml index e022063fa..e94e33764 100644 --- a/memory/registry.yml +++ b/memory/registry.yml @@ -801,13 +801,12 @@ features: feature: memory/features/auth-onboarding-join.md flows: - memory/context/golfhelm-features.md - - docs/architecture/COMPREHENSIVE_AUTH_SYSTEM_PLAN.md - docs/security/auth-config.md ui: - memory/projects/golfhelm.md - docs/v3-design-language.md business_logic: - - docs/architecture/USER_ROLE_DATA_OWNERSHIP.md + - docs/v3-rls-template.md - docs/v3-testing-standards.md incidents: - docs/operations/2026-05-17-p0-runbook.md @@ -842,7 +841,7 @@ features: review: required_docs: - memory/features/auth-onboarding-join.md - - docs/architecture/USER_ROLE_DATA_OWNERSHIP.md + - docs/v3-rls-template.md - docs/security/auth-config.md required_checks: - npm run typecheck @@ -1018,7 +1017,6 @@ features: docs: feature: memory/features/team-access-control.md flows: - - docs/architecture/USER_ROLE_DATA_OWNERSHIP.md - docs/v3-rls-template.md ui: - AGENTS.md @@ -1029,6 +1027,7 @@ features: - docs/SECURITY_AUDIT.md incidents: - docs/operations/2026-05-17-p0-runbook.md + - docs/operations/2026-07-03-p0-service-role-key-rotation-runbook.md code: routes: - src/app/golf/(dashboard)/dashboard/settings/** diff --git a/scripts/__tests__/chart-palette.test.mjs b/scripts/__tests__/chart-palette.test.mjs deleted file mode 100644 index 090c3698f..000000000 --- a/scripts/__tests__/chart-palette.test.mjs +++ /dev/null @@ -1,150 +0,0 @@ -import { test } from 'node:test'; -import assert from 'node:assert/strict'; -import { readFile } from 'node:fs/promises'; -import { join, resolve } from 'node:path'; - -/** - * Regression guard for Wave W5B — chart palette + decoration normalization - * (agent palette). - * - * W5A shipped the canonical chart primitives + the five-role CHART_PALETTE. - * W5B enforces that vocabulary across the player-facing chart components and - * strips the decorative chrome the audit flagged: - * - * - PuttHeatmap → the made-putt dot fill is the brand-green TOKEN - * (`var(--color-primary-600)`), not the raw emerald `#10b981`. - * - PuttMakeChart → the 9-color green→yellow→orange→red rainbow ramp - * (in ProgressStats) is collapsed to a single-hue primary ramp. - * - PatternImpactPanel → the 5-slice lifecycle donut becomes a horizontal - * bar via ; no PieChart/Pie/donut chrome remains. - * - DispersionStats → the decorative `repeat: Infinity` pulse circles - * are removed (rendered static). - * - ProgressStats → the 10× copy-pasted top-edge gradient dividers are - * deleted. - * - * This test re-asserts that contract so the targeted chart hardcodes and - * decorations cannot silently drift back in. It reads file SOURCE (not a - * runtime render) so it stays fast + dependency-free. - * - * ALLOWLIST: a handful of raw values legitimately cannot use a token — - * ctx fills, brand/DB-driven inline styles, Resend email HTML, - * src/app/globals.css raw declarations, and the SVG green-felt gradient stops - * inside PuttHeatmap that are part of the illustration (not a data series). - * Those are NOT `#10b981` and are not asserted against here, so the test still - * FAILS on any NON-allowlisted reintroduction of the banned patterns. - * - * Audit reference: ultra-audit master synthesis A4 (component sprawl) + A2 / - * §5 (one chart palette, one chart surface, no decorative chrome). - */ - -const REPO_ROOT = resolve(import.meta.dirname, '..', '..'); -const src = (...parts) => join(REPO_ROOT, 'src', ...parts); - -const PUTT_HEATMAP = src('components', 'golf', 'coachhelm', 'v3', 'PuttHeatmap', 'index.tsx'); -const PROGRESS_STATS = src('components', 'golf', 'stats', 'ProgressStats.tsx'); -const PUTTING_STATS = src('components', 'golf', 'stats', 'sections', 'PuttingStats.tsx'); -const DISPERSION_STATS = src('components', 'golf', 'stats', 'sections', 'DispersionStats.tsx'); -const PATTERN_IMPACT = src('components', 'golf', 'coachhelm', 'analytics', 'PatternImpactPanel.tsx'); - -const read = (path) => readFile(path, 'utf8'); - -test('PuttHeatmap: made-putt fill uses the primary token, not raw #10b981', async () => { - const code = await read(PUTT_HEATMAP); - assert.ok( - !/#10b981/i.test(code), - 'PuttHeatmap must not hardcode #10b981 — use var(--color-primary-600).', - ); - assert.match( - code, - /var\(--color-primary-600\)/, - 'PuttHeatmap must color made-putt dots with var(--color-primary-600).', - ); -}); - -test('No targeted chart hardcodes #10b981 anywhere in the player chart paths', async () => { - for (const path of [PUTT_HEATMAP, PROGRESS_STATS, PUTTING_STATS, DISPERSION_STATS, PATTERN_IMPACT]) { - const code = await read(path); - assert.ok( - !/#10b981/i.test(code), - `${path} must not hardcode #10b981 (emerald) — reach for a token.`, - ); - } -}); - -test('PuttMakeChart: 9-color rainbow ramp collapsed to a single-hue primary ramp', async () => { - const code = await read(PROGRESS_STATS); - // Isolate the PuttMakeChart data block (the one keyed by `puttMakePct0_3`). - const start = code.indexOf('puttMakePct0_3'); - assert.ok(start !== -1, 'PuttMakeChart distance buckets must still exist.'); - const block = code.slice(start, start + 1200); - - // The old rainbow hexes (yellow/orange/red ramp) must be gone from the bucket fills. - for (const banned of ['#fbbf24', '#f97316', '#ef4444', '#dc2626', '#b91c1c', '#991b1b', '#7f1d1d', '#22c55e']) { - assert.ok( - !block.includes(banned), - `PuttMakeChart must not use the rainbow-ramp hex ${banned}; use a single-hue primary ramp.`, - ); - } - // The single-hue primary ramp uses primary-scale token vars. - assert.match( - block, - /var\(--color-primary-\d00\)/, - 'PuttMakeChart bucket fills must be primary-scale token vars (single-hue ramp).', - ); - // PRESERVATION: every distance bucket data point is still present. - for (const key of [ - 'puttMakePct0_3', 'puttMakePct3_5', 'puttMakePct5_10', 'puttMakePct10_15', - 'puttMakePct15_20', 'puttMakePct20_25', 'puttMakePct25_30', 'puttMakePct30_35', - 'puttMakePct35Plus', - ]) { - assert.ok(block.includes(key), `PuttMakeChart must preserve the ${key} bucket.`); - } -}); - -test('PatternImpactPanel: lifecycle distribution is a bar via ChartShell, not a pie/donut', async () => { - const code = await read(PATTERN_IMPACT); - // No pie/donut chrome. - assert.ok(!/\bPieChart\b/.test(code), 'PatternImpactPanel must not render a PieChart.'); - assert.ok(!/ (donut).'); - assert.ok( - !/import\s*\{[^}]*\bPie\b[^}]*\}\s*from\s*['"]recharts['"]/.test(code), - 'PatternImpactPanel must not import Pie/PieChart from recharts.', - ); - // It now renders a (horizontal) bar via ChartShell. - assert.match(code, /\bChartShell\b/, 'PatternImpactPanel must wrap the distribution chart in ChartShell.'); - assert.match(code, / { - const code = await read(DISPERSION_STATS); - assert.ok( - !/repeat:\s*Infinity/.test(code), - 'DispersionStats must not use repeat: Infinity decorative pulses (render the SVG marks static).', - ); - // PRESERVATION: the point-cloud render + outcome styling stays. - assert.match(code, /OUTCOME_STYLES/, 'DispersionStats must preserve OUTCOME_STYLES.'); - assert.match(code, /group\.points\.map/, 'DispersionStats must still plot every shot point.'); -}); - -test('ProgressStats: the 10x copy-pasted top-edge gradient dividers are deleted', async () => { - const code = await read(PROGRESS_STATS); - assert.ok( - !code.includes('absolute inset-x-0 top-0 h-px'), - 'ProgressStats must not re-add the copy-pasted top-edge gradient divider divs.', - ); - assert.ok( - !code.includes('linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent)'), - 'ProgressStats must not re-add the decorative top-edge gradient.', - ); - // PRESERVATION: the real chart sections are all still here. - for (const heading of [ - 'Scoring Trend', 'Score Distribution', 'Recent Rounds', 'Round Comparison', - 'Putt Make % by Distance', 'GIR % by Hole Type', 'Strokes Gained', 'Key Performance Indicators', - ]) { - assert.ok(code.includes(heading), `ProgressStats must preserve the "${heading}" section.`); - } -}); diff --git a/scripts/__tests__/drawers-mobile.test.mjs b/scripts/__tests__/drawers-mobile.test.mjs index c9640e963..411bc8ca2 100644 --- a/scripts/__tests__/drawers-mobile.test.mjs +++ b/scripts/__tests__/drawers-mobile.test.mjs @@ -8,7 +8,7 @@ import { join, resolve } from 'node:path'; * mobile responsiveness fixes. * * Background (ultra-audit master synthesis, mobile surface wave): on - * short iOS / Capacitor viewports the IntentDrawer + GoalCreationModal + * short iOS / Capacitor viewports the GoalCreationModal * panels could push their action buttons off-screen with no scroll, the * ChatDrawer FAB + composer sat under the home indicator, the * ShotTrackingComprehensive putt quick-select packed six <44pt targets @@ -18,7 +18,8 @@ import { join, resolve } from 'node:path'; * past the card edge with no horizontal scroll. * * The fixes: - * - IntentDrawer / GoalCreationModal panels: `max-h-[90dvh]` + + * - GoalCreationModal panel: `max-h-[90dvh]` + + * (IntentDrawer was deleted 2026-07-10 in the agent-legibility dead-code sweep) * `overflow-y-auto` so the form scrolls within the dynamic viewport. * - ChatDrawer FAB: bottom calc that adds `env(safe-area-inset-bottom)`. * - ChatComposer: bottom padding adds `env(safe-area-inset-bottom)`. @@ -39,7 +40,6 @@ const REPO_ROOT = resolve(import.meta.dirname, '..', '..'); const COMPONENTS = resolve(REPO_ROOT, 'src', 'components'); const FILES = { - intentDrawer: join(COMPONENTS, 'golf', 'coachhelm', 'v3', 'IntentDrawer', 'index.tsx'), goalModal: join(COMPONENTS, 'golf', 'coachhelm', 'v3', 'GoalCreationModal', 'index.tsx'), chatDrawer: join(COMPONENTS, 'golf', 'coachhelm', 'v3', 'Chat', 'ChatDrawer.tsx'), chatComposer: join(COMPONENTS, 'golf', 'coachhelm', 'v3', 'Chat', 'ChatComposer.tsx'), @@ -61,12 +61,6 @@ function assertContains(content, needle, file, why) { ); } -test('IntentDrawer panel is dvh-capped and scrolls', async () => { - const src = await read(FILES.intentDrawer); - assertContains(src, 'max-h-[90dvh]', FILES.intentDrawer, 'panel must cap to 90% of the dynamic viewport on short screens'); - assertContains(src, 'overflow-y-auto', FILES.intentDrawer, 'panel content must scroll instead of pushing actions off-screen'); -}); - test('GoalCreationModal panel is dvh-capped and scrolls', async () => { const src = await read(FILES.goalModal); assertContains(src, 'max-h-[90dvh]', FILES.goalModal, 'panel must cap to 90% of the dynamic viewport on short screens'); diff --git a/src/app/soreness-preview/page.tsx b/src/app/soreness-preview/page.tsx deleted file mode 100644 index 2f635ef38..000000000 --- a/src/app/soreness-preview/page.tsx +++ /dev/null @@ -1,45 +0,0 @@ -'use client'; - -// THROWAWAY preview route for visual QA of the Lift Lab soreness body map. -// DELETE after visual verification — not part of the product. - -import { useState } from 'react'; -import { SorenessBodyMap, type SorenessMapState } from '@/components/lifting/soreness'; - -export default function SorenessPreviewPage() { - const [value, setValue] = useState({ - right_shoulder: { severity: 6, tags: ['Tight', 'Sharp'], note: 'Felt it after long toss' }, - right_hip: { severity: 4, tags: [], note: '' }, - } as SorenessMapState); - - return ( -
-
-

- Helm Lifting Lab -

-

- Soreness Check -

-

Due before 10:00 AM

-
- -
- ); -} diff --git a/src/components/baseball/command-center/analytics/GameVsPracticePanel.tsx b/src/components/baseball/command-center/analytics/GameVsPracticePanel.tsx deleted file mode 100644 index 508ce8c98..000000000 --- a/src/components/baseball/command-center/analytics/GameVsPracticePanel.tsx +++ /dev/null @@ -1,560 +0,0 @@ -'use client'; - -import { useMemo, useState } from 'react'; -import { cn } from '@/lib/utils'; -import type { BaseballRosterPlayer } from '@/lib/types'; -import { - IconTrendingUp, - IconTrendingDown, - IconMinus, - IconActivity, - IconTarget, - IconMaximize, - IconInfo, -} from '@/components/icons'; -import { Avatar } from '@/components/ui/avatar'; -import { StatTypeFilter, type StatCategory } from './StatTypeFilter'; -import { Button, IconButton } from '@/components/ui/button'; -import { - PressureGapIndicator, - getGapColor, - getGapBgColor, -} from './PressureGapIndicator'; -import { PaperCard } from '@/components/baseball/living-annual'; - -// ============================================================================ -// TYPES -// ============================================================================ - -interface GameVsPracticePanelProps { - players: BaseballRosterPlayer[]; - onExpandClick?: () => void; - className?: string; -} - -interface PlayerPressure { - id: string; - name: string; - avatarUrl: string | null; - position: string | null; - practiceAvg: number; - gameAvg: number; - gap: number; // Positive = better in games, Negative = struggles in games - practiceSessions: number; - gameSessions: number; - trend: 'up' | 'down' | 'stable' | null; -} - -// ============================================================================ -// HELPER FUNCTIONS -// ============================================================================ - -function formatAvg(value: number | null | undefined): string { - if (value == null) return '---'; - return value.toFixed(3).replace(/^0\./, '.'); -} - -function formatGap(gap: number): string { - const points = Math.abs(gap * 1000).toFixed(0); - return `${gap >= 0 ? '+' : '-'}${points} pts`; -} - -// ============================================================================ -// STAT BREAKDOWN BY TIER -// ============================================================================ - -interface StatBreakdownProps { - players: PlayerPressure[]; - category: StatCategory; -} - -function StatBreakdown({ players, category }: StatBreakdownProps) { - const tiers = useMemo(() => { - const clutch = players.filter((p) => p.gap > 0.02); - const consistent = players.filter((p) => p.gap >= -0.02 && p.gap <= 0.02); - const struggling = players.filter((p) => p.gap < -0.02); - - return { clutch, consistent, struggling }; - }, [players]); - - const categoryLabel = - category === 'all' - ? 'Overall' - : category.charAt(0).toUpperCase() + category.slice(1); - - return ( -
-

- {categoryLabel} Performance Breakdown -

- -
- {/* Clutch */} -
-
- - {tiers.clutch.length} -
- Clutch -
- - {/* Consistent */} -
-
- - {tiers.consistent.length} -
- Consistent -
- - {/* Struggling */} -
-
- - {tiers.struggling.length} -
- Struggling -
-
- - {/* Session count summary */} -
- - Avg Practice Sessions:{' '} - {players.length > 0 - ? ( - players.reduce((sum, p) => sum + p.practiceSessions, 0) / players.length - ).toFixed(1) - : 0} - - - Avg Game Sessions:{' '} - {players.length > 0 - ? ( - players.reduce((sum, p) => sum + p.gameSessions, 0) / players.length - ).toFixed(1) - : 0} - -
-
- ); -} - -// ============================================================================ -// PLAYER ROW COMPONENT -// ============================================================================ - -interface PlayerRowProps { - player: PlayerPressure; - showDetails: boolean; - onToggleDetails: () => void; -} - -function PlayerRow({ player, showDetails, onToggleDetails }: PlayerRowProps) { - return ( -
- - - {/* Expanded details */} - {showDetails && ( -
- {/* Side-by-side comparison */} -
-
-
- - Practice -
- - {formatAvg(player.practiceAvg)} - -

- {player.practiceSessions} sessions -

-
-
-
- - Game -
- - {formatAvg(player.gameAvg)} - -

- {player.gameSessions} sessions -

-
-
- - {/* Pressure gap indicator */} - - - {/* Trend indicator */} - {player.trend && ( -
- {player.trend === 'up' && ( - <> - - Game performance trending up - - )} - {player.trend === 'down' && ( - <> - - Game performance trending down - - )} - {player.trend === 'stable' && ( - <> - - Stable game performance - - )} -
- )} -
- )} -
- ); -} - -// ============================================================================ -// MAIN COMPONENT -// ============================================================================ - -export function GameVsPracticePanel({ - players, - onExpandClick, - className, -}: GameVsPracticePanelProps) { - // NOTE: practice_avg / game_avg / pressure_gap are career aggregates computed - // from all imported sessions with no date sub-slice. There is no date dimension - // on the underlying data exposed to this component, so time-range filtering is - // not supported. The filter UI is intentionally omitted here to avoid showing a - // control that would silently do nothing. - const [statCategory, setStatCategory] = useState('all'); - const [expandedPlayerId, setExpandedPlayerId] = useState(null); - const [showAllPlayers, setShowAllPlayers] = useState(false); - - // Process player data — career aggregates only; no per-period slicing. - const pressureData = useMemo(() => { - const playersWithBothTypes = players.filter( - (p) => - p.aggregates?.practice_avg != null && - p.aggregates?.game_avg != null && - p.aggregates?.pressure_gap != null - ); - - if (playersWithBothTypes.length === 0) return null; - - const data: PlayerPressure[] = playersWithBothTypes.map((p) => ({ - id: p.id, - name: `${p.first_name || ''} ${p.last_name || ''}`.trim(), - avatarUrl: p.avatar_url, - position: p.primary_position, - practiceAvg: p.aggregates!.practice_avg!, - gameAvg: p.aggregates!.game_avg!, - gap: p.aggregates!.pressure_gap!, - practiceSessions: p.aggregates!.practice_sessions || 0, - gameSessions: p.aggregates!.game_sessions || 0, - trend: p.aggregates!.recent_trend as 'up' | 'down' | 'stable' | null, - })); - - // Sort by gap magnitude (biggest gaps first) - data.sort((a, b) => Math.abs(b.gap) - Math.abs(a.gap)); - - return data; - }, [players]); - - // Team summary stats - const teamSummary = useMemo(() => { - if (!pressureData || pressureData.length === 0) return null; - - const clutchPlayers = pressureData.filter((p) => p.gap > 0.02).length; - const strugglePlayers = pressureData.filter((p) => p.gap < -0.02).length; - const avgGap = - pressureData.reduce((sum, p) => sum + p.gap, 0) / pressureData.length; - - return { - clutchPlayers, - strugglePlayers, - avgGap, - totalTracked: pressureData.length, - }; - }, [pressureData]); - - // Displayed players (limited or all) - const displayedPlayers = useMemo(() => { - if (!pressureData) return []; - return showAllPlayers ? pressureData : pressureData.slice(0, 6); - }, [pressureData, showAllPlayers]); - - // Empty state - if (!pressureData || pressureData.length === 0) { - return ( - -

- Practice vs Game Performance -

-
-
- -
-

- No comparison data available yet -

-

- Upload both practice and game stats to see how your players perform under - pressure. -

-
-
- ); - } - - return ( - - {/* Header */} -
-
-
-

Practice vs Game

- {onExpandClick && ( - - - - )} -
-

- {teamSummary?.totalTracked} players with comparison data -

-
-
- {teamSummary && ( -
- - - {teamSummary.clutchPlayers} clutch - - - - {teamSummary.strugglePlayers} struggle - -
- )} -
-
- - {/* Filters Row — TimeRange is not available for this view (career aggregates only) */} -
- -
-
-
- - {/* Team Average Gap Card */} - {teamSummary && ( -
-
-
- - Team Pressure Gap - -

- {teamSummary.avgGap >= 0 - ? 'Team rises to the occasion in games' - : 'Team needs to work on game-day execution'} -

-
-
- {teamSummary.avgGap > 0.01 ? ( - - ) : teamSummary.avgGap < -0.01 ? ( - - ) : ( - - )} - - {formatGap(teamSummary.avgGap)} - -
-
- - {/* Mobile summary */} -
- - - {teamSummary.clutchPlayers} clutch - - - - {teamSummary.strugglePlayers} struggle - -
-
- )} - - {/* Stat Breakdown */} -
- -
- - {/* Player List */} -
- {displayedPlayers.map((player) => ( - - setExpandedPlayerId(expandedPlayerId === player.id ? null : player.id) - } - /> - ))} -
- - {/* Show more/less button */} - {pressureData.length > 6 && ( - - )} -
- ); -} - -// ============================================================================ -// SKELETON COMPONENT -// ============================================================================ - -export function GameVsPracticePanelSkeleton() { - return ( - - {/* Header skeleton */} -
-
-
-
-
-
-
-
-
-
- - {/* Filter skeleton */} -
-
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
-
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
-
- - {/* Summary card skeleton */} -
-
-
-
-
-
-
-
-
- - {/* Breakdown skeleton */} -
-
-
- {Array.from({ length: 3 }).map((_, i) => ( -
- ))} -
-
- - {/* Player list skeleton */} -
- {Array.from({ length: 5 }).map((_, i) => ( -
-
-
-
-
-
-
-
- ))} -
- - ); -} diff --git a/src/components/baseball/command-center/analytics/PlayerPerformanceGrid.tsx b/src/components/baseball/command-center/analytics/PlayerPerformanceGrid.tsx deleted file mode 100644 index d5c460be1..000000000 --- a/src/components/baseball/command-center/analytics/PlayerPerformanceGrid.tsx +++ /dev/null @@ -1,289 +0,0 @@ -'use client'; - -import { useMemo, useState } from 'react'; -import { cn } from '@/lib/utils'; -import type { BaseballRosterPlayer } from '@/lib/types'; -import { IconChevronDown, IconChevronUp } from '@/components/icons'; -import { PaperCard } from '@/components/baseball/living-annual'; - -interface PlayerPerformanceGridProps { - players: BaseballRosterPlayer[]; -} - -type MetricKey = 'avg' | 'obp' | 'slg' | 'ops' | 'sessions'; - -interface MetricConfig { - key: MetricKey; - label: string; - shortLabel: string; - getValue: (p: BaseballRosterPlayer) => number | null; - format: (v: number) => string; - thresholds: { excellent: number; good: number; average: number }; - higherIsBetter: boolean; -} - -const METRICS: MetricConfig[] = [ - { - key: 'avg', - label: 'Batting Average', - shortLabel: 'AVG', - getValue: (p) => p.aggregates?.career_avg ?? null, - format: (v) => v.toFixed(3).replace(/^0\./, '.'), - thresholds: { excellent: 0.32, good: 0.28, average: 0.24 }, - higherIsBetter: true, - }, - { - key: 'obp', - label: 'On-Base Percentage', - shortLabel: 'OBP', - getValue: (p) => p.aggregates?.career_obp ?? null, - format: (v) => v.toFixed(3).replace(/^0\./, '.'), - thresholds: { excellent: 0.4, good: 0.35, average: 0.3 }, - higherIsBetter: true, - }, - { - key: 'slg', - label: 'Slugging Percentage', - shortLabel: 'SLG', - getValue: (p) => p.aggregates?.career_slg ?? null, - format: (v) => v.toFixed(3).replace(/^0\./, '.'), - thresholds: { excellent: 0.5, good: 0.42, average: 0.35 }, - higherIsBetter: true, - }, - { - key: 'ops', - label: 'OPS', - shortLabel: 'OPS', - getValue: (p) => { - const obp = p.aggregates?.career_obp; - const slg = p.aggregates?.career_slg; - return obp != null && slg != null ? obp + slg : null; - }, - format: (v) => v.toFixed(3), - thresholds: { excellent: 0.9, good: 0.77, average: 0.65 }, - higherIsBetter: true, - }, - { - key: 'sessions', - label: 'Total Sessions', - shortLabel: 'GP', - getValue: (p) => p.aggregates?.total_sessions ?? null, - format: (v) => v.toString(), - thresholds: { excellent: 20, good: 10, average: 5 }, - higherIsBetter: true, - }, -]; - -function getHeatmapColor( - value: number | null, - config: MetricConfig -): string { - if (value == null) return 'bg-warm-100 text-warm-400'; - - const { thresholds, higherIsBetter } = config; - - // Determine performance level - let level: 'excellent' | 'good' | 'average' | 'below'; - if (higherIsBetter) { - if (value >= thresholds.excellent) level = 'excellent'; - else if (value >= thresholds.good) level = 'good'; - else if (value >= thresholds.average) level = 'average'; - else level = 'below'; - } else { - if (value <= thresholds.excellent) level = 'excellent'; - else if (value <= thresholds.good) level = 'good'; - else if (value <= thresholds.average) level = 'average'; - else level = 'below'; - } - - // Return Tailwind classes for heat map colors. Living Annual ink system: - // both below-average tiers read pursuit clay (never amber/red), at two - // weights — soft tint for "average", a firmer tint + hairline for "below" - // — so the two remain visually distinguishable without relying on hue alone. - const colors: Record = { - excellent: 'bg-primary-500 text-white', - good: 'bg-primary-200 text-primary-900', - average: 'bg-pursuit/10 text-pursuit', - below: 'bg-pursuit/20 text-pursuit border border-pursuit/40', - }; - - return colors[level] ?? 'bg-warm-100 text-warm-600'; -} - -export function PlayerPerformanceGrid({ players }: PlayerPerformanceGridProps) { - const [sortMetric, setSortMetric] = useState('avg'); - const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); - - const sortedPlayers = useMemo(() => { - const metric = METRICS.find((m) => m.key === sortMetric); - if (!metric) return players; - - return [...players].sort((a, b) => { - const aVal = metric.getValue(a) ?? (sortDirection === 'desc' ? -Infinity : Infinity); - const bVal = metric.getValue(b) ?? (sortDirection === 'desc' ? -Infinity : Infinity); - return sortDirection === 'desc' ? bVal - aVal : aVal - bVal; - }); - }, [players, sortMetric, sortDirection]); - - const handleSort = (key: MetricKey) => { - if (sortMetric === key) { - setSortDirection((d) => (d === 'desc' ? 'asc' : 'desc')); - } else { - setSortMetric(key); - setSortDirection('desc'); - } - }; - - if (players.length === 0) { - return ( - -

Performance Grid

-

- No player data available. -

-
- ); - } - - return ( - -
-

Performance Grid

-

- Click column headers to sort · Colors indicate performance level -

-
- -
- - - - - {METRICS.map((metric) => ( - - ))} - - - - {sortedPlayers.slice(0, 15).map((player) => { - const name = `${player.first_name || ''} ${player.last_name || ''}`.trim(); - return ( - - - {METRICS.map((metric) => { - const value = metric.getValue(player); - return ( - - ); - })} - - ); - })} - -
- Player - handleSort(metric.key)} - > -
- {metric.shortLabel} - {sortMetric === metric.key && ( - sortDirection === 'desc' ? ( - - ) : ( - - ) - )} -
-
-
- - {name} - - {player.primary_position && ( - - {player.primary_position} - - )} -
-
-
- {value != null ? metric.format(value) : '—'} -
-
-
- - {players.length > 15 && ( -
-

- Showing top 15 of {players.length} players -

-
- )} - - {/* Legend */} -
-
-
-
- Excellent -
-
-
- Good -
-
-
- Average -
-
-
- Below Avg -
-
-
- - ); -} - -export function PlayerPerformanceGridSkeleton() { - return ( - -
-
-
-
- -
-
- {/* Header */} -
-
- {Array.from({ length: 6 }).map((_, i) => ( -
- ))} -
- {/* Rows */} - {Array.from({ length: 8 }).map((_, i) => ( -
-
- {Array.from({ length: 6 }).map((_, j) => ( -
- ))} -
- ))} -
-
- - ); -} diff --git a/src/components/baseball/command-center/analytics/PressureGapIndicator.tsx b/src/components/baseball/command-center/analytics/PressureGapIndicator.tsx deleted file mode 100644 index 81e7eb3f1..000000000 --- a/src/components/baseball/command-center/analytics/PressureGapIndicator.tsx +++ /dev/null @@ -1,209 +0,0 @@ -'use client'; - -import { cn } from '@/lib/utils'; -import { - IconTrendingUp, - IconTrendingDown, - IconMinus, -} from '@/components/icons'; - -// ============================================================================ -// TYPES -// ============================================================================ - -interface PressureGapIndicatorProps { - gap: number; // Positive = better in games, Negative = struggles in games - practiceAvg: number; - gameAvg: number; - compact?: boolean; - showLabels?: boolean; - size?: 'sm' | 'md' | 'lg'; - className?: string; -} - -// ============================================================================ -// HELPERS -// ============================================================================ - -function formatAvg(value: number | null | undefined): string { - if (value == null) return '---'; - return value.toFixed(3).replace(/^0\./, '.'); -} - -function formatGap(gap: number): string { - const points = Math.abs(gap * 1000).toFixed(0); - return `${gap >= 0 ? '+' : '-'}${points} pts`; -} - -export function getGapColor(gap: number): string { - if (gap > 0.02) return 'text-primary-600'; - // Negative (pressure-struggle) gap reads pursuit clay — Living Annual ink - // system, never raw red. - if (gap < -0.02) return 'text-pursuit'; - return 'text-warm-500'; -} - -export function getGapBgColor(gap: number): string { - if (gap > 0.02) return 'bg-primary-50'; - if (gap < -0.02) return 'bg-pursuit/10'; - return 'bg-warm-50'; -} - -function getGapLabel(gap: number): string { - if (gap > 0.02) return 'clutch performer'; - if (gap < -0.02) return 'pressure struggles'; - return 'consistent'; -} - -// ============================================================================ -// COMPACT INDICATOR -// ============================================================================ - -function CompactIndicator({ - gap, - size = 'sm', -}: { - gap: number; - size: PressureGapIndicatorProps['size']; -}) { - const iconSize = size === 'lg' ? 18 : size === 'md' ? 16 : 14; - const textClass = - size === 'lg' ? 'text-base' : size === 'md' ? 'text-sm' : 'text-sm'; - - return ( -
- {gap > 0.01 ? ( - - ) : gap < -0.01 ? ( - - ) : ( - - )} - - {formatGap(gap)} - -
- ); -} - -// ============================================================================ -// FULL INDICATOR -// ============================================================================ - -function FullIndicator({ - gap, - practiceAvg, - gameAvg, - showLabels, - size = 'md', -}: { - gap: number; - practiceAvg: number; - gameAvg: number; - showLabels?: boolean; - size: PressureGapIndicatorProps['size']; -}) { - // Calculate percentage for visual bar (capped at -100 to +100 points) - const maxPoints = 100; - const gapPoints = gap * 1000; - const barPercentage = Math.min(Math.abs(gapPoints) / maxPoints, 1) * 50; - const isPositive = gap >= 0; - - const barHeight = size === 'lg' ? 'h-3' : size === 'md' ? 'h-2' : 'h-1.5'; - const iconSize = size === 'lg' ? 18 : size === 'md' ? 16 : 14; - const valueClass = - size === 'lg' ? 'text-lg' : size === 'md' ? 'text-sm' : 'text-xs'; - const labelClass = size === 'lg' ? 'text-sm' : 'text-xs'; - - return ( -
- {/* Labels */} - {showLabels && ( -
- Practice: {formatAvg(practiceAvg)} - Game: {formatAvg(gameAvg)} -
- )} - - {/* Visual bar */} -
- {/* Center marker */} -
- - {/* Gap fill */} -
-
- - {/* Gap value and label */} -
- {gap > 0.01 ? ( - - ) : gap < -0.01 ? ( - - ) : ( - - )} - - {formatGap(gap)} - - - {getGapLabel(gap)} - -
-
- ); -} - -// ============================================================================ -// MAIN COMPONENT -// ============================================================================ - -export function PressureGapIndicator({ - gap, - practiceAvg, - gameAvg, - compact = false, - showLabels = true, - size = 'md', - className, -}: PressureGapIndicatorProps) { - if (compact) { - return ( -
- -
- ); - } - - return ( -
- -
- ); -} - diff --git a/src/components/baseball/command-center/analytics/StatTypeFilter.tsx b/src/components/baseball/command-center/analytics/StatTypeFilter.tsx deleted file mode 100644 index 6a6bc7200..000000000 --- a/src/components/baseball/command-center/analytics/StatTypeFilter.tsx +++ /dev/null @@ -1,107 +0,0 @@ -'use client'; - -import { cn } from '@/lib/utils'; -import { Button } from '@/components/ui/button'; -import { - IconChartBar, - IconTarget, - IconActivity, - IconShield, -} from '@/components/icons'; - -// ============================================================================ -// TYPES -// ============================================================================ - -export type StatCategory = 'all' | 'batting' | 'pitching' | 'fielding'; - -interface StatCategoryOption { - value: StatCategory; - label: string; - shortLabel: string; - icon: React.ReactNode; - description: string; -} - -interface StatTypeFilterProps { - value: StatCategory; - onChange: (value: StatCategory) => void; - size?: 'sm' | 'md'; - showLabels?: boolean; - className?: string; -} - -// ============================================================================ -// CONSTANTS -// ============================================================================ - -const STAT_CATEGORIES: StatCategoryOption[] = [ - { - value: 'all', - label: 'All Stats', - shortLabel: 'All', - icon: , - description: 'View all stat types combined', - }, - { - value: 'batting', - label: 'Batting', - shortLabel: 'Bat', - icon: , - description: 'AVG, OBP, SLG, OPS, K%, BB%', - }, - { - value: 'pitching', - label: 'Pitching', - shortLabel: 'Pitch', - icon: , - description: 'ERA, WHIP, K/9, BB/9, velo', - }, - { - value: 'fielding', - label: 'Fielding', - shortLabel: 'Field', - icon: , - description: 'FLD%, errors, putouts, assists', - }, -]; - -// ============================================================================ -// MAIN COMPONENT -// ============================================================================ - -export function StatTypeFilter({ - value, - onChange, - size = 'sm', - showLabels = true, - className, -}: StatTypeFilterProps) { - return ( -
- {STAT_CATEGORIES.map((cat) => ( - - ))} -
- ); -} - diff --git a/src/components/baseball/command-center/analytics/TeamBattingOverview.tsx b/src/components/baseball/command-center/analytics/TeamBattingOverview.tsx deleted file mode 100644 index 9cdc02778..000000000 --- a/src/components/baseball/command-center/analytics/TeamBattingOverview.tsx +++ /dev/null @@ -1,215 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import { cn } from '@/lib/utils'; -import type { BaseballRosterPlayer } from '@/lib/types'; -import { - IconActivity, - IconTarget, - IconTrendingUp, - IconChartBar, -} from '@/components/icons'; -import { PaperCard } from '@/components/baseball/living-annual'; - -interface TeamBattingOverviewProps { - players: BaseballRosterPlayer[]; -} - -interface TeamStats { - teamAvg: number | null; - teamOBP: number | null; - teamSLG: number | null; - teamOPS: number | null; - playersWithData: number; - totalAtBats: number; -} - -function calculateTeamStats(players: BaseballRosterPlayer[]): TeamStats { - const playersWithAggregates = players.filter((p) => p.aggregates?.career_avg != null); - - if (playersWithAggregates.length === 0) { - return { - teamAvg: null, - teamOBP: null, - teamSLG: null, - teamOPS: null, - playersWithData: 0, - totalAtBats: 0, - }; - } - - // Calculate weighted averages based on total sessions (as proxy for at-bats) - let totalWeight = 0; - let weightedAvg = 0; - let obpWeight = 0; - let weightedOBP = 0; - let slgWeight = 0; - let weightedSLG = 0; - let totalAtBats = 0; - - for (const player of playersWithAggregates) { - const agg = player.aggregates!; - const weight = agg.total_sessions || 1; - totalWeight += weight; - totalAtBats += agg.total_sessions || 0; - - if (agg.career_avg != null) { - weightedAvg += agg.career_avg * weight; - } - if (agg.career_obp != null) { - obpWeight += weight; - weightedOBP += agg.career_obp * weight; - } - if (agg.career_slg != null) { - slgWeight += weight; - weightedSLG += agg.career_slg * weight; - } - } - - const teamAvg = totalWeight > 0 ? weightedAvg / totalWeight : null; - const teamOBP = obpWeight > 0 ? weightedOBP / obpWeight : null; - const teamSLG = slgWeight > 0 ? weightedSLG / slgWeight : null; - const teamOPS = teamOBP != null && teamSLG != null ? teamOBP + teamSLG : null; - - return { - teamAvg, - teamOBP, - teamSLG, - teamOPS, - playersWithData: playersWithAggregates.length, - totalAtBats, - }; -} - -function formatAvg(value: number | null): string { - if (value == null) return '---'; - return value.toFixed(3).replace(/^0\./, '.'); -} - -function formatOPS(value: number | null): string { - if (value == null) return '---'; - return value.toFixed(3); -} - -function getStatColor(value: number | null, type: 'avg' | 'obp' | 'slg' | 'ops'): string { - if (value == null) return 'text-warm-400'; - - // Thresholds for good/average/below average - const thresholds: Record = { - avg: { good: 0.3, average: 0.25 }, - obp: { good: 0.38, average: 0.32 }, - slg: { good: 0.45, average: 0.38 }, - ops: { good: 0.8, average: 0.7 }, - }; - - const t = thresholds[type]; - if (!t) return 'text-warm-900'; - if (value >= t.good) return 'text-primary-600'; - if (value >= t.average) return 'text-warm-900'; - // Below-average tier reads pursuit clay (Living Annual ink system), never amber. - return 'text-pursuit'; -} - -export function TeamBattingOverview({ players }: TeamBattingOverviewProps) { - const stats = useMemo(() => calculateTeamStats(players), [players]); - - const statCards = [ - { - label: 'Team AVG', - value: formatAvg(stats.teamAvg), - icon: IconTarget, - color: getStatColor(stats.teamAvg, 'avg'), - description: 'Batting Average', - }, - { - label: 'Team OBP', - value: formatAvg(stats.teamOBP), - icon: IconActivity, - color: getStatColor(stats.teamOBP, 'obp'), - description: 'On-Base %', - }, - { - label: 'Team SLG', - value: formatAvg(stats.teamSLG), - icon: IconTrendingUp, - color: getStatColor(stats.teamSLG, 'slg'), - description: 'Slugging %', - }, - { - label: 'Team OPS', - value: formatOPS(stats.teamOPS), - icon: IconChartBar, - color: getStatColor(stats.teamOPS, 'ops'), - description: 'OBP + SLG', - }, - ]; - - if (stats.playersWithData === 0) { - return ( - -

Team Batting

-

- No batting data available yet. Upload stats to see team performance. -

-
- ); - } - - return ( - -
-

Team Batting

- - {stats.playersWithData} players · {stats.totalAtBats} sessions - -
- -
- {statCards.map((stat) => ( -
-
- - - {stat.label} - -
-

- {stat.value} -

-

{stat.description}

-
- ))} -
-
- ); -} - -export function TeamBattingOverviewSkeleton() { - return ( - -
-
-
-
- -
- {Array.from({ length: 4 }).map((_, i) => ( -
-
-
-
-
-
-
-
- ))} -
- - ); -} diff --git a/src/components/baseball/command-center/analytics/TrendAnalysisPanel.tsx b/src/components/baseball/command-center/analytics/TrendAnalysisPanel.tsx deleted file mode 100644 index f8729f238..000000000 --- a/src/components/baseball/command-center/analytics/TrendAnalysisPanel.tsx +++ /dev/null @@ -1,317 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import Link from 'next/link'; -import type { BaseballRosterPlayer } from '@/lib/types'; -import { - IconTrendingUp, - IconTrendingDown, - IconMinus, - IconChevronRight, - IconBolt, - IconAlertCircle, -} from '@/components/icons'; -import { Avatar } from '@/components/ui/avatar'; -import { PaperCard } from '@/components/baseball/living-annual'; - -interface TrendAnalysisPanelProps { - players: BaseballRosterPlayer[]; -} - -interface TrendPlayer { - id: string; - name: string; - avatarUrl: string | null; - position: string | null; - trend: 'improving' | 'stable' | 'declining'; - trendMagnitude: number; - careerAvg: number | null; - last5Avg: number | null; - last10Avg: number | null; - sessions: number; -} - -function formatAvg(value: number | null): string { - if (value == null) return '—'; - return value.toFixed(3).replace(/^0\./, '.'); -} - -function formatChange(current: number | null, previous: number | null): string { - if (current == null || previous == null) return ''; - const diff = (current - previous) * 1000; - const sign = diff >= 0 ? '+' : ''; - return `${sign}${diff.toFixed(0)} pts`; -} - -export function TrendAnalysisPanel({ players }: TrendAnalysisPanelProps) { - const trendData = useMemo(() => { - const playersWithTrends = players.filter( - (p) => p.aggregates?.recent_trend != null - ); - - const data: TrendPlayer[] = playersWithTrends.map((p) => ({ - id: p.id, - name: `${p.first_name || ''} ${p.last_name || ''}`.trim(), - avatarUrl: p.avatar_url, - position: p.primary_position, - trend: p.aggregates!.recent_trend!, - trendMagnitude: p.aggregates!.trend_magnitude ?? 0, - careerAvg: p.aggregates!.career_avg, - last5Avg: p.aggregates!.last_5_avg, - last10Avg: p.aggregates!.last_10_avg, - sessions: p.aggregates!.total_sessions || 0, - })); - - // Sort by magnitude (biggest changes first) - data.sort((a, b) => Math.abs(b.trendMagnitude) - Math.abs(a.trendMagnitude)); - - return { - all: data, - improving: data.filter((p) => p.trend === 'improving'), - declining: data.filter((p) => p.trend === 'declining'), - stable: data.filter((p) => p.trend === 'stable'), - }; - }, [players]); - - const summary = useMemo(() => { - return { - improvingCount: trendData.improving.length, - decliningCount: trendData.declining.length, - stableCount: trendData.stable.length, - hottest: trendData.improving[0], - coldest: trendData.declining[0], - }; - }, [trendData]); - - if (trendData.all.length === 0) { - return ( - -

Trend Analysis

-

- Need more session data to analyze player trends. Keep tracking - performance! -

-
- ); - } - - return ( - -

Trend Analysis

- - {/* Summary Stats */} -
-
-
- - Rising -
-

- {summary.improvingCount} -

-
-
-
- - Steady -
-

- {summary.stableCount} -

-
-
-
- - Falling -
-

- {summary.decliningCount} -

-
-
- - {/* Hottest & Coldest Highlights */} - {(summary.hottest || summary.coldest) && ( -
- {summary.hottest && ( -
-
- -
-
-
- - Hottest - -
-

- {summary.hottest.name} -

-

- {formatAvg(summary.hottest.last5Avg)} last 5 games - - {formatChange( - summary.hottest.last5Avg, - summary.hottest.careerAvg - )} - -

-
- - - -
- )} - - {summary.coldest && ( -
-
- -
-
-
- - Needs Attention - -
-

- {summary.coldest.name} -

-

- {formatAvg(summary.coldest.last5Avg)} last 5 games - - {formatChange( - summary.coldest.last5Avg, - summary.coldest.careerAvg - )} - -

-
- - - -
- )} -
- )} - - {/* Player Trend List */} -
-

- All Players by Trend -

- -
- {trendData.all.slice(0, 10).map((player) => ( - - - -
-

- {player.name} -

-

- {formatAvg(player.careerAvg)} career · {player.sessions} games -

-
- -
- {player.trend === 'improving' ? ( -
- - - +{(player.trendMagnitude * 100).toFixed(1)}% - -
- ) : player.trend === 'declining' ? ( -
- - - {(player.trendMagnitude * 100).toFixed(1)}% - -
- ) : ( -
- - Stable -
- )} - - -
- - ))} -
- - {trendData.all.length > 10 && ( -

- +{trendData.all.length - 10} more players -

- )} -
-
- ); -} - -export function TrendAnalysisPanelSkeleton() { - return ( - -
- - {/* Summary Stats */} -
- {Array.from({ length: 3 }).map((_, i) => ( -
-
-
-
- ))} -
- - {/* Highlights */} -
- {Array.from({ length: 2 }).map((_, i) => ( -
-
-
-
-
-
-
-
- ))} -
- - {/* List */} -
- {Array.from({ length: 5 }).map((_, i) => ( -
-
-
-
-
-
-
-
- ))} -
- - ); -} diff --git a/src/components/baseball/command-center/analytics/index.ts b/src/components/baseball/command-center/analytics/index.ts deleted file mode 100644 index 92e65ac4e..000000000 --- a/src/components/baseball/command-center/analytics/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Team Overview -export { - TeamBattingOverview, - TeamBattingOverviewSkeleton, -} from './TeamBattingOverview'; - -// Game vs Practice Analysis -export { - GameVsPracticePanel, - GameVsPracticePanelSkeleton, -} from './GameVsPracticePanel'; - -// Player Performance -export { - PlayerPerformanceGrid, - PlayerPerformanceGridSkeleton, -} from './PlayerPerformanceGrid'; - -// Trend Analysis -export { - TrendAnalysisPanel, - TrendAnalysisPanelSkeleton, -} from './TrendAnalysisPanel'; diff --git a/src/components/demo/DemoEnterTracker.tsx b/src/components/demo/DemoEnterTracker.tsx index bbf402953..87ea38ce9 100644 --- a/src/components/demo/DemoEnterTracker.tsx +++ b/src/components/demo/DemoEnterTracker.tsx @@ -9,10 +9,11 @@ * * Mounted in the dashboard layout as a pure side-effect leaf (renders null). * Placement decision: the dashboard layout (src/app/golf/(dashboard)/layout.tsx) - * is a server component that renders GolfDashboardShell (a client component). - * Rather than converting the layout to a client component or adding state to - * the shell, DemoEnterTracker is rendered as a sibling to {children} inside - * GolfDashboardShell (see mounting instructions below). If that proves + * is a server component that renders FairwayDashboardShell (a client + * component; successor to the deleted GolfDashboardShell). Rather than + * converting the layout to a client component or adding state to the shell, + * DemoEnterTracker is rendered as a sibling to {children} inside + * FairwayDashboardShell (see mounting instructions below). If that proves * awkward during shell refactors, it can alternatively be placed in the * dashboard index page — the effect is identical since `?demo=1` only appears * on the initial landing URL. diff --git a/src/components/golf/GolfTabBar.tsx b/src/components/golf/GolfTabBar.tsx deleted file mode 100644 index 94c798068..000000000 --- a/src/components/golf/GolfTabBar.tsx +++ /dev/null @@ -1,145 +0,0 @@ -'use client'; - -/** - * GolfTabBar — editorial tab navigation. - * - * Redesigned May 2026 (audited through impeccable + ui-ux-pro-max). - * The previous treatment was an iOS UISegmentedControl: cream-100 tray, - * white sticker-shadow active pill, cramped count badges. That pattern - * clashed with the California-modern matte vocabulary and stacked four - * colors into a tiny strip. - * - * The new treatment matches the Eyebrow + hairline-rule language used - * by PageHeader and the Round Library period headers: - * - No tray. Tabs sit on the page surface, separated by a single - * hairline rule below. - * - Active state = warm-900 text + 2px primary-accent underline that - * animates in on cubic-bezier(0.16, 1, 0.3, 1) — same easing as - * every other transition in the system. - * - Inactive state = warm-400 text. Hover = warm-700. - * - Count is plain tabular-nums text after the label — no badge shell. - * - Urgent dot = small primary-500 pip top-right of the label, only - * when not active (active state already implies focus). - * - * Compact mode drops the vertical padding for tight toolbars; default - * keeps editorial breath. - */ - -import type { ReactNode } from 'react'; -import { cn } from '@/lib/utils'; -import { triggerHaptic } from '@/lib/utils/capacitor'; -import { Button } from '@/components/ui/button'; - -export interface GolfTabBarItem { - id: T; - label: string; - icon?: ReactNode; - count?: number; - dot?: boolean; -} - -interface GolfTabBarProps { - tabs: GolfTabBarItem[]; - value: T; - onChange: (value: T) => void; - ariaLabel: string; - className?: string; - listClassName?: string; - stretch?: boolean; - scrollable?: boolean; - compact?: boolean; -} - -export function GolfTabBar({ - tabs, - value, - onChange, - ariaLabel, - className, - listClassName, - stretch = false, - scrollable = false, - compact = false, -}: GolfTabBarProps) { - // Vertical breath: compact = tight (toolbars), default = editorial. - // 44px touch target preserved either way (UX rule #2). - const verticalPad = compact ? 'py-2.5' : 'py-3'; - const fontSize = compact ? 'text-body-sm' : 'text-body-sm'; - - return ( -
-
- {tabs.map((tab) => { - const isActive = value === tab.id; - return ( - - ); - })} -
-
- ); -} diff --git a/src/components/golf/calendar/PremiumCalendarClient.tsx b/src/components/golf/calendar/PremiumCalendarClient.tsx index 22092df34..c53a647f4 100644 --- a/src/components/golf/calendar/PremiumCalendarClient.tsx +++ b/src/components/golf/calendar/PremiumCalendarClient.tsx @@ -1133,7 +1133,8 @@ export function PremiumCalendarClient({ )} {/* Header. NOTE: no desktop NotificationCenter here — the global - bell in GolfDashboardShell already covers desktop; mounting a + bell in FairwayDashboardShell (successor to the deleted + GolfDashboardShell) already covers desktop; mounting a second one doubled the 30s notification poller (audit #32). */}
= { - short: 180, - long: 0, - left: 270, - right: 90, - short_left: 225, - short_right: 135, - long_left: 315, - long_right: 45, -}; - -// Friendly labels for directions -const DIRECTION_LABELS: Record = { - short: 'Short', - long: 'Long', - left: 'Left', - right: 'Right', - short_left: 'Short-Left', - short_right: 'Short-Right', - long_left: 'Long-Left', - long_right: 'Long-Right', -}; - -// Colors based on severity/frequency -function getSegmentColor(percentage: number): string { - if (percentage >= 50) return '#ef4444'; // Red - critical - if (percentage >= 35) return '#f59e0b'; // Amber - warning - if (percentage >= 20) return '#22c55e'; // Green - moderate - return '#94a3b8'; // Slate - low -} - -const sizes = { - sm: { viewBox: 200, center: 100, radius: 75, labelRadius: 95 }, - md: { viewBox: 280, center: 140, radius: 105, labelRadius: 130 }, - lg: { viewBox: 360, center: 180, radius: 140, labelRadius: 170 }, -}; - -export function MissPatternChart({ - data, - title = 'Miss Pattern', - subtitle, - size = 'md', - className, - showLegend = true, - animated = true, -}: MissPatternChartProps) { - const prefersReducedMotion = useReducedMotion(); - const { viewBox, center, radius, labelRadius } = sizes[size]; - - // Sort data by percentage for consistent rendering - const sortedData = useMemo(() => { - return [...data] - .filter(d => d.percentage > 0) - .sort((a, b) => b.percentage - a.percentage); - }, [data]); - - // Calculate segment arc paths - const segments = useMemo(() => { - return sortedData.map((item) => { - const angleDeg = DIRECTION_ANGLES[item.direction] ?? 0; - const angleRad = (angleDeg - 90) * (Math.PI / 180); - - // Segment arc span (45 degrees for 8 directions) - const arcSpan = 40; - const startAngle = angleDeg - arcSpan / 2; - const endAngle = angleDeg + arcSpan / 2; - - // Calculate segment radius based on percentage - const segmentRadius = (radius * 0.4) + (radius * 0.6 * (item.percentage / 100)); - - // Arc path - const startRad = (startAngle - 90) * (Math.PI / 180); - const endRad = (endAngle - 90) * (Math.PI / 180); - - const x1 = center + segmentRadius * Math.cos(startRad); - const y1 = center + segmentRadius * Math.sin(startRad); - const x2 = center + segmentRadius * Math.cos(endRad); - const y2 = center + segmentRadius * Math.sin(endRad); - - const largeArc = arcSpan > 180 ? 1 : 0; - - const path = ` - M ${center} ${center} - L ${x1} ${y1} - A ${segmentRadius} ${segmentRadius} 0 ${largeArc} 1 ${x2} ${y2} - Z - `; - - // Label position - const labelX = center + labelRadius * Math.cos(angleRad); - const labelY = center + labelRadius * Math.sin(angleRad); - - return { - ...item, - path, - color: getSegmentColor(item.percentage), - labelX, - labelY, - angleDeg, - }; - }); - }, [sortedData, center, radius, labelRadius]); - - // Determine primary miss (highest percentage) - const primaryMiss = sortedData[0]; - - return ( -
- {/* Header */} -
-

{title}

- {subtitle && ( -

{subtitle}

- )} -
- - {/* Chart Container */} -
- - {/* Background circles for reference */} - - - - - {/* Direction guide lines */} - {[0, 45, 90, 135, 180, 225, 270, 315].map((angle) => { - const rad = (angle - 90) * (Math.PI / 180); - const x2 = center + radius * Math.cos(rad); - const y2 = center + radius * Math.sin(rad); - return ( - - ); - })} - - {/* Segments */} - {segments.map((segment, index) => ( - - ))} - - {/* Center circle (green/target) */} - - - - {/* Direction labels around the chart */} - - LONG - - - SHORT - - - LEFT - - - RIGHT - - - - {/* Center stats overlay */} - {primaryMiss && ( - -
-
- {primaryMiss.percentage}% -
-
- {DIRECTION_LABELS[primaryMiss.direction] || primaryMiss.direction} -
-
-
- )} -
- - {/* Legend */} - {showLegend && sortedData.length > 0 && ( - - {sortedData.slice(0, 4).map((item) => ( -
-
- - {DIRECTION_LABELS[item.direction] || item.direction} - - - {item.percentage}% - -
- ))} - - )} - - {/* Empty state */} - {sortedData.length === 0 && ( - - )} -
- ); -} - diff --git a/src/components/golf/coachhelm/analytics/ShotAnalyticsPanel.tsx b/src/components/golf/coachhelm/analytics/ShotAnalyticsPanel.tsx deleted file mode 100644 index 97aa312bd..000000000 --- a/src/components/golf/coachhelm/analytics/ShotAnalyticsPanel.tsx +++ /dev/null @@ -1,461 +0,0 @@ -'use client'; - -/** - * ShotAnalyticsPanel - Main Shot Analytics Dashboard Component - * - * Combines all visualization components into a cohesive analytics panel - * with AI-generated insights and responsive layout. - */ - -import { useState, useTransition } from 'react'; -import { motion, AnimatePresence, useReducedMotion } from 'framer-motion'; -import { cn } from '@/lib/utils'; -import { Card } from '@/components/ui/card'; -import { Shimmer } from '@/components/ui/shimmer'; -import { EmptyState } from '@/components/ui/empty-state'; -import { IconSparkles, IconRefresh, IconTarget, IconChartRadar, IconChart, IconTrendingUp } from '@/components/icons'; -import { MissPatternChart } from './MissPatternChart'; -import { ShotTypeBreakdown } from './ShotTypeBreakdown'; -import { TrendSummary } from './TrendIndicator'; -import type { PlayerShotAnalytics } from '@/app/golf/actions/shot-analytics'; -import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; -import { Button } from '@/components/ui/button'; - -async function loadShotAnalyticsAction() { - const { getPlayerShotAnalytics } = await import('@/app/golf/actions/shot-analytics'); - return getPlayerShotAnalytics; -} - -interface ShotAnalyticsPanelProps { - playerId: string; - playerName?: string; - initialData?: PlayerShotAnalytics | null; - periodDays?: number; - className?: string; -} - -type TabType = 'overview' | 'patterns' | 'breakdown' | 'trends'; - -export function ShotAnalyticsPanel({ - playerId, - playerName, - initialData, - periodDays = 30, - className, -}: ShotAnalyticsPanelProps) { - const prefersReducedMotion = useReducedMotion(); - const [isPending, startTransition] = useTransition(); - const [data, setData] = useState(initialData || null); - const [error, setError] = useState(null); - const [activeTab, setActiveTab] = useState('overview'); - const [selectedPeriod, setSelectedPeriod] = useState(periodDays); - - const handleRefresh = () => { - startTransition(async () => { - setError(null); - const getPlayerShotAnalytics = await loadShotAnalyticsAction(); - const result = await getPlayerShotAnalytics(playerId, selectedPeriod); - if (result.success) { - setData(result.data); - } else { - setError(result.error); - } - }); - }; - - const handlePeriodChange = (days: number) => { - setSelectedPeriod(days); - startTransition(async () => { - setError(null); - const getPlayerShotAnalytics = await loadShotAnalyticsAction(); - const result = await getPlayerShotAnalytics(playerId, days); - if (result.success) { - setData(result.data); - } else { - setError(result.error); - } - }); - }; - - const tabs: { id: TabType; label: string; icon: React.ReactNode }[] = [ - { id: 'overview', label: 'Overview', icon: }, - { id: 'patterns', label: 'Patterns', icon: }, - { id: 'breakdown', label: 'Breakdown', icon: }, - { id: 'trends', label: 'Trends', icon: }, - ]; - - return ( - setActiveTab(v as TabType)} - className={cn('space-y-4', className)} - > - {/* Header — slim control strip. The "Shot Analytics" label lives on - the outer section tab, so we don't repeat it here; this row is - just period + refresh so it reads as sub-navigation, not a new - page title. */} -
-

- {playerName ? `${playerName} · ` : ''}Last {selectedPeriod} days -

- -
- {/* Period selector — segmented control for the small set of windows - we actually support. Replaces a legacy setAlertPosture(p.value)} - className="h-4 w-4 text-primary-600 focus:ring-primary-500" - data-testid={`intent-posture-${p.value}`} - /> - - ))} -
- - {/* Notes */} - -