Personal website and resume. Live at karanshukla.ca.
Most personal sites are a static page with a bio and some project links. This one is a bit weirder.
A widget polls the Last.fm API every 30 seconds and shows whatever is currently playing, including album art, track name, and the top genre tags scraped from the track metadata. If nothing is playing, it falls back to the most recently scanned track. Respects prefers-reduced-motion for the blinking "live" indicator.
Every 3 days, a GitHub Actions workflow runs a Python script that fetches recent commits and their diffs, sends them to the GitHub Models API (Mistral), and gets back a short natural-language summary of what changed. That summary is committed as pulse.json and baked into the static build. No backend, no runtime LLM calls.
Weekly, another workflow runs the full Vitest coverage suite and a Lighthouse CI audit, then a Python script aggregates the numbers into stats.json. That file is also committed and served statically. The sidebar widget reads it at build time, so performance and coverage scores are always up to date without any runtime API calls.
Project cards fetch live star counts, fork counts, primary language, and last-updated timestamps from the GitHub API. The data is fetched at runtime in the browser, so the cards stay current without a redeploy.
Numpad keys 1 through 4 navigate between the main sections of the site. You probably have a numpad. Now you know what to do with it.
| Layer | Tools |
|---|---|
| Framework | React 18 + TypeScript |
| Build | Vite |
| UI | Material UI v9 + Emotion |
| Routing | React Router v7 |
| State | Zustand v5 |
| Testing | Vitest + React Testing Library |
| Linting | ESLint + TypeScript ESLint + Prettier |
| Automation | GitHub Actions + Python scripts |
| AI | GitHub Models API (Mistral) |
karanshukla.ca/
├── src/ # React + TypeScript + Vite app
│ └── data/ # Auto-generated JSON (pulse.json, stats.json)
└── scripts/ # Python scripts run by GitHub Actions
├── summarize.py # Fetches recent commits + diffs, generates pulse.json via GitHub Models
└── generate_stats.py # Reads Vitest coverage + Lighthouse results, generates stats.json
yarn install
yarn dev # http://localhost:5173| Workflow | Schedule | What it does |
|---|---|---|
main.yml |
Push to main |
Build + deploy to GitHub Pages |
pr-check.yml |
Pull requests | Lint, type-check, test |
pulse.yml |
Every 3 days | Fetch recent commits, summarise via Mistral, commit pulse.json |
stats.yml |
Weekly (Mon 02:00 UTC) | Run Vitest coverage + Lighthouse CI, commit stats.json |
standard-site.yml |
Push touching src/data/posts/** |
Publish Standard.site publication/document records to the pds.karanshukla.ca PDS |
pulse.json and stats.json are static files baked into the build at deploy time. The sidebar widgets (GitHubPulse, SiteStatsWidget) read these files at build time with no runtime API calls needed.
Blog posts are published as Standard.site records so they're discoverable in Standard.site readers such as standard-reader.app. Whenever a post is added, edited, or removed under src/data/posts/, standard-site.yml runs scripts/publish_standard_site.py, which:
- Creates the
site.standard.publicationrecord on thepds.karanshukla.caPDS (once) and writes its AT-URI topublic/.well-known/site.standard.publicationfor domain verification. - Creates, updates, or deletes a
site.standard.documentrecord per post, keyed by a content hash tracked insrc/data/standardSiteRecords.jsonso unchanged posts are skipped.
This requires a PDS_APP_PASSWORD repo secret (an app password for the karanshukla.ca PDS account) and, optionally, a PDS_IDENTIFIER secret (handle or DID to log in with - defaults to the karanshukla.ca handle; set it to the account's DID so this keeps working if the handle ever changes). It does not require any changes to how posts are written or rendered on the site itself.
Deployment is automatic via GitHub Actions (.github/workflows/main.yml). Any push to main triggers the workflow, which:
- Installs dependencies with Yarn
- Builds the app with Vite (
yarn build) - Writes a
CNAMEfile withkaranshukla.cainto the build output - Publishes the build to the
gh-pagesbranch usingpeaceiris/actions-gh-pages
GitHub Pages serves the gh-pages branch at the custom domain.
- Last.fm Hook for music scrobbling data
- GitHub Models for LLM-powered commit summaries (Mistral)
- Lighthouse CI for automated performance auditing
- Material UI for components and theming
- shields.io for badges