-
Notifications
You must be signed in to change notification settings - Fork 0
BaseballHelm coherence: one shell, capped nav IA, one Lift Lab, data honesty #792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
211fee2
4ed9617
cf0d024
660f51f
44b036d
01adf35
31ea5e8
672f970
255ea47
356e44c
7df21b6
6ba82a4
bb386d5
0e3a0b5
ecb435b
d357fc6
0eb85ca
cd68952
063382f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # BaseballHelm Coherence Ruling — 2026-07-08 (overnight consolidation) | ||
|
|
||
| **Authority:** Commander decision doc for the one-night coherence mission. Supersedes conflicting | ||
| guidance in CANONICAL_SPEC §2.1 (stale 10-item nav). Builds on COACH_NAV_8TAB_PROPOSAL.md | ||
| (owner-approved 2026-07-01) and the 2026-06-30 shell postmortem's unexecuted recommendations. | ||
| Baseline: `origin/main` e63de6044 — tsc/lint/ratchet/unit all green. | ||
|
|
||
| ## Ruling 1 — ONE shell: BaseballFairwayShell, unconditional | ||
|
|
||
| `NEXT_PUBLIC_REDESIGN=true` is what prod serves. The legacy fork is pure regression risk | ||
| (`.env.example` defaults it off). Therefore: | ||
|
|
||
| - `(dashboard)/layout.tsx` and `(player-dashboard)/player/layout.tsx` render | ||
| `BaseballFairwayShell` **unconditionally** — delete the `isRedesignEnabled()` forks | ||
| (baseball layouts only; golf untouched). | ||
| - Delete `src/app/baseball/(coach-dashboard)/` entirely (zero page.tsx, confirmed dead). | ||
| - Remove the **baseball** nav paths from `src/components/layout/sidebar.tsx` | ||
| (5 legacy arrays: collegeTeamNav/hsCoachTeamNav/jucoTeamNav/showcaseOrgNav/playerTeamNav + | ||
| `buildCondensedBaseballNavigation`); the golf branch stays byte-identical. | ||
| - Delete `BaseballShellLayout`/`BaseballDashboardShell` baseball render path once unreferenced. | ||
| - Route groups `(dashboard)` + `(player-dashboard)` both stay (URLs are load-bearing: | ||
| PWA start_url, bookmarks). Folding them is deferred — not tonight's risk. | ||
|
|
||
| ## Ruling 2 — Navigation IA: ≤8 primary, ≤3 subtabs per primary (hard caps) | ||
|
|
||
| Top level keeps the owner-approved hub set (College sees 7, HS 6, JUCO 8 — all ≤8): | ||
| **Dashboard · Team · Messages · Stats & Performance · Development · Recruiting · | ||
| Academics (JUCO) · Management**. The fix is inside the hubs — every destination stays | ||
| reachable ≤2 clicks (subtab landing pages surface deeper routes as cards/CTAs; command | ||
| palette stays flat with everything). Deep routes keep their URLs; `resolve-active-hub` | ||
| maps them to the owning subtab for highlight + breadcrumbs. | ||
|
|
||
| | Hub | Subtabs (≤3) | Folded in (reachable from landing) | | ||
| |---|---|---| | ||
| | Dashboard | Overview · Signals | — | | ||
| | Team | Roster · Calendar · Operations | Operations = new landing: Documents, Travel, Practice Planner, Practice Effectiveness | | ||
| | Messages | Messages · Announcements | announcements moves here from Team (it's comms) | | ||
| | Stats & Performance | Stats Center · Games · Postgame | Season = view inside Stats Center; Upload + Import Center = CTAs inside Stats Center | | ||
| | Development | Dev Plans · Training · Videos | Training = existing /dashboard/performance landing → Programs, Live Weight Room, Builder, Groups | | ||
| | Recruiting | Pipeline · Discover · Scouting | Scouting = new landing: Watchlist, Compare, Saved Comparisons, Scout Packets, Camps | | ||
| | Academics | (single page) | JUCO only | | ||
| | Management | Decision Room · Settings · Organization | Settings = existing card-grid landing (KEEP grid, DELETE the 9-tab splice from COACH_MANAGEMENT_TABS); Organization = org/teams/events (Showcase types) | | ||
|
|
||
| Player nav (Fairway): Today · Schedule · My Stats · Development · Team · Messages · | ||
| My Profile (+ Recruiting when activated) — already ≤8; enforce ≤3 subtabs per hub the same way. | ||
| Players hitting `/baseball/dashboard/practice` redirect to `/baseball/player/practice` (canonical | ||
| player practice surface). | ||
|
|
||
| Nav-manifest test extended: every coach/both registry entry maps to exactly one hub, and no | ||
| hub resolves >3 subtabs for any coach type. That test is the anti-regression lock. | ||
|
|
||
| ## Ruling 3 — ONE Lift Lab | ||
|
|
||
| Canonical = `src/components/lifting/*` + `helm_lifting_*`. Repoint the 6 baseball | ||
| performance routes at the canonical components; delete `src/components/baseball/performance/*` | ||
| (23-file legacy tree, GolfHelm-palette, writes legacy `baseball_lift_*`). Completes the | ||
| in-flight unification train. | ||
|
|
||
| ## Ruling 4 — Data honesty & correctness cluster | ||
|
|
||
| - "Today" is **team-local** everywhere (`resolveTeamTimezone` + `todayIsoInTz`), never server-UTC: | ||
| readiness page, command-center read-model, player-today read-model. | ||
| - Calendar: null `end_time` renders start + 1h default (never zero-duration); events query gets | ||
| lookback bound + limit; badge labels pluralize. | ||
| - Academics eligibility is tri-state: `null` = gray "Not on file"; red "Ineligible" only for real `false`. | ||
| - Roster: drop EXIT V column (column doesn't exist in schema, no write path — honest UI); | ||
| backfill career_obp/slg/ops for existing rows (prod data op). | ||
| - Breadcrumbs: UUID-shaped segments never title-cased; dynamic routes supply real names | ||
| (players/[id], stats/games/[id], dev-plans/[id]). | ||
| - `createBaseballEvent` game-insert errors checked, not swallowed. | ||
| - E2E: spec cleans up its own rows (service-role delete in teardown); prod junk rows | ||
| (`E2E Created Opponent%`) deleted as a data op; isolated E2E project documented as follow-up | ||
| needing owner (new Supabase project). | ||
| - Seed script gets realistic event times (practice 15:30–17:30, games 13:00–16:00, meetings 12:00–13:00 team-local); | ||
| demo team's polluted event rows corrected in place. | ||
|
|
||
| ## Ruling 5 — Dead code deleted, not layered over | ||
|
|
||
| `(coach-dashboard)`; legacy sidebar baseball arrays; legacy shell baseball path; legacy Lift Lab | ||
| tree; `players/[id]/profile` duplicate page (canonical = `players/[id]` PlayerProfileClient); | ||
| knip-confirmed orphans (MatchScoreBadge, match-calculator, dashboard-types); 5 orphaned | ||
| `baseballhelm-*.{mjs,workflow.js}` scripts (superseded by Helm Bridge). | ||
|
|
||
| ## Out of scope tonight (documented, not forgotten) | ||
|
|
||
| Route-group merge of `(player-dashboard)` into `(dashboard)`; dedicated E2E Supabase project; | ||
| PlayerPassportCard→Fairway preview swap if polish wave runs out of clock; full 3-lane | ||
| Living-Annual masthead vision (ui-migration-map L56) — the 8-hub IA is the stepping stone. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { test, expect, type Page } from '@playwright/test'; | ||
| import { getE2eAdminClient } from '../scripts/e2e-supabase-admin'; | ||
| import { loginAsCoach, loginAsPlayer } from './helpers/auth'; | ||
| import { waitForPageLoad } from './helpers/common'; | ||
|
|
||
|
|
@@ -23,10 +24,13 @@ import { waitForPageLoad } from './helpers/common'; | |
| * including `status`), per the "destructive tests rely on seed reset" | ||
| * pattern used throughout this fixture. | ||
| * | ||
| * The "create game" test adds one extra (non-seeded) game row each run — | ||
| * there is currently no reachable delete affordance in the UI (GamesList's | ||
| * delete button is shipped with a permanent `hidden` class), so this is a | ||
| * known, documented trade-off rather than an oversight. See e2e/README.md. | ||
| * The "create game" test adds one extra (non-seeded) game row each run. | ||
| * `GamesList`'s delete button is still shipped with a permanent `hidden` | ||
| * class, so there is no reachable UI delete affordance — cleanup instead | ||
| * happens directly against the database: a `test.afterAll` below deletes | ||
| * every `baseball_games` row (+ its linked `baseball_events` row) this file | ||
| * created, via the same service-role client construction | ||
| * `scripts/seed-baseball-e2e.ts` uses for seeding. See e2e/README.md. | ||
| * | ||
| * Gated on PLAYWRIGHT_BASEBALL_SEEDED=1, with a per-test self-skip if the | ||
| * login fixture is unavailable in the target environment — same pattern as | ||
|
|
@@ -42,6 +46,18 @@ const SEEDED = | |
| const SCHEDULED_OPPONENT = 'Riverside University'; | ||
| const COMPLETED_OPPONENT = 'Eastview College'; | ||
|
|
||
| /** | ||
| * Service-role Supabase client for teardown-only writes (deleting rows this | ||
| * spec itself created) — provided by scripts/e2e-supabase-admin.ts so this | ||
| * spec never references the service-role env var directly (ast-grep rule | ||
| * helmv3-no-service-role-key). Returns `null` (teardown becomes a no-op) | ||
| * rather than throwing when the env is missing — cleanup must never fail an | ||
| * otherwise-passing run. | ||
| */ | ||
| function getServiceRoleClient() { | ||
| return getE2eAdminClient(); | ||
| } | ||
|
|
||
| async function loginCoachOrSkip(page: Page) { | ||
| try { | ||
| await loginAsCoach(page); | ||
|
|
@@ -65,12 +81,43 @@ async function loginPlayerOrSkip(page: Page) { | |
| test.describe('Coach - Create New Game', () => { | ||
| test.skip(!SEEDED, 'no seeded baseball team fixture (set PLAYWRIGHT_BASEBALL_SEEDED=1)'); | ||
|
|
||
| // Opponent names created by the "create new game" test below — tracked so | ||
| // the afterAll teardown can delete exactly (and only) the rows this file | ||
| // created, never touching the seeded fixture games. | ||
| const createdOpponents: string[] = []; | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| await loginCoachOrSkip(page); | ||
| await page.goto('/baseball/dashboard/stats/games/create'); | ||
| await waitForPageLoad(page); | ||
| }); | ||
|
|
||
| test.afterAll(async () => { | ||
| if (createdOpponents.length === 0) return; | ||
| const supabase = getServiceRoleClient(); | ||
| if (!supabase) return; | ||
|
|
||
| // The create form defaults `create_calendar_event` to true, so each | ||
| // created game also has a linked baseball_events row (event_id) — | ||
| // delete both so no orphaned event survives the game's deletion. | ||
| const { data: games } = await supabase | ||
| .from('baseball_games') | ||
| .select('id, event_id') | ||
| .in('opponent_name', createdOpponents); | ||
|
|
||
| const gameIds = (games ?? []).map((g) => g.id); | ||
| const eventIds = (games ?? []) | ||
| .map((g) => g.event_id) | ||
| .filter((id): id is string => Boolean(id)); | ||
|
|
||
| if (gameIds.length > 0) { | ||
| await supabase.from('baseball_games').delete().in('id', gameIds); | ||
| } | ||
| if (eventIds.length > 0) { | ||
| await supabase.from('baseball_events').delete().in('id', eventIds); | ||
| } | ||
| }); | ||
|
Comment on lines
+95
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Confirm the opponent-name generator embeds enough entropy to avoid cross-worker collisions.
rg -n "opponent" e2e/baseball-box-score.spec.ts -B2 -A10 | rg -n "const opponent"Repository: njrini99-code/helmv3 Length of output: 224 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the relevant section with line numbers and surrounding context.
sed -n '130,170p' e2e/baseball-box-score.spec.ts | cat -n
# Check whether the teardown already has any team-scoping data available nearby.
rg -n "team_id|opponent_name|createdOpponents|create_calendar_event|baseball_games|baseball_events" e2e/baseball-box-score.spec.tsRepository: njrini99-code/helmv3 Length of output: 3035 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,130p' e2e/baseball-box-score.spec.ts | cat -nRepository: njrini99-code/helmv3 Length of output: 6535 Delete teardown rows by game ID, not 🤖 Prompt for AI Agents |
||
|
|
||
| test('should display the new game form with date, opponent, and venue fields', async ({ page }) => { | ||
| await expect(page.getByRole('heading', { name: /Add Game/i })).toBeVisible(); | ||
| await expect(page.locator('#new-game-date')).toBeVisible(); | ||
|
|
@@ -92,6 +139,7 @@ test.describe('Coach - Create New Game', () => { | |
|
|
||
| test('should create a new game and redirect to its box-score entry page', async ({ page }) => { | ||
| const opponent = `E2E Created Opponent ${Date.now()}`; | ||
| createdOpponents.push(opponent); | ||
| const futureDate = new Date(); | ||
| futureDate.setDate(futureDate.getDate() + 45); | ||
| const dateStr = futureDate.toISOString().slice(0, 10); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
6. E2e teardown too broad
🐞 Bug☼ ReliabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools