diff --git a/.changeset/config.json b/.changeset/config.json index 65c3a86ebf..26d3ac44aa 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -22,7 +22,6 @@ "baseBranch": "stable", "updateInternalDependencies": "patch", "ignore": [ - "docs", "nextjs-turbopack", "nextjs-webpack", "workflow-sdk-compiler-playground", diff --git a/.github/workflows/docs-checks.yml b/.github/workflows/docs-checks.yml index 3454e963d3..c85830dd96 100644 --- a/.github/workflows/docs-checks.yml +++ b/.github/workflows/docs-checks.yml @@ -1,3 +1,7 @@ +# The docs/ directory does not exist on the stable branch. +# Docs are deployed only from main. This workflow is kept as a +# no-op on stable so that any branch protection rules referencing +# these job names still pass. name: Docs Checks on: @@ -16,74 +20,14 @@ concurrency: jobs: docs-typecheck: name: Docs Code Samples + if: false runs-on: ubuntu-latest - timeout-minutes: 5 - env: - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v3 - with: - version: 10.14.0 - - - name: Setup Node.js 22.x - uses: actions/setup-node@v4 - with: - node-version: 22.x - cache: "pnpm" - - - name: Install Dependencies - run: pnpm install --frozen-lockfile - - - name: Build packages - run: pnpm build - - - name: Type-check documentation code samples - run: pnpm test:docs + - run: true docs-preview-smoke: name: Docs Preview Smoke Checks + if: false runs-on: ubuntu-latest - timeout-minutes: 5 - env: - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v3 - with: - version: 10.14.0 - - - name: Setup Node.js 22.x - uses: actions/setup-node@v4 - with: - node-version: 22.x - cache: "pnpm" - - - name: Install Dependencies - run: pnpm install --frozen-lockfile - - - name: Waiting for the Docs Vercel deployment - id: waitForDocsDeployment - uses: ./.github/actions/wait-for-vercel-project - with: - team-id: "team_nLlpyC6REAqxydlFKbrMDlud" - project-id: "prj_aLMkVj1S4Alk08AThC76nXgwbLEB" - vercel-token: ${{ secrets.VERCEL_DOCS_TOKEN }} - timeout: 1000 - check-interval: 15 - environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} - - - name: Verify OpenGraph images and sitemap - run: pnpm --filter docs test:smoke - env: - DEPLOYMENT_URL: ${{ steps.waitForDocsDeployment.outputs.deployment-url }} - VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} - VERCEL_DOCS_TOKEN: ${{ secrets.VERCEL_DOCS_TOKEN }} + - run: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2948e2d3d5..440c7d6f8d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,33 +36,11 @@ jobs: exit 1 fi + # The docs/ directory does not exist on the stable branch. + # This job is kept as a no-op so branch protection rules still pass. docs-links: name: Docs Links + if: false runs-on: ubuntu-latest - env: - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v3 - with: - version: 10.14.0 - - - name: Setup Node.js 22.x - uses: actions/setup-node@v4 - with: - node-version: 22.x - cache: "pnpm" - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - - - name: Install Dependencies - run: pnpm install --frozen-lockfile - - - name: Validate docs links - run: bun ./scripts/lint.ts - working-directory: docs + - run: true diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 0805f529d5..0000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -# deps -/node_modules - -# generated content -.contentlayer -.content-collections -.source - -# test & build -/coverage -/.next/ -/out/ -/build -*.tsbuildinfo - -# misc -.DS_Store -*.pem -/.pnp -.pnp.js -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# others -.env*.local -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# pagefind -_pagefind/ - -# tarballs -*.tgz -.env*.local diff --git a/docs/LICENSE.md b/docs/LICENSE.md deleted file mode 120000 index 7eabdb1c27..0000000000 --- a/docs/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.md \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index d68bb3497c..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Workflow SDK Docs - -Check out the docs [here](https://useworkflow.dev/) diff --git a/docs/app/[lang]/(home)/components/code-block.tsx b/docs/app/[lang]/(home)/components/code-block.tsx deleted file mode 100644 index 17c0123c96..0000000000 --- a/docs/app/[lang]/(home)/components/code-block.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { codeToHtml } from 'shiki'; -import { cn } from '@/lib/utils'; - -type CodeBlockProps = { - code: string; - lang: string; - codeblock?: { - className?: string; - }; -}; - -export const CodeBlock = async ({ code, lang, codeblock }: CodeBlockProps) => { - const html = await codeToHtml(code, { - lang, - themes: { - light: 'github-light-default', - dark: 'github-dark-default', - }, - defaultColor: false, - }); - - return ( -
- ); -}; diff --git a/docs/app/[lang]/(home)/components/cta.tsx b/docs/app/[lang]/(home)/components/cta.tsx deleted file mode 100644 index 4275bad89b..0000000000 --- a/docs/app/[lang]/(home)/components/cta.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import Link from 'next/link'; -import { Button } from '@/components/ui/button'; - -export const CTA = () => ( -
-

- Create your first workflow today. -

- -
-); diff --git a/docs/app/[lang]/(home)/components/features.tsx b/docs/app/[lang]/(home)/components/features.tsx deleted file mode 100644 index ece1bfbcba..0000000000 --- a/docs/app/[lang]/(home)/components/features.tsx +++ /dev/null @@ -1,30 +0,0 @@ -const data = [ - { - title: 'Reliability, minus the plumbing', - description: - 'Start with plain async code. No queues to wire, no schedulers to tune, no YAML. Best‑in‑class DX that compiles reliability into your app with zero config.', - }, - { - title: 'See every step, instantly', - description: - 'Inspect every run end‑to‑end. Pause, replay, and time‑travel through steps with traces, logs, and metrics automatically captured — no extra services or setup.', - }, - { - title: 'A versatile paradigm', - description: - 'Workflows can power a wide array of apps, from streaming realtime agents, to CI/CD pipelines, or multi day email subscriptions workflows.', - }, -]; - -export const Features = () => ( -
- {data.map((item) => ( -
-

- {item.title} -

-

{item.description}

-
- ))} -
-); diff --git a/docs/app/[lang]/(home)/components/frameworks.tsx b/docs/app/[lang]/(home)/components/frameworks.tsx deleted file mode 100644 index 2c9d68deda..0000000000 --- a/docs/app/[lang]/(home)/components/frameworks.tsx +++ /dev/null @@ -1,809 +0,0 @@ -'use client'; - -import { track } from '@vercel/analytics'; -import Link from 'next/link'; -import type { ComponentProps } from 'react'; -import { toast } from 'sonner'; -import { Badge } from '@/components/ui/badge'; - -export const Express = (props: ComponentProps<'svg'>) => ( - - Express - - -); - -export const Fastify = (props: ComponentProps<'svg'>) => ( - - Fastify - - -); - -export const AstroDark = (props: ComponentProps<'svg'>) => ( - - Astro - - - - - - - - - - - - - - - -); - -export const AstroLight = (props: ComponentProps<'svg'>) => ( - - Astro - - - - - - - - - - -); - -export const AstroGray = (props: ComponentProps<'svg'>) => ( - - Astro - - - - - - - - - - -); - -export const TanStack = (props: ComponentProps<'svg'>) => ( - - TanStack - - - - - - - - - -); - -export const TanStackGray = (props: ComponentProps<'svg'>) => ( - - - -); - -export const Vite = (props: ComponentProps<'svg'>) => ( - - Vite - - - - - - - - - - - - - - -); - -export const Nitro = (props: ComponentProps<'svg'>) => ( - - Nitro - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); - -export const SvelteKit = (props: ComponentProps<'svg'>) => ( - - SvelteKit - - - -); - -export const SvelteKitGray = (props: ComponentProps<'svg'>) => ( - - SvelteKit - - - -); - -export const Nuxt = (props: ComponentProps<'svg'>) => ( - - Nuxt - - -); - -export const NuxtGray = (props: ComponentProps<'svg'>) => ( - - Nuxt - - -); - -export const Hono = (props: ComponentProps<'svg'>) => ( - - Hono - - - - - - - - - -); - -export const HonoGray = (props: ComponentProps<'svg'>) => ( - - Hono - - - - - - - - - -); - -export const Bun = (props: ComponentProps<'svg'>) => ( - - Bun - - - - - - - - - - - - - - - - - - - - - - - - - -); - -export const BunGray = (props: ComponentProps<'svg'>) => ( - - Bun - - - - - - - - - - - - - - - - - - - - - - - - - -); - -export const Nest = (props: ComponentProps<'svg'>) => ( - - NestJS - - -); - -export const NestGray = (props: ComponentProps<'svg'>) => ( - - NestJS - - -); - -export const Next = (props: ComponentProps<'svg'>) => ( - - Next.js - - - - - - - - - - - - - - -); - -export const Frameworks = () => { - const handleRequest = (framework: string) => { - track('Framework requested', { framework: framework.toLowerCase() }); - toast.success('Request received', { - description: `Thanks for expressing interest in ${framework}. We will be adding support for it soon.`, - }); - }; - - return ( -
-
-

- Universally compatible. Works - with the frameworks you already use with more coming soon. -

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - Coming soon - -
-
handleRequest('TanStack')} - > - - -
-
-
-
-
- ); -}; diff --git a/docs/app/[lang]/(home)/components/hero.tsx b/docs/app/[lang]/(home)/components/hero.tsx deleted file mode 100644 index a5eb732c28..0000000000 --- a/docs/app/[lang]/(home)/components/hero.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client'; - -import { track } from '@vercel/analytics'; -import { CheckIcon, CopyIcon } from 'lucide-react'; -import Link from 'next/link'; -import { useState } from 'react'; -import { toast } from 'sonner'; -import { Button } from '@/components/ui/button'; - -type HeroProps = { - title: string; - description: string; -}; - -export const Hero = ({ title, description }: HeroProps) => { - const [copied, setCopied] = useState(false); - - const handleCopy = () => { - try { - navigator.clipboard.writeText('npm install workflow'); - setCopied(true); - track('Copy installer command'); - setTimeout(() => { - setCopied(false); - }, 2000); - } catch (error) { - const message = - error instanceof Error - ? error.message - : 'Failed to copy text to clipboard'; - - toast.error(message); - } - }; - - const Icon = copied ? CheckIcon : CopyIcon; - - return ( -
-
-

