Lovel Up
Private, end-to-end encrypted app for keeping a long-distance relationship active

Overview
Keeping a long-distance relationship active is a real problem with very little decent software behind it. Calls are easy enough to arrange. What disappears is everything unscheduled, the small shared things two people do without either of them proposing it, which is most of what makes a relationship feel current rather than merely maintained. I started building this to solve it for myself. The first version was narrow, something that would suggest an activity for the evening, and it became obvious fairly quickly that the suggestion was the smallest part of the problem. What it is now is closer to a shared space. It recommends activities and rituals and reminds you of the ones you have kept up. It runs games and activities you play together inside the app, with the live tools those need: a scratchpad, a random generator, a scorekeeper. It holds photo streaks, notes and compliments left for the other person to find, and a record of everything you have done together. All of it is encrypted so that only the two people involved can read any of it. Nobody running a server sees a name, a photo or an answer. It is in daily use and still being extended.
My role
Completely solo build, from the first idea through to daily use. The product decisions, the interface and the design system behind it, the data layer that lives on the device, the encrypted syncing between two phones, the engine that chooses what to suggest, the sign-in and the face or PIN lock over it, and the notifications. Still mine to maintain, and still being improved.
The problem
Long distance is not mainly a communication problem. Calls are easy to arrange and easy to have. What goes missing is everything unscheduled: the ordinary shared things that happen without either person suggesting them first. The software that exists for this is mostly the wrong shape. Relationship apps treat a couple as content, with an account, a feed, and a database on someone else's servers holding two people's private evenings in plain text. For something this personal that was not acceptable, and it is not a problem you can solve by trusting a privacy policy. There was a practical constraint as well. It had to keep working when the connection did not. An app that fails on a bad evening is an app you stop opening, and one you stop opening is worse than none.
The solution
The phone is the source of truth, not a server. Everything lives on the device, and the engine that picks tonight's suggestion runs instantly from what is already there. It is never waiting on a network, a sync, or an AI model, so a bad connection cannot delay or change what it chooses. The two phones stay in step by passing along a running list of changes, each one encrypted before it leaves the device. The server in the middle is deliberately unintelligent: it can see that something was sent, and when, but never a name, a photo, or an answer. The key is derived from a code the couple share, and it exists nowhere else, which also means there is no password reset, only an encrypted backup. For the moments meant to be shared, both people answer first and reveal together, each answer sealed with a hash so neither can look ahead at the other's.
Key features
- Open the app and be told what to do tonight, chosen instantly from what is already on the phone, so it works the same on a train, on bad hotel wifi, or with the connection off entirely.
- Play games and run activities together inside the app, with the live tools they need: a shared scratchpad, a random generator and a scorekeeper, both screens staying in step.
- Answer a question about your mood at the same time as your partner and reveal together, with neither able to see the other's answer first.
- Leave a note, a compliment or something to find later, for them to open when they wake up in a different timezone.
- Browse a library of things to try, send each other ideas, build up traditions that recur, and keep a daily photo streak.
- Look back over a shared timeline of everything you have done, and react to it.
- Tell the app about your relationship, the dates that matter, the things you both like, what is off limits, and have tonight's suggestion actually reflect it.
- Keep it genuinely private: only two email addresses can ever sign in, the app locks behind Face ID or a PIN, and notifications say something is waiting without saying what.
- Hold the only key. The code the couple share is what everything is encrypted with, so recovery is an encrypted backup rather than a password reset by someone else.
Architecture & engineering
Three strict layers with hard seams: (1) local-first core: IndexedDB is the system of record and a synchronous deterministic engine is never awaited on I/O; (2) E2EE append-log sync: commit() writes the row and appends a plaintext op in one IndexedDB transaction, ordered by Lamport clock + deviceId, and the op payload is AES-256-GCM-encrypted only at relay time, applied idempotently; (3) real-time co-presence over an ephemeral Supabase channel whose live frames never enter the durable op log. The couple code is the sole E2EE root (PBKDF2 600k iters → HKDF into independently-salted roomId/dataKey/connSecret); the relay only ever sees ciphertext plus routing/ordering metadata and presence/timing. Next.js 16 App Router with a persistent provider tree (Auth → Lock → Sync → Live → Sky) that survives client navigation. Optional SDKs (Supabase, Anthropic) are dynamic-imported to keep the offline core bundle lean; the single AI seam (BYO key, off by default) always has a deterministic fallback.
The hardest problem
Two devices have to produce an identical shared reveal, while a fault anywhere in syncing or in the AI layer must never be able to break or delay tonight's suggestion. That is handled by keeping the recommendation engine synchronous and completely isolated from the network, the sync layer and the model, so it only ever reads what is already on the device. The sharper problem was the commit-reveal itself. Both people answer, each answer is sealed with a hash, and the hashes are exchanged before either is opened, so neither can look ahead. The flaw in the obvious version is that the set of possible answers is small, a few dozen combinations of mood and energy, which means anyone holding the hash can simply try all of them and read the answer before the reveal. Hashing the answer together with a random value, and exchanging that value only at reveal time, closes it. Cryptography that looks correct is worth nothing if the space of secrets is small enough to walk through. Syncing was hardened over several QA passes: deterministic identifiers for check-ins so a unique-index collision cannot wedge syncing for the pair, a durable cursor that advances only on an ordered backfill, and failure isolation so one bad operation cannot stall the rest.
Results
In daily use, every day, by the two people it was built for. For an app with exactly two users that is the only measure that means anything, and it is still being improved rather than left where it landed. Initial product built solo over about two months, across six phases, three rounds of feature work and four documented QA passes. The test suite covers the parts where being wrong would matter most: the recommendation engine, the encryption keys, the syncing and merging between devices, the simultaneous reveal, the app lock, and a full round trip through the relay to prove that the server genuinely cannot read what passes through it.
Key learnings
Drawing hard lines between the parts paid off more than anything else. Keeping the piece that picks tonight's suggestion instant and completely separate from syncing, networking and AI meant it could be reasoned about properly, and it stopped every feature added later from creeping into the middle of it. Real encryption without accounts is an honest trade, not a free win. If the couple's shared code is the only key, then nobody, including me, can reset it. Recovery has to be an encrypted backup and a re-pair, and that limitation has to be designed for rather than discovered later. The smallest lesson was the sharpest. There are only a few dozen possible mood answers, so sealing one with a plain hash is no protection at all: anyone holding the hash can simply try them all. It needed a random value mixed in, exchanged only at the reveal. Cryptography that looks correct can still be pointless if the space of secrets is small enough to walk through.
Curious how this works?
This one's a personal build and isn't available to try, but I'm happy to talk through how it works.