← Back to blog

Apr 12, 2026

BOL BHARAT

AI-powered civic issue reporting platform using Gemini Vision for issue detection.

I built BOL BHARAT because I was tired of watching civic issues disappear into phone trees and unresponsive municipal websites. The idea was straightforward: let someone snap a photo of a broken streetlight or flooded road, pin it to a map, and track whether anything ever gets done about it.

I chose React 18.3 with TypeScript 5.5 because I wanted type safety without the overhead of a full framework. Firebase 11 handled auth, Firestore for issue storage, and Cloud Storage for images. I had used this stack before, so I knew where the sharp edges were. I underestimated how much the image handling would matter.

The first real problem hit during geolocation. I assumed navigator.geolocation.getCurrentPosition would be sufficient. It wasn't. On Android Chrome, the permission prompt behavior is inconsistent, and if a user denies it once, the API silently fails on subsequent calls without re-prompting. I spent two days thinking my code was broken before I realized the browser was permanently caching the denial. I switched to a manual map pin fallback, but that introduced a new issue: users would drop pins in obviously wrong locations, sometimes oceans away. I added a bounding box check against India's geographic limits and a confirmation dialog before submission. That stopped the bad data, but the flow felt clunky. I left it as-is because the alternative, reverse-geocoding every tap to verify location names, added too much latency for the user's network conditions.

Firebase's offline persistence was another trap. Firestore enables it by default in recent versions, which seems helpful until you realize that writes queue locally and sync later. I had built a flow where users could delete their own issues, and I was checking ownership server-side with security rules. A user could delete an issue while offline, the local cache would apply it immediately, and the UI would show it gone. When they came back online, the security rule would reject the actual server delete if their token had expired, but the local cache wouldn't revert. The issue appeared deleted to them but persisted for everyone else. I only caught this because I tested on a train with spotty connectivity. I had to implement a manual sync status indicator and retry logic, which Firestore doesn't expose cleanly. I ended up tracking pending operations in a separate local collection and reconciling on reconnect. It worked. It should not have been necessary.

The image upload flow had its own problems. Firebase Storage has a 10MB limit per file, which I discovered when a user tried uploading a 4K video thinking it was a photo. The error message from the SDK was generic, something about network failure. I had to catch the file size client-side before even touching the SDK, but File.size is unreliable for HEIC files on certain Samsung devices, reporting smaller sizes than the actual upload. I added a server-side Cloud Function to validate and compress, but that meant users without fast internet waited twice, once for the failed client attempt, again for the retry. I eventually set a hard client-side limit of 5MB and converted everything to JPEG before upload. Quality suffered. I accepted the tradeoff.

Auth was relatively painless until I considered account deletion. Indian data protection regulations require complete erasure, and Firebase Auth doesn't delete user data from Firestore or Storage automatically. I wrote a Cloud Function triggered on user deletion, but it had a race condition with the client-side signout. If the user closed the app immediately after requesting deletion, the function sometimes failed silently because the auth token was already invalidated. I added a two-step confirmation with a forced delay and an email verification loop. Users hated it. I kept it anyway.

The "Final", "Final 2", "final 3", "At last" commit history tells the story I don't need to explain. What I shipped works. What I learned is that Firebase's convenience APIs hide enough state that building anything requiring consistency guarantees means fighting the abstraction. I used to reach for Firebase for speed. Now I reach for it knowing exactly which parts I'll have to rebuild myself.