Skip to content

feat(shared): one expected-empty-state registry for UI copy + telemetry classification#809

Open
njrini99-code wants to merge 2 commits into
mainfrom
feat/shared-empty-state-registry
Open

feat(shared): one expected-empty-state registry for UI copy + telemetry classification#809
njrini99-code wants to merge 2 commits into
mainfrom
feat/shared-empty-state-registry

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Summary

UI-audit item SH-002 (view-state contract) — the natural completion of #804/#807. The empty-state codes those PRs introduced were telemetry-only: EXPECTED_EMPTY_STATE_CODES lived inside the soft-failure observer while UI empty surfaces rendered hardcoded copy with no knowledge of why data was absent, so user-facing copy and observability classification could silently drift apart.

New leaf module src/lib/view-state/expected-empty-states.ts (zero imports — safe from both server-only modules and client components) owns:

  • the four registered codes (engine_no_recent_rounds, no_rounds_in_period, no_completed_rounds, insufficient_rounds),
  • the global-classification CONTRACT (moved from the observer),
  • user-facing copy per code: honest title + next-action description, optional unit/required hints for gap meters.

Consumers:

  • observe-action-result.ts now imports the code set — classification behavior unchanged (full 64-test suite green).
  • The player CoachHelm route (/golf/dashboard/coachhelm) preserves failure codes instead of collapsing { success: false, code } to bare null, and FairwayPlayerCoachHelm's InsufficientData surfaces render registry copy for the actual absence reason — "No rounds yet — record or import a round" for a brand-new player vs "Trend analysis unlocks at 3 completed rounds" — falling back to the existing generic lines when no code is present (real failures keep degrading exactly as before).

A registry test locks the invariant: observer classification and registry codes can never disagree.

Partner-readable summary

When a player's analytics are empty, the app now explains the actual reason (new player, quiet date range, or not enough rounds yet) using the same classification the error-monitoring system uses — so the copy on screen and the ops dashboards can never tell different stories.

Type of change

  • Bug fix
  • Feature / new behavior
  • Security / RLS / auth
  • Database migration
  • CI / tooling / chore
  • Docs only

Area

golf · coachhelm · shared observability

Risk level

  • Low — behavior-preserving refactor of the observer (suite green) plus copy-only improvements to empty surfaces; real failures degrade exactly as before

Git Activity Timeline note

Empty-state copy and error-monitoring classification now share one registry, so what players see and what ops dashboards record can't drift apart.

Checklist

  • npm run typecheck passes
  • observe-action-result suite green (64 tests); new registry suite green (16 tests)
  • eslint --max-warnings 0 clean on touched files
  • No migrations, no RLS changes, no secrets

🤖 Generated with Claude Code

https://claude.ai/code/session_019WJ1Gzjj9MT3UsxhMoMzSV


Generated by Claude Code

…ry classification

UI-audit item SH-002 (§12 view-state contract): the empty-state codes
introduced in #804/#807 were telemetry-only — EXPECTED_EMPTY_STATE_CODES
lived in the soft-failure observer while UI surfaces rendered hardcoded
copy with no knowledge of WHY data was absent, so user-facing copy and
observability classification could silently drift.

New leaf module src/lib/view-state/expected-empty-states.ts owns the
codes, the global-classification CONTRACT, and user-facing copy (title +
next-action description, optional unit/required gap-meter hints) for all
four registered codes. Consumers:

- observe-action-result.ts imports the code set (behavior unchanged —
  full 64-test suite green).
- The player CoachHelm route preserves the failure codes instead of
  collapsing to bare null, and FairwayPlayerCoachHelm's InsufficientData
  surfaces render registry copy for the ACTUAL absence reason (brand-new
  player vs quiet period vs <3 rounds), falling back to the existing
  generic lines when no code is present.

A registry test locks the invariant: observer classification and registry
codes can never disagree.

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

vercel Bot commented Jul 12, 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 12, 2026 3:09am

Request Review

@supabase

supabase Bot commented Jul 12, 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 ↗︎.

@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown

Greptile Summary

This PR shares expected empty-state reasons between UI copy and telemetry classification. The main changes are:

  • Adds a leaf registry for expected empty-state codes and copy.
  • Reuses the registry in the soft-failure observer.
  • Passes V3 empty-state codes into the player CoachHelm UI.
  • Updates CoachHelm empty surfaces to show reason-specific copy.
  • Adds tests that keep registry codes and observer classification aligned.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
