diff --git a/README.md b/README.md
index 6ed377a..67b8746 100644
--- a/README.md
+++ b/README.md
@@ -2,49 +2,72 @@
Your abandoned repos deserve a proper funeral.
-**Live:** [commitmentissues.dev](https://commitmentissues.dev)
+**Live:** [commitmentissues.dev](https://commitmentissues.dev) · Built by [Dot Systems](https://github.com/dotsystemsdevs)
-
-
+[](LICENSE)
+[](https://commitmentissues.dev)
+[](https://nextjs.org)
-Paste a public GitHub URL. Get a shareable **Certificate of Death** — cause of death, last words, repo age, exportable graphics. No account required.
+---
-## Screenshots
+Paste a public GitHub URL. Get a shareable **Certificate of Death** — algorithmic cause of death, last commit as last words, repo age, and exportable graphics. No signup. No account. Completely free.
+
+## Embed your dead repo
-Home:
+Got a dead repo? Add the badge to your README:
-
+[](https://commitmentissues.dev)
-Certificate:
+```markdown
+[](https://commitmentissues.dev/?repo=YOUR_OWNER/YOUR_REPO)
+```
-
+Or embed the full certificate image:
-About:
+```markdown
+[](https://commitmentissues.dev/?repo=YOUR_OWNER/YOUR_REPO)
+```
+
+Both are generated automatically on the certificate page — just hit **Copy** after analyzing your repo.
+
+## Screenshots
-
+
+
+
+
+
## Features
-- **Certificate of Death** — A4-style layout with cause, last words, repo age, and derived stats
-- **Exports** — Multiple aspect ratios (feed, square, story-style) for Instagram, X, Facebook
-- **Mobile share** — Native share sheet on mobile with story-friendly format
-- **Hall of Shame** — Curated leaderboard of famously abandoned repos
-- **Recently Buried** — Live feed of the latest public burials
-- **Chrome extension** — Tombstone badge injected on any GitHub repo page (MVP)
+- **Certificate of Death** — A4 layout with cause of death, last words, repo age, stars, forks, and language
+- **Algorithmic scoring** — `src/lib/scoring.ts` computes a death index from commit activity, archive status, issue count, and time since last push
+- **Export** — PNG downloads in multiple aspect ratios: A4, Instagram (4:5 and 1:1), X/Twitter (16:9), Facebook feed, and Stories (9:16)
+- **Mobile share** — Native share sheet on iOS/Android with a story-formatted image
+- **README badge** — Embed a `⚰ DECLARED DEAD` shields.io badge linking back to the certificate
+- **Certificate embed** — Full certificate image via `/api/certificate-image/[owner]/[repo]` for GitHub READMEs
+- **Recently Buried** — Live scrolling feed of the latest public burials
+- **Famous Casualties** — Curated graveyard of famously abandoned repos
+- **Rate limiting** — Redis-backed per-IP limiting with graceful fallback
+- **Timeout + race condition handling** — AbortController on every GitHub API call
## Tech stack
| | |
|---|---|
| Framework | Next.js 14 (App Router) |
+| Styling | Tailwind CSS + inline styles |
| Fonts | UnifrakturMaguntia, Courier Prime, Inter |
+| Export | `html-to-image`, Canvas API |
+| Certificate image | `next/og` (Satori, Node.js runtime) |
| Hosting | Vercel |
-| Storage | Upstash Redis (counters + recent burials) |
+| Storage | Upstash Redis (rate limiting + recent burials + stats) |
| Data | GitHub public API |
+| Analytics | Vercel Analytics + Plausible |
## Getting started
-Prerequisites: Node 18+
+**Prerequisites:** Node 18+
```bash
git clone https://github.com/dotsystemsdevs/commitmentissues.git
@@ -55,92 +78,110 @@ npm run dev
Open [http://localhost:3000](http://localhost:3000).
-### Environment
+### Environment variables
-Add a GitHub token to raise API rate limits (optional but recommended):
+Create a `.env.local` in the project root:
```env
+# GitHub — optional but strongly recommended (raises API rate limits from 60 to 5000 req/hr)
GITHUB_TOKEN=ghp_yourtoken
+
+# Upstash Redis — optional (enables Recently Buried feed, rate limiting, and buried counter)
+KV_REST_API_URL=https://your-instance.upstash.io
+KV_REST_API_TOKEN=your_token
```
-Generate one at **GitHub → Settings → Developer settings → Personal access tokens**. Fine-grained or classic tokens both work.
+**Without any env vars** the app still works fully — you just get GitHub's unauthenticated rate limits (60 req/hr) and the Recently Buried feed is hidden.
-> **Note:** The Recently Buried feed requires Upstash Redis (`KV_REST_API_URL` + `KV_REST_API_TOKEN`). Without it, the feed is hidden and the buried counter falls back to the historical baseline.
+Generate a GitHub token at **Settings → Developer settings → Personal access tokens**. Fine-grained or classic both work; no special scopes needed for public repo access.
## How we pronounce repos dead
| Step | What happens |
-|------|----------------|
-| Input | You submit a public GitHub URL |
-| Data | The app fetches public metadata via the GitHub API |
-| Score | A death index and narrative are computed in `src/lib/scoring.ts` |
-| Output | Certificate rendered on-screen, exportable as PNG |
-
-Hall of Shame entries are hand-curated; Recently Buried reflects real usage.
-
-## Testing
-
-```bash
-npm test
-```
+|------|-------------|
+| Input | User submits a public GitHub URL |
+| Fetch | App fetches repo metadata + latest commit via GitHub API |
+| Score | `computeDeathIndex()` in `src/lib/scoring.ts` produces a 0–10 death index |
+| Narrative | `determineCauseOfDeath()` picks a cause based on the index and repo signals |
+| Output | Certificate rendered client-side, exportable as high-res PNG |
-## Contributing
-
-- Read `.github/CONTRIBUTING.md` before opening a PR
-- Use the issue and PR templates
-- CI runs lint, tests, and build on pull requests to `master`
+The scoring algorithm weighs: time since last commit, archive status, open issues, fork ratio, star count, and whether the last commit message reads like a final sigh.
## Project structure
-```text
+```
src/
├── app/
-│ ├── page.tsx
-│ ├── about/
+│ ├── page.tsx ← homepage
+│ ├── about/page.tsx ← /about
+│ ├── layout.tsx ← root layout, fonts, analytics, JSON-LD
│ └── api/
-│ ├── repo/
-│ ├── stats/
-│ └── recent/
+│ ├── repo/route.ts ← main analysis endpoint
+│ ├── recent/route.ts ← recently buried feed
+│ ├── random/route.ts ← random dead repo picker
+│ ├── stats/route.ts ← buried counter
+│ ├── badge/[owner]/[repo]/ ← shields.io-compatible badge SVG
+│ └── certificate-image/[owner]/[repo]/ ← OG image for README embeds
├── components/
-│ ├── CertificateCard.tsx
-│ ├── Leaderboard.tsx
-│ ├── SearchForm.tsx
-│ └── LoadingState.tsx
+│ ├── CertificateCard.tsx ← certificate view + all export/share logic
+│ ├── CertificateFixed.tsx ← the actual certificate layout (A4)
+│ ├── SearchForm.tsx ← URL input + random button
+│ ├── RecentlyBuried.tsx ← scrolling marquee feed
+│ ├── Leaderboard.tsx ← Famous Casualties graveyard
+│ ├── LoadingState.tsx ← loading skeleton
+│ ├── ErrorDisplay.tsx ← error + retry UI
+│ ├── PageHero.tsx ← shared hero (emoji, title, subtitle)
+│ ├── SubpageShell.tsx ← shell for /about and future subpages
+│ └── SiteFooter.tsx ← footer
└── lib/
- ├── scoring.ts
- ├── rateLimit.ts
- ├── recentStore.ts
- └── types.ts
-extension/ ← Chrome extension (MV3, load unpacked)
+ ├── scoring.ts ← death index + cause of death logic
+ ├── scoring.test.ts ← scoring unit tests
+ ├── rateLimit.ts ← Redis-backed rate limiting
+ ├── recentStore.ts ← recently buried Redis store
+ └── types.ts ← shared TypeScript types
```
-## Chrome extension (MVP)
+## Running tests
-A MV3 extension lives under `extension/`. It injects a tombstone badge on GitHub repo pages and links to the full certificate.
+```bash
+npm test
+```
+
+Tests cover the scoring algorithm in `src/lib/scoring.test.ts`.
-### Load unpacked in Chrome
+## Contributing
+
+Contributions are welcome. Please read [`.github/CONTRIBUTING.md`](.github/CONTRIBUTING.md) before opening a PR.
-1. Open `chrome://extensions/`
-2. Enable **Developer mode**
-3. Click **Load unpacked** and select the `extension/` subfolder
+- Use the issue templates for bugs and feature requests
+- CI runs lint, tests, and build on every pull request to `master`
+- Keep PRs focused — one thing at a time
-### Test flow
+## Roadmap
-1. Open a GitHub repo page (e.g. `https://github.com/vercel/next.js`)
-2. Verify a tombstone badge appears near the repo header
-3. Click the badge to open the full certificate on `commitmentissues.dev`
-4. Navigate to another repo without a full reload; verify no duplicate badge appears
+Items are loosely prioritized. Community PRs welcome on anything marked **good first issue**.
-If the API is rate-limited or unavailable, the badge falls back to `Reaper busy`.
+### Near-term
+- [ ] Upgrade to Next.js 16 (planned within one month of launch)
+- [ ] Dark mode
+- [ ] `/api/certificate-image` caching layer (currently no Redis cache)
+- [ ] Repo comparison — bury two repos side by side
-## Docs
+### Longer-term
+- [ ] Chrome extension — tombstone badge injected on GitHub repo pages
+- [ ] Language-specific causes of death ("Died of PHP fatigue", "Last seen in CoffeeScript")
+- [ ] Death anniversary emails — opt-in reminders on the date of last commit
+- [ ] API for third-party integrations
-- Release notes: `docs/releases/`
-- Screenshots: `docs/screenshots/`
-- Repository conventions: `docs/repository-conventions.md`
+### Won't do (by design)
+- Private repo support — we don't break into houses
+- Accounts / login — the funeral is free and anonymous
+- Monetization — coffee button stays, paywalls don't
## License
-MIT — see repository license file.
+MIT — see [`LICENSE`](LICENSE).
+
+---
-Built by [Dot Systems](https://github.com/dotsystemsdevs).
+Built with too much free time by [Dot Systems](https://github.com/dotsystemsdevs). If it made you laugh, [keep us alive](https://buymeacoffee.com/commitmentissues).
diff --git a/ROADMAP.md b/ROADMAP.md
index ccc9fa1..c8c8d01 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -43,6 +43,9 @@ Living document. Completed work at the top, upcoming at the bottom. Add new entr
- OG image aligned with live hero (light theme, correct fonts)
- Custom roast line for commitmentissues repo itself
+### Bug fixes
+- Fixed "bury something else →" on error screen — was retrying same broken URL instead of resetting to the form
+
### Data integrity
- Audited all 28 Famous Casualties graveyard entries against live GitHub API
- Fixed `angularjs/angular.js` → `angular/angular.js` (org transferred, old path 404)
diff --git a/next.config.mjs b/next.config.mjs
index 08c806e..96dc693 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -18,6 +18,18 @@ const nextConfig = {
{ key: 'X-XSS-Protection', value: '1; mode=block' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
+ {
+ key: 'Content-Security-Policy',
+ value: [
+ "default-src 'self'",
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com",
+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
+ "font-src 'self' https://fonts.gstatic.com",
+ "img-src 'self' data: blob: https://img.shields.io https://avatars.githubusercontent.com",
+ "connect-src 'self' https://vitals.vercel-insights.com https://va.vercel-scripts.com",
+ "frame-ancestors 'none'",
+ ].join('; '),
+ },
],
},
]
diff --git a/package-lock.json b/package-lock.json
index 6b0d10e..c4a9a2a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1222,9 +1222,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
- "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2028,9 +2028,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
+ "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3490,9 +3490,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
+ "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4945,9 +4945,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6102,9 +6102,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index c627587..dc6e853 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -2,12 +2,12 @@ import type { Metadata } from 'next'
import SubpageShell from '@/components/SubpageShell'
export const metadata: Metadata = {
- title: 'About — commitmentissues',
- description: 'How commitmentissues works: real GitHub data, a death score algorithm, and a healthy dose of dark humor for your abandoned repos.',
+ title: 'About — Commitment Issues | How It Works',
+ description: 'How Commitment Issues works: we analyze your GitHub repo\'s commit history, activity decay, and open issues to assign a cause of death and generate a printable death certificate.',
alternates: { canonical: 'https://commitmentissues.dev/about' },
openGraph: {
- title: 'About — commitmentissues',
- description: 'How commitmentissues works: real GitHub data, a death score algorithm, and a healthy dose of dark humor for your abandoned repos.',
+ title: 'About — Commitment Issues | How It Works',
+ description: 'How Commitment Issues works: we analyze your GitHub repo\'s commit history, activity decay, and open issues to assign a cause of death and generate a printable death certificate.',
url: 'https://commitmentissues.dev/about',
},
}
@@ -62,7 +62,7 @@ export default function AboutPage() {
padding: '20px 18px',
border: '2px solid #1a1a1a',
borderRadius: '0',
- background: '#f2f2f2',
+ background: '#EDE8E1',
}}
>