From c0b56f646be6436c89ff141066da887088a96580 Mon Sep 17 00:00:00 2001 From: cyph3rasi Date: Wed, 15 Jul 2026 01:10:54 -0700 Subject: [PATCH] Block route rendering until auth and NSFW node classification are resolved Hop-State: A_06FP9N1CECVZN3TAJZTJJ4R Hop-Proposal: R_06FP9N09YGWW0S9XV4S3XNG Hop-Task: T_06FP9MBVAVXP6ZRX0VG9RSG Hop-Attempt: AT_06FP9MBVARDT7GQQ58C9WG0 --- src/app/explore/page.tsx | 14 +++----------- src/components/LayoutWrapper.tsx | 5 ++++- src/lib/bootstrap/readiness.test.ts | 16 ++++++++++++++++ src/lib/bootstrap/readiness.ts | 9 +++++++++ 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 src/lib/bootstrap/readiness.test.ts create mode 100644 src/lib/bootstrap/readiness.ts diff --git a/src/app/explore/page.tsx b/src/app/explore/page.tsx index 10bb962..afdab84 100644 --- a/src/app/explore/page.tsx +++ b/src/app/explore/page.tsx @@ -10,6 +10,7 @@ import { Bot, Network, Server, EyeOff } from 'lucide-react'; import { useAuth } from '@/lib/contexts/AuthContext'; import { signedAPI } from '@/lib/api/signed-fetch'; import { AvatarImage } from '@/components/AvatarImage'; +import { useRuntimeConfig } from '@/lib/contexts/ConfigContext'; interface User { id: string; @@ -85,6 +86,7 @@ interface SwarmPost { export default function ExplorePage() { const { user, did, handle } = useAuth(); + const { config } = useRuntimeConfig(); const [query, setQuery] = useState(''); const [activeTab, setActiveTab] = useState<'node' | 'swarm' | 'users' | 'search'>('node'); const [nodePosts, setNodePosts] = useState([]); @@ -99,17 +101,7 @@ export default function ExplorePage() { const [loadingMore, setLoadingMore] = useState(false); const [hasMoreNode, setHasMoreNode] = useState(true); const [hasMoreSwarm, setHasMoreSwarm] = useState(true); - const [isNsfwNode, setIsNsfwNode] = useState(false); - - // Fetch node info to check if NSFW - useEffect(() => { - fetch('/api/node') - .then(res => res.json()) - .then(data => { - setIsNsfwNode(data.isNsfw || false); - }) - .catch(() => { }); - }, []); + const isNsfwNode = config?.isNsfw === true; useEffect(() => { // Load node posts (local only) diff --git a/src/components/LayoutWrapper.tsx b/src/components/LayoutWrapper.tsx index a6ef64d..ee887fe 100644 --- a/src/components/LayoutWrapper.tsx +++ b/src/components/LayoutWrapper.tsx @@ -4,9 +4,12 @@ import { usePathname } from 'next/navigation'; import { Sidebar } from './Sidebar'; import { RightSidebar } from './RightSidebar'; import { useAuth } from '@/lib/contexts/AuthContext'; +import { useRuntimeConfig } from '@/lib/contexts/ConfigContext'; +import { isAppBootstrapReady } from '@/lib/bootstrap/readiness'; export function LayoutWrapper({ children }: { children: React.ReactNode }) { const { loading } = useAuth(); + const { isLoading: configLoading } = useRuntimeConfig(); const pathname = usePathname(); // Paths that should NOT have the app layout @@ -17,7 +20,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { // Hide right sidebar on chat page for more space const hideRightSidebar = false; - if (loading) { + if (!isAppBootstrapReady({ authLoading: loading, configLoading })) { return (
{ + it('blocks route rendering while authentication is unresolved', () => { + expect(isAppBootstrapReady({ authLoading: true, configLoading: false })).toBe(false); + }); + + it('blocks route rendering while node classification is unresolved', () => { + expect(isAppBootstrapReady({ authLoading: false, configLoading: true })).toBe(false); + }); + + it('allows route rendering only after both dependencies are resolved', () => { + expect(isAppBootstrapReady({ authLoading: false, configLoading: false })).toBe(true); + }); +}); diff --git a/src/lib/bootstrap/readiness.ts b/src/lib/bootstrap/readiness.ts new file mode 100644 index 0000000..e97e562 --- /dev/null +++ b/src/lib/bootstrap/readiness.ts @@ -0,0 +1,9 @@ +export function isAppBootstrapReady({ + authLoading, + configLoading, +}: { + authLoading: boolean; + configLoading: boolean; +}): boolean { + return !authLoading && !configLoading; +}