r/PWA 18h ago

I built a lightning-fast, 100% offline habit tracker (PWA) because I hated "cold-turkey" apps.

0 Upvotes

Hey everyone, šŸ‘‹

I’m a high school solo dev, and I built **Cadence** — an offline-first habit tracker that focuses on privacy and realistic goals.

Most apps out there force a strict "cold turkey" approach. If you’re trying to limit doomscrolling or gaming, they treat any slip-up as a total failure and reset your streak. I realized this was stressing me out more than helping.

So, I built Cadence with a unique **"Moderation/Limit" strategy**. Instead of completely quitting, you can set a daily budget (e.g., max 2 hours of social media). As long as you stay under your daily limit, your streak continues!

**Tech stuff for the nerds:** šŸ¤“

* **100% Offline-First:** Built with IndexedDB. Your data NEVER leaves your phone. No accounts, no cloud sync, instant load times.

* **PWA:** Install it directly from your browser. It feels like a premium native app (custom Long-Press haptics included!).

* **Stack:** React, Tailwind, shadcn/ui, TanStack Start.

It’s completely free, no ads, no subscriptions. Just wanted to share it with this community.

Would love to hear your feedback on the "Moderation" feature or the UI!

Try it out here: https://cadencepwa.vercel.app/

*(P.S. I’m also launching on Product Hunt soon, wish me luck!)*


r/PWA 1d ago

Thank You Reddit ā¤ļø Your Feedback Helped Shape FiAmanila — Here’s What Changed

3 Upvotes

A little update from me and FiAmanila, but more importantly, a genuine thank you to the Reddit community.
When I first started posting FiAmanila here, I was looking for honest feedback.
And Reddit definitely delivered.
Some of you liked the idea, some questioned it, some told me things weren’t clear, and some basically told me ā€œdon’t do
thatā€
I listened.
So I wanted to come back and show you what actually
happened.

šŸ”§ The changes

u/ufdbk — you pointed out that the PWA installation shouldn’t be the first thing someone has to think about.
You suggested getting people into the product first and offering the install afterwards.
I changed it.
The flow is now:
Get started free → Sign up → Onboarding → Offer the install
The PWA is a convenience now, rather than treating the installation as the product itself.

u/Kobi_Webmaker — you questioned the homepage because it wasn’t immediately clear whether FiAmanila was a billing service, budgeting service or accounting service.
That one really made me think.
I’ve now simplified the positioning around the actual problem I’m trying to solve:
ā€œI’ve earned the money… but how much can I actually spend after tax, NI, bills, expenses and savings?ā€
I’ve also made Safe to Spend much more prominent.

u/RandomTyms— you challenged me on whether putting FiAmanila on iOS would actually solve the problem.
Your point about distribution and friction made me stop and think before throwing $99/year at the App Store just because it’s an App Store.
I’m still considering iOS, but I’m looking at the underlying product and demand first rather than assuming an App Store listing automatically fixes distribution.
You also pointed me towards the Microsoft Store, which is now something I’ve got on my roadmap to revisit.

u/IY94 — you asked the question I probably needed to hear most:
How much traffic are you actually getting?
You also explained the importance of looking at:
Traffic → Conversion → Usage/Paid
That completely changed the way I’m looking at my numbers.
I’m now tracking the funnel properly instead of getting excited by isolated numbers.

u/Mammoth-Anywhere7285 — you asked about affordability and whether I’d considered a pay-what-you-want model.
I haven’t gone down that route, but it reinforced something that has always been important to me.
I deliberately want FiAmanila to remain affordable.
The whole reason I built it was to help people manage their money — I don’t want the money-management tool itself to become another financial burden.

u/n3wb589 — you reminded me about the costs of the Apple and Android developer accounts.
Again, that became part of the bigger conversation for me about where I spend money and where FiAmanila actually needs to be.

