Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .github/workflows/docs-negotiation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
DOCS_E2E_BASE_URL: http://127.0.0.1:3001
DOCS_NEGOTIATION_REPORT_PATH: ${{ github.workspace }}/test-results/docs-negotiation.json
DOCS_HEADER_REPORT_PATH: ${{ github.workspace }}/test-results/docs-headers.json
DOCS_NAVIGATION_REPORT_PATH: ${{ github.workspace }}/test-results/navigation/docs.json
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
Expand All @@ -46,13 +47,16 @@ jobs:
run: bun run --cwd apps/docs build
- name: Verify native header edge cases
run: bun run --cwd apps/docs test:headers:e2e
- name: Install browser for navigation checks
run: bunx --no-install playwright install --with-deps chromium
- name: Verify HTTP content negotiation
run: |
mkdir -p test-results
bun run --cwd apps/docs start --port 3001 >test-results/docs-server.log 2>&1 &
DOCS_SERVER_PID=$!
trap 'kill "$DOCS_SERVER_PID" 2>/dev/null || true' EXIT
bun run --cwd apps/docs test:negotiation:e2e
bun run --cwd apps/docs test:navigation:e2e
- name: Upload HTTP regression report
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ Use the owner and generation commands listed in the editorial guide. Do not hand
## Validate

From the repository root, run the required lint, type, audit, and test gates in the development guidelines. From this directory, `bun run type-check` compiles MDX and checks the docs workspace, and `bun run build` validates the production site. Inspect affected page types on desktop and a narrow viewport, and check internal links and existing anchors after heading changes.

The docs HTTP workflow also checks sidebar navigation, API language switching, and copying Markdown on desktop and mobile layouts. It uploads screenshots and a JSON report with the HTTP regression artifacts. These checks run against the built site in CI; no visitor telemetry is collected. Use Vercel's build logs and usage reports to compare deployment cost and duration.
24 changes: 12 additions & 12 deletions apps/docs/app/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { CodeBlock } from '@/components/ui/code-block'
import { Heading } from '@/components/ui/heading'
import { IntegrationIcon } from '@/components/ui/integration-icon'
import { ResponseSection } from '@/components/ui/response-section'
import { getApiSpecContent, getAuthenticatedCodeSamples, openapi } from '@/lib/openapi'
import { getAuthenticatedCodeSamples, openapi } from '@/lib/openapi'
import { simShikiOptions } from '@/lib/shiki-theme'
import { type PageData, source } from '@/lib/source'
import { DOCS_BASE_URL } from '@/lib/urls'
Expand Down Expand Up @@ -266,11 +266,6 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>

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

return (
<>
Expand Down Expand Up @@ -306,7 +301,10 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
>
<PageHeader title={data.title} className='api-page-header'>
<div className='hidden sm:flex'>
<LLMCopyButton content={apiPageContent} />
<LLMCopyButton
key={page.url}
markdownUrl={`/llms.mdx${page.url === '/' ? '' : page.url}`}
/>
</div>
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
</PageHeader>
Expand All @@ -318,23 +316,22 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
)
}

const MDX = data.body
const markdownContent = await data.getText('processed')
const { body: MDX, toc, lastModified } = await data.load()

