This commit is contained in:
Dax Raad 2026-07-13 00:50:43 -04:00
commit fe3b2e8f90
51 changed files with 33307 additions and 15 deletions

View file

@ -0,0 +1,55 @@
import { Children, isValidElement, type ComponentProps, type ReactNode } from "react"
import { Callout } from "fumadocs-ui/components/callout"
import { Card, Cards } from "fumadocs-ui/components/card"
import { Tab as FumadocsTab, Tabs as FumadocsTabs } from "fumadocs-ui/components/tabs"
import defaultMdxComponents from "fumadocs-ui/mdx"
import type { MDXComponents } from "mdx/types"
function Tip(props: { children: ReactNode }) {
return <Callout type="idea">{props.children}</Callout>
}
function Note(props: { children: ReactNode }) {
return <Callout>{props.children}</Callout>
}
function Warning(props: { children: ReactNode }) {
return <Callout type="warn">{props.children}</Callout>
}
function Tab(props: { title?: string; value?: string; children: ReactNode }) {
return <FumadocsTab value={props.value ?? props.title}>{props.children}</FumadocsTab>
}
function Tabs(props: { children: ReactNode }) {
const items = Children.toArray(props.children).flatMap((child) => {
if (!isValidElement<ComponentProps<typeof Tab>>(child)) return []
const value = child.props.value ?? child.props.title
return value ? [value] : []
})
return <FumadocsTabs items={items}>{props.children}</FumadocsTabs>
}
function CardGroup(props: { children: ReactNode; cols?: number }) {
return <Cards>{props.children}</Cards>
}
export function getMdxComponents(components?: MDXComponents) {
return {
...defaultMdxComponents,
Tip,
Note,
Warning,
Tabs,
Tab,
CardGroup,
Card,
...components,
} satisfies MDXComponents
}
export const useMDXComponents = getMdxComponents
declare global {
type MDXProvidedComponents = ReturnType<typeof getMdxComponents>
}

View file

@ -0,0 +1,22 @@
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: "OpenCode",
url: "/",
},
links: [
{
text: "Docs",
url: "/docs",
active: "nested-url",
},
{
text: "GitHub",
url: "https://github.com/anomalyco/opencode",
external: true,
},
],
}
}

View file

@ -0,0 +1,7 @@
import { docs } from "collections/server"
import { loader } from "fumadocs-core/source"
export const source = loader({
baseUrl: "/docs",
source: docs.toFumadocsSource(),
})

View file

@ -0,0 +1,104 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'
import { Route as DocsSplatRouteImport } from './routes/docs/$'
import { Route as ApiSearchRouteImport } from './routes/api/search'
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const DocsSplatRoute = DocsSplatRouteImport.update({
id: '/docs/$',
path: '/docs/$',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSearchRoute = ApiSearchRouteImport.update({
id: '/api/search',
path: '/api/search',
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/api/search': typeof ApiSearchRoute
'/docs/$': typeof DocsSplatRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/api/search': typeof ApiSearchRoute
'/docs/$': typeof DocsSplatRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/api/search': typeof ApiSearchRoute
'/docs/$': typeof DocsSplatRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/api/search' | '/docs/$'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/api/search' | '/docs/$'
id: '__root__' | '/' | '/api/search' | '/docs/$'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ApiSearchRoute: typeof ApiSearchRoute
DocsSplatRoute: typeof DocsSplatRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/docs/$': {
id: '/docs/$'
path: '/docs/$'
fullPath: '/docs/$'
preLoaderRoute: typeof DocsSplatRouteImport
parentRoute: typeof rootRouteImport
}
'/api/search': {
id: '/api/search'
path: '/api/search'
fullPath: '/api/search'
preLoaderRoute: typeof ApiSearchRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ApiSearchRoute: ApiSearchRoute,
DocsSplatRoute: DocsSplatRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}

View file

@ -0,0 +1,16 @@
import { createRouter } from "@tanstack/react-router"
import { routeTree } from "./routeTree.gen"
export function getRouter() {
return createRouter({
routeTree,
scrollRestoration: true,
defaultPreload: "intent",
})
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof getRouter>
}
}

View file

@ -0,0 +1,34 @@
import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"
import { RootProvider } from "fumadocs-ui/provider/tanstack"
import type { ReactNode } from "react"
import styles from "../styles.css?url"
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ title: "OpenCode" },
{
name: "description",
content: "The open source AI coding agent.",
},
],
links: [{ rel: "stylesheet", href: styles }],
}),
shellComponent: RootDocument,
})
function RootDocument(props: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<HeadContent />
</head>
<body>
<RootProvider>{props.children}</RootProvider>
<Scripts />
</body>
</html>
)
}

