Case study
Second Memory
An offline-first personal organizer that replaces a pile of notes apps and spreadsheets, with undo/redo everywhere and an optional sync server I wrote myself.
View Live Site (opens in a new tab) → View Code on GitHub (opens in a new tab) →

The goal
My books, recipes, to-dos, shopping lists, notes, and household bills lived in a half-dozen different apps and spreadsheets. I wanted one place for all of it that works with no connection, keeps my data on my own devices, and never loses an edit I regret.
What I built
- A 12-section organizer. Home, Books, Recipes, Medications, Diagnoses, Weight, To-Do, Shopping List, Notes, Budget, résumé and portfolio links, and a private Journal, all in one single-page app of about 8,400 lines of plain JavaScript with no framework.
- Undo and redo across every collection. A snapshot-based history (up to 50 steps) lets me reverse an edit or deletion, with a guard so that replaying history never records itself.
- Filtering, sorting, and search within each collection, plus one search across all of them.
- Everyday tools. Repeating to-dos that create the next one when completed, recipe scaling and a cook mode, a yearly reading goal, autopay bills, a pay-period budget view, a light and dark theme, and an optional PIN lock.
- A permanent test suite. Over 500 browser checks and 47 server tests, built up over three full code reviews, with a regression test for each bug the reviews found.
- An installable, offline app. A web app manifest and a service worker cache the app shell, so it installs to a phone's home screen and runs without a connection.
- An optional sync server. A small Python server, using only the standard library, keeps a phone and a computer in step. It runs on Render with a persistent disk, and the app talks to it with a single passphrase.
Decisions that mattered
- Local-first by default. Data is stored in the browser, and nothing leaves the device unless syncing is turned on.
- Conflicts never lose an edit. The server owns a version number for every record. If two devices change the same record, the server keeps its copy and saves the other device's version as a separate new record, so neither side is silently discarded. Deletions are kept as markers so they spread to every device instead of being brought back by a stale copy.
- Tracking down a real sync bug. Every sync re-sent every record, and the server was counting unchanged records as edits, inflating version numbers and raising the odds of false conflicts. I traced it to the merge step and fixed it by comparing record content, ignoring bookkeeping fields, so an unchanged resend now does nothing.
- Locking down the server. The passphrase is checked with a constant-time comparison, saves are written to a temporary file and swapped in so a crash can't leave a half-written data file, and browser access is limited to an explicit list of allowed origins.
- Making updates reach installed copies. The service worker's cache name is versioned, because a browser only notices an update when the worker file changes; I documented that in the code so a deploy can't leave users stuck on old code.
- No dependencies. Both the app and the server use only what the browser and Python already provide, which keeps it easy to host and maintain.
What shipped
The app is live on GitHub Pages and installable on a phone, and the sync server is deployed separately so the two devices I use share one set of data. The server keeps daily restore points for 30 days, and the app reminds me when a backup is overdue. It is built for my own daily use.
Skills this project shows
- Data modeling and state management for multiple related collections
- Designing a conflict-aware sync protocol and building the server for it in Python
- Progressive web app techniques: manifest, service worker, offline caching, versioned updates
- Security basics: constant-time secret comparison, origin allow-listing, safe file writes
- Automated testing and code review: reproducing a bug, fixing it, and keeping a test so it stays fixed