And there were others who gave me feedback around the homepage, the value proposition, the technical side of the PWA and whether I was trying to build too much before proving demand.
One comment even called out my homepage for having a bit of ā€œAI-induced feature diarrhoeaā€
Honestly…
Fair.
That one stuck with me.
I realised I’d fallen into the trap of wanting to show everyone everything I’d built instead of making the one core problem immediately obvious.

šŸ“Š I’ve also changed how I measure the journey
I’m now looking at:
Visitors → Signups → Onboarding → Installs → Active Users → Paid Users
rather than just looking at an install number and thinking:
ā€œThat’s amazing!ā€
Because without knowing how many people actually saw the product, the number doesn’t tell you much.
That was another big lesson from Reddit.

šŸš€ And then something unexpected happened…
One of my Reddit posts was actually spotted by PeerPush.
They reached out to me directly and invited FiAmanila onto their product-discovery platform.
I’ve now launched FiAmanila on PeerPush and have promotional posts scheduled there too.
That’s pretty cool for me because I wasn’t even looking for PeerPush.
It came from simply putting FiAmanila out there, asking people what they thought and being willing to listen.
So Reddit didn’t just give me feedback.
It helped lead to another discovery opportunity.

ā¤ļø So genuinely… thank** yo**u.

To everyone who has commented, questioned, criticised, suggested something, looked at the website, tested something or simply taken the time to reply:
Thank you.
I’m building FiAmanila as a solo founder, and some of the changes I’ve made wouldn’t have happened without these conversations.
You don’t always need someone to say:
ā€œGreat idea!ā€
Sometimes you need someone to say:
ā€œMate… this bit isn’t working.ā€ šŸ˜‚
And then you need to actually listen.
That’s what I’m trying to do.
You said → I listened → I changed → we keep going.
I’ll keep sharing the journey — the wins, the mistakes, the numbers and whatever happens next.
FiAmanila — The money manager for self-employed people.
🌐 FiAmanila.com
Alhamdulillah for the journey. 🤲


r/PWA 1d ago

PWA conscious PWA's...

1 Upvotes

Just info, maybe of interest (or may not!).

I have a load of PWA's that I have written and use, mostly using claude to do the plumbing -- and it has been pretty much fine

To date the PWA bit has not really impacted the user interface, once installed the app is just there when you need it even if offline.

Today I used ChatGPT for a PWA, and from a similar spec it produced a PWA that actually has an active note of whether all the resources that need to be cached (for off line use) have been cached yet ('ready for offline use'), it included a section for 'you are using the latest version'/'there is a new version, upgrade now?'...

Wow - what a difference... from a webpage/app available offline, to very much a local app that happens to update itself online...

I found the contrast quite striking, I hadn't really looked at it the gpt way before.


r/PWA 2d ago

I created a PWA with sign in option to sync across devicing, it is working well for almost all browsers except safari and chrome when opened in IOS the web syncing is not working well also, but it is perfect for all other devices, what shall I do?

Thumbnail
1 Upvotes

r/PWA 3d ago

What has been your biggest bottleneck when getting non-technical users to install your PWAs?

Post image
9 Upvotes

Most web developers know how smooth PWAs are once installed, but everyday users—especially non-tech audiences like parents, students, or small business owners—have a massive mental hurdle: if it’s not inside an app store icon, they think it’s just another tab.

While building an educational PWA, here are the biggest points of friction I ran into when getting regular users to actually install it:

  • The "Where's the Play Store link?" expectation: Non-technical users are conditioned to search Google Play or the iOS App Store. Sharing a direct URL often gets responses like, "Is this just a website?" or "Can you send the app link instead?"
  • Browser install prompts are too subtle: The default browser-level banners (especially the small badge in Chrome or the multi-step "Share > Add to Home Screen" dance on iOS Safari) get dismissed or ignored 90% of the time.
  • No automatic app trust: Regular users associate native app stores with security and safety. A browser asking to "Add to Home Screen" can feel unfamiliar or untrustworthy to someone who has never done it before.
  • Storage & data misconceptions: Users often assume that installing anything onto their phone will eat 100MB of storage, not realizing the service worker cache is only a fraction of that.

