Photo by Rafael Garcin on Unsplash
How we rebuilt our website (and taught it to write blogs)
88 commits, one database and a Notion-style editor later: how a hand-edited memo page became a site that runs itself, told with diagrams and our favourite bugs.
Picture this. It's , a little before 1 a.m., and
Ronit Choudhury pushes a commit called FIRST COMMIT. Out comes a lovely little website, styled like an internet standards memo, with thick black rules and a yellow bird. Ten weeks and 88 commits later, that same page runs on a database, has an admin panel, counts its visitors without cookies, and has a blog with a Notion-style editor. Also you are just reading a post written in it. š
This is the story of how we got it here: what we changed, why, and the bugs that bit us along the way. No computer science degree required.
Once upon a time: a memo with a to-do list
The first version was one HTML page and one important file: js/data.js. Every event, every link and every member lived in that file as JavaScript. Adding Thursday's study session meant opening the code, typing the event in the right format, committing, and pushing to GitHub.

That worked fine for one person who knew the code. It had two problems:
- Only coders could update the site. Our outreach and design leads had to message someone and wait.
- The Projects list could go blank. The page asked GitHub for our repositories straight from your browser. The Thing is, GitHub allows only 60 of those requests an hour per network, so on shared college Wi-Fi, everyone's visits drained the same 60. Then the list became empty for Anyone new visiting the site.
Until one day: "can I add an event without Git?"
On we decided to start the remake. We Made sure that Before writing any code, we wrote down rules for ourselves and those were :
- Keep it static. No React, no bundler. A visitor's browser downloads only the plain files and that's it.
- Logic gets tests first. Dates, time zones and calendars are where bugs hide, so every bit of logic is a small module with tests. There are 139 tests š« now, and they run before every deploy.
- The database decides who can do what, not the page. More on that in a minute.
- Keep the soul. The RFC-memo look was good and we wanted to keep it but also to Improve upon it, not replace it entirely.
Here's comes July next to today. Same memo and same bird, but the header now shows live facts (the last commit, Discord members online), and the brand got a proper Design system: colours, type and a redrawn mark.

Because of that: a database behind a static site
The trick that makes everything else possible is Supabase: a hosted Postgres database plus sign-in plus small server functions, on a free plan. Think of the website as a shop window and Supabase as the stock room behind it. The window is still glass and paint (static files on GitHub Pages), but now it's stocked from the back.

| Piece | What it does | Where it lives |
|---|---|---|
| Public site + blog | What you're reading right now | GitHub Pages |
| Database | Events, members, links, blog posts | Supabase (Postgres) |
| Edge Functions | Image uploads, event emails, analytics, rebuilds | Supabase |
| Blog images | Pictures in posts like this one | A public GitHub repo, served by jsDelivr |
| The robot | Rebuilds the blog and its link-preview cards | GitHub Actions, every 30 minutes and on publish |
And the GitHub rate limit? The robot now fetches the repository list once per deploy, with its own token, and ships it with the site as a file. Your browser never asks GitHub directly, so shared Wi-Fi can't empty the list any more.
The bouncer lives in the database
Here's the part we're proudest of. In a lot of websites, the page decides what you're allowed to do: the admin button is simply hidden from you. But anyone can open the browser's developer tools and un-hide a button.
So we put the bouncer in the database instead. Postgres has a feature called row-level security: a rule attached to a table that runs on every query, no matter who sends it.

This is the actual rule for blog posts:
create policy "read published, editors read all" on public.blog_posts for select
using (status = 'published' or (select private.current_role()) in ('admin','editor'));In English: you can read a post if it's published, or if you're an editor or admin. Drafts simply don't exist for anyone else. They aren't hidden by the page; the database never sends them.
Because of that: an admin panel anyone can use
With the data in a database, we could build an admin panel for the whole team, not just whoever knows Git. Events are a kanban board you drag cards across. Everything else opens in a side drawer with a live preview of how it will look on the site.
What makes it safe to click around
- Every change is written to an activity log
- Deleted things can be restored
- Idle sessions sign themselves out
What makes it pleasant
- Toasts that say what actually happened
- A Team page to invite people by email
- Keyboard shortcuts for the busy
Because of that: counting visitors without cookies
We wanted to know which sections people read, without cookie banners or tracking anyone. So the analytics are ours: a tiny script sends "someone viewed this page" to an Edge Function, which counts it. No IP address is stored, and the ID that tells today's visits apart is scrambled with a salt that changes every day, so nobody can be followed across days.
The numbers are ordinary maths. Our favourite, the bounce rate:
Until finally: a blog that feels like Notion
Then came the blog, and we got ambitious: a full-page editor with a big cover, a page icon, and a / menu for headings, lists, callouts, toggles, code, equations, tables, images and video. Basically, Notion.
Under the hood, every post is stored as Markdown, a plain-text format where **bold** means bold. The editor turns Markdown into blocks you can click and type in, and turns them back into Markdown when it saves. That round trip has its own set of tests, so a post never quietly changes shape between saves.

Images take a fun detour. Instead of paying for storage, every picture becomes a commit in a public GitHub repository and is served worldwide by jsDelivr, a free CDN. The address points at one exact commit, so it can never change under you.

A few more things you get for free with every post:
- A link-preview card for WhatsApp, Discord and LinkedIn, drawn by the robot from the post's title, cover and authors
- Autosave about a second after you stop typing, and a version history where you can restore any version
- A favicon before every link, and a little screenshot preview when you hover one
Our favourite bugs
Every project has them. Here are some of ours, each one fixed.
The site's font changed after you visited the blog
The blog editor added a typewriter font called "Latin Modern Mono", and the homepage's font list happened to mention a font with the same name. On a fresh visit the homepage wasn't allowed to load it, so you saw Fira Code. After you'd visited the blog, the browser had it cached and happily used it everywhere. The fix was giving our copy its own name.
Pressing "/" teleported you to the search box
The admin panel has a shortcut: / jumps to search. It didn't notice you were typing in the post editor, so starting a slash command stole your cursor and dumped it into an invisible search box. The shortcuts now step aside while the editor is open.
Undo jumped to random lines
Undo used to rebuild the page and put the cursor back by line number. After you added or removed a block, the line numbers shifted, so the cursor landed somewhere else. Now undo works out exactly where the text changed and puts the cursor there.
The version history was empty, for everyone
The tables were there and the code was right, but the database had never been told that signed-in users may use them. Every save was politely refused, and the editor quietly fell back to an older list. One grant later, versions flow.
Ever since

By the numbers: 88 commits, 31 database migrations, 6 server functions, 139 tests and about 20,000 lines of hand-written HTML, CSS and JavaScript. And still not a single framework.
What's next on the list:
- Tags and filters on the blog
- Search across posts
- More writers. That could be you: ask in the Discord.
The whole thing is open source. Read it, break it, and send a fix and Remember Keep HUNTING:


