diff --git a/AGENTS.md b/AGENTS.md index 4b71e05..d6c7d58 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1522,6 +1522,16 @@ session with **no other memory of this project**. So: (which also `saveLastLocation()`s), so `MapPage`'s mount-time `getLastLocation()` opens centered on their area. The logged-out wallet section's dead disabled "Log in first" button became a plain line. + - **Desktop welcome sign-in modal (2026-10-26):** `Layout` opens the themed + `LoginDialog` **once** for a logged-out **desktop** visitor (non-native, + `min-width: 1024px`, after ~900 ms) as a welcome/sign-in offer; dismissal is + remembered in `skatespots:desktop-welcome-dismissed`, and it also offers the + normal `SignupDialog`. It reuses the existing methods (browser extension, + bunker/signer, Amber on Android, nsec, and the **NostrConnect QR**), which is + what the reference screenshot showed — the dialog is still **tabbed**, not + the reference's 3-card layout (a restyle would need a visual QA pass). + ⚠️ `SignupDialog` is a **default-only** export, so lazy-load it as + `lazy(() => import('…/SignupDialog'))`. - **First-run checklist + backup-reminder gating (fixed 2026-10-12).** Two onboarding prompts nagged returning users: 1. `FirstRunChecklist` (map) showed for **any logged-in user** until they @@ -2268,6 +2278,16 @@ Rules: bounds the push (`timeout 180`) since the git remote signs via the remote signer. Timer: `scripts/install-deps-timer.sh` → `skatespots-deps.timer` (daily tick). Flags: `--scan-only`, `--no-push`, `--force`. Logs: `journalctl --user -u skatespots-deps`. + **osv-scanner added (2026-10-26):** the scan block now also runs + `osv-scanner scan source -L package-lock.json` (binary at + `~/go/bin/osv-scanner`, installed with `go install + github.com/google/osv-scanner/v2/cmd/osv-scanner@latest`; override with + `OSV_BIN`), pre- and post-update, report-only. ⚠️ It immediately found **107 + known vulnerabilities (5 critical, 53 high)** that `npm audit` was NOT + surfacing — mostly **dev-only** `vite`/`vitest` (patchable within-range) plus + `xlsx@0.18.5` (two high; **no fix published**) and `zod` (patch). Dev-dep CVEs + don't ship to users; `xlsx` is lazy-loaded for exports. Run `--force` to apply + the fixable ones (gated on tests). ### Community relay — self-hosted Newlay (2026-10-12) diff --git a/scripts/update-deps.sh b/scripts/update-deps.sh index 2948a76..7ecd10f 100755 --- a/scripts/update-deps.sh +++ b/scripts/update-deps.sh @@ -28,6 +28,9 @@ STATE_DIR="${DEPS_STATE_DIR:-$HOME/.local/state/skatespots-deps}" mkdir -p "$STATE_DIR" LAST="$STATE_DIR/last-run" REPORT="$STATE_DIR/report-$(LC_ALL=C date +%F).txt" +# OSV-Scanner (broader than npm audit: queries the OSV database for the lockfile). +# Installed with: go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest +OSV_BIN="${OSV_BIN:-$HOME/go/bin/osv-scanner}" SCAN_ONLY=0; NO_PUSH=0; FORCE=0 for a in "$@"; do @@ -64,6 +67,13 @@ fi echo echo "=== npm audit ===" npm audit 2>&1 || true + echo + echo "=== osv-scanner (OSV database) ===" + if [ -x "$OSV_BIN" ]; then + "$OSV_BIN" scan source -L package-lock.json 2>&1 || true + else + echo "osv-scanner not installed (expected $OSV_BIN)" + fi } | tee "$REPORT" if [ "$SCAN_ONLY" -eq 1 ]; then @@ -86,6 +96,8 @@ npm update 2>&1 | tail -3 npm audit fix 2>&1 | tail -3 { echo; echo "=== post-update npm audit ==="; npm audit 2>&1 || true + echo; echo "=== post-update osv-scanner ===" + if [ -x "$OSV_BIN" ]; then "$OSV_BIN" scan source -L package-lock.json 2>&1 || true; fi } | tee -a "$REPORT" # ── gate: full tests + boot smoke ─────────────────────────────────────────── diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 187c037..6e6fa88 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, lazy, Suspense } from 'react'; +import { useState, useRef, useEffect, lazy, Suspense } from 'react'; import { Link, Outlet, useLocation } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { cn } from '@/lib/utils'; @@ -81,6 +81,7 @@ const LazySetStatusDialog = lazy(() => const LazyLoginDialog = lazy(() => import('@/components/auth/LoginDialog').then((m) => ({ default: m.LoginDialog })), ); +const LazySignupDialog = lazy(() => import('@/components/auth/SignupDialog')); const LazyBackupReminder = lazy(() => import('@/components/BackupReminder').then((m) => ({ default: m.BackupReminder })), ); @@ -516,6 +517,26 @@ export function Layout({ children, noPadding }: LayoutProps) { const [showAddAccount, setShowAddAccount] = useState(false); const { currentMessage, dismissCurrentMessage } = useSasquatchGuide(); const { user } = useCurrentUser(); + + // Desktop, logged-out, first visit: offer the sign-in modal once. Reuses the + // themed LoginDialog/SignupDialog (extension, bunker/signer, Amber, nsec, + // NostrConnect QR). Dismissal is remembered so it never nags again. + const WELCOME_LOGIN_DISMISSED = 'skatespots:desktop-welcome-dismissed'; + const [showWelcomeLogin, setShowWelcomeLogin] = useState(false); + const [showWelcomeSignup, setShowWelcomeSignup] = useState(false); + const dismissWelcomeLogin = () => { + try { localStorage.setItem(WELCOME_LOGIN_DISMISSED, '1'); } catch { /* ignore */ } + setShowWelcomeLogin(false); + }; + useEffect(() => { + if (user || isNativePlatform()) return; + try { + if (localStorage.getItem(WELCOME_LOGIN_DISMISSED) === '1') return; + } catch { return; } + if (typeof window === 'undefined' || !window.matchMedia?.('(min-width: 1024px)').matches) return; + const timer = window.setTimeout(() => setShowWelcomeLogin(true), 900); + return () => window.clearTimeout(timer); + }, [user]); const { t } = useTranslation(); const { currentUser: currentAccount, removeLogin } = useLoggedInAccounts(); const author = useAuthor(user?.pubkey || ''); @@ -1163,6 +1184,29 @@ export function Layout({ children, noPadding }: LayoutProps) { /> + {/* Desktop, logged-out, first visit — one-time sign-in offer */} + + { + dismissWelcomeLogin(); + setShowWelcomeSignup(true); + }} + /> + + + setShowWelcomeSignup(false)} + onUseAmber={() => { + setShowWelcomeSignup(false); + setShowWelcomeLogin(true); + }} + /> + + {/* Newsletter: floating pill + Monday admin reminder */}