ANONYMOUS ENTRY, IN ONE STEP

Enter from a shared link and start talking.

This walkthrough is simulated locally and does not send data.

yap. demo
Hey — you made it. No account needed.

Send the prepared message

ROLE
Full-stack Product Engineer
DURATION
Ongoing
CLIENT
Independent Product
RESPONSIBILITIES
Product strategy, UX/UI, frontend, edge backend, security, and operations
TOOLS
Next.js, React, Tailwind CSS, Workers, D1, R2, Durable Objects
LINKS

PROBLEM

Joining should feel as light as opening a link. Ownership, privacy, and safety should remain explicit.

The design problem was not simply “make a chat app.” It was balancing instant participation with explicit privacy and moderation boundaries.

01Participation

No-account entry

A shared link should open directly into a room with the composer ready.

02Trust

Control without widening visibility

Moderation authority should stay explicit without broadening access beyond the scoped evidence required for review.

03Delivery

Durable first, realtime second

The durable write defines the record before realtime delivery accelerates it.

Remove account friction, not product boundaries.

FROM PERSONAL ROOM TO PLATFORM

Personal room multi-tenant platform

yap. began as a single-channel personal page built with Vanilla JS and Supabase. Rather than porting that implementation, I used its behavior as a specification and redesigned the product as a multi-tenant platform on Next.js, Cloudflare Workers, D1, Durable Objects, and R2.

This required explicit channel ownership, separate anonymous and authenticated identities, platform-admin authority, media lifecycle rules, and operational monitoring.

I later selectively migrated the original production channel, preserving its history while keeping legacy anonymous identities pseudonymous and unlinked. Reply and gallery references, along with record counts, were verified before and after the move.

01 · Original behaviorSingle personal room

Vanilla JS and Supabase established the product behavior.

02 · Platform boundariesExplicit ownership and identity

Anonymous, authenticated, owner, and admin authority became separate states.

03 · Verified migrationHistory preserved

References and record counts were checked before and after migration.

Migration integrity checks

685Messages preserved
204Replies preserved
25Images preserved

KEY PRODUCT SOLUTIONS

01

A room begins with a link.

Hosts create a channel and share its URL. Guests can enter public rooms immediately, while optional passcodes add friction only when the conversation needs it.

User problem
A signup form makes people leave before the conversation even starts.
Design decision
A shared link opens directly into a room with the composer ready: no login, nickname, or profile.
Why this way
Friction belongs only where the conversation needs it, through optional passcodes, never at the front door.
02

Anonymous does not mean

Owners can shape entry, pacing, and message rules without leaving the conversation. Passcodes, chat freezes, and word filters change the room immediately.

User problem
Anonymity invites participation, but without controls it also invites chaos, and owners feel they can't keep a room safe.
Design decision
Passcodes, chat freezes, and word filters change the room instantly without making the owner leave the conversation.
Why this way
Moderation authority should stay explicit and immediate without widening visibility. Control, not surveillance.
random thoughts

What’s everyone thinking about today?

Mostly small ideas that won’t leave me alone.

Room active
03

Private messages have a visible boundary.

A visitor can message the channel owner privately without leaving the room. The sender and owner see the same exchange; other visitors continue to see only public messages.

User problem
In an anonymous space, “wait, who can see this?” is the fear that quietly breaks trust.
Design decision
The sender and owner share the same private thread; other visitors continue to see only public messages.
Why this way
Privacy has to be a visible boundary, not a hidden rule. People share honestly when they can see who is on the other side.
random thoughts

Anyone around?

The background looks great today.

I’ll be back in 10.

Type a message
Guest is viewing public room messages only.
04

Live is intentionallytemporary.

A host can open a separate live session for a shared moment, then end it cleanly. Live messages and reactions disappear while the normal room remains.

User problem
The weight of a permanent record discourages the light, in-the-moment exchanges people actually want.
Design decision
A host opens a separate live session whose messages and reactions disappear when it ends, while the normal room stays intact.
Why this way
Making impermanence an explicit state gives people permission to be casual without worrying about a lasting record.
random thoughts

Did everyone see the update?

I’ll be here for a bit.

Let’s try live for a moment.

Type a message
Normal room

SYSTEM DESIGN

One product, deliberately separated responsibilities.

Next.js owns the web experience and session boundary. The Worker independently authorizes API access, D1 stores durable records, Durable Objects coordinate room presence and delivery, and R2 stores protected media.

Authoritative writes commit to D1 before realtime fan-out. Public messages and authorized private threads can share one ordered timeline without widening DM visibility, while media access remains separately signed and scoped.

REQUESTCOMMITDELIVER

Hover, focus, or click to trace

HTTP REQUESTCOMMIT FIRSTWEBSOCKET · REALTIME FAN-OUTMEDIA
MESSAGE PATH · COMMIT FIRST

↩ Realtime fan-out to Browser

MEDIA PATH
SYSTEM PRINCIPLE

D1 defines the durable conversation record; Durable Objects accelerate delivery, while R2 stores protected media behind scoped access.

