Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d2aa1a5
fix(security): W0 — P0/P1 privacy, auth, and correctness wave
Jul 9, 2026
ffd0fd8
feat(golf): W1 — Fairway unconditional, legacy dual-tree deleted (gol…
Jul 9, 2026
84048a3
feat(baseball): W3+W4 — player nav to 8 tabs, Living Annual completion
Jul 9, 2026
5f49490
feat(golf): W2 — nav consolidated to 8 coach hubs / 8 player tabs
Jul 9, 2026
6866580
feat(baseball): W5 — Lift Lab chrome, a11y P1s, dead-code + manifest …
Jul 9, 2026
0a13f50
feat(golf+admin): W6+W9 — green ruled leaders, join-page fixes, admin…
Jul 9, 2026
3ebc803
Merge branch 'wave/w5-lift-a11y-deadcode' into integration/mission-ve…
Jul 9, 2026
c9935b6
fix(verify): Phase D round 1 — 19 adversarially-confirmed findings fixed
Jul 9, 2026
fbcac24
fix(verify): Phase D round 2 — 6 confirmed findings + follow-ups
Jul 9, 2026
2019db3
docs(matrix): W7 — truthful re-grade of all 22 readiness rows against…
Jul 9, 2026
afa2bfa
fix(verify): Phase D round 3 — final residue (glass in join/public/st…
Jul 9, 2026
70aa5da
fix: surface Signal Inbox mutation failures; unflash program-profile …
Jul 9, 2026
2f0eb19
fix: surface silent server-action failures across 7 mutation surfaces…
Jul 9, 2026
11180c5
feat: defect-sweep fixes + full taste-polish wave (Phase D mega-wave)
Jul 9, 2026
8f82063
fix: round-6 verification repairs — 26 confirmed findings across the …
Jul 10, 2026
c886c29
feat: tree-wide ink conversion + golf token sweep + Escape-bug class …
Jul 10, 2026
03e1aa8
docs: mission doc — Phase D final ledger + Phase E state (supersedes …
Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 5 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,9 @@ RLS_DIAGNOSTIC_TEST_PASSWORD=
# RINI_DEMO_FILLER_PASSWORD=

# -----------------------------------------------------------------------------
# Fairway redesign feature flag
# Fairway redesign feature flag (REMOVED — Wave W1, 2026-07-09)
# -----------------------------------------------------------------------------
# Gates the "Fairway" warm-premium design system (see ui-intelligence/
# DESIGN-SYSTEM.md). Default OFF — the current app is 100% unchanged; only
# redesigned components that opt in via src/lib/redesign/flag.ts + the
# `.fairway-ds` scope consume the new tokens/fonts. Accepts 1/true/on/yes.
# NEXT_PUBLIC_REDESIGN=false
# NEXT_PUBLIC_REDESIGN used to gate the "Fairway" warm-premium design system
# (see ui-intelligence/DESIGN-SYSTEM.md). Fairway is now the only dashboard
# tree — src/lib/redesign/flag.ts's isRedesignEnabled()/useRedesign() are
# hardcoded true — so this env var no longer has any effect. No action needed.
1 change: 0 additions & 1 deletion .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ permissions:
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL || 'https://dummy-ci-build.supabase.co' }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'dummy-anon-key-ci-build-only' }}
NEXT_PUBLIC_REDESIGN: "true"
E2E_GOLF_EMAIL: ${{ secrets.E2E_GOLF_EMAIL }}
E2E_GOLF_PASSWORD: ${{ secrets.E2E_GOLF_PASSWORD }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/pr-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ permissions:
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL || 'https://dummy-ci-build.supabase.co' }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'dummy-anon-key-ci-build-only' }}
NEXT_PUBLIC_REDESIGN: "true"
NODE_OPTIONS: --max-old-space-size=4096

jobs:
Expand Down
6 changes: 3 additions & 3 deletions docs/audits/PRODUCTION_READINESS_MISSION_2026-07-09.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
**Nav counts today** (primary+secondary rail destinations, Settings footer excluded):
| Surface | Today | Target |
|---|---|---|
| Baseball coach | **8 hubs** ✅ (Dashboard, Messages, Team, Stats & Performance, Development, Recruiting, Academics, Management) | keep |
| Baseball coach | **8 hubs** ✅ (8 standard: Dashboard, Messages, Team, Stats & Performance, Development, Recruiting, Academics, Management; 7 for High School — Recruiting hidden by design, `RECRUITING_PROGRAM_TYPES`; showcase variant has its own two-level org→team rail) | keep |
| Baseball player | **9** (7 primary + exposureNoun + Settings-in-rail) | **8** — move Settings to pinned footer (parity with coach shell) |
| Golf coach (Fairway, live) | **15** (7 primary + 8 secondary) | **8 hubs** |
| Golf player (Fairway, live) | **12** (8 primary + 4 secondary) | **8** |
Expand Down Expand Up @@ -101,7 +101,7 @@ policies on hot golf tables, 199 unindexed FKs, 256 unused indexes).

## Target IA (~8 tabs, owner directive)

### Baseball coach — unchanged (8 hubs) ✅
### Baseball coach — unchanged (8 hubs: 8 standard; 7 for HS — Recruiting hidden by design; showcase variant has its own two-level rail) ✅
### Baseball player — 8 (move Settings out of rail to pinned footer)
Today · Schedule · My Profile · Stats · Development · Team · Messages · [exposureNoun]

