Security hardening, AI registry overhaul, and PWA install
Three-stream audit driving real fixes: closed two critical security holes, hardened guest-write paths, made the AI registry builder iterative (inline edit + swap + add-custom), and shipped a PWA install affordance. Plus the welcome flow now hands you straight to the AI builder.
- Security: /admin no longer falls open when the password env is unset (was wide open in prod)
- Security: /api/fetch-product validates URL host against private IPs, GCP metadata, and internal TLDs; redirects re-validated per hop
- Hardening: guest fund writes can no longer overwrite e-transfer contact, link URL, goal — only contributedAmount goes up
- Hardening: gift claiming is now monotonic (quantityPurchased can only grow, capped at quantity) — closes a double-claim race
- Hardening: /api/email/collaborate rate-limited 5/hr per registry + 10/hr per IP (email-bomb prevention)
- Hardening: getPublicRegistries() and getPageViews() now have limit() caps — bounded Firestore reads as scale grows
- AI registry: inline edit names, prices, qty, priority directly in the Step 4 preview — no more commit-then-edit
- AI registry: 'Add custom item' button under each category with URL auto-enrich via /api/fetch-product
- AI registry: 'Swap' button per row hits a new /api/ai/swap-item that picks an alternative from the same category — fast, free, deterministic
- AI registry: catalog-miss URL inputs now auto-fill name/price/image as you type
- AI registry: modal state (event type, checklist, context) persists to localStorage so a closed-and-reopened modal doesn't lose your work
- PWA: installable as a standalone app on Chrome (Android + desktop) — standalone display, theme color, home-screen icon
- Onboarding: welcome flow step 3 now offers a 'Start AI builder' CTA — go from sign-up to a populated registry in one click
- SEO: /login and /quick-add are now noindex
- Fix: 'Get gift ideas' (and three other AI client calls) were missing the Bearer token and 401-ing — now authenticated
- Batch crons: batch-a (9am UTC) and batch-b (9pm UTC) routes committed — previously untracked