Skip to content

feat(stats): premium glass driving spray chart in the Driving tab - #303

Closed
njrini99-code wants to merge 1 commit into
mainfrom
feat/driving-spray-chart
Closed

feat(stats): premium glass driving spray chart in the Driving tab#303
njrini99-code wants to merge 1 commit into
mainfrom
feat/driving-spray-chart

Conversation

@njrini99-code

@njrini99-code njrini99-code commented Jun 15, 2026

Copy link
Copy Markdown
Owner

What

"Where's the spray chart for driving too? Create a premium glass spray chart showing driving directions — it should sit in the driving tab on stats. The one in there before was trashy."

Adds FairwayDrivingSpray — a premium SVG "landing view" of off-the-tee directions — to the Driving tab on the stats cockpit, and removes the old flat canvas driving scatter.

The chart

  • Frosted glass plot panel (gradient + lit top edge), a dashed green intended-target line, and an honest 1σ dispersion ellipse (anisotropic-safe projected path) showing the spread shape.
  • Shot landings colored by outcome — fairway (green) · trouble (amber) · penalty (red) — plus the player's mean-bias marker (ringed).
  • A left · center · right distribution bar + chips (dominant miss accented), an outcome legend, a plain-English tendency takeaway ("Misses lean right"), and avg carry.
  • Honest by construction: plots only real SprayChartShotGroup.points (no fabricated coords), the ellipse needs ≥3 shots, and there's a calm empty state when there's no tee-shot spray yet. role="img" carries a full text summary for screen readers.

Wiring

  • Driving tab (FairwayStatsCockpit) now renders <FairwayDrivingSpray group={sprayData?.driving} /> beneath DrivingSection.
  • Removed the old driving ShotDispersion from the Analysis "Shot patterns" board (kept the approach miss map + putting heatmap) — no trashy duplicate.
  • Deep-imports Surface/InsufficientData (not the giant @/components/fairway barrel) to avoid a circular-init in the cockpit chunk.

Preview

Rendered in an isolated harness with real-shaped mock data (24 right-biased tee shots):

aria: "Driving spray: 24 tee shots plotted. 42% center, 13% left, 46% right. Average carry 271 yards. Misses lean right."

(Screenshot attached in the chat that opened this PR.)

Verification

  • tsc --noEmit clean · npm run lint 0 errors · 2510 unit tests pass.
  • ✅ Component verified rendering live in an isolated route (points, ellipse, mean marker, direction split, legend, empty state).
  • ⚠️ A live cockpit screenshot was blocked by an unrelated Turbopack dev server-action "module factory" wedge that breaks all dashboard routes in dev (independent of this change — reproduced with the change stashed). It's dev-only; the route builds fine in prod, so this will render in the Driving tab on the Vercel preview / prod.

Reviewer note

Independent of #299/#300/#302. Touches FairwayStatsCockpit in different regions than #302 (lie filter), so they merge cleanly. Not merged.

🤖 Generated with Claude Code

Greptile Summary

This PR replaces the old flat-canvas driving ShotDispersion with FairwayDrivingSpray, a new premium SVG spray chart rendered in the Driving tab of FairwayStatsCockpit. The old driving chart is simultaneously removed from the Analysis "Shot patterns" board, leaving only the approach miss map and putting heatmap there.

  • FairwayDrivingSpray (421 lines, new): pure SVG frosted-glass plot with a 1σ covariance ellipse, shot landings coloured by outcome, a mean-bias marker, a left/center/right distribution bar, and an outcome legend — all backed only by real SprayChartShotGroup.points with a calm empty state when data is absent.
  • FairwayStatsCockpit: Driving tab now wraps DrivingSection + FairwayDrivingSpray in a flex flex-col gap-6; ShotPatterns strips the driving ShotDispersion and its associated computed variables, and the hasAnyShotData guard now only checks approach/putting data.

Confidence Score: 4/5

Safe to merge — the new component is a self-contained leaf with a calm empty state and no data fabrication; the cockpit wiring is minimal and touches only the Driving tab and the Analysis shot-patterns board in the expected regions.

The math (covariance ellipse, projection, scale clamping) is correct and well-guarded. The main open question is whether the legend's server-supplied outcome counts (playableCount / troubleCount / penaltyCount) are scoped to plotted shots or to all tracked shots — if they include unplotted shots the legend will show numbers larger than the visible dot count. The independent-rounding issue in the distribution bar is cosmetic. Neither finding blocks shipping but both are worth a quick check before the feature is promoted.