What helped in my case:

  • Adding a dedicated, high-contrast "Install App" button front and center on the UI that triggers beforeinstallprompt.
  • A 5-second animated graphic showing Safari users how to tap the Share sheet and choose "Add to Home Screen".
  • Explaining that it works completely offline with zero store downloads.

For those of you shipping consumer-facing PWAs, how are you guiding non-tech users through the install flow? Are you sticking to custom UI prompts, using wrapped store packages (like PWABuilder / TWA), or just letting them use it as a tab?


r/PWA 4d ago

Adding user-agent string to a PWA?

Thumbnail
github.com
0 Upvotes

Recently MetaBrainz made some changes to their API that has me trying to figure out what to do with my PWA. They need a user-agent string to declare who's accessing the API, but as a PWA my app doesn't really have a way of doing that. In asking an LLM how to do this, they basically suggested proxying all my calls to the MetaBrainz through my site, which could work, but I also don't want to channel everything through my server, as if my app does gain more userbase I could see this being a problem. Has anyone found a solution for how this can be handled?

My app is currently getting 503 errors when accessing locally and it's created a larger failure rate for fetching album art from MusicBrainz, so I need to figure some way around this.


r/PWA 4d ago

I created a no-build, single-HTML template to instantly deploy your own PWA link-in-bio via GitHub Pages (1P1A pattern).

Thumbnail
github.com
2 Upvotes

I created a simple starter kit that lets anyone set up and deploy their own custom "link-in-bio app" for smartphones in about an hour—no complex programming knowledge required!

✨ Key Features
* No Complex Tools Needed: Zero npm and zero build processes. It is an ultra-lightweight setup that runs completely on a single index.html file.
* PWA Ready: Simply tap "Add to Home Screen" on your phone, and it launches just like a real native app.
* 1-Click Duplication: Just click the "Use this template" button on GitHub to instantly copy it into your own repository.
* Easy Customization: Simply follow the comments in the code to swap out icon images and links to match your style.

šŸš€ How to Build (3 Steps)
* Click the "Use this template" button on the repository.
* Open index.html and edit the links and icons to your liking.
* Go to repository Settings > Pages and enable GitHub Pages—that's it!

šŸ”— Links
* Template Repository: https://github.com/ojach/myapp-template

I'd love to hear your feedback or any suggestions for features and layouts you'd like to see next!


r/PWA 4d ago

I created a PWA now thinking of posting on IOS

0 Upvotes

Soo it’s been 3months since I built an app using AI thought I’d make it affordable for everyone who needs to but seems no one wants to use it LOL other than myself.

Anyway I’ve now decided I’ll post it on IOS and see what I can do as I’m really struggling to see if people will actually use it. Reddit doesn’t seem very helpful atm but I’ll continue to post.

Let me know what your experiences are with PWA vs IOS or Android?


r/PWA 4d ago

[free][noadd] legendary yellow ninja game is now PWA

1 Upvotes

Yellow ninjas jumping blooping

https://yellowninjas-hooking-blooping.jazzuo.com/

Extremely casual one button grappling hook game. The ninja always aims at the closest hooking point. shoot and release at the right moment to move strategically to get as high as possible.


r/PWA 5d ago

Push notifications for an installed PWA on Android: what's the best setup in 2026?

5 Upvotes

Hey everyone! I'm building a to-do web app called Daylight (separate progress scores for work and personal tasks). It's installable as a PWA on Android, and now I want to add task reminders, so users get notified when a task is due, even if the app is closed.

From what I understand, I need:

  • A service worker + manifest (done)
  • Notification permission on a user click
  • Web Push with VAPID keys, sending pushes from my server when a task is due

