Skip to content

Baseball statsync verification: box-score↔profile sync proven, destructive RPC replaced, shell/routes remediation - #794

Merged
njrini99-code merged 30 commits into
mainfrom
integration/final-verification-2026-07-08
Jul 9, 2026
Merged

Baseball statsync verification: box-score↔profile sync proven, destructive RPC replaced, shell/routes remediation#794
njrini99-code merged 30 commits into
mainfrom
integration/final-verification-2026-07-08

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Summary

Integration branch from the 7-round statsync verify+fix cycle (2026-07-08/09):

  • Box-score save RPC — migration 20260708150000 replaces the banned DELETE-then-INSERT in save_baseball_full_box_score with per-row UPSERT + targeted delete of removed players; per-player recalc failures no longer roll back the whole game; real error_detail/error_code returned. Applied to prod 2026-07-09, verified live (pg_proc body check).
  • Migration 20260708141000 committed as a file with an explicit ⛔ HOLD header — NOT applied (touches a golf function + invite-redemption gating risks; needs owner sign-off).
  • Box-score/game/profile stat display parity (shared format-stat helper, EditGameModal, GameDetailHeader).
  • Shell/nav/routes remediation: BaseballFairwayShell + nav-registry consistency, dead season-stats + video-detail routes deleted, phantom exit-velocity columns dropped.
  • Includes previously-merged-in fix/baseballhelm-coherence-2026-07-08 + fix/db-security-hardening-2026-07-08 + cost-control CI changes.

Gates (local, exit codes captured)

  • tsc --noEmit = 0
  • eslint --max-warnings 0 = 0
  • unit: 4497 passed / 1 failed — pre-existing on origin/main (chat-tools-visibility.test.ts, reproduced in clean worktree at origin/main), tracked for the follow-up production-readiness mission.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX

Fable Integrator and others added 25 commits July 8, 2026 01:06
…ell, capped nav IA, one lift lab

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…te dead (coach-dashboard) route group

Coherence Ruling 1 (docs/baseball/COHERENCE_RULING_2026-07-08.md): NEXT_PUBLIC_REDESIGN=true
is what prod serves, so the legacy shell fork in (dashboard)/layout.tsx and
(player-dashboard)/player/layout.tsx was pure regression risk. Both now mount
BaseballFairwayShell unconditionally; the team-context DashboardSessionGuard is
unchanged. Deletes src/app/baseball/(coach-dashboard)/ entirely (confirmed dead:
only layout.tsx/template.tsx/error.tsx, zero page.tsx). Updates stale
BaseballShellLayout references in comments/test descriptions to BaseballFairwayShell.
…, strip baseball nav from shared sidebar

Both files had zero real importers once the (dashboard)/(player-dashboard) layouts
render BaseballFairwayShell unconditionally (previous commit) — deleted per Ruling 1/5.

src/components/layout/sidebar.tsx is shared with golf: removed the 5 legacy baseball
nav arrays (collegeTeamNav/hsCoachTeamNav/jucoTeamNav/showcaseOrgNav/playerTeamNav),
buildCondensedBaseballNavigation(), the showcase org/team TeamSwitcher branch, and
every other baseball-only branch. The isGolf branch (golfCoachNav, dashboardHref,
EXACT_MATCH_HREFS golf entry, coachSecondaryNav/playerSecondaryNav fallback,
isHubItemActive) is byte-identical to before — golf renders the exact same values it
did prior to this change.

Note: BaseballHelm no longer renders this Sidebar component at all (it only ever had
one consumer, the now-deleted BaseballDashboardShell) — GolfHelm's real rendered
sidebar is the separate GolfSidebar.tsx via GolfDashboardShell, not this file. This
component is consequently unreferenced by any route today; flagged for the commander
as a follow-up deletion candidate, not removed here since it falls outside this task's
named scope and the file is marked shared/untouchable without further authorization.
…dar/documents/messages pages

Calendar, Documents, and Messages pages had their own local isRedesignEnabled()
forks rendering a legacy pre-Fairway UI. Since the shell fork is gone (BaseballFairwayShell
is now the only shell), these pages always take the Fairway branch — deleted the
legacy JSX and now-unused imports (Link, BaseballCalendarWrapper, EVENT_TYPE_CONFIG,
Card/CardContent/Input/Select/Checkbox/DocumentCard/cn, the messages skeleton block).
practice/page.tsx already had no fork (comment-only precedent reference, untouched).
…ubtabs (Ruling 2)

Implements the coach hub/subtab table from
docs/baseball/COHERENCE_RULING_2026-07-08.md Ruling 2. Mechanism: nav-registry.ts
gets a `foldedUnder` field (a registry entry marks itself as reachable only
from a parent landing page instead of its own hub-tab slot, but keeps its
registry row so command palette + deep links still work) and a `hubTabLabel`
override (lets a subtab read differently from the entry's canonical label,
e.g. "Performance" reads "Training" under Development). hub-definitions.ts's
`hubEntries()` excludes folded entries from the rendered strip.

Regrouped: Announcements moves from Team to a new real Messages hub
(Messages · Announcements, replacing the old bare flat link); Practice
Planner/Effectiveness move from Stats & Performance to Team (fold into the
new Operations landing); Performance moves from Stats & Performance to
Development (folds in as "Training"); Import Center folds into Stats Center;
Interest folds into Pipeline; Watchlist/Compare/Saved Comparisons/Scout
Packets/Camps fold into a new Scouting landing; Program Info/Staff
Settings/Program Settings fold into the existing Settings landing; Teams/
Events fold into Organization. Deleted the 9-item SETTINGS_* hub-tab splice
and the 4-item PERFORMANCE_* splice outright (dead code, not layered over)
since Performance's own page already links Programs/Live/Builder/Groups.

Command Center's nav-visible label renames to "Overview" (breadcrumbs, hub
subtab, command palette, mobile nav) so "Dashboard" never has two names in
the UI; the route/page masthead keep "Command Center".

BaseballFairwayShell.tsx: Messages now flows through the generic
COACH_HUB_ORDER loop (with its unread badge threaded in by hub id) instead of
a hand-injected flat item; showcase org/team rails route their Messages item
through the same hub-aware path so showcase coaches don't lose Announcements
reachability. Mobile bottom nav trimmed to the top 3 destinations per role
(Ruling 2 item 8) — the top-bar hamburger (already bridged to the same
drawer) is the "+ More".

/baseball/dashboard/practice now server-redirects players to the canonical
/baseball/player/practice surface (item 7); coach/staff rendering unchanged.

nav-manifest.ts and route-contract.ts extended to track the new Messages hub
tabs for completeness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
New card-grid landing pages that keep the folded destinations reachable in
≤2 clicks now that Team and Recruiting cap at 3 rendered subtabs:

- /baseball/dashboard/operations (Team hub): Documents, Travel, Practice
  Planner, Practice Effectiveness.
- /baseball/dashboard/scouting (Recruiting hub): Watchlist, Compare Players,
  Saved Comparisons, Scout Packets, Camps.

Both are server components (no hooks) built from the Living Annual kit
(SectionMasthead/PaperCard/Eyebrow). Neither re-declares capability or
program-type gating — each card is resolved via getBaseballNavEntry +
isBaseballNavEntryVisible against the SAME registry entry the sidebar and
command palette use, so a coach without a capability simply doesn't see that
card, exactly like they wouldn't see it in the sidebar. Operations uses the
green "team" ink lane; Scouting uses the clay "pursuit" ink lane (recruiting).

Also adds the Stats Center masthead's persistent header-level actions
(Upload, Import Center) per Ruling 2 item 2 — Upload + Import Center are now
CTAs on the Stats Center page instead of their own hub-tab slots; the
existing empty-state "Import a box score" CTA is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…e (Ruling 2)

The card-grid landing at /dashboard/settings stays the single settings nav
surface (its own 13-tab strip is gone now that Management caps at 3 subtabs:
Decision Room · Settings · Organization). It was mounting the legacy
<Header> component on top of the shell's own top bar, duplicating breadcrumbs
and the mobile-menu affordance; replaced with a plain title block. Verified
the card grid still covers every fold target (Program Info/Settings, Staff,
Team, Season, Philosophy, Roles, Permissions, Imports, Integrations, Audit) —
nothing orphaned by the splice removal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…(Ruling 2)

nav-manifest.test.ts: extends the "every coach/both entry declares exactly
one hub" test to the new 8-hub set (adds 'messages'), and adds a permanent
lock (item 10 of the A2 task) asserting:
  (b) every coach hub renders ≤3 subtabs — both the raw arrays and a
      fully-capable head coach in every program type.
  (c) every player hub renders ≤3 subtabs.
  (d) every foldedUnder-marked registry entry still resolves to a hub via
      resolveActiveHub (command palette + deep links stay live even without
      their own hub-tab slot).

resolve-active-hub.test.ts: rewritten for the restructured hubs — Training
(performance) now resolves under Development instead of Stats & Performance;
Stats Center's Season/Upload/Import Center folds; Team's Operations folds
(Documents/Travel/Practice Planner/Practice Effectiveness); the new Messages
hub (Announcements moved off Team); Management's single Settings tab
(replacing the deleted 9-tab splice) plus Organization's Teams/Events folds;
Recruiting's Scouting folds and Pipeline's Interest fold. Behavior
intentionally changed per the ruling, so these tests were updated WITH the
change rather than left asserting the old structure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…listic seed event times

Readiness page dated check-ins in server UTC while the gate compared team-local
(evening US submissions mis-dated, gate kept nagging). Command Center day + day
bounds now team-local via the same contract-day helpers player-today already
uses. Seed script events get real times (practice 15:30-17:30, game 13:00-16:00,
meeting 12:00-13:00 ET) instead of the script's own wall-clock with zero duration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…IT V, unclip KPI numerals, slash-line backfill

Academics: is_eligible is boolean|null end-to-end; no record renders neutral
'Not on file', red Ineligible reserved for a real determination. Roster: EXIT V
column removed (avg_exit_velocity doesn't exist in the live schema and has no
write path). RuledStatLine: drop leading-none stacked over the text-ink token's
own line-height (numeral clipping). New idempotent backfill script populated
career_obp/slg/ops for 20/22 aggregate rows in prod using the app's tested
computeCareerSlashLine logic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…y real names

