Watercooler
A coworker coffee-chat matcher that pairs people on the semantic meaning of their bio and interests, rebuilt from a 2022 hackathon prototype onto a modern stack.
- Tech
- Next.js, TypeScript, Supabase (Postgres), pgvector, Hugging Face (Sentence Transformers)
- Date
- Jul 26 to 29, 2026 (rewrite of a 2022 project)
- Status
- Prototype
- Links
- Not publicly deployed
Watercooler pairs coworkers for coffee chats based on what they actually care about, matching on the meaning of a person's bio and interests instead of keyword overlap. It started as a 2022 hackathon project; I rebuilt it from scratch this year on a modern stack, replacing a bare Express server with a hardcoded database password and unsalted SHA-256 hashing with a proper Supabase/Postgres backend, pgvector similarity search, and Next.js Server Components.
Architecture
Semantic Matching
A profile's bio, current work, and interests get flattened into a single string and embedded through a sentence-transformer model, stored as a 384-dimension vector in Postgres behind an HNSW cosine index via pgvector. Matching runs entirely inside a Postgres function that blends semantic similarity with literal shared-interest overlap, so the ranking logic lives in one place instead of being split across the database and the application layer.
Score Calibration
Raw cosine similarity across the seed profiles clustered tightly, between roughly 0.19 and 0.62, which would have rendered as an undifferentiated 40-60% band in the UI regardless of how good or bad a match actually was. I measured the real score distribution and wrote a documented, provably rank-preserving rescale into the matching SQL, so the displayed percentage still reflects genuine differences in match quality instead of compressing everything into the middle of the range.
Technical Challenges
The embedding API returns a different response shape depending on whether a call is a single string, a batch, or a token-level (unpooled) result, so the embedding client detects all three shapes and does the mean-pooling itself rather than assuming the server always pools. Recurring weekly availability windows needed to survive daylight saving time and local times that cross UTC midnight without corrupting into the wrong day or hour, handled by storing time as a week-minute ring and re-deriving each occurrence's real UTC offset at read time instead of extrapolating one fixed rule.
Other
- Notifications are written exclusively by database triggers, never application code, so a missed call site in the frontend can never produce a silently broken notification.
- Falls back to interest-overlap-only matching, with the UI stating the degraded mode explicitly, if the embedding API is unavailable.