src/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx — specifically the legend section and the pct() rounding in the distribution bar.

Important Files Changed

Filename Overview
src/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx New 421-line premium SVG spray chart component; well-structured with honest empty-state, proper aria labelling, and covariance-ellipse math. Two minor concerns: legend outcome counts use server-supplied aggregate fields that may not align with the plotted-point count, and independent percentage rounding can produce bar widths that don't sum to 100%.
src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx Driving tab updated to render FairwayDrivingSpray; ShotPatterns board cleanly stripped of the old driving ShotDispersion. hasAnyShotData guard updated to only check approach/putting. Changes are minimal and isolated to the expected regions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[FairwayStatsCockpit] -->|Driving tab| B[DrivingSection]
    A -->|Driving tab| C[FairwayDrivingSpray\ngroup=sprayData?.driving ?? null]
    A -->|Analysis tab| D[ShotPatterns]

    C --> E{group & points.length > 0?}
    E -- No --> F[InsufficientData empty state]
    E -- Yes --> G[Compute model\nlatMax, distMax, stats]
    G --> H[SVG glass panel\n+ target line]
    G --> I{n >= 3?}
    I -- Yes --> J[1σ covariance ellipse\nellipsePath]
    I -- No --> K[skip ellipse]
    H --> L[Shot landing dots\ncoloured by outcomeBucket]
    H --> M[Mean-bias marker]
    H --> N[Axis hints LEFT / TARGET / RIGHT]
    G --> O[Left·Center·Right\ndistribution bar]
    G --> P[Outcome legend\nplayable / trouble / penalty]

    D --> Q[ShotDispersion\nApproach only]
    D --> R[PuttingHeatmap]
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(stats): premium glass driving spray..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Adds FairwayDrivingSpray — a premium SVG "landing view" of off-the-tee
directions — to the Driving tab on the stats cockpit, replacing the flat canvas
ShotDispersion that previously only lived (for driving) in the Analysis tab.

The chart:
- Frosted glass plot panel (gradient + lit top edge), a dashed green intended-
  target line, and an honest 1σ covariance ellipse (anisotropic-safe projected
  path) showing the dispersion shape.
- Shot landings colored by outcome (fairway / trouble / penalty) + the player's
  mean-bias marker.
- A left·center·right distribution bar + chips (dominant miss accented) and an
  outcome legend, with a plain-English tendency takeaway ("Misses lean right")
  and avg carry.
- Honest: plots ONLY real SprayChartShotGroup.points (no fabricated coords),
  ellipse needs ≥3 shots, and an empty state when there's no tee-shot spray yet.
  role="img" with a full text summary for screen readers.

Wiring:
- FairwayStatsCockpit Driving tab now renders <FairwayDrivingSpray
  group={sprayData?.driving} /> beneath DrivingSection.
- Removed the old driving ShotDispersion from the Analysis "Shot patterns" board
  (kept the approach miss map + putting heatmap) so there's no trashy duplicate.
- Deep-imports Surface/InsufficientData (not the big @/components/fairway barrel)
  to avoid a circular-init in the cockpit chunk.

