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
13 changes: 13 additions & 0 deletions docs/AI_USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,19 @@
- **Review:** The team refined the layout, colors, difficulty display, and
accessibility semantics during review.

## Element detail page (`src/components/elements/`, `src/lib/catalog.ts`)

- **Tool:** GitHub Copilot
- **What the team did:** Provided the detail-page design and specified modular
sections, reuse of the existing variant and difficulty components, existing
CSS colors only, and accessibility requirements.
- **What GitHub Copilot did:** Added a route-backed page, a local catalog data
adapter, reusable header, metadata, diagram and action sections, and tests
for catalog lookup and variant navigation. The page composes the existing
`ElementVariants` and `DifficultyBadge` components.
- **Review:** The team checked the page in the browser, corrected theme-token
use and image sizing, and reviewed keyboard navigation and accessible names.

## Catalog search (`backend/src/catalog/search.ts`, `eslint.config.js`, `docs/API.md`)

- **Tool:** Claude Code
Expand Down
60 changes: 59 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"cn": "^0.4.0",
"lucide-react": "^1.58.0",
"react": "^19.2.8",
"react-dom": "^19.2.8"
"react-dom": "^19.2.8",
"react-router-dom": "^7.18.4"
},
"devDependencies": {
"@babel/core": "^7.29.7",
Expand Down
125 changes: 10 additions & 115 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,121 +1,16 @@
import { useState } from 'react'
import heroImg from './assets/hero.png'
import reactLogo from './assets/react.svg'
import viteLogo from './assets/vite.svg'
import './App.css'
import { Navigate, Route, Routes } from 'react-router-dom'
import ElementDetailPage from './components/elements/ElementDetailPage'

function App() {
const [count, setCount] = useState(0)

return (
<>
<section id="center">
<div className="hero">
<img src={heroImg} className="base" width="170" height="179" alt="" />
<img src={reactLogo} className="framework" alt="React logo" />
<img src={viteLogo} className="vite" alt="Vite logo" />
</div>
<div>
<h1>Get started</h1>
<p>
Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
</p>
</div>
<button
type="button"
className="counter"
onClick={() => setCount((count) => count + 1)}
>
Count is {count}
</button>
</section>

<div className="ticks"></div>

<section id="next-steps">
<div id="docs">
<svg className="icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#documentation-icon"></use>
</svg>
<h2>Documentation</h2>
<p>Your questions, answered</p>
<ul>
<li>
<a href="https://vite.dev/" target="_blank">
<img className="logo" src={viteLogo} alt="" />
Explore Vite
</a>
</li>
<li>
<a href="https://react.dev/" target="_blank">
<img className="button-icon" src={reactLogo} alt="" />
Learn more
</a>
</li>
</ul>
</div>
<div id="social">
<svg className="icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#social-icon"></use>
</svg>
<h2>Connect with us</h2>
<p>Join the Vite community</p>
<ul>
<li>
<a href="https://github.com/vitejs/vite" target="_blank">
<svg
className="button-icon"
role="presentation"
aria-hidden="true"
>
<use href="/icons.svg#github-icon"></use>
</svg>
GitHub
</a>
</li>
<li>
<a href="https://chat.vite.dev/" target="_blank">
<svg
className="button-icon"
role="presentation"
aria-hidden="true"
>
<use href="/icons.svg#discord-icon"></use>
</svg>
Discord
</a>
</li>
<li>
<a href="https://x.com/vite_js" target="_blank">
<svg
className="button-icon"
role="presentation"
aria-hidden="true"
>
<use href="/icons.svg#x-icon"></use>
</svg>
X.com
</a>
</li>
<li>
<a href="https://bsky.app/profile/vite.dev" target="_blank">
<svg
className="button-icon"
role="presentation"
aria-hidden="true"
>
<use href="/icons.svg#bluesky-icon"></use>
</svg>
Bluesky
</a>
</li>
</ul>
</div>
</section>

<div className="ticks"></div>
<section id="spacer"></section>
</>
<Routes>
<Route
path="/"
element={<Navigate to="/elements/UB-1.101-a" replace />}
/>
<Route path="/elements/:elementId" element={<ElementDetailPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
)
}

Expand Down
40 changes: 40 additions & 0 deletions src/components/elements/ElementActions.tsx
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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useState } from 'react'
import { LockKeyhole, Plus } from 'lucide-react'
import { Button } from '@/components/ui/button'
import type { ElementDetail } from '@/lib/catalog'

export default function ElementActions({
element,
}: {
element: ElementDetail
}) {
const [status, setStatus] = useState('')

return (
<div
data-element-id={element.id}
className="flex flex-wrap items-center gap-3 text-left"
>
<Button
type="button"
variant="outline"
className="h-10 rounded-full bg-(--primary-color) px-5 font-heading text-(--text-color-dark)"
onClick={() => setStatus('The routine builder is not connected yet.')}
>
Try in a routine
<Plus aria-hidden="true" />
</Button>
<Button
type="button"
className="h-10 rounded-full border border-(--box-stroke) bg-(--text-color-dark) px-5 font-heading text-(--text-color-light) hover:bg-(--text-color-dark)"
onClick={() => setStatus('Sign-in is not connected yet.')}
>
Add to gymnast
<LockKeyhole aria-hidden="true" />
</Button>
<p className="sr-only" aria-live="polite" aria-atomic="true">
{status}
</p>
</div>
)
}
38 changes: 38 additions & 0 deletions src/components/elements/ElementDetailHeader.tsx
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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ArrowLeft } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import type { ElementDetail } from '@/lib/catalog'

export default function ElementDetailHeader({
element,
}: {
element: ElementDetail
}) {
const navigate = useNavigate()

return (
<header className="space-y-3 text-left">
<button
type="button"
onClick={() => navigate(-1)}
className="inline-flex items-center gap-2 rounded-md py-2 text-sm font-medium text-(--text-color-dark) underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
>
<ArrowLeft aria-hidden="true" className="size-4" />
Back
</button>
<div>
<h1 className="m-0 text-left text-4xl font-bold leading-tight text-(--text-color-dark)">
{element.code}
{element.name && (
<>
<span aria-hidden="true"> · </span>
{element.name}
</>
)}
</h1>
<p className="mt-2 text-left text-base leading-relaxed text-(--text-color-dark)">
{element.description}
</p>
</div>
</header>
)
}
Loading