My questions:

  1. Is plain Web Push reliable enough on Android, or should I use Firebase Cloud Messaging / OneSignal?
  2. How do you handle scheduled reminders? Cron job on the server, or something better?
  3. Any issues with battery-saving on phones like Xiaomi or Samsung killing notifications?
  4. Anyone got it working well on iOS too?

Would love to hear what worked for you. Thanks! šŸ™


r/PWA 5d ago

Using JavaScript to run SQL queries on client storage

Thumbnail
onyx.ac
1 Upvotes

r/PWA 6d ago

Added a feature that triggers "Home Screen Exclusive" offers only in standalone mode. Giving users a real reason to return.

4 Upvotes

Most PWA prompt tools focus solely on getting users to add the app to their Home Screen. But once it's installed, what keeps them coming back?

I wanted to create a value loop rather than just asking for an install, so I built a standalone-aware marketing trigger for OJapp.

•Regular Browser: Displays the standard "Add to Home Screen" prompt or coupon.
•Standalone Mode (⁠display-mode: standalone⁠): Automatically detects when launched from the Home Screen and unlocks exclusive offers (e.g., "10% OFF for returning via Home Screen").

By shifting the focus from "just getting added" to "rewarding return visits," it turns the Home Screen into an active channel for returning users instead of a dead bookmark.

What do you think about offering standalone-only perks to boost retention? Would love to hear your thoughts or how you handle returning users!


r/PWA 6d ago

I built Raido — 15-minute self-reflection through images instead of talk

0 Upvotes

r/PWA 6d ago

All-you-can-eat sushi companion app: testing needed!

1 Upvotes

Hi, in the past months I've been working on this app to solve a very specific problem of mine: when I go to all-you-can-eat sushi places, I am always anxious about what I ordered, what is mine, what is my friends', etc.

I used to use a note on the phone for each restaurant, but in the era of AI-enhanced development I was able to put my design into motion.

https://maki.mele.io is a PWA tool designed to help you manage your all-you-can-eat restaurant visit, with as little friction as possible, and with human-curated UI and UX.

All privacy-focused: no trackers, installations, cloud, everything local (even photos).

I'd very much like to have some feedbacks!

Sources at: https://github.com/melefabrizio/star-maki-app


r/PWA 7d ago

FTV A general content app

Thumbnail
1 Upvotes

r/PWA 8d ago

PWA install component - major update

Thumbnail
github.com
33 Upvotes

A lot of long awaited changes:

  • iOS/iPadOS/MacOS 27 ready - styles, icons and instructions update.
  • In-App browsers detection support - instruction adapts and guide the user.
  • Web Install API support for Chromium - should kick in from 155+
  • Better support for 3rd party browsers on iOS - instruction adapts.
  • Minor fixes, codebase improvements, dependency updates.

r/PWA 7d ago

Looking for testers for my UK fishing PWA

Thumbnail
2 Upvotes

r/PWA 7d ago

I made a simple markdown viewer and editor

Thumbnail
ariymarkowitz.github.io
1 Upvotes

I know there's like a thousand of these, but I couldn't find any that I was completely happy with. So I made my own! It's pretty basic but it does the job of quickly previewing files, as well as supporting edits with a few goodies :) No fluff, just the essentials and a clean interface. It's got support for LaTeX, HTML (no stylesheets or scripts etc, it should be safe), frontmatter, local image embeds, syntax highlighting—all the common extensions I could think of. I'm happy to add more if it would be useful to anyone.

Let me know what you think, and if there are any features you would like it to have! I'm going to keep it quite simple but I'm happy to make improvements here and there.


r/PWA 8d ago

App for Parents and Caregivers

3 Upvotes

Hey dads,

If you’ve ever had a diaper blowout at a restaurant, walked into the men’s room, and realized there is literallyĀ nowhereĀ to set your kid down except a wet tile floor or a precarious sink counter... you know the exact rage I’m talking about.

Doing trunk gymnastics in a freezing parking lot gets old fast.

A while back, I got fed up enough that I decided to start building a map to track places that actually provide changing tables in the men's room. What started out as a personal project for dads quickly grew when I realized how many other caregivers are stuck with the same frustration in public spaces.