Verified: rendered in an isolated harness (real-shaped mock data) — 24 points +
mean marker, accurate aria summary, correct empty state. typecheck clean · lint
0 errors · 2510 unit tests pass. (Live cockpit screenshot was blocked by an
unrelated Turbopack dev server-action "module factory" wedge affecting all
dashboard routes — dev-only; the route builds fine in prod.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
helmv3 Ready Ready Preview, Comment Jun 15, 2026 2:37pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features
    • Added a new driving spray chart visualization to the Driving section, displaying shot distribution with color-coded outcomes and bias indicators.
    • Updated Shot patterns view to focus on approach shot dispersion and putting performance metrics.

Walkthrough

Adds a new FairwayDrivingSpray React component that renders a frosted-glass SVG driving spray chart with covariance ellipse, shot outcome circles, directional split bar, and legend. Integrates it into the Driving tab of FairwayStatsCockpit and removes the driving dispersion logic from the ShotPatterns component.

Changes

Driving Spray Chart and Cockpit Wiring

Layer / File(s) Summary
Module constants, sector mappings, and covariance math
src/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx
Defines styling tokens, lateral-sector mapping, outcome-to-color selection, and statistical helpers: mean/SD/covariance over shot coordinates and a 1σ ellipse path builder that samples in data space and converts to SVG coordinates.
FairwayDrivingSprayProps interface, component logic, and SVG chart
src/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx
Exports FairwayDrivingSprayProps and FairwayDrivingSpray; memoizes scales, stats, and L/C/R counts; renders a Header subcomponent; gates on insufficient data; and for valid data produces the frosted-glass SVG panel with gradients, filter, target line, 1σ ellipse (n≥3 only), outcome-colored shot circles, mean marker, and axis labels.
Directional split bar and outcome legend
src/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx
Renders the stacked left/center/right percentage bar with labeled chips and an outcome legend that filters to nonzero-count buckets only.
Cockpit integration and ShotPatterns refactor
src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx
Imports FairwayDrivingSpray, adds it to the Driving tab via sprayData?.driving, and strips driving points/sector/takeaway from ShotPatterns; the two-column driving+approach layout is replaced by approach-only ShotDispersion plus PuttingHeatmap.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 10 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning Title uses 'stats' scope, which is not in the required scope list (golf, baseball, coachhelm, supabase, ios, ci, design-system, types, deps, docs, ops). Replace 'stats' with a required scope. Use 'feat(coachhelm):' since the change is in FairwayStatsCockpit under the coachhelm directory.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Description check ✅ Passed Description thoroughly details the new FairwayDrivingSpray component, integration into FairwayStatsCockpit, removal of old driving scatter, and implementation approach with verification steps.
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.
No Service-Role In Client Bundles ✅ Passed No SUPABASE_SERVICE_ROLE_KEY references found in PR files or codebase. FairwayDrivingSpray.tsx is a 'use client' component with only React, Surface, and InsufficientData imports—no Supabase client...
Rls Coverage On New Tables ✅ Passed PR contains only React/TypeScript component files (FairwayDrivingSpray.tsx, FairwayStatsCockpit.tsx); no SQL migrations or CREATE TABLE statements present.
Auth Check In Server Actions ✅ Passed PR modifies only client components (FairwayDrivingSpray.tsx, FairwayStatsCockpit.tsx) under src/components/, not server action files under src/app//actions//*.ts. Custom check scope does not ap...
Sport-Prefixed Table Names ✅ Passed Both modified/new *.tsx files are pure React components with no Supabase queries. Data fetching uses server actions in stats-data.ts, stats-leak-maps.ts, and insights.ts, all of which exclusively u...
No Destructive Writes ✅ Passed No destructive write patterns found. Both FairwayDrivingSpray.tsx and FairwayStatsCockpit.tsx are client-side UI components with no database operations, server mutations, or DELETE/INSERT patterns.
No Edits To Historical Migrations ✅ Passed No files under supabase/migrations/ were modified. PR only adds/modifies React components in src/components/fairway/pages/coachhelm/.
Conventional Commits ✅ Passed Commit subject "feat(stats): premium glass driving spray chart in the Driving tab" matches Conventional Commits format with type (feat), scope (stats), and description.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/driving-spray-chart

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.43.0)
src/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx

Error: Cannot parse rule /ast-grep-rules/../git/.coderabbit/ast-grep/no-explicit-any.yml
Help: The file is not a valid ast-grep rule. Please refer to doc and fix the error.
See also: https://ast-grep.github.io/guide/rule-config.html

✖ Caused by
╰▻ Fail to parse yaml as Rule.
╰▻ rule is not configured correctly.
╰▻ Rule contains invalid pattern matcher.
╰▻ Multiple AST nodes are detected. Please check the pattern source as any.

src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx

Error: Cannot parse rule /ast-grep-rules/../git/.coderabbit/ast-grep/no-explicit-any.yml
Help: The file is not a valid ast-grep rule. Please refer to doc and fix the error.
See also: https://ast-grep.github.io/guide/rule-config.html

✖ Caused by
╰▻ Fail to parse yaml as Rule.
╰▻ rule is not configured correctly.
╰▻ Rule contains invalid pattern matcher.
╰▻ Multiple AST nodes are detected. Please check the pattern source as any.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a69c1769b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


