2026-03-09|3 min read|--debugging--firebase--firestore--nextjs--indie-making--devlog

The Day I Chased a Ghost Bug for 8 Hours (And What Firebase Taught Me)

There's a special kind of exhaustion that comes from debugging. Not the physical kind. The mental kind — where you've been staring at the same error message for hours, tried everything that should work, and the bug just... laughs at you. Today was that day.

It Started Simple Enough I'm building FlashBee — a flashcard app for kids to learn vocabulary. Today's goal: make sure when a user rates a flashcard, the progress gets saved to Firestore. One function. One Firestore write. How hard could it be? FirebaseError: Missing or insufficient permissions. Famous last words.

The Ghost I added debug logs right before the Firestore call: typescriptconsole.log('[submitReview] auth.currentUser?.uid:', currentUser?.uid);


The log never appeared.

The error was on line 521. My log was on line 463. The error was *inside* `submitReview` — but the log at the top wasn't firing.

Which meant one thing: the browser was running **old code**.

Next.js dev server had cached the previous bundle. My changes weren't being picked up. I was debugging code that didn't exist yet.

Killed the server. Restarted. Hard refreshed. Finally saw the logs.

---

## The Real Culprit

The `cardProgress` collection stores documents with IDs formatted as `{userId}_{cardId}`. My Firestore rule was:

allow update: if request.auth != null && resource.data.userId == request.auth.uid;


Works fine for *updating* existing documents. But for **creating new ones**, `resource.data` is null. So `resource.data.userId` throws, and the rule fails.

Fix: match on the document ID itself.

allow read, write: if request.auth != null && progressId.matches(request.auth.uid + '_.*');


---

## Then My Wife Found Another Bug

I thought I was done.

My wife tested the app on her account. Same error — but on the "Edit Card" page. Her cards. Her deck. Her account. Still blocked.

Turned out `cards` documents never had a `userId` field in Firestore. The `createCard` function never saved it. So the rule always failed because `resource.data.userId` was `undefined`.

Fix: allow update/delete if the card has no `userId` field at all (legacy), while enforcing ownership for future cards.

allow update, delete: if request.auth != null && (resource.data.userId == request.auth.uid || !('userId' in resource.data));

And Then Safari While testing on iPhone, my wife noticed the flashcard flip animation was broken. The card showed two images — one normal, one upside down, both visible at the same time. Classic Safari. typescript// Before style={{ backfaceVisibility: 'hidden' }}

// After
style={{ backfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden' }} Safari still needs the -webkit- prefix for 3D transforms. Chrome forgives. Safari does not.

What I Learned Today

  1. >Stale caches will gaslight you. If your logs aren't appearing, you're not running the code you think you are. Kill everything, restart, hard refresh.
  2. >resource.data is null on document creation. If your rule reads from resource.data, it will fail for new documents. Use request.resource.data for creates, or match on the document ID.
  3. >Always store userId in every document. Even if you don't think you'll need it. Future-you will be debugging at 10pm wondering why a rule that should work doesn't.
  4. >Test on Safari. Especially with CSS 3D transforms. Chrome forgives. Safari does not.

The Quiet Win By the end of the day, everything worked. Cards save. Progress tracks. The flip animation looks clean on iPhone. No launch. No viral moment. Just a thing that was broken, now fixed. That's most of software development. Long stretches of confusion, punctuated by the quiet satisfaction of a thing finally working. It's enough.

FlashBee is a flashcard app for kids learning English vocabulary. Still in development — but getting closer every day.