I'm a breakup coach. For eight years the most common thing I've asked clients to do is also the simplest: stop contacting your ex for 45, 60 or 90 days. And for eight years I've watched them track those days in the worst tools available: gym-streak apps, sobriety counters, one man with 74 daily alarms all labeled "DON'T".
So I built the thing they kept asking for. The code is MIT and public at github.com/get-your-ex-back/no-contact-tracker, there's a live demo on GitHub Pages, and the canonical page is on my site: lover-fighter.com/tools/no-contact-tracker. This is the build log.
The constraint that decided everything
Nobody fresh out of a breakup wants to create an account. And "day 12 of not texting my ex" is about as private as a note gets, so it should never touch a server.
That one decision settled the architecture. The whole app is one HTML file. State lives in localStorage under a single key (nct-v1). No framework, no build step, no server, and nothing phones home. The deploy pipeline is git push and a GitHub Pages workflow.
What it does
Counts days since a start date against a 45, 60 or 90-day target. At day 7, 30, 45, 60 and 90 it unlocks a short piece of coaching I'd normally say on a call, because those are the days people crack. There's an urge log for the moments you wanted to text and didn't. And the reset is honest: slip, and the counter goes back to zero, but the restart count stays in your history. Pretending the slip never happened is the failure mode of every habit app I've seen clients use.
Your data leaves the device one way, an export button that hands you a small JSON file. Import reads it back. That's also the migration story between the web app and the extension.
The parts worth writing down
The counter is thirty lines; the product is the milestone copy. I treated the coaching text as content with an unlock schedule rather than a feature, which meant most of the build time went into writing, and the code stayed small enough to read in one sitting.
The PWA part cost almost nothing. A manifest and a small cache-first service worker make it installable and fully offline. For an app people open at 2am in a bad moment, offline matters more than it does for most tools.
The Chrome extension port was the annoying part. Manifest V3 shuts down the service worker whenever it likes, so a setTimeout reminder dies within seconds. chrome.alarms with periodInMinutes: 1440 is the reliable way to do a daily reminder, and it survives both the worker teardown and browser restarts. The popup is the same app with the layout squeezed to 360px.
One detail for the CI-minded: the icon set is generated by a pure-Python script that writes PNGs with zlib and struct, because the build machine had no PIL and I refused to add an image library to a project whose whole point is having no dependencies.
What I would change
localStorage was right for v1, with one sharp edge: Safari can evict it if someone doesn't open the page for a week. The export button is the current mitigation. Asking the browser for persistent storage with navigator.storage.persist() is next on the list, probably alongside an IndexedDB fallback.
If you read the repo and find something wrong, file an issue; I answer them myself. And if you're 12 days into no contact and this is the wrong tab to be reading: close it. The counter doesn't care.
Top comments (0)