diff --git a/docs/AI_USAGE.md b/docs/AI_USAGE.md
index 653e0e5..64ee17b 100644
--- a/docs/AI_USAGE.md
+++ b/docs/AI_USAGE.md
@@ -187,6 +187,18 @@
- **Review:** The team checked the page in the browser, corrected theme-token
use and image sizing, and reviewed keyboard navigation and accessible names.
+## Your gymnasts overview (`src/components/gymnasts/`, `src/lib/gymnasts.ts`)
+
+- **Tool:** GitHub Copilot
+- **What the team did:** Provided the page mockup and required existing theme
+ colors, accessible rows, and an add-gymnast flow.
+- **What GitHub Copilot did:** Added the `/gymnasts` route, gymnast rows and
+ initials, name normalization, and an accessible inline add form with local
+ state. It used the project's existing CSS variables rather than the mockup's
+ placeholder colors.
+- **Review:** The team exercised the form in the browser at desktop and mobile
+ widths. The list remains local until the GraphQL API is available.
+
## Missing fields in the GraphQL schema (`backend/src/schema.graphql`, `docs/API.md`)
- **Tool:** Claude Code, and Claude for the research.
diff --git a/src/App.tsx b/src/App.tsx
index e85caf4..c474b73 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,6 +3,7 @@ import './index.css'
import './styles/variables.css'
import './styles/global.css'
import ElementDetailPage from './components/elements/ElementDetailPage'
+import YourGymnastsPage from './components/gymnasts/YourGymnastsPage'
function App() {
return (
@@ -12,6 +13,7 @@ function App() {
element={}
/>
} />
+ } />
} />
)
diff --git a/src/components/gymnasts/GymnastList.test.tsx b/src/components/gymnasts/GymnastList.test.tsx
new file mode 100644
index 0000000..e3b933b
--- /dev/null
+++ b/src/components/gymnasts/GymnastList.test.tsx
@@ -0,0 +1,34 @@
+import { renderToString } from "react-dom/server";
+import { describe, expect, it } from "vitest";
+import GymnastList from "./GymnastList";
+
+describe("GymnastList", () => {
+ it("renders names with initials", () => {
+ const markup = renderToString(
+ ,
+ );
+
+ expect(markup).toContain("Ingrid Haugen");
+ expect(markup).toContain("IH");
+ expect(markup).toContain("Emma Berg");
+ expect(markup).toContain("EB");
+ });
+
+ it("has a heading for the list", () => {
+ const markup = renderToString();
+
+ expect(markup).toMatch(/
]*>Gymnast list<\/h2>/);
+ });
+
+ it("announces the empty list state", () => {
+ const markup = renderToString();
+
+ expect(markup).toContain('role="status"');
+ expect(markup).toContain("You haven’t added any gymnasts yet.");
+ });
+});
\ No newline at end of file
diff --git a/src/components/gymnasts/GymnastList.tsx b/src/components/gymnasts/GymnastList.tsx
new file mode 100644
index 0000000..39f56d0
--- /dev/null
+++ b/src/components/gymnasts/GymnastList.tsx
@@ -0,0 +1,30 @@
+import GymnastRow from "@/components/gymnasts/GymnastRow";
+import type { GymnastSummary } from "@/lib/gymnasts";
+
+export default function GymnastList({
+ gymnasts,
+}: {
+ gymnasts: GymnastSummary[];
+}) {
+ return (
+
+
+ Gymnast list
+
+ {gymnasts.length === 0 ? (
+
+ You haven’t added any gymnasts yet.
+
+ ) : (
+
+ {gymnasts.map((gymnast) => (
+
+ ))}
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/gymnasts/GymnastRow.tsx b/src/components/gymnasts/GymnastRow.tsx
new file mode 100644
index 0000000..2a955f8
--- /dev/null
+++ b/src/components/gymnasts/GymnastRow.tsx
@@ -0,0 +1,18 @@
+import type { GymnastSummary } from "@/lib/gymnasts";
+import { getGymnastInitials } from "@/lib/gymnasts";
+
+export default function GymnastRow({ gymnast }: { gymnast: GymnastSummary }) {
+ return (
+