TLDR: Placeholder data tests your assumptions. Real training data tests your code. They are not the same test.

the setup

I've been building my personal gym PWA (Progressive Web App, basically a static HTML/JS app that runs offline and lives on your home screen) — to track my M/W/F workouts.

The workout plan I'm running is RPT: Reverse Pyramid Training, where you go heaviest first while fresh, then drop weight and chase more reps each set.

The old app stored a count of sets. Completely useless for RPT. I needed per-set data — actual weight and actual reps for each individual set, stored separately.

what I built

The sets field went from a number to an array of {w, reps} objects.

I wrote a normalize() migration — idempotent, metric-and-shape-gated, stamps v:2 on save. For old uniform sets, it collapses back to the classic display string "60 lb × 10 · 3×" so history looks right.

Tested it. Looked perfect. Shipped it.

the wall I didn't see coming

Here's the thing about testing a migration with clean data…

It always works.

I was using tidy placeholder entries — "Bench, 3 sets, 60 lb × 10." Migration ran. Display collapsed beautifully. History looked right. Everything was fine.

Of course it was. Uniform sets are shaped EXACTLY like the code expects. Placeholder data tests your assumptions, not the edge cases.

what real data actually looks like

I ran Day A for real on June 22nd — Reverse Pyramid, 6/8/10 — and logged actual numbers from my workout.

Lateral Raise: 25 lb × 6, 20 lb × 8, 15 lb × 10.

Standing Calf: 12.5 each hand / 10.x3 — a mixed notation because I was holding dumbbells, eyeballing rep counts mid-set.

Decline Crunch: +25 plate × 6, +10 plate × 8, bodyweight × failure. Three different "weights" — one of which is zero.

That's what real lifting data looks like. Not clean. Not uniform. Not shaped the way you wrote your schema when you were sitting at your desk with a coffee.

the real test

I ran the migration against my actual v1 backup from June 17th — 8 sessions, 69 entries.

Every single one came through identical.

v:2 stamped. Display strings correct. History editor showed the right per-set rows. Whoop (my HRV tracker) copy and CSV export both went per-set with one row per set.

That was the test that actually mattered. Not the placeholder. The 69 entries I'd logged over weeks of real workouts.

why this matters to me

Every time you test a migration with placeholder data, you're testing your assumptions — not your code.

Real users log 12.5 each hand / 10.x3. They do decline crunches with a plate and finish on bodyweight failure. They don't lift in round numbers at round reps in perfectly uniform sets.

If you're touching a schema that stores user data: find your ugliest real records first. Run the migration against those. A migration that passes clean data isn't done. It's just not broken yet.

P.S. The migration ran against 69 real entries and came back identical. That was the moment I actually trusted it.