From 9f5ba056ecd70ebd8dc035770fcb8ead80e9761b Mon Sep 17 00:00:00 2001 From: Sinduri Guntupalli Date: Wed, 10 Jun 2026 17:26:44 +0200 Subject: [PATCH 1/9] feat: add solution pages with a11y and SEO compliance - New route `/adventures/:id/levels/:levelId/solution` with static prerendering - SolutionDetail.tsx: narrative hero, collapsible step cards, sidebar TOC, Codespaces card, outro ending - Solution data pipeline: types.ts, generate-solutions.mjs, barrel index.ts; `prebuild` now runs both generators - Beginner solution for Echoes Lost in Orbit with step-by-step YAML walkthrough and solution images (WebP) - ChallengeDetail: "Solution walkthrough" link surfaced at the bottom of each level - A11y: tabindex=-1 on details elements for fragment-nav focus movement; motion-reduce:transition-none on summary chevron; aria-live region for copy confirmation; blockquote replaced with div[role=note] - SEO: buildSitemapBody emits solution URLs when authored solution files exist; llms.txt includes solution page links; BreadcrumbList JSON-LD on solution pages - Project commands migrated from .claude/skills/ to .claude/commands/; add-solution command added Signed-off-by: Sinduri Guntupalli --- .claude/{skills => commands}/a11y-audit.md | 0 .claude/commands/add-solution.md | 190 ++++ .claude/{skills => commands}/keyboard.md | 0 .claude/{skills => commands}/navigation.md | 0 .../progressive-enhancement.md | 0 .../user-personalization.md | 0 CLAUDE.md | 21 +- README.md | 1 + e2e/smoke.spec.ts | 3 + package.json | 3 +- public/llms.txt | 1 + public/sitemap.xml | 1 + .../beginner-healthy-apps.webp | Bin 0 -> 32908 bytes .../beginner-no-apps.webp | Bin 0 -> 24148 bytes .../beginner-two-apps.webp | Bin 0 -> 47316 bytes react-router.config.ts | 3 + scripts/generate-adventures.mjs | 17 +- scripts/generate-solutions.mjs | 143 +++ .../echoes-lost-in-orbit/beginner.ts | 320 +++++++ src/data/solutions/index.ts | 8 + src/data/solutions/types.ts | 43 + src/index.css | 15 + src/pages/ChallengeDetail.tsx | 23 +- src/pages/SolutionDetail.tsx | 812 ++++++++++++++++++ src/routes.ts | 1 + src/test/prerender.test.ts | 6 + src/test/seo.test.ts | 3 + styleguide.md | 45 + 28 files changed, 1643 insertions(+), 16 deletions(-) rename .claude/{skills => commands}/a11y-audit.md (100%) create mode 100644 .claude/commands/add-solution.md rename .claude/{skills => commands}/keyboard.md (100%) rename .claude/{skills => commands}/navigation.md (100%) rename .claude/{skills => commands}/progressive-enhancement.md (100%) rename .claude/{skills => commands}/user-personalization.md (100%) create mode 100644 public/solutions/echoes-lost-in-orbit/beginner-healthy-apps.webp create mode 100644 public/solutions/echoes-lost-in-orbit/beginner-no-apps.webp create mode 100644 public/solutions/echoes-lost-in-orbit/beginner-two-apps.webp create mode 100644 scripts/generate-solutions.mjs create mode 100644 src/data/solutions/echoes-lost-in-orbit/beginner.ts create mode 100644 src/data/solutions/index.ts create mode 100644 src/data/solutions/types.ts create mode 100644 src/pages/SolutionDetail.tsx diff --git a/.claude/skills/a11y-audit.md b/.claude/commands/a11y-audit.md similarity index 100% rename from .claude/skills/a11y-audit.md rename to .claude/commands/a11y-audit.md diff --git a/.claude/commands/add-solution.md b/.claude/commands/add-solution.md new file mode 100644 index 000000000..0c3761865 --- /dev/null +++ b/.claude/commands/add-solution.md @@ -0,0 +1,190 @@ +--- +name: add-solution +description: > + Generate a structured TypeScript solution file for a challenge. + Accepts any input format: markdown, YAML, HTML, or plain text. + Downloads and converts images to WebP. Produces a file at + src/data/solutions//.ts that matches + the Solution type in src/data/solutions/types.ts. +--- + +# Add Solution Command + +Generate a structured TypeScript solution walkthrough for an OffOn challenge. + +## What this command does + +1. Parses the solution input (any format) into structured steps. +2. Downloads any referenced images and converts them to WebP using `cwebp`. +3. Writes `src/data/solutions//.ts` with the structured content. +4. Runs the generator to update `src/data/solutions/index.ts` and all region patches. +5. Runs the build to verify the output compiles. + +--- + +## Step 0: Get the inputs + +Before doing anything else, confirm with the user: + +| Input | Description | +|---|---| +| Adventure ID | The kebab-case adventure ID (e.g. `echoes-lost-in-orbit`) | +| Level ID | The level ID: `beginner`, `intermediate`, or `expert` | +| Solution content | The raw content to convert (paste, file path, or URL) | + +If any are missing, ask for them now. Do not proceed until all three are provided. + +--- + +## Step 1: Locate the adventure + +Find the adventure in `src/data/adventures/index.ts` or the generated files to confirm the IDs are valid: + +```bash +grep -r "id: \"\"" src/data/adventures/ +``` + +If the adventure or level doesn't exist, stop and report it to the user. + +--- + +## Step 2: Parse the input into structured steps + +Read the input regardless of format (markdown, YAML, HTML, plain text). Extract: + +- **Title** — the solution title (e.g. "Beginner Solution: Broken Echoes"). Never include difficulty emoji. No em dashes. +- **Spoiler warning** — one sentence. Plain text, no markdown. +- **Intro** — one or two sentences. Plain text, no markdown. +- **Context section** (optional) — "Understanding the Setup" or similar. Explains the tooling/architecture before the steps. +- **Steps** — one per numbered objective in the challenge. Each step has: + - `id` — kebab-case, no numbers (e.g. `two-applications`, `isolated-namespaces`) + - `title` — title case, no leading emoji + - `intro` — one to two sentences in plain text + - `body` — array of `SolutionBlock` (see type below) + - `takeaways` — array of plain-text strings, sentence case + - `furtherReading` — array of `{ title, url }` pairs +- **Complete solution** (optional) — the final fixed config or code block + +### SolutionBlock types + +```typescript +| { type: "text"; html: string } // HTML paragraph or list — use md-content rules +| { type: "code"; language: string; title?: string; code: string } +| { type: "image"; src: string; alt: string; caption?: string } +| { type: "callout"; variant: "tip" | "warning" | "info"; html: string } +``` + +**Text blocks:** Write minimal HTML. Use `

`, `