Introduction
Learning how software actually gets built.
This is a two-surface nightlife product: a customer-facing site where people book tables and tickets for club events, and an internal dashboard where venue teams run the business behind it — events, inventory, artists, menus, merch and a floor plan of the room itself.
I joined it as a collaborative build alongside two working industry engineers, specifically to see how things are done outside coursework: pull requests, review cycles, a shared branch, a real deployment target and a codebase nobody can hold entirely in their head. I own the business dashboard end to end and shipped two feature PRs on the public site.
Real-time
One socket, many features.
Messaging, issue tracking and presence all ride a single multiplexed WebSocket rather than a connection per feature. I wrote it as a singleton service that fans inbound frames out to per-event listener sets, so any component can subscribe and get a teardown closure back.
It reconnects on exponential backoff capped at thirty seconds, resets its attempt counter on open, and suppresses reconnection when the client disconnects deliberately. The Go backend streams JSON-Lines, so the client splits frames on newlines and tolerates two different envelope shapes — the kind of thing you only discover by integrating against a service somebody else owns.
Hard parts
A chat window that scrolls both ways.
The message list paginates bi-directionally: older messages load upward, newer ones downward, and jump-to-search-result replaces the window entirely. That combination breaks naively — a slow page request can land after the window has already moved and merge duplicate ids into a virtualized list, corrupting React keys.
I fixed it with an epoch counter per chat space. Any fetch that replaces the window bumps the epoch; load-more requests capture the epoch when they start and discard their response if it changed. On top of that sit request-dedup guards with short cooldowns, because React StrictMode double-effects and socket bursts were firing the same fetch two and three times.
Sending is optimistic: a temporary message appears immediately, mirrors into any open reply thread, bumps the parent's reply count in three places at once, then promotes to the server id — or gets filtered back out if the write fails. Reactions work the same way, and every mutation falls back to HTTP when the socket is down.
Canvas
Drawing the room.
Venue staff need to map tables onto a photo of their floor so bookings can point at a real place. I built that as a Konva canvas with rectangle, circle and polygon tools over the uploaded floor plan.
Coordinates are stored normalized between zero and one rather than in pixels, so a plan drawn on a laptop renders correctly on a phone and survives an image swap. Tables owned by another booking category render in a disabled state, and the editor deliberately allows only one unsaved shape at a time — a constraint that removed a whole class of ambiguous-save bugs.
Working in a team
What the group project actually taught me.
The engineering I'm proudest of here is mostly unglamorous. Query keys became a hierarchical factory per feature because ad-hoc string keys stopped being invalidatable once several people were writing mutations. Form modules got split into schema, change detection and payload builder so edits send only dirty fields. A Vite plugin patches out the HMR reconnect reload because mobile backgrounding was wiping in-progress composer state during testing on real phones.
I also learned what a codebase looks like when it is shipping rather than finished. There are 2000-line components that want decomposing, debug logging still in production paths, and no automated test suite — all things I would push on with more time, and all things I could not see from the outside before working on something at this size.
9
feature verticals owned end to end
2
surfaces: customer booking site and ops dashboard
Live
in production for real venues
What's inside
Key features.
Multiplexed WebSocket
One connection carrying messaging, issues and presence, with backoff reconnect and per-event listener fan-out.
Epoch-guarded pagination
Bi-directional cursor paging plus jump-to-message, with stale responses discarded by epoch comparison.
Virtualized chat
TanStack Virtual list with manual scroll anchoring so prepending older messages doesn't jump the viewport.
Floor-plan editor
Konva canvas with rect, circle and polygon tools over resolution-independent normalized coordinates.
Issue tracker
Internal Jira-lite: eight statuses, priority and severity, duplicate and blocked-by relations, live comments.
QR redemption
Door-scan workflow parsing a custom payload format, guarded by in-flight lock, cooldown and duplicate dedupe.
Merch variant SKUs
Variant dimensions like size and colour expanded into a per-SKU matrix editor linked to inventory.
Ticket transfer flow
On the public site: shareable transfer links with a six-state view machine and third-party detail redaction.
Built with