diff --git a/.claudeignore b/.claudeignore
index fd98048..1938815 100644
--- a/.claudeignore
+++ b/.claudeignore
@@ -10,8 +10,6 @@ qa-screens/
redesign_stitch/
stitch_petsphere_app_redesign/
*.xml
-*.html
-*.jsx
# ==========================================
# 2. FLUTTER BUILD & CACHE
diff --git a/.cursor/hooks/state/continual-learning-index.json b/.cursor/hooks/state/continual-learning-index.json
deleted file mode 100644
index 5c07df3..0000000
--- a/.cursor/hooks/state/continual-learning-index.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "version": 1,
- "files": {
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\ce1b2c8d-81a4-4639-90f8-3721e6c503a7\\ce1b2c8d-81a4-4639-90f8-3721e6c503a7.jsonl": 1779035133681,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\94643488-4659-4d0e-9545-49a79437f020\\94643488-4659-4d0e-9545-49a79437f020.jsonl": 1778967578761,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\36e3088e-0d5c-45e5-b1ba-d1054303de94\\36e3088e-0d5c-45e5-b1ba-d1054303de94.jsonl": 1779039991962,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\bced3497-a066-4257-b439-2b857e493c49\\bced3497-a066-4257-b439-2b857e493c49.jsonl": 1778888950579,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\80eedde5-7184-424c-b917-a680f1ff70a2\\80eedde5-7184-424c-b917-a680f1ff70a2.jsonl": 1778892745538,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\87c931dd-27e3-40aa-bdc6-3274831f9214\\87c931dd-27e3-40aa-bdc6-3274831f9214.jsonl": 1778958857308,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\60e1a481-cb4c-4ac2-aa03-bfae7cefa5fe\\60e1a481-cb4c-4ac2-aa03-bfae7cefa5fe.jsonl": 1778881486521,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\bb710a65-baaf-4764-9eae-dacedace3006\\bb710a65-baaf-4764-9eae-dacedace3006.jsonl": 1778786794129,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\274c391d-7a36-49d5-87be-4df8b522bd16\\274c391d-7a36-49d5-87be-4df8b522bd16.jsonl": 1778800364415,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\194eaf83-5b12-43e4-b1ea-318bbf90c26c\\194eaf83-5b12-43e4-b1ea-318bbf90c26c.jsonl": 1778954732395,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\25b68db3-c204-4def-aead-73ff3500c794\\25b68db3-c204-4def-aead-73ff3500c794.jsonl": 1778876459233,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\8c5433a9-d387-4e9b-ba3a-1e1e006c37bf\\8c5433a9-d387-4e9b-ba3a-1e1e006c37bf.jsonl": 1778856406418,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\e6552f8a-c6c0-4b71-b118-14f55640ae1f\\e6552f8a-c6c0-4b71-b118-14f55640ae1f.jsonl": 1778776581760,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\16628f4f-057e-4274-96e0-ec9ad3d893be\\16628f4f-057e-4274-96e0-ec9ad3d893be.jsonl": 1778882210597,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\0148be8a-b886-48f4-bdd3-67d076c8182c\\0148be8a-b886-48f4-bdd3-67d076c8182c.jsonl": 1778792789903,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\7abfd4c7-d454-4cf6-a391-333321a013ad\\7abfd4c7-d454-4cf6-a391-333321a013ad.jsonl": 1779058557683,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\5acfed98-7cbf-4420-ac3d-091356b8e44f\\5acfed98-7cbf-4420-ac3d-091356b8e44f.jsonl": 1778871687683,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\d7bd1b6a-f748-47f6-87ac-953246413613\\d7bd1b6a-f748-47f6-87ac-953246413613.jsonl": 1778799809267,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\2d86f00f-6bfb-451f-928c-ff0fea1f79d2\\2d86f00f-6bfb-451f-928c-ff0fea1f79d2.jsonl": 1779034182814,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\6460028d-51b8-402d-8b25-5fbcae4596f2\\6460028d-51b8-402d-8b25-5fbcae4596f2.jsonl": 1778961242286,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\88c2a075-4e77-4d24-81f3-b7ba5b774ac4\\88c2a075-4e77-4d24-81f3-b7ba5b774ac4.jsonl": 1778891774309,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\9ebdd98e-260d-475c-999c-dd06afa742cd\\9ebdd98e-260d-475c-999c-dd06afa742cd.jsonl": 1778802413316,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\5bead4b5-9471-461d-ab9a-957f4c1c2490\\5bead4b5-9471-461d-ab9a-957f4c1c2490.jsonl": 1778892105848,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\dab4e7c9-f531-4cb9-a593-62d5c029e54b\\dab4e7c9-f531-4cb9-a593-62d5c029e54b.jsonl": 1778879297617,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\8ea6431f-f572-411a-98e3-0dc5552c5a51\\8ea6431f-f572-411a-98e3-0dc5552c5a51.jsonl": 1778783272217,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\babe61d3-90ce-4696-be96-37e1e4e0f971\\babe61d3-90ce-4696-be96-37e1e4e0f971.jsonl": 1778891057278,
- "C:\\Users\\syedr\\.cursor\\projects\\g-GitHub-petfolio\\agent-transcripts\\09fd1bb0-4e5e-4e5d-a591-a22fac645ab3\\09fd1bb0-4e5e-4e5d-a591-a22fac645ab3.jsonl": 1778873095891
- }
-}
diff --git a/.cursor/hooks/state/continual-learning.json b/.cursor/hooks/state/continual-learning.json
deleted file mode 100644
index 34fa82f..0000000
--- a/.cursor/hooks/state/continual-learning.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "version": 1,
- "lastRunAtMs": 1779058550389,
- "turnsSinceLastRun": 6,
- "lastTranscriptMtimeMs": 1779058549884.8618,
- "lastProcessedGenerationId": "2559ad56-7755-4a6c-a9c0-f183a58b10cb",
- "trialStartedAtMs": null
-}
diff --git a/.cursor/settings.json b/.cursor/settings.json
new file mode 100644
index 0000000..67c10f0
--- /dev/null
+++ b/.cursor/settings.json
@@ -0,0 +1,16 @@
+{
+ "plugins": {
+ "stripe": {
+ "enabled": true
+ },
+ "supabase": {
+ "enabled": true
+ },
+ "modern-web-guidance": {
+ "enabled": true
+ },
+ "firebase": {
+ "enabled": true
+ }
+ }
+}
diff --git a/.cursorignore b/.cursorignore
index 631c1be..1938815 100644
--- a/.cursorignore
+++ b/.cursorignore
@@ -1,27 +1,18 @@
# ==========================================
-# 1. SECURITY & SECRETS (CRITICAL)
+# 1. PETFOLIO SPECIFIC NOISE (CRITICAL TO IGNORE)
# ==========================================
-
-*.key
-*.pem
-credentials.json
-supabase/.temp/
-
-# ==========================================
-# 2. PETFOLIO SPECIFIC NOISE
-# ==========================================
-# Prevents AI from reading massive UI mockups & QA logs
-claude_design/
+# These folders contain hundreds of HTML, JSX, XML, and PNG
+# files from UI audits and design stitches. They will flood
+# the context window and cause severe token bloat.
+docs/resolved-deprecated/claude_design/
docs/logs/
qa-screens/
redesign_stitch/
stitch_petsphere_app_redesign/
*.xml
-*.html
-*.jsx
# ==========================================
-# 3. FLUTTER BUILD & CACHE
+# 2. FLUTTER BUILD & CACHE
# ==========================================
build/
.dart_tool/
@@ -29,68 +20,96 @@ build/
.pub/
# ==========================================
-# 4. CODE GENERATION
+# 3. CODE GENERATION
# ==========================================
-# Forces AI to rely on your models, not the generated outputs
+# Ignore generated files so the AI doesn't try to edit them directly
*.g.dart
*.freezed.dart
*.mocks.dart
*.part.dart
# ==========================================
-# 5. NATIVE PLATFORM DIRECTORIES
-# ==========================================
-android/.gradle
-android/.kotlin
-android/gradle-wrapper.jar
-android/captures/
-android/gradlew
-android/gradlew.bat
-android/local.properties
-android/GeneratedPluginRegistrant.java
-android/.cxx/
-
-ios/**/dgph
-ios/*.mode1v3
-ios/*.mode2v3
-ios/*.moved-aside
-ios/*.pbxuser
-ios/*.perspectivev3
-ios/**/*sync/
-ios/.sconsign.dblite
-ios/.tags*
-ios/**/.vagrant/
-ios/**/DerivedData/
-ios/Icon?
-ios/**/Pods/
-ios/**/.symlinks/
-ios/profile
-ios/xcuserdata
-ios/**/.generated/
-ios/Flutter/App.framework
-ios/Flutter/Flutter.framework
-ios/Flutter/Flutter.podspec
-ios/Flutter/Generated.xcconfig
-ios/Flutter/ephemeral/
-ios/Flutter/app.flx
-ios/Flutter/app.zip
-ios/Flutter/flutter_assets/
-ios/Flutter/flutter_export_environment.sh
-ios/ServiceDefinitions.json
-ios/Runner/GeneratedPluginRegistrant.*
-
-
-
-web/
-macos/
-windows/
-linux/
-
-# ==========================================
-# 6. ASSETS & MEDIA
-# ==========================================
-assets/
-google_fonts/
+# 4. NATIVE PLATFORM DIRECTORIES
+# ==========================================
+# Unless you are specifically asking the agent to write Kotlin/Swift,
+# keep these ignored so it focuses on the `lib/` directory.
+
+# Android related
+**/android/**/gradle-wrapper.jar
+.gradle/
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+**/android/key.properties
+*.jks
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/.last_build_id
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# macOS
+**/Flutter/ephemeral/
+**/Pods/
+**/macos/Flutter/GeneratedPluginRegistrant.swift
+**/macos/Flutter/ephemeral
+**/xcuserdata/
+
+# Windows
+**/windows/flutter/generated_plugin_registrant.cc
+**/windows/flutter/generated_plugin_registrant.h
+**/windows/flutter/generated_plugins.cmake
+
+# Linux
+**/linux/flutter/generated_plugin_registrant.cc
+**/linux/flutter/generated_plugin_registrant.h
+**/linux/flutter/generated_plugins.cmake
+
+# Coverage
+coverage/
+
+# Symbols
+app.*.symbols
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
+!/dev/ci/**/Gemfile.lock
+
+# ==========================================
+# 5. ASSETS & MEDIA
+# ==========================================
+
*.png
*.jpg
*.jpeg
@@ -101,13 +120,23 @@ google_fonts/
*.zip
# ==========================================
-# 7. IDE & ENVIRONMENT
+# 6. SUPABASE LOCAL STATE
+# ==========================================
+supabase/.temp/
+
+# ==========================================
+# 7. DEPENDENCY LOCKS
+# ==========================================
+pubspec.lock
+skills-lock.json
+
+# ==========================================
+# 8. IDE & ENVIRONMENT
# ==========================================
.idea/
.vscode/
.claude/
-.cursor/hooks/
+
+.cursor/
*.iml
.metadata
-pubspec.lock
-skills-lock.json
\ No newline at end of file
diff --git a/.env.example b/.env.example
index e31df98..8ec36b2 100644
--- a/.env.example
+++ b/.env.example
@@ -1,4 +1,18 @@
SUPABASE_URL=
SUPABASE_ANON_KEY=
STRIPE_PUBLISHABLE_KEY=
-NVIDIA_API_KEY=
\ No newline at end of file
+NVIDIA_API_KEY=
+
+FIREBASE_VAPID_KEY=
+FIREBASE_PROJECT_ID=petfolio-v1
+FIREBASE_MESSAGING_SENDER_ID=86798095066
+FIREBASE_WEB_API_KEY=AIzaSyDG5_rufMTdwV9X2wc7M5YNyEkWwXN8tGM
+FIREBASE_WEB_APP_ID=1:86798095066:web:61021d3c9119434a68cbe3
+FIREBASE_AUTH_DOMAIN=petfolio-v1.firebaseapp.com
+FIREBASE_STORAGE_BUCKET=petfolio-v1.firebasestorage.app
+
+FCM_DISPATCH_SECRET=
+
+# GitHub Actions (deploy-web.yml): gh secret set -f .env -R CodeStorm-Hub/petfolio
+# Also set VERCEL_TOKEN manually: gh secret set VERCEL_TOKEN -R CodeStorm-Hub/petfolio
+# Supabase hosted checkout (production): npx supabase secrets set PUBLIC_APP_ORIGIN=https://petfolio.live --project-ref jqyjvhwlcqcsuwcqgcwf
diff --git a/.firebaserc b/.firebaserc
new file mode 100644
index 0000000..9dfe387
--- /dev/null
+++ b/.firebaserc
@@ -0,0 +1,5 @@
+{
+ "projects": {
+ "default": "petfolio-v1"
+ }
+}
diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml
index 5f6a910..e7e94b4 100644
--- a/.github/workflows/deploy-web.yml
+++ b/.github/workflows/deploy-web.yml
@@ -1,14 +1,18 @@
-name: Deploy Flutter Web to Vercel
+name: Deploy Flutter Web to Vercel (production)
on:
push:
branches: [main]
- pull_request:
- branches: [main]
+ workflow_dispatch:
+
+concurrency:
+ group: deploy-web-production
+ cancel-in-progress: true
jobs:
build-and-deploy:
runs-on: ubuntu-latest
+ timeout-minutes: 30
env:
VERCEL_ORG_ID: team_lC8aTJK0XiU9qDfaHeTfCJs6
@@ -21,33 +25,55 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
- flutter-version: '3.x'
+ flutter-version: '3.44.0'
channel: stable
cache: true
+ - name: Enable Flutter web
+ run: flutter config --enable-web
+
- name: Install dependencies
run: flutter pub get
+ - name: Materialize CI .env from secrets
+ run: |
+ umask 077
+ {
+ echo "SUPABASE_URL=${{ secrets.SUPABASE_URL }}"
+ echo "SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }}"
+ echo "STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PUBLISHABLE_KEY }}"
+ echo "NVIDIA_API_KEY=${{ secrets.NVIDIA_API_KEY }}"
+ echo "FIREBASE_VAPID_KEY=${{ secrets.FIREBASE_VAPID_KEY }}"
+ echo "FIREBASE_PROJECT_ID=${{ secrets.FIREBASE_PROJECT_ID }}"
+ echo "FIREBASE_MESSAGING_SENDER_ID=${{ secrets.FIREBASE_MESSAGING_SENDER_ID }}"
+ echo "FIREBASE_WEB_API_KEY=${{ secrets.FIREBASE_WEB_API_KEY }}"
+ echo "FIREBASE_WEB_APP_ID=${{ secrets.FIREBASE_WEB_APP_ID }}"
+ echo "FIREBASE_AUTH_DOMAIN=${{ secrets.FIREBASE_AUTH_DOMAIN }}"
+ echo "FIREBASE_STORAGE_BUCKET=${{ secrets.FIREBASE_STORAGE_BUCKET }}"
+ } > .env
+
+ - name: Sync Firebase web config for service worker
+ run: dart run tool/sync_firebase_web_config.dart
+
- name: Build Flutter Web (release)
run: |
flutter build web --release \
--dart-define=SUPABASE_URL=${{ secrets.SUPABASE_URL }} \
--dart-define=SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }} \
--dart-define=STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PUBLISHABLE_KEY }} \
- --dart-define=NVIDIA_API_KEY=${{ secrets.NVIDIA_API_KEY }}
+ --dart-define=NVIDIA_API_KEY=${{ secrets.NVIDIA_API_KEY }} \
+ --dart-define=FIREBASE_VAPID_KEY=${{ secrets.FIREBASE_VAPID_KEY }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
- - name: Vercel build (preview)
- if: github.event_name == 'pull_request'
- run: npx vercel build --yes --token=${{ secrets.VERCEL_TOKEN }}
+ - name: Install Vercel CLI
+ run: npm install -g vercel@latest
- name: Vercel build (production)
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- run: npx vercel build --prod --yes --token=${{ secrets.VERCEL_TOKEN }}
-
- - name: Deploy preview to Vercel (PRs)
- if: github.event_name == 'pull_request'
- run: npx vercel deploy --prebuilt --yes --token=${{ secrets.VERCEL_TOKEN }}
+ run: vercel build --prod --yes --token=${{ secrets.VERCEL_TOKEN }}
- - name: Deploy to Vercel production (main only)
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- run: npx vercel deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }}
+ - name: Deploy to Vercel production
+ run: vercel deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 681da64..48c63c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -119,6 +119,9 @@ app.*.symbols
!/dev/ci/**/Gemfile.lock
.env
.env.local
+*firebase-adminsdk*.json
+petfolio-v1-firebase-adminsdk*.json
+supabase/.secrets.fcm.env
# Generated seeding output from scripts/generate_inserts.dart — not a versioned migration
inserts.sql
diff --git a/AGENTS.md b/AGENTS.md
index 9d73dbb..cbe3d13 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -12,11 +12,13 @@ You are an expert Flutter and Supabase developer. For the PetFolio project, you
## Learned User Preferences
- When applying Supabase schema to the hosted project, prefer the Supabase MCP migration path when available; if using the Supabase CLI, prefix commands with `npx` (for example `npx supabase db push`).
-- After completing a distinct care, marketplace, or backend phase, keep `progress.md` updated and use `/remember` to persist high-signal context before starting the next phase.
+- After completing a distinct care, marketplace, backend, or PWA/web phase, keep `progress.md` updated and use `/remember` to persist high-signal context before starting the next phase.
+- Web/PWA work follows phased rollout in `PWA_WEB_AUDIT.md`; do not change Android APK behavior—gate web-only paths with `kIsWeb` and `lib/core/platform/`.
- Avoid importing `router.dart` from screens that `router.dart` already imports; use literal paths or query strings for deep links to prevent circular imports.
- For optimistic UI and one-off actions (care task toggles, Stripe seller onboarding), surface failures with `AppSnackBar.showError`; do not set long-lived providers (e.g. `myShopProvider`) to `AsyncValue.error` for transient failures.
- When a subagent result is already visible in the UI, avoid re-summarizing it unless multi-task synthesis is needed; a short third-person completion note is enough, and avoid repeating the same confirmation every turn.
- When asked to save audits, reviews, or comparable findings, write them as Markdown under the repository root unless a different path is specified.
+- For hosted FCM setup, use `tool/set_fcm_supabase_secrets.ps1` and `firebase-instructions.md`; `FIREBASE_SERVICE_ACCOUNT_JSON` in Supabase must be minified valid JSON and `FCM_DISPATCH_SECRET` must match `private.fcm_internal_config`.
## Learned Workspace Facts
@@ -30,3 +32,5 @@ You are an expert Flutter and Supabase developer. For the PetFolio project, you
- Social screen header Messages navigates to `/matching`.
- PostGIS matching: `/matching/inbox`, `/matching/chat/:threadId`; `swipes`/`matches` and `matching_discovery_candidates` RPC (`MatchingRepository`); `chat_threads.mutual_match_id` + `ensure_chat_thread_for_match` (legacy `participant_*`/`match_request_id` possible). RPC needs `is_discoverable IS TRUE` and non-null `pets.location`; `petHasLocation` uses `.not('location', 'is', null)`. `mutualMatchInsertStreamProvider` + `MatchCelebrationOverlay`; `matchPreferenceControllerProvider` + `discoveryCandidatesControllerProvider` (replenish when stack drops below 5, ~450ms debounced `invalidateSelf()` — do not `ref.watch` prefs in `build()` on slider drags). Location: `location_service.dart`; denied/services-off empty state on `MatchingScreen`. Riverpod 3: generated notifiers omit type params (`extends _$Foo`); use `AsyncValue.value`, not `.valueOrNull`.
- Multi-vendor marketplace (`docs/claude-handoff.md`): cart `itemsByShop` + per-shop `startCheckoutForShop`; **Discover Shops** via `shopListProvider` → `/shop/:id`; seller routes `/seller`, `/seller/setup`, `/seller/onboarding`, products/orders. `ShopRepository.startOnboarding` calls Edge Function `stripe-onboard-vendor` with `functions.invoke(..., body: {'shopId'})` (not `.rpc()`), reads `accountLinkUrl`; platform Stripe account needs **Connect** enabled.
+- **FCM push:** `FcmService` + `user_fcm_devices`; Edge `send-fcm-notification` / `_shared/fcm_send.ts`; DB triggers on `chat_messages`, `notifications`, `matches`, `marketplace_orders`; outbox `fcm_push_outbox` + `process-fcm-outbox`; care `care_web_reminders` + `process-care-fcm-reminders`. Tray UI: `petfolio_push` channel, `fcm_push_display.dart`, tap → `FcmMessageRouter`. Setup: `firebase-instructions.md`, `tool/set_fcm_supabase_secrets.ps1`.
+- PWA/web (`PWA_WEB_AUDIT.md`, Vercel deploy): Phases 1–2 done—`web/index.html` shell fixes; `lib/core/platform/` (`PlatformNotifications` IO local vs web `care_web_reminders`, `useStripeHostedCheckout` + hosted `create-payment-intent` session on web, `pickGalleryImage`, web FCM via `FIREBASE_VAPID_KEY` + `FcmService.syncToken` / `firebase-messaging-sw.js`). Matching on web uses pet profile location (`petMatchLocationProvider`), not device GPS.
diff --git a/PWA_WEB_AUDIT.md b/PWA_WEB_AUDIT.md
new file mode 100644
index 0000000..5552028
--- /dev/null
+++ b/PWA_WEB_AUDIT.md
@@ -0,0 +1,353 @@
+# PetFolio Web / PWA Audit (Android unchanged)
+
+**Scope:** `web/` directory, Vercel deploy, iOS Safari + Add to Home Screen PWA, `lib/` platform gaps.
+**Date:** 2026-06-05 (revised — full `web/` review with `index.html` accessible)
+**Goal:** Parity with Android where feasible; identify freeze / dead-touch causes and platform gaps.
+
+---
+
+## Executive summary
+
+The **`web/` layer is small but intentional** (custom splash, iOS meta, Stripe.js, install banner). It follows modern Flutter bootstrapping (`flutter_bootstrap.js` + `flutter-first-frame`). **`lib/` remains mobile-first** with only **three `kIsWeb` branches** — that gap still drives missing location/notifications and most feature parity issues.
+
+**Highest-probability causes of “frozen screen / buttons not clickable” on iOS PWA:**
+
+| # | Cause | Where |
+|---|--------|--------|
+| 1 | Flutter iOS PWA engine bugs (ghost keyboard viewport after `TextField`) | Engine — [flutter#111896](https://github.com/flutter/flutter/issues/111896), [flutter#115829](https://github.com/flutter/flutter/issues/115829) |
+| 2 | `#pwa-loading` never removed if `flutter-first-frame` never fires | `web/index.html` |
+| 3 | **Double safe-area** — CSS `body` padding + Flutter `MediaQuery.padding` | `web/index.html` + `AppShell` floating nav |
+| 4 | **`pwa_banner.js`** fixed bottom sheet (`z-index: 99999`) in iOS Safari (pre-install) | Overlaps Flutter bottom nav hit zone |
+| 5 | Full-screen Flutter overlays + bottom sheets + keyboard | `lib/` UI |
+| 6 | Heavy `CachedNetworkImage` → Safari reload on real devices | Social / matching |
+
+---
+
+## `web/` directory — full review
+
+### Inventory
+
+| File | Lines | Purpose |
+|------|-------|---------|
+| `web/index.html` | 123 | App shell, PWA meta, splash, Stripe, bootstrap |
+| `web/manifest.json` | 39 | Web app manifest |
+| `web/pwa_banner.js` | 69 | iOS Safari “Add to Home Screen” promo |
+| `web/icons/*`, `web/favicon.png` | (Flutter defaults) | Referenced by HTML/manifest; standard `flutter create` assets |
+
+There is **no** custom `web/flutter_bootstrap.js` — the project uses the **build-generated** bootstrap (correct for Flutter 3.22+).
+
+---
+
+### `web/index.html` — line-by-line assessment
+
+#### Document head — good
+
+| Lines | Content | Verdict |
+|-------|---------|---------|
+| 4 | `