Skip to content

Conversation

ronjalh
Copy link
Member

@ronjalh ronjalh commented Oct 16, 2026 •

Summary

Build a modular, route-backed element detail page using the existing variant and difficulty components.

What's in it

  • Add /elements/:elementId with loading, not-found, and error/retry states.
  • Add focused header, metadata, diagram, and action sections.
  • Load element, apparatus, group, image, and variant data through a local catalog adapter.
  • Reuse ElementVariants and DifficultyBadge; use existing CSS variables for page and badge colors.
  • Add catalog and variant rendering tests, plus the required AI-usage entry.

Choices

The GraphQL server is not implemented yet, so the page reads the checked-in catalog JSON behind a separate adapter that can be replaced with an API client later. No new color values are introduced.

  • src/lib/catalog.ts reads the catalog from data/*.json in the frontend, so the whole catalog goes into the bundle (JS grows from about 224 kB to 496 kB). This is a stand-in until the GraphQL element / catalogItem queries are used.
  • / redirects to a fixed element (UB-1.101-a) while this is the only page.
  • The drawings in data/images are copied to dist/data/images by vite.config.ts on build, so they also work after npm run build.

Testing

  • npm run lint passed.
  • npm run build passed.
  • npm test -- --run: 26 passed, 23 skipped.
  • Focused ElementVariants tests passed after the final badge change.

Not done (still open)

  • The action buttons provide accessible feedback but are not connected to routine navigation or sign-in; see Create "add to gymnast"-button component #47.
  • Replace the local catalog adapter with GraphQL when the server is available.
  • Add page-level tests for loading, error, and not-found states.

Dependencies

This draft is stacked on PR #62, which in turn depends on PR #61. Its temporary base is 48-element-variant-card; retarget it to dev after #62 merges.

Part of #63

Generated with GitHub Copilot

@ronjalh ronjalh self-assigned this Oct 16, 2026
@ronjalh ronjalh force-pushed the 48-element-variant-card branch from 9f23af7 to 41097f6 Compare October 16, 2026 15:47
@ronjalh ronjalh marked this pull request as ready for review October 16, 2026 16:01
@ronjalh ronjalh requested a review from a team as a code owner October 16, 2026 16:01
@anettkva
Copy link
Member

Checked against 48-element-variant-card: it merges cleanly, and lint, type check, build and all 49 tests pass. Two things to fix before merging:

  1. Prettier fails on 11 of the changed files. The new files use double quotes and semicolons, while the repo uses single quotes and no semicolons. Some also lack the final newline. Run npx prettier --write on the changed files.
  2. The drawings are missing in the build. catalog.ts uses /data/images/.... That works in npm run dev, but data/ is not in public/, so the images are not copied into dist/ and all image links break after npm run build.

Fine for now, but please mention it in the PR description:

  • catalog.ts reads the catalog from data/*.json in the frontend. The whole catalog goes into the bundle (JS grows from about 224 kB to 496 kB). That's OK as a stand-in, but it should be replaced by the GraphQL element / catalogItem queries when the API is ready.
  • / redirects to a fixed element (UB-1.101-a). That's OK while this is the only page.

Generated with claude code

… the badge

- Merge 48-element-variant-card (keeps this branch's badge and variant list).
- Prettier on all changed source files (single quotes, no semicolons).
- vite.config.ts copies data/images to dist/data/images on build, so the
  drawings also work after npm run build.
- DifficultyBadge: title and a visually hidden label instead of role=img.
Copy link
Member

Choose a reason for hiding this comment

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

Great work setting up the aria-live region for screen readers!

Two quick fixes before merging:

  1. Visual Feedback: p.sr-only hides the status text from sight, leaving sighted users with no feedback when clicking the buttons. Render the status message visually (e.g. <p role="status" className="text-xs text-muted-foreground">) so everyone sees why the feature is locked/unbuilt.
  2. Icon Sizing: Add an explicit size class like size-4 to <Plus /> and <LockKeyhole /> to prevent SVG scaling layout glitches across different browsers.

Copy link
Member

Choose a reason for hiding this comment

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

Looking good! Two quick accessibility improvements before merging:

  1. Back Button aria-label: Add aria-label="Go back to previous page" to the back button so screen readers give a descriptive context when navigating by interactive elements.
  2. Conditional Description Guard: Wrap <p> with {element.description && ...} so empty description fields don't leave empty <p> elements in the DOM.

Copy link
Member

Choose a reason for hiding this comment

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

ElementDetailPage.tsx state machine and data-fetching flow look very solid!

One quick styling polish: Add heading font utility classes (text-3xl font-bold) to the <h1> elements in the not-found and error conditional branches so they don't render as unstyled browser defaults when a 404 or network error occurs.

Copy link
Member

Choose a reason for hiding this comment

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

Just a tiny polish suggestion when you get a chance:

  • Alt Text: We could simplify the alt attribute to Illustration for ${element.code}${element.name ? : ${element.name} : ''}. Screen readers automatically announce "Image", so keeping it focused on the code/name avoids reading out long description text or repeating words like "sequence".
  • Semantics: Since there isn't a visible <figcaption>, changing <figure> to a standard <div> (or adding a <figcaption> if we want a caption below the image) will keep the HTML structure super tight.

Copy link
Member

Choose a reason for hiding this comment

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

Love how clean and well-structured ElementMetadataCard.tsx is coming along! Using <dl>, <dt>, and <dd> inside a semantic <section> gives it fantastic document structure for accessibility.

Just a couple of small suggestions to consider:

  • Badge Label Consistency: Inside MetadataItem, element.isVault ? 'D-value' : 'Difficulty' works great, but for non-vaults with an unmapped difficulty letter, ElementValueBadge receives label="Difficulty value". We might want to pass label="Difficulty" there too so screen reader announcements stay consistent regardless of whether it falls back to a numeric value or renders DifficultyBadge.
  • Card Padding Utilities: If you're using Tailwind v4 variable utilities elsewhere in the layout, swapping style={{ backgroundColor: 'var(--background-color-element)' }} for bg-(--background-color-element) will keep the styling approach uniform across your UI components.

Really nice work on the layout and semantic markup!

Copy link
Member

Choose a reason for hiding this comment

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

Looks good! Just two quick accessibility tweaks to consider:

  1. Replace inline style with Tailwind v4 syntax: Swap style={{ backgroundColor: 'var(--background-color-element)' }} for className="w-full bg-(--background-color-element)" on <Card> to keep your utility class pattern consistent across components.
  2. Standardize ElementValueBadge label: Inside ElementValueBadge, pass label={variant.isVault ? 'D-value' : 'Difficulty'} (matching ElementMetadataCard) so screen reader announcements stay completely uniform across fallback states.

Copy link
Member

Choose a reason for hiding this comment

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

Looks great! Just two small data-handling improvements to consider:

  1. Fallback for Missing Image Filename: If record.image is undefined or null on a record, ${import.meta.env.BASE_URL}data/images/${apparatusId}/${record.image} will produce an invalid image path ending in /undefined. Setting imageUrl to record.image ? ... : null ensures the component cleanly triggers its "No illustration available" fallback state.
  2. Dynamic Base URL Trailing Slashes: Depending on how import.meta.env.BASE_URL is configured in Vite, concatenating without normalization can lead to double slashes (e.g. //data/images/...). Replacing import.meta.env.BASE_URL with a light path-join or stripping extra slashes will prevent unexpected 404s in production subfolder deployments.

Copy link
Member

Choose a reason for hiding this comment

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

Looks good! Just two quick test suite improvements to consider:

  1. Explicit imageUrl test for missing images: Add a test case for an element record where image is undefined or null to ensure getElementDetail sets imageUrl to null instead of building an invalid path ending in /undefined.
  2. Loosen strict path structure in imageUrl match: If import.meta.env.BASE_URL changes between environments (e.g., local preview vs. CI/staging subpaths), checking expect(detail?.imageUrl).toContain('UB-1.101-a.webp') keeps the assertion clean while preventing fragile regex matches on leading slash patterns.

Copy link
Member

Choose a reason for hiding this comment

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

Looks good! Just two quick improvements to consider:

  1. Floating-point comparison safety in getDifficultyLetter: Rather than relying on exact equality after toFixed(1), using an epsilon check (e.g. Math.abs(difficultyValue - value) < 0.05) prevents precision edge cases if dynamic values like 0.10000000000000002 are passed in.
  2. Reverse lookup map: Pre-building a Map<number, DifficultyLetter> once at module scope avoids running Object.entries() array iterations on every lookup call.

Copy link
Member

Choose a reason for hiding this comment

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

Looks good! Just two quick structural improvements to consider:

  1. Move CSS imports into App.tsx or a single styles entry point: Keeping global styles (variables.css, global.css, index.css) organized in App.tsx or bundled through a central stylesheet keeps main.tsx cleanly focused on bootstrapping the React root and router.
  2. Set basename on BrowserRouter for subpath deployments: If hosting on a subfolder or virtual machine path (like Vite's import.meta.env.BASE_URL), passing <BrowserRouter basename={import.meta.env.BASE_URL}> prevents routing 404s on page refresh or direct navigation.

Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants