Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b7e377e
v0.5.91: docs i18n, turborepo upgrade
waleedlatif1 Feb 16, 2026
da46a38
v0.5.92: shortlinks, copilot scrolling stickiness, pagination
waleedlatif1 Feb 17, 2026
fdca736
v0.5.93: NextJS config changes, MCP and Blocks whitelisting, copilot …
waleedlatif1 Feb 18, 2026
15ace5e
v0.5.94: vercel integration, folder insertion, migrated tracking redi…
waleedlatif1 Feb 19, 2026
67aa4bb
v0.5.95: gemini 3.1 pro, cloudflare, dataverse, revenuecat, redis, up…
waleedlatif1 Feb 20, 2026
34d92fa
v0.5.96: sim oauth provider, slack ephemeral message tool and blockki…
waleedlatif1 Feb 21, 2026
115f04e
v0.5.97: oidc discovery for copilot mcp
waleedlatif1 Feb 21, 2026
0d86ea0
v0.5.98: change detection improvements, rate limit and code execution…
waleedlatif1 Feb 22, 2026
af59234
v0.5.99: local dev improvements, live workflow logs in terminal
waleedlatif1 Feb 23, 2026
67f8a68
v0.5.100: multiple credentials, 40% speedup, gong, attio, audit log i…
waleedlatif1 Feb 25, 2026
4fd0989
v0.5.101: circular dependency mitigation, confluence enhancements, go…
waleedlatif1 Feb 26, 2026
0d2e6ff
v0.5.102: new integrations, new tools, ci speedups, memory leak instr…
waleedlatif1 Feb 28, 2026
d81055d
feat(docs): add API reference with OpenAPI spec and auto-generated en…
waleedlatif1 Mar 1, 2026
d26c8cc
multiline curl
waleedlatif1 Mar 1, 2026
ee0745d
random improvements
waleedlatif1 Mar 1, 2026
0207d61
cleanup
waleedlatif1 Mar 2, 2026
5534b00
update docs copy
waleedlatif1 Mar 2, 2026
9d2d237
fix build
waleedlatif1 Mar 2, 2026
5734f2c
cast
waleedlatif1 Mar 2, 2026
dc8a2e3
fix builg
waleedlatif1 Mar 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 136 additions & 33 deletions apps/docs/app/[lang]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type React from 'react'
import type { Root } from 'fumadocs-core/page-tree'
import { findNeighbour } from 'fumadocs-core/page-tree'
import type { ApiPageProps } from 'fumadocs-openapi/ui'
import { createAPIPage } from 'fumadocs-openapi/ui'
import { Pre } from 'fumadocs-ui/components/codeblock'
import defaultMdxComponents from 'fumadocs-ui/mdx'
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page'
Expand All @@ -12,36 +15,83 @@ import { LLMCopyButton } from '@/components/page-actions'
import { StructuredData } from '@/components/structured-data'
import { CodeBlock } from '@/components/ui/code-block'
import { Heading } from '@/components/ui/heading'
import { ResponseSection } from '@/components/ui/response-section'
import { i18n } from '@/lib/i18n'
import { getApiSpecContent, openapi } from '@/lib/openapi'
import { type PageData, source } from '@/lib/source'

const SUPPORTED_LANGUAGES: Set<string> = new Set(i18n.languages)
const BASE_URL = 'https://docs.sim.ai'

function resolveLangAndSlug(params: { slug?: string[]; lang: string }) {
const isValidLang = SUPPORTED_LANGUAGES.has(params.lang)
const lang = isValidLang ? params.lang : 'en'
const slug = isValidLang ? params.slug : [params.lang, ...(params.slug ?? [])]
return { lang, slug }
}

const APIPage = createAPIPage(openapi, {
playground: { enabled: false },
content: {
renderOperationLayout: async (slots) => {
return (
<div className='flex @4xl:flex-row flex-col @4xl:items-start gap-x-6 gap-y-4'>
<div className='min-w-0 flex-1'>
{slots.header}
{slots.apiPlayground}
{slots.authSchemes && <div className='api-section-divider'>{slots.authSchemes}</div>}
{slots.paremeters}
{slots.body && <div className='api-section-divider'>{slots.body}</div>}
<ResponseSection>{slots.responses}</ResponseSection>
{slots.callbacks}
</div>
<div className='@4xl:sticky @4xl:top-[calc(var(--fd-docs-row-1,2rem)+1rem)] @4xl:w-[400px]'>
{slots.apiExample}
</div>
</div>
)
},
},
})

