
Github Grader
An Apple Fitness–style dashboard for developers that visualizes your GitHub activity, daily coding stats, and repository health. Built with SvelteKit, it uses GitHub OAuth and Octokit APIs to aggregate commits, lines of code, and repo metadata into interactive SVG “activity rings” and stat cards.
Overview
I built github-grader as a SvelteKit web app that turns GitHub activity into something that feels more like Apple Fitness rings than a traditional developer dashboard. Instead of raw lists of repositories and commits, the app visualizes daily programming milestones—commits, lines of code, and file changes—as progress rings and simple achievement-style components.
The goal was to explore how to make engineering progress feel more tangible and motivating, while giving me hands-on experience with GitHub OAuth, the Octokit SDK, and SvelteKit’s server-side API routes.
Role & Context
I designed and implemented this project end-to-end:
- Defined the concept and UX direction (Apple Fitness–inspired “rings” for coding activity).
- Implemented GitHub OAuth in a SvelteKit environment with secure cookies.
- Built server-side endpoints to aggregate GitHub activity data.
- Built the dashboard UI with reusable Svelte components for repos, medals, and the activity ring.
- Set up basic testing, linting, and formatting for a healthy development workflow.
This is a personal project intended both as a learning vehicle and as a foundation I can extend into a more fully featured “programming fitness” app.
Tech Stack
- Svelte
- SvelteKit
- JavaScript (ES modules)
- HTML / CSS
- Vite
- Octokit (GitHub REST API)
- Playwright (basic E2E tests)
- Prettier + prettier-plugin-svelte
- dotenv
- Node.js
Problem
Developer dashboards often present GitHub data in a way that’s useful but not especially motivating: lists of repos, raw commit counts, and activity feeds. I wanted something that:
- Felt visually rewarding and quick to interpret at a glance.
- Focused on daily progress, not long-term vanity metrics.
- Used authentic GitHub data (repos, commits, code changes) but surfaced it in a way that feels like closing rings in Apple Fitness.
Concretely, I needed to solve:
- How to authenticate with GitHub and keep the user session secure.
- How to aggregate today’s activity from GitHub (commits, lines, file changes).
- How to transform repo/activity data into a compact dashboard that encourages regular programming “streaks.”
Approach / Architecture
I used SvelteKit’s file-based routing to separate concerns cleanly between:
- Auth routes (
/api/auth/*) for GitHub OAuth and user session management. - GitHub data routes (
/api/github/*) for repository and daily activity queries. - UI routes for the public landing page (
/) and the authenticated dashboard (/dashboard).
High-level architecture:
Frontend (Svelte/SvelteKit)
+layout.sveltehandles global navigation and checks the current user via/api/auth/user.+page.svelte(root) is a simple welcome page prompting sign-in.dashboard/+page.sveltefetches repos and renders the “fitness-inspired” view with an activity ring and repo cards.
Auth flow
/api/auth/githubredirects to GitHub’s OAuth authorize endpoint./api/auth/callbackexchanges thecodefor an access token, fetches the authenticated user via Octokit, and stores both user and token in HTTP-only cookies./api/auth/userreturns the user from a secure cookie for client-side checks./api/auth/signoutclears the cookies.
Data aggregation
/api/github/repositoriesuses Octokit to fetch recent repositories and enrich them with pseudo-commit counts, language-based size, and other metadata, returning a compact list for the dashboard./api/github/daily-activitycomputes “rings metrics” (commits, total lines changed, and file size changes) for today using GitHub’s search and commit APIs, then returns a single summary object.
On the client, components like DailyActivityWheel.svelte, Ring.svelte, and Repo.svelte transform this data into the visual “fitness” metaphor.
Key Features
- GitHub OAuth sign-in with secure, HTTP-only cookie-based sessions.
- Apple Fitness–style circular progress visualization of daily coding activity.
- Aggregated “today” metrics: commit count, lines of code changed, and file change volume.
- Repository grid with per-repo stats (pseudo-commit count and language-based size).
- Personalized dashboard greeting plus dynamic encouragement messages based on progress.
- Reusable Svelte components for rings, repos, and medals/achievements.
- Basic end-to-end test coverage for the core landing page.
Technical Details
Authentication and Session Handling
I implemented OAuth using GitHub’s standard flow and SvelteKit server routes:
Auth redirect:
src/routes/api/auth/github/+server.jsconstructs the GitHub OAuth URL usingGITHUB_CLIENT_IDfrom$env/static/privateand redirects the user.- A static
REDIRECT_URIpoints back to/api/auth/callbackduring local development.
Callback and token exchange:
src/routes/api/auth/callback/+server.js:- Reads the
codequery param. - POSTs to
https://github.com/login/oauth/access_tokenwithclient_id,client_secret, andcode, requesting JSON. - Extracts the
access_tokenfrom the response. - Uses
Octokitwith that token to callusers.getAuthenticated()and retrieve user info.
- Reads the
Cookie storage:
- Stores the serialized user object as
github_userand the access token asgithub_token:httpOnly: trueto mitigate XSS.secureconditioned onNODE_ENV === 'production'.maxAgeset to one week.
- After setting cookies, redirects to
/dashboard.
- Stores the serialized user object as
Session endpoints:
api/auth/user: readsgithub_userfrom cookies, returns401if absent.api/auth/signout: deletesgithub_userandgithub_tokencookies, returning200.
On the client, +layout.svelte calls /api/auth/user on mount to hydrate the navigation with the current user and to show either “Dashboard / Sign Out” or “Sign In with GitHub.”
GitHub Data Fetching and Aggregation
I used both the Octokit library and a small helper wrapper around fetch:
src/lib/github.jsexposes:fetchRepoData(username, token)fetchRepoDetails(owner, repo, token)fetchCommits(owner, repo, token)
These are lower-level helpers, but for the dashboard I rely on dedicated API routes built with Octokit.
Repositories API
src/routes/api/github/repositories/+server.js:
Reads
github_tokenfrom cookies; returns401if missing.Creates an
Octokitinstance:new Octokit({ auth: token }).Calls
octokit.rest.repos.listForAuthenticatedUser({ sort: 'updated', per_page: 10 })for the 10 most recently updated repositories.For each repo:
- Fetches a page of commits via
repos.listCommits({ per_page: 1 }):- Uses the first commit’s SHA and converts it into a pseudo-count:
commitCount = commits[0]?.sha ? parseInt(commits[0].sha, 16) % 1000 : 0.
This is intentionally approximate and mainly for prototype visualization.
- Uses the first commit’s SHA and converts it into a pseudo-count:
- Fetches language breakdown via
repos.listLanguages():- Sums the byte counts across languages to get
totalSize. - Converts to kilobytes:
Math.round(totalSize / 1024).
- Sums the byte counts across languages to get
- Fetches a page of commits via
Returns a simplified object for each repo:
{
name: repo.name,
description: repo.description || 'No description provided',
commits_count: commitCount,
size: Math.round(totalSize / 1024),
url: repo.html_url,
language: repo.language,
stars: repo.stargazers_count
} The client maps this directly into <Repo /> components.
Daily Activity API
src/routes/api/github/daily-activity/+server.js powers the fitness-style rings:
- Reads
github_tokenfrom cookies; returns401if absent. - Creates an
Octokitinstance. - Determines today’s date string (
YYYY-MM-DD). - Gets the authenticated user via
users.getAuthenticated()to obtain their login. - Uses GitHub’s commit search API:
const { data: commits } = await octokit.rest.search.commits({
q: `author-date:${today} author:${user.login}`,
sort: 'author-date',
order: 'desc',
per_page: 100
}); - Initializes aggregate counters:
totalCommitsfromcommits.total_count.totalLinesAdded,totalLinesRemoved,totalFileSizeall start at 0.
- For each commit in
commits.items, callsrepos.getCommitto compute:- Sum of
stats.additionsandstats.deletions. - Sum of
file.changesacross all files in the commit.
- Sum of
- Returns:
{
commits: totalCommits,
linesOfCode: totalLinesAdded + totalLinesRemoved,
fileSize: totalFileSize
} This compact structure is ideal for rendering progress rings.
Visualizations & Components
Activity Ring
src/lib/components/Ring.svelte is a generic, multi-layer circular progress component:
- Props:
size,strokeWidth,backgroundColor,showBackground.layers: array of{ startColor, endColor, progress }.
- Derived values:
radius,normalizedRadius, andcircumference.
- Offsets:
calculateOffset(progress)computesstroke-dashoffsetfrom a percentage.- Each ring layer uses
stroke-dasharray/stroke-dashoffsetwith atransform: rotate(-90deg)to start at the top.
- Uses
<defs>and multiple<linearGradient>elements for layered gradients. - Animates via
transition: stroke-dashoffset 1s ease-in-out.
DailyActivityWheel.svelte wires this to daily activity metrics:
- On mount:
- Fetches
/api/github/daily-activity. - Handles loading and error states explicitly.
- Fetches
- Derives progress from task-based “goals” (hard-coded for now):
commitProgress = min((commits / 10) * 100, 100).linesProgress = min((linesOfCode / 1000) * 100, 100).fileSizeProgress = min((fileSize / 10000) * 100, 100).
- Passes three layers to
Ring.svelte, each with its own color pair.
This gives me an extensible visual foundation that can represent any metric as a ring.
Repo Cards
src/lib/components/Repo.svelte displays key repo stats:
- Inputs:
title,description,commits,size. - Derives
sizeFormattedas KB or MB based on the numeric size. - Uses a card-style layout with:
- Hover transform (
translateY(-5px)) and shadow changes. - Truncated multi-line descriptions using
-webkit-line-clamp.
- Hover transform (
The dashboard renders a grid of these cards, each bound to a single item from /api/github/repositories.
Dashboard
src/routes/dashboard/+page.svelte:
- Fetches repos from
/api/github/repositorieson mount. - Holds a
ringsplaceholder array and uses a helpergetEncouragingPhrase(rings)to calculate a motivational message based on average progress. - Layout:
- Header section with:
- Greeting:
Welcome, {userName}!(currently a placeholder; can be wired to cookie user). - Encouraging phrase.
- Activity wheel component (
<DailyActivityWheel />).
- Greeting:
- A “Your GitHub Repositories” section with a responsive grid of
<Repo />cards.
- Header section with:
Tooling & Configuration
- Vite: SvelteKit plugin and a simple
defineoverride forprocess.env. - Playwright: A minimal test to assert the presence of the landing page
<h1>. - Prettier + Svelte plugin: Ensures code formatting, with a
.prettierrctuned for tabs and single quotes. - Environment management:
.envfiles are ignored; sensitive GitHub credentials are accessed via$env/static/private.
Results
- Delivered a working SvelteKit dashboard that:
- Authenticates with GitHub via OAuth.
- Persists sessions with secure HTTP-only cookies.
- Aggregates and visualizes daily coding activity.
- Presents a readable, motivational overview of a developer’s GitHub work.
- Created a reusable SVG-based ring component suitable for future “fitness-like” dashboards.
- Established a clean separation between auth, data APIs, and presentation components, making future iterations (e.g., badges, streaks, goals) straightforward.
Lessons Learned
- SvelteKit’s server routes and cookies provide a clean model for implementing OAuth without needing a separate backend.
- GitHub’s search and commit APIs are powerful but can be expensive; iterating over commits to gather detailed stats requires attention to pagination and performance when scaling beyond a prototype.
- Data shaping on the server (e.g., pre-aggregating and normalizing fields) greatly simplifies the client, especially when building visual metaphors like progress rings.
- SVG-based components like
Ring.sveltebenefit from thoughtful abstractions—once the math and gradients were right, reusing the component for multiple layers and metrics became trivial. - Tight tooling integration (Prettier, Playwright, strict
.npmrc) improved developer experience even for a small personal project.