Undo is usually built in the client: hide the row, wait five seconds, then send the delete. That breaks the moment the tab closes, the phone sleeps or the delete happens on another device. PaceStreak’s undo is a restore instead: the server deletes straight away, keeps a copy, and Undo puts the copy back.
A snapshot, not a soft delete
Adding deleted_at to every table means every query, everywhere, has to
remember to filter it out, and one that forgets shows deleted data. So the
trash is one table: trash_items, holding a JSON snapshot of what was
deleted.
The snapshot is generic. It walks the model’s columns, stores dates as ISO
strings and UUIDs as text, and on restore converts each value back by its
SQL type. A habit’s snapshot carries its logs as children, so restoring it
brings back its whole history. A column added to Habit next month is
carried through without anyone touching the trash code.
Workouts are the exception: they were already soft-deleted, because offline
sync needs tombstones. Restoring one clears deleted_at and takes a new
sync sequence number, which is what makes every device pick it up.
Same id, so nothing dangles
Restored rows keep their original ids. A meal that pointed at a saved food points at it again once the food is back. A routine still lists the habit.
Refusing, cleanly
If something now sits in its place, like a new journal entry for the same day or a food saved with the same barcode, the restore fails with a 409 and changes nothing. It never overwrites the newer thing, and the trash entry stays, so it can be retried after you sort out which one you want.
After 30 days the worker deletes trash entries for good. Emptying the trash early does the same, at once.
AlzyWelzy
Building PaceStreak. GitHub
Related
Security & PrivacyYour data leaves with you
Export as JSON, CSV or calendar; import it back; delete with a 30-day safety net; units that can't corrupt history; and moderation that never touches your training log.
3 min
Security & PrivacyEmailing you your own data, carefully
The monthly backup email can now carry the export itself. It was held back for a privacy reason, then built behind a second opt-in with a plain warning. Here's the trade-off and how it's guarded.
1 min
ProductA coach made of rules
The new daily coach is a few if-statements over your own data. Why that beats a generated paragraph, and why the app shows only the notes its existing cards don't already cover.
1 min