A core reason for me to keep a personal website is for the sake of a playground. Obviously, myCv page is nice along with the possibility to find connections or showcase my skills, but a place to build generally useful things, learn something in the process, is just as important to me. On my own site I do not need to justify the effort, and I can test perhaps unorthodox use cases. User stories? Well I am the user - the retrospective tool I built is exactly the example: koivupuu.ee/tools/retro
Why?
I have been working in agile environments daily for some years, as a product owner I run retrospectives regularly. Commonly known retro tools are for example Miro, EasyRetro, Retrotool – there are a ton of those, but generally come with accounts, pricing tiers, and varying levels of privacy.
I wanted something simple: create retro session, share the link, everybody joins, run it and be done with it. Secondly, it's an excuse to build with WebSockets, which I hadn't done from scratch before. Frontend – yes, I do let AI write the page templates for me, I do not need to manually write the basics. The learning aspect, what I need, is back-end, database, server and connection setups.
Concept
Retros and the tool structures around sessions — short-lived rooms created by a host. A session has one or more rounds, each titled descriptively (e.g. "What went well?") with a timer. Participants join the session anonymously or with a name and in each round write sticky notes. At rounds’ end, all notes are revealed on a shared review board. If you forgot to describe something – upvote other participants notes. Host controls the pace by starting rounds, moving to reviews, and ending the session. Essentially the flow follows always the logic:
- Host creates a session — names the session, sets up rounds with titles and durations, enters their own name.
- Participants join — via a session ID or direct link. Join either with actual name or just incognito.
- Writing phase — when host starts a round, timer starts and each participant writes their notes privately. Nothing is yet revealed to others.
- Review phase — all notes appear on the shared board. Participants vote on valuable notes.
- Next round or end — the host advances through rounds and eventually ends the session, at which point results can be exported.

Under the Hood
My site is essentially a React project running on Next.js, hosted on a VPS. Real-time layer is built with WebSockets (ws library), running alongside Next.js via a custom server.ts.
Session state lives entirely in server memory — no round-trips to a database during active use. Server is thus the single source of truth; state changes result in a session-state broadcast to all connected clients in the particular session.
A custom React hook, handles the WebSocket lifecycle on client side: connecting, sending typed messages, receiving state updates, and storing session tokens in localStorage for reconnection. The server tracks each connected socket's session membership and token directly on the socket object, which keeps routing simple — no separate lookup tables needed for most operations.
Persistence is intentionally minimal. When a round completes, notes and round metadata are written to database. When a session ends, it's marked as ended. The in-memory session is cleaned up after 60 seconds. This means the data is there for audit or export, but the tool doesn't depend on the database for normal operation.

MVP 1
- Session creation with configurable rounds (prompt + timer per round)
- Join by session ID with a display name
- Lobby view with participant list
- Timed writing rounds with server-authoritative countdown
- Anonymous note submission
- Shared review board visible to all participants after each round
- Host-only controls (start round, advance to review, next round, end session)
- Rejoin support — if you disconnect and reconnect, your token in
localStoragegets you back in

MVP 2
After some manual testing locally and a first user testing, some weak spots and improvement possibilities emerged.
Anonymity controls Added a proper incognito mode at join time — incognito participants have no display name, only a generic label. The host can still track connections to know participation in the room, but note attribution is hidden.
Voting: This is implemented from first test round with a rubber duck colleague. It was suggested that during review phase, participants should have the possibility to upvote each others notes. Each participant gets one vote per note, and re-click removes the upvote again. This facilitates discussion to focus on what the team actually cares about.
Export and note deletion - after “rubber ducking” round I realized that I might want to reconsider and delete a note, either due to typos or change of mind. So I added ability for participants to delete individual notes. And as PO I wanted to retain the data - export the full session results — notes grouped by round, with vote counts — to CSV/PDF. I realized that the end of a retro needs a takeaway - a list of what was discussed, and I can use this to report it further if needed or use for a team Wiki etc.

The Follow-Up
The retro tool works and I used this in a session already. Something that I would like to have in the future is the possibility for note grouping — the ability to drag related stickies together into clusters before or during discussion. It was in scope at my initial ideation, but the implementation added enough complexity (both in the UI and in the real-time sync logic) to deserve its own focused effort. I'll come back to it, but meanwhile I have a sufficiently practical tool.
For now, I'm happy with what I have. If you want to try it, you can create a session at koivupuu.ee/tools/retro. Free, no account needed, sessions disappear after use. In case of any questions or feedback get back to me through kaarel@koivupuu.ee.