TLDR: /_([a-z])/g skips underscores followed by digits. run_rate_7d becomes runRate_7d. TypeScript doesn't notice. ?? 0 turns undefined into a real-looking zero. Your whole dashboard lies quietly.

the wall

I pulled up the supply-chain dashboard — my internal ops app (a Next.js + Supabase inventory tracker for an ecommerce business, an ecommerce brand I work with) — and the run-rate columns were completely zeroed.

Every single product. 0.0/day.

Days Left still showed real values — 52d, 86d, 818d. The last-10-days velocity looked fine. Forecast, fine. Just both rate columns: zeros, all the way down.

the wrong turn

My first instinct? Stale snapshot. Sync outage again.

We'd had a bad snapshot before. I went straight for the usual suspects — checked the last-written timestamps, looked for a broken cron, poked at the data pipeline.

Nothing. The DB rows looked completely healthy.

That's when the contradiction stopped me.

the pivot

Here's the thing: Days Left and run rate are both calculated from the same snapshot row, written by the same route, from the same data. If there were a write bug, both would be broken.

But only the rates were zero. Days Left was real.

That means the DB was never wrong. Something was breaking only on the read — specifically on the name of the field being read.

the actual bug

I dug into src/lib/supabase/case.ts, the utility that converts Supabase's snake_case column names into JavaScript-friendly camelCase keys.

The regex: /_([a-z])/g

Spot it?

[a-z] only matches lowercase letters. An underscore followed by a digit_7, _30 — never matches.

So:

  • days_of_inventorydaysOfInventory ✓ (letter after the underscore)
  • run_rate_7drunRate_7d ✗ (digit after the last underscore — never transformed)

Every time my code read snapshot.runRate7d, JavaScript returned undefined. And I had ?? 0 as a fallback — because zero seemed like a safe default for a rate.

So undefined silently became 0.0/day and nothing threw. TypeScript didn't notice either — camelCaseRow returns result as T, so the compiler just trusts the declared type and never sees the runtime key mismatch. A passing typecheck was zero evidence.

The fix was one character: /_([a-z0-9])/g.

Widened the character class to include digits. Added a regression test (src/lib/__tests__/case.test.ts). tsc clean, 61/61 tests green. Done.

why this one stuck

Three things compounded to make this invisible:

  • ?? 0 turned undefined into plausible data. No error, no NaN, just a real-looking business number.
  • TypeScript cast silently trusted the declared type. The key mismatch lived entirely at runtime. The types said it was fine.
  • The columns that broke all had digit segments (run_rate_7d, run_rate_30d). The one that worked didn't (days_of_inventory). That per-key selectivity is the fingerprint of a name-transform bug — not a sync issue, not bad data.

When some fields in a row are right and others are zero, don't chase the data source first. Look at what's different about the names. The answer is usually in the transform.

P.S. Post-fix: runRate7d × daysOfInventory lands right back near actual stock on hand for every product. The data was right the whole time.