Skip to content

fix(coachhelm): coach-facing surfaces speak coach voice, not player voice - #931

Closed
njrini99-code wants to merge 3 commits into
mainfrom
fix/coach-surface-voice
Closed

fix(coachhelm): coach-facing surfaces speak coach voice, not player voice#931
njrini99-code wants to merge 3 commits into
mainfrom
fix/coach-surface-voice

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Problem

Player-POV components render on coach-facing surfaces, and several color/icon valences are inverted (bug #915). Verified live:

  1. Team Stats SG cards (StandingStrip, shared with the player's own stats view via FairwayStatsCockpit) always render "YOU −3.34 … Below team average / Bottom of your team" regardless of who is reading — a coach viewing a teammate's card is never "you" to that player.
  2. Patterns tab (coach-only) shows player-first-person, grammatically malformed copy: "When After 5+ days off and In tournament, you tend to score 4.7 strokes worse than average. Monitor this pattern and discuss with your coach."
  3. Pattern card icons color by severity tier (priority, bucketed by |stroke_impact| magnitude) instead of direction — a high-magnitude plays-better pattern could land in the 'high' tier (warning-orange flame) while a low-magnitude plays-worse pattern landed in 'medium' (green sparkle).
  4. Score trend Ribbon (Team Stats / player stats cockpit) renders an improving (falling) score as an amber "▼ −7.0" decline — Ribbon never told Readout which raw direction is good for the plotted metric.

Fix

  1. StandingBar/StandingStrip — added viewer_context: 'self' | 'coach' + player_name to the shared StandingBarProps. neutralizeForCoach/initialsFromName/standingSubjectLabel (new pure helpers in utils.ts) swap "You" → the player's initials and strip the "your team" possessive for a coach reader. Threaded through FairwayTeamStats (always coach), FairwayStatsCockpit (derives it from the existing isOwnStats flag — no new flag for callers to keep in sync), and FairwayPlayerStats (passes the already-resolved teammate name down). The player's own view is byte-for-byte unchanged.
  2. pattern-miner.ts — new joinConditionLabels helper fixes the "When X and Y" double-conjunction (condition labels already carry their own leading connector) so "After 5+ days off in tournament rounds, …" reads naturally instead of "When After 5+ days off and In tournament, …".
  3. patternToInsightVocabulary.ts (confirmed coach-only — /dashboard/patterns guards if (!coach)) — new toCoachVoice rewrites the mined pattern's "you tend to…" into third person with the player's name, and swaps the generic "discuss with your coach" recommendation for "Worth a conversation with <name>." Team-authored patterns (already third-person) pass through as a no-op.
  4. InsightCard/InsightPanel — new iconTone: 'positive' | 'negative' | 'neutral' prop overrides the icon glyph + wrap color independently of priority (which keeps driving the tint bar/severity ordering — a legitimate, separate signal). New SignalRow.valence, derived from the signed stroke_impact, is wired into both the scanned card and the expand-in-place panel so a pattern reads identically either way.
  5. Ribbon — new goodDirection prop (reuses the shared classifyTrend classifier already used by Sparkline/StatTile/TrendChip) computes the trend delta's verdict instead of leaving Readout to infer it from raw sign. Wired goodDirection="down" on the "Score by round" Ribbon. Also fixed an inverted direction ternary on FairwayRoundDetail's score-to-par delta found during the same audit (an under-par round was rendering the same false amber decline).

Gates

  • npx tsc --noEmit -p tsconfig.json — clean
  • npx eslint <19 changed files> — clean
  • npx vitest run on all new/updated suites + adjacent regression suites (FairwayTeamStats.test.ts, surface-registry.test.ts, CoachInsightCard.test.tsx) — 636 passing, 0 failing (20 pre-existing skips, unrelated)

Tests added

  • StandingBar.test.tsxneutralizeForCoach, initialsFromName, standingSubjectLabel, coach-context deriveAriaLabel
  • pattern-miner.test.tsjoinConditionLabels (including the exact reported compound-pattern case) + end-to-end PatternMiner.generateDescription
  • patternToInsightVocabulary.test.ts (new) — toCoachVoice + patternToSignalRow title/body/valence, including a same-priority-tier/opposite-valence regression lock
  • InsightCard.test.tsx (new) — iconTone overriding the icon wrap color independent of priority
  • Ribbon.test.tsxgoodDirection="down" flipping the trend verdict on the same falling series

Caveats

  • Diff is 19 files (guidance was ≤15) — the four bug items share a small set of primitives (StandingBar/StandingStrip, InsightCard/InsightPanel, Ribbon) fanning out to their real call sites plus co-located tests; I kept each touch as small as possible rather than batching fixes into fewer, larger files.
  • Did not touch team-pattern-generator.ts (already correctly third-person) or the broader coachhelm/v2/mining/* generators that also use first-person "you"/"your" phrasing outside the two reported templates — out of scope for this issue; flagging in case a follow-up wants a wider audience-voice sweep.
  • FairwayRoundDetail.tsx's inverted-delta fix is a bonus finding from auditing "other metrics rendered by the same component" (Readout), not one of the four originally reported items — no dedicated test added for it (no existing test scaffold for that component); covered by the same direction-semantics reasoning locked down in Ribbon.test.tsx.

Fixes #915

Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg

Fable Integrator and others added 2 commits July 17, 2026 17:52
… math, accuracy gate

Four independently-verified bugs on the Signals/Effectiveness surfaces:

1. By-player grouping ("Unknown player"): EvidenceInsight never carried a
   resolved player name (only golf_coach_insights.player_id), so every
   insight/alert row hard-coded playerName: undefined and collapsed into one
   "Unknown player" bucket. Patterns were unaffected (getTeamPatterns already
   joins golf_players inline). insightToSignalRow/insightsToSignalRows now
   accept an optional player_id -> name map; /alerts and /insights SSR-fetch
   the team roster via getTeamPlayers() and pass it through.

2. KPI tile semantics: "Urgent + high" counted every loaded row regardless of
   status while "Open" filtered by status, so /alerts could show
   "Urgent + high 13 > Open 10" — a severity breakdown reading as MORE than
   its own superset. Urgent+high is now a true subset of Open (matches the
   shell badge's own getAlertCounts().counts.critical semantics). Tile labels
   now say the sub-tab noun ("Open alerts"/"Open insights"/"Open patterns")
   instead of a generic "Open signals", and "Loaded" (dev-speak) is renamed
   "Showing".

3. LeakBoard math/label: the "Where the team is bleeding" total is a SUM
   across every player and every leak insight in a category, but was labeled
   "str/rd" (a per-round rate) — reading as if −26.4 meant 26 strokes lost
   EVERY round, ~8x the real team SG-putting figure (~−3.19/rd). Relabeled to
   an honest "total", and the rollup now reads from its own full team-wide
   fetch (no priority filter) instead of the active sub-tab's own scoped
   `insights` state, so the banner no longer silently re-totals per sub-tab.

4. Effectiveness accuracy headline: gated on GAUGE_MIN_RESOLVED (2) while the
   calibration side panel's own copy says "needs 5 resolved predictions" —
   the two thresholds could disagree, letting a near-empty sample render an
   authoritative "100% ▲+8%". Now gated on the SAME 5-resolved threshold
   (isAccuracyHeadlineLive), with "Calibrating" replacing "Awaiting
   predictions". Also fixed: StrokesGainedTornado's y-scale keyed rows by
   display label, so two rows sharing a label (e.g. one player with two
   top-N patterns) collapsed onto the same band — bars and value text
   rendered on top of each other ("+4.10+4.67"). Now keyed by row index.
   And the "no trend yet" cell rendered a bare "—" with no visible label
   (only a tooltip); now matches the icon idiom the other three trend states
   already use, dimmed to read as "no data" rather than a genuine flat trend.

Gates: tsc --noEmit clean, eslint clean on changed files, full vitest suite
green (272 files / 2636 passed).

Part of #914 + Fixes #907

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg
…oice (#915)

Player-POV components rendered on coach-facing surfaces, and several
color/icon valences were inverted:

1. SG cards (StandingStrip, shared by Team Stats and the player's own
   stats view) always said "YOU -3.34 ... Below team average / Bottom
   of your team" regardless of who was reading. Threaded a
   viewer_context/player_name pair through StandingBar's shared
   StandingBarProps -> StandingStrip -> FairwayTeamStats/
   FairwayStatsCockpit/FairwayPlayerStats: a coach reader now sees the
   player's name/initials and possessive-free cohort text ("Bottom of
   team"); the player's own view is unchanged ("You" / "your team").

2. Mined-pattern copy on the coach-only Patterns tab was
   player-first-person and grammatically malformed: "When After 5+
   days off and In tournament, you tend to score 4.7 strokes worse
   than average. ... discuss with your coach." Fixed the template
   concatenation in PatternMiner.generateDescription (new
   joinConditionLabels helper) so condition labels compose into one
   natural clause instead of double-conjuncting "When X and Y", and
   added a toCoachVoice rewrite in the coach-only
   patternToInsightVocabulary.ts adapter that swaps "you tend to" for
   the player's name and "discuss with your coach" for "Worth a
   conversation with <name>."

3. Pattern card icon/accent was derived from `priority`, a severity
   tier bucketed by |stroke_impact| magnitude alone (blind to sign) -
   a high-magnitude plays-BETTER pattern could land in the 'high' tier
   (warning-orange flame) while a low-magnitude plays-WORSE pattern
   landed in 'medium' (green sparkle). Added a signed `valence` field
   to SignalRow and an `iconTone` override to InsightCard/InsightPanel
   that recolors the icon by direction, independent of the severity
   tint bar.

4. The Score-by-round trend Ribbon (Team Stats / player stats cockpit)
   rendered an improving (falling) score as an amber "-7.0" decline
   because Ribbon never told Readout which raw direction was good for
   the metric. Added a `goodDirection` prop to Ribbon (reusing the
   shared classifyTrend classifier) and set it to "down" for the score
   series. Also fixed an inverted `direction` ternary on
   FairwayRoundDetail's score-to-par delta found during the same
   audit (an under-par round rendered the same false amber decline).

Gates: tsc --noEmit clean; eslint clean on all changed files; 636
vitest tests passing (0 failing) across the new/updated suites plus
adjacent regression suites (FairwayTeamStats, surface-registry,
CoachInsightCard).

Fixes #915

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

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@cursor

cursor Bot commented Jul 17, 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 17, 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 17, 2026 11:41pm

Request Review

@supabase

supabase Bot commented Jul 17, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


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

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@njrini99-code, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cc54b48a-08ef-44a6-9104-d36733813e52

📥 Commits

Reviewing files that changed from the base of the PR and between 57c7088 and aa5cdee.

📒 Files selected for processing (11)
  • src/app/golf/(dashboard)/dashboard/alerts/page.tsx
  • src/app/golf/(dashboard)/dashboard/insights/page.tsx
  • src/components/fairway/charts/StrokesGainedTornado.test.tsx
  • src/components/fairway/charts/StrokesGainedTornado.tsx
  • src/components/fairway/pages/coachhelm/FairwayCoachHelmSignals.tsx
  • src/components/fairway/pages/coachhelm/FairwayEffectiveness.test.ts
  • src/components/fairway/pages/coachhelm/FairwayEffectiveness.tsx
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.test.ts
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts
  • src/components/golf/coachhelm/coach/LeakBoard.test.tsx
  • src/components/golf/coachhelm/coach/LeakBoard.tsx

Walkthrough

The PR fixes Bug #915 across coach-helm insights and standings by separating signed valence from priority, adding coach-aware language and labels, supporting explicit icon tones, correcting lower-is-better trend classification, and updating pattern grammar with regression tests.

Changes

Coach Helm semantics

Layer / File(s) Summary
Pattern narrative and valence
src/lib/coachhelm/v2/mining/pattern-miner.ts, src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts, src/test/coachhelm/v2/mining/pattern-miner.test.ts, src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.test.ts
Pattern condition text uses natural conjunctions, pattern narratives are rewritten for coach voice, and SignalRow.valence is derived from signed strokeImpact independently of priority.
Insight icon tone propagation
src/components/fairway/cards-insight/*, src/components/fairway/pages/coachhelm/FairwayCoachHelmSignals.tsx, src/components/fairway/cards-insight/InsightCard.test.tsx
InsightCard and InsightPanel accept explicit icon tones, and signal rows pass valence to both list and detail renderings while preserving priority fallback behavior.
Coach standing context
src/components/golf/coachhelm/v3/StandingBar/*, src/components/fairway/charts/StandingStrip.tsx, src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx, src/components/fairway/pages/coachhelm/FairwayPlayerStats.tsx, src/components/fairway/pages/coachhelm/FairwayTeamStats.tsx, src/test/golf/components/StandingBar.test.tsx
Standing components receive viewer context and player names, derive coach-facing labels and initials, neutralize possessive wording, and generate coach-specific accessible text.
Trend verdict corrections
src/components/fairway/charts/Ribbon.tsx, src/components/fairway/charts/Ribbon.test.tsx, src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx, src/components/fairway/pages/rounds/FairwayRoundDetail.tsx
Trend deltas use goodDirection classification, scoring treats downward movement as good, and round score direction maps under-par and over-par values to the corresponding verdicts.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PatternMiner
  participant SignalVocabulary
  participant CoachHelmSignals
  participant InsightCard
  participant InsightPanel
  PatternMiner->>PatternMiner: joinConditionLabels(conditions)
  PatternMiner->>SignalVocabulary: generated pattern text
  SignalVocabulary->>SignalVocabulary: derivePatternValence(strokeImpact)
  SignalVocabulary->>CoachHelmSignals: coach-voiced SignalRow
  CoachHelmSignals->>InsightCard: row.valence as iconTone
  CoachHelmSignals->>InsightPanel: openRow.valence as iconTone
Loading
sequenceDiagram
  participant FairwayPlayerStats
  participant FairwayStatsCockpit
  participant StandingStrip
  participant StandingBarUtils
  FairwayPlayerStats->>FairwayStatsCockpit: playerName and isOwnStats
  FairwayStatsCockpit->>StandingStrip: viewer_context and player_name
  StandingStrip->>StandingBarUtils: derive subject and neutralize cohort text
  StandingBarUtils-->>StandingStrip: coach-aware labels and aria text
Loading

Possibly related issues

  • njrini99-code/helmv3#915 — The PR directly implements the issue’s coach-voice, valence, direction, and labeling corrections.

Possibly related PRs

  • njrini99-code/helmv3#49 — Introduced the v3 StandingBar structure and utilities extended here with coach-context props and accessible labeling helpers.

Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
No Service-Role In Client Bundles ❌ Error pattern-miner.ts:268,938 calls createAdminClient(); that wrapper uses SUPABASE_SERVICE_ROLE_KEY in src/lib/supabase/admin.ts:6-15, outside the allowed admin/api paths. Move the admin-client creation into src/lib/supabase/admin* or an api/admin route, and pass a non-service-role client into pattern-miner.ts instead.
✅ Passed checks (11 passed)
Check name Status Explanation
Title check ✅ Passed Conventional Commits format is used and the coachhelm scope matches the PR’s main coach-facing UI changes.
Description check ✅ Passed The description directly describes the coach-voice, valence, and trend-direction fixes reflected in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
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: The PR diff contains no migration files, so there’s no CREATE TABLE/RLS/policy combo to evaluate.
Auth Check In Server Actions ✅ Passed No changed files match src/app//actions//*.ts, so the auth-order check is not applicable to this PR.
Sport-Prefixed Table Names ✅ Passed Only changed Supabase queries are pattern-miner.ts:278,951,993,1038 and they use prefixed tables (golf_rounds, golf_patterns_v2).
No Destructive Writes ✅ Passed src/lib/coachhelm/v2/mining/pattern-miner.ts:935-1018 uses select/upsert/update only; no changed file contains a DELETE→INSERT table rebuild.
No Edits To Historical Migrations ✅ Passed git diff --name-only HEAD^ HEAD shows 19 changed files and none under supabase/migrations/.
Conventional Commits ✅ Passed The squashed commit subject fix(coachhelm): coach-facing surfaces speak coach voice, not player voice (#915) matches the Conventional Commits regex.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/coach-surface-voice
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

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.

@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/cards-insight/InsightCard.test.tsx`:
- Around line 18-20: Add a local framer-motion mock in InsightCard.test.tsx, or
extend the shared setup mock, including useReducedMotion with a deterministic
return value so InsightCard assertions do not depend on animation behavior.
Preserve the existing mocks and test structure.

In `@src/components/fairway/charts/Ribbon.test.tsx`:
- Around line 92-96: Remove the getDeltaDirection helper and its
document.querySelector/data-direction assertion from the Ribbon tests. Import or
reuse screen from `@testing-library/react`, then update each direction test to
assert the user-visible glyph rendered for that state: ▲, ▼, or ►, using
screen.getByText and preserving each test’s existing expectations.

In
`@src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts`:
- Around line 94-104: Use one metric-aware strokeImpact sign contract: update
the valence documentation and derivation logic in patternToInsightVocabulary.ts
so lower-is-better score_to_par inverts the raw impact sign, pass the outcome
metric from patternToSignalRow into valence derivation, and leave insight
priority-only behavior unchanged. In patternToInsightVocabulary.test.ts, assert
positive score deltas produce negative valence and negative deltas produce
positive valence; rename the relevant pattern-miner test in
pattern-miner.test.ts to state that negative score impact means “better.”

In `@src/test/coachhelm/v2/mining/pattern-miner.test.ts`:
- Around line 248-251: Correct the test name in the `it` block to state that a
negative `stroke_impact` reads “better,” not “worse,” matching the `-1.8`
argument passed to `generateDescription`. Leave the test implementation and
expected description unchanged.
🪄 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: 060a891b-37ba-4d54-b733-38aad79aa008

📥 Commits

Reviewing files that changed from the base of the PR and between 6d25e44 and 57c7088.

📒 Files selected for processing (19)
  • src/components/fairway/cards-insight/InsightCard.test.tsx
  • src/components/fairway/cards-insight/InsightCard.tsx
  • src/components/fairway/cards-insight/InsightPanel.tsx
  • src/components/fairway/charts/Ribbon.test.tsx
  • src/components/fairway/charts/Ribbon.tsx
  • src/components/fairway/charts/StandingStrip.tsx
  • src/components/fairway/pages/coachhelm/FairwayCoachHelmSignals.tsx
  • src/components/fairway/pages/coachhelm/FairwayPlayerStats.tsx
  • src/components/fairway/pages/coachhelm/FairwayStatsCockpit.tsx
  • src/components/fairway/pages/coachhelm/FairwayTeamStats.tsx
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.test.ts
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts
  • src/components/fairway/pages/rounds/FairwayRoundDetail.tsx
  • src/components/golf/coachhelm/v3/StandingBar/index.tsx
  • src/components/golf/coachhelm/v3/StandingBar/types.ts
  • src/components/golf/coachhelm/v3/StandingBar/utils.ts
  • src/lib/coachhelm/v2/mining/pattern-miner.ts
  • src/test/coachhelm/v2/mining/pattern-miner.test.ts
  • src/test/golf/components/StandingBar.test.tsx

Comment on lines +18 to +20
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/react';
import { InsightCard } from './InsightCard';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -i '.*(framer|motion|setup).*' src/test . |
  xargs -r rg -n -C3 "useReducedMotion|vi\\.mock\\(['\"]framer-motion"

Repository: njrini99-code/helmv3

Length of output: 15653


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== InsightCard.test.tsx ==\n'
sed -n '1,220p' src/components/fairway/cards-insight/InsightCard.test.tsx

printf '\n== Vitest setup candidates ==\n'
git ls-files | rg '(^|/)(vitest|vitest\.config|setup|test-setup|tests?/setup).*'
printf '\n== Motion/mock references in test setup files ==\n'
for f in $(git ls-files | rg '(^|/)(vitest|vitest\.config|setup|test-setup|tests?/setup).*'); do
  echo "--- $f"
  rg -n "framer-motion|useReducedMotion|vi\.mock|mock\('framer-motion'" "$f" || true
done

Repository: njrini99-code/helmv3

Length of output: 6408


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== vitest.config.ts ==\n'
sed -n '1,220p' vitest.config.ts

printf '\n== src/test/setup.tsx ==\n'
sed -n '1,220p' src/test/setup.tsx

printf '\n== InsightCard component references ==\n'
rg -n "useReducedMotion|framer-motion|motion" src/components/fairway/cards-insight/InsightCard.tsx src/components/fairway/cards-insight -g '!*.test.tsx'

Repository: njrini99-code/helmv3

Length of output: 14368


Add the missing framer-motion test mock
src/test/setup.tsx:12-67 mocks next/navigation, next/font/google, next/image, and @number-flow/react, but not framer-motion. InsightCard.test.tsx:18-20 needs a local vi.mock('framer-motion', ...) or a shared mock with useReducedMotion so these assertions stay deterministic.

🤖 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/cards-insight/InsightCard.test.tsx` around lines 18 -
20, Add a local framer-motion mock in InsightCard.test.tsx, or extend the shared
setup mock, including useReducedMotion with a deterministic return value so
InsightCard assertions do not depend on animation behavior. Preserve the
existing mocks and test structure.

Source: Path instructions

Comment on lines +92 to +96
function getDeltaDirection(): string | null {
const delta = document.querySelector('[data-slot="readout-delta"]');
expect(delta).not.toBeNull();
return delta!.getAttribute('data-direction');
}

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 | ⚡ Quick win

Query user-visible state instead of DOM implementation details.

document.querySelector couples the test to an internal data attribute. As per path instructions, prefer @testing-library/react queries and assert on user-visible state.

Drop this helper and assert the presence of the semantic visual glyphs (, , or ) in each test instead.

♻️ Proposed pattern
import { screen } from '`@testing-library/react`';

// ... inside your test block
it('goodDirection="down": the SAME falling series now reads "up" (green)', () => {
  render(<Ribbon title="Score by round" data={DECLINING} seriesName="Score" goodDirection="down" />);
  
  // The 'up' direction visually renders a '▲' glyph
  expect(screen.getByText(//)).toBeInTheDocument();
});
🤖 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/charts/Ribbon.test.tsx` around lines 92 - 96, Remove
the getDeltaDirection helper and its document.querySelector/data-direction
assertion from the Ribbon tests. Import or reuse screen from
`@testing-library/react`, then update each direction test to assert the
user-visible glyph rendered for that state: ▲, ▼, or ►, using screen.getByText
and preserving each test’s existing expectations.

Source: Path instructions

Comment on lines +94 to +104
/**
* Bug #915 — the pattern icon/accent must derive from the SIGNED
* stroke_impact (a plays-better pattern is 'positive', plays-worse is
* 'negative'), not from the severity/priority tier alone. `priority`
* buckets by |impact| magnitude regardless of sign, which let a
* high-magnitude POSITIVE pattern land in the 'high' tier (warning-orange
* flame) and a low-magnitude NEGATIVE one land in 'medium' (green
* sparkle) — backwards. Set ONLY by `patternToSignalRow`; insights leave
* it undefined (their priority-only tone is unaffected by this bug).
*/
valence?: 'positive' | 'negative' | 'neutral';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use one metric-aware strokeImpact sign contract.

The miner stores score_to_par impact as condition average minus baseline, so positive means worse and negative means better. The adapter and its tests currently interpret that sign backwards.

  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts#L94-L104: document that valence depends on metric direction, not the raw sign alone.
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts#L181-L195: invert classification for lower-is-better score_to_par.
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts#L502-L502: pass the outcome metric into the valence derivation.
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.test.ts#L121-L155: assert positive score deltas are negative valence and negative deltas are positive valence.
  • src/test/coachhelm/v2/mining/pattern-miner.test.ts#L248-L251: rename the test to state that negative score impact reads “better.”
📍 Affects 3 files
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts#L94-L104 (this comment)
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts#L181-L195
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts#L502-L502
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.test.ts#L121-L155
  • src/test/coachhelm/v2/mining/pattern-miner.test.ts#L248-L251
🤖 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/signals/patternToInsightVocabulary.ts`
around lines 94 - 104, Use one metric-aware strokeImpact sign contract: update
the valence documentation and derivation logic in patternToInsightVocabulary.ts
so lower-is-better score_to_par inverts the raw impact sign, pass the outcome
metric from patternToSignalRow into valence derivation, and leave insight
priority-only behavior unchanged. In patternToInsightVocabulary.test.ts, assert
positive score deltas produce negative valence and negative deltas produce
positive valence; rename the relevant pattern-miner test in
pattern-miner.test.ts to state that negative score impact means “better.”

Comment on lines +248 to +251
it('a positive stroke_impact reads "better", never "worse"', () => {
const miner = new PatternMiner('player-1') as unknown as Miner;
const description = miner.generateDescription([AFTER_5_DAYS], {}, -1.8);
expect(description).toBe('After 5+ days off, you tend to score 1.8 strokes better than average.');

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 | 🟡 Minor | ⚡ Quick win

Correct the sign stated in the test name.

Line 250 passes -1.8, so this verifies that a negative score delta reads “better,” not a positive one.

-  it('a positive stroke_impact reads "better", never "worse"', () => {
+  it('a negative score_to_par stroke_impact reads "better", never "worse"', () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('a positive stroke_impact reads "better", never "worse"', () => {
const miner = new PatternMiner('player-1') as unknown as Miner;
const description = miner.generateDescription([AFTER_5_DAYS], {}, -1.8);
expect(description).toBe('After 5+ days off, you tend to score 1.8 strokes better than average.');
it('a negative score_to_par stroke_impact reads "better", never "worse"', () => {
const miner = new PatternMiner('player-1') as unknown as Miner;
const description = miner.generateDescription([AFTER_5_DAYS], {}, -1.8);
expect(description).toBe('After 5+ days off, you tend to score 1.8 strokes better than average.');
🤖 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/test/coachhelm/v2/mining/pattern-miner.test.ts` around lines 248 - 251,
Correct the test name in the `it` block to state that a negative `stroke_impact`
reads “better,” not “worse,” matching the `-1.8` argument passed to
`generateDescription`. Leave the test implementation and expected description
unchanged.

@njrini99-code

Copy link
Copy Markdown
Owner Author

🤖 Mission Control — PR summary

What it changes: Coach-facing surfaces rendered player-POV copy and inverted color/icon valences (#915):

  1. SG cards (StandingStrip, shared by Team Stats and the player's own view) always said "YOU −3.34 … Below team average / Bottom of your team" regardless of who was reading. Threads a viewer_context / player_name pair through StandingBarStandingStripFairwayTeamStats / FairwayStatsCockpit / FairwayPlayerStats: a coach reader now sees the player's name/initials and possessive-free cohort text ("Bottom of team"); the player's own view is unchanged ("You" / "your team").
  2. Mined-pattern copy on the coach-only Patterns tab was first-person and grammatically malformed ("When After 5+ days off and In tournament, you tend to…"). Fixed the PatternMiner.generateDescription template concatenation.

Risk / areas: golf CoachHelm shared stats components (Team Stats / Patterns / player stats).

Watch: viewer_context threaded correctly through every StandingBar consumer; the player's own view stays exactly as before; grammaticality of generated pattern strings across branches.

CI: ⏳ just opened (22:05Z) — checks still initializing (28 passing, several pending, 0 failing); mergeable state BLOCKED on required review. Awaiting review + CI completion.

…s in patternToInsightVocabulary.test.ts

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

Copy link
Copy Markdown
Owner Author

Superseded — landed on main inside merge train #938 (commit 6ecede6). Branch kept.

@njrini99-code
njrini99-code deleted the fix/coach-surface-voice branch July 20, 2026 20:53
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.

[QA] Player-voice copy rendered on coach surfaces + inverted good/bad styling

1 participant