Skip to content

fix: normalize all-caps athlete names at render time#233

Open
rootulp wants to merge 1 commit into
mainfrom
fix/224-normalize-name-casing
Open

fix: normalize all-caps athlete names at render time#233
rootulp wants to merge 1 commit into
mainfrom
fix/224-normalize-name-casing

Conversation

@rootulp

@rootulp rootulp commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Closes #224

Summary

Athlete names scraped from ironman.com have inconsistent casing, so search results showed "MULLER Nicolas" next to "Muller Alain", and the all-caps entries carried through to profile, result, and race pages.

This is a purely render-time fix (no data files, index scripts, or search/matching logic touched, to avoid conflicts with #218):

  • New pure helper formatAthleteName in app/src/lib/format.ts:
    • Only transforms tokens that are fully uppercase with 2+ letters ("MULLER" → "Muller"); mixed-case tokens like "McDonald" and "van der Berg" are left untouched
    • Handles hyphenated parts ("SMITH-JONES" → "Smith-Jones"), apostrophes ("O'BRIEN" → "O'Brien"), "Mc" prefixes ("MCDONALD" → "McDonald"; "Mac" intentionally not special-cased as it's ambiguous), and accented uppercase ("MÜLLER" → "Müller")
    • Single-letter initials are preserved
  • Applied at every athlete-name render site:
    • GlobalSearchBar search results (display only)
    • CommandPalette search results
    • Athlete profile page heading (/athlete/[slug])
    • Result page heading (/race/[slug]/result/[id]) and its OG share image
    • Top-finisher tables on the race page (/race/[slug])
    • Stats page ("Athlete with Most Races")

Test notes

  • Red/green TDD: added app/src/lib/__tests__/format.test.ts (13 cases: all-caps surname, already-correct names unchanged, hyphen/apostrophe/Mc/Mac, accented caps, initials, whitespace preservation) — written first and confirmed failing, then implemented
  • npx vitest run: 10 files, 98 tests, all passing
  • npm run lint: clean

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Athlete names throughout the application are now consistently formatted with proper title casing for improved readability and presentation. Name formatting has been applied across all athlete profiles, race results, top finishers leaderboards, search results, and statistics pages, delivering a more polished and professional user experience across the entire platform.

Athlete names scraped from ironman.com arrive with inconsistent casing,
so search results show "MULLER Nicolas" next to "Muller Alain". Add a
pure formatAthleteName helper that title-cases only fully-uppercase
tokens (handling hyphens, apostrophes, Mc prefixes, and accented
letters) while leaving correctly mixed-case names like "McDonald" and
"van der Berg" untouched, and apply it at every render site: global
search results, command palette, athlete profile heading, result page
heading, race page top-finisher tables, stats page, and the OG share
image. No data files or index-build scripts are modified.

Closes #224

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rootulp rootulp self-assigned this Jun 12, 2026
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
tritimes Ready Ready Preview, Comment Jun 12, 2026 3:35am

@rootulp rootulp enabled auto-merge (squash) June 12, 2026 02:55
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR introduces a new formatAthleteName utility function that normalizes athlete name display casing by title-casing only fully-uppercase tokens, with special handling for Mc prefixes and accented characters. The function is then applied consistently across seven UI surfaces where athlete names are rendered.

Changes

Athlete Name Formatting Utility

Layer / File(s) Summary
formatAthleteName implementation and tests
app/src/lib/format.ts, app/src/lib/__tests__/format.test.ts
New formatAthleteName(name: string) export detects all-caps tokens using Unicode property matching and applies title-casing selectively. Internal helpers isAllCaps, titleCaseToken, and titleCasePart handle hyphen/apostrophe splitting and Mc prefix restoration. Comprehensive test suite validates casing normalization, accent support, initial preservation, empty strings, and whitespace exactness.
Integration across UI surfaces
app/src/app/athlete/[slug]/page.tsx, app/src/app/race/[slug]/page.tsx, app/src/app/race/[slug]/result/[id]/opengraph-image.tsx, app/src/app/race/[slug]/result/[id]/page.tsx, app/src/app/stats/page.tsx, app/src/components/CommandPalette.tsx, app/src/components/GlobalSearchBar.tsx
formatAthleteName is imported and applied to athlete name display across athlete profile headings, race leaderboard links, result OpenGraph image headers, result page headings, stats page links, and search result text in CommandPalette and GlobalSearchBar.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A name's just a string, sometimes ALL IN CAPS,
But now they'll display with the right overlap!
McDonald and O'Reilly shine bright and true,
Across every page, the formatting's new. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a formatAthleteName helper to normalize all-caps athlete names during rendering across multiple pages.
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.

✏️ 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 fix/224-normalize-name-casing

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

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

🧹 Nitpick comments (1)
app/src/app/stats/page.tsx (1)

1-18: ⚡ Quick win

Use the shared time formatter here instead of the local copy.

app/src/lib/format.ts already exports formatTime, and this local formatSeconds has already drifted (seconds % 60 here vs Math.round(seconds % 60) there). Reusing the shared helper keeps time formatting consistent across pages and avoids another copy to maintain.

♻️ Proposed cleanup
 import Link from "next/link";
 import { getStatsPageData } from "`@/lib/data`";
 import { getCountryFlagISO } from "`@/lib/flags`";
-import { formatAthleteName } from "`@/lib/format`";
+import { formatAthleteName, formatTime } from "`@/lib/format`";
@@
-function formatSeconds(seconds: number): string {
-  const h = Math.floor(seconds / 3600);
-  const m = Math.floor((seconds % 3600) / 60);
-  const s = seconds % 60;
-  if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
-  return `${m}:${String(s).padStart(2, "0")}`;
-}
-
@@
-          <BigNumber value={formatSeconds(agg.averageHalfFinishSeconds)} />
+          <BigNumber value={formatTime(agg.averageHalfFinishSeconds)} />
@@
-          <BigNumber value={formatSeconds(agg.averageFullFinishSeconds)} />
+          <BigNumber value={formatTime(agg.averageFullFinishSeconds)} />
🤖 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 `@app/src/app/stats/page.tsx` around lines 1 - 18, The local formatSeconds
function in page.tsx should be removed and the shared formatter imported from
app/src/lib/format.ts; replace all uses of formatSeconds with formatTime and add
an import for formatTime from "`@/lib/format`", relying on formatTime's Math.round
behavior to keep formatting consistent (update any references to function name
formatSeconds to formatTime and delete the local formatSeconds implementation).
🤖 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.

Nitpick comments:
In `@app/src/app/stats/page.tsx`:
- Around line 1-18: The local formatSeconds function in page.tsx should be
removed and the shared formatter imported from app/src/lib/format.ts; replace
all uses of formatSeconds with formatTime and add an import for formatTime from
"`@/lib/format`", relying on formatTime's Math.round behavior to keep formatting
consistent (update any references to function name formatSeconds to formatTime
and delete the local formatSeconds implementation).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: be87f6d8-923b-44ce-b841-d80f9015c485

📥 Commits

Reviewing files that changed from the base of the PR and between eca33ed and e39e76d.

📒 Files selected for processing (9)
  • app/src/app/athlete/[slug]/page.tsx
  • app/src/app/race/[slug]/page.tsx
  • app/src/app/race/[slug]/result/[id]/opengraph-image.tsx
  • app/src/app/race/[slug]/result/[id]/page.tsx
  • app/src/app/stats/page.tsx
  • app/src/components/CommandPalette.tsx
  • app/src/components/GlobalSearchBar.tsx
  • app/src/lib/__tests__/format.test.ts
  • app/src/lib/format.ts

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown

Greptile Summary

Introduces a pure formatAthleteName helper in app/src/lib/format.ts that normalises all-caps tokens from ironman.com scraped data at render time, then applies it consistently across all athlete-name display sites (search bars, command palette, profile, result, race leaderboard, stats pages, and OG share image).

  • The core transform splits on whitespace, detects all-caps tokens (≥ 2 Unicode letters, all uppercase), and title-cases each — preserving hyphens, apostrophes, Mc-prefixes, accented letters, and mixed-case tokens like "McDonald" or "van der Berg" untouched.
  • All 98 existing tests pass and 13 new focused unit tests are added; the change is intentionally scoped to render time to avoid touching the search index or data files.

Confidence Score: 4/5

Safe to merge; the change is purely cosmetic at render time and cannot corrupt data or break search matching.

The helper is well-designed and the call sites are all straightforward substitutions. The one edge case is multi-letter period-separated initials (e.g. J.R.) which would be lowercased to J.r. — the test suite covers single-letter initials but not this combination.

app/src/lib/format.ts and app/src/lib/tests/format.test.ts — the isAllCaps letter-count guard and test coverage for period-separated multi-initial tokens.

Important Files Changed

Filename Overview
app/src/lib/format.ts Adds formatAthleteName helper; logic is solid but multi-letter period-separated initials would be incorrectly lowercased
app/src/lib/tests/format.test.ts 13 test cases covering main scenarios; multi-letter period-separated initials not covered
app/src/app/athlete/[slug]/page.tsx Applies formatAthleteName to profile heading h1
app/src/app/race/[slug]/result/[id]/opengraph-image.tsx Applies formatAthleteName to athlete name in the OG share image
app/src/app/race/[slug]/page.tsx Applies formatAthleteName to the top-finisher leaderboard table entries
app/src/components/GlobalSearchBar.tsx Applies formatAthleteName to search result display names
app/src/components/CommandPalette.tsx Applies formatAthleteName to command palette search result display names
app/src/app/stats/page.tsx Applies formatAthleteName to Athlete with Most Races display name
app/src/app/race/[slug]/result/[id]/page.tsx Applies formatAthleteName to the result page heading

Reviews (1): Last reviewed commit: "fix: normalize all-caps athlete names at..." | Re-trigger Greptile

Comment thread app/src/lib/format.ts
Comment on lines +21 to +25
function isAllCaps(token: string): boolean {
const letters = token.match(/\p{L}/gu);
if (!letters || letters.length < 2) return false;
return letters.every((c) => c !== c.toLowerCase() && c === c.toUpperCase());
}

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 Multi-letter period-separated initials are mangled

isAllCaps counts only Unicode letter code points, so a token like "J.R." has two uppercase letters and passes the >= 2 guard — titleCasePart then lowercases everything after the first character, turning it into "J.r.". The single-initial test works because "J." has only one letter (below the threshold). Any athlete stored as e.g. "SMITH J.R." would display as "Smith J.r.". A guard that treats tokens containing non-letter non-separator characters as non-transformable would cover this.

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.

data: athlete name casing is inconsistent in search results ("MULLER Nicolas" vs "Muller Alain")

1 participant