diff --git a/app/(docs)/[[...slug]]/page.tsx b/app/(docs)/[[...slug]]/page.tsx index a936a02a..fbbacd72 100644 --- a/app/(docs)/[[...slug]]/page.tsx +++ b/app/(docs)/[[...slug]]/page.tsx @@ -11,25 +11,33 @@ import { notFound } from 'next/navigation'; import { CLICommand } from '@/components/CLICommand'; import { CodeExample } from '@/components/CodeExample'; import { Mermaid } from '@/components/Mermaid'; +import { SDKExplorerIframe } from '@/components/SDKExplorerIframe'; import { Sparkle } from '@/components/Sparkle'; import { Tag } from '@/components/Tag'; import { ThemeImage } from '@/components/ThemeImage'; import { TypingAnimation } from '@/components/TypingAnimation'; import { XButton } from '@/components/XButton'; import { source } from '@/lib/source'; +import CodeFromFiles from '../../../components/CodeFromFiles'; import { CommunityButton } from '../../../components/Community'; import CopyPageDropdown from '../../../components/CopyPageDropdown'; import { NavButton } from '../../../components/NavButton'; -import CodeFromFiles from '../../../components/CodeFromFiles'; import TutorialStep from '../../../components/TutorialStep'; - export default async function Page(props: { params: Promise<{ slug?: string[] }>; }) { const params = await props.params; - const page = source.getPage(params.slug); + if (params.slug === undefined) { + return ( + + + + ); + } + + const page = source.getPage(params.slug); if (!page) notFound(); const MDX = page.data.body; @@ -107,6 +115,14 @@ export async function generateMetadata(props: { params: Promise<{ slug?: string[] }>; }) { const params = await props.params; + + if (params.slug === undefined) { + return { + title: 'SDK Explorer — Agentuity Docs', + description: 'Interactive examples showcasing the Agentuity SDK', + }; + } + const page = source.getPage(params.slug); if (!page) notFound(); diff --git a/app/global.css b/app/global.css index d18e9357..c0c10adf 100644 --- a/app/global.css +++ b/app/global.css @@ -39,9 +39,9 @@ } .dark { - --color-fd-primary: var(--color-cyan-400); + --color-fd-primary: var(--color-cyan-500); --color-fd-primary-foreground: var(--color-black); - --color-fd-ring: var(--color-cyan-400); + --color-fd-ring: var(--color-cyan-500); } /* make sure empty lines are rendered */ @@ -140,3 +140,27 @@ article > p { .dark .prose a:not([data-card]) { text-decoration-color: var(--color-cyan-500); } + +/* SDK Explorer - iframe rendered directly inside DocsLayout */ +.sdk-explorer-wrapper { + position: fixed; + inset: 0; + top: var(--fd-nav-height, 0px); + left: var(--fd-sidebar-width, 0px); + z-index: 10; + background: var(--color-fd-background); +} + +.sdk-explorer-wrapper iframe { + width: 100%; + height: 100%; + border: 0; +} + +/* Mobile: full width, account for nav */ +@media (max-width: 767px) { + .sdk-explorer-wrapper { + left: 0; + top: var(--fd-nav-height, 56px); + } +} diff --git a/app/layout.config.tsx b/app/layout.config.tsx index 732988aa..aeb8e2ec 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -8,7 +8,7 @@ import { XButton } from '../components/XButton'; */ export const baseOptions: BaseLayoutProps = { nav: { - url: '/Get-Started/what-is-agentuity', + url: '/', title: (
(null); + const [mounted, setMounted] = useState(false); + const [iframeLoaded, setIframeLoaded] = useState(false); + // Capture initial theme for iframe src (don't change src on theme updates) + const [initialTheme] = useState(() => + typeof window !== 'undefined' + ? document.documentElement.classList.contains('dark') + ? 'dark' + : 'light' + : 'dark' + ); + + // Only render iframe after mount to avoid hydration mismatch + useEffect(() => { + setMounted(true); + }, []); + + // Send theme changes to iframe via postMessage + useEffect(() => { + if (mounted && iframeRef.current?.contentWindow) { + iframeRef.current.contentWindow.postMessage( + { type: 'SET_THEME', theme: resolvedTheme }, + EXPLORER_URL + ); + } + }, [resolvedTheme, mounted]); + + // Listen for navigation requests from iframe + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + // Validate origin to prevent open redirect + if (event.origin !== new URL(EXPLORER_URL).origin) { + return; + } + if (event.data?.type === 'NAVIGATE' && event.data.path) { + const path = event.data.path; + // Only allow relative paths to prevent open redirects + if ( + typeof path === 'string' && + path.startsWith('/') && + !path.startsWith('//') + ) { + window.location.href = path; + } + } + }; + window.addEventListener('message', handleMessage); + return () => window.removeEventListener('message', handleMessage); + }, []); + + // Show placeholder until mounted to avoid hydration mismatch + if (!mounted) { + return
; + } + + return ( +
+