Files
vercel__workflow/docs/components/worlds/world-detail-page.tsx
Pranay Prakash 8a872529fe docs: make /worlds the canonical home for World docs (#2934)
* docs: make /worlds the canonical home for World docs

The world pages (Local/Postgres/Vercel) and Building a World were
duplicated inside the v4 and v5 docs trees while /worlds/[id] rendered
the v4 copy — hiding v5-only content like multi-region and leaving two
diverging sources of truth.

- Move world docs to an unversioned docs/content/worlds/ collection
  (based on the v5 copies, with inline 4.x callouts for factory naming
  and 5.x-only env vars), rendered at /worlds/*
- Add /worlds/building-a-world; flatten the docs Deploying section to a
  single intro page and drop its Rocket icon
- Point every link, frontmatter ref, and worlds-manifest docs field at
  /worlds/*; add redirects for the removed v5 and building-a-world URLs
- Keep world docs on agent-facing surfaces: search, llms.txt,
  sitemap.md/.xml, and .md exports now serve the worlds collection
- Extend the docs link linter to validate worlds pages (with heading
  anchors) and their outgoing links

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>

* docs: version the world docs like the docs trees (v4/v5 switcher)

Instead of a single unversioned copy, world docs now follow the same
versioning strategy as the docs pages: content/worlds/v4 is served at
/worlds/* (current) and content/worlds/v5 at /v5/worlds/*, restoring the
original per-version content. Each world detail page (and Building a
World) renders the docs version switcher — the worlds listing page has
no natural home for it, so it lives on the world pages themselves.

- Render-time href rewriting on v5 pages now covers /worlds/... links
  (shared rewriteHrefForVersion helper, also used by the v5 docs and
  cookbook routes), and the markdown-export rewrite does the same
- v5 world pages are noindexed with a canonical to /worlds/<id>;
  community worlds stay unversioned (/v5/worlds/<id> redirects)
- /v5/docs/deploying/world/* redirects now land on /v5/worlds/*;
  /v5/worlds and /v5/worlds/compare redirect to the unversioned pages
- Link linter models the versioned worlds URL spaces (v5 pages resolve
  /worlds hrefs against the v5 collection); sitemap.md and the .md
  export routes cover /v5/worlds/*

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>

* docs: fix v4 multi-region anchor and tighten version-prefix matching

Address PR review:
- The v4 Deploying page linked /worlds/vercel#multi-region, but the
  Multi-region section only exists on the v5 world page; use the
  explicit cross-version /v5/worlds/vercel#multi-region link (this was
  the Docs Links CI failure)
- rewriteHrefForVersion now uses the boundary-checked hasPathPrefix
  (shared leaf module lib/geistdocs/path-prefix.ts, also used by
  source.ts) instead of bare startsWith
- buildVersionUrl's shared-route fast path is segment-based rather than
  substring includes()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>

---------

Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:15:07 +07:00

218 lines
6.7 KiB
TypeScript

import type { TableOfContents } from 'fumadocs-core/toc';
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, redirect } from 'next/navigation';
import type { ComponentProps, ComponentType, ReactNode } from 'react';
import { FluidComputeCallout } from '@/components/custom/fluid-compute-callout';
import { getMDXComponents } from '@/components/geistdocs/mdx-components';
import { v5WorldsSource, worldsSource } from '@/lib/geistdocs/source';
import { rewriteHrefForVersion } from '@/lib/geistdocs/version-href';
import type { DocsVersionId } from '@/lib/geistdocs/versions';
import { getWorldData } from '@/lib/worlds-data';
import { WorldDataProvider } from './WorldDataProvider';
import { WorldDetailHero } from './WorldDetailHero';
import { WorldDetailToc } from './WorldDetailToc';
import { WorldInstructions } from './WorldInstructions';
import { WorldTestingPerformance } from './WorldTestingPerformance';
import { WorldTestingPerformanceMDX } from './WorldTestingPerformanceMDX';
import { WorldVersionSelect } from './WorldVersionSelect';
const isPreview = process.env.VERCEL_ENV === 'preview';
/** MDX wrapper — passes preview gate to benchmark section */
const WorldTestingPerformanceForMDX = (props: Record<string, unknown>) => (
<WorldTestingPerformanceMDX {...props} showBenchmarks={isPreview} />
);
// Official worlds with a page in the content/worlds/<version> collections
const officialWorldMdxSlugs: Record<string, string[]> = {
local: ['local'],
postgres: ['postgres'],
vercel: ['vercel'],
};
const VERSION_SOURCES = {
v4: worldsSource,
v5: v5WorldsSource,
} as const;
const VERSION_PREFIXES = {
v4: '',
v5: '/v5',
} as const;
export const officialWorldIds = Object.keys(officialWorldMdxSlugs);
export async function generateWorldMetadata(
id: string,
version: DocsVersionId
): Promise<Metadata> {
const data = await getWorldData(id);
if (!data) {
return {
title: 'World Not Found',
};
}
const versionPrefix = VERSION_PREFIXES[version];
const isPreRelease = version === 'v5';
return {
title: `${data.world.name} World${isPreRelease ? ' · Pre-release' : ''} | Workflow SDK`,
description: data.world.description,
openGraph: {
images: [`/og/worlds/${id}`],
},
alternates: {
canonical: `/worlds/${id}`,
types: {
'text/markdown': `${versionPrefix}/worlds/${id}.md`,
},
},
...(isPreRelease
? {
robots: {
index: false,
follow: true,
},
}
: {}),
};
}
export async function WorldDetailPage({
id,
version,
}: {
id: string;
version: DocsVersionId;
}) {
const data = await getWorldData(id);
if (!data) {
notFound();
}
const { world, meta } = data;
// For official worlds, load MDX content and extract TOC
const isOfficial = world.type === 'official' && officialWorldMdxSlugs[id];
// Community worlds have no versioned content — their canonical page lives
// at /worlds/<id> only.
if (version !== 'v4' && !isOfficial) {
redirect(`/worlds/${id}`);
}
const source = VERSION_SOURCES[version];
const versionPrefix = VERSION_PREFIXES[version];
let mdxContent: ReactNode = null;
let tocItems: { id: string; title: ReactNode }[] = [];
if (isOfficial) {
const slugs = officialWorldMdxSlugs[id];
const page = source.getPage(slugs);
if (page) {
const pageData = page.data as typeof page.data & {
body: ComponentType<{ components?: Record<string, unknown> }>;
toc: TableOfContents;
};
const MDX = pageData.body;
// Extract TOC from MDX headings (only h2s, not h3s)
tocItems = pageData.toc
.filter((item) => item.depth === 2)
.map((item) => ({
id: item.url.slice(1), // Remove leading #
title: item.title,
}));
// Content links are authored against the raw /docs/... and /worlds/...
// URL spaces; on the pre-release route they are rewritten into the /v5
// view so navigation doesn't escape to the current-version pages.
const RelativeLink = createRelativeLink(source, page);
const VersionedLink = (props: ComponentProps<'a'>) => (
<RelativeLink
{...props}
href={rewriteHrefForVersion(props.href, versionPrefix)}
/>
);
mdxContent = (
<MDX
components={getMDXComponents({
a: VersionedLink,
Step,
Steps,
Tabs,
Tab,
FluidComputeCallout,
WorldTestingPerformance: WorldTestingPerformanceForMDX,
})}
/>
);
}
} else {
// Community worlds use hardcoded TOC
tocItems = [
{ id: 'installation', title: 'Installation & Usage' },
{ id: 'testing', title: 'Testing & Compatibility' },
];
}
return (
<WorldDataProvider worldId={id} world={world} meta={meta}>
<div className="[&_h1]:tracking-tighter [&_h2]:tracking-tighter [&_h3]:tracking-tighter">
<div className="mx-auto w-full max-w-[1080px] px-4">
{/* Hero Section */}
<div className="mt-[var(--fd-nav-height)]">
<WorldDetailHero
id={id}
world={world}
versionSelect={
isOfficial ? <WorldVersionSelect current={version} /> : null
}
/>
</div>
{/* Content + TOC Grid */}
<div className="relative grid grid-cols-1 lg:grid-cols-[1fr_200px] gap-8 lg:gap-12">
{/* Main Content */}
<main className="min-w-0">
{isOfficial && mdxContent ? (
// Official worlds: MDX controls the entire content structure
<div className="py-8 sm:py-12 prose prose-neutral dark:prose-invert max-w-none">
{mdxContent}
</div>
) : (
// Community worlds: use template components
<>
<WorldInstructions id={id} world={world} />
<WorldTestingPerformance
worldId={id}
world={world}
meta={meta}
showBenchmarks={isPreview}
/>
</>
)}
</main>
{/* TOC Sidebar - sticky on desktop, hidden on mobile */}
<aside className="hidden lg:block pt-8 sm:pt-12">
<div className="sticky top-24">
<WorldDetailToc items={tocItems} />
</div>
</aside>
</div>
</div>
</div>
</WorldDataProvider>
);
}