|
| 1 | +# Architecture Overview |
| 2 | + |
| 3 | +This document explains how the project is structured, how data flows through the app, and the key decisions behind the implementation. |
| 4 | + |
| 5 | +## Stack |
| 6 | + |
| 7 | +- Runtime/UI: React 19 + Radix primitives (custom UI in `src/components/ui`) |
| 8 | +- Styling: Tailwind CSS v4, design tokens via `theme.json` |
| 9 | +- Build: Vite 6 + SWC React plugin, Spark vite plugins |
| 10 | +- DX: TypeScript 5 (no type-check on build for speed), ESLint v9 flat config |
| 11 | +- Spark: `@github/spark` for integrations and the `useKV` persistent state hook |
| 12 | + |
| 13 | +## App bootstrapping |
| 14 | + |
| 15 | +- `index.html` mounts the app into `#root` and includes base CSS. |
| 16 | +- `src/main.tsx` |
| 17 | + - Imports Spark web components: `import "@github/spark/spark"` |
| 18 | + - Renders `<App />` under `react-error-boundary` with a custom fallback (`src/ErrorFallback.tsx`). In dev, errors are rethrown for a better dev experience. |
| 19 | + - Loads global styles: `main.css`, `styles/theme.css`, `index.css`. |
| 20 | + |
| 21 | +## Folder layout |
| 22 | + |
| 23 | +``` |
| 24 | +src/ |
| 25 | + App.tsx # main page; tabs for Achievements/Repositories |
| 26 | + ErrorFallback.tsx # production-only error boundary UI |
| 27 | + components/ # feature and shared UI components |
| 28 | + AchievementCard.tsx |
| 29 | + AchievementDetails.tsx |
| 30 | + RepositoryList.tsx |
| 31 | + UserProfileHeader.tsx |
| 32 | + ui/ # Radix-driven UI primitives styled with Tailwind |
| 33 | + hooks/ |
| 34 | + use-mobile.ts # responsive helper (matchMedia) |
| 35 | + lib/ |
| 36 | + achievements.ts # domain model and static achievement catalog |
| 37 | + utils.ts # helpers (cn()) |
| 38 | + styles/ |
| 39 | + theme.css # CSS variables for theme tokens |
| 40 | +``` |
| 41 | + |
| 42 | +## Data model and flow |
| 43 | + |
| 44 | +- Achievements are defined statically in `src/lib/achievements.ts`. |
| 45 | +- The app resolves a GitHub username in two ways: |
| 46 | + 1) If running inside a Spark-enabled context, `window.spark.user()` is used to prefill the current GitHub user. |
| 47 | + 2) A user can search any username in `UserProfileHeader`. |
| 48 | +- `App.tsx` fetches user data from the public GitHub REST API (`GET /users/:username`). |
| 49 | + - Errors are surfaced using `sonner` toasts (404 vs. generic/network). |
| 50 | + - The data is normalized to `UserData`. |
| 51 | +- Persistence: `useKV` from Spark stores small pieces of state locally |
| 52 | + - `github-user-data` — last loaded user |
| 53 | + - `unlocked-achievements` — ids of achievements the app considers unlocked (simulated) |
| 54 | + |
| 55 | +### Simulated achievement logic |
| 56 | + |
| 57 | +`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. |
| 58 | + |
| 59 | +Note: This is intentionally heuristic; it does not call any private APIs. Real unlock status would require more detailed GitHub events/PR/Dicussions data. |
| 60 | + |
| 61 | +## UI structure |
| 62 | + |
| 63 | +- `App.tsx` renders two top-level tabs: |
| 64 | + - Achievements: grid of `AchievementCard` filtered by All/Unlocked/Locked. |
| 65 | + - Repositories: `RepositoryList` shows user repos with simple sorting; uses public GitHub API (`/users/:username/repos`). |
| 66 | +- `AchievementDetails` is a dialog showing requirements and tips, with progress if partially complete. |
| 67 | +- `UserProfileHeader` renders avatar, progress bar, and the username search. |
| 68 | +- All common primitives (Button, Card, Tabs, Dialog, etc.) live under `src/components/ui` and are styled with Tailwind + Radix. |
| 69 | + |
| 70 | +## Styling & theming |
| 71 | + |
| 72 | +- Tailwind v4 config in `tailwind.config.js` reads tokens from `theme.json` if present. |
| 73 | +- Custom CSS variables (colors, radii, spacing) are referenced in the Tailwind theme. |
| 74 | +- Dark mode is driven by a data attribute: `[data-appearance="dark"]`. |
| 75 | + |
| 76 | +## Error handling |
| 77 | + |
| 78 | +- Global: `react-error-boundary` wraps the tree; dev mode rethrows to surface stacktraces via Vite overlay. In production, an error alert with a "Try Again" button is displayed. |
| 79 | +- Fetch calls: explicit 404 handling with toasts; generic network failures also show toasts. |
| 80 | + |
| 81 | +## Tooling |
| 82 | + |
| 83 | +- Scripts (`package.json`): |
| 84 | + - `dev`: start Vite dev server |
| 85 | + - `build`: `tsc -b --noCheck && vite build` |
| 86 | + - `preview`: preview static build |
| 87 | + - `lint`: ESLint v9 (flat config) |
| 88 | + - `kill`: Linux-only helper (not used on Windows) |
| 89 | +- ESLint: flat config in `eslint.config.js` with `@eslint/js`, `typescript-eslint`, `react-hooks`, and `react-refresh`. |
| 90 | + |
| 91 | +## External dependencies & constraints |
| 92 | + |
| 93 | +- Public GitHub API calls are unauthenticated and subject to rate limits (60/hr per IP). Heavy usage may require a token-proxy or server. |
| 94 | +- Spark-specific functionality: |
| 95 | + - `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. |
| 96 | + |
| 97 | +## Quality gates (current status) |
| 98 | + |
| 99 | +- Build: PASS (Vite build succeeds). Note: CSS optimizer reports warnings for container query-like tokens; these are benign with current setup. |
| 100 | +- Lint: PASS with warnings (hooks exhaustive-deps, fast-refresh guidance). No blocking errors. |
| 101 | +- Tests: Not configured. |
| 102 | + |
| 103 | +## Opportunities / recommendations |
| 104 | + |
| 105 | +1. Achievement engine |
| 106 | + - Move from simulated unlocks to real checks by calling relevant GitHub endpoints (PRs, issues, discussions, stars). Cache results via `react-query` (`@tanstack/react-query` already present) with per-user keys. |
| 107 | + - Define per-achievement `checkProgress(user)` functions directly in `achievements.ts` for co-located logic. |
| 108 | + |
| 109 | +2. State & data |
| 110 | + - Adopt `react-query` for fetches (users, repos), retries, cache, and loading states. Keeps `App.tsx` slimmer and handles race conditions. |
| 111 | + - Persist last-searched username in `useKV` for better UX. |
| 112 | + |
| 113 | +3. Routing |
| 114 | + - Consider adding client-side routing (e.g., `/user/:login`) for shareable links to a profile and deep-linking to an achievement via query params. |
| 115 | + |
| 116 | +4. DX |
| 117 | + - Keep `eslint.config.js` (added) and optionally add Prettier or formatting rules. |
| 118 | + - The `kill` script is Linux-only; consider removing or guarding for Windows environments. |
| 119 | + |
| 120 | +5. Performance |
| 121 | + - Split the Achievements grid via code-splitting or windowing if the catalog grows large. |
| 122 | + - Memoize derived lists (`filteredAchievements`) and progress calculations if they become expensive. |
| 123 | + |
| 124 | +6. A11y |
| 125 | + - Radix provides a strong base; ensure all interactive controls have labels and keyboard focus states (most are covered). Consider testing with Axe. |
| 126 | + |
| 127 | +7. Testing |
| 128 | + - 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 `simulateUnlockedAchievements` and any future `checkProgress` functions. |
| 129 | + |
| 130 | +8. API limits & resiliency |
| 131 | + - Show remaining rate-limit in the UI if detected; back off with friendly messaging. |
| 132 | + - Optional: add a tiny proxy/server for authenticated requests when needed. |
| 133 | + |
| 134 | +## Key contracts |
| 135 | + |
| 136 | +- `UserData`: minimal normalized user object used across components. |
| 137 | +- `Achievement`: |
| 138 | + - id, name, description, tier, icon, category, requirements, tips |
| 139 | + - optional `checkProgress(userData) -> { progress, current, target, unlocked }` |
| 140 | + |
| 141 | +## Edge cases considered |
| 142 | + |
| 143 | +- Unknown user (404) |
| 144 | +- Network failures |
| 145 | +- No public repos / no topics |
| 146 | +- Non-Spark environment (no `window.spark`) — app still usable via manual search |
| 147 | + |
| 148 | +--- |
| 149 | + |
| 150 | +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. |
0 commit comments