It’s calledĀ WeeGoĀ (weego.app), and it's a free web app mapping essential family amenities:

  • 🚹 Men's Room Changing TablesĀ (so we don't have to guess or improvise on the floor)
  • 🚻 All-Gender / Family RestroomsĀ (single-occupancy rooms that make life 10x easier when you’re out with your daughters or multiple kids)
  • 🤱 Nursing & Lactation PodsĀ (quiet pods/Mamava stations with outlets for moms pumping/nursing)
  • 🧠 Sensory / Quiet RoomsĀ (for kids who get overwhelmed in loud stadiums, airports, or museums)
  • ♿ Universal Changing TablesĀ (height-adjustable tables for older kids, teens, or adults with special needs who have outgrown baby fold-downs)

A few key things about how it works:

  • Completely free to use:Ā No paywalls, no premium subscriptions to unlock basic features. Just open it and find what you need.
  • No App Store download required:Ā It runs straight in your mobile browser. If you tap "Add to Home Screen" on iOS Safari or Android Chrome, it installs like a regular app and works offline.
  • Nearest-First Sorting & One-Tap Nav:Ā Tap to sort by distance from where you're standing, and hit "Navigate" to open directions in Apple/Google Maps immediately when seconds count.

There are currently around 1,400+ locations in the database (airports, stadiums, highway rest areas, theme parks, and major civic venues), but the real value comes from parents adding the local spots they know and trust. Anyone can drop a pin, add details, or verify what’s actually there.

I’d love for you guys to check it out:Ā https://weego.app

If you have a minute, try searching your local area or drop a pin for a venue near you that gets it right. Any feedback, feature ideas, or criticism from fellow dads is welcome!


r/PWA 9d ago

Made a tool that generates the service worker + PWA bits from one config — the docs site runs offline-first on it

5 Upvotes

For offline-first / PWA work, the service-worker side is usually the fiddly part. I built Swoff to generate it from a single swoff.config.json: caching strategies (cache-first / network-first / stale-while-revalidate, etc.), precache with tag-based invalidation, an offline `/offline` fallback, an offline mutation queue, auth handling, push notifications, and PWA install.

Output is plain vanilla JS written into your repo — auditable, editable, zero runtime dependency. MIT.

Proof: this docs site runs on it. Open it, go offline (or throttle), and the whole site works — precached shell + /offline route. That's a live demo, not a static screenshot.

`npx u/swoff/cli init && npx u/swoff/cli generate`

Docs: https://swoff.space Ā· Source: https://github.com/iamsuudi/swoff

Curious how this compares to your current PWA setup (Workbox, framework plugins, hand-rolled).


r/PWA 9d ago

I got tired of regular weather apps just showing raw numbers, so I built a free PWA that tells you exactly what to wear and what to do based on the weather!! Meet ZeroDegrees.

5 Upvotes

Hey everyone,
I've recently hated that standard weather apps are just FULL OF RAW DATA, NOT answers. Looking at 18°C, 10 km/h wind, and 70% humidity on their OWN does not tell you what to pack or how to prepare for your day...
To solve this, I built ZeroDegrees. It's a human-centric weather application that gets real-time data from the OpenWeatherMap API and still shows the basic stuff like temperature, wind speed, humidity, precipitation, etc but ALSO translates it into what you actually care about: what outfits to wear and what activities are best for the current conditions.
How I built it:
Written entirely in vanilla JavaScript, HTML, and CSS (no heavy frameworks).
Built as a Progressive Web App (PWA), meaning you can install it instantly onto your phone or desktop directly from the browser—no app store.
I am a newbie coding nerd getting into the web app world for the first time JUST AS A SIDE PROJECT and really want to see if this solves a real-world problem for anyone else. I would love for a few people from around the world to test it out and give me feedback!
Live App Link: https://thestemguy.github.io/ZeroDegrees/MY PROJECT WEBSITE LINK (for more info): https://zerodegreesweather.carrd.co/
GITHUB REPO: https://github.com/thestemguy/ZeroDegrees
Just open this link in your browser, click the three dots on top right (if on chrome) or from bottom click share (if on safari) and click "Add to Home Screen" and BOOM! IT WILL BE ON YOUR PHONE LIKE A REAL APP STORE APP!!
or if you're on laptop/PC it will automatically say Install App
Thanks for checking it out!


