diff --git a/AGENTS.md b/AGENTS.md
index 17f6c86..f712920 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2077,8 +2077,22 @@ session with **no other memory of this project**. So:
used by **both** maps (`SpotMap`, `RouteDialog`). Deliberately NOT a service
worker: a SW only helps the web build, while the Android app runs inside
Capacitor's WebView where a SW never sees tile requests. Works on both.
- Still open: offline **app shell** (SW) — unnecessary for native, deferred for
- web.
+ **Web app shell (2026-10-28):** `public/push-sw.js` is also the app-shell
+ cache — hashed assets cache-first, navigations network-first with
+ `/index.html` fallback, old `skate-spots-shell-*` caches removed. It is
+ registered unconditionally on web by `lib/appServiceWorker.ts` (skipped on
+ native). The push handlers are unchanged; `/api/*` and cross-origin model
+ requests are never cached.
+- **Android navigation + app links (2026-10-28):** `@capacitor/app` is now a
+ real dependency. `DeepLinkHandler` dynamically imports it and handles
+ `appUrlOpen` (HTTPS paths, `#/` HashRouter paths, and
+ `org.skatespots.app://` host routes) plus hardware `backButton` (close the top
+ Radix overlay → history back → minimise at the map). `scripts/inject-deep-links.mjs`
+ adds the verified `https://skate-spots.org` + custom-scheme intent filters to
+ the generated manifest; `public/.well-known/assetlinks.json` carries the
+ release cert SHA-256 `B7:52:F2:6A:…:12:0A` for package
+ `org.skatespots.app`. If the signing cert changes, update assetlinks and
+ redeploy; app-link verification happens at install.
- **Photo redundancy (2026-09-28):** `useUploadFile` now uses **two** Blossom
hosts (`blossom.primal.net`, `blossom.band`) instead of three. ⚠️
`BlossomUploader.upload` signs ONE auth event then does
diff --git a/package-lock.json b/package-lock.json
index 2b27f85..82fa2b8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"dependencies": {
"@bitcoinerlab/secp256k1": "^1.2.0",
"@capacitor/android": "^8.4.2",
+ "@capacitor/app": "^8.1.2",
"@capacitor/camera": "^8.2.1",
"@capacitor/core": "^8.4.2",
"@capacitor/geolocation": "^8.2.0",
@@ -324,6 +325,15 @@
"@capacitor/core": "^8.5.0"
}
},
+ "node_modules/@capacitor/app": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/@capacitor/app/-/app-8.1.2.tgz",
+ "integrity": "sha512-uKBlsrXH0IdvUn69e/KKYbZ32A06pcrUdG8LWuZ/6y33YMXZXLEEdNSwl+jKS/SrsTWqnePs8EqSWaiQM3mUGA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@capacitor/core": ">=8.0.0"
+ }
+ },
"node_modules/@capacitor/camera": {
"version": "8.2.4",
"resolved": "https://registry.npmjs.org/@capacitor/camera/-/camera-8.2.4.tgz",
diff --git a/package.json b/package.json
index e1e3675..b1c45eb 100644
--- a/package.json
+++ b/package.json
@@ -22,10 +22,10 @@
"test": "npm i --silent && tsc --noEmit && tsc -p cvm-server/tsconfig.json && tsc -p ai-proxy/tsconfig.json && tsc -p push-service/tsconfig.json && eslint && node scripts/check-i18n.mjs && vitest run --reporter=dot --silent && vite build -l error && cp dist/index.html dist/404.html && echo 'All tests passed!'",
"deploy": "npm i --silent && npm run build && npx -y nostr-deploy-cli deploy --skip-setup",
"android:init": "npm run android:build:apk && npx cap add android && npx cap sync android && node scripts/inject-amber-queries.mjs && node scripts/inject-main-activity.mjs && node scripts/inject-network-security.mjs && node scripts/inject-signing-config.mjs",
- "android:sync": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs && node scripts/inject-main-activity.mjs && node scripts/inject-network-security.mjs && node scripts/inject-signing-config.mjs",
+ "android:sync": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs && node scripts/inject-deep-links.mjs && node scripts/inject-main-activity.mjs && node scripts/inject-network-security.mjs && node scripts/inject-signing-config.mjs",
"android:open": "npx cap open android",
"android:version": "node scripts/sync-android-version.mjs",
- "android:build": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs && node scripts/inject-main-activity.mjs && node scripts/inject-network-security.mjs && node scripts/inject-signing-config.mjs && cd android && ./gradlew assembleRelease",
+ "android:build": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs && node scripts/inject-deep-links.mjs && node scripts/inject-main-activity.mjs && node scripts/inject-network-security.mjs && node scripts/inject-signing-config.mjs && cd android && ./gradlew assembleRelease",
"android:build:apk": "CAPACITOR_BUILD=1 vite build && sed -i 's|||g' dist/index.html && sed -i 's|