Client PWA
The client package is the end-user Green Goods web app. It owns browser/public routes and the installed PWA shell, while hooks, providers, stores, auth helpers, domain logic, and shared components come from @green-goods/shared.
What this package owns
- Public/browser routes through the public shell path.
- Installed/authenticated PWA routes through the protected app shell and bottom app bar.
- Mobile-first work submission surfaces, offline queue UX, install guidance, profile/settings, and route composition.
- Package-local views and components only when the behavior is client-specific.

Builder contract
- Keep reusable hooks and providers in
@green-goods/shared; do not add client-local copies. - Preserve the offline-first queue flow for work submissions, including passkey users.
- Use shared auth APIs and app defaults; do not treat wallet chain state as the app source of truth.
- Manage media object URLs through shared media utilities so preview blobs are cleaned up.
- Add any user-facing string to
en,es, andpt.
Route ownership
The browser-facing editorial site and the installed app intentionally share the same origin, but not the same route ownership boundary.
- Public/browser routes stay on the public shell:
/,/gardens,/gardens/:id,/fund,/impact,/actions,/cookies,/glossary. - Installed PWA routes are canonical under
/home:/home,/home/login,/home/garden,/home/profile, plus garden detail routes under/home/:id. - Legacy app routes
/login,/garden, and/profileare compatibility redirects in PWA presentation mode; browser presentation redirects them back to/. - The browser-origin manifest keeps
id: "/"for install migration safety, but scopes the installed PWA to/home. - IPFS/static builds keep relative assets and hash routing, so the same app routes are represented as
./#/home,./#/home/garden, and./#/home/profile.
For production browser-origin builds, the service worker registers at /home, preserves the Workbox precache,
serves the app shell for offline /home navigations, and performs a one-time cleanup of legacy root-scoped Green
Goods service workers.
Commands
cd packages/client
bun run test
bun run build
bun run lint
Run repo-level quick verification when a client change reaches into packages/shared.
Testing on a real device
The client is a mobile-first PWA — testing on a real phone is part of the dev workflow. When bun run dev is running, a cloudflared tunnel automatically creates a temporary public HTTPS URL pointing to your local client on port 3001.
How it works:
- Run
bun run dev— thetunnelPM2 service starts alongside the client - Open
https://localhost:3001on your laptop - The landing page QR code automatically shows the tunnel URL
- Scan the QR code with your phone — full PWA with service worker, install prompt, and passkey auth
If cloudflared is not installed, the tunnel service exits silently and the QR code falls back to window.location.origin. Install it with brew install cloudflared.
Service worker update behavior
The client uses vite-plugin-pwa with registerType: "prompt" — users control when updates apply. When a new service worker is detected:
- A persistent "Update available" toast appears (stays until acted on)
- User taps "Update now" → the waiting SW calls
skipWaiting()→ page reloads - On activation, the new SW clears stale runtime caches (
js-cache,indexer-cache,graphql-cache) to prevent serving old content - React Query's IndexedDB cache is busted via
VITE_APP_VERSIONto prevent stale data hydration
The ipfs-cache and image-cache are preserved across updates since IPFS CIDs are immutable and images are static.
Next page
Next best action
Most client behavior depends on shared hooks and app-wide domain models.
Open shared package