/**
* ============================================================================
* FairwayDrivingSpray — the premium off-the-tee spray chart (driving directions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add this component to the feature routing table

The repository AGENTS.md requires mapped feature code to be routed through memory/registry.yml, but running npm run knowledge:map -- --files src/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx returns impactedFeatures: []. This new stats/CoachHelm component will therefore be reviewed without the required current-state documentation and checks; add the Fairway cockpit path to the appropriate stats feature mapping.

Useful? React with 👍 / 👎.

Comment on lines +194 to +195
// ── Honest empty state — no fabricated chart when there are no plotted shots.
if (!group || n === 0 || !model) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Distinguish spray fetch failures from an empty history

When the spray query fails, getSprayChartData catches the error and returns a fulfilled response containing an empty driving group, so this branch displays “No tee-shot spray yet” and tells an existing player to log rounds. Previously the failed/empty driving enrichment was hidden with the shot-pattern section; preserve an error/availability signal or omit the chart on fetch failure rather than presenting the zero-point response as genuine absence of data.

Useful? React with 👍 / 👎.

Comment on lines +240 to +244
const legend = [
{ label: 'Fairway', count: group.playableCount, color: ACCENT },
{ label: 'Trouble', count: group.troubleCount, color: WARNING },
{ label: 'Penalty', count: group.penaltyCount, color: DANGER },
].filter((l) => l.count > 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Legend counts may not match visible dot count

group.playableCount, group.troubleCount, and group.penaltyCount are server-supplied fields on SprayChartShotGroup. Looking at the type, they sit alongside totalShots and plottedShots as separate fields, which suggests they may count all shots tracked (even those without valid coordinates), not just the plottedShots that appear in points[]. If that's the case, a user could see e.g. "Penalty: 2" in the legend while only one red dot is visible on the chart — confusing at best, silently wrong at worst. The direction bar already uses only points (plotted shots) for its counts; the legend should be consistent with that. Worth confirming whether these counts are scoped to plottedShots on the server side, and if not, deriving them directly from points instead. Do playableCount, troubleCount, and penaltyCount in SprayChartShotGroup count only shots that have valid coordinates (i.e. appear in points[]), or do they count all shots in totalShots (including those that couldn't be plotted)?

Fix in Claude Code

Comment on lines +215 to +216
const total = left + center + right;
const pct = (c: number) => (total > 0 ? Math.round((c / total) * 100) : 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Independent rounding can leave the distribution bar at 99% or 101%

pct rounds each bucket independently, so three values like 13, 42, 46 sum to 101 (as demonstrated in the PR's own aria preview). At 101%, the overflow-hidden container clips the rightmost segment, potentially trimming its last:rounded-r-full corner. At 99%, a 1-pixel sliver of bg-surface-sunken is visible at the right edge. A common fix is to compute the last segment as 100 - pct(left) - pct(center) (or equivalently use a single Math.round for the remainder), so the three values always sum to exactly 100.

Fix in Claude Code

@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: 4

🤖 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/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx`:
- Around line 58-60: The labels in SECTOR_TAKEAWAY constant and the outcome
bucket labels around lines 234-243 are overstating what the data represents by
conflating sector direction and outcome bucket values with specific terrain
features. The data contract only provides sector direction (center, left, right)
and outcomeBucket values (playable, trouble, penalty), but the current labels
suggest specific terrain types like "short grass" and "Fairway." Update the
SECTOR_TAKEAWAY labels to accurately describe the center, left, and right
sectors without assuming terrain type, and update the outcome bucket labels in
the affected code region to describe playable, trouble, and penalty outcomes
honestly without conflating them with specific fairway features.
- Around line 275-285: Replace the hardcoded hex colors in the SVG gradient and
filter definitions with Fairway design tokens to maintain theme consistency. In
the linearGradient element, replace the stopColor="`#ffffff`" values with an
appropriate design token variable. In the feDropShadow filter element, replace
the floodColor="`#1c1917`" hardcoded hex value with the corresponding Fairway
design token. Follow the existing pattern where ACCENT is already being used for
the radialGradient stops, ensuring all color references throughout the component
use design-system tokens instead of inline hex values.
- Around line 29-33: The import of SprayChartShotGroup, SprayChartPoint, and
SprayChartSector types in FairwayDrivingSpray.tsx is currently sourced from the
action module '`@/app/golf/actions/stats-data-types`', but component TSX files
should import types from the shared type boundary '`@/lib/types`'. First, verify
that these three types are exported from '`@/lib/types`'; if they are not yet
exported there, re-export them from that shared types module. Then update the
import statement in FairwayDrivingSpray.tsx to import these types from
'`@/lib/types`' instead of from the action module, ensuring the component adheres
to the coding guideline of consuming types from the centralized type
definitions.

In `@src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx`:
- Line 574: The issue is that when getSprayChartData rejects, sprayData is set
to null, which causes FairwayDrivingSpray to display "No tee-shot spray yet"
incorrectly since this message should only appear for successful responses with
empty data, not for fetch failures. To fix this, distinguish between fetch
failures and actual no-data by tracking the error state separately from
sprayData, or by using a different null/undefined sentinel value for failures.
Modify the code that handles the getSprayChartData rejection to set a separate
error state instead of setting sprayData to null, then only pass null to
FairwayDrivingSpray when sprayData is populated but contains no driving data,
not when the fetch fails.
🪄 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

Run ID: 5d7dbb26-6403-4b15-8837-d9dcc87bf6fe

📥 Commits

Reviewing files that changed from the base of the PR and between b310847 and 1a69c17.

📒 Files selected for processing (2)
  • src/components/fairway/pages/coachhelm/FairwayDrivingSpray.tsx
  • src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx

Comment on lines +29 to +33
import type {
SprayChartShotGroup,
SprayChartPoint,
SprayChartSector,
} from '@/app/golf/actions/stats-data-types';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Import spray chart types through @/lib/types.

Line 29 imports type definitions from an action module, but component TSX should consume type definitions from the shared type surface.

Proposed change
 import { InsufficientData } from '`@/components/fairway/feedback/InsufficientData`';
 import type {
   SprayChartShotGroup,
   SprayChartPoint,
   SprayChartSector,
-} from '`@/app/golf/actions/stats-data-types`';
+} from '`@/lib/types`';

If these spray-chart types are not exported there yet, re-export them from @/lib/types and keep this component on the shared type boundary.

As per coding guidelines, “Import types from '@/lib/types' only; never use '@/types/database' or '@/types/supabase'” and “Import types from @/lib/types for type definitions.”

🤖 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/fairway/pages/coachhelm/FairwayDrivingSpray.tsx` around lines
29 - 33, The import of SprayChartShotGroup, SprayChartPoint, and
SprayChartSector types in FairwayDrivingSpray.tsx is currently sourced from the
action module '`@/app/golf/actions/stats-data-types`', but component TSX files
should import types from the shared type boundary '`@/lib/types`'. First, verify
that these three types are exported from '`@/lib/types`'; if they are not yet
exported there, re-export them from that shared types module. Then update the
import statement in FairwayDrivingSpray.tsx to import these types from
'`@/lib/types`' instead of from the action module, ensuring the component adheres
to the coding guideline of consuming types from the centralized type
definitions.

Source: Coding guidelines

Comment on lines +58 to +60
const SECTOR_TAKEAWAY: Record<string, string> = {
center: 'Tee shots tend to find the short grass',
left: 'Misses lean left',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep sector and outcome labels data-honest.

Line 59 treats center as “short grass,” line 236 labels the center sector as “Fairway line,” and line 241 labels the playable bucket as “Fairway.” The provided contract exposes sector direction separately from outcomeBucket: 'playable' | 'trouble' | 'penalty', so these labels can overstate the data.

Proposed change
 const SECTOR_TAKEAWAY: Record<string, string> = {
-  center: 'Tee shots tend to find the short grass',
+  center: 'Tee shots tend to finish near the intended line',
   left: 'Misses lean left',
   right: 'Misses lean right',
@@
   const dist = [
     { key: 'left', label: 'Left', count: left, color: dominantMiss === 'left' ? WARNING : 'var(--fw-color-text-tertiary)' },
-    { key: 'center', label: 'Fairway line', count: center, color: ACCENT },
+    { key: 'center', label: 'Target line', count: center, color: ACCENT },
     { key: 'right', label: 'Right', count: right, color: dominantMiss === 'right' ? WARNING : 'var(--fw-color-text-tertiary)' },
   ] as const;
 
   const legend = [
-    { label: 'Fairway', count: group.playableCount, color: ACCENT },
+    { label: 'Playable', count: group.playableCount, color: ACCENT },
     { label: 'Trouble', count: group.troubleCount, color: WARNING },
     { label: 'Penalty', count: group.penaltyCount, color: DANGER },
   ].filter((l) => l.count > 0);

Also applies to: 234-243

🤖 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/fairway/pages/coachhelm/FairwayDrivingSpray.tsx` around lines
58 - 60, The labels in SECTOR_TAKEAWAY constant and the outcome bucket labels
around lines 234-243 are overstating what the data represents by conflating
sector direction and outcome bucket values with specific terrain features. The
data contract only provides sector direction (center, left, right) and
outcomeBucket values (playable, trouble, penalty), but the current labels
suggest specific terrain types like "short grass" and "Fairway." Update the
SECTOR_TAKEAWAY labels to accurately describe the center, left, and right
sectors without assuming terrain type, and update the outcome bucket labels in
the affected code region to describe playable, trouble, and penalty outcomes
honestly without conflating them with specific fairway features.

Comment on lines +275 to +285
<stop offset="0%" stopColor="#ffffff" stopOpacity="0.55" />
<stop offset="42%" stopColor="#ffffff" stopOpacity="0.14" />
<stop offset="100%" stopColor={ACCENT} stopOpacity="0.06" />
</linearGradient>
{/* 1σ ellipse fill — soft helm-green from the center out. */}
<radialGradient id={ellId} cx="50%" cy="50%" r="60%">
<stop offset="0%" stopColor={ACCENT} stopOpacity="0.22" />
<stop offset="100%" stopColor={ACCENT} stopOpacity="0.06" />
</radialGradient>
<filter id={glowId} x="-40%" y="-40%" width="180%" height="180%">
<feDropShadow dx="0" dy="0.5" stdDeviation="1.1" floodColor="#1c1917" floodOpacity="0.18" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace inline SVG hex colors with Fairway tokens.