export default async function Page(props: { params: Promise<{ slug?: string[]; lang: string }> }) {
const params = await props.params
const page = source.getPage(params.slug, params.lang)
const { lang, slug } = resolveLangAndSlug(params)
const page = source.getPage(slug, lang)
if (!page) notFound()

const data = page.data as PageData
const MDX = data.body
const baseUrl = 'https://docs.sim.ai'
const markdownContent = await data.getText('processed')
const data = page.data as unknown as PageData & {
_openapi?: { method?: string }
getAPIPageProps?: () => ApiPageProps
}
const isOpenAPI = '_openapi' in data && data._openapi != null
const isApiReference = slug?.some((s) => s === 'api-reference') ?? false

const pageTreeRecord = source.pageTree as Record<string, any>
const pageTree =
pageTreeRecord[params.lang] ?? pageTreeRecord.en ?? Object.values(pageTreeRecord)[0]
const neighbours = pageTree ? findNeighbour(pageTree, page.url) : null
const pageTreeRecord = source.pageTree as Record<string, Root>
const pageTree = pageTreeRecord[lang] ?? pageTreeRecord.en ?? Object.values(pageTreeRecord)[0]
const rawNeighbours = pageTree ? findNeighbour(pageTree, page.url) : null
const neighbours = isApiReference
? {
previous: rawNeighbours?.previous?.url.includes('/api-reference/')
? rawNeighbours.previous
: undefined,
next: rawNeighbours?.next?.url.includes('/api-reference/') ? rawNeighbours.next : undefined,
}
: rawNeighbours

const generateBreadcrumbs = () => {
const breadcrumbs: Array<{ name: string; url: string }> = [
{
name: 'Home',
url: baseUrl,
url: BASE_URL,
},
]

const urlParts = page.url.split('/').filter(Boolean)
let currentPath = ''

urlParts.forEach((part, index) => {
if (index === 0 && ['en', 'es', 'fr', 'de', 'ja', 'zh'].includes(part)) {
if (index === 0 && SUPPORTED_LANGUAGES.has(part)) {
currentPath = `/${part}`
return
}
Expand All @@ -56,12 +106,12 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
if (index === urlParts.length - 1) {
breadcrumbs.push({
name: data.title,
url: `${baseUrl}${page.url}`,
url: `${BASE_URL}${page.url}`,
})
} else {
breadcrumbs.push({
name: name,
url: `${baseUrl}${currentPath}`,
url: `${BASE_URL}${currentPath}`,
})
}
})
Expand All @@ -73,7 +123,6 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l

const CustomFooter = () => (
<div className='mt-12'>
{/* Navigation links */}
<div className='flex items-center justify-between py-8'>
{neighbours?.previous ? (
<Link
Expand All @@ -100,10 +149,8 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
)}
</div>

{/* Divider line */}
<div className='border-border border-t' />

{/* Social icons */}
<div className='flex items-center gap-4 py-6'>
<Link
href='https://x.com/simdotai'
Expand Down Expand Up @@ -169,13 +216,69 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
</div>
)

if (isOpenAPI && data.getAPIPageProps) {
const apiProps = data.getAPIPageProps()
const apiPageContent = getApiSpecContent(
data.title,
data.description,
apiProps.operations ?? []
)

return (
<>
<StructuredData
title={data.title}
description={data.description || ''}
url={`${BASE_URL}${page.url}`}
lang={lang}
breadcrumb={breadcrumbs}
/>
<DocsPage
toc={data.toc}
breadcrumb={{
enabled: false,
}}
tableOfContent={{
style: 'clerk',
enabled: false,
}}
tableOfContentPopover={{
style: 'clerk',
enabled: false,
}}
footer={{
enabled: true,
component: <CustomFooter />,
}}
>
<div className='api-page-header relative mt-6 sm:mt-0'>
<div className='absolute top-1 right-0 flex items-center gap-2'>
<div className='hidden sm:flex'>
<LLMCopyButton content={apiPageContent} />
</div>
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
</div>
<DocsTitle>{data.title}</DocsTitle>
<DocsDescription>{data.description}</DocsDescription>
</div>
<DocsBody>
<APIPage {...apiProps} />
</DocsBody>
</DocsPage>
</>
)
}

const MDX = data.body
const markdownContent = await data.getText('processed')

return (
<>
<StructuredData
title={data.title}
description={data.description || ''}
url={`${baseUrl}${page.url}`}
lang={params.lang}
url={`${BASE_URL}${page.url}`}
lang={lang}
breadcrumb={breadcrumbs}
/>
<DocsPage
Expand Down Expand Up @@ -252,14 +355,14 @@ export async function generateMetadata(props: {
params: Promise<{ slug?: string[]; lang: string }>
}) {
const params = await props.params
const page = source.getPage(params.slug, params.lang)
const { lang, slug } = resolveLangAndSlug(params)
const page = source.getPage(slug, lang)
if (!page) notFound()

const data = page.data as PageData
const baseUrl = 'https://docs.sim.ai'
const fullUrl = `${baseUrl}${page.url}`
const data = page.data as unknown as PageData
const fullUrl = `${BASE_URL}${page.url}`

const ogImageUrl = `${baseUrl}/api/og?title=${encodeURIComponent(data.title)}`
const ogImageUrl = `${BASE_URL}/api/og?title=${encodeURIComponent(data.title)}`

return {
title: data.title,
Expand All @@ -286,10 +389,10 @@ export async function generateMetadata(props: {
url: fullUrl,
siteName: 'Sim Documentation',
type: 'article',
locale: params.lang === 'en' ? 'en_US' : `${params.lang}_${params.lang.toUpperCase()}`,
locale: lang === 'en' ? 'en_US' : `${lang}_${lang.toUpperCase()}`,
alternateLocale: ['en', 'es', 'fr', 'de', 'ja', 'zh']
.filter((lang) => lang !== params.lang)
.map((lang) => (lang === 'en' ? 'en_US' : `${lang}_${lang.toUpperCase()}`)),
.filter((l) => l !== lang)
.map((l) => (l === 'en' ? 'en_US' : `${l}_${l.toUpperCase()}`)),
images: [
{
url: ogImageUrl,
Expand Down Expand Up @@ -323,13 +426,13 @@ export async function generateMetadata(props: {
alternates: {
canonical: fullUrl,
languages: {
'x-default': `${baseUrl}${page.url.replace(`/${params.lang}`, '')}`,
en: `${baseUrl}${page.url.replace(`/${params.lang}`, '')}`,
es: `${baseUrl}/es${page.url.replace(`/${params.lang}`, '')}`,
fr: `${baseUrl}/fr${page.url.replace(`/${params.lang}`, '')}`,
de: `${baseUrl}/de${page.url.replace(`/${params.lang}`, '')}`,
ja: `${baseUrl}/ja${page.url.replace(`/${params.lang}`, '')}`,
zh: `${baseUrl}/zh${page.url.replace(`/${params.lang}`, '')}`,
'x-default': `${BASE_URL}${page.url.replace(`/${lang}`, '')}`,
en: `${BASE_URL}${page.url.replace(`/${lang}`, '')}`,
es: `${BASE_URL}/es${page.url.replace(`/${lang}`, '')}`,
fr: `${BASE_URL}/fr${page.url.replace(`/${lang}`, '')}`,
de: `${BASE_URL}/de${page.url.replace(`/${lang}`, '')}`,
ja: `${BASE_URL}/ja${page.url.replace(`/${lang}`, '')}`,
zh: `${BASE_URL}/zh${page.url.replace(`/${lang}`, '')}`,
},
},
}
Expand Down
6 changes: 5 additions & 1 deletion apps/docs/app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,11 @@ type LayoutProps = {
params: Promise<{ lang: string }>
}

const SUPPORTED_LANGUAGES: Set<string> = new Set(i18n.languages)

export default async function Layout({ children, params }: LayoutProps) {
const { lang } = await params
const { lang: rawLang } = await params
const lang = SUPPORTED_LANGUAGES.has(rawLang) ? rawLang : 'en'

const structuredData = {
'@context': 'https://schema.org',
Expand Down Expand Up @@ -107,6 +110,7 @@ export default async function Layout({ children, params }: LayoutProps) {
title: <SimLogoFull className='h-7 w-auto' />,
}}
sidebar={{
tabs: false,
defaultOpenLevel: 0,
collapsible: false,
footer: null,
Expand Down
Loading