Turn your changelog into a game - A retro Space Invaders-style arcade game React component
npm install changelog-invaders> Your changelog is boring. Let's fix that.
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ WHAT IF YOUR USERS ACTUALLY LOOKED FORWARD TO โ
โ READING YOUR CHANGELOG? โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Drop this React component into your app and watch your changelog transform into a full arcade game where users blast through bugs, collect power-ups, and fly through stargate portals representing your version milestones.
8-bit chiptune soundtrack included. ๐ต


---
Blast through bugs, collect power-ups, fly through version stargates!
Try it right now on the Right Click Prompt changelog page โ no install needed!
---
`bash`
npm install changelog-invaders
Create a new file like app/changelog/page.tsx (Next.js) or src/pages/Changelog.tsx (Vite/CRA):
`tsx
import { ChangelogInvaders } from 'changelog-invaders'
export default function ChangelogPage() {
return (
versions={["v1.0.0", "v1.1.0", "v2.0.0"]}
/>
)
}
`
Visit your changelog page and start playing. High scores save automatically.
---
- ๐ฎ Users will actually visit your changelog โ Nobody reads changelogs. Everyone plays games.
- ๐ Built-in engagement โ High scores make users come back
- ๐ Ship your versions as literal stargates โ v1.0 โ v2.0 becomes an epic journey
- ๐ Make bug fixes satisfying โ Your users get to literally shoot the bugs you fixed
- โก 5 minutes to integrate โ One component, zero config required
---
Originally created by Kamil Banc for Right Click Prompt and open-sourced for the community.
- ๐ฎ Full arcade experience - Complete game with shooting, power-ups, and boss gates
- ๐ต Procedural chiptune audio - Heavy metal-inspired 8-bit music and sound effects
- ๐จ Retro pixel art - CRT screen effects, scanlines, and pixel-perfect graphics
- ๐ Stargate portals - Fly through version gates to progress
- ๐ High scores - Works offline with localStorage, no database required
- โ๏ธ Fully customizable - Colors, sprites, enemies, power-ups, and more
- ๐ฑ Responsive - Works on any screen size
- ๐ Audio toggle - Players can mute/unmute with M key
- ๐ Zero dependencies - Only requires React, no database or backend needed
`bash`
npm install changelog-invaders
`tsx
import { ChangelogInvaders } from 'changelog-invaders'
export default function ChangelogPage() {
return (
versions={["v1.0.0", "v1.1.0", "v1.2.0", "v2.0.0"]}
/>
)
}
`
That's literally it. Three lines of config. Your changelog is now a game with:
- Generic spaceship sprite
- Default color palette (blue/green/purple/amber)
- localStorage high scores
- Full procedural audio engine
- All game mechanics
`tsx
gameTitle="ACME ODYSSEY" // Title on the start screen
versions={["v1.0", "v2.0", "v3.0"]} // Version gates to fly through
// Optional branding
gameSubtitle="โ JOURNEY TO V3 โ" // Subtitle below title
/>
`
`tsx`
versions={["v1.0", "v2.0"]}
colors={{
bug: "#ff6b6b", // Blue enemies
feature: "#4ecdc4", // Green enemies
improvement: "#ffe66d", // Purple enemies
breaking: "#ff8c42", // Amber enemies
accent: "#6c5ce7" // UI accent (bullets, etc.)
}}
/>
`tsx`
versions={["v1.0", "v2.0"]}
enemyItems={[
{ type: "bug", text: "CRASH BUG" },
{ type: "bug", text: "MEMORY LEAK" },
{ type: "feature", text: "SCOPE CREEP" },
{ type: "improvement", text: "TECH DEBT" },
{ type: "breaking", text: "BREAKING!" },
]}
powerUpItems={[
{ type: "weapon", text: "SHIPPED!" },
{ type: "weapon", text: "DEPLOYED!" },
{ type: "shield", text: "TESTED!" },
]}
/>
> Note: The game works perfectly fine without any backend. High scores are automatically saved to localStorage and persist across sessions. You only need a backend if you want a global leaderboard shared across all users.
If you want a global leaderboard, provide an API endpoint:
`tsx`
versions={["v1.0", "v2.0"]}
highscoresEndpoint="/api/game/highscores" // Only if you want global scores
/>
Your API should implement:
- GET /api/game/highscores โ Returns { highScores: HighScore[] }POST /api/game/highscores
- โ Accepts { playerName, score, gatesPassed, maxSpeed }
See the Backend Setup section for a Supabase example (or use any backend you prefer).
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| gameTitle | string | required | Game title on start screen |versions
| | string[] | required | Version gates to fly through |gameSubtitle
| | string | "" | Subtitle below title |colors
| | Partial | Default palette | Custom color scheme |shipSprite
| | string[] | Generic ship | Custom ship pixel art |shipColors
| | ShipColors | Default colors | Color mapping for ship |enemyItems
| | EnemyItem[] | Default items | Custom enemy definitions |powerUpItems
| | PowerUpItem[] | Default items | Custom power-up definitions |highscoresEndpoint
| | string | undefined | API endpoint for scores |localStorageKey
| | string | "changelog-invaders-high-scores" | Local storage key |enableSound
| | boolean | true | Enable/disable audio |enableLeaderboard
| | boolean | true | Show leaderboard panel |height
| | number | 280 | Canvas height in pixels |
| Key | Action |
|-----|--------|
| โ / A | Move left |โ
| / D | Move right |Space
| | Shoot |M
| | Toggle mute |Esc
| | Return to demo |
1. Shoot Bugs - Destroy enemy bugs for points
2. Collect Power-ups - Lightning bolts upgrade your weapons (up to level 5)
3. Fly Through Gates - Pass through version stargates for speed bonuses
4. Don't Let Bugs Escape - Penalties increase for each escaped bug
5. Reach the Final Version - Complete the journey to win!
- Bugs: 10-20 points (รweapon level)
- Power-ups: 100 points (รweapon level)
- Gates: 150 points (รspeed multiplier)
- Time survival: 1 point every 0.5 seconds
- Movement multiplier: Up to 5ร for continuous movement
- Victory bonus: 5000 points
> You don't need this section unless you want a global leaderboard. The game works completely offline with localStorage for high scores.
1. Create a migration file:
`sql
-- supabase/migrations/001_high_scores.sql
CREATE TABLE game_high_scores (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
player_name VARCHAR(10) NOT NULL,
score INTEGER NOT NULL,
gates_passed INTEGER DEFAULT 0,
max_speed DECIMAL(4,2) DEFAULT 1.0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_high_scores_score ON game_high_scores(score DESC);
-- Row Level Security
ALTER TABLE game_high_scores ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can read high scores"
ON game_high_scores FOR SELECT
TO anon, authenticated
USING (true);
CREATE POLICY "Anyone can insert high scores"
ON game_high_scores FOR INSERT
TO anon, authenticated
WITH CHECK (true);
`
2. Create API routes (Next.js example):
`typescript
// app/api/game/highscores/route.ts
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!
)
export async function GET() {
const { data, error } = await supabase
.from('game_high_scores')
.select('id, player_name, score, gates_passed, max_speed, created_at')
.order('score', { ascending: false })
.limit(10)
if (error) {
return Response.json({ error: error.message }, { status: 500 })
}
const highScores = data.map((row, index) => ({
...row,
rank: index + 1
}))
return Response.json({ highScores })
}
export async function POST(request: Request) {
const body = await request.json()
const { playerName, score, gatesPassed, maxSpeed } = body
const { data, error } = await supabase
.from('game_high_scores')
.insert({
player_name: playerName.substring(0, 10),
score,
gates_passed: gatesPassed,
max_speed: maxSpeed
})
.select()
.single()
if (error) {
return Response.json({ error: error.message }, { status: 500 })
}
// Check if it's top 10
const { count } = await supabase
.from('game_high_scores')
.select('*', { count: 'exact', head: true })
.gt('score', score)
const rank = (count || 0) + 1
const isTop10 = rank <= 10
return Response.json({ success: true, isTop10, rank })
}
`
Create your own ship using pixel art strings:
`tsx
const myShip = [
"........BB........", // B = Black outline
".......BWWB.......", // W = White
"......BWWWWB......", // R = Red/accent
".....BWWRRWWB.....", // G = Gray
"....BWWWWWWWWB....", // . = Transparent
"...BWWWWWWWWWWB...",
"..BWWWWWWWWWWWWB..",
".BWWWWWWWWWWWWWWB.",
"BWWWWWWWWWWWWWWWWB",
"BWWWGWWWWWWWWGWWWB",
"BWWWGWWWWWWWWGWWWB",
".BWWGWWWWWWWWGWWB.",
"..BWGGWWWWWWGGWB..",
"...BGGGWWWWGGGB...",
"....BGGGGGGGB.....",
".....BBBBBBB......",
]
versions={["v1.0", "v2.0"]}
shipSprite={myShip}
shipColors={{
B: "#000000",
W: "#ffffff",
R: "#ff0000",
G: "#666666",
}}
/>
`
Full TypeScript support with exported types:
`typescript
import {
ChangelogInvaders,
ChangelogInvadersConfig,
InvaderType,
PowerUpType,
ColorPalette,
EnemyItem,
PowerUpItem,
} from 'changelog-invaders'
const config: ChangelogInvadersConfig = {
gameTitle: "MY APP",
versions: ["v1.0", "v2.0"],
// ... fully typed
}
`
- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
Requires:
- Web Audio API (for sound)
- Canvas 2D API (for graphics)
- ResizeObserver (for responsive sizing)
Contributions are welcome! Please read our contributing guidelines first.
`bashClone the repo
git clone https://github.com/kbanc85/changelog-invaders.git
cd changelog-invaders
License
MIT ยฉ Kamil Banc
Credits
Originally built for Right Click Prompt - a prompt management tool for AI workflows.
Created by Kamil Banc โ follow for more developer tools:
- ๐ฆ X/Twitter: @kamilbanc
- ๐ป GitHub: @kbanc85
---
Built with โค๏ธ for developers who believe changelogs should be fun.
`
๐พ PEW PEW PEW ๐พ
``