Skip to content

Case study · 2026

Dearroom: shared state without premature reveals

I am building an Expo and React Native app for two people living apart, with a shared 3D room, messages, and plans backed by Supabase. The app is in development.

Some state is shared, but not everything should reach both devices immediately. Unrevealed answers and unopened gifts are filtered on the server.

React NativeExpoTypeScriptSupabasePostgreSQL
Dearroom: shared state without premature reveals screenshot

My role and current status

I am building the mobile interface and the Supabase backend. Dearroom gives two people a shared room with notes, a conversation, questions, and plans across time zones.

The screenshot is a capture of the mobile-width app interface. The project is in development; this case study does not claim an App Store release or verified delivery of physical-device push notifications and widgets.

The decision: filter before data reaches a device

Daily questions are answered separately and revealed together. Sending both answers to both phones and hiding one in the interface would leave the private answer in the other client's data.

The backend keeps the full room state in a table that app users cannot read directly. Each change passes through a Supabase Edge Function. The function validates the command, applies the domain transition, and saves a filtered view for each member.

The visibleRoom projection removes the other person's answers until both have answered. It also removes unrevealed journal answers, limits personal journal favorites to their owner, and strips the message from a gift until its recipient opens it.

Authenticated command
  → membership and input checks
  → room state transition
  → member-specific projections
  → private views and live updates

The UI can make the reveal feel immediate because the privacy decision has already happened on the server.

Shared writes need an explicit conflict boundary

Two people can act at the same time. The room update function reads a version alongside state and uses optimistic concurrency. If another update wins first, it reloads the newer state and reapplies the change, with a bounded retry loop.

That is a practical fit for a small two-person room. It is not a claim that an entire collaborative product can scale indefinitely around one state object. More participants or heavy write traffic would require revisiting that boundary.

Leaving a room changes access

Membership governs private views, live channels, and media. When someone leaves, the room's live-channel key changes so an old subscription does not retain access. Invitations are hashed, expire, and are consumed once.

Photos and voice notes use private storage and short-lived signed links. This is access-controlled storage, not end-to-end encryption.

Verification and remaining work

The repository includes domain tests for room and journal behavior, Supabase integration tests for membership and private data, and browser flows for paired sessions, plans, and journal interactions.

These are distinct layers. Domain tests can check the filtered state. Integration tests need a running Supabase stack to check database policies and commands. Browser previews can exercise two sessions, but cannot establish physical-device haptic feel, background push delivery, or installed widget behavior.

What this project demonstrates

The room is the visible part of the product. The engineering work connects a React Native interface to authenticated shared state, concurrent updates, and explicit privacy boundaries.