Projects
Selected work
Projects
Things I have built. The desktop view shows them as an interactive solar system — here is the readable list.
-
HRM
liveFull-stack HR management system
An HR system with the boring parts done properly. Every change is written to an append-only log, and each entry is signed against the one before it. If someone edits the history, an admin endpoint walks the chain and tells you the exact entry where it broke. Two databases, because people data and audit data want different things. Rate limiting runs on Postgres, so there is no Redis to keep alive.
- 2910 tests
- 92.2% coverage
- Tamper-evident audit log
Integrations
- Google OAuth
- GitHub OAuth
- Sentry
- OpenTelemetry
- Next.js
- React
- TypeScript
- PostgreSQL
- MongoDB
- Prisma
- MUI
- Jest
- Playwright
- Docker
- GitHub Actions
- Zod
- NextAuth
- next-intl
- ReactFlow
- Pino
- pg-boss
- Anthropic API
- Stryker
- OpenTelemetry
- Sentry
- Vercel
-
Platform
liveCommunity platform built on HRM
A live community site for a World of Warcraft guild, running at vuohiliitto.com. HRM sits inside it as a git submodule, so accounts, permissions and auditing came for free and the rest got built on top. Members earn XP and levels, chat in tabs with proper whispers, and put together Mythic+ teams by pulling character stats from Raider.IO. New members get a guided tour instead of a wall of buttons.
- Real users
- 1388 tests
- HRM as a submodule
Integrations
- Raider.IO API
- Google OAuth
- GitHub OAuth
- GitHub API
- Turborepo
- Next.js
- React
- TypeScript
- PostgreSQL
- Prisma
- NextAuth
- MUI
- Playwright
- Jest
- next-intl
- Zod
- framer-motion
- Vercel
-
Portfolio
wipThis site
The site you are reading. The pages themselves are static, with nothing running behind them, but the terminal on the contact page is wired to a language model on a machine in my house. Ask it something and a FastAPI service answers from my own repositories, retrieving over pgvector with a Finnish language router in front, reached through a Tailscale funnel. The projects page is a solar system you can drag around and the landing page is one particle field that keeps rearranging itself. It is where the motion work happens, so the production apps do not have to carry it.
- Self-hosted LLM on a home GPU
- Answers only from my own corpus
- Static build, no SSR
- Astro
- Three.js
- GSAP
- TypeScript
- Tailwind CSS
- postprocessing
- FastAPI
- pgvector
- fastembed
- lingua
- asyncpg
- Tailscale Funnel
- Docker
- GitHub Actions
- CodeQL
- Vitest
- Playwright
- pytest
- Sentry
-
ReadLog
liveTrack every book you've read
A reading log. Search for a book, say whether you read it on paper, as an e-book or listened to it, give it a rating, done. It asks Open Library and Google Books at the same time and merges the answers, because neither of them has everything. There is a public feed of what people finished recently, with no names attached.
- 90 tests
- Searches two book APIs at once
Integrations
- Open Library
- Google Books
- Google OAuth
- Next.js
- React
- TypeScript
- Prisma
- PostgreSQL
- NextAuth
- MUI
- Jest
- Neon
- Vercel
-
ReadLog .NET
liveReadLog, rebuilt in .NET
The same reading log, written again from scratch in ASP.NET Core to find out what a port actually costs. Razor Pages instead of React, EF Core and SQLite instead of Prisma and Postgres. It runs on Azure free tier, which means it falls asleep after twenty minutes and the first visit is slow. The repo keeps notes on every decision where the .NET way and the Next.js way disagreed.
- Same app, second stack
- Free Azure tier
- Porting notes for every decision
Integrations
- Open Library
- Google Books
- Google OAuth
- .NET 8
- ASP.NET Core
- Razor Pages
- EF Core
- SQLite
- ASP.NET Identity
- Bootstrap 5
- xUnit
- Docker
- GitHub Actions
- Azure App Service
-
AudiobookMaker
wipTurns books into audiobooks
A desktop app that reads a PDF, EPUB or Word file out loud and saves it as an audiobook. Scanned books go through OCR first. Four voice engines to pick from, depending on whether you want it free, offline, or cloned from a short clip of someone speaking. Finnish is the hard part, so it gets its own nineteen-pass cleanup before a word is spoken: Finnish text-to-speech mangles numbers and abbreviations in very predictable ways. Ships as a Windows installer with everything bundled.
- 3000+ tests
- Nineteen-pass Finnish text cleanup
- Voices the story in Spacepotatis
Integrations
- Microsoft Edge-TTS
- Python
- PyMuPDF
- ebooklib
- ocrmypdf
- Tesseract
- num2words
- edge-tts
- Piper
- Chatterbox
- VoxCPM2
- PyTorch
- CustomTkinter
- pydub
- pygame
- ffmpeg
- PyInstaller
- Inno Setup
- GitHub Actions
- Qwen VoiceDesign
- pytest
-
Spacepotatis
liveBrowser shooter: your potato vs the galaxy
A browser game where a potato in a shield bubble shoots bugs. It boots like an old terminal, opens into a 3D solar system you can drag around, then drops you into a top-down fight. All the music comes from strudel-patterns and all the voice from AudiobookMaker, narrated by Grandmom. In May a player lost their save, because the anti-cheat checked that numbers had not grown too fast but never that they had shrunk. That one is written up as an incident runbook.
- ~1170 tests
- Original music and voice
- Has an incident runbook
Integrations
- Google OAuth
- Next.js
- React
- TypeScript
- Phaser 4
- Three.js
- GSAP
- PostgreSQL
- Kysely
- NextAuth
- Tailwind CSS
- Zod
- Neon
- Vercel
- Vitest
-
Strudel Patterns
liveAlgorithmic music, written as code
Music written in Strudel, where a whole track is one JavaScript expression you edit while it is playing. Drums, bass, synth layers and effects live as separate pieces you can stack into new tracks. Nine are written up, and some of those ended up as the Spacepotatis soundtrack and the music on this landing page. Every session is logged next to the git history, so you can see how a track got where it did.
- Nine finished tracks
- Scores Spacepotatis and this site
- Strudel
- JavaScript
- Web Audio API
- Claude Code
-
claude-continue
wipKeeps Claude Code running back to back
Claude Code works in five-hour windows. This waits for one to roll over and starts the next, so there is no dead time while you are asleep. Python with no dependencies at all, running unattended through launchd or Task Scheduler, and it checks whether a session is mid-thought before it types anything. The README is honest that working this way grows the pile of code nobody has reviewed yet.
- No runtime dependencies
- macOS, Windows, WSL, Linux
- Runs unattended
- Python
- Tkinter
- ccusage
- PyInstaller
- launchd
- Task Scheduler
- tmux
- GitHub Actions
-
PasswordManager
wipZero-knowledge password manager in Rust
All the crypto lives in one Rust crate. The CLI, the browser client, the sync server and the Chrome extension all use that same crate, compiled either natively or to WebAssembly, so there is only ever one place to get it wrong. The server only ever sees ciphertext. Unlocking takes about 430 milliseconds on purpose, because the key derivation is tuned well above the library defaults. The README also lists five things it will not protect you from, which seemed more useful than pretending.
- One crypto crate, four clients
- Server sees only ciphertext
- ~430ms unlock, on purpose
Integrations
- Google OAuth
- Tailscale Funnel
- Cloudflare Tunnel
- Rust
- WebAssembly
- Tokio
- SQLite
- Argon2id
- XChaCha20-Poly1305
- wasm-bindgen
- Chrome Extension (MV3)
- JavaScript
- GitHub Actions
- axum
- zeroize
-
claude-agents
liveCheaper models for the boring work
Fourteen Claude Code subagents, each pinned to the cheapest model that can do its job. Reading and reporting goes to Haiku, mechanical edits go to Sonnet, and only design work gets the expensive model. Each one works out what a repo uses on its own, so the same set covers JavaScript, C# and Python without configuration. Two of them exist because a review workflow quietly spent 3.8 million tokens at the wrong price.
- 14 model-pinned agents
- Model and effort both pinned
- MIT licensed
- Claude Code
- Claude Haiku
- Claude Sonnet
- Claude Opus
- Bash
- Markdown
-
Feedback Intelligence
liveReads customer feedback without making things up
Takes messy free-text feedback and turns it into something a manager can act on. The interesting part is how little of it is AI: the model tidies up what people typed and finds themes across a pile of it, and the rest is ordinary code. Alerts are keyword scans. Sentiment is arithmetic. It runs a Finnish model on a machine at home, so hosting costs nothing. Every decision about where AI was allowed is written down, including the four rounds of arguing it back out.
- AI in exactly two places
- Finnish-first, runs locally
- Costs nothing to host
Integrations
- Tailscale Funnel
- .NET 8
- C#
- SQLite
- Ollama
- Poro 2 8B
- Microsoft.Extensions.AI
- xUnit
- Docker
- Node.js
- Azure Static Web Apps
- Azure Functions
- GitHub Actions
- CodeQL