View file

@ -0,0 +1,15 @@
import { createFileRoute } from "@tanstack/react-router"
import { createFromSource } from "fumadocs-core/search/server"
import { source } from "@/lib/source"
const search = createFromSource(source, {
language: "english",
})
export const Route = createFileRoute("/api/search")({
server: {
handlers: {
GET: ({ request }) => search.GET(request),
},
},
})

View file

@ -0,0 +1,62 @@
import { createFileRoute, notFound } from "@tanstack/react-router"
import { createServerFn } from "@tanstack/react-start"
import browserCollections from "collections/browser"
import { useFumadocsLoader } from "fumadocs-core/source/client"
import { DocsLayout } from "fumadocs-ui/layouts/docs"
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/layouts/docs/page"
import { Suspense } from "react"
import { getMdxComponents } from "@/components/mdx"
import { baseOptions } from "@/lib/layout"
import { source } from "@/lib/source"
export const Route = createFileRoute("/docs/$")({
loader: async ({ params }) => {
const slugs = params._splat?.split("/").filter(Boolean) ?? []
const data = await loadPage({ data: slugs })
await clientLoader.preload(data.path)
return data
},
head: ({ loaderData }) => ({
meta: [
{ title: `${loaderData?.title ?? "Docs"} | OpenCode` },
{ name: "description", content: loaderData?.description ?? "OpenCode documentation." },
],
}),
component: Documentation,
})
const loadPage = createServerFn({ method: "GET" })
.validator((slugs: string[]) => slugs)
.handler(async ({ data: slugs }) => {
const page = source.getPage(slugs)
if (!page) throw notFound()
return {
path: page.path,
title: page.data.title,
description: page.data.description,
tree: await source.serializePageTree(source.getPageTree()),
}
})
const clientLoader = browserCollections.docs.createClientLoader({
component({ toc, frontmatter, default: Content }) {
return (
<DocsPage toc={toc}>
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<DocsBody>
<Content components={getMdxComponents()} />
</DocsBody>
</DocsPage>
)
},
})
function Documentation() {
const data = useFumadocsLoader(Route.useLoaderData())
return (
<DocsLayout {...baseOptions()} tree={data.tree} tabMode="top">
<Suspense>{clientLoader.useContent(data.path)}</Suspense>
</DocsLayout>
)
}

View file

@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router"
export const Route = createFileRoute("/")({
component: Home,
})
function Home() {
return (
<main className="home">
<p className="home-kicker">OpenCode</p>
<h1>The open source AI coding agent</h1>
<p className="home-copy">The new opencode.ai SSR site is running. The V2 documentation is available now.</p>
<a className="home-link" href="/docs">
Read the docs
</a>
</main>
)
}

View file

@ -0,0 +1,79 @@
@import "tailwindcss";
@import "fumadocs-ui/css/neutral.css";
@import "fumadocs-ui/css/preset.css";
:root {
--site-background: #f7f7f5;
--site-foreground: #171717;
--site-border: #d8d8d2;
--site-accent: #3b7dd8;
--fd-layout-width: 90rem;
}
* {
box-sizing: border-box;
}
html,
body {
min-height: 100%;
}
body {
margin: 0;
}
.home {
min-height: 100vh;
display: grid;
align-content: center;
justify-items: start;
gap: 1.5rem;
padding: clamp(1.5rem, 7vw, 7rem);
color: var(--site-foreground);
background: var(--site-background);
}
.home-kicker,
.home-copy,
.home h1 {
margin: 0;
}
.home-kicker {
font:
600 0.75rem/1.2 ui-monospace,
monospace;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.home h1 {
max-width: 13ch;
font:
500 clamp(3rem, 9vw, 8rem) / 0.92 ui-monospace,
monospace;
letter-spacing: -0.07em;
}
.home-copy {
max-width: 42rem;
font:
400 clamp(1rem, 2vw, 1.25rem) / 1.5 ui-monospace,
monospace;
}
.home-link {
display: inline-block;
padding: 0.8rem 1rem;
color: var(--site-background);
background: var(--site-foreground);
font:
500 0.875rem/1 ui-monospace,
monospace;
text-decoration: none;
}
.home-link:hover {
background: var(--site-accent);
}