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 */}