From 3bf249c2c6d98832e7fc36688916d077010646f8 Mon Sep 17 00:00:00 2001 From: Anettkva Date: Thu, 22 Oct 2026 12:44:36 +0200 Subject: [PATCH 1/3] feat: show the variant letter in the element title and variant cards --- src/components/elements/ElementDetailHeader.tsx | 4 ++-- src/components/ui/ElementVariants.tsx | 5 +++-- src/lib/catalog.ts | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/components/elements/ElementDetailHeader.tsx b/src/components/elements/ElementDetailHeader.tsx index 7c35d3d..70eabdb 100644 --- a/src/components/elements/ElementDetailHeader.tsx +++ b/src/components/elements/ElementDetailHeader.tsx @@ -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, @@ -22,7 +22,7 @@ export default function ElementDetailHeader({

- {element.code} + {displayCode(element, element.variants.length > 0)} {element.name && ( <> diff --git a/src/components/ui/ElementVariants.tsx b/src/components/ui/ElementVariants.tsx index 7cb74bf..63996b7 100644 --- a/src/components/ui/ElementVariants.tsx +++ b/src/components/ui/ElementVariants.tsx @@ -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({ @@ -39,7 +39,8 @@ export default function ElementVariants({
- {variant.code} + {/* A card is only here because the code has several variants. */} + {displayCode(variant, true)} {variant.name && ` · ${variant.name}`}

diff --git a/src/lib/catalog.ts b/src/lib/catalog.ts index e6091e2..50de2a4 100644 --- a/src/lib/catalog.ts +++ b/src/lib/catalog.ts @@ -50,6 +50,23 @@ export function buildImageUrl( return `${baseUrl.replace(/\/+$/, '')}/data/images/${apparatusId}/${image}` } +/** The variant letter of an id: "b" in "UB-2.305-b". */ +export const variantLetter = (id: string): string => + id.slice(id.lastIndexOf('-') + 1) + +/** + * The code to show for an element or vault. When the code has other variants, + * the variant letter is added ("2.305-b"), so the variants can be told apart. + * Without other variants it is the code alone ("2.305"), so most titles stay + * short. The title and the variant cards both use this. + */ +export function displayCode( + item: Pick, + hasOtherVariants: boolean, +): string { + return hasOtherVariants ? `${item.code}-${variantLetter(item.id)}` : item.code +} + function toCatalogVariant(record: CatalogRecord): CatalogVariant { const isVault = 'dValue' in record From 2f3bc77ed8227c539f8598728f82cd23c28c1d15 Mon Sep 17 00:00:00 2001 From: Anettkva Date: Thu, 22 Oct 2026 12:44:49 +0200 Subject: [PATCH 2/3] test: check the variant letter in the title, the cards and displayCode --- .../elements/ElementDetailHeader.test.tsx | 47 +++++++++++++++++++ src/components/ui/ElementVariants.test.tsx | 1 + src/lib/catalog.test.ts | 20 +++++++- 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/components/elements/ElementDetailHeader.test.tsx diff --git a/src/components/elements/ElementDetailHeader.test.tsx b/src/components/elements/ElementDetailHeader.test.tsx new file mode 100644 index 0000000..0aa20a9 --- /dev/null +++ b/src/components/elements/ElementDetailHeader.test.tsx @@ -0,0 +1,47 @@ +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-b', + 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): string => { + const markup = renderToString( + + + , + ) + const h1 = /]*>(.*?)<\/h1>/.exec(markup)?.[1] ?? '' + return h1.replace(/<[^>]+>/g, '') +} + +describe('ElementDetailHeader', () => { + it('shows the variant letter when the code has other variants', () => { + const variant = { ...element, id: 'UB-2.305-a' } + expect(titleOf({ ...element, variants: [variant] })).toBe('2.305-b') + }) + + it('shows the code alone when there are no other variants', () => { + expect(titleOf(element)).toBe('2.305') + }) + + it('gives two variants of the same code different titles', () => { + const a = { ...element, id: 'UB-2.305-a' } + const b = { ...element, id: 'UB-2.305-b' } + expect(titleOf({ ...a, variants: [b] })).toBe('2.305-a') + expect(titleOf({ ...b, variants: [a] })).toBe('2.305-b') + }) +}) diff --git a/src/components/ui/ElementVariants.test.tsx b/src/components/ui/ElementVariants.test.tsx index 2db38d7..c63115a 100644 --- a/src/components/ui/ElementVariants.test.tsx +++ b/src/components/ui/ElementVariants.test.tsx @@ -24,6 +24,7 @@ describe('ElementVariants', () => { expect(markup).toMatch(/Variants of this element.*1.*<\/h2>/) expect(markup).toContain('href="/elements/UB-1.101-b"') + expect(markup).toContain('1.101-b') // the card shows its variant letter expect(markup).toContain('Glide kip with half turn') expect(markup).toContain('Difficulty ') expect(markup).toContain('Value ') diff --git a/src/lib/catalog.test.ts b/src/lib/catalog.test.ts index 13b7d6e..7543693 100644 --- a/src/lib/catalog.test.ts +++ b/src/lib/catalog.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from 'vitest' -import { buildImageUrl, getElementDetail } from './catalog' +import { + buildImageUrl, + displayCode, + getElementDetail, + variantLetter, +} from './catalog' describe('getElementDetail', () => { it('returns catalog metadata, other variants, and the diagram URL', async () => { @@ -32,3 +37,16 @@ describe('buildImageUrl', () => { expect(buildImageUrl('/', 'UB', 'a.webp')).toBe('/data/images/UB/a.webp') }) }) + +describe('displayCode', () => { + it('reads the variant letter from the end of the id', () => { + expect(variantLetter('UB-2.305-b')).toBe('b') + expect(variantLetter('VT-1.00-a')).toBe('a') + }) + + it('adds the 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') + }) +}) From 10ecb133420d15a0a882d2570ebb986a281204e9 Mon Sep 17 00:00:00 2001 From: Anettkva Date: Fri, 23 Oct 2026 11:29:38 +0200 Subject: [PATCH 3/3] refactor: simplify the variant letter to one helper, and fix the review points --- docs/AI_USAGE.md | 11 +++++++ .../elements/ElementDetailHeader.test.tsx | 33 +++++++------------ src/components/elements/ElementDiagram.tsx | 4 +-- src/components/ui/ElementVariants.test.tsx | 3 +- src/lib/catalog.test.ts | 15 ++------- src/lib/catalog.ts | 14 +++----- 6 files changed, 34 insertions(+), 46 deletions(-) diff --git a/docs/AI_USAGE.md b/docs/AI_USAGE.md index bc5eb73..d428a79 100644 --- a/docs/AI_USAGE.md +++ b/docs/AI_USAGE.md @@ -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. diff --git a/src/components/elements/ElementDetailHeader.test.tsx b/src/components/elements/ElementDetailHeader.test.tsx index 0aa20a9..fe746b9 100644 --- a/src/components/elements/ElementDetailHeader.test.tsx +++ b/src/components/elements/ElementDetailHeader.test.tsx @@ -5,7 +5,7 @@ import type { ElementDetail } from '@/lib/catalog' import ElementDetailHeader from './ElementDetailHeader' const element: ElementDetail = { - id: 'UB-2.305-b', + id: 'UB-2.305-a', code: '2.305', name: null, description: 'Giant circle with hop', @@ -18,30 +18,19 @@ const element: ElementDetail = { variants: [], } -const titleOf = (detail: ElementDetail): string => { - const markup = renderToString( - - - , - ) - const h1 = /]*>(.*?)<\/h1>/.exec(markup)?.[1] ?? '' - return h1.replace(/<[^>]+>/g, '') -} +const titleOf = (detail: ElementDetail) => + /]*>(.*?)<\/h1>/.exec( + renderToString( + + + , + ), + )?.[1] describe('ElementDetailHeader', () => { - it('shows the variant letter when the code has other variants', () => { - const variant = { ...element, id: 'UB-2.305-a' } - expect(titleOf({ ...element, variants: [variant] })).toBe('2.305-b') - }) - - it('shows the code alone when there are no other variants', () => { - expect(titleOf(element)).toBe('2.305') - }) - it('gives two variants of the same code different titles', () => { - const a = { ...element, id: 'UB-2.305-a' } const b = { ...element, id: 'UB-2.305-b' } - expect(titleOf({ ...a, variants: [b] })).toBe('2.305-a') - expect(titleOf({ ...b, variants: [a] })).toBe('2.305-b') + expect(titleOf({ ...element, variants: [b] })).toBe('2.305-a') + expect(titleOf({ ...b, variants: [element] })).toBe('2.305-b') }) }) diff --git a/src/components/elements/ElementDiagram.tsx b/src/components/elements/ElementDiagram.tsx index 1f0ca8a..4b23503 100644 --- a/src/components/elements/ElementDiagram.tsx +++ b/src/components/elements/ElementDiagram.tsx @@ -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, @@ -13,7 +13,7 @@ export default function ElementDiagram({ {element.imageUrl ? ( {`Illustration 0)}${element.name ? `: ${element.name}` : ''}`} className="block h-auto w-full object-contain" /> ) : ( diff --git a/src/components/ui/ElementVariants.test.tsx b/src/components/ui/ElementVariants.test.tsx index c63115a..17565aa 100644 --- a/src/components/ui/ElementVariants.test.tsx +++ b/src/components/ui/ElementVariants.test.tsx @@ -24,7 +24,8 @@ describe('ElementVariants', () => { expect(markup).toMatch(/Variants of this element.*1.*<\/h2>/) expect(markup).toContain('href="/elements/UB-1.101-b"') - expect(markup).toContain('1.101-b') // the card shows its variant letter + // In the h3, since the link address also contains "1.101-b". + expect(markup).toMatch(/]*>1\.101-b<\/h3>/) expect(markup).toContain('Glide kip with half turn') expect(markup).toContain('Difficulty ') expect(markup).toContain('Value ') diff --git a/src/lib/catalog.test.ts b/src/lib/catalog.test.ts index 7543693..d3bb3a6 100644 --- a/src/lib/catalog.test.ts +++ b/src/lib/catalog.test.ts @@ -1,10 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - buildImageUrl, - displayCode, - getElementDetail, - variantLetter, -} from './catalog' +import { buildImageUrl, displayCode, getElementDetail } from './catalog' describe('getElementDetail', () => { it('returns catalog metadata, other variants, and the diagram URL', async () => { @@ -39,14 +34,10 @@ describe('buildImageUrl', () => { }) describe('displayCode', () => { - it('reads the variant letter from the end of the id', () => { - expect(variantLetter('UB-2.305-b')).toBe('b') - expect(variantLetter('VT-1.00-a')).toBe('a') - }) - - it('adds the letter only when the code has other variants', () => { + 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') }) }) diff --git a/src/lib/catalog.ts b/src/lib/catalog.ts index 50de2a4..8261305 100644 --- a/src/lib/catalog.ts +++ b/src/lib/catalog.ts @@ -50,21 +50,17 @@ export function buildImageUrl( return `${baseUrl.replace(/\/+$/, '')}/data/images/${apparatusId}/${image}` } -/** The variant letter of an id: "b" in "UB-2.305-b". */ -export const variantLetter = (id: string): string => - id.slice(id.lastIndexOf('-') + 1) - /** - * The code to show for an element or vault. When the code has other variants, - * the variant letter is added ("2.305-b"), so the variants can be told apart. - * Without other variants it is the code alone ("2.305"), so most titles stay - * short. The title and the variant cards both use this. + * 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, hasOtherVariants: boolean, ): string { - return hasOtherVariants ? `${item.code}-${variantLetter(item.id)}` : item.code + const letter = /-([a-z])$/.exec(item.id)?.[1] + return hasOtherVariants && letter ? `${item.code}-${letter}` : item.code } function toCatalogVariant(record: CatalogRecord): CatalogVariant {