src/lib/view-state/expected-empty-states.ts Adds the shared empty-state registry used by both UI copy and telemetry classification.
src/lib/admin/observe-action-result.ts Moves expected-empty classification to the shared registry without changing the observer behavior.
src/app/golf/(dashboard)/dashboard/coachhelm/page.tsx Preserves V3 empty-state codes and passes them to the player CoachHelm component.
src/components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx Uses shared empty-state copy and prefers all-time profile reasons over period-scoped shot reasons in the overview empty state.
src/lib/view-state/tests/expected-empty-states.test.ts Adds tests for registry completeness and observer agreement.

Reviews (2): Last reviewed commit: "fix(golf): overview empty banner prefers..." | Re-trigger Greptile

Comment thread src/components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx Outdated
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change centralizes expected empty-state definitions, reuses them for observer classification, and passes V3 empty-state codes from the CoachHelm server page to registry-driven client rendering.

Changes

CoachHelm Empty-State Handling

Layer / File(s) Summary
Shared empty-state registry and validation
src/lib/view-state/expected-empty-states.ts, src/lib/view-state/__tests__/expected-empty-states.test.ts
Defines registered codes, UI copy, metadata, lookup helpers, and tests for registry and observer consistency.
Observer registry integration
src/lib/admin/observe-action-result.ts
Imports the shared code set while preserving expected-empty-state classification behavior.
CoachHelm empty-state rendering
src/app/golf/(dashboard)/dashboard/coachhelm/page.tsx, src/components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx
Captures failed V3 action codes and uses registry copy for profile, trend, shots, and overview insufficient-data states.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CoachHelmPage
  participant FairwayPlayerCoachHelm
  participant expectedEmptyStateCopy
  CoachHelmPage->>FairwayPlayerCoachHelm: pass v3EmptyCodes
  FairwayPlayerCoachHelm->>expectedEmptyStateCopy: resolve registered code
  expectedEmptyStateCopy-->>FairwayPlayerCoachHelm: return empty-state copy
  FairwayPlayerCoachHelm-->>CoachHelmPage: render InsufficientData surfaces
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 8

❌ Failed checks (1 warning, 7 inconclusive)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title matches the PR theme, but it uses the non-permitted scope shared instead of one of the required Conventional Commit scopes. Change it to a Conventional Commit title with an allowed scope, e.g. feat(coachhelm): ... or feat(golf): ....
No Service-Role In Client Bundles ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Rls Coverage On New Tables ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Auth Check In Server Actions ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Sport-Prefixed Table Names ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
No Destructive Writes ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
No Edits To Historical Migrations ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Conventional Commits ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage 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.
Description check ✅ Passed The description is clearly about the shared empty-state registry, CoachHelm copy, and observer classification changes.
✨ 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/shared-empty-state-registry
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

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

…oped

Greptile P2: a brand-new player gets no_completed_rounds (all-time) from
the profile call but no_rounds_in_period (period-scoped) from shot
context; the banner's shots-first priority showed 'widen the date range'
to players who should be told to record their first round. Profile's
all-time verdict now wins.

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

@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.

Caution

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

⚠️ Outside diff range comments (2)
src/components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx (1)

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

Hoist repeated expectedEmptyStateCopy(...) lookups per block.

Each InsufficientData block re-invokes expectedEmptyStateCopy with the identical argument 2-3 times (Line 639/643 for profile ?? shots; Line 666/668 for profile; Line 684/686/690 for trend). Functionally fine today, but this is the same shape of bug the PR's own follow-up fix had to correct (one call site getting the priority order right, another not). A single hoisted const per block removes that drift risk and reads cleaner.

♻️ Example for the trend block (Lines 682-693)
-                  ) : (
-                    <Surface padding="md">
-                      <InsufficientData
-                        title={expectedEmptyStateCopy(v3EmptyCodes.trend)?.title ?? 'Trends warming up'}
-                        description={
-                          expectedEmptyStateCopy(v3EmptyCodes.trend)?.description ??
-                          'Log a couple more rounds and your performance trend lines fill in.'
-                        }
-                        unit="rounds"
-                        required={expectedEmptyStateCopy(v3EmptyCodes.trend)?.required}
-                      />
-                    </Surface>
-                  )}
+                  ) : (
+                    <Surface padding="md">
+                      {(() => {
+                        const trendCopy = expectedEmptyStateCopy(v3EmptyCodes.trend);
+                        return (
+                          <InsufficientData
+                            title={trendCopy?.title ?? 'Trends warming up'}
+                            description={
+                              trendCopy?.description ??
+                              'Log a couple more rounds and your performance trend lines fill in.'
+                            }
+                            unit="rounds"
+                            required={trendCopy?.required}
+                          />
+                        );
+                      })()}
+                    </Surface>
+                  )}

Same pattern applies to the profile ?? shots block (Lines 637-646) and the profile-only block (Lines 665-673).

🤖 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/FairwayPlayerCoachHelm.tsx` around
lines 629 - 693, Hoist each repeated expectedEmptyStateCopy lookup into a local
constant within the three affected InsufficientData blocks: the overview block
using v3EmptyCodes.profile ?? v3EmptyCodes.shots, the profile fallback using
v3EmptyCodes.profile, and the trend fallback using v3EmptyCodes.trend. Reuse the
corresponding constant for title, description, and required values without
changing the existing fallback text or priority order.
src/app/golf/(dashboard)/dashboard/coachhelm/page.tsx (1)

140-161: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

One throwing V3 action blanks all three empty-state codes, not just its own.

Promise.all at Line 150 means if any of getPlayerProfile/getPlayerTrendAnalysis/getPlayerShotContext throws (vs. returning {success:false, code}), the whole block falls into catch at Line 161 and v3EmptyCodes stays {} for all three — even for calls that would have resolved successfully. That undercuts the precision this registry is built for: the UI falls back to generic copy for panels that had a perfectly good code available.

♻️ Sketch: isolate failures per call
-    const [profileResult, trendResult, shotResult] = await Promise.all([
-      getPlayerProfile(player.id),
-      getPlayerTrendAnalysis(player.id),
-      getPlayerShotContext(player.id),
-    ]);
+    const [profileResult, trendResult, shotResult] = await Promise.all([
+      getPlayerProfile(player.id).catch(() => ({ success: false as const, code: null })),
+      getPlayerTrendAnalysis(player.id).catch(() => ({ success: false as const, code: null })),
+      getPlayerShotContext(player.id).catch(() => ({ success: false as const, code: 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/app/golf/`(dashboard)/dashboard/coachhelm/page.tsx around lines 140 -
161, Update the V3 fetch flow around getPlayerProfile, getPlayerTrendAnalysis,
and getPlayerShotContext so each action’s rejection is handled independently
instead of allowing one Promise.all failure to clear all v3EmptyCodes. Preserve
successful data and codes for calls that resolve, while assigning only the
throwing call’s data and empty code to their existing fallback values.
🤖 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.

Outside diff comments:
In `@src/app/golf/`(dashboard)/dashboard/coachhelm/page.tsx:
- Around line 140-161: Update the V3 fetch flow around getPlayerProfile,
getPlayerTrendAnalysis, and getPlayerShotContext so each action’s rejection is
handled independently instead of allowing one Promise.all failure to clear all
v3EmptyCodes. Preserve successful data and codes for calls that resolve, while
assigning only the throwing call’s data and empty code to their existing
fallback values.

In `@src/components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx`:
- Around line 629-693: Hoist each repeated expectedEmptyStateCopy lookup into a
local constant within the three affected InsufficientData blocks: the overview
block using v3EmptyCodes.profile ?? v3EmptyCodes.shots, the profile fallback
using v3EmptyCodes.profile, and the trend fallback using v3EmptyCodes.trend.
Reuse the corresponding constant for title, description, and required values
without changing the existing fallback text or priority order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9cc187e9-b1af-4a2a-9f8b-d2a0b39c7120

📥 Commits

Reviewing files that changed from the base of the PR and between 3237633 and 0c72c1d.

📒 Files selected for processing (5)
  • src/app/golf/(dashboard)/dashboard/coachhelm/page.tsx
  • src/components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx
  • src/lib/admin/observe-action-result.ts
  • src/lib/view-state/__tests__/expected-empty-states.test.ts
  • src/lib/view-state/expected-empty-states.ts

@linear-code linear-code Bot marked this pull request as ready for review July 12, 2026 03: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.

2 participants