This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend
What I Built
A lot of my friends build their own tools. They hit a problem and just make the thing they want. I wanted to build something for someone else this time, and the answer was sitting right next to me.
My husband has wanted to learn vim for a long time. He's tried a bunch of times, and it always ends the same way: he gives up. When I asked him why, it came down to two things. There are too many commands to memorize, and everything feels slow and awkward compared to what he's used to.
So I built hjkl, a browser app that teaches vim in tiny drills.
Each drill gives you a small buffer, a goal, and a keystroke "par" to beat. You solve it in a real vim editor, not a simulation. A few things I designed around his actual problems:
- Only a few commands at a time. Each lesson introduces a small set of commands, with a cheat sheet always on screen.
-
Every command shows the VS Code habit it replaces.
ddis Ctrl+Shift+K,ois Ctrl+Enter,ciwis roughly Ctrl+D and type. If there isn't a real equivalent (like.orf), it just says so. - Par instead of pass/fail. You always finish the drill. If you took the long way, you see the shorter answer right there.
- A Review list. Any drill you solved over par goes into Review, worst first, and drops off once you hit par. Those are exactly the commands that haven't stuck yet.
- A full Vim reference. The lesson cheat sheet only shows what you're currently learning, but there's also an All Commands reference with everything covered in the app. You can search it by key, action, or the VS Code shortcut you're used to.
-
Plain names under weird keys.
<CR>shows "Enter" underneath and<Esc>shows "Escape". I added this after I had to ask what<CR>meant myself while testing.
Right now there are 17 lessons and 124 hand-written drills, starting with basic movement and insert mode and working up through text objects, counts and combos, search, visual mode, substitutions, macros, registers, and more.
Then there's the AI part, which runs entirely in the browser.
When I handed it over to him, he actually sat there and worked through it for a while, which felt like a pretty good sign considering every previous attempt at learning vim had ended with him giving up. He liked the short drills and being able to see the more efficient answer when he took the long way. More importantly, he kept going without me having to convince him to.
Demo
Live: https://hjkl.pinkpixel.dev
You'll want a desktop browser with a physical keyboard (vim on a phone keyboard is not a good time). The AI coach needs WebGPU.
Code
hjkl is a browser app for learning vim through short drills. Each drill gives you a tiny buffer, a goal, and a keystroke par to beat, and you solve it in a real vim editor.
I'm building it for my husband. He's tried to learn vim a few times, but it always ends the same way: too many commands to remember, and everything feels slow and awkward compared to what his hands already know. So instead of one long tutorial, hjkl teaches two or three commands at a time, shows the VS Code style habit each one replaces, and lets you practice until it sticks.
Try it at https://hjkl.pinkpixel.dev
What's in it right now
- 17 lessons and 124 hand-written drills. They start with movement and insert mode, then work through word and line jumps, file jumps (
gg,G,%,{/}), small fixes (…
How I Built It
The app itself is Vite and plain TypeScript, no framework. The editor is CodeMirror 6 with @replit/codemirror-vim, so it's real vim behavior: motions, operators, text objects, dot-repeat, registers, search.
Every drill is checked in real vim
Every drill has a reference solution, and par is just its length. The test suite runs every reference solution through the actual vim engine and checks that it really produces the goal. That turned out to matter a lot. My first pass had a few pars that couldn't be hit, and the tests caught them immediately. There are 182 tests now.
It also caught some fun bugs. My favorite: search drills worked fine on their own but broke after pressing Next. The vim plugin keeps search history and jump positions globally, so the second search tried to jump to a position from the previous, longer buffer and crashed. Now vim's global state resets for every drill.
The coach: Gemma running on your GPU
The AI coach is optional and off by default. When you turn it on, it downloads Gemma 4 E2B once and runs it in a Web Worker on WebGPU with Transformers.js. Your keystrokes and prompts never leave the browser. The model files come from Hugging Face, and after that everything is local.
A few details:
- It loads Gemma 4 through the text-only path, so it only downloads the text model and skips the vision and audio encoders.
- It uses
q4f16when the GPU supports fp16 shaders and falls back toq4otherwise. - Gemma 3 1B is there as a lighter option you can switch to without turning the coach off.
On my machine, Gemma 4 E2B writes about 6 to 7 tokens a second, so a one-sentence explanation takes a few seconds. Gemma 3 1B is faster (about 10 tokens a second), but when I tested both on the same drills, 1B kept getting the commands wrong, and once it just greeted him instead of giving feedback. So Gemma 4 is the default.
The coach does two things:
- After an over-par solve, it explains what the shorter answer does differently.
- The Hint button nudges you toward the right command without giving away the whole sequence.
Getting the feedback accurate took some work. My first version sent the model the raw keystrokes, and after a drill solved with x seven times, it confidently told him he'd used dd. A small model reading xxxxxxx doesn't really see seven presses of one key.
The fix was to stop making the model parse vim. Before the keys go into the prompt, the app groups them the way vim reads them and labels each one:
x ×7 (delete character)
instead of xxxxxxx, and
cw (change to word end), typed "blue", <Esc> (back to normal mode)
instead of cwblue<Esc>. After that change, all of my test cases came back accurate, like: "dw deletes the word under the cursor, which is much faster than deleting each character individually."
AI-generated drills
The part I'm happiest with is the 5 more button on each lesson.
My first attempt let Gemma write whole drills: the text, the starting cursor, the solution keys. Only 2 of 12 drafts survived validation. The rest had missing cursor marks, keys that didn't do what the instruction said, or text glued onto other words. I kept adding filters and kept finding new kinds of mistakes.
So I split the job up based on what each side is good at:
- Gemma writes a short snippet of text (a SQL query, a shopping list, a CSS rule), plus a short instruction.
- The app supplies the keys by reusing the key patterns from that lesson's hand-written drills on the new text. Then it plays them in real vim to get the goal and par.
-
The instruction gets checked. If Gemma's wording doesn't name what actually changes, the app swaps in a plain one like
Change "milk" to "eggs".
Now every batch keeps 5 out of 5, in about 15 seconds. Every generated drill is solvable at par, because the app played it before showing it.
Deploying
It's a static site on Cloudflare Workers static assets. The only wrinkle was ONNX Runtime's wasm file, which is about 27 MB, over Cloudflare's 25 MiB per-file limit. Transformers.js already loads it from jsDelivr pinned to the exact version, so I just left it out of the build.
Why Does Open Innovation Matter?
For this project it's pretty concrete. An open-weight model is the reason the coach can run in his browser at all.
With a closed API, every hint and every generated drill would be a paid request, plus a server and an API key to manage. For a gift that's supposed to stick around, that's a bad fit. Running Gemma locally means it costs nothing per use, it works without an account, and his keystrokes and mistakes stay on his machine.
It also means I could do things a hosted API would make awkward. I picked a model size that fits a laptop GPU, compared two Gemma models side by side on the same drills, and swapped between them with one click. The rest of the stack is open too: Transformers.js, ONNX Runtime Web, CodeMirror, and the vim plugin that made "use real vim" possible in the first place.
If I keep working on it, I’d like to add more lessons and keep improving the generated drills, but I also want to see what happens after he’s used it for a while. The whole point was to build something around the way one actual person was struggling to learn, so whether it really helps those commands stick is probably the best test I could give it.
Prize Categories
Gemma: the coach and the AI drill generator both run Gemma 4 E2B locally in the browser through Transformers.js on WebGPU, with Gemma 3 1B as a lighter option.
Top comments (8)
I absolutely love it, it's beautifully made! Well, However Vim and I have never seen eye to eye 🤪
Haha, totally fair 😅 Vim definitely has a way of making you feel like you’re fighting the editor at first. That’s honestly a big part of why I made this. I was trying to make that learning curve a little less hostile.
hats off mam🫡
great work. It is useful for many friends like me alongside your husband.
refreshing one to practice vim.
Thank you! 😊 That’s exactly what I was hoping for too. I built it around the way my husband was struggling with Vim, but I figured there are probably a lot of people in the same boat who want a less painful way to practice.
Nice😃
Thanks!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.