Dec 2025 – Present

Venue Ops Platform

Team Project | Real-Time | Production

Venue Ops Platform cover

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

ReactTypeScriptZustandTanStack QueryWebSocketsKonvaTailwind