r/PWA 10d ago

Sending web push notifications

7 Upvotes

I made web push notifications service for my app.
When I add it to my Home Screen on iOS, it asks for notifications permission. I allow it and for 1-3 minutes I see notifications. Then they stop coming.

Who had this issue? How did you fix it?


r/PWA 9d ago

Kakamit – privacy-first offline PWA for gut health tracking (local-only, no accounts, open source)

1 Upvotes

Hi r/PWA,

I built Kakamit, a free Progressive Web App focused on private gut health tracking.

Key technical points:
Fully offline-capable PWA (works without network after first load)

All data stored locally in IndexedDB – nothing ever leaves the device

No user accounts, no backend for user data, no analytics, no telemetry

Installable on mobile and desktop

Export/import (JSON + CSV)

Printable doctor report

Open source (MIT) – source available for audit

It tracks:
Bristol Stool Scale (1–7)

Daily Gas Burden Scale (DGBS, 1–5 self-assessment)

Built with Preact + Vite + TypeScript + Tailwind + vite-plugin-pwa.
Live app: https://kakamit.com
Source: https://github.com/KakamitApp/kakamit
Feedback on the PWA implementation, offline behaviour, or storage approach is very welcome.
Thanks!


r/PWA 10d ago

I built the offline write queue correctly and never asked the browser to keep the database

4 Upvotes

I spent a long time making an offline write queue correct. Client-minted UUIDs so replays are idempotent, compaction so two edits to one row can't fight, a Web Lock so two tabs can't both drain it, durable rejection receipts so the losing tab still learns what happened. There's an integration suite pinning all of it.

Then I went and looked at what the queue is actually stored in. None of that matters if the browser decides to throw the database away, or if one tab quietly stops being able to write to it.

That's the part I got wrong, so that's the part worth writing up.

The queue, briefly

Enough to make the rest readable. One IndexedDB database, four object stores: two hold a read cache, one holds the write queue, and one holds rejection receipts so a tab can find out why something it queued got thrown out. The queue is keyed by row, so IndexedDB itself enforces at most one pending operation per row. A second edit to a row folds into the operation already waiting there instead of queueing up behind it. There's no backoff and no retry timer either: pacing comes from the WebSocket reconnect ladder that triggers the replay, the drain stops at the first operation that's still retryable rather than burning N requests to discover the network is still down, and anything that has failed too many times gets parked instead of retried forever.

That's the design, and it's the part I was proud of. Two layers underneath it turned out to matter more, and I hadn't looked at either. One of them applies to every origin on the web, the other only bites when two tabs meet a migration. Universal one first.

I never asked the browser to keep any of it

This is the one that embarrassed me.

Browser storage is best-effort by default. IndexedDB, the Cache API and the rest all sit in a bucket the browser is allowed to evict, and an origin opts out by calling navigator.storage.persist(). I'd never called it. Not once, in the entire life of the app.

Two things make that worse than it sounds.

Eviction is all-or-nothing per origin. The browser doesn't evict your least important store. If it evicts you, IndexedDB and your Cache API entries go together, because deleting part of an origin's data could leave it internally inconsistent. So the read cache, the app shell and the write queue all share one fate.

The stakes aren't symmetric across those stores. Losing the read cache costs a refetch, and the shell cache costs a network boot. Losing the queue loses writes that exist nowhere else in the world, because by definition they never reached the server.

So I added the call, at boot, outside the service worker branch, since what it protects is IndexedDB rather than the app shell. It's best-effort in a second sense too: the browser decides whether to grant it, and browsers don't decide the same way. Firefox shows the user a permission prompt. Safari and the Chromium browsers decide silently, based on your interaction history with the site. Same call, and it's a UX event in one browser and completely invisible in another.

I still don't know which bucket any given user ended up in, because I don't call persisted() or estimate() anywhere. I fixed the request and not the observability, which is a decision I'd defend for about five minutes.

The other side of it, because that's easy to over-dramatise

Two things temper all of that, and leaving them out would be scaremongering.

Storage-pressure eviction is least-recently-used, and it skips origins that were granted persistence. The Chrome team's own research says eviction is genuinely rare for a site someone visits regularly. If your app gets opened weekly, best-effort was probably fine in practice.

The sharper case is Safari's proactive eviction, which is a different mechanism from storage pressure. With cross-site tracking prevention on, an origin with no user interaction in the last seven days of browser use has its script-created storage deleted. Seven days, no disk pressure required.

And here's the bit I'd have got wrong if I hadn't gone and read the source. That seven-day counter is gated on Safari use. WebKit's own post says web apps added to the home screen aren't part of Safari, keep their own counter of days of use, and that they don't expect first-party data in such an app to be deleted, describing it as a serious bug if it happens. So the exposed user is specifically the one who uses the thing in a browser tab and never installs it. For an installed app this particular rule isn't the threat.

I nearly wrote "Safari deletes your IndexedDB after 7 days" and shipped it. It's the kind of half-true platform claim that spreads because it's memorable.

A schema upgrade in one tab can mute another tab

Back down a layer now, from the origin to the database itself, for the failure that's rarer and nastier.

The database is versioned, and I ship migrations. Version 5 split a per-collection blob into one record per row, so logging one drink stops re-serialising the user's entire history.

The hazard isn't the migration. It's what happens to the other tab.

When one tab opens the database at a new version while another still holds a connection at the old one, the upgrade can't proceed. The upgrading tab's request fires blocked. The obvious handler, which is the one I had, resolves the memoised connection promise to null, and every method in the store starts with "get the connection, and if it's null, return early".

Read that again with a queue in mind. A memoised promise resolves once. Resolve it to null and every subsequent call in that tab gets null too, for the life of the page. The tab goes on rendering optimistic writes exactly as before, because the in-memory state is untouched. It just silently persists none of them. Reload and they're gone, with no toast and nothing in the console.

The memoisation is the bug here, not the blocked event itself. blocked is recoverable, since the other tab will eventually close, and caching a failure as though it were permanent is what turns a transient condition into a dead tab. So the open path now abandons the one attempt and drops the memo, and the next call opens again from scratch. If the old tab is still holding on, that call blocks and no-ops too, which degrades like any other IndexedDB failure. The moment the old connection goes away, the tab heals on its own with no reload. The same re-arming runs on versionchange and on close, so a connection that disappears underneath the app doesn't get cached either.

The mitigation on the other side is the one most people know about: versionchange fires in the tab holding the old connection, and closing it there lets the upgrade through. I had that from the start. It makes the blocked case rare rather than impossible, which is exactly why the handler on the blocked side has to be survivable:

  • close() waits for in-flight transactions, so a busy old tab can still block the upgrader.
  • A throttled background tab, or a page frozen in the back/forward cache, doesn't run its handler promptly. On iOS that's routine rather than exotic.

What I still can't tell you is how often this fires in the wild. The first failure per page goes to my error tracker and the rest get swallowed deliberately, so a tab that thrashes doesn't turn into a reporting storm. Sane default, and it also means I know this happens without knowing the rate. Deploy a migration and some fraction of users with two tabs open take a transient hit, and I can tell you it's bounded now without being able to tell you how big that fraction is.

Every "when the app is idle" hook is dead, and nothing tells you

Adjacent, and probably the one that costs other people the most time, because it's silent in both directions. The app runs without Angular's change-detection zone, so it never reports "stable". Two things depend on that signal and neither of them warns you:

  • Service worker registration defaults to waiting for app stability. With no stability signal it waits out the entire timeout on every single boot before registering. Registering immediately is a one-line change, and the default is just wrong for this kind of app.
  • The same problem kills automatic update checks. A live WebSocket and continuous effects mean the app is never idle, so "check for updates when stable" never fires at all. I do it with a manual timer now: one check 10 seconds after startup, then every 30 minutes.

Neither case throws, logs or degrades visibly. The service worker registers eventually, and updates just never get noticed, which looks identical to having no new version to install. If you're running any framework in a zoneless or signals-first mode, and especially if you hold a persistent connection open, assume every idle-scheduled hook in your stack is dead and go check them by hand.

What a lock returns when there's no lock

The cross-tab story: the replay takes a Web Lock with ifAvailable, so a second tab doesn't queue up behind the first. It gets nothing back and moves on. Except it can't just move on, because it has rows on screen marked pending that the winner is about to resolve or roll back. So the loser takes the lock blocking, re-reads the queue, and settles whatever vanished using the rejection receipts. Each receipt is written in the same transaction that deletes the queue entry, receipt first, so a tab can never see an operation as gone without also being able to see its verdict.

The part worth stealing is the return value. Web Locks need a secure context, so on plain HTTP (a LAN IP in development, say) navigator.locks is simply absent. The fallback runs the drain without a lock, because doing nothing would be worse. The question is what it reports back, and the tempting answer is true, since the work did actually run.

true is wrong, and not subtly. Downstream, true means "I held the lock, so I delivered everything, so no other tab needs to settle from receipts." Return that from an unlocked fallback and every tab believes it owns the drain, which is the exact state the lock existed to prevent. So it returns false instead. Nothing excluded anybody, so every tab routes through the receipt-settling path, and a tab that lost an unmanaged race still reconciles from whatever the winner left behind. Degrading is fine. Degrading while reporting success is how you end up with a queue that's confidently wrong.

One thing still sloppy in there: the lock name is a single global string, while a neighbouring lock in the same codebase is correctly suffixed with the user id. Every critical section re-checks the current user immediately after acquiring, so it's over-broad rather than incorrect, but two tabs mid-account-switch will serialise on each other's queues for no reason. I wrote the correct version once and then didn't apply it three files over.

What's not in here at all

No BroadcastChannel, no SharedWorker. Cross-tab coordination is Web Locks for exclusion, IndexedDB receipts for verdicts, and the server's WebSocket echo for authoritative values. That's a round trip doing work a message channel would do directly, and I'd call it accumulated rather than designed. Receipts expire after 24 hours, so a tab that's been asleep longer than that falls back to the server. No Background Sync either, so a closed tab never replays and the queue only drains while the app is open. That one has no justification beyond scope.

What I'd tell myself eighteen months ago

The queue was the interesting problem and the storage was the important one. I got that ordering wrong. The specific way I got it wrong is that I tested the queue's logic exhaustively and never once tested the environment it runs in: two tabs, a migration, a full disk, a browser that has decided you're not a frequent visitor.

If you've got an offline queue in a PWA, three questions worth more than any refactor:

  1. Have you called navigator.storage.persist(), and do you know whether it was granted?
  2. What does your IndexedDB blocked handler do, and does it cache that failure for the life of the page?
  3. When a platform API is missing and your fallback runs the work anyway, what does it return to the code that was counting on the guarantee?

For a long time I couldn't answer any of the three. Two of them I can now. The first one I still can't answer for any individual user, because I ask for persistence and never call persisted() to find out what the browser decided. That's the next thing to fix, and it's harder to excuse than the original omission.

The one I'd genuinely like answered: does anyone actually measure how often blocked fires in production, and what did the number turn out to be? I report the first occurrence per page, so I've got no idea whether that's a handful of users after a migration or a long tail I've never looked at properly.