This document explains how the project is structured, how data flows through the app, and the key decisions behind the implementation.
- Runtime/UI: React 19 + Radix primitives (custom UI in
src/components/ui) - Styling: Tailwind CSS v4, design tokens via
theme.json - Build: Vite 6 + SWC React plugin, Spark vite plugins
- DX: TypeScript 5 (no type-check on build for speed), ESLint v9 flat config
- Spark:
@github/sparkfor integrations and theuseKVpersistent state hook
index.htmlmounts the app into#rootand includes base CSS.src/main.tsx- Imports Spark web components:
import "@github/spark/spark" - Renders
<App />underreact-error-boundarywith a custom fallback (src/ErrorFallback.tsx). In dev, errors are rethrown for a better dev experience. - Loads global styles:
main.css,styles/theme.css,index.css.
- Imports Spark web components:
src/
App.tsx # main page; tabs for Achievements/Repositories
ErrorFallback.tsx # production-only error boundary UI
components/ # feature and shared UI components
AchievementCard.tsx
AchievementDetails.tsx
RepositoryList.tsx
UserProfileHeader.tsx
ui/ # Radix-driven UI primitives styled with Tailwind
hooks/
use-mobile.ts # responsive helper (matchMedia)
lib/
achievements.ts # domain model and static achievement catalog
utils.ts # helpers (cn())
styles/
theme.css # CSS variables for theme tokens
- Achievements are defined statically in
src/lib/achievements.ts. - The app resolves a GitHub username in two ways:
- If running inside a Spark-enabled context,
window.spark.user()is used to prefill the current GitHub user. - A user can search any username in
UserProfileHeader.
- If running inside a Spark-enabled context,
App.tsxfetches user data from the public GitHub REST API (GET /users/:username).- Errors are surfaced using
sonnertoasts (404 vs. generic/network). - The data is normalized to
UserData.
- Errors are surfaced using
- Persistence:
useKVfrom Spark stores small pieces of state locallygithub-user-data— last loaded userunlocked-achievements— ids of achievements the app considers unlocked (simulated)
simulateUnlockedAchievements(user) in App.tsx marks some achievements as unlocked based on simple heuristics (public repos, followers, account age). getAchievementProgress derives a percentage for each item to show partial progress.
Note: This is intentionally heuristic; it does not call any private APIs. Real unlock status would require more detailed GitHub events/PR/Dicussions data.
App.tsxrenders two top-level tabs:- Achievements: grid of
AchievementCardfiltered by All/Unlocked/Locked. - Repositories:
RepositoryListshows user repos with simple sorting; uses public GitHub API (/users/:username/repos).
- Achievements: grid of
AchievementDetailsis a dialog showing requirements and tips, with progress if partially complete.UserProfileHeaderrenders avatar, progress bar, and the username search.- All common primitives (Button, Card, Tabs, Dialog, etc.) live under
src/components/uiand are styled with Tailwind + Radix.
- Tailwind v4 config in
tailwind.config.jsreads tokens fromtheme.jsonif present. - Custom CSS variables (colors, radii, spacing) are referenced in the Tailwind theme.
- Dark mode is driven by a data attribute:
[data-appearance="dark"].
- Global:
react-error-boundarywraps the tree; dev mode rethrows to surface stacktraces via Vite overlay. In production, an error alert with a "Try Again" button is displayed. - Fetch calls: explicit 404 handling with toasts; generic network failures also show toasts.
- Scripts (
package.json):dev: start Vite dev serverbuild:tsc -b --noCheck && vite buildpreview: preview static buildlint: ESLint v9 (flat config)kill: Linux-only helper (not used on Windows)
- ESLint: flat config in
eslint.config.jswith@eslint/js,typescript-eslint,react-hooks, andreact-refresh.
- Public GitHub API calls are unauthenticated and subject to rate limits (60/hr per IP). Heavy usage may require a token-proxy or server.
- Spark-specific functionality:
window.spark.user()is available only in Spark-aware environments. The app guards this with try/catch and works without it by using manual search.
- Build: PASS (Vite build succeeds). Note: CSS optimizer reports warnings for container query-like tokens; these are benign with current setup.
- Lint: PASS with warnings (hooks exhaustive-deps, fast-refresh guidance). No blocking errors.
- Tests: Not configured.
-
Achievement engine
- Move from simulated unlocks to real checks by calling relevant GitHub endpoints (PRs, issues, discussions, stars). Cache results via
react-query(@tanstack/react-queryalready present) with per-user keys. - Define per-achievement
checkProgress(user)functions directly inachievements.tsfor co-located logic.
- Move from simulated unlocks to real checks by calling relevant GitHub endpoints (PRs, issues, discussions, stars). Cache results via
-
State & data
- Adopt
react-queryfor fetches (users, repos), retries, cache, and loading states. KeepsApp.tsxslimmer and handles race conditions. - Persist last-searched username in
useKVfor better UX.
- Adopt
-
Routing
- Consider adding client-side routing (e.g.,
/user/:login) for shareable links to a profile and deep-linking to an achievement via query params.
- Consider adding client-side routing (e.g.,
-
DX
- Keep
eslint.config.js(added) and optionally add Prettier or formatting rules. - The
killscript is Linux-only; consider removing or guarding for Windows environments.
- Keep
-
Performance
- Split the Achievements grid via code-splitting or windowing if the catalog grows large.
- Memoize derived lists (
filteredAchievements) and progress calculations if they become expensive.
-
A11y
- Radix provides a strong base; ensure all interactive controls have labels and keyboard focus states (most are covered). Consider testing with Axe.
-
Testing
- Add Vitest + React Testing Library to validate rendering and critical flows (user search, repo list, details dialog open/close). Keep a couple of unit tests for
simulateUnlockedAchievementsand any futurecheckProgressfunctions.
- Add Vitest + React Testing Library to validate rendering and critical flows (user search, repo list, details dialog open/close). Keep a couple of unit tests for
-
API limits & resiliency
- Show remaining rate-limit in the UI if detected; back off with friendly messaging.
- Optional: add a tiny proxy/server for authenticated requests when needed.
UserData: minimal normalized user object used across components.Achievement:- id, name, description, tier, icon, category, requirements, tips
- optional
checkProgress(userData) -> { progress, current, target, unlocked }
- Unknown user (404)
- Network failures
- No public repos / no topics
- Non-Spark environment (no
window.spark) — app still usable via manual search
If you have questions or want to iterate on the achievement engine (real data integration), see the recommendations above; happy to help wire it up.