- {title} -

-

- - use workflow - {' '} - brings durability, reliability, and observability to async JavaScript. - Build apps and AI Agents that can suspend, resume, and maintain state - with ease. -

-
-
- -
-
-            npm i workflow
-          
- -
-
-
- ); -}; diff --git a/docs/app/[lang]/(home)/components/implementation.tsx b/docs/app/[lang]/(home)/components/implementation.tsx deleted file mode 100644 index eb665bfce7..0000000000 --- a/docs/app/[lang]/(home)/components/implementation.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { CodeBlock } from '@/app/[lang]/(home)/components/code-block'; - -const data = [ - { - code: `import { sleep } from "workflow"; -import { - createUser, - sendWelcomeEmail, - sendOneWeekCheckInEmail -} from "./steps" - -export async function userSignup(email) { - "use workflow"; - - // Create the user and send the welcome email - const user = await createUser(email); - await sendWelcomeEmail(email); - - // Pause for 7 days - // without consuming any resources - await sleep("7 days"); - await sendOneWeekCheckInEmail(email); - - return { userId: user.id, status: "done" }; -}`, - caption: 'Creating a workflow', - }, - { - code: `import { Resend } from 'resend'; -import { FatalError } from 'workflow'; - -export async function sendWelcomeEmail(email) { - "use step" - - const resend = new Resend('YOUR_API_KEY'); - - const resp = await resend.emails.send({ - from: 'Acme ', - to: [email], - subject: 'Welcome!', - html: \`Thanks for joining Acme.\`, - }); - - if (resp.error) { - throw new FatalError(resp.error.message); - } -}; - -// Other steps...`, - caption: 'Defining steps', - }, -]; - -export const Implementation = () => ( -
-
-

- Effortless setup -

-

- With a simple declarative API to define and use your workflows. -

-
-
- {data.map((item) => ( -
-

- {item.caption} -

- -
- ))} -
-
-); diff --git a/docs/app/[lang]/(home)/components/intro/intro-tabs.tsx b/docs/app/[lang]/(home)/components/intro/intro-tabs.tsx deleted file mode 100644 index e753fd0ee8..0000000000 --- a/docs/app/[lang]/(home)/components/intro/intro-tabs.tsx +++ /dev/null @@ -1,67 +0,0 @@ -'use client'; - -import { track } from '@vercel/analytics'; -import type { ReactNode } from 'react'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; - -interface Tab { - id: string; - smallLabel: string; - label: string; -} - -const tabs: Tab[] = [ - { - id: 'with', - smallLabel: 'With Workflow SDK', - label: 'With Workflow SDK', - }, - { - id: 'without', - smallLabel: 'Without Workflow SDK', - label: 'Without Workflow SDK', - }, -]; - -export const IntroTabs = ({ - withWorkflow, - withoutWorkflow, -}: { - withWorkflow: ReactNode; - withoutWorkflow: ReactNode; -}) => { - const tabContent: Record = { - with: withWorkflow, - without: withoutWorkflow, - }; - - return ( - track('Intro tab changed', { tab: value })} - > - - {tabs.map((tab) => ( - - {tab.label} - {tab.smallLabel} - - ))} - - {tabs.map((tab) => ( - - {tabContent[tab.id]} - - ))} - - ); -}; diff --git a/docs/app/[lang]/(home)/components/intro/intro.tsx b/docs/app/[lang]/(home)/components/intro/intro.tsx deleted file mode 100644 index 7291bde09d..0000000000 --- a/docs/app/[lang]/(home)/components/intro/intro.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { CodeBlock } from '@/app/[lang]/(home)/components/code-block'; -import { IntroTabs } from './intro-tabs'; -import { NonWorkflowExample } from './non-workflow'; -import { WorkflowExample } from './workflow'; - -const workflowCode = `export async function welcome(userId: string) { - "use workflow"; - const user = await getUser(userId); - const { subject, body } = await generateEmail({ - name: user.name, plan: user.plan - }); - const { status } = await sendEmail({ - to: user.email, - subject, - body, - }); - return { status, subject, body }; -}`; - -const nonWorkflowCode = `export async function welcome(userId: string) { - - const user = await getUser(userId); - const { subject, body } = await generateEmail({ - name: user.name, plan: user.plan - }); - const { status } = await sendEmail({ - to: user.email, - subject, - body, - }); - return { status, subject, body }; -}`; - -const workflowLogs = [ - // Line 1 (getUser): 3s total - { - duration: 500, - text: 'Queueing the getUser step...', - }, - { - duration: 2500, - text: 'Running the getUser step...', - }, - { - duration: 1000, - text: 'getUser step succeeded. logging telemetry...', - }, - // 2s delay then Line 2 (generateEmail): 3s - { - duration: 1000, - text: 'Queueing the generateEmail step...', - }, - { - duration: 3000, - text: 'Running the generateEmail step...', - }, - { - duration: 1000, - text: 'generateEmail step succeeded. logging telemetry...', - }, - // 2s delay then Line 3 (sendEmail): 3s → error - { - duration: 1000, - text: 'Queueing the sendEmail step...', - }, - { - duration: 3000, - text: 'Running the sendEmail step...', - }, - { - duration: 1000, - text: 'sendEmail step failed, retrying...', - }, - // 1s wait, then show "Retrying..." for 1s - { - duration: 2000, - text: 'Retrying...', - }, - // 2s delay then Line 3 retry: 4s → success - { - duration: 1000, - text: 'Queueing the sendEmail step...', - }, - { - duration: 2500, - text: 'Running the sendEmail step...', - }, - { - duration: 1000, - text: 'sendEmail step succeeded. logging telemetry...', - }, - { - duration: 1000, - text: 'Workflow completed. logging telemetry...', - }, -]; - -const nonWorkflowLogs = [ - // Line 1 (getUser): 3s total - { - duration: 3000, - text: 'Calling getUser directly...', - }, - { - duration: 2000, - text: 'getUser completed.', - }, - // 2s delay then Line 2 (generateEmail): 3s - { - duration: 1000, - text: 'Calling generateEmail directly...', - }, - { - duration: 2000, - text: 'Waiting for LLM response...', - }, - { - duration: 1500, - text: 'generateEmail timed out, process failed.', - }, -]; - -export const Intro = async () => { - const codeBlockClassname = - 'shadow-none border-x-0 border-t-0 dark:bg-sidebar with-line-numbers with-checks'; - - const workflowCodeBlock = ( - - ); - const nonWorkflowCodeBlock = ( - - ); - - return ( -
-
-

- Reliability-as-code -

-

- Move from hand-rolled queues and custom retries to durable, resumable - code with simple directives. -

-
-
- - } - withoutWorkflow={ - - } - /> -
-
- ); -}; diff --git a/docs/app/[lang]/(home)/components/intro/non-workflow.tsx b/docs/app/[lang]/(home)/components/intro/non-workflow.tsx deleted file mode 100644 index 7cd90633d7..0000000000 --- a/docs/app/[lang]/(home)/components/intro/non-workflow.tsx +++ /dev/null @@ -1,118 +0,0 @@ -'use client'; - -import { CheckIcon, Loader2Icon, XIcon } from 'lucide-react'; -import { useEffect, useState } from 'react'; -import { WorkflowLogs } from './workflow-logs'; - -type WorkflowLog = { - duration: number; - text: string; -}; - -const Loading = ( - -); -const Success = ( -
- -
-); -const ErrorIndicator = ( -
- -
-); - -type LineState = 'idle' | 'loading' | 'success' | 'error'; - -export const NonWorkflowExample = ({ - codeBlock, - logs, -}: { - codeBlock: React.ReactNode; - logs: WorkflowLog[]; -}) => { - const [lineStates, setLineStates] = useState([ - 'idle', - 'idle', - 'idle', - ]); - - useEffect(() => { - let cancelled = false; - - const animate = async () => { - // Reset to idle - setLineStates(['idle', 'idle', 'idle']); - - // Line 1 (getUser): 3s total - setLineStates((prev) => { - const next = [...prev]; - next[0] = 'loading'; - return next; - }); - - await new Promise((resolve) => setTimeout(resolve, 3000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[0] = 'success'; - return next; - }); - - // Line 2 (generateEmail): 2s delay + 3s loading → error - await new Promise((resolve) => setTimeout(resolve, 2000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[1] = 'loading'; - return next; - }); - - await new Promise((resolve) => setTimeout(resolve, 3000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[1] = 'error'; - return next; - }); - }; - - animate(); - - return () => { - cancelled = true; - }; - }, []); - - const renderIndicator = (state: LineState) => { - if (state === 'loading') return Loading; - if (state === 'success') return Success; - if (state === 'error') return ErrorIndicator; - return null; - }; - - return ( -
-
-
-
- {renderIndicator(lineStates[0])} -
- {renderIndicator(lineStates[1])} -
- {renderIndicator(lineStates[2])} -
- {codeBlock} -
- -
-
- ); -}; diff --git a/docs/app/[lang]/(home)/components/intro/workflow-logs.tsx b/docs/app/[lang]/(home)/components/intro/workflow-logs.tsx deleted file mode 100644 index 35e476a803..0000000000 --- a/docs/app/[lang]/(home)/components/intro/workflow-logs.tsx +++ /dev/null @@ -1,95 +0,0 @@ -'use client'; - -import { - AnimatePresence, - type AnimatePresenceProps, - motion, - type Transition, - type Variants, -} from 'motion/react'; -import { useEffect, useState } from 'react'; -import { cn } from '@/lib/utils'; - -type WorkflowLog = { - duration: number; - text: string; -}; - -export type WorkflowLogsProps = { - logs: WorkflowLog[]; - className?: string; - transition?: Transition; - variants?: Variants; - onIndexChange?: (index: number) => void; - trigger?: boolean; - mode?: AnimatePresenceProps['mode']; -}; - -export function WorkflowLogs({ - logs, - className, - transition = { duration: 0.3 }, - variants, - onIndexChange, - trigger = true, - mode = 'popLayout', -}: WorkflowLogsProps) { - const [currentIndex, setCurrentIndex] = useState(0); - - useEffect(() => { - if (!trigger || currentIndex >= logs.length) return; - - const currentLog = logs[currentIndex]; - - const timer = setTimeout(() => { - const nextIndex = currentIndex + 1; - - if (nextIndex < logs.length) { - onIndexChange?.(nextIndex); - setCurrentIndex(nextIndex); - } - }, currentLog.duration); - - return () => clearTimeout(timer); - }, [currentIndex, logs, onIndexChange, trigger]); - - const motionVariants: Variants = { - initial: { y: 20, opacity: 0 }, - animate: { y: 0, opacity: 1 }, - exit: { y: -20, opacity: 0 }, - }; - - if (currentIndex >= logs.length) { - return null; - } - - const currentText = logs[currentIndex].text; - const hasError = - currentText.toLowerCase().includes('error') || - currentText.toLowerCase().includes('failed'); - - return ( -
- - - {currentText} - - -
- ); -} diff --git a/docs/app/[lang]/(home)/components/intro/workflow.tsx b/docs/app/[lang]/(home)/components/intro/workflow.tsx deleted file mode 100644 index f4754f7840..0000000000 --- a/docs/app/[lang]/(home)/components/intro/workflow.tsx +++ /dev/null @@ -1,176 +0,0 @@ -'use client'; - -import { CheckIcon, Loader2Icon, XIcon } from 'lucide-react'; -import { useEffect, useState } from 'react'; -import { WorkflowLogs } from './workflow-logs'; - -type WorkflowLog = { - duration: number; - text: string; -}; - -const Loading = ( - -); -const Success = ( -
- -
-); -const ErrorIndicator = ( -
- -
-); - -type LineState = 'idle' | 'loading' | 'success' | 'error'; - -export const WorkflowExample = ({ - codeBlock, - logs, -}: { - codeBlock: React.ReactNode; - logs: WorkflowLog[]; -}) => { - const [lineStates, setLineStates] = useState([ - 'idle', - 'idle', - 'idle', - ]); - const [isRetry, setIsRetry] = useState(false); - - useEffect(() => { - let cancelled = false; - - const animate = async () => { - if (isRetry) { - // On retry with workflow: only the failed step re-runs - // Steps 1 and 2 are already successful, so skip directly to step 3 - // Wait 2s delay before starting - await new Promise((resolve) => setTimeout(resolve, 2000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[2] = 'loading'; - return next; - }); - - // Run for 4s (queue + run + success logs) - await new Promise((resolve) => setTimeout(resolve, 4000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[2] = 'success'; - return next; - }); - } else { - // Initial run: reset to idle - setLineStates(['idle', 'idle', 'idle']); - - // Line 1 (getUser): 3 logs, 3 seconds total - setLineStates((prev) => { - const next = [...prev]; - next[0] = 'loading'; - return next; - }); - - await new Promise((resolve) => setTimeout(resolve, 3000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[0] = 'success'; - return next; - }); - - // Line 2 (generateEmail): 2s delay + 3 logs - await new Promise((resolve) => setTimeout(resolve, 2000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[1] = 'loading'; - return next; - }); - - await new Promise((resolve) => setTimeout(resolve, 3000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[1] = 'success'; - return next; - }); - - // Line 3 (sendEmail): 2s delay + 4 logs, ends in error - await new Promise((resolve) => setTimeout(resolve, 2000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[2] = 'loading'; - return next; - }); - - await new Promise((resolve) => setTimeout(resolve, 3000)); - - if (cancelled) return; - - setLineStates((prev) => { - const next = [...prev]; - next[2] = 'error'; - return next; - }); - - // Wait 1s after error, then trigger retry - await new Promise((resolve) => setTimeout(resolve, 1000)); - - if (cancelled) return; - - setIsRetry(true); - } - }; - - animate(); - - return () => { - cancelled = true; - }; - }, [isRetry]); - - const renderIndicator = (state: LineState) => { - if (state === 'loading') return Loading; - if (state === 'success') return Success; - if (state === 'error') return ErrorIndicator; - return null; - }; - - return ( -
-
-
-
- {renderIndicator(lineStates[0])} -
- {renderIndicator(lineStates[1])} -
- {renderIndicator(lineStates[2])} -
- {codeBlock} -
- -
-
- ); -}; diff --git a/docs/app/[lang]/(home)/components/observability.tsx b/docs/app/[lang]/(home)/components/observability.tsx deleted file mode 100644 index 3721b2eacf..0000000000 --- a/docs/app/[lang]/(home)/components/observability.tsx +++ /dev/null @@ -1,99 +0,0 @@ -'use client'; - -import { motion } from 'motion/react'; -import { cn } from '@/lib/utils'; - -const rows = [ - { - label: 'workflow()', - className: - 'bg-[#E1F0FF] dark:bg-[#00254D] border-[#99CEFF] text-[#0070F3] dark:border-[#0067D6] dark:text-[#52AEFF]', - start: 0, - duration: 100, - }, - { - label: 'process()', - className: - 'bg-[#DCF6DC] dark:bg-[#1B311E] border-[#99E59F] text-[#46A758] dark:border-[#297C3B] dark:text-[#6CDA76]', - start: 0, - duration: 20, - }, - { - label: 'parse()', - className: - 'bg-[#DCF6DC] dark:bg-[#1B311E] border-[#99E59F] text-[#46A758] dark:border-[#297C3B] dark:text-[#6CDA76]', - start: 20, - duration: 25, - }, - { - label: 'transform()', - className: - 'bg-[#DCF6DC] dark:bg-[#1B311E] border-[#99E59F] text-[#46A758] dark:border-[#297C3B] dark:text-[#6CDA76]', - start: 45, - duration: 20, - }, - { - label: 'enrich()', - className: - 'bg-[#DCF6DC] dark:bg-[#1B311E] border-[#99E59F] text-[#46A758] dark:border-[#297C3B] dark:text-[#6CDA76]', - start: 65, - duration: 15, - }, - { - label: 'validate()', - className: - 'bg-[#DCF6DC] dark:bg-[#1B311E] border-[#99E59F] text-[#46A758] dark:border-[#297C3B] dark:text-[#6CDA76]', - start: 80, - duration: 20, - }, -]; - -export const Observability = () => ( -
-

- Observability. Inspect every run - end‑to‑end. Pause, replay, and time‑travel through steps with traces, - logs, and metrics automatically. -

-
-
- {rows.map((row, index) => ( -
-
- -
- - {row.label} - - {index === 0 && ( - {row.duration}ms - )} -
-
-
-
- ))} -
-
-
-); diff --git a/docs/app/[lang]/(home)/components/preview-badge.tsx b/docs/app/[lang]/(home)/components/preview-badge.tsx deleted file mode 100644 index 204c2735d3..0000000000 --- a/docs/app/[lang]/(home)/components/preview-badge.tsx +++ /dev/null @@ -1,136 +0,0 @@ -'use client'; - -import { CheckIcon, CopyIcon, ExternalLinkIcon, EyeIcon } from 'lucide-react'; -import { useState } from 'react'; -import { toast } from 'sonner'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog'; - -type PreviewBadgeProps = { - deploymentUrl: string; -}; - -function CopyButton({ text }: { text: string }) { - const [copied, setCopied] = useState(false); - - const handleCopy = () => { - if ( - typeof navigator === 'undefined' || - !navigator.clipboard || - typeof navigator.clipboard.writeText !== 'function' - ) { - toast.error('Clipboard not available'); - return; - } - - try { - const writeResult = navigator.clipboard.writeText(text); - - Promise.resolve(writeResult) - .then(() => { - setCopied(true); - toast.success('Copied to clipboard'); - setTimeout(() => setCopied(false), 2000); - }) - .catch(() => { - toast.error('Failed to copy'); - }); - } catch { - toast.error('Failed to copy'); - } - }; - - return ( - - ); -} - -export function PreviewBadge({ deploymentUrl }: PreviewBadgeProps) { - const baseUrl = deploymentUrl.replace(/\/$/, ''); - const installCmd = `pnpm i ${baseUrl}/workflow.tgz`; - const npxCmd = `npx workflow@${baseUrl}/workflow.tgz web`; - - return ( - - - - - - - Preview Deployment - - {"You're viewing a preview deployment. Helpful links:"} - - -
-
-

- Install the workflow package from this commit: -

-
- - {installCmd} - - -
-
-
-

- Run the web UI in your project: -

-
- - {npxCmd} - - -
-
-
-

- SWC Compiler Playground: -

- - - workflow-swc-playground.labs.vercel.dev - - - -
-
-
-
- ); -} diff --git a/docs/app/[lang]/(home)/components/run-anywhere.tsx b/docs/app/[lang]/(home)/components/run-anywhere.tsx deleted file mode 100644 index d57363118c..0000000000 --- a/docs/app/[lang]/(home)/components/run-anywhere.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import type { ComponentProps } from 'react'; -import { CodeBlock } from '@/app/[lang]/(home)/components/code-block'; -import { cn } from '@/lib/utils'; - -const DigitalOcean = (props: ComponentProps<'svg'>) => ( - - DigitalOcean - - - - - -); - -const AWS = (props: ComponentProps<'svg'>) => ( - - AWS - - - - -); - -const Docker = (props: ComponentProps<'svg'>) => ( - - Docker - - -); - -const Vercel = (props: ComponentProps<'svg'>) => ( - - Vercel - - -); - -const code = `export async function welcome(userId: string) { - "use workflow"; - - const user = await getUser(userId); - const { subject, body } = await generateEmail({ - name: user.name, plan: user.plan - }); - - const { status } = await sendEmail({ - to: user.email, - subject, - body, - }); - - return { status, subject, body }; -}`; - -export const RunAnywhere = () => ( -
-
-

- Run anywhere, no lock‑in -

-

- The same code runs locally on your laptop, in Docker, on Vercel or any - other cloud. Open source and portable by design. -

-
-
-
- {[DigitalOcean, AWS].map((Logo, index) => ( -
- -
- ))} -
-
- -
-
- {[Docker, Vercel].map((Logo, index) => ( -
- -
- ))} -
-
-
-); diff --git a/docs/app/[lang]/(home)/components/templates/flight.png b/docs/app/[lang]/(home)/components/templates/flight.png deleted file mode 100644 index a624ff7b05..0000000000 Binary files a/docs/app/[lang]/(home)/components/templates/flight.png and /dev/null differ diff --git a/docs/app/[lang]/(home)/components/templates/index.tsx b/docs/app/[lang]/(home)/components/templates/index.tsx deleted file mode 100644 index 1d3f2df6d4..0000000000 --- a/docs/app/[lang]/(home)/components/templates/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import Image from 'next/image'; -import { cn } from '@/lib/utils'; -import Flight from './flight.png'; -import Storytime from './storytime.png'; -import Vectr from './vectr.png'; - -const data = [ - { - title: 'Story Generator Slack Bot', - description: - "Slackbot that generates children's stories from collaborative input.", - image: Storytime, - link: 'https://vercel.com/guides/stateful-slack-bots-with-vercel-workflow', - }, - { - title: 'Flight Booking App', - description: - 'Use Workflow to make AI agents more reliable and production-ready.', - image: Flight, - link: 'https://github.com/vercel/workflow-examples/tree/main/flight-booking-app', - }, - { - title: 'Natural Language Image Search', - description: - 'A free, open-source template for building natural language image search.', - image: Vectr, - link: 'https://www.vectr.store', - }, -]; - -export const Templates = () => ( -
-
-

- Get started quickly -

-

- See Workflow SDK in action with one of our templates. -

-
-
- {data.map((item) => ( - -

{item.title}

-

- {item.description} -

- {item.title} -
- ))} -
-
-); diff --git a/docs/app/[lang]/(home)/components/templates/storytime.png b/docs/app/[lang]/(home)/components/templates/storytime.png deleted file mode 100644 index 617d33b34d..0000000000 Binary files a/docs/app/[lang]/(home)/components/templates/storytime.png and /dev/null differ diff --git a/docs/app/[lang]/(home)/components/templates/vectr.png b/docs/app/[lang]/(home)/components/templates/vectr.png deleted file mode 100644 index 71c469d471..0000000000 Binary files a/docs/app/[lang]/(home)/components/templates/vectr.png and /dev/null differ diff --git a/docs/app/[lang]/(home)/components/tweet-wall.tsx b/docs/app/[lang]/(home)/components/tweet-wall.tsx deleted file mode 100644 index aa2777d824..0000000000 --- a/docs/app/[lang]/(home)/components/tweet-wall.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import type { ReactNode } from 'react'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; - -const BLOB_URL = 'https://lishhsx6kmthaacj.public.blob.vercel-storage.com'; - -type Tweet = { - url: string; - name: string; - username: string; - image: string; - tweet: ReactNode; -}; - -const TWEETS: Tweet[] = [ - { - url: 'https://x.com/michaelcaaarter/status/1986078356325187762', - name: 'Michael Carter', - username: 'michaelcaaarter', - image: `${BLOB_URL}/michaelcaaarter.jpg`, - tweet: ( - - We just migrated to use workflow and it's - beautiful. Production app here, VC backed and many real fortune 100 - customers using our app daily… not sure why you wouldn't{' '} - use workflow to move fast and focus on building - a great experience. - - ), - }, - { - url: 'https://x.com/nick_tikhonov/status/1985971284577050699', - name: 'Nick Tikhonov', - username: 'nick_tikhonov', - image: `${BLOB_URL}/nick_tikhonov.jpg`, - tweet: ( - <> - - fully migrated to workflows - our use case are AI agents that execute - over a multiple-day time frame, making multiple outbound voice calls - and processing the results - - - before:
- scheduling service
- queues
- workers{' '} -
- cron jobs -
- now: - 5 functions in one file - - 🤯 - - - ), - }, - { - url: 'https://x.com/ryancarson/status/1996318671749120315', - name: 'Ryan Carson', - username: 'ryancarson', - image: `${BLOB_URL}/ryancarson.jpg`, - tweet: ( - <> - What a time to be a content marketer. - Sheesh this is mind-blowing. - - Built a complete end-to-end workflow with{' '} - @ampcode using{' '} - @WorkflowDevKit and{' '} - @vercel AI Gateway - - - - Custom DurableAgent tools for research
- Opus 4.5 for - generation
- Gemini 3 Pro for content verification -
- Nano Banana for image creation -
- AEO locked in. - - ), - }, - { - url: 'https://x.com/ale__vigano/status/1993822442616213851', - name: 'Ale Vigano', - username: 'ale__vigano', - image: `${BLOB_URL}/ale__vigano.jpg`, - tweet: ( - <> - - During my time at @mercadopago we struggled a - lot with concurrency issues handling millions of payments. - - - Hard to believe that almost all the complexity I remember from back - then is now solved with just a use workflow - - - ), - }, -]; - -function InlineCode({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} - -function InlineLink({ children }: { children: ReactNode }) { - return {children}; -} - -function VerifiedBadge() { - return ( - - - - ); -} - -function TweetCard({ url, name, username, image, tweet }: Tweet) { - return ( - -
- - - {name[0]} - -
- - {name} - - - @{username} -
-
-

- {tweet} -

-
- ); -} - -export const TweetWall = () => ( -
-

- What builders say about Workflow SDK -

-
- {TWEETS.map((tweet) => ( -
- -
- ))} -
-
-); diff --git a/docs/app/[lang]/(home)/components/use-cases-client.tsx b/docs/app/[lang]/(home)/components/use-cases-client.tsx deleted file mode 100644 index 59499cdafa..0000000000 --- a/docs/app/[lang]/(home)/components/use-cases-client.tsx +++ /dev/null @@ -1,55 +0,0 @@ -'use client'; - -import { track } from '@vercel/analytics'; -import { useState } from 'react'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; - -type UseCase = { - id: string; - label: string; - codeBlock: React.ReactNode; -}; - -export const UseCasesClient = ({ useCases }: { useCases: UseCase[] }) => { - const [selectedCase, setSelectedCase] = useState(useCases[0].id); - const currentCase = - useCases.find((uc) => uc.id === selectedCase) || useCases[0]; - - const handleCaseChange = (value: string) => { - setSelectedCase(value); - track('Use case changed', { case: value }); - }; - - return ( -
-
-

- Build anything with - -

-

- Build reliable, long-running processes with automatic retries, state - persistence, and observability built in. -

-
-
{currentCase.codeBlock}
-
- ); -}; diff --git a/docs/app/[lang]/(home)/components/use-cases-server.tsx b/docs/app/[lang]/(home)/components/use-cases-server.tsx deleted file mode 100644 index 1689911904..0000000000 --- a/docs/app/[lang]/(home)/components/use-cases-server.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { CodeBlock } from '@/app/[lang]/(home)/components/code-block'; -import { UseCasesClient } from './use-cases-client'; - -const useCases = [ - { - id: 'ai-agents', - label: 'AI Agents', - code: `export async function aiAgentWorkflow(query: string) { - "use workflow"; - - // Step 1: Generate initial response - const response = await generateResponse(query); - - // Step 2: Research and validate - const facts = await researchFacts(response); - - // Step 3: Refine with fact-checking - const refined = await refineWithFacts(response, facts); - - return { response: refined, sources: facts }; -}`, - }, - { - id: 'retrying', - label: 'Retrying', - code: `import { RetryableError, FatalError } from "workflow"; - -async function callAPI(endpoint) { - "use step"; - - const response = await fetch(endpoint); - - if (response.status >= 500) { - // Uncaught exceptions are retried by default - throw new Error("Server error"); - } - - if (response.status === 404) { - // Explicitly throw a FatalError to skip retrying - throw new FatalError("Resource not found. Skipping retries."); - } - - if (response.status === 429) { - // Customize retry delay - accepts duration strings, milliseconds, or Date instances - throw new RetryableError("Too many requests. Retrying...", { - retryAfter: "30s" - }); - } - - return response.json(); -} - -// Customize max retries -callAPI.maxRetries = 5;`, - }, - { - id: 'sleep', - label: 'Sleep', - code: `import { sleep } from "workflow"; - -export async function sendBirthdayCard(birthday: Date) { - "use workflow"; - - // Sleep for minutes, days, weeks or even months - await sleep("5 days"); - - // Or sleep until a certain date - await sleep(birthday); - - // The workflow consumes no resources while asleep - - await sendBirthdayCard(); -};`, - }, - { - id: 'webhook', - label: 'Webhook', - code: `import { createWebhook, fetch } from "workflow"; - -export async function validatePaymentMethod(rideId) { - "use workflow"; - - // Create a new webhook - const webhook = createWebhook(); - - // Every webhook has a url that can be used to resume - // the workflow - await fetch("https://api.example-payments.com/validate-method", { - method: "POST", - body: JSON.stringify({ rideId, callback: webhook.url }), - }); - - // Suspend the workflow until the webhook is invoked - const { request } = await webhook; - - const confirmation = await request.json(); - - return { rideId, status: confirmation.status }; -}`, - }, - { - id: 'streaming', - label: 'Streaming', - code: `import { getWritable } from "workflow"; - -export async function streamWorkflow() { - "use workflow"; - - // Get the workflow's writable stream - const writable = getWritable(); - - // And send it into a step - await writeStream(writable, 'Hello, world!'); -} - -async function writeStream(writable: WritableStream, data: string) { - "use step"; - - // Steps can write to the stream - const writer = writable.getWriter(); - await writer.write(new TextEncoder().encode(data)); - writer.close(); -}; - -const run = await start(streamWorkflow); // Start the workflow -const stream = run.readable; // Consume the readable stream`, - }, - { - id: 'concurrency', - label: 'Concurrency', - code: `export async function getUserDetails(userId: string) { - "use workflow"; - - // Running steps in parallel just uses Promise.all - const [user, orders, preferences] = await Promise.all([ - fetchUser(userId), - fetchOrders(userId), - fetchPreferences(userId) - ]); - - // Use Promise.race to get the fastest response from multiple sources - const inventory = await Promise.race([ - fetchFromUSWarehouse(userId), - fetchFromEUWarehouse(userId), - sleep("10m") - ]); - - return { user, orders, preferences, inventory }; -}`, - }, -]; - -export const UseCases = async () => { - const codeBlocks = await Promise.all( - useCases.map(async (useCase) => ({ - id: useCase.id, - label: useCase.label, - codeBlock: ( - - ), - })) - ); - - return ; -}; diff --git a/docs/app/[lang]/(home)/layout.tsx b/docs/app/[lang]/(home)/layout.tsx deleted file mode 100644 index 3600925c2c..0000000000 --- a/docs/app/[lang]/(home)/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { HomeLayout } from '@/components/geistdocs/home-layout'; -import { source } from '@/lib/geistdocs/source'; - -const Layout = async ({ children, params }: LayoutProps<'/[lang]'>) => { - const { lang } = await params; - - return ( - -
{children}
-
- ); -}; - -export default Layout; diff --git a/docs/app/[lang]/(home)/page.tsx b/docs/app/[lang]/(home)/page.tsx deleted file mode 100644 index f0ca14e1d5..0000000000 --- a/docs/app/[lang]/(home)/page.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import type { Metadata } from 'next'; -import { CTA } from './components/cta'; -import { Features } from './components/features'; -import { Frameworks } from './components/frameworks'; -import { Hero } from './components/hero'; -import { Implementation } from './components/implementation'; -import { Intro } from './components/intro/intro'; -import { Observability } from './components/observability'; -import { PreviewBadge } from './components/preview-badge'; -import { RunAnywhere } from './components/run-anywhere'; -import { Templates } from './components/templates'; -import { TweetWall } from './components/tweet-wall'; -import { UseCases } from './components/use-cases-server'; - -const title = 'Make any TypeScript Function Durable'; -const description = - '"use workflow" brings durability, reliability, and observability to async JavaScript. Build apps and AI Agents that can suspend, resume, and maintain state with ease.'; - -export const metadata: Metadata = { - title: 'Workflow SDK - Make any TypeScript Function Durable', - description, - alternates: { - canonical: '/', - }, - openGraph: { - images: ['/og'], - }, -}; - -const isPreview = process.env.VERCEL_ENV === 'preview'; -const deploymentUrl = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : ''; - -const Home = () => ( -
-
- - {isPreview && deploymentUrl && ( -
- -
- )} -
- - -
- - -
- - - - - - -
-
-
-); - -export default Home; diff --git a/docs/app/[lang]/docs/[[...slug]]/page.tsx b/docs/app/[lang]/docs/[[...slug]]/page.tsx deleted file mode 100644 index e1bca864c3..0000000000 --- a/docs/app/[lang]/docs/[[...slug]]/page.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { Step, Steps } from 'fumadocs-ui/components/steps'; -import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; -import { createRelativeLink } from 'fumadocs-ui/mdx'; -import type { Metadata } from 'next'; -import { notFound } from 'next/navigation'; -import { AgentTraces } from '@/components/custom/agent-traces'; -import { FluidComputeCallout } from '@/components/custom/fluid-compute-callout'; -import { AskAI } from '@/components/geistdocs/ask-ai'; -import { CopyPage } from '@/components/geistdocs/copy-page'; -import { - DocsBody, - DocsDescription, - DocsPage, - DocsTitle, -} from '@/components/geistdocs/docs-page'; -import { EditSource } from '@/components/geistdocs/edit-source'; -import { Feedback } from '@/components/geistdocs/feedback'; -import { getMDXComponents } from '@/components/geistdocs/mdx-components'; -import { OpenInChat } from '@/components/geistdocs/open-in-chat'; -import { ScrollTop } from '@/components/geistdocs/scroll-top'; -import * as AccordionComponents from '@/components/ui/accordion'; -import { Badge } from '@/components/ui/badge'; -import { Separator } from '@/components/ui/separator'; -import { getLLMText, getPageImage, source } from '@/lib/geistdocs/source'; -import { TSDoc } from '@/lib/tsdoc'; - -// No-op component for world MDX files rendered outside /worlds/ context -// These pages redirect to /worlds/[id] but still get statically generated -const WorldTestingPerformanceNoop = () => null; - -const Page = async ({ params }: PageProps<'/[lang]/docs/[[...slug]]'>) => { - const { slug, lang } = await params; - - const page = source.getPage(slug, lang); - - if (!page) { - notFound(); - } - - const markdown = await getLLMText(page); - const MDX = page.data.body; - - return ( - - - - - - - - -
- ), - }} - toc={page.data.toc} - > - {page.data.title} - {page.data.description} - - - - - ); -}; - -export const generateStaticParams = () => source.generateParams(); - -export const generateMetadata = async ({ - params, -}: PageProps<'/[lang]/docs/[[...slug]]'>) => { - const { slug, lang } = await params; - const page = source.getPage(slug, lang); - - if (!page) { - notFound(); - } - - const metadata: Metadata = { - title: page.data.title, - description: page.data.description, - openGraph: { - images: getPageImage(page).url, - }, - alternates: { - canonical: page.url, - types: { - 'text/markdown': `${page.url}.md`, - }, - }, - }; - - return metadata; -}; - -export default Page; diff --git a/docs/app/[lang]/docs/layout.tsx b/docs/app/[lang]/docs/layout.tsx deleted file mode 100644 index 583e850925..0000000000 --- a/docs/app/[lang]/docs/layout.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { DocsLayout } from '@/components/geistdocs/docs-layout'; -import { source } from '@/lib/geistdocs/source'; - -const Layout = async ({ children, params }: LayoutProps<'/[lang]/docs'>) => { - const { lang } = await params; - - return {children}; -}; - -export default Layout; diff --git a/docs/app/[lang]/layout.tsx b/docs/app/[lang]/layout.tsx deleted file mode 100644 index 98c8e61210..0000000000 --- a/docs/app/[lang]/layout.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import '../global.css'; -import type { Metadata } from 'next'; -import { Footer } from '@/components/geistdocs/footer'; -import { Navbar } from '@/components/geistdocs/navbar'; -import { GeistdocsProvider } from '@/components/geistdocs/provider'; -import { basePath } from '@/geistdocs'; -import { mono, sans } from '@/lib/geistdocs/fonts'; -import { cn } from '@/lib/utils'; - -const getMetadataBase = () => { - // Use VERCEL_URL for preview deployments, production URL for production - if (process.env.VERCEL_ENV === 'production') { - return new URL('https://useworkflow.dev'); - } - if (process.env.VERCEL_URL) { - return new URL(`https://${process.env.VERCEL_URL}`); - } - // Fallback for local development - return new URL('http://localhost:3000'); -}; - -export const metadata: Metadata = { - metadataBase: getMetadataBase(), -}; - -const Layout = async ({ children, params }: LayoutProps<'/[lang]'>) => { - const { lang } = await params; - - return ( - - - - - {children} -