Lines 275, 276, 285, and 306 hardcode hex colors in a component under src/components. Use the existing design tokens/CSS variables so the glass treatment remains theme-consistent.

Proposed change
-                <stop offset="0%" stopColor="`#ffffff`" stopOpacity="0.55" />
-                <stop offset="42%" stopColor="`#ffffff`" stopOpacity="0.14" />
+                <stop offset="0%" stopColor={SURFACE} stopOpacity="0.55" />
+                <stop offset="42%" stopColor={SURFACE} stopOpacity="0.14" />
@@
-                <feDropShadow dx="0" dy="0.5" stdDeviation="1.1" floodColor="`#1c1917`" floodOpacity="0.18" />
+                <feDropShadow dx="0" dy="0.5" stdDeviation="1.1" floodColor="var(--fw-color-text-primary)" floodOpacity="0.18" />
@@
-              stroke="`#ffffff`"
+              stroke={SURFACE}

As per coding guidelines, “React + Tailwind. Use design-system tokens only … Reject inline hex values, ad-hoc spacing, or radius outside rounded-2xl/rounded-xl/ rounded-lg.”

Also applies to: 306-306

🤖 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/fairway/pages/coachhelm/FairwayDrivingSpray.tsx` around lines
275 - 285, Replace the hardcoded hex colors in the SVG gradient and filter
definitions with Fairway design tokens to maintain theme consistency. In the
linearGradient element, replace the stopColor="`#ffffff`" values with an
appropriate design token variable. In the feDropShadow filter element, replace
the floodColor="`#1c1917`" hardcoded hex value with the corresponding Fairway
design token. Follow the existing pattern where ACCENT is already being used for
the radialGradient stops, ensuring all color references throughout the component
use design-system tokens instead of inline hex values.

