Designing plan.hwmoon.com as a local-first PWA before adding cloud accounts
The first infrastructure decision record for plan.hwmoon.com: one responsive PWA, local-first storage for free users, and a staged Azure backend for Plus backup, restore, and later sync.
- Hosting
- Azure Static Web Apps
- Client storage
- IndexedDB
- Backend phase
- Planned Azure Functions
On this page
plan.hwmoon.com is starting as a small daily planning product, but the first infrastructure decision is not small in impact: do not make the free product depend on a database too early.
The first version should be a local-first PWA. It should work on desktop and mobile from the same URL, store daily planning data in the browser, and avoid backend cost until the product has enough daily usefulness to justify cloud features.
The architecture decision is:
Use one responsive PWA at
plan.hwmoon.com. Keep free planning local. Add accounts and cloud backup only when the Plus tier is ready.
Product Shape
The product is a calm daily planner. It tracks today’s tasks, schedule, mood, and end-of-day reflection. That data is personal, frequently edited, and valuable mainly to the person using the browser in front of them.
That creates a useful constraint. The free tier does not need server storage on day one.
| Tier | Storage | Cloud feature |
|---|---|---|
| Free | Browser IndexedDB | none |
| Plus | Local storage plus encrypted cloud backup | backup and restore |
| Pro later | Local storage plus cloud sync | multi-device continuity |
This keeps the cost model honest. Server cost begins when the product offers server value.
Why Not m.plan.hwmoon.com
The mobile experience should not be a separate mobile subdomain for the first version.
A separate m. domain would create extra work in routing, auth, analytics, cache policy, deployment, and support. The product behavior would still be the same planner. That is not enough reason to split the surface.
The better first architecture is:
plan.hwmoon.com
-> responsive PWA
-> desktop web
-> mobile web
-> installed PWA
-> future Android shell
If Android distribution becomes important, the same PWA can be packaged later with Trusted Web Activity or Capacitor. The product keeps one URL, one content security model, and one application state model.
Current Baseline
The current baseline is intentionally simple:
| Area | Current choice |
|---|---|
| Host | Azure Static Web Apps |
| Domain | plan.hwmoon.com |
| Deploy | Azure DevOps pipeline |
| App type | static PWA |
| Client data | IndexedDB |
| Legacy migration | localStorage to IndexedDB |
| Account gate | local browser-profile lock |
The local gate is not real account authentication. It is only a local device lock. That distinction matters because cloud backup, account recovery, and subscription entitlement need a durable server identity.
Target Architecture
The staged target is:
Browser / installed PWA
|
| HTTPS
v
Azure Static Web Apps: plan.hwmoon.com
|
| /api/* after Plus backend is enabled
v
Azure Functions API
|
+--> auth identity
+--> metadata store
+--> encrypted backup payloads in Blob Storage
+--> Key Vault for secrets
+--> Application Insights for health and failures
The API should not appear because “apps need a backend.” It should appear because a paid feature needs it.
Backend Build Order
The first backend feature should be backup and restore, not real-time sync.
Backup and restore has a simpler mental model:
- create a versioned local export
- upload an encrypted snapshot
- list available snapshots
- restore a selected snapshot to a new browser or device
Sync is harder. It needs conflict resolution, per-device cursors, offline merge behavior, and a clear user experience when two devices edit the same day differently. That complexity should wait.
Storage Choice
The recommended first backend storage shape is:
| Data | Service |
|---|---|
| User metadata | Azure Table Storage or Cosmos DB |
| Backup snapshot index | Azure Table Storage or Cosmos DB |
| Encrypted backup payload | Azure Blob Storage |
| Secrets | Azure Key Vault |
| API telemetry | Application Insights |
The metadata store should hold only operational data: user ID, entitlement, device ID, backup ID, schema version, timestamp, payload size, and blob location.
It should not store task text, schedule text, or reflection text as queryable metadata. Those belong inside the encrypted backup payload.
Authentication Timing
Real authentication is necessary before paid cloud backup. It is not necessary before the local MVP proves daily value.
The practical sequence is:
| Phase | Auth state |
|---|---|
| MVP | local device lock only |
| Beta | sign in, sign out, session check |
| Plus | entitlement check and backup ownership |
| Paid production | account deletion and billing state handling |
Azure Static Web Apps authentication may be enough for early provider-based login. If email/password, advanced account recovery, or custom account flows become important, the product can move to a backend-owned auth provider later.
Operations
The MVP needs build and route confidence. The Plus backend needs service confidence.
Before backend:
- build check
- PWA manifest and service worker check
- mobile viewport check
- local export/import compatibility check
- warning when browser storage is unavailable
After backend:
- backup API success rate
- restore failure alert
- blob read/write failure alert
- payment webhook failure alert
- storage growth monitoring
- resource group budget alert
Operational alerts should stay quiet. Daily planner usage events do not need Slack noise. Backup failures, restore failures, deployment failures, and billing webhook failures do.
Production Separation
The first beta can use the existing development foundation resources. Paid production should have a cleaner boundary.
Recommended production shape:
| Resource | Purpose |
|---|---|
rg-hwmoon-plan-prod-p01 | Plan production boundary |
swa-hwmoon-plan-prod-p01 | public PWA host |
func-hwmoon-plan-prod-p01 | backend API |
sthwmoonplanprodp01 | backup payloads and metadata |
kv-hwmoon-plan-prod-p01 | secrets |
appi-hwmoon-plan-prod-p01 | telemetry |
The reason is not scale. It is privacy, permissions, cost tracking, deletion policy, and paid-user operational discipline.
Decision Summary
The first infrastructure architecture is deliberately staged:
- one URL, not a separate mobile subdomain
- responsive PWA first
- IndexedDB for free local planning
- no paid database until cloud value exists
- auth before backup
- backup and restore before sync
- production separation before paid launch
This keeps plan.hwmoon.com small enough to ship and clear enough to grow. The important thing is not to build the final cloud platform early. The important thing is to make the first daily planning loop worth opening tomorrow.