PLATFORM OPERATIONS

Platform operations

Beyond room-level moderation, yap. needed an operational layer for monitoring service health, triaging reports, and handling support. This dashboard brought incident awareness, queue management, and escalated user issues into one place.

Channel ownerPlatform admin
Room-level moderationEscalated abuse
Local noticesGlobal notices
Channel restrictionsPlatform restrictions
PRODUCTION VIEW

How the platform was actually operated

The admin surface connected incident awareness, queue management, escalated reports, and support handling in one operational view.

yap. production operations dashboard showing service health, routes needing attention, queue status, reports, and support tickets.
A production view, not a concept mock.
  1. 01
    Service health

    Platform-wide health, failures, slow paths, and alert status.

  2. 02
    Routes needing attention

    Problem routes surface where errors and slow responses are accumulating.

  3. 03
    Queue triage

    Open, unread, stale, and reply-needed work is summarized for fast prioritization.

  4. 04
    Escalated reports

    Abuse reports move beyond room-owner handling into platform review.

  5. 05
    Support tickets

    One-to-one user issues are handled in the same operational surface.

INCIDENTS THAT CHANGED THE ARCHITECTURE

Observed latency4–18 sec
Post-cutover sample~80 ms

~80 ms is a limited post-cutover Worker sample, not a long-term percentile.

SIGNAL
Channel initialization slowed by 4–18 seconds while measured SQL execution remained in the millisecond range.
DIAGNOSIS
Route-level stage timings and a same-code, same-data comparison on a fresh D1 database isolated the delay to one database path rather than query complexity or traffic volume.
RESPONSE
I prepared and validated a production snapshot, ran integrity checks, retained a rollback copy, and moved traffic to the replacement database. A post-cutover Worker sample reduced channel initialization to about 80 ms.
PREVENTION
Core routes retain stage-level latency instrumentation. Successful but abnormally slow requests are now recorded as operational health events, surfaced in the super-admin dashboard, and included in deduplicated external alerts. A documented, reversible database-cutover procedure remains available if database-scoped degradation recurs.
Database-scoped latency. Observed latency: 4–18 sec. Post-cutover sample: ~80 ms.

PRODUCTION EVIDENCE

6,517
messages created in one production weekSeptember 5–11, 2026 (UTC)
332Median messages per day
~550Average excluding the largest spike
433Active senders during the period

COUNTING NOTEScope: public conversations and private DMs. Created-message totals include messages later deleted by users.

CALCULATION NOTEMedian and adjusted average use the September 5–11, 2026 (UTC) window; the adjusted average excludes the largest event-driven spike.

LISTENING

Listening to real users, then shipping

yap. asks for feedback on each user's 10th visit — late enough for the response to be grounded in real experience. I review what returning users report, identify recurring themes, and ship changes from that evidence instead of guessing at improvements.

FEEDBACK → ITERATION

What users told me → what I shipped

Experience refinements

User feedbackWhat I changed
New messages pulled me back down while I was scrolling up to read earlier messages.Held the reader’s position until they return to the bottom, alongside anchor-based scroll correction for long histories.
Users wanted notifications so they could keep up with new activity without continuously watching the chat.Added notifications for new chat activity.
Users wanted clearer time context when reading and returning to conversations.Added timestamps to chat messages.
The interface felt visually tiring: the colors were hard on the eyes, text felt large, and messages felt cramped.Refined the interface as one system with a softer palette, a calmer type scale, and more space between messages.

Reliability fix from feedback

User feedbackWhat I changed
Rapid repeat taps and retries after a lost server response could create duplicate messages during slow or interrupted connections.Combined stable send IDs, idempotent retries, database duplicate prevention, and persistence-based UI updates so one send attempt converges on one stored message—even after reconnecting, re-entering the channel, or losing the original response.

TAKEAWAYS

What operating yap. taught me beyond the interface

yap. was the first product that pushed me past interface design and into the realities of running a system: durable state, realtime delivery, moderation, support, route health, and operator tooling. The biggest shift was realizing that product design does not stop at the user flow. It extends into how the product fails, recovers, and gets operated.

01Realtime makes chat feel immediate, but persistence makes it trustworthy.

Once messages, DMs, reactions, live state, and moderation actions had to survive reconnects, retries, and partial failure, I stopped treating realtime delivery as the system itself. yap. taught me to separate transport from the durable record and design the interface around recoverable sync rather than perfect immediacy.

02Operational visibility became part of the product, not just backend maintenance.

Once room entry, messaging, previews, authentication, and moderation were live, failures stopped being abstract engineering problems and became user experience problems. Building yap. pushed me to think in route-level health, alerting, queue triage, and reversible incident response, not just feature completion.

03Moderation works better as explicit, recoverable state.

Anonymous chat created edge cases around freezing rooms, passcodes, banned words, reports, deletion, and appeals. yap. taught me that enforcement should not be hidden operator intervention. It works better when actions are legible, stateful, and reversible where appropriate for both room owners and platform operators.