Expand All @@ -114,7 +114,7 @@ Today · Schedule · My Profile · Stats · Development · Team · Messages · [
6. **Messages** (Messages · Announcements)
7. **Operations** (Tasks · Documents)
8. **Courses**
Footer: Settings · Sign out. Mobile bottom bar: Home, CoachHelm, Roster, Calendar, Messages (unchanged).
Footer: Settings · Sign out. Mobile bottom bar: Home, CoachHelm, Team, Calendar, Messages (code renders label "Team", opening the Team hub — not "Roster"; unchanged from live behavior).

### Golf player — 8
1. **Dashboard** (merge Hub into Dashboard landing — two "homes" is duplicative)
Expand Down
16 changes: 13 additions & 3 deletions docs/baseball/ui-migration-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@
kit so the product reads as a single publication. No surface keeps a bespoke card, header,
empty state, or stat display once the kit covers it. Consistency is the deliverable.

Delivery mechanism = the existing **Fairway redesign layer** (`NEXT_PUBLIC_REDESIGN`,
`.fairway-ds` scope, `--fw-*` tokens). The Living-Annual kit is the **baseball-native
vocabulary layered on top of the Fairway tokens** — not a second design system.
Delivery mechanism = the existing **Fairway redesign layer** (`.fairway-ds` scope,
`--fw-*` tokens — unconditional, no flag). The Living-Annual kit is the
**baseball-native vocabulary layered on top of the Fairway tokens** — not a second
design system.

> **Update (2026-07-09, golf Wave W1):** `NEXT_PUBLIC_REDESIGN` no longer gates
> anything — `isRedesignEnabled()`/`useRedesign()` in `src/lib/redesign/flag.ts`
> are hardcoded `true` (the legacy `GolfDashboardShell`/`GolfSidebar` fork they
> used to gate was deleted). Setting the env var has zero effect. Baseball's
> `BaseballFairwayShell` renders unconditionally and was never flag-gated the
> way golf's dual tree was; `src/components/layout/header.tsx` still calls
> `isRedesignEnabled()` (always `true`) to suppress its own legacy chrome, but
> that is a hardcoded return, not env-driven behavior.

---

Expand Down
6 changes: 2 additions & 4 deletions e2e/course-library.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,8 @@ test.describe('Cloud Course Library — authenticated flow', () => {
});

// Capture screenshots of the premium course picker for review as CI artifacts
// (uploaded by .github/workflows/playwright.yml). Requires
// NEXT_PUBLIC_REDESIGN=true so the redesign FairwayCoursePicker renders
// (otherwise this shoots the legacy TeePickerDrawer). Best-effort — the point
// is the image, not a gate; a missing affordance skips rather than fails.
// (uploaded by .github/workflows/playwright.yml). Best-effort — the point is
// the image, not a gate; a missing affordance skips rather than fails.
test('capture: premium course picker screenshots', async ({ page }) => {
const dir = 'e2e-screenshots';

Expand Down
108 changes: 61 additions & 47 deletions e2e/golf-dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { test, expect } from '@playwright/test';
* run against real CI (no such account) failed at login. Set E2E_GOLF_EMAIL
* / E2E_GOLF_PASSWORD to a seeded golf coach/player login to run these;
* otherwise they self-skip instead of failing.
*
* UI: assertions target the current Fairway redesign (Wave W1 — Fairway is
* the only dashboard tree; see src/lib/redesign/flag.ts), not the legacy
* emerald/`bg-emerald-600` UI. Selectors use accessible roles/names rather
* than color utility classes, which are an implementation detail that
* changes with the design system, not the user-facing contract.
*/

const GOLF_EMAIL = process.env.E2E_GOLF_EMAIL;
Expand All @@ -36,8 +42,8 @@ test.describe('Golf Dashboard - Player Flow', () => {
// Verify dashboard loaded
await expect(page).toHaveURL(/\/golf\/dashboard/);

// Should see dashboard heading
await expect(page.locator('h1')).toContainText(/Good (morning|afternoon|evening)/);
// Should see dashboard heading (FairwayPlayerDashboard's ViewHeader greeting)
await expect(page.getByRole('heading', { level: 1 })).toContainText(/Good (morning|afternoon|evening)/);
});

test('should navigate to rounds page', async ({ page }) => {
Expand All @@ -47,27 +53,29 @@ test.describe('Golf Dashboard - Player Flow', () => {
// Should navigate to rounds page
await expect(page).toHaveURL(/\/golf\/dashboard\/rounds/);

// Should see rounds heading
await expect(page.locator('h1')).toContainText('Rounds');
// Should see the rounds masthead (FairwayRoundsLibrary's ViewHeader title
// is "Your rounds." for a player, "The library." for a coach).
await expect(page.getByRole('heading', { level: 1 })).toContainText(/rounds/i);
});

test('should access new round page and see all steps', async ({ page }) => {
// Navigate to new round page
await page.goto('/golf/dashboard/rounds/new');

// Step 1: Setup - Should see course setup form
await expect(page.locator('h1')).toContainText('New Round');
// Step 1: Setup - Should see course setup form (FairwayNewRoundEntry's
// cockpit band h1, not a literal "New Round" string).
await expect(page.getByRole('heading', { level: 1 })).toContainText(/track every shot/i);
await expect(page.locator('#courseName')).toBeVisible();

// Fill in course setup
await page.fill('#courseName', 'E2E Test Course');
await page.fill('#courseCity', 'Test City');
await page.fill('#courseState', 'CA');

// Should see emerald-colored button (modern design)
const nextButton = page.locator('button:has-text("Next: Configure Holes")');
// Manual entry (no course-library/saved-course pick) → the primary CTA
// submits straight to hole configuration.
const nextButton = page.getByRole('button', { name: 'Next: configure holes →' });
await expect(nextButton).toBeVisible();
await expect(nextButton).toHaveClass(/bg-emerald-600/);

// Click next
await nextButton.click();
Expand All @@ -76,31 +84,34 @@ test.describe('Golf Dashboard - Player Flow', () => {
await expect(page.locator('text=E2E Test Course')).toBeVisible();
await expect(page.locator('text=Total Par')).toBeVisible();

// Should see hole configuration grid
await expect(page.locator('text=Hole')).toBeVisible();
await expect(page.locator('text=Par')).toBeVisible();
await expect(page.locator('text=Yardage')).toBeVisible();

// Should see Front 9 / Back 9 tabs
await expect(page.locator('button:has-text("Front 9")')).toBeVisible();
await expect(page.locator('button:has-text("Back 9")')).toBeVisible();

// Should see par selector buttons with modern styling
const parButtons = page.locator('button:has-text("3"), button:has-text("4"), button:has-text("5")').first();
await expect(parButtons).toBeVisible();

// Should see save button with emerald styling
const saveButton = page.locator('button:has-text("Save Course & Start Round")');
// Should see hole configuration grid headers (FairwayHoleConfig: "Yards",
// not the legacy "Yardage"). Exact match — "Par"/"Total Par" and
// "Yards"/"Total Yards" both appear on this screen, so a bare substring
// match would hit more than one element (strict-mode violation).
await expect(page.getByText('Hole', { exact: true })).toBeVisible();
await expect(page.getByText('Par', { exact: true })).toBeVisible();
await expect(page.getByText('Yards', { exact: true })).toBeVisible();

// Should see Front 9 / Back 9 segmented options (rendered as role="radio",
// labeled "Front 9 · <par>" / "Back 9 · <par>").
await expect(page.getByRole('radio', { name: /^Front 9/ })).toBeVisible();
await expect(page.getByRole('radio', { name: /^Back 9/ })).toBeVisible();

// Should see the per-hole par selector chips (accessible name "Hole N par P").
const parButton = page.getByRole('button', { name: /^Hole 1 par (3|4|5)$/ }).first();
await expect(parButton).toBeVisible();

// Should see the primary action to start tracking
const saveButton = page.getByRole('button', { name: 'Start round →' });
await expect(saveButton).toBeVisible();
await expect(saveButton).toHaveClass(/bg-emerald-600/);

// Click save to proceed to shot tracking
await saveButton.click();

// Step 3: Shot Tracking - Should see ShotTrackingComprehensive
// Step 3: Shot Tracking - Should see the tracking surface for hole 1
await expect(page.locator('text=Hole 1')).toBeVisible({ timeout: 5000 });

// Should see shot tracking interface
// Should see the shot entry interface
// (Note: Full shot tracking test would be more complex,
// this just verifies the component loaded)
});
Expand All @@ -109,39 +120,42 @@ test.describe('Golf Dashboard - Player Flow', () => {
// Navigate to stats page
await page.goto('/golf/dashboard/stats');

// Should see stats page
await expect(page.locator('h1')).toContainText('Stats');

// Should see category pills (modern design)
await expect(page.locator('button:has-text("Scoring")')).toBeVisible();
await expect(page.locator('button:has-text("Driving")')).toBeVisible();
await expect(page.locator('button:has-text("Approach")')).toBeVisible();
await expect(page.locator('button:has-text("Putting")')).toBeVisible();
await expect(page.locator('button:has-text("Scrambling")')).toBeVisible();
// Should see stats page (FairwayPlayerStats' title is "Your stats" for a
// player viewing their own page).
await expect(page.getByRole('heading', { level: 1 })).toContainText(/stats/i);

// Should see category tabs (FairwayStatsCockpit renders these as
// role="tab" via the Fairway Tabs/Radix primitive, not plain buttons).
await expect(page.getByRole('tab', { name: 'Scoring' })).toBeVisible();
await expect(page.getByRole('tab', { name: 'Driving' })).toBeVisible();
await expect(page.getByRole('tab', { name: 'Approach' })).toBeVisible();
await expect(page.getByRole('tab', { name: 'Putting' })).toBeVisible();
await expect(page.getByRole('tab', { name: 'Scrambling' })).toBeVisible();
});

test('should verify emerald color scheme throughout', async ({ page }) => {
test('should keep the primary setup/holes CTAs keyboard-accessible', async ({ page }) => {
// Go to new round page
await page.goto('/golf/dashboard/rounds/new');

// Check for emerald focus rings on inputs
// The primary CTA must be a real, named, focusable control — not just a
// colored div. (This replaces the old bg-emerald-600 color-scheme check,
// which asserted on an implementation-detail utility class rather than
// the accessible contract.)
const courseNameInput = page.locator('#courseName');
await courseNameInput.click();
await courseNameInput.fill('Accessibility Test Course');

// Check button has emerald background
const nextButton = page.locator('button:has-text("Next: Configure Holes")');
await expect(nextButton).toHaveClass(/bg-emerald-600/);
const nextButton = page.getByRole('button', { name: 'Next: configure holes →' });
await expect(nextButton).toBeEnabled();

// Navigate to holes step
await page.fill('#courseName', 'Color Test Course');
await nextButton.click();

// Verify hole configuration uses emerald colors
// Verify hole configuration loaded and its primary action is likewise a
// real, named, focusable control.
await expect(page.locator('text=Total Par')).toBeVisible();

// Check save button uses emerald
const saveButton = page.locator('button:has-text("Save Course & Start Round")');
await expect(saveButton).toHaveClass(/bg-emerald-600/);
const saveButton = page.getByRole('button', { name: 'Start round →' });
await expect(saveButton).toBeEnabled();
});
});

Expand Down
Loading
Loading