diff --git a/apps/dashboard/src/components/BullSwarm3D.tsx b/apps/dashboard/src/components/BullSwarm3D.tsx index b46336a4..29d1ab92 100644 --- a/apps/dashboard/src/components/BullSwarm3D.tsx +++ b/apps/dashboard/src/components/BullSwarm3D.tsx @@ -428,7 +428,8 @@ export function BullSwarm3D({ nodes, highlightIds, onHover, onSelect, onLiveJoin if (isHl) { col.setHSL(0.44, 1, Math.min(1, 0.6 + 0.4 * tw)); } else if (isHover) { col.setHSL(0.4, 0.8, 0.85); } else { - if (hasHl) bright *= 0.25; // dim the field when something's recalled + // Lights stay ON — a recall highlights nodes by brightening THEM (the isHl + // branch above), never by dimming the rest of the field. col.setHSL(0.4 - baseBright[i] * 0.06, 0.95, Math.min(0.94, 0.03 + bright * 0.9)); } mesh.setColorAt(i, col); diff --git a/apps/dashboard/src/pages/ansem-explore.tsx b/apps/dashboard/src/pages/ansem-explore.tsx index f865cff4..845e5228 100644 --- a/apps/dashboard/src/pages/ansem-explore.tsx +++ b/apps/dashboard/src/pages/ansem-explore.tsx @@ -1162,6 +1162,38 @@ export function AnsemExplore() { .catch(() => { if (!stop) setNodeCtx((c) => (c ? { ...c, loading: false } : null)); }); return () => { stop = true; }; }, [nodePopup]); + + // ── Ambient "memory surfacing" — a random punchy memory pops up over the + // constellation every ~3s, one at a time, with a bouncy pop; tap it to deep-dive. + const [funPop, setFunPop] = useState<{ node: BullNode; x: number; y: number; id: number; leaving: boolean } | null>(null); + useEffect(() => { + if (loading || error || nodes.length === 0) return; + // his short punchy lines are the memorable/funny ones — bias to those, skip bare + // links and anything too long to read in a couple seconds. + const pool = nodes.filter((n) => { + const c = (n.content || '').trim(); + return c.length > 14 && c.length < 150 && !/^(?:@\w+\s+)*https?:\/\/\S+$/.test(c); + }); + if (pool.length === 0) return; + let alive = true; + const timers: number[] = []; + let seq = 0; + const loop = () => { + if (!alive) return; + const node = pool[Math.floor(Math.random() * pool.length)]; + const vw = window.innerWidth, vh = window.innerHeight; + // safe band over the constellation — clear of the top-left HUD + bottom controls + const x = Math.round(vw * (0.4 + Math.random() * 0.4)); + const y = Math.round(vh * (0.24 + Math.random() * 0.44)); + const id = ++seq; + setFunPop({ node, x, y, id, leaving: false }); + timers.push(window.setTimeout(() => { if (alive) setFunPop((p) => (p && p.id === id ? { ...p, leaving: true } : p)); }, 2600)); + timers.push(window.setTimeout(() => { if (alive) { setFunPop((p) => (p && p.id === id ? null : p)); loop(); } }, 3300)); + }; + const start = window.setTimeout(loop, 4600); // let the cinematic intro land first + timers.push(start); + return () => { alive = false; timers.forEach(clearTimeout); }; + }, [loading, error, nodes]); // live-stream affordances: joined-count + transient toasts + the on-chain log modal const [liveJoined, setLiveJoined] = useState(0); const [toasts, setToasts] = useState>([]); @@ -1430,6 +1462,48 @@ export function AnsemExplore() { )} + {/* ── Ambient memory surfacing — a random memory pops up, tap to deep-dive ── */} + + {funPop && !nodePopup && !loading && !error && ( + + )} + {/* HUD — title, subtitle, count, disclaimer */}
$ANSEM
-
- the black bull community clone -
-
- the movement · made tangible +
+ {countLabel} memories and growing. One community mind.
-
- {countLabel} memories, one mind +
+ The $ANSEM Black Bull movement made tangible.
-
- his posts, the believers, the live timeline — the $ANSEM movement distilled into one living entity you can speak to. +
+ A living entity shaped by the posts, the believers, and the live timeline.
-
- community-built AI clone · not the real Ansem · not financial advice +
+ Community built AI clone.
+ Not the real Ansem. Not financial advice.
{/* live-stream status — how content flows in + the on-chain proof */}
- streaming the $ANSEM timeline live{liveJoined > 0 ? ` · ${liveJoined} joined` : ''} + Streaming the $ANSEM timeline live{liveJoined > 0 ? ` · ${liveJoined} joined` : ''}
diff --git a/infra/gcp/smoke.sh b/infra/gcp/smoke.sh new file mode 100755 index 00000000..5d382cb0 --- /dev/null +++ b/infra/gcp/smoke.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# +# Post-deploy smoke check. Run against ANY deployment of the server (Cloud Run, +# Railway, a PR-preview revision) — pass the base URL: +# +# ./infra/gcp/smoke.sh https://cluude-server-xxxx-uc.a.run.app +# PRIVY_APP_ID= ./infra/gcp/smoke.sh https://clude.io +# +# WHY THIS EXISTS: this repo's deploy failures are silent — /health stays 200 +# while the SPAs ship broken. The two documented failure modes it targets: +# 1. PRIVY_APP_ID baked wrong/missing at BUILD time (Vite inlines +# VITE_PRIVY_APP_ID as a literal; a bad --build-arg ships bundles where +# login silently breaks). With PRIVY_APP_ID set, we grep the served chat +# bundle for the literal id. +# 2. A route module or static mount dying while /health (which touches +# nothing) keeps answering 200. +# +# Exit code 0 = all checks pass. Non-zero = at least one failed (each failure +# prints FAIL with what to look at). No gcloud dependency — pure curl. + +set -uo pipefail + +BASE="${1:?usage: smoke.sh [worker-health-url]}" +BASE="${BASE%/}" +WORKER="${2:-}" +PRIVY_APP_ID="${PRIVY_APP_ID:-}" +# For Cloud Run services not yet public (org policy / --no-allow-unauthenticated): +# SMOKE_AUTH_TOKEN="$(gcloud auth print-identity-token)" ./infra/gcp/smoke.sh +SMOKE_AUTH_TOKEN="${SMOKE_AUTH_TOKEN:-}" + +pass=0 +fail=0 + +note() { printf '%s\n' "$*"; } +ok() { pass=$((pass+1)); note " OK $*"; } +bad() { fail=$((fail+1)); note " FAIL $*"; } + +# fetch — sets globals BODY and STATUS (no subshell: set -u safe) +fetch() { + local url="$1" raw + local -a auth=() + [ -n "$SMOKE_AUTH_TOKEN" ] && auth=(-H "Authorization: Bearer $SMOKE_AUTH_TOKEN") + if ! raw=$(curl -sS --max-time 20 -w $'\n%{http_code}' "${auth[@]}" "$url" 2>/dev/null); then + STATUS=000; BODY=""; return 1 + fi + STATUS="${raw##*$'\n'}" + BODY="${raw%$'\n'*}" +} + +note "Smoke: ${BASE}" + +# 1. /health — the basic liveness everyone already trusts (baseline, not proof) +fetch "${BASE}/health" +if [ "$STATUS" = "200" ]; then ok "/health 200"; else bad "/health -> $STATUS"; fi + +# 2. A real API route that exercises Express routing + the DB read path. +# /api/proof/tokens-saved is mounted unconditionally (unlike /api/compound, +# which is gated on COMPOUND_ENABLED) and returns JSON even with zero data. +fetch "${BASE}/api/proof/tokens-saved" +if [ "$STATUS" = "200" ] && printf '%s' "$BODY" | grep -q '{'; then + ok "/api/proof/tokens-saved 200 + JSON" +else + bad "/api/proof/tokens-saved -> $STATUS (route modules or DB path dead while /health stays green)" +fi + +# 3. MCP discovery — the connector integration breaks silently if this mount is lost. +fetch "${BASE}/.well-known/oauth-protected-resource" +if [ "$STATUS" = "200" ]; then + ok "MCP .well-known/oauth-protected-resource 200" +else + bad "MCP .well-known -> $STATUS (connector auth discovery dead)" +fi + +# 4 + 5. The two SPAs: HTML serves, references a hashed Vite bundle, bundle fetches. +check_spa() { # check_spa