return (
<>
<StructuredData
title={data.title}
description={data.description || ''}
url={`${BASE_URL}${page.url}`}
dateModified={data.lastModified?.toISOString()}
dateModified={lastModified?.toISOString()}
breadcrumb={breadcrumbs}
/>
<DocsPage
className={DOCS_PAGE_CLASS_NAME}
role='main'
tabIndex={-1}
toc={data.toc.filter(
toc={toc.filter(
(item) => isContentHeading(item) && (slug?.[0] !== 'integrations' || item.depth <= 3)
)}
full={data.full}
Expand All @@ -359,7 +356,10 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
className='max-sm:[&>h1]:basis-0 max-sm:[&_a]:size-10'
>
<div className='hidden sm:flex'>
<LLMCopyButton content={markdownContent} />
<LLMCopyButton
key={page.url}
markdownUrl={`/llms.mdx${page.url === '/' ? '' : page.url}`}
/>
</div>
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
</PageHeader>
Expand Down
17 changes: 10 additions & 7 deletions apps/docs/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import { DOCS_BASE_URL } from '@/lib/urls'

export const revalidate = 3600

export default function sitemap(): MetadataRoute.Sitemap {
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// The docs root redirects to /introduction, which is listed on its own.
return source
.getPages()
.filter((page) => page.url !== '/')
.map((page) => ({
const entries: MetadataRoute.Sitemap = []
for (const page of source.getPages()) {
if (page.url === '/') continue
const data = page.data.type === 'docs' ? await page.data.load() : undefined
Comment thread
waleedlatif1 marked this conversation as resolved.
entries.push({
url: `${DOCS_BASE_URL}${page.url}`,
lastModified: 'lastModified' in page.data ? page.data.lastModified : undefined,
}))
lastModified: data?.lastModified,
})
}
return entries
}
2 changes: 1 addition & 1 deletion apps/docs/components/docs-layout/sidebar-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export function SidebarFolder({ item, children }: SidebarFolderProps) {
)}
{hasChildren && (
<div id={contentId} hidden={!open}>
<div className='mt-px ml-4 flex flex-col gap-px'>{children}</div>
{open && <div className='mt-px ml-4 flex flex-col gap-px'>{children}</div>}
</div>
)}
</div>
Expand Down
60 changes: 49 additions & 11 deletions apps/docs/components/page-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
'use client'

import { Chip } from '@sim/emcn'
import { useState } from 'react'
import { Chip, useCopyToClipboard } from '@sim/emcn'
import { Check, Duplicate } from '@sim/emcn/icons'
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'

export function LLMCopyButton({ content }: { content: string }) {
const [checked, onClick] = useCopyButton(() => navigator.clipboard.writeText(content))
interface LLMCopyButtonProps {
markdownUrl: string
}

export function LLMCopyButton({ markdownUrl }: LLMCopyButtonProps) {
const { copied, copy } = useCopyToClipboard()
const [pending, setPending] = useState(false)
const [failed, setFailed] = useState(false)
const checked = copied && !pending && !failed

async function onClick() {
setPending(true)
setFailed(false)
const prepare = async () => {
const response = await fetch(markdownUrl, {
signal: typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout(10000) : undefined,
})
if (!response.ok) throw new Error('Could not load page Markdown')
return response.text()
}
try {
// Promise-backed writes preserve Safari's transient user activation during the fetch.
const success =
typeof ClipboardItem !== 'undefined' && typeof navigator.clipboard.write === 'function'
? await copy({ prepare, fallback: '' })
: await copy(await prepare())
setFailed(!success)
} catch {
setFailed(true)
} finally {
setPending(false)
}
}

return (
<Chip
onClick={onClick}
leftIcon={checked ? Check : Duplicate}
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
>
{checked ? 'Copied' : 'Copy page'}
</Chip>
<div className='relative'>
<Chip
onClick={onClick}
disabled={pending}
aria-busy={pending}
leftIcon={checked ? Check : Duplicate}
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
>
{checked ? 'Copied' : pending ? 'Copying…' : 'Copy page'}
</Chip>
<span role='status' className='absolute top-full right-0 whitespace-nowrap text-caption'>
{failed ? 'Could not copy. Try again.' : ''}
</span>
</div>
)
}
1 change: 1 addition & 0 deletions apps/docs/lib/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,5 @@ export type PageData = DocData &
description?: string
full?: boolean
integration?: { type: string; color: string }
load: () => Promise<DocData & { lastModified?: Date }>
}
1 change: 0 additions & 1 deletion apps/docs/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const withMDX = createMDX()

const config: NextConfig = {
reactStrictMode: true,
productionBrowserSourceMaps: true,
transpilePackages: ['@sim/emcn', '@sim/workflow-renderer'],
images: {
unoptimized: true,
Expand Down
4 changes: 3 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"lint:check": "biome check .",
"format": "biome format --write .",
"format:check": "biome format .",
"llms-full:generate": "fumadocs-mdx && bun scripts/generate-llms-full.ts"
"llms-full:generate": "fumadocs-mdx && bun scripts/generate-llms-full.ts",
"test:navigation:e2e": "bun scripts/test-navigation-e2e.ts"
},
"dependencies": {
"@sim/db": "workspace:*",
Expand Down Expand Up @@ -46,6 +47,7 @@
"zod": "4.3.6"
},
"devDependencies": {
"@playwright/test": "1.61.1",
"@sim/tsconfig": "workspace:*",
"@tailwindcss/postcss": "^4.3.3",
"@types/node": "24.19.2",
Expand Down
Loading
Loading