buildBreadcrumbs extracted to a pure module with an id-shape guard (UUID/numeric
segments fall back to the owning label, never title-cased). New BreadcrumbLabel
override channel: players/[id] shows the player's name, stats/games/[gameId] the
opponent, dev-plans/[id] the plan. 17 unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
Null end_time renders start+1h (display only), events query bounded (90d back /
365d fwd, limit 500 under the PostgREST cap), count badges pluralize. Swallowed
supabase insert errors in createBaseballEvent/createGame/box-score paths are now
checked and surfaced (ActionResult.warning). E2E box-score spec deletes its own
created games+events in teardown (prod was already purged of 160 junk rows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…cal helm_lifting stack

All six lift/performance routes (programs, program editor, groups, live weight
room, player lift home, player session) repointed at src/components/lifting/*.
Six legacy duplicates in components/baseball/performance/ deleted (GolfHelm
palette, legacy baseball_lift_* writes). PlayerLiftToday comment corrected to
helm_lifting_*. Coach edit access preserved via helm_lifting_org_viewers bridge
migration (9/10 coaches; 10th has no org).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…lumns (applied to prod)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…cold-URL bounce

Deleted: duplicate players/[id]/profile page (canonical = players/[id]),
orphaned sidebar.tsx + team-switcher.tsx (zero importers — golf renders
GolfSidebar), knip-confirmed orphans (MatchScoreBadge, match-calculator,
dashboard-types), stale baseballhelm-* local scripts. Dev-plan page is now a
server component (requireBaseballPlayerRoute) wrapping DevPlanClient — no more
guard-race bounce to Today on hard navigation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…ber empty states, dev-plan chrome, warning toasts

Removed duplicate page-level Header mounts (shell owns the masthead). All
empty/error states on team-ops + player surfaces route through the kit
(EmptyIssue/EditorsLetter) — ReadModelStateNotice de-ambered for its 6 call
sites. DevPlanClient rebuilt on kit primitives with ≥44px touch targets.
createBaseballEvent partial-failure warnings surface as toasts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…-portal revalidation, read-only dynamic groups

Command Center week window now team-local AND respects forDate (was bare
new Date()). Player snapshot cards fall back to career obp/slg/ops like avg.
Lifting mutations revalidate the baseball routes that now render them (Wave C),
not just /lifting. StrengthGroupsClient renders rule-managed dynamic groups
first-class read-only (badge + rule summary, destructive affordances disabled)
— both existing prod groups are dynamic. +15 unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…inventory docs

Stat-layer manifest grandfathers the command-center test fixture (mirrors the
already-grandfathered read-model); RosterFairway comment drops the literal
deprecated-table name; product-trust documents contract asserts the structural
EmptyIssue empty path + kit copy source instead of the pre-redesign string;
inventory docs regenerated (same content as auto-PR #784).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…-grep gate)

The box-score spec's cleanup client referenced SUPABASE_SERVICE_ROLE_KEY
directly, tripping helmv3-no-service-role-key (e2e/ is in scan scope;
scripts/ is the sanctioned exempt home, same as seed-baseball-e2e.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…ge holes)

get_users_with_auth() and get_platform_health_stats() were authenticated-
executable with zero authorization — any logged-in user could dump every
user's email + auth metadata. Both now gated with is_admin()/is_super_admin(),
matching sibling admin RPCs. update_user_last_seen() constrained to self-or-
admin; dead task-reminder RPCs, two CRM analytics RPCs, and the cron-only
refresh_crm_coach_engagement had authenticated EXECUTE revoked. Verified each
against app call sites; anon never had access. Applied to prod via MCP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…write, reconcile type with table

uploadStatsCSV wrote upload_batch_id (a column that never existed on
baseball_player_stats) on every row → every CSV upload failed the insert.
Removed the legacy write (import_run_id is the canonical linkage). Added the 7
real stat columns the type declared but the table lacked (caught_stealing,
sacrifice_bunts, runs_allowed, pitches_thrown, strikes_thrown, launch_angle,
spin_rate) additively; BaseballPlayerStats type now names the 6 real source_*/
import_run_id columns it was missing. Table and type now match exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…07-08' into integration/final-verification-2026-07-08
…7-08' into integration/final-verification-2026-07-08

# Conflicts:
#	src/lib/types/database.ts
…sync, shell/routes/features pass

7-round verify+fix cycle (2026-07-08/09) across the baseball surface:
- Box-score save path: RPC migration 20260708150000 replaces the banned
  DELETE-then-INSERT in save_baseball_full_box_score with per-row UPSERT
  (ON CONFLICT game_id,player_id) + targeted removal of dropped players;
  recalc loop isolated so one player's failure can't roll back the game;
  real error_detail/error_code surfaced. APPLIED to prod 2026-07-09.
- Migration 20260708141000 committed as file only — header marked HOLD
  (golf function + invite-redemption gating risks); intentionally NOT applied.
- Box-score UI: shared format-stat helper, EditGameModal, GameDetailHeader;
  stat display parity between box score, game detail, and player profiles.
- Shell/nav: BaseballFairwayShell + nav-registry consistency, dead-route
  and stat-layer-manifest cleanup, exit-velocity phantom columns dropped
  from BaseballPlayerAggregates.
- e2e + readiness matrix updated.

Known pre-existing failure (also on origin/main, untouched by this branch):
src/test/coachhelm/v3/chat-tools-visibility.test.ts — tracked for the
production-readiness mission.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Preview Jul 9, 2026 7:29am

Request Review

@supabase

supabase Bot commented Jul 9, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo due to reaching the limit of concurrent preview branches.
Go to Project Integrations Settings ↗︎ if you wish to update this limit.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown

Too many files changed for review. (180 files found, 50 file limit)

Bypass the limit by tagging @greptile-apps to review.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7d9cc599-51f5-46d5-8174-0c9bf4c1487a

📥 Commits

Reviewing files that changed from the base of the PR and between 761cacb and e8d213b.

📒 Files selected for processing (1)
  • docs/operations/BASEBALLHELM_FEATURE_READINESS_MATRIX.md

Summary by CodeRabbit

  • New Features

    • Added new Baseball dashboard sections for Operations and Scouting.
    • Introduced a player Development Plan page and updated Lift, Program, and Messages navigation.
  • Bug Fixes

    • Improved date handling for calendars, readiness, lifts, and event seeds to match team-local time.
    • Added safer cleanup for automated game creation and other test data, helping prevent leftover records.
    • Refined player stats, box scores, and breadcrumbs for clearer, more accurate display.
  • Documentation

    • Added several audit and readiness notes documenting current product and database status.

Walkthrough

This PR adds Baseball docs/tooling updates, unifies the shell and hub model, migrates Lift Lab flows to helm_lifting_*, and refactors calendar, stats, and player-profile behavior. It also tightens team-local date handling, partial-write warnings, and E2E cleanup.

Changes

Docs, Scripts & E2E Tooling

Layer / File(s) Summary
Audit, readiness, and route inventory docs
docs/audits/*, docs/baseball/COHERENCE_RULING_2026-07-08.md, docs/operations/BASEBALLHELM_FEATURE_READINESS_MATRIX.md, memory/projects/golfhelm.md, docs/audits/PRODUCTION_READINESS_MISSION_2026-07-09.md
New audit and mission docs are added, and route inventories/readiness tables are updated to match the current route set.
E2E teardown, backfill, and demo seeding
scripts/e2e-supabase-admin.ts, e2e/baseball-box-score.spec.ts, e2e/README.md, scripts/backfill-baseball-slash-lines.ts, scripts/seed-rini-baseball-demo.ts
A service-role teardown helper and box-score cleanup flow are added, a slash-line backfill script paginates aggregate rows and updates slash-line columns, and demo seeding uses DST-safe local times.

Baseball Shell, Breadcrumbs, and Hub Navigation

Layer / File(s) Summary
Unified BaseballFairwayShell layouts
src/app/baseball/(dashboard)/BaseballFairwayShell.tsx, src/app/baseball/(dashboard)/layout.tsx, src/app/baseball/(player-dashboard)/player/layout.tsx, src/hooks/use-baseball-auth.ts, src/hooks/__tests__/use-baseball-auth.test.tsx, src/components/baseball/NotificationBell.tsx
The dashboard and player layouts now always render BaseballFairwayShell, and related comments/tests are updated to reference the new shell mount path.
Breadcrumb override provider and builder
src/app/baseball/(dashboard)/_components/breadcrumb-label.tsx, src/app/baseball/(dashboard)/_components/breadcrumbs.ts, src/lib/baseball/__tests__/breadcrumbs.test.ts, src/app/baseball/(dashboard)/BaseballFairwayShell.tsx
A route-scoped breadcrumb label context feeds a pure breadcrumb builder that handles id-shaped segments, singularization, and route-level overrides.
Hub/tab registry rules and Messages hub
src/app/baseball/(dashboard)/_components/hub-definitions.ts, src/app/baseball/(dashboard)/_components/resolve-active-hub.ts, src/lib/baseball/nav-manifest.ts, src/lib/baseball/__tests__/*
Coach tabs now derive from the nav registry, a Messages hub is added, and routing/tests enforce the ≤3-subtab rule and the new hub mappings.
Operations, Scouting, and Messages surfaces
src/app/baseball/(dashboard)/dashboard/operations/page.tsx, src/app/baseball/(dashboard)/dashboard/scouting/page.tsx, src/app/baseball/(dashboard)/dashboard/messages/*, src/components/messages/EmptyChatState.tsx
New landing pages use nav-visible card grids, while Messages rendering/loading is simplified and EmptyChatState is standardized on EmptyIssue.

Header Removal and Living-Annual UI Migration

Layer / File(s) Summary
Dashboard pages replace Header with inline markup
src/app/baseball/(dashboard)/dashboard/*, src/app/baseball/(dashboard)/dashboard/travel/page.tsx, src/app/baseball/(dashboard)/dashboard/documents/page.tsx, src/components/baseball/travel/TravelClient.tsx, src/components/baseball/documents/DocumentsFairway.tsx, src/components/baseball/ReadModelStateNotice.tsx
Many dashboard pages replace the shared Header with inline headers or Living-Annual components like SectionMasthead, EditorsLetter, and EmptyIssue.
Settings client components inline headers
src/components/baseball/settings/*
ImportSources, Integrations, Philosophy, Season, Audit, and Team settings clients inline their headers and actions.
New Dev Plan client and server wrapper
src/app/baseball/(dashboard)/dashboard/dev-plan/*, src/app/baseball/(dashboard)/dashboard/dev-plans/*
A new player Dev Plan client is added, and the list/detail pages switch to inline headers and breadcrumb labels.

Timezone, Eligibility, Calendar, and Warning-Path Correctness

Layer / File(s) Summary
Team-local today in readiness and lift flows
src/app/baseball/(dashboard)/dashboard/readiness/page.tsx, src/app/baseball/actions/player-today-lift.ts, src/app/baseball/(dashboard)/dashboard/performance/builder/page.tsx, src/components/baseball/performance/PlayerLiftToday.tsx, src/components/baseball/performance/PerformanceDashboardClient.tsx, src/app/baseball/(dashboard)/dashboard/lift/_lift-athlete-context.ts, src/app/baseball/(dashboard)/dashboard/lift/[sessionId]/page.tsx, src/app/baseball/(dashboard)/dashboard/lift/page.tsx, src/app/baseball/(dashboard)/dashboard/performance/page.tsx
today now comes from the team timezone instead of server UTC in readiness, lift, and performance flows.
Calendar fallback end time and bounded fetch
src/app/baseball/(dashboard)/dashboard/calendar/page.tsx, src/hooks/useCalendarEvents.ts, src/components/baseball/calendar/CalendarFairway.tsx
Timed events get a display-only fallback end time, the fetch window is bounded, and the calendar frame gains a separate no-team empty state and updated upcoming-event strip.
Tri-state eligibility and partial calendar writes
src/app/baseball/(dashboard)/dashboard/academics/AcademicsClient.tsx, src/app/baseball/actions/academics.ts, src/app/baseball/actions/calendar.ts, src/components/baseball/calendar/BaseballCalendarWrapper.tsx
Academic eligibility preserves null for missing records, and calendar event creation can return a warning when secondary writes fail.
Practice routing and lift onboarding guards
src/app/baseball/(dashboard)/dashboard/practice/page.tsx, src/components/baseball/performance/LiftOnboardingGate.tsx
Player roles are redirected server-side to the player practice route, and the lift onboarding gate exits early when no athlete id is available.

Lift Lab / Helm Lifting Unification

Layer / File(s) Summary
Lift pages and athlete context
src/app/baseball/(dashboard)/dashboard/lift/_lift-athlete-context.ts, src/app/baseball/(dashboard)/dashboard/lift/[sessionId]/page.tsx, src/app/baseball/(dashboard)/dashboard/lift/page.tsx, src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx, src/app/baseball/(dashboard)/dashboard/performance/groups/page.tsx, src/app/baseball/(dashboard)/dashboard/performance/programs/[programId]/page.tsx, src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx, src/app/lifting/(dashboard)/dashboard/lift/page.tsx, src/app/lifting/(dashboard)/dashboard/programs/*, src/components/lifting/programs/*
Lift pages and shared program clients now use helm_lifting_* tables, server-side athlete resolution, and basePath-aware lifting components.
Groups, live room, and programs pages
src/app/baseball/(dashboard)/dashboard/performance/groups/page.tsx, src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx, src/components/lifting/groups/StrengthGroupsClient.tsx, src/components/lifting/groups/__tests__/StrengthGroupsClient.dynamic.test.tsx
Groups/live/program pages assemble data from lifting tables and render lifting-native clients; dynamic groups are displayed as rule-managed and read-only.
Lifting actions and cache revalidation
src/app/lifting/actions/*, src/app/baseball/actions/signals.ts, src/app/lifting/actions/__tests__/*, src/app/baseball/actions/__tests__/*
Program, group, session, and lift actions revalidate baseball routes too; dynamic groups are blocked from manual edits; lift_modification now materializes into helm_lifting_sessions.
Lift canvas and styles
src/components/baseball/performance/lift-canvas/*, src/components/baseball/performance/lift-onboarding/LiftOnboardingGate.tsx
Lift canvas surfaces switch selected/idle backgrounds to cream and the onboarding gate exits early when athleteId is missing.

Box Score, Player Profile, and Stats Rendering Refactor

Layer / File(s) Summary
Shared box-score formatting
src/components/baseball/box-score/format-stat.ts, src/components/baseball/box-score/BoxScoreEntry.tsx, src/components/baseball/box-score/BoxScoreView.tsx
New box-score format helpers and EMPTY_STAT are added, and box-score entry/view components adopt them.
Games actions, uploads, and player-season stats
src/app/baseball/actions/games.ts, src/app/baseball/actions/stats.ts, src/app/baseball/actions/insights.ts
Game/save/upload actions now handle insert failures explicitly, revalidate dynamic game/player routes, and remove an old exit-velocity insight; one CSV upload link field is removed.
Game detail UI and seeded games list
src/components/baseball/games/EditGameModal.tsx, src/components/baseball/games/GameDetailHeader.tsx, src/components/baseball/games/GamesList.tsx, src/app/baseball/(dashboard)/dashboard/stats/games/*, src/app/baseball/(dashboard)/dashboard/players/[id]/stats/page.tsx
New game-detail UI is added, GamesList accepts seeded initial state, game stats pages route through the new header, and the old profile-route link is removed.
Exit-velocity removal
src/app/baseball/(dashboard)/dashboard/roster/*, src/components/baseball/command-center/analytics/PlayerPerformanceGrid.tsx, src/components/baseball/player-stats/StatsOverviewCards.tsx
Exit-velocity columns and metrics are removed from roster export/sort, analytics grids, and stats overview cards.
Player profile canonical season stats
src/app/baseball/(dashboard)/dashboard/players/[id]/page.tsx, src/components/baseball/player-profile/PlayerProfileClient.tsx, src/components/baseball/season-stats/*
Player profile pages fetch season stats and render season/per-game metrics, trend, and pitching logs from box-score read models instead of legacy aggregate stats; the old season-stats client is removed.

Estimated code review effort: 5 (Critical) | ~110 minutes

Possibly related PRs

Suggested labels: security, database


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
No Service-Role In Client Bundles ❌ Error FAIL: scripts/e2e-supabase-admin.ts:5-7, scripts/backfill-baseball-slash-lines.ts:11-14, scripts/seed-rini-baseball-demo.ts:9-12 read SUPABASE_SERVICE_ROLE_KEY outside allowed admin paths. Move service-role clients into src/lib/supabase/admin* or src/app/api//admin/, or remove direct env reads from these changed scripts.
Title check ⚠️ Warning The title is related, but it does not follow the required Conventional Commit format or include an allowed scope. Rewrite it as a Conventional Commit with an allowed scope, e.g. docs(baseball): summarize statsync verification and shell/routes remediation.
Docstring Coverage ⚠️ Warning Docstring coverage is 40.80% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (9 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the PR’s statsync, routing, and hardening work, so it passes the lenient relevance check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Rls Coverage On New Tables ✅ Passed PASS: touched migrations are function/index rewrites only (20260708141000:35-78,102-156; 20260708150000:52-220; 20260709000000:24-26), so no changed migration adds CREATE TABLE.
Auth Check In Server Actions ✅ Passed Scanned 12 changed action files; no exported async function body contained direct .from()/.rpc() before supabase.auth.getUser(), and no async export arrows were present.
Sport-Prefixed Table Names ✅ Passed PASS: changed-file scan found no bare teams/players/coaches/rounds/events literals; query sites use prefixed tables like baseball_events and baseball_games.
No Destructive Writes ✅ Passed PASS: migration 20260708150000 replaces delete-then-insert with UPSERT+targeted DELETE, and saveCsvBoxScoreViaRpc uses the atomic RPC (migration:19-36; games.ts:3-12).
No Edits To Historical Migrations ✅ Passed Diff only touches supabase/migrations/202607*.sql files; none have a prefix <= 20260527120000.
Conventional Commits ✅ Passed PASS: The squashed commit subject docs(matrix): remove 22 closed owner-issue links per maintenance rule matches the Conventional Commits regex.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch integration/final-verification-2026-07-08
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

Comment @coderabbitai help to get the list of available commands.

@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@njrini99-code

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Pushed 1297b57 addressing the review: 53 root-cause fixes across all Major + correctness findings (pagination cap, cross-New-Year season bug, team-local dates, clearable optional fields, error-vs-empty honesty, stuck spinner, breadcrumb collapse, teardown guard, token violations, test-coverage gaps — full breakdown in the commit message).

Intentionally deferred, tracked in docs/audits/PRODUCTION_READINESS_MISSION_2026-07-09.md (added in this push): shared header-primitive extractions — those headers are replaced wholesale by the Living Annual masthead migration (wave W4); the readiness-matrix closed-issue cleanup lands with the W7 full matrix refresh.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
git log --oneline -15
echo "---"
git show --stat 1297b579 | head -100
echo "---commit message---"
git show -s --format=%B 1297b579

Length of output: 9597


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== backfill-baseball-slash-lines.ts pagination ==="
sed -n '1,220p' scripts/backfill-baseball-slash-lines.ts

echo "=== games.ts season_year ==="
rg -n "season_year|game_date" src/app/baseball/actions/games.ts | head -40

echo "=== EditGameModal clearable fields ==="
sed -n '1,220p' src/components/baseball/games/EditGameModal.tsx

echo "=== GamesList double fetch ==="
sed -n '1,80p' src/components/baseball/games/GamesList.tsx

echo "=== lift athlete context ==="
sed -n '1,140p' 'src/app/baseball/(dashboard)/dashboard/lift/_lift-athlete-context.ts'

Length of output: 27823


The verification confirms the claimed fixes are substantively implemented and root-caused, not superficial patches. Spot-checking the key items:

  • Pagination capscripts/backfill-baseball-slash-lines.ts:70-80 (fetchAllRows): properly pages via .range(from, to) in PAGE=1000 chunks for both the aggregates scan and the per-player stats fetch, matching the described PostgREST cap fix.
  • Cross-New-Year season bugsrc/app/baseball/actions/games.ts:729: seasonYear is now parsed from game.game_date.slice(0, 4) instead of new Date().getFullYear(), correctly fixing a Dec 31 game recalculated in January.
  • Clearable optional fieldssrc/components/baseball/games/EditGameModal.tsx:55-61: emptied optional fields (opponent_name, location, notes, weather) now send null instead of undefined, with an inline comment explaining why updateGame would otherwise silently retain stale values.
  • GamesList double-fetch/stalenesssrc/components/baseball/games/GamesList.tsx:71-79: adds a useEffect syncing initialGames/initialRecord/initialError into client state so a revalidated RSC payload from router.refresh() actually reaches the UI.
  • Lift athlete context (JUCO + Showcase dual-team)_lift-athlete-context.ts:39-89: resolvePlayerTeamIds now returns all active memberships and resolvePlayerLiftAthleteContext iterates through each before giving up, with logServerError on query failure instead of silent null. This is a legitimate root-cause fix (dual-team players previously could get incorrectly stuck on the first membership's org if it had no seeded Lift Lab athlete).

These are root-cause fixes, not surface patches — good work. The deferred items (shared header-primitive extraction to W4 Living Annual masthead migration, readiness-matrix cleanup to W7) are reasonable scope calls given they're superseded by planned follow-on waves rather than being ignored.

I'll kick off a fresh review pass over 1297b57 to catch anything the above spot-check didn't cover (e.g., the signals.ts team-local-today change, the lifting/actions revalidation helper, and the new nav/breadcrumb test assertions).

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (16)
src/app/baseball/(dashboard)/dashboard/performance/page.tsx (1)

163-168: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Readiness lookback window still computed in UTC — same bug class this diff exists to fix.

L165-168 builds fromDate for the 7-day readiness check-in query using new Date() (server UTC), while L238 correctly switches to a team-local today for the client. Both variables are named today but are different types/semantics (Date vs. team-local ISO string) declared in overlapping scope — easy to conflate later.

Net effect: for a team behind UTC, the readiness query's 7-day window can silently miss/include a day at the UTC boundary — exactly the class of bug todayIsoInTz(resolveTeamTimezone(...)) was introduced to eliminate, just not applied here.

🐛 Proposed fix — compute team-local `today` once, reuse for the readiness window
-  let readiness: BaseballReadinessSummary[] = [];
-  if (canViewReadiness && liftCtx) {
-    const today = new Date();
-    const sevenDaysAgo = new Date(today);
-    sevenDaysAgo.setDate(today.getDate() - 7);
-    const fromDate = sevenDaysAgo.toISOString().slice(0, 10);
+  // Team-local "today" so both the readiness lookback window and the client's
+  // check-in day math can't drift across the UTC boundary for teams west of
+  // Greenwich.
+  const today = todayIsoInTz(await resolveTeamTimezone(supabase, teamId));
+
+  let readiness: BaseballReadinessSummary[] = [];
+  if (canViewReadiness && liftCtx) {
+    const sevenDaysAgo = new Date(`${today}T00:00:00Z`);
+    sevenDaysAgo.setUTCDate(sevenDaysAgo.getUTCDate() - 7);
+    const fromDate = sevenDaysAgo.toISOString().slice(0, 10);

And drop the now-redundant const today = todayIsoInTz(...) block at L236-239 since it's hoisted above.

Also applies to: 236-239

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/performance/page.tsx around lines 163
- 168, The readiness 7-day lookback window is still being derived from server
UTC instead of the team’s local timezone, causing boundary-day mismatches. In
dashboard/performance/page.tsx, update the readiness query setup around the
canViewReadiness/liftCtx block to use the same team-local today value produced
by todayIsoInTz(resolveTeamTimezone(...)) that is used later for the client
view, and reuse that single value when computing fromDate. Remove the redundant
later today declaration so there is one clear today value with consistent
semantics across the readiness logic.
src/app/lifting/actions/programs.ts (2)

219-224: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

updateProgram was not actually migrated onto the new helper — despite the changelog claiming it was.

The line-range change description for this block says updateProgram now calls revalidateProgramPaths(programId), but the code still hand-rolls all 4 revalidatePath() calls verbatim — the exact duplication the new helper exists to eliminate. Functionally equivalent today, but it defeats the DRY purpose of the refactor and will silently drift the moment revalidateProgramPaths gains another path (e.g. a future BASEBALL_LIVE_PATH bust on status change).

♻️ Adopt the helper here too
-    revalidatePath(`${LAB_PATH}/${programId}`);
-    revalidatePath(LAB_PATH);
-    revalidatePath(`${BASEBALL_PROGRAMS_PATH}/${programId}`);
-    revalidatePath(BASEBALL_PROGRAMS_PATH);
+    revalidateProgramPaths(programId);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/lifting/actions/programs.ts` around lines 219 - 224, `updateProgram`
is still duplicating cache invalidation instead of using the shared helper.
Update the `updateProgram` flow in `programs.ts` to call
`revalidateProgramPaths(programId)` after a successful save, matching the
refactor used elsewhere, and remove the four inline `revalidatePath()` calls so
the invalidation logic stays centralized and consistent with future path
additions.

601-614: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

publishProgram mutates program status but never revalidates the program's own detail route.

L603-607 flips prog.status from draft to active on first publish, but the revalidation block (L609-613) only busts the list routes (LAB_PATH, /lifting/dashboard/sessions, BASEBALL_PROGRAMS_PATH, BASEBALL_LIVE_PATH, BASEBALL_LIFT_PATH) — never ${LAB_PATH}/${programId} or ${BASEBALL_PROGRAMS_PATH}/${programId}. A coach who publishes from the program's own detail/builder page will keep seeing a stale draft status in the cached RSC payload until an unrelated navigation elsewhere triggers a refetch. updateProgram/duplicateLiftDay/duplicateLiftWeek all correctly bust the per-program detail route on status-affecting writes — publishProgram is the one mutation here that skips it.

As per path instructions, src/app/**/actions/**/*.ts requires "Mutations must revalidatePath() the affected routes."

🐛 Bust the program detail route on publish
-    revalidatePath(LAB_PATH);
-    revalidatePath('/lifting/dashboard/sessions');
-    revalidatePath(BASEBALL_PROGRAMS_PATH);
-    revalidatePath(BASEBALL_LIVE_PATH);
-    revalidatePath(BASEBALL_LIFT_PATH);
+    revalidateProgramPaths(programId);
+    revalidatePath('/lifting/dashboard/sessions');
+    revalidatePath(BASEBALL_LIVE_PATH);
+    revalidatePath(BASEBALL_LIFT_PATH);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/lifting/actions/programs.ts` around lines 601 - 614, publishProgram
updates the program from draft to active but only revalidates list routes, so
add revalidation for the program’s own detail page after the status update. In
the publishProgram flow, ensure revalidatePath is called for the affected
per-program route(s) derived from LAB_PATH and BASEBALL_PROGRAMS_PATH using
programId, alongside the existing list-route invalidations, so the cached
builder/detail view reflects the new status immediately.

Source: Path instructions

src/app/baseball/actions/calendar.ts (1)

182-224: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No zod validation on CreateEventInput before it reaches Supabase.

Per path instructions for src/app/**/actions/**/*.ts: "Validate every input with zod before passing to Supabase." input.title, input.eventType, input.startDate, etc. flow straight into the fromUntyped(...).insert(...) call with no schema validation — an unvalidated eventType string, for instance, can silently violate whatever CHECK constraint baseball_events.event_type has and surface as a raw sanitized DB error instead of a clean 400-style validation message. Pre-existing in this file (not introduced by this diff), but since the function is touched here, it's a reasonable point to add a zod schema (z.object({ title: z.string().min(1), eventType: z.enum([...]), startDate: z.string(), ... })) and parse input at the top of the action.

Based on path instructions: "Server action files. Every exported function MUST start with auth check... Validate every input with zod before passing to Supabase."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/actions/calendar.ts` around lines 182 - 224, The
CreateEventInput action currently passes raw user input straight into the
Supabase insert without zod validation, which violates the action input
contract. Add a zod schema at the top of this exported action in
createEvent/CreateEventInput flow, parse input before any use, and validate
fields like title, eventType, startDate, startTime, endDate, timezoneOffset, and
ids before calling fromUntyped(...).insert(...); keep the existing auth check
and only proceed to Supabase after successful schema parsing.

Source: Path instructions

src/app/baseball/(dashboard)/dashboard/dev-plans/[id]/page.tsx (1)

34-44: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Any thrown error is silently rendered as "plan not found."

fetchPlan's catch block (L38-41) sets notFound(true) unconditionally regardless of why getDevPlanForCoach threw — a real permissions error, a network timeout, or an RLS denial gets the exact same "This development plan could not be found." copy as an actually-missing plan. This hides real failures from the coach and from anyone triaging a support ticket, and is the same class of error-vs-empty-state conflation the PR description says was fixed elsewhere in this batch — it wasn't fixed here.

🐛 Proposed fix: distinguish real error from not-found
   const [notFound, setNotFound] = useState(false);
+  const [fetchError, setFetchError] = useState<string | null>(null);
   const [isPending, startTransition] = useTransition();
   const [pendingGoalId, setPendingGoalId] = useState<string | null>(null);

   const fetchPlan = useCallback(async () => {
     if (!params?.id) return;
     setLoading(true);

     try {
       const data = await getDevPlanForCoach(params.id);
       setPlan(data);
       setNotFound(false);
+      setFetchError(null);
     } catch (error) {
       console.error('Error fetching dev plan:', error);
       setPlan(null);
-      setNotFound(true);
+      // Only a genuine "no such plan / not visible to this coach" result
+      // should render the not-found state; anything else is a real error.
+      if (error instanceof DevPlanNotFoundError) {
+        setNotFound(true);
+      } else {
+        setFetchError(error instanceof Error ? error.message : 'Could not load this plan.');
+      }
     } finally {
       setLoading(false);
     }
   }, [params?.id]);

Then render a distinct error state (with a retry action) before the notFound || !plan branch, instead of collapsing both into the same message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/dev-plans/[id]/page.tsx around lines
34 - 44, The fetchPlan error handling in the dashboard page is conflating real
failures with a missing plan by unconditionally calling setNotFound(true) in the
catch block. Update the page component around getDevPlanForCoach, fetchPlan, and
the render logic so only an actual “not found” case sets notFound, while other
errors set a separate error state and show a distinct retryable error UI before
the notFound || !plan branch.
src/app/baseball/actions/games.ts (1)

200-219: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Orphaned calendar event when the game insert fails after the event was created.

The new fail-fast at Line 194-196 correctly prevents "a game the coach believes is on the calendar but isn't." The mirror case is unhandled: when create_calendar_event created the event (Line 170-188) but the baseball_games insert errors at Line 219, the event row survives — a calendar event with no backing game. importScheduleImpl already uses the rollback pattern (Line 1763) for exactly this.

Only roll back the event we ourselves created (not a caller-supplied input.event_id).

🐛 Proposed fix
-    if (error) return { success: false, error: sanitizeDbError(error, 'games') };
+    if (error) {
+      // Roll back the calendar event we just created so we don't strand an
+      // event on the calendar with no backing game (mirrors importScheduleImpl).
+      if (input.create_calendar_event && !input.event_id && eventId) {
+        await supabase.from('baseball_events').delete().eq('id', eventId);
+      }
+      return { success: false, error: sanitizeDbError(error, 'games') };
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/actions/games.ts` around lines 200 - 219, The game insert
path in createScheduleGame leaves behind an orphaned calendar event when the
baseball_games insert fails after create_calendar_event succeeds. Update the
create flow around the insert/error handling in games.ts so that, if the insert
returns an error and this request created the event itself, it rolls back that
event before returning the sanitized error. Use the existing rollback pattern
from importScheduleImpl and make sure you only delete the event created in this
function, not any caller-supplied input.event_id.
src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx (1)

62-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Week/day badge counts can truncate at the API row cap. src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx:62-77 — both .in() reads are unpaginated, so linked helm_lifting_weeks/helm_lifting_days rows can silently stop at PostgREST’s 1000-row limit and undercount week_count/day_count. Chunk the reads or switch to count-only aggregation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/performance/programs/page.tsx around
lines 62 - 82, The week/day badge counting logic in the programs page can
silently undercount because the `fromUntyped(...).in(...)` queries for
`helm_lifting_weeks` and `helm_lifting_days` are unpaginated and can hit the
PostgREST row cap. Update the counting flow in the programs page so
`weekCountByProgram` and `dayCountByProgram` are built from chunked queries or
count-only aggregation rather than a single bulk fetch, and keep the existing
`programByWeek`/`weeks` handling consistent while doing so.
src/app/baseball/actions/signals.ts (1)

410-419: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a DB-level dedupe constraint for NULL-program lift sessions. src/app/baseball/actions/signals.ts:410-419 only checks (athlete_id, scheduled_date, title) in app code, but supabase/migrations/20260625000020_helm_lifting_data_sessions_readiness.sql:19-29 only enforces UNIQUE (program_assignment_id, athlete_id), which does not protect program_assignment_id IS NULL rows. Add a partial unique index on (athlete_id, scheduled_date, title) for NULL-program sessions and handle the unique-violation path instead of relying on .maybeSingle().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/actions/signals.ts` around lines 410 - 419, The dedupe in
signals.ts is only enforced in app code for NULL-program sessions, so add a
database-level partial unique index for helm_lifting_sessions on athlete_id,
scheduled_date, and title where program_assignment_id IS NULL in the readiness
migration. Then update the conversion flow in signals.ts to rely on that
constraint by catching the unique-violation path instead of using .maybeSingle()
as the only guard, keeping the existing dedupe logic around the
createLiftAssignment/session creation path.
src/app/baseball/(dashboard)/dashboard/lift/page.tsx (1)

12-13: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Apply the same split-query fix in src/app/lifting/(dashboard)/dashboard/lift/page.tsx:103-126. It still uses one combined or(...) query with .limit(20) and a post-filter, so capped results can drop overdue open sessions. Extract the session read into a shared helper to keep both lift pages aligned.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/lift/page.tsx around lines 12 - 13,
The lift dashboard still uses a single combined or(...) query with .limit(20)
and a post-filter, which can omit overdue open sessions when the cap is hit.
Update the `src/app/lifting/(dashboard)/dashboard/lift/page.tsx` flow to use the
same split-query approach as the other lift page by separating the session read
and readiness check reads, then merging results in code. Factor the session
fetch logic into a shared helper so both `page.tsx` implementations stay aligned
and reuse the same overdue-session handling.
src/app/baseball/(dashboard)/dashboard/performance/programs/[programId]/page.tsx (1)

210-231: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

getAssignContext still swallows query failures — inconsistent with the new honest-error handling.

getProgramTree now surfaces DB/RLS failures, but the newly-introduced getAssignContext discards error on both queries (athletes ?? [], groups ?? []). A failed athletes/groups read renders an empty assign roster with no signal, silently blocking the coach from assigning the program. At minimum, log the errors so the failure is observable.

🛠️ Proposed fix (minimal: log on failure)
-  const [{ data: athletes }, { data: groups }] = await Promise.all([
+  const [{ data: athletes, error: athletesError }, { data: groups, error: groupsError }] = await Promise.all([
     fromUntyped(supabase, 'helm_lifting_athletes')
       .select('id, first_name, last_name, position, sport')
       .eq('organization_id', organizationId)
       .eq('team_id', teamId)
       .eq('is_active', true)
       .order('last_name', { ascending: true })
-      .limit(500) as Promise<{ data: Array<Pick<HelmLiftingAthleteRow, 'id' | 'first_name' | 'last_name' | 'position' | 'sport'>> | null }>,
+      .limit(500) as Promise<{ data: Array<Pick<HelmLiftingAthleteRow, 'id' | 'first_name' | 'last_name' | 'position' | 'sport'>> | null; error: unknown }>,
     fromUntyped(supabase, 'helm_lifting_groups')
       .select('id, name, group_type')
       .eq('organization_id', organizationId)
       .eq('team_id', teamId)
       .eq('is_active', true)
       .order('name', { ascending: true })
-      .limit(100) as Promise<{ data: Array<Pick<HelmLiftingGroupRow, 'id' | 'name' | 'group_type'>> | null }>,
+      .limit(100) as Promise<{ data: Array<Pick<HelmLiftingGroupRow, 'id' | 'name' | 'group_type'>> | null; error: unknown }>,
   ]);
+
+  if (athletesError || groupsError) {
+    await logServerError(
+      `[performance/programs] getAssignContext query failed: ${
+        (athletesError as Error)?.message ?? (groupsError as Error)?.message ?? 'unknown'
+      }`,
+      { action: 'baseball.programEditor.getAssignContext', metadata: { organizationId, teamId } },
+    );
+  }

   return { athletes: athletes ?? [], groups: groups ?? [] };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/baseball/`(dashboard)/dashboard/performance/programs/[programId]/page.tsx
around lines 210 - 231, `getAssignContext` is still hiding failures from the
athletes/groups queries, so make the error observable instead of returning
silent empty arrays. In `getAssignContext`, inspect the results from the two
`fromUntyped(...).select(...)` calls and, when either query returns an `error`,
log it with enough context to identify whether it came from
`helm_lifting_athletes` or `helm_lifting_groups`. Keep the existing
`getAssignContext` shape, but avoid swallowing the failure without any signal so
the assignment UI can be debugged consistently with `getProgramTree`.
src/app/baseball/(dashboard)/_components/breadcrumbs.ts (1)

1-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add unit tests for this module — it's explicitly designed to be unit-tested, and it already had a bug slip through review once.

The header comment (lines 5-8) states this file was split out specifically "so this logic can be unit-tested directly without dragging in the whole shell's client-only dependency graph." No breadcrumbs.test.ts is in this changeset. Given buildBreadcrumbs's hub-vs-subpage label logic was already wrong once (the settings-collapse bug fixed at lines 80-91), lock in the fix with cases like:

  • /dashboard/settingsSettings (hub landing page, best.label used)
  • /dashboard/settings/permissionsPermissions (subpage, toTitle(lastSegment) used)
  • /dashboard/players/<uuid> → parent-based fallback, never the raw UUID
  • /dashboard/players/<uuid>/stats with overrideLabel → override wins
  • a numeric segment case per the sibling comment on isIdShapedSegment

Want me to draft the test file (toTitle/singularize/isIdShapedSegment/buildBreadcrumbs, table-driven) as a follow-up?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/_components/breadcrumbs.ts around lines 1 - 92,
Add a unit test file for breadcrumbs.ts to lock in the pure breadcrumb behavior
that this module was split out for. Cover buildBreadcrumbs, toTitle,
singularize, and isIdShapedSegment with table-driven cases: hub landing pages
should use best.label, deeper subpages should use the trailing segment
title-cased, UUID/numeric segments should never be exposed raw, parent-based
fallback should apply for id-shaped routes, and overrideLabel should win when
present. Reference the existing buildBreadcrumbs and isIdShapedSegment symbols
so the tests stay aligned with the current routing logic.
src/app/baseball/(dashboard)/dashboard/calendar/page.tsx (1)

75-84: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Calendar query is still capped at 500 rows src/app/baseball/(dashboard)/dashboard/calendar/page.tsx:95-101 — teams with >500 events in the 455-day window will silently drop later rows. The repo’s Supabase config already allows 1000 rows, so raise this limit to 1000 or switch to explicit chunking if you need more.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/calendar/page.tsx around lines 75 -
84, The calendar data fetch in the page component is still limited to 500 rows,
which can drop events inside the bounded window. Update the query in the
dashboard calendar page’s event-loading logic to use the repo-supported 1000-row
cap, or replace the hard cap with explicit chunking if the result set may exceed
that; keep the existing window logic in sync with the fetch in the same calendar
page flow.
src/app/baseball/(dashboard)/dashboard/players/[id]/page.tsx (1)

109-133: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Surface season-stat query errors instead of blank stats src/app/baseball/actions/games.ts:1399-1447 ignores Supabase error values and still returns success: true, so src/app/baseball/(dashboard)/dashboard/players/[id]/page.tsx:210-212 can only render null/[] and can’t distinguish “no stats yet” from “stats query failed.” Return { success: false, error } on those query failures; withAdminObserved will then emit the failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/players/[id]/page.tsx around lines
109 - 133, The season-stats fetch in getPlayerSeasonStats is swallowing Supabase
errors and returning success as if the query worked, which makes the player page
in page.tsx render blank stats without knowing it failed. Update the action to
return success: false with the underlying error whenever the stats query fails,
and keep the normal success path only for real results so withAdminObserved can
log the failure and the dashboard can distinguish “no stats” from “query error.”
src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx (1)

123-198: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Residual: post-sessions query errors are still swallowed into a degraded board.

sessionsError now correctly returns { ok: false } (line 97-103), but sessionExercisesError (line 127) and the Promise.all batch errors (athletesError, setResultsError, etc., logged at 191-198) are only logged. If helm_lifting_session_exercises RLS/transient-fails while helm_lifting_sessions succeeds, exerciseIds is [], set-results is skipped, and every athlete renders with exercises: [], current_exercise: null, total_exercises: 0 (line 259-261) — a live board where no coach can see or log a set, indistinguishable from "athletes have no assigned work." That's the same error-vs-empty conflation this PR is standardizing away, applied inconsistently one query later.

At minimum, treat sessionExercisesError like sessionsError (it's the load-bearing query for the write surface):

🛡️ Proposed fix
   await logQueryError('helm_lifting_session_exercises', sessionExercisesError, teamId);
+
+  if (sessionExercisesError) {
+    // Same rationale as sessionsError: a hard failure here yields an
+    // athlete grid with zero loggable exercises — surface it, don't degrade.
+    return { ok: false };
+  }

Consider extending the same treatment to the athletes query in the batch (a null athletes map silently drops names/positions).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/performance/live/page.tsx around
lines 123 - 198, The live board is still collapsing query failures into empty
data, especially for the load-bearing `sessionExercisesError` in the `page.tsx`
data fetch. Update the `load`/query path around `sessionExercisesError` and the
`Promise.all` batch so a failed `helm_lifting_session_exercises` request returns
a non-success state instead of continuing with `exerciseIds = []`; also consider
treating `athletesError` the same way so missing athletes data is not silently
rendered as empty rows. Use the existing `sessionsError` handling pattern and
the identified symbols `sessionExercisesError`, `Promise.all`, and
`logQueryError` to keep the failure visible.
e2e/baseball-box-score.spec.ts (1)

397-415: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

/baseball/dashboard/stats/season is no longer a supported route e2e/baseball-box-score.spec.ts:397-415
page.goto('/baseball/dashboard/stats/season') at :402 still targets a 404. The stats flow now lives at /baseball/dashboard/stats-center (with /baseball/dashboard/stats redirecting there), so this block should be repointed or removed if season stats is no longer intended.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/baseball-box-score.spec.ts` around lines 397 - 415, The Coach - Season
Stats Dashboard test block is still navigating to a removed route and should be
updated. In the `test.beforeEach` for `Coach - Season Stats Dashboard`, replace
the `page.goto` target from the old season stats path to the supported stats
center route used by the app, and keep the assertions aligned with the new page
content; if the season-specific coverage is no longer valid, remove the `should
display...` and `should show...` tests from this `describe` block instead of
keeping a 404 target.
src/components/baseball/player-profile/PlayerProfileClient.tsx (1)

196-202: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Parse baseball game_date values as local midnight

src/components/baseball/player-profile/PlayerProfileClient.tsx:196-202 uses new Date(iso) for stat.game?.game_date (:1108) and trend labels (:396). baseball_games.game_date is a date column (supabase/migrations/20260527000000_prod_public_baseline.sql:7730), so YYYY-MM-DD values can render a day early in US timezones. Match the existing baseball date pattern used in src/components/baseball/box-score/BoxScoreView.tsx:25 and src/components/baseball/box-score/BoxScoreEntry.tsx:221.

Suggested fix
 function formatDate(iso: string): string {
-  return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
+  const d = /^\d{4}-\d{2}-\d{2}$/.test(iso) ? new Date(`${iso}T00:00:00`) : new Date(iso);
+  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
 }

 function formatShortDate(iso: string): string {
-  return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
+  const d = /^\d{4}-\d{2}-\d{2}$/.test(iso) ? new Date(`${iso}T00:00:00`) : new Date(iso);
+  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/baseball/player-profile/PlayerProfileClient.tsx` around lines
196 - 202, The baseball date formatting helpers in PlayerProfileClient treat
YYYY-MM-DD values as UTC, which can shift game_date labels by a day in US
timezones. Update formatDate and formatShortDate to parse baseball game dates as
local midnight using the same pattern already used in BoxScoreView and
BoxScoreEntry, and keep the call sites for stat.game?.game_date and the trend
label formatting unchanged except for using the corrected helpers.
♻️ Duplicate comments (4)
src/app/baseball/actions/calendar.ts (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both prior review findings are now fixed.

  • Soft-failure logging: logServerError(..., 'warning') is now called for both the attendance-insert failure (246-254) and the linked-game-insert failure (272-280), so these no longer disappear once the toast is dismissed.
  • Separator bug: warnings.join('; ') (289) no longer garbles concatenated messages when both secondary writes fail.

One item from the prior review is still outstanding: no test asserts createBaseballEvent returns { success: true, warning: '...' } when baseball_event_attendance or baseball_games inserts fail. EventsClient.tsx branches on result.warning, so a regression here (e.g. reverting the join separator, or dropping a logServerError call) wouldn't be caught by tsc/lint alone.

Also applies to: 73-84, 225-233, 242-260, 271-290

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/actions/calendar.ts` at line 7, Add a regression test for
createBaseballEvent to verify that when either the baseball_event_attendance
insert or the baseball_games insert fails, the function still returns { success:
true, warning: '...' } and preserves the warning text used by EventsClient.tsx.
Cover both single-failure and dual-failure cases so the warning aggregation path
(including warnings.join('; ')) and the logServerError calls remain protected
from regressions.
src/app/baseball/(dashboard)/dashboard/dev-plans/[id]/page.tsx (1)

90-102: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Header block still copy-pasted 4x — not extracted.

The focus-visible ring was added to all four Link instances (good — addresses half of the prior ask), but the 13-line back-link/title/subtitle block at lines 90-102, 113-125, 140-152, and 168-180 remains a verbatim quadruple copy-paste. Any future wording/behavior change needs 4 synchronized edits.

Extract a local DevPlanHeader component and render it in each of the 4 branches — same fix proposed previously, still applicable to this revision.

Also applies to: 113-125, 140-152, 168-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/dev-plans/[id]/page.tsx around lines
90 - 102, The back-link/title/subtitle header block in the dev plan page is
still duplicated across the four conditional branches, so extract it into a
local DevPlanHeader component and reuse it everywhere. Move the repeated Link +
heading markup into that component and render it from each branch in page.tsx,
keeping the existing focus-visible styles intact.
src/app/baseball/(dashboard)/dashboard/academics/AcademicsClient.tsx (1)

234-237: 📐 Maintainability & Code Quality | 🔵 Trivial

Header duplication still unresolved — text drift is fixed, but the root cause isn't.

The previous review flagged this exact 4x-copy-pasted header block because the text had drifted between states. That drift is now fixed (all four now say "Track student-athlete academic progress and eligibility"), but the underlying problem — no shared header component, four independent copies of the same markup — is still there (the Header import was removed and replaced by these inline copies). The next edit to this copy will silently re-introduce drift the same way the last one did.

♻️ Extract a shared header (still applicable)
+function AcademicsHeader() {
+  return (
+    <div className="border-b border-warm-200/60 px-6 pb-5 pt-6 lg:px-8 lg:pt-8">
+      <h1 className="text-h2 font-semibold text-warm-900">Academics</h1>
+      <p className="mt-1 text-body-sm text-warm-500">
+        Track student-athlete academic progress and eligibility
+      </p>
+    </div>
+  );
+}

Replace each of the 4 inline blocks (234-237, 253-256, 310-313, 368-371) with <AcademicsHeader />.

Also applies to: 253-256, 310-313, 368-371

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/academics/AcademicsClient.tsx around
lines 234 - 237, The Academics page still has four duplicated inline header
blocks in AcademicsClient instead of using a shared component, which leaves the
copy prone to drift. Restore a reusable header by extracting the repeated markup
into AcademicsHeader and use it in AcademicsClient for each of the four existing
header sections, replacing the inline h1/p blocks with the shared component so
there is one source of truth.
src/app/baseball/(dashboard)/dashboard/program/page.tsx (1)

214-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Header block is still duplicated 3x — extraction not applied.

The diff only fixed the flex-wrap on line 251; the previously-flagged copy-paste of the border-b … px-6 pb-5 pt-6 … header shell across the coach-access, no-org, and main branches (lines 214-217, 232-235, 251-262) is unchanged. Same drift risk as before: a future style tweak has to be applied in 3 places, and a 4th branch could easily reintroduce the missing-flex-wrap bug this PR just fixed on one of the three.

♻️ Proposed local extraction
+function ProgramPageHeader({
+  subtitle,
+  action,
+}: { subtitle: string; action?: React.ReactNode }) {
+  return (
+    <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-3 border-b border-warm-200/60 px-6 pb-5 pt-6 lg:px-8 lg:pt-8">
+      <div>
+        <h1 className="text-h2 font-semibold text-warm-900">Program Profile</h1>
+        <p className="mt-1 text-body-sm text-warm-500">{subtitle}</p>
+      </div>
+      {action}
+    </div>
+  );
+}

Then replace each inline <div> block at 214-217, 232-235, and 251-262 with <ProgramPageHeader subtitle="..." action={...} />.

Also applies to: 232-235, 251-262

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/program/page.tsx around lines 214 -
217, The `ProgramPage` header shell is still duplicated across the coach-access,
no-org, and main branches instead of being extracted. Move the shared `border-b
... px-6 pb-5 pt-6 ...` layout into a reusable `ProgramPageHeader` component in
`page.tsx` and replace each inline header block with that component, passing the
appropriate subtitle and action/content for each branch so future style changes
only need one edit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/baseball/`(dashboard)/_components/breadcrumbs.ts:
- Around line 89-91: The breadcrumb label logic in breadcrumbs.ts still uses a
non-null assertion on best when computing the label. Refactor the
isHubLandingPage/label flow in the breadcrumbs builder so the best !== null
check is kept in the same expression that accesses best.label, removing best!
entirely while preserving the same Dashboard and overrideLabel behavior.

In `@src/app/baseball/`(dashboard)/dashboard/lift/_lift-athlete-context.ts:
- Around line 38-44: The doc comment in _lift-athlete-context overstates the
dual-membership case as “JUCO + Showcase,” which conflicts with the Baseball
Product user-type matrix. Update the comment above the membership lookup logic
to describe the correct dual-team-capable player types (High School with
optional Showcase, Showcase with optional High School) while keeping the
explanation for returning the full ordered list and iterating through
memberships in place.

In `@src/app/baseball/`(dashboard)/dashboard/scouting/page.tsx:
- Around line 78-96: The `SectionMasthead` plus descriptive paragraph is
duplicated in both the null-context branch and the main return in
`ScoutingPage`; hoist that shared JSX into a local const and reuse it in both
places so the copy stays in sync. Update the `ScoutingPage` render path to
reference the shared masthead content instead of repeating the same block in
each branch.

In `@src/app/baseball/`(dashboard)/dashboard/stats/games/page.tsx:
- Around line 53-60: The season-record fetch failure in the dashboard page is
being ignored, while GamesList only understands the games-level initialError
banner. Update the page component that builds the props for GamesList to carry a
separate record-specific error state such as initialRecordError/recordError
instead of reusing initialError, and wire GamesList to render or otherwise
surface that distinct season-record failure without showing “Couldn't load
games.” Use the existing symbols recordResult, initialError, and GamesList to
locate the affected prop flow.
- Around line 45-60: The season-record fetch failure is only being logged in the
page component and never reaches the UI, so `recordResult` errors are hidden
while `gamesResult` errors are shown through `initialError`. Update `page.tsx`
in the `GamesPage` flow to carry a separate record-error state (or merge it into
the existing error handling) and pass it into the header/list rendering path
alongside `GamesList`, using the existing `getTeamSeasonRecord`, `getTeamGames`,
and `initialError` wiring so DB/RLS failures surface visibly instead of being
silently ignored.

In `@src/components/baseball/settings/PhilosophySettingsClient.tsx`:
- Line 247: The settings route is hardcoded in multiple places in
PhilosophySettingsClient, so extract a shared SETTINGS_PATH constant for
'/baseball/dashboard/settings' and use it in the router.push call and the other
matching reference to prevent drift if the route changes.

---

Outside diff comments:
In `@e2e/baseball-box-score.spec.ts`:
- Around line 397-415: The Coach - Season Stats Dashboard test block is still
navigating to a removed route and should be updated. In the `test.beforeEach`
for `Coach - Season Stats Dashboard`, replace the `page.goto` target from the
old season stats path to the supported stats center route used by the app, and
keep the assertions aligned with the new page content; if the season-specific
coverage is no longer valid, remove the `should display...` and `should show...`
tests from this `describe` block instead of keeping a 404 target.

In `@src/app/baseball/`(dashboard)/_components/breadcrumbs.ts:
- Around line 1-92: Add a unit test file for breadcrumbs.ts to lock in the pure
breadcrumb behavior that this module was split out for. Cover buildBreadcrumbs,
toTitle, singularize, and isIdShapedSegment with table-driven cases: hub landing
pages should use best.label, deeper subpages should use the trailing segment
title-cased, UUID/numeric segments should never be exposed raw, parent-based
fallback should apply for id-shaped routes, and overrideLabel should win when
present. Reference the existing buildBreadcrumbs and isIdShapedSegment symbols
so the tests stay aligned with the current routing logic.

In `@src/app/baseball/`(dashboard)/dashboard/calendar/page.tsx:
- Around line 75-84: The calendar data fetch in the page component is still
limited to 500 rows, which can drop events inside the bounded window. Update the
query in the dashboard calendar page’s event-loading logic to use the
repo-supported 1000-row cap, or replace the hard cap with explicit chunking if
the result set may exceed that; keep the existing window logic in sync with the
fetch in the same calendar page flow.

In `@src/app/baseball/`(dashboard)/dashboard/dev-plans/[id]/page.tsx:
- Around line 34-44: The fetchPlan error handling in the dashboard page is
conflating real failures with a missing plan by unconditionally calling
setNotFound(true) in the catch block. Update the page component around
getDevPlanForCoach, fetchPlan, and the render logic so only an actual “not
found” case sets notFound, while other errors set a separate error state and
show a distinct retryable error UI before the notFound || !plan branch.

In `@src/app/baseball/`(dashboard)/dashboard/lift/page.tsx:
- Around line 12-13: The lift dashboard still uses a single combined or(...)
query with .limit(20) and a post-filter, which can omit overdue open sessions
when the cap is hit. Update the
`src/app/lifting/(dashboard)/dashboard/lift/page.tsx` flow to use the same
split-query approach as the other lift page by separating the session read and
readiness check reads, then merging results in code. Factor the session fetch
logic into a shared helper so both `page.tsx` implementations stay aligned and
reuse the same overdue-session handling.

In `@src/app/baseball/`(dashboard)/dashboard/performance/live/page.tsx:
- Around line 123-198: The live board is still collapsing query failures into
empty data, especially for the load-bearing `sessionExercisesError` in the
`page.tsx` data fetch. Update the `load`/query path around
`sessionExercisesError` and the `Promise.all` batch so a failed
`helm_lifting_session_exercises` request returns a non-success state instead of
continuing with `exerciseIds = []`; also consider treating `athletesError` the
same way so missing athletes data is not silently rendered as empty rows. Use
the existing `sessionsError` handling pattern and the identified symbols
`sessionExercisesError`, `Promise.all`, and `logQueryError` to keep the failure
visible.

In `@src/app/baseball/`(dashboard)/dashboard/performance/page.tsx:
- Around line 163-168: The readiness 7-day lookback window is still being
derived from server UTC instead of the team’s local timezone, causing
boundary-day mismatches. In dashboard/performance/page.tsx, update the readiness
query setup around the canViewReadiness/liftCtx block to use the same team-local
today value produced by todayIsoInTz(resolveTeamTimezone(...)) that is used
later for the client view, and reuse that single value when computing fromDate.
Remove the redundant later today declaration so there is one clear today value
with consistent semantics across the readiness logic.

In
`@src/app/baseball/`(dashboard)/dashboard/performance/programs/[programId]/page.tsx:
- Around line 210-231: `getAssignContext` is still hiding failures from the
athletes/groups queries, so make the error observable instead of returning
silent empty arrays. In `getAssignContext`, inspect the results from the two
`fromUntyped(...).select(...)` calls and, when either query returns an `error`,
log it with enough context to identify whether it came from
`helm_lifting_athletes` or `helm_lifting_groups`. Keep the existing
`getAssignContext` shape, but avoid swallowing the failure without any signal so
the assignment UI can be debugged consistently with `getProgramTree`.

In `@src/app/baseball/`(dashboard)/dashboard/performance/programs/page.tsx:
- Around line 62-82: The week/day badge counting logic in the programs page can
silently undercount because the `fromUntyped(...).in(...)` queries for
`helm_lifting_weeks` and `helm_lifting_days` are unpaginated and can hit the
PostgREST row cap. Update the counting flow in the programs page so
`weekCountByProgram` and `dayCountByProgram` are built from chunked queries or
count-only aggregation rather than a single bulk fetch, and keep the existing
`programByWeek`/`weeks` handling consistent while doing so.

In `@src/app/baseball/`(dashboard)/dashboard/players/[id]/page.tsx:
- Around line 109-133: The season-stats fetch in getPlayerSeasonStats is
swallowing Supabase errors and returning success as if the query worked, which
makes the player page in page.tsx render blank stats without knowing it failed.
Update the action to return success: false with the underlying error whenever
the stats query fails, and keep the normal success path only for real results so
withAdminObserved can log the failure and the dashboard can distinguish “no
stats” from “query error.”

In `@src/app/baseball/actions/calendar.ts`:
- Around line 182-224: The CreateEventInput action currently passes raw user
input straight into the Supabase insert without zod validation, which violates
the action input contract. Add a zod schema at the top of this exported action
in createEvent/CreateEventInput flow, parse input before any use, and validate
fields like title, eventType, startDate, startTime, endDate, timezoneOffset, and
ids before calling fromUntyped(...).insert(...); keep the existing auth check
and only proceed to Supabase after successful schema parsing.

In `@src/app/baseball/actions/games.ts`:
- Around line 200-219: The game insert path in createScheduleGame leaves behind
an orphaned calendar event when the baseball_games insert fails after
create_calendar_event succeeds. Update the create flow around the insert/error
handling in games.ts so that, if the insert returns an error and this request
created the event itself, it rolls back that event before returning the
sanitized error. Use the existing rollback pattern from importScheduleImpl and
make sure you only delete the event created in this function, not any
caller-supplied input.event_id.

In `@src/app/baseball/actions/signals.ts`:
- Around line 410-419: The dedupe in signals.ts is only enforced in app code for
NULL-program sessions, so add a database-level partial unique index for
helm_lifting_sessions on athlete_id, scheduled_date, and title where
program_assignment_id IS NULL in the readiness migration. Then update the
conversion flow in signals.ts to rely on that constraint by catching the
unique-violation path instead of using .maybeSingle() as the only guard, keeping
the existing dedupe logic around the createLiftAssignment/session creation path.

In `@src/app/lifting/actions/programs.ts`:
- Around line 219-224: `updateProgram` is still duplicating cache invalidation
instead of using the shared helper. Update the `updateProgram` flow in
`programs.ts` to call `revalidateProgramPaths(programId)` after a successful
save, matching the refactor used elsewhere, and remove the four inline
`revalidatePath()` calls so the invalidation logic stays centralized and
consistent with future path additions.
- Around line 601-614: publishProgram updates the program from draft to active
but only revalidates list routes, so add revalidation for the program’s own
detail page after the status update. In the publishProgram flow, ensure
revalidatePath is called for the affected per-program route(s) derived from
LAB_PATH and BASEBALL_PROGRAMS_PATH using programId, alongside the existing
list-route invalidations, so the cached builder/detail view reflects the new
status immediately.

In `@src/components/baseball/player-profile/PlayerProfileClient.tsx`:
- Around line 196-202: The baseball date formatting helpers in
PlayerProfileClient treat YYYY-MM-DD values as UTC, which can shift game_date
labels by a day in US timezones. Update formatDate and formatShortDate to parse
baseball game dates as local midnight using the same pattern already used in
BoxScoreView and BoxScoreEntry, and keep the call sites for stat.game?.game_date
and the trend label formatting unchanged except for using the corrected helpers.

---

Duplicate comments:
In `@src/app/baseball/`(dashboard)/dashboard/academics/AcademicsClient.tsx:
- Around line 234-237: The Academics page still has four duplicated inline
header blocks in AcademicsClient instead of using a shared component, which
leaves the copy prone to drift. Restore a reusable header by extracting the
repeated markup into AcademicsHeader and use it in AcademicsClient for each of
the four existing header sections, replacing the inline h1/p blocks with the
shared component so there is one source of truth.

In `@src/app/baseball/`(dashboard)/dashboard/dev-plans/[id]/page.tsx:
- Around line 90-102: The back-link/title/subtitle header block in the dev plan
page is still duplicated across the four conditional branches, so extract it
into a local DevPlanHeader component and reuse it everywhere. Move the repeated
Link + heading markup into that component and render it from each branch in
page.tsx, keeping the existing focus-visible styles intact.

In `@src/app/baseball/`(dashboard)/dashboard/program/page.tsx:
- Around line 214-217: The `ProgramPage` header shell is still duplicated across
the coach-access, no-org, and main branches instead of being extracted. Move the
shared `border-b ... px-6 pb-5 pt-6 ...` layout into a reusable
`ProgramPageHeader` component in `page.tsx` and replace each inline header block
with that component, passing the appropriate subtitle and action/content for
each branch so future style changes only need one edit.

In `@src/app/baseball/actions/calendar.ts`:
- Line 7: Add a regression test for createBaseballEvent to verify that when
either the baseball_event_attendance insert or the baseball_games insert fails,
the function still returns { success: true, warning: '...' } and preserves the
warning text used by EventsClient.tsx. Cover both single-failure and
dual-failure cases so the warning aggregation path (including warnings.join(';
')) and the logServerError calls remain protected from regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5acffa08-3bea-4b94-a9f3-671abc9a573b

📥 Commits

Reviewing files that changed from the base of the PR and between da3d420 and 1297b57.

📒 Files selected for processing (51)
  • docs/audits/PRODUCTION_READINESS_MISSION_2026-07-09.md
  • e2e/baseball-box-score.spec.ts
  • scripts/backfill-baseball-slash-lines.ts
  • scripts/e2e-supabase-admin.ts
  • src/app/baseball/(dashboard)/_components/breadcrumbs.ts
  • src/app/baseball/(dashboard)/dashboard/academics/AcademicsClient.tsx
  • src/app/baseball/(dashboard)/dashboard/calendar/page.tsx
  • src/app/baseball/(dashboard)/dashboard/dev-plan/DevPlanClient.tsx
  • src/app/baseball/(dashboard)/dashboard/dev-plans/[id]/page.tsx
  • src/app/baseball/(dashboard)/dashboard/documents/page.tsx
  • src/app/baseball/(dashboard)/dashboard/events/EventsClient.tsx
  • src/app/baseball/(dashboard)/dashboard/lift/[sessionId]/page.tsx
  • src/app/baseball/(dashboard)/dashboard/lift/_lift-athlete-context.ts
  • src/app/baseball/(dashboard)/dashboard/lift/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/builder/__tests__/page.test.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/groups/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/programs/[programId]/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx
  • src/app/baseball/(dashboard)/dashboard/players/[id]/page.tsx
  • src/app/baseball/(dashboard)/dashboard/program/page.tsx
  • src/app/baseball/(dashboard)/dashboard/readiness/__tests__/page.test.tsx
  • src/app/baseball/(dashboard)/dashboard/scouting/page.tsx
  • src/app/baseball/(dashboard)/dashboard/stats/games/page.tsx
  • src/app/baseball/actions/__tests__/player-today-lift-timezone.test.ts
  • src/app/baseball/actions/calendar.ts
  • src/app/baseball/actions/games.ts
  • src/app/baseball/actions/signals.ts
  • src/app/lifting/actions/__tests__/groups.test.ts
  • src/app/lifting/actions/__tests__/player-sessions.test.ts
  • src/app/lifting/actions/__tests__/sessions.test.ts
  • src/app/lifting/actions/groups.ts
  • src/app/lifting/actions/programs.ts
  • src/app/lifting/actions/sessions.ts
  • src/components/baseball/calendar/CalendarFairway.tsx
  • src/components/baseball/documents/DocumentsFairway.tsx
  • src/components/baseball/games/EditGameModal.tsx
  • src/components/baseball/games/GameDetailHeader.tsx
  • src/components/baseball/games/GamesList.tsx
  • src/components/baseball/player-profile/PlayerProfileClient.tsx
  • src/components/baseball/settings/ImportSourcesClient.tsx
  • src/components/baseball/settings/PhilosophySettingsClient.tsx
  • src/components/baseball/stats-center/StatsCenterClient.tsx
  • src/components/baseball/travel/TravelClient.tsx
  • src/components/lifting/programs/ProgramListClient.tsx
  • src/lib/baseball/__tests__/nav-manifest.test.ts
  • src/lib/baseball/__tests__/resolve-active-hub.test.ts
  • src/lib/types/index.ts
  • src/test/coachhelm/v3/chat-tools-visibility.test.ts
  • supabase/migrations/20260708141900_baseball_event_source_columns_converge.sql
💤 Files with no reviewable changes (1)
  • src/app/baseball/(dashboard)/dashboard/documents/page.tsx

Comment thread src/app/baseball/(dashboard)/_components/breadcrumbs.ts Outdated
Comment on lines +78 to +96
return (
<div className="mx-auto w-full max-w-5xl px-4 py-8 sm:px-6 lg:px-8">
<SectionMasthead eyebrow="THE WAR ROOM · SCOUTING" title="Scouting" ink="pursuit">
<p className="max-w-2xl font-annual text-body text-text-secondary">
The evaluation toolkit — shortlists, side-by-side comparisons, and exportable packets —
once a prospect is worth a closer look.
</p>
</SectionMasthead>
<PaperCard className="mt-8 p-6">
<Eyebrow ink="pursuit">Unable to load your team</Eyebrow>
<p className="mt-2 font-annual text-body text-text-secondary">
We couldn&apos;t resolve your active team just now — this isn&apos;t a permissions
issue. Refresh the page, or switch teams from the sidebar if you coach more than one
program.
</p>
</PaperCard>
</div>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dedupe the repeated masthead block.

Lines 80-85 (null-context branch) and 105-110 (main return) render an identical SectionMasthead/paragraph pair. Hoist it into a local const and reuse it in both branches so future copy edits to this masthead can't drift out of sync.

+  const masthead = (
+    <SectionMasthead eyebrow="THE WAR ROOM · SCOUTING" title="Scouting" ink="pursuit">
+      <p className="max-w-2xl font-annual text-body text-text-secondary">
+        The evaluation toolkit — shortlists, side-by-side comparisons, and exportable packets —
+        once a prospect is worth a closer look.
+      </p>
+    </SectionMasthead>
+  );
+
   if (!navContext) {
     return (
       <div className="mx-auto w-full max-w-5xl px-4 py-8 sm:px-6 lg:px-8">
-        <SectionMasthead eyebrow="THE WAR ROOM · SCOUTING" title="Scouting" ink="pursuit">
-          <p className="max-w-2xl font-annual text-body text-text-secondary">
-            The evaluation toolkit — shortlists, side-by-side comparisons, and exportable packets —
-            once a prospect is worth a closer look.
-          </p>
-        </SectionMasthead>
+        {masthead}
         <PaperCard className="mt-8 p-6">

Same substitution for the main return's masthead block at lines 105-110.

Also applies to: 103-110

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/scouting/page.tsx around lines 78 -
96, The `SectionMasthead` plus descriptive paragraph is duplicated in both the
null-context branch and the main return in `ScoutingPage`; hoist that shared JSX
into a local const and reuse it in both places so the copy stays in sync. Update
the `ScoutingPage` render path to reference the shared masthead content instead
of repeating the same block in each branch.

Comment thread src/app/baseball/(dashboard)/dashboard/stats/games/page.tsx
Comment thread src/app/baseball/(dashboard)/dashboard/stats/games/page.tsx
Comment thread src/components/baseball/settings/PhilosophySettingsClient.tsx Outdated
…ing, DB dedupe index)

- performance page: single team-local today (todayIsoInTz) reused for the
  7-day readiness lookback AND the client prop — no more UTC drift
- stats/games: season-record fetch failure surfaced distinctly in UI
- dev-plans/[id]: real error vs not-found distinguished, retryable error UI
- games.ts: createGame rolls back its own orphaned calendar event on failure;
  getPlayerSeasonStats returns honest errors (PGRST116 stays empty-state)
- signals.ts: DB-level dedupe for NULL-program lift sessions — new partial
  unique index migration 20260709000000 (APPLIED to prod check-first: zero
  existing dupes) + 23505 catch-and-requery
- lifting programs: revalidateProgramPaths on update + publish detail bust
- calendar action: zod input validation (v4 .issues); calendar page row cap
  1000; performance/programs paginate via fetchAllRowsResult
- performance/live + [programId]: loader errors fail honestly, logServerError
- breadcrumbs: no non-null assertion + new unit tests
- PlayerProfileClient: date-only strings parse as local midnight
- lift page (lifting shell): split current/overdue queries matching baseball
- PhilosophySettingsClient: settings route constant; lift-athlete-context doc
  matrix corrected; e2e dead stats/season spec removed

Skipped (W4 deferral): scouting masthead dedupe — replaced wholesale by the
Living Annual masthead migration.

Gates: tsc=0, eslint touched files=0, unit 4519 passed (14 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX
@njrini99-code

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Round 2 addressed in 6af6433: all 22 findings — 21 fixed (incl. the UTC readiness window, orphaned-event rollback, DB-level NULL-program session dedupe via a new partial unique index applied to prod check-first, honest error surfacing across performance/live/programs/dev-plans/stats), 1 skipped per the documented W4 masthead deferral.

@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git log --oneline -5
echo "---commit message 6af6433c---"
git show -s --format=%B 6af6433c
echo "---stat---"
git show --stat 6af6433c | head -150

Length of output: 5766


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== migration file ==="
fd "20260709000000" -t f
cat supabase/migrations/20260709000000_helm_lifting_sessions_null_program_dedupe.sql 2>/dev/null || fd "null_program_dedupe" -t f -x cat {}

echo "=== signals.ts dedupe/catch ==="
rg -n "23505|onConflict|helm_lifting_sessions" src/app/baseball/actions/signals.ts | head -40

echo "=== games.ts orphan rollback ==="
rg -n "createGame|calendar_event|orphan|rollback|PGRST116" src/app/baseball/actions/games.ts | head -60

echo "=== performance/page.tsx today reuse ==="
sed -n '1,80p' 'src/app/baseball/(dashboard)/dashboard/performance/page.tsx'

echo "=== breadcrumbs.ts non-null assertion ==="
sed -n '1,100p' 'src/app/baseball/(dashboard)/_components/breadcrumbs.ts'

echo "=== PlayerProfileClient date parsing ==="
rg -n "local midnight|parseLocal|new Date\(" src/components/baseball/player-profile/PlayerProfileClient.tsx | head -20

echo "=== calendar.ts zod ==="
rg -n "zod|\.issues|safeParse" src/app/baseball/actions/calendar.ts | head -30

echo "=== lift page split queries (lifting shell) ==="
sed -n '1,80p' 'src/app/lifting/(dashboard)/dashboard/lift/page.tsx' 2>/dev/null || fd -p "lifting/.*dashboard/lift/page.tsx"

Length of output: 16806


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== games.ts createGame full context ==="
sed -n '140,236p' src/app/baseball/actions/games.ts

echo "=== signals.ts dedupe context ==="
sed -n '400,470p' src/app/baseball/actions/signals.ts

echo "=== performance/page.tsx today usage further down ==="
sed -n '80,160p' 'src/app/baseball/(dashboard)/dashboard/performance/page.tsx'

echo "=== dev-plans/[id]/page.tsx error handling ==="
sed -n '1,120p' 'src/app/baseball/(dashboard)/dashboard/dev-plans/[id]/page.tsx'

Length of output: 16405


@njrini99-code Verified. Spot-checked the highest-risk items in 6af6433c:

  • Orphaned calendar event rollbacksrc/app/baseball/actions/games.ts:222-227: the delete is correctly scoped to input.create_calendar_event && !input.event_id && eventId, so it only ever deletes the event this call created, never a caller-supplied event_id. Correct.
  • NULL-program session dedupe — this is a proper two-layer fix, not just an app-level patch:
    • App layer: src/app/baseball/actions/signals.ts:413-420 check-then-insert on (athlete_id, scheduled_date, title) scoped to program_assignment_id IS NULL.
    • DB layer: supabase/migrations/20260709000000_helm_lifting_sessions_null_program_dedupe.sql — partial unique index closing the race window Postgres' NULL != NULL semantics leaves open for the existing uq_helm_lifting_session constraint.
    • Race handling: signals.ts:439-455 catches sessionErr?.code === '23505' and re-queries rather than surfacing a spurious error to the caller. This is correct Postgres unique-violation handling.
  • UTC readiness driftsrc/app/baseball/(dashboard)/dashboard/performance/page.tsx:93: today is computed once via todayIsoInTz(resolveTeamTimezone(...)) and reused for both the 7-day lookback and the client prop, eliminating the split-source drift.
  • dev-plans/[id] honest errorssrc/app/baseball/(dashboard)/dashboard/dev-plans/[id]/page.tsx:44-58: isGenuineNotFound is scoped to the permission-denied message and PGRST116/no-rows patterns only; everything else now sets fetchError instead of silently rendering "not found."
  • Breadcrumbs non-null assertion removedsrc/app/baseball/(dashboard)/_components/breadcrumbs.ts:71 now uses best?.label ?? fallback, no !.
  • Date-only local-midnight parsingsrc/components/baseball/player-profile/PlayerProfileClient.tsx:203 appends T00:00:00 for bare YYYY-MM-DD strings before constructing Date, avoiding the UTC-midnight-renders-as-prior-day bug.

No blocking findings (security/RLS/type-safety/a11y) in this batch. The one deferred item (scouting masthead dedupe → W4 Living Annual migration) is a reasonable scope call since it's superseded scope, not skipped work.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot removed the security Auth, secrets, RLS, PII, webhooks label Jul 9, 2026
@njrini99-code

Copy link
Copy Markdown
Owner Author

🤖 Mission Control — PR summary

What this changes
Baseball statsync verification + coherence remediation. ~189 files, +8.7k / −11.3k (large net deletion = dead-route / manifest / phantom-column cleanup). Main surfaces:

  • Box score ↔ profile sync: save_baseball_full_box_score RPC rewritten from the banned DELETE-then-INSERT to per-row UPSERT (ON CONFLICT game_id,player_id) with an isolated recalc loop so one player's failure can't roll back the game; migration 20260708150000 (already applied to prod). Shared format-stat helper gives box score / game detail / player profile stat parity.
  • Migrations: converge 20260708141900 (source_trust_level / source_visibility, no-op vs prod) and a lifting NULL-program dedupe partial unique index 20260709000000 (+ 23505 catch-and-requery), both applied. ⚠️ 20260708141000 is committed file-only and HELD (not applied) — flagged golf-function + invite-redemption gating risk.
  • Correctness fixes: team-local "today" / week bounds (was server-UTC), cross-New-Year season_year parse, PostgREST 1000-row pagination in the slash-line backfill, honest error-vs-empty-state surfacing across stats / games / performance / dev-plans, EditGameModal clearable fields, off-palette color tokens → canonical.
  • Shell / nav: single BaseballFairwayShell + nav-registry consistency, dead-route / stat-layer-manifest cleanup, exit-velocity phantom columns dropped; lifting-program revalidation across baseball routes.

Risk / areas to watch

  • DB migrations on a Golf-shared production DB — verify the box-score UPSERT path is idempotent and that branch code does not assume the HELD 20260708141000 is applied.
  • Very large deletion set — confirm no still-referenced code / routes were removed.
  • Box-score → player-profile stat parity; lifting-session dedupe (23505 requery path).

CI (as of this comment)

  • 🔴 check (advisory — routes + owner issues)failing, but self-labeled advisory (BaseballHelm Readiness Matrix; non-gating).
  • 🟡 Required checks still running: Next build, Smoke checks (Playwright E2E); CodeRabbit review in progress.
  • 🟢 Green: TypeScript, ESLint, Unit tests, Business contracts, Supabase lint + RLS, Schema / Route / Feature-knowledge gates, Review Gate all, CodeQL; Vercel build skipped by the cost-control ignore-step (expected).
  • Merge state BLOCKED — review decision CHANGES_REQUESTED (CodeRabbit re-review requested ~06:00 UTC) plus the pending required checks above.

Posted by the Helm Mission Control sweep — read-only automation. No merge, deploy, or rerun action was taken.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/app/baseball/(dashboard)/dashboard/performance/programs/[programId]/page.tsx (1)

61-64: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

getAssignContext swallows query errors while getProgramTree on the same page surfaces them.

Lines 234-241 log the failure but still return { athletes: [], groups: [] } at line 243, identical to the legitimate "nothing configured" case. AssignContext has no error field (61-64) for the page to react to, unlike ProgramTreeResult (66-69). A transient RLS/DB failure here renders as "no athletes/groups exist" with zero UI signal, while the same failure class in getProgramTree triggers ReadModelStateNotice.

🐛 Proposed fix: propagate the error like `ProgramTreeResult` does
 interface AssignContext {
   athletes: Array<Pick<HelmLiftingAthleteRow, 'id' | 'first_name' | 'last_name' | 'position' | 'sport'>>;
   groups: Array<Pick<HelmLiftingGroupRow, 'id' | 'name' | 'group_type'>>;
+  error?: boolean;
 }
-  return { athletes: athletes ?? [], groups: groups ?? [] };
+  return { athletes: athletes ?? [], groups: groups ?? [], error: Boolean(athletesError || groupsError) };

Then in the page, either render the same ReadModelStateNotice when assign.error is true, or pass the flag into ProgramEditorClient so the assign panel can show an inline warning instead of a blank roster.

Also applies to: 210-244, 264-289

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/baseball/`(dashboard)/dashboard/performance/programs/[programId]/page.tsx
around lines 61 - 64, `getAssignContext` currently hides query failures by
logging and returning empty athletes/groups, which makes real DB/RLS errors
indistinguishable from valid empty state. Update `AssignContext` and
`getAssignContext` to propagate an error flag/status like `ProgramTreeResult`
does, using the existing `getProgramTree`/`ReadModelStateNotice` pattern as a
reference. Then update the page and `ProgramEditorClient` call site to react to
that flag by surfacing the same warning or an inline assign-panel alert instead
of rendering a blank roster.
src/components/baseball/player-profile/PlayerProfileClient.tsx (1)

184-184: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Rename the stats filter value from practice to scrimmage.

src/components/baseball/player-profile/PlayerProfileClient.tsx:184 still defines StatFilter as 'practice', but the changed profile stats are sourced from baseball_games.game_type, which uses scrimmage. The Scrimmages filter will not match those rows.

🐛 Proposed fix
-type StatFilter = 'all' | 'game' | 'practice';
+type StatFilter = 'all' | 'game' | 'scrimmage';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/baseball/player-profile/PlayerProfileClient.tsx` at line 184,
The StatFilter union in PlayerProfileClient should use scrimmage instead of
practice so the filter matches baseball_games.game_type values. Update the
StatFilter type and any related filter option labels/logic in
PlayerProfileClient to reference scrimmage consistently, ensuring the Scrimmages
view selects the correct rows.
src/app/baseball/(dashboard)/dashboard/calendar/page.tsx (1)

85-121: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not collapse calendar query failures into an empty calendar.

src/app/baseball/(dashboard)/dashboard/calendar/page.tsx:85-121 never checks eventsResult.error. A PostgREST/RLS/schema failure still becomes events = [], which is indistinguishable from a real empty calendar.

🐛 Proposed fix
     ]);
+
+    if (eventsResult.error) {
+      throw new Error('Could not load calendar events.');
+    }
 
     // Map baseball_events → CalendarEvent. Row is annotated because the query
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/calendar/page.tsx around lines 85 -
121, The calendar data fetch in the `Promise.all` block is swallowing
`eventsResult.error`, so query failures look like a valid empty state. Update
the `page.tsx` calendar loader to check the result from `fromUntyped(supabase,
'baseball_events')` before mapping `events`, and if `eventsResult.error` is
present, surface that failure instead of assigning `events = []`. Keep the
existing `eventsResult`, `membersResult`, and `teamOrgResult` flow, but add
explicit error handling around the `eventsResult` path so PostgREST/RLS/schema
issues are distinguishable from a truly empty calendar.
src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx (2)

248-252: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pick the assigned exercise before completed work.

src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx:251 returns the first assigned or completed row in order, so after exercise 1 is completed and exercise 2 is assigned, the live row can keep showing exercise 1 as current.

🐛 Proposed fix
-    const currentSe = seList.find((e) => e.status === 'assigned' || e.status === 'completed');
+    const currentSe =
+      seList.find((e) => e.status === 'assigned') ??
+      [...seList].reverse().find((e) => e.status === 'completed');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/performance/live/page.tsx around
lines 248 - 252, The current live-session selection in liveAthletes uses the
first exercise with either assigned or completed status, which can leave an
older completed exercise displayed instead of the newly assigned one. Update the
selection logic in page.tsx around liveAthletes/currentSe so it prefers an
assigned exercise over any completed one, using the existing exercisesBySession
and latestSetBySeId flow without changing the surrounding mapping structure.

298-316: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not render the writable live room with an empty org id.

src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx:298-316 converts a missing liftCtx into { ok: true }, then renders LiveWeightRoomClient with orgId="" and canEdit=true. That exposes a broken write surface instead of the same explicit setup failure used by the programs page.

🐛 Proposed fix
   const liftCtx = await resolveBaseballLiftingOrg(teamId);
-  const liveRoomResult: LiveRoomResult = liftCtx
-    ? await buildLiveRoomData(liftCtx.organizationId, teamId, canViewReadiness)
-    : { ok: true, athletes: [], exerciseLibrary: [] };
+  if (!liftCtx) {
+    return (
+      <div className="mx-auto max-w-7xl px-4 py-6">
+        <ReadModelStateNotice state="empty" title="Lift Lab access not set up" />
+      </div>
+    );
+  }
+
+  const liveRoomResult: LiveRoomResult = await buildLiveRoomData(liftCtx.organizationId, teamId, canViewReadiness);
@@
-      orgId={liftCtx?.organizationId ?? ''}
+      orgId={liftCtx.organizationId}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/performance/live/page.tsx around
lines 298 - 316, The live performance page is treating a missing lifting context
as a successful empty result, which then renders LiveWeightRoomClient with an
empty orgId and writable access. Update the logic around
resolveBaseballLiftingOrg/buildLiveRoomData to fail closed when liftCtx is
missing, matching the explicit setup/error handling used elsewhere instead of
returning { ok: true } with empty data. Ensure the render path only reaches
LiveWeightRoomClient when a valid organizationId is present, and otherwise
return the same error notice/state used for the failed live room load.
src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx (1)

52-92: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Propagate program/count query errors instead of rendering partial counts.

src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx:52-92 ignores errors from the programs query and both fetchAllRowsResult calls. Because fetchAllRowsResult can return partial data with error, week/day counts can silently undercount again.

🐛 Proposed fix
-  const { data: programs } = (await fromUntyped(supabase, 'helm_lifting_programs')
+  const { data: programs, error: programsError } = (await fromUntyped(supabase, 'helm_lifting_programs')
@@
-    .limit(200)) as { data: HelmLiftingProgramRow[] | null };
+    .limit(200)) as { data: HelmLiftingProgramRow[] | null; error: { message: string } | null };
+
+  if (programsError) throw new Error('Could not load lifting programs.');
@@
-  const { data: weeksData } = await fetchAllRowsResult<{ id: string; program_id: string }>(
+  const { data: weeksData, error: weeksError } = await fetchAllRowsResult<{ id: string; program_id: string }>(
@@
   );
+  if (weeksError) throw new Error('Could not load lifting program weeks.');
@@
-    const { data: daysData } = await fetchAllRowsResult<{ week_id: string }>((from, to) =>
+    const { data: daysData, error: daysError } = await fetchAllRowsResult<{ week_id: string }>((from, to) =>
@@
     );
+    if (daysError) throw new Error('Could not load lifting program days.');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/`(dashboard)/dashboard/performance/programs/page.tsx around
lines 52 - 92, The programs/count aggregation in the dashboard page is dropping
query failures and can render partial counts. Update the programs query and both
fetchAllRowsResult calls in the page component to check for returned error
values and fail fast instead of continuing with whatever data was fetched.
Propagate or throw the error from the programs, weeks, and days queries so the
page does not silently undercount. Use the existing aggregation flow around
programs, weeksData, and daysData to locate the fix.
src/app/baseball/actions/games.ts (1)

1409-1416: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle season lookup errors before returning an empty log. src/app/baseball/actions/games.ts:1409-1416 drops the baseball_games query error, so a DB/RLS failure can collapse into seasonGameIds = [] and hit the no-games path. Return sanitizeDbError(...) before mapping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/actions/games.ts` around lines 1409 - 1416, The season
lookup in the baseball games flow is swallowing database errors, causing query
failures to look like an empty result. In the `seasonGames` fetch inside
`games.ts`, check the `db.from('baseball_games')` response for an error before
building `seasonGameIds`, and return `sanitizeDbError(...)` immediately when the
query fails. Keep the existing mapping logic only for successful responses so
the no-games path is reserved for true empty results.
src/app/baseball/actions/signals.ts (1)

487-499: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Handle the seed query errors src/app/baseball/actions/signals.ts:487-499
fromUntyped() returns a result object (src/lib/supabase/untyped.ts:69-71), so try/catch will not catch a failed select() or insert(). Check both error values and log/abort the seed; otherwise a converted lift can persist without its matching helm_lifting_session_exercises row.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/baseball/actions/signals.ts` around lines 487 - 499, The seed logic
in the lifting session exercise creation block is ignoring Supabase result
errors because fromUntyped() returns a result object rather than throwing. In
the flow that checks existingSe and inserts the HelmLiftingSessionExerciseInsert
payload, inspect and handle the error field for both the select and insert
operations, and abort/log the seed if either fails so a converted lift cannot
continue without its matching helm_lifting_session_exercises row.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/baseball/`(dashboard)/dashboard/dev-plans/[id]/page.tsx:
- Around line 157-189: The error state in the dev plan detail page is using raw
red utility classes instead of the design-system destructive tokens. Update the
`fetchError` return block in `page.tsx` to replace the `border-red-*`,
`bg-red-*`, and `text-red-*` classes with the canonical `destructive` color
family while keeping the same structure and messaging. Use the existing error
card and the `Button` retry action as the place to apply the tokenized styles.

In `@src/app/lifting/`(dashboard)/dashboard/lift/page.tsx:
- Around line 124-129: The upcoming sessions query in the lift dashboard is
currently including all statuses for current and future dates, so non-open
sessions can leak into the upcoming list. Update the query in the page component
around the fromUntyped(... 'helm_lifting_sessions') call to apply the same
OPEN_STATUSES filter used by the overdue query, keeping only open sessions for
scheduled_date >= today. Make the change in the lift page logic that builds the
upcoming sessions data so the dashboard only renders actionable sessions.

In `@src/components/baseball/games/GamesList.tsx`:
- Around line 166-170: The error state text in GamesList uses a raw red utility
class instead of the design-system destructive token. Update the conditional
season-record message in GamesList to use the canonical destructive color family
for the paragraph styling, keeping the existing layout logic tied to
recordError, seasonRecord, and title unchanged.

---

Outside diff comments:
In `@src/app/baseball/`(dashboard)/dashboard/calendar/page.tsx:
- Around line 85-121: The calendar data fetch in the `Promise.all` block is
swallowing `eventsResult.error`, so query failures look like a valid empty
state. Update the `page.tsx` calendar loader to check the result from
`fromUntyped(supabase, 'baseball_events')` before mapping `events`, and if
`eventsResult.error` is present, surface that failure instead of assigning
`events = []`. Keep the existing `eventsResult`, `membersResult`, and
`teamOrgResult` flow, but add explicit error handling around the `eventsResult`
path so PostgREST/RLS/schema issues are distinguishable from a truly empty
calendar.

In `@src/app/baseball/`(dashboard)/dashboard/performance/live/page.tsx:
- Around line 248-252: The current live-session selection in liveAthletes uses
the first exercise with either assigned or completed status, which can leave an
older completed exercise displayed instead of the newly assigned one. Update the
selection logic in page.tsx around liveAthletes/currentSe so it prefers an
assigned exercise over any completed one, using the existing exercisesBySession
and latestSetBySeId flow without changing the surrounding mapping structure.
- Around line 298-316: The live performance page is treating a missing lifting
context as a successful empty result, which then renders LiveWeightRoomClient
with an empty orgId and writable access. Update the logic around
resolveBaseballLiftingOrg/buildLiveRoomData to fail closed when liftCtx is
missing, matching the explicit setup/error handling used elsewhere instead of
returning { ok: true } with empty data. Ensure the render path only reaches
LiveWeightRoomClient when a valid organizationId is present, and otherwise
return the same error notice/state used for the failed live room load.

In
`@src/app/baseball/`(dashboard)/dashboard/performance/programs/[programId]/page.tsx:
- Around line 61-64: `getAssignContext` currently hides query failures by
logging and returning empty athletes/groups, which makes real DB/RLS errors
indistinguishable from valid empty state. Update `AssignContext` and
`getAssignContext` to propagate an error flag/status like `ProgramTreeResult`
does, using the existing `getProgramTree`/`ReadModelStateNotice` pattern as a
reference. Then update the page and `ProgramEditorClient` call site to react to
that flag by surfacing the same warning or an inline assign-panel alert instead
of rendering a blank roster.

In `@src/app/baseball/`(dashboard)/dashboard/performance/programs/page.tsx:
- Around line 52-92: The programs/count aggregation in the dashboard page is
dropping query failures and can render partial counts. Update the programs query
and both fetchAllRowsResult calls in the page component to check for returned
error values and fail fast instead of continuing with whatever data was fetched.
Propagate or throw the error from the programs, weeks, and days queries so the
page does not silently undercount. Use the existing aggregation flow around
programs, weeksData, and daysData to locate the fix.

In `@src/app/baseball/actions/games.ts`:
- Around line 1409-1416: The season lookup in the baseball games flow is
swallowing database errors, causing query failures to look like an empty result.
In the `seasonGames` fetch inside `games.ts`, check the
`db.from('baseball_games')` response for an error before building
`seasonGameIds`, and return `sanitizeDbError(...)` immediately when the query
fails. Keep the existing mapping logic only for successful responses so the
no-games path is reserved for true empty results.

In `@src/app/baseball/actions/signals.ts`:
- Around line 487-499: The seed logic in the lifting session exercise creation
block is ignoring Supabase result errors because fromUntyped() returns a result
object rather than throwing. In the flow that checks existingSe and inserts the
HelmLiftingSessionExerciseInsert payload, inspect and handle the error field for
both the select and insert operations, and abort/log the seed if either fails so
a converted lift cannot continue without its matching
helm_lifting_session_exercises row.

In `@src/components/baseball/player-profile/PlayerProfileClient.tsx`:
- Line 184: The StatFilter union in PlayerProfileClient should use scrimmage
instead of practice so the filter matches baseball_games.game_type values.
Update the StatFilter type and any related filter option labels/logic in
PlayerProfileClient to reference scrimmage consistently, ensuring the Scrimmages
view selects the correct rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 08992724-9b79-4877-9eb6-3f5152c1c572

📥 Commits

Reviewing files that changed from the base of the PR and between 1297b57 and 6af6433.

📒 Files selected for processing (20)
  • e2e/baseball-box-score.spec.ts
  • src/app/baseball/(dashboard)/_components/breadcrumbs.test.ts
  • src/app/baseball/(dashboard)/_components/breadcrumbs.ts
  • src/app/baseball/(dashboard)/dashboard/calendar/page.tsx
  • src/app/baseball/(dashboard)/dashboard/dev-plans/[id]/page.tsx
  • src/app/baseball/(dashboard)/dashboard/lift/_lift-athlete-context.ts
  • src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/programs/[programId]/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx
  • src/app/baseball/(dashboard)/dashboard/stats/games/page.tsx
  • src/app/baseball/actions/calendar.ts
  • src/app/baseball/actions/games.ts
  • src/app/baseball/actions/signals.ts
  • src/app/lifting/(dashboard)/dashboard/lift/page.tsx
  • src/app/lifting/actions/programs.ts
  • src/components/baseball/games/GamesList.tsx
  • src/components/baseball/player-profile/PlayerProfileClient.tsx
  • src/components/baseball/settings/PhilosophySettingsClient.tsx
  • supabase/migrations/20260709000000_helm_lifting_sessions_null_program_dedupe.sql

Comment thread src/app/baseball/(dashboard)/dashboard/dev-plans/[id]/page.tsx
Comment thread src/app/lifting/(dashboard)/dashboard/lift/page.tsx
Comment thread src/components/baseball/games/GamesList.tsx
…s filter, destructive tokens)

- calendar/page.tsx + performance/programs pages + games.ts season lookup +
  performance/live loaders: query failures now surface (throw to boundary or
  sanitizeDbError) instead of collapsing into empty states
- performance/live: prefer newly-assigned exercise over last completed; fail
  closed on missing lifting org (no writable room with empty org id)
- performance/programs/[programId]: assign-context errors surfaced inline
- lifting lift page: current/future query filters to open statuses
- signals.ts: seed select/insert errors actually checked + logServerError
  (the try/catch never fired — supabase resolves errors, doesn't throw)
- PlayerProfileClient: StatFilter union renamed practice→scrimmage, dead
  translation layer removed
- dev-plans/[id] + GamesList: raw red classes → destructive tokens

Gates: tsc=0, lint=0, unit 4519 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX
@njrini99-code

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

Round 3 addressed in 761cacb — all 11 findings fixed (error-honesty across calendar/performance/games/signals loaders, open-status filter, assigned-exercise preference, fail-closed live room, destructive tokens, StatFilter cleanup). Requesting a full from-scratch review per docs/operations/coderabbit-review-workflow.md so the decision reflects the whole current diff rather than incremental deltas. The only intentionally-open items remain the W4/W7 deferrals documented in docs/audits/PRODUCTION_READINESS_MISSION_2026-07-09.md (Living Annual masthead migration replaces the duplicated headers; the readiness matrix gets a full truthful refresh).

@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git log --oneline -8
echo "---commit message 761cacb3---"
git show -s --format=%B 761cacb3
echo "---stat---"
git show --stat 761cacb3 | head -150

Length of output: 4302


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== calendar/page.tsx diff ==="
git show 761cacb3 -- 'src/app/baseball/(dashboard)/dashboard/calendar/page.tsx'

echo "=== dev-plans/[id]/page.tsx diff ==="
git show 761cacb3 -- 'src/app/baseball/(dashboard)/dashboard/dev-plans/[id]/page.tsx'

echo "=== performance/live/page.tsx diff ==="
git show 761cacb3 -- 'src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx'

echo "=== performance/programs/[programId]/page.tsx diff ==="
git show 761cacb3 -- 'src/app/baseball/(dashboard)/dashboard/performance/programs/[programId]/page.tsx'

echo "=== performance/programs/page.tsx diff ==="
git show 761cacb3 -- 'src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx'

echo "=== games.ts diff ==="
git show 761cacb3 -- 'src/app/baseball/actions/games.ts'

echo "=== signals.ts diff ==="
git show 761cacb3 -- 'src/app/baseball/actions/signals.ts'

echo "=== lifting lift page diff ==="
git show 761cacb3 -- 'src/app/lifting/(dashboard)/dashboard/lift/page.tsx'

echo "=== GamesList.tsx diff ==="
git show 761cacb3 -- 'src/components/baseball/games/GamesList.tsx'

echo "=== PlayerProfileClient.tsx diff ==="
git show 761cacb3 -- 'src/components/baseball/player-profile/PlayerProfileClient.tsx'

Length of output: 32023


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== OPEN_STATUSES definition ==="
rg -n "OPEN_STATUSES" 'src/app/lifting/(dashboard)/dashboard/lift/page.tsx'

echo "=== sanitizeDbError import in games.ts ==="
rg -n "sanitizeDbError" src/app/baseball/actions/games.ts | head -10

echo "=== getPlayerSeasonStatsImpl full context ==="
sed -n '1380,1460p' src/app/baseball/actions/games.ts

echo "=== calendar/page.tsx error.tsx presence ==="
fd -p 'error.tsx' 'src/app/baseball/(dashboard)'

echo "=== check dashboard error boundary exists for calendar route group ==="
fd -p 'error.tsx' src/app/baseball

echo "=== signals.ts surrounding try/catch full block ==="
sed -n '460,545p' src/app/baseball/actions/signals.ts

Length of output: 19991


@njrini99-code Verified 761cacb3 against all 11 claimed fixes. File:line citations below; no blocking findings.

Confirmed correct:

  • src/app/baseball/(dashboard)/dashboard/calendar/page.tsx:117-120eventsResult.error now throws instead of silently rendering events=[]. Error boundary exists at src/app/baseball/(dashboard)/dashboard/calendar/error.tsx, so this fails loud, not blank.
  • src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx:251-256currentSe selection bug fixed: seList.find(e => e.status === 'assigned') ?? [...seList].reverse().find(e => e.status === 'completed') correctly prefers the newly-assigned exercise over a stale completed one.
  • Same file, :305-320 — fail-closed branch added when liftCtx is null, before ever calling buildLiveRoomData. This also removes the orgId={liftCtx?.organizationId ?? ''} type-unsafe fallback at the old call site — good, that empty-string org id was a real footgun for a writable surface.
  • src/app/baseball/(dashboard)/dashboard/performance/programs/[programId]/page.tsx:63-66,247,285-291AssignContext.error is now distinguishable from "no athletes/groups yet" and surfaced inline. Correct.
  • src/app/baseball/(dashboard)/dashboard/performance/programs/page.tsx:52-92programsError/weeksError/daysError are now checked and thrown; previously undercounted week/day totals on silent failure.
  • src/app/baseball/actions/games.ts:1406-1416 (getPlayerSeasonStatsImpl) — seasonGamesError now returns sanitizeDbError(...) instead of proceeding with an empty game-id list on a real DB failure. Confirmed sanitizeDbError was already imported (line 6), no new import risk.
  • src/app/baseball/actions/signals.ts:487-517 — this is the most substantive fix in the batch: existingSeError and insertSeError are now checked explicitly. The comment nails the actual root cause — fromUntyped() resolves { data, error }, it doesn't throw, so the surrounding try/catch was structurally dead code for real query failures. Good catch on your part.
  • src/app/lifting/(dashboard)/dashboard/lift/page.tsx:127.in('status', OPEN_STATUSES) added to the today/future query; OPEN_STATUSES = ['assigned','started','modified'] (line 100) is applied consistently to both the today/future and overdue branches (confirmed both call sites at lines 127 and 134).
  • src/components/baseball/player-profile/PlayerProfileClient.tsx:184,416,454,1037StatFilter union now uses 'scrimmage' directly, dead wantType translation removed from both filteredStats and statSummary, and the filter-button array updated to match. No remaining 'practice' references in this filter path.
  • Destructive-token swaps (dev-plans/[id]/page.tsx:174-176, GamesList.tsx:167) — cosmetic only, red-*destructive/*, correct and consistent with the design-token migration elsewhere in the PR.

No regressions or new blocking issues (security/RLS/type-safety/a11y) found in this round. The deferred W4/W7 items remain a reasonable scope call as previously assessed — they're superseded by planned follow-on waves, not silently dropped.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added the security Auth, secrets, RLS, PII, webhooks label Jul 9, 2026
18 Owner Issue cells updated: closed issues (368-416 range, all verified
CLOSED via gh) removed; only open issues 377/379/394 remain linked. Cells
left with no open issue now say so explicitly and point at the W7 full
matrix refresh for the row re-grade (the matrix's row content is known
stale — several rows describe already-fixed defects, e.g. the box-score
delete-then-insert replaced by the upsert RPC applied 2026-07-09).

check:readiness-matrix passes with GITHUB_TOKEN (routes + issue states).
Clears the advisory 'check (routes + owner issues)' CI failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@njrini99-code
njrini99-code merged commit a394633 into main Jul 9, 2026
42 checks passed
@njrini99-code
njrini99-code deleted the integration/final-verification-2026-07-08 branch July 9, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

database Schema, migrations, indexes, SQL security Auth, secrets, RLS, PII, webhooks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant