A couple of months ago, I had a Python script that ran on my desk. Today the same project is an installable app on the Microsoft Store — with Google Play and iOS on the way — and it never got a native rewrite or a cloud host for the front-end.
I learned how to do this from scattered blog posts, docs, and forum answers, and I hit a pile of small traps that cost hours each. So I wrote down the whole path and open-sourced it. This is the short version of the story; the full step-by-step guide is on GitHub.
What I was starting with
My project is a personal research dashboard for public prediction markets — it watches which proven traders are moving, cross-checks that against bookmaker odds and news, and grades every one of its own calls against reality. (It's a research tool — it places no bets and holds no money. Nothing here is financial advice.)
The important part for this post: it was a plain Python backend rendering an HTML page, running on my home PC. Great on my desk. Invisible everywhere else.
The one idea that unlocked it
Your web app + a web manifest + a service worker = a PWA (Progressive Web App). A PWA is installable on every platform. And a free tool called PWABuilder packages that same PWA into native store bundles:
| Store | Bundle | Difficulty | Cost |
|---|---|---|---|
| Microsoft Store | MSIX | ⭐ easiest | Free (individual fee waived) |
| Google Play | Android .aab (TWA) |
⭐⭐ | $25 once + hoops |
| iOS App Store | Xcode project | ⭐⭐⭐ | $99/yr + a Mac |
The part that surprised me most: you don't need a web host. A local app can be given a stable public HTTPS address with a free named Cloudflare Tunnel. That URL is what the stores point at.
Recommended order: PWA → Microsoft Store → Google Play → iOS. Ship the free, easy one first for the morale win.
The gotchas that actually cost me time
These are the ones I wish someone had put in bold:
-
Service workers only register over HTTPS or
localhost. Testing over a plainhttp://<LAN-IP>address silently skips them and you'll think it's broken. -
A 404 on
sw.jsbreaks everything downstream — and the app still looks fine. If your server uses an allow-list, it has to actually servesw.js, your icons, the manifest, and/.well-known/assetlinks.jsonat those exact paths. - The store app must load with no login. A store app opens for strangers, so your public view can't require a secret key. Keep any private/owner view behind auth.
-
Google re-signs your Android bundle. So the fingerprint that goes in
assetlinks.jsonis Google's app-signing SHA-256 (from Play Console), not the upload key PWABuilder hands you. This one silently breaks deep-link verification. - Package IDs are permanent on both stores. Decide before your first upload.
- Back up your Android signing keystore + passwords. Lose them and you can never update the app.
What I shared, and what I kept
Here's a line I think more solo builders should draw deliberately: I shared the mechanics, not the edge.
- Public: the PWA-to-stores guide, and a clean reference skeleton showing how a self-grading research agent fits together — the loop, the grading/trust-ranking system, and how I route news through an LLM into a probability.
-
Private: the actual scoring math, the prompts, the wallet lists — the stuff that makes it good. Every one of those spots in the open-source skeleton is a labeled
>>> PLACEHOLDER.
Sharing the frame costs you nothing and helps a lot of people. The recipe stays yours.
If you want to do the same
- 📖 The full playbook, with every gotcha marked: ship-pwa-to-stores
- 🧩 The research-agent reference skeleton: prediction-agent-skeleton
- 🎯 The project itself, and how it was built: project page · live app
I built this solo, part-time, under the name PVG Production. If any of it saves you a few hours, that's the whole point — and if you find the next gotcha, open a PR.
Research tool only — not financial advice. Everything it produces is evidence to weigh, and every decision stays with a human.
Top comments (0)