Source: Coding guidelines

<DrivingSection detailedStats={detailedStats} />
<div className="flex flex-col gap-6">
<DrivingSection detailedStats={detailedStats} />
<FairwayDrivingSpray group={sprayData?.driving ?? null} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t show “No tee-shot spray yet” for a failed spray fetch.

Line 574 passes null into FairwayDrivingSpray, but sprayData is also set to null when getSprayChartData rejects. That turns an enrichment failure into a false no-data message; fulfilled no-data responses already carry an empty driving group.

Proposed change
-              <FairwayDrivingSpray group={sprayData?.driving ?? null} />
+              {sprayData ? <FairwayDrivingSpray group={sprayData.driving} /> : null}
🤖 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/fairway/pages/coachhelm/FairwayStatsCockpit.tsx` at line 574,
The issue is that when getSprayChartData rejects, sprayData is set to null,
which causes FairwayDrivingSpray to display "No tee-shot spray yet" incorrectly
since this message should only appear for successful responses with empty data,
not for fetch failures. To fix this, distinguish between fetch failures and
actual no-data by tracking the error state separately from sprayData, or by
using a different null/undefined sentinel value for failures. Modify the code
that handles the getSprayChartData rejection to set a separate error state
instead of setting sprayData to null, then only pass null to FairwayDrivingSpray
when sprayData is populated but contains no driving data, not when the fetch
fails.

@njrini99-code

Copy link
Copy Markdown
Owner Author

Superseded by #304, which rolls this onto a single branch (feat/coachhelm-stats-roundup) with all four PRs merged clean + a merge-interaction fix. Branch feat/driving-spray-chart is untouched and this PR can be reopened if needed.

@njrini99-code
njrini99-code deleted the feat/driving-spray-chart branch June 29, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant