Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/AI_USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,14 @@
- **Review:** The team reviewed the theme changes and confirmed the colors
should come from the existing CSS variables rather than new palette values.


## The variant letter in the element title (`src/lib/catalog.ts`, `src/components/elements/`, `src/components/ui/ElementVariants.tsx`)

- **Tool:** Claude Code
- **What the team did:** Wrote issue #94 and chose to show the letter only when the
code has other variants. In the review, the team found that the card test also
passed without the letter, that the diagram alt text was the same for every
variant, and that an id in another format would give a strange title, and asked
for a smaller solution.
- **What Claude did:** Checked that all 836 ids in `data/` end in "-" and a letter,
wrote `displayCode` and the tests, and fixed the review points.
36 changes: 36 additions & 0 deletions src/components/elements/ElementDetailHeader.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { renderToString } from 'react-dom/server'
import { MemoryRouter } from 'react-router-dom'
import { describe, expect, it } from 'vitest'
import type { ElementDetail } from '@/lib/catalog'
import ElementDetailHeader from './ElementDetailHeader'

const element: ElementDetail = {
id: 'UB-2.305-a',
code: '2.305',
name: null,
description: 'Giant circle with hop',
value: 0.5,
isVault: false,
apparatusId: 'UB',
apparatusName: 'Uneven bars',
elementGroupName: 'Giant circles',
imageUrl: null,
variants: [],
}

const titleOf = (detail: ElementDetail) =>
/<h1[^>]*>(.*?)<\/h1>/.exec(
renderToString(
<MemoryRouter>
<ElementDetailHeader element={detail} />
</MemoryRouter>,
),
)?.[1]

describe('ElementDetailHeader', () => {
it('gives two variants of the same code different titles', () => {
const b = { ...element, id: 'UB-2.305-b' }
expect(titleOf({ ...element, variants: [b] })).toBe('2.305-a')
expect(titleOf({ ...b, variants: [element] })).toBe('2.305-b')
})
})
4 changes: 2 additions & 2 deletions src/components/elements/ElementDetailHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ArrowLeft } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import type { ElementDetail } from '@/lib/catalog'
import { displayCode, type ElementDetail } from '@/lib/catalog'

export default function ElementDetailHeader({
element,
Expand All @@ -22,7 +22,7 @@ export default function ElementDetailHeader({
</button>
<div>
<h1 className="m-0 text-left text-4xl font-bold leading-tight text-(--text-color-dark)">
{element.code}
{displayCode(element, element.variants.length > 0)}
{element.name && (
<>
<span aria-hidden="true"> · </span>
Expand Down
4 changes: 2 additions & 2 deletions src/components/elements/ElementDiagram.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Card, CardContent } from '@/components/ui/card'
import type { ElementDetail } from '@/lib/catalog'
import { displayCode, type ElementDetail } from '@/lib/catalog'

export default function ElementDiagram({
element,
Expand All @@ -13,7 +13,7 @@ export default function ElementDiagram({
{element.imageUrl ? (
<img
src={element.imageUrl}
alt={`Illustration for ${element.code}${element.name ? `: ${element.name}` : ''}`}
alt={`Illustration for ${displayCode(element, element.variants.length > 0)}${element.name ? `: ${element.name}` : ''}`}
className="block h-auto w-full object-contain"
/>
) : (
Expand Down
2 changes: 2 additions & 0 deletions src/components/ui/ElementVariants.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ describe('ElementVariants', () => {

expect(markup).toMatch(/Variants of this element.*1.*<\/h2>/)
expect(markup).toContain('href="/elements/UB-1.101-b"')
// In the h3, since the link address also contains "1.101-b".
expect(markup).toMatch(/<h3[^>]*>1\.101-b<\/h3>/)
expect(markup).toContain('Glide kip with half turn')
expect(markup).toContain('<span class="sr-only">Difficulty </span>')
expect(markup).toContain('<span class="sr-only">Value </span>')
Expand Down
5 changes: 3 additions & 2 deletions src/components/ui/ElementVariants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Link } from 'react-router-dom'
import { Card, CardContent, CardTitle } from '@/components/ui/card'
import DifficultyBadge from '@/components/ui/DifficultyBadge'
import ElementValueBadge from '@/components/ui/ElementValueBadge'
import type { CatalogVariant } from '@/lib/catalog'
import { displayCode, type CatalogVariant } from '@/lib/catalog'
import { getDifficultyLetter } from '@/lib/difficulty'

export default function ElementVariants({
Expand Down Expand Up @@ -39,7 +39,8 @@ export default function ElementVariants({
<CardContent className="flex items-center justify-between gap-4 px-5 py-3">
<div className="min-w-0 space-y-1 text-left">
<CardTitle as="h3" className="text-left">
{variant.code}
{/* A card is only here because the code has several variants. */}
{displayCode(variant, true)}
{variant.name && ` · ${variant.name}`}
</CardTitle>
<p className="text-sm leading-relaxed text-(--text-color-dark)">
Expand Down
11 changes: 10 additions & 1 deletion src/lib/catalog.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { buildImageUrl, getElementDetail } from './catalog'
import { buildImageUrl, displayCode, getElementDetail } from './catalog'

describe('getElementDetail', () => {
it('returns catalog metadata, other variants, and the diagram URL', async () => {
Expand Down Expand Up @@ -32,3 +32,12 @@ describe('buildImageUrl', () => {
expect(buildImageUrl('/', 'UB', 'a.webp')).toBe('/data/images/UB/a.webp')
})
})

describe('displayCode', () => {
it('adds the variant letter only when the code has other variants', () => {
const item = { id: 'UB-2.305-b', code: '2.305' }
expect(displayCode(item, true)).toBe('2.305-b')
expect(displayCode(item, false)).toBe('2.305')
expect(displayCode({ id: 'UB-2.305', code: '2.305' }, true)).toBe('2.305')
})
})
13 changes: 13 additions & 0 deletions src/lib/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ export function buildImageUrl(
return `${baseUrl.replace(/\/+$/, '')}/data/images/${apparatusId}/${image}`
}

/**
* The code to show: "2.305-b" when the code has other variants, so they can be
* told apart, and "2.305" otherwise. An id that does not end in "-" and a
* letter gives the code alone.
*/
export function displayCode(
item: Pick<CatalogVariant, 'id' | 'code'>,
hasOtherVariants: boolean,
): string {
const letter = /-([a-z])$/.exec(item.id)?.[1]
return hasOtherVariants && letter ? `${item.code}-${letter}` : item.code
}

function toCatalogVariant(record: CatalogRecord): CatalogVariant {
const isVault = 'dValue' in record

Expand Down