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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Next.js 16 (App Router) + fumadocs site for [bitrouter.ai](https://bitrouter.ai)
- Lint with `pnpm lint:docs` (`scripts/check-docs.mjs`) after editing docs.
- **Always use the scripts — never hand-edit generated output.** Anything produced by a generator is regenerated at `prebuild`, so manual edits are silently lost:
- API reference (`content/docs/reference/<tag>/`) — edit `openapi.yaml`, then `pnpm generate:openapi`.
- CLI reference (`content/docs/reference/cli/`) — edit `cli-overlays/<group>.md`, then `pnpm generate:cli`.
- CLI reference (`content/docs/(guide)/usage/cli/`) — edit `cli-overlays/<group>.md`, then `pnpm generate:cli`.
- `.cli-snapshot.json` — re-capture from the binary with `pnpm snapshot:cli`; never edit by hand.
- `.models-snapshot.json` / changelog-latest data — `pnpm generate:models` / `pnpm generate:changelog`.

Expand Down
5 changes: 4 additions & 1 deletion app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export default async function Page({ params }: Props) {
const MDX = page.data.body;
const slugPath = slug?.join("/") ?? "";
const markdownUrl = `/api/docs/llms-mdx/${slugPath}`;
const githubUrl = `${GITHUB_REPO}/blob/main/content/docs/${slugPath}/index.mdx`;
// `page.path` is the real file path relative to content/docs — slugs can't be
// used here, since folder groups like `(guide)/` are stripped from the URL and
// pages are a mix of `<name>.md` and `<name>/index.mdx`.
const githubUrl = `${GITHUB_REPO}/blob/main/content/docs/${page.path}`;

const isFaqPage =
slug?.length === 2 && slug[0] === "overview" && slug[1] === "faqs";
Expand Down
4 changes: 3 additions & 1 deletion app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ type Entry = MetadataRoute.Sitemap[number];
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// ── Docs, git-dated per source file ──
const docPages = source.getPages().map((page) => {
const mdxPath = `content/docs/${page.slugs.join("/")}.mdx`;
// Real file path, not the slugs: the `(guide)/` folder group is stripped
// from URLs, and pages are a mix of `.md`, `.mdx`, and `<name>/index.mdx`.
const mdxPath = `content/docs/${page.path}`;
return {
url: `${BASE_URL}${page.url}`,
lastModified: getGitLastModified(mdxPath),
Expand Down
21 changes: 20 additions & 1 deletion components/docs-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { usePathname } from "next/navigation";
import Link from "next/link";
import { PanelLeft } from "lucide-react";
import { useNotebookLayout } from "fumadocs-ui/layouts/notebook";
import { isLayoutTabActive } from "fumadocs-ui/layouts/shared";
import { WebHeaderBody } from "@/components/site-header-wired";
Expand All @@ -19,9 +20,11 @@ import { cn } from "@/lib/cn";
*/
export function DocsHeader() {
const {
slots,
props: { tabs, tabMode },
} = useNotebookLayout();
const pathname = usePathname();
const SidebarTrigger = slots.sidebar?.trigger;
const showTabs = tabMode === "navbar" && tabs.length > 0;
const selectedIdx = tabs.findLastIndex((tab) =>
isLayoutTabActive(tab, pathname),
Expand All @@ -40,7 +43,23 @@ export function DocsHeader() {
showTabs && "lg:layout:[--fd-header-height:88px]",
)}
>
<WebHeaderBody />
{/* Below `md` the sidebar becomes a drawer, and the drawer is the only
place the layout-tabs dropdown lives — without this trigger the docs
nav and the API Reference tab are unreachable on a phone. Native
notebook headers render the same slot; ours has to opt in because it
replaces the whole header. */}
<WebHeaderBody
leadingSlot={
SidebarTrigger ? (
<SidebarTrigger
aria-label="Toggle docs navigation"
className="-ms-1.5 flex items-center rounded-[9px] p-2 text-[var(--z-ink-4)] transition-colors hover:bg-white/[0.05] hover:text-[var(--z-ink)] md:hidden"
>
<PanelLeft className="size-[18px]" />
</SidebarTrigger>
) : null
}
/>

{showTabs && (
<div className="flex h-10 flex-row items-end gap-6 overflow-x-auto border-t border-[var(--z-rule)] px-6 max-lg:hidden">
Expand Down
5 changes: 4 additions & 1 deletion components/header/site-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ export function SiteHeaderBody({
}}
/>
</span>
<span className="font-sans text-[17px] font-semibold tracking-[-0.01em] text-[var(--z-ink)]">
{/* Wordmark drops below `sm`: the row is logo + search + CTA + menu,
which overflows a 375px viewport and pushes the CTA over the search
trigger. The mark alone still identifies and links home. */}
<span className="hidden font-sans text-[17px] font-semibold tracking-[-0.01em] text-[var(--z-ink)] sm:inline">
bitrouter.
</span>
</a>
Expand Down
15 changes: 11 additions & 4 deletions components/site-header-wired.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import type * as React from "react";
import { usePathname } from "next/navigation";
import { SiteHeader, SiteHeaderBody, type HeaderSession } from "@/components/header";
import { authClient } from "@/lib/auth-client";
Expand All @@ -21,7 +22,7 @@ const searchSlot = <AISearchBar />;

// ── shared prop plumbing ──────────────────────────────────────────────

function useWebHeaderProps() {
function useWebHeaderProps(leadingSlot?: React.ReactNode) {
const { data: session } = authClient.useSession();
const pathname = usePathname();
const headerSession: HeaderSession | null = session?.user
Expand All @@ -42,6 +43,7 @@ function useWebHeaderProps() {
},
searchSlot,
utilitySlot,
leadingSlot,
};
}

Expand All @@ -50,7 +52,12 @@ export function WebHeader() {
return <SiteHeader {...useWebHeaderProps()} />;
}

/** Headerless body — for the docs notebook grid header (DocsHeader). */
export function WebHeaderBody() {
return <SiteHeaderBody {...useWebHeaderProps()} />;
/**
* Headerless body — for the docs notebook grid header (DocsHeader).
*
* `leadingSlot` is where docs passes the sidebar drawer trigger, so the docs
* nav (and the tabs dropdown inside it) is reachable on small screens.
*/
export function WebHeaderBody({ leadingSlot }: { leadingSlot?: React.ReactNode }) {
return <SiteHeaderBody {...useWebHeaderProps(leadingSlot)} />;
}
File renamed without changes.
14 changes: 14 additions & 0 deletions content/docs/(guide)/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"title": "Documentation",
"root": true,
"pagesIndex": "[Documentation](/docs/overview/what-is-bitrouter)",
"pages": [
"overview",
"usage",
"gateway-and-routing",
"observability",
"integrations",
"guides",
"ai-resources"
]
}
File renamed without changes.
6 changes: 6 additions & 0 deletions content/docs/(guide)/usage/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"title": "Usage",
"icon": "Terminal",
"collapsible": false,
"pages": ["cli", "mcp"]
}
11 changes: 1 addition & 10 deletions content/docs/meta.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
{
"title": "Documentation",
"root": true,
"pages": [
"overview",
"gateway-and-routing",
"observability",
"integrations",
"guides",
"ai-resources",
"reference"
]
"pages": ["(guide)", "reference"]
}
6 changes: 2 additions & 4 deletions content/docs/reference/meta.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
{
"title": "Reference",
"title": "API Reference",
"icon": "Library",
"root": true,
"collapsible": false,
"pages": [
"index",
"---Interfaces---",
"cli",
"mcp",
"---Cloud API---",
"health",
"discovery",
Expand Down
44 changes: 32 additions & 12 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,33 @@ is rendered from this repo — docs are committed directly here under

## What publishes

Each top-level folder under `content/docs/` (`overview`,
`gateway-and-routing`, `observability`, `guides`, `integrations`) is a
documentation section on the site. Page order within a section is the `pages` list in that
section's `meta.json`; the overall section order is the `pages` list in
`content/docs/meta.json`.
The docs site has two **tabs**, and each is a folder marked `"root": true` in
its `meta.json`:

The **API reference** (generated from the BitRouter Cloud OpenAPI spec) and
**AI resources** (docs MCP, llms.txt, drop-in skills) are also site sections,
but the reference pages are generated — don't hand-author those.
- **Documentation** — `content/docs/(guide)/`. The parentheses make it a
*folder group*: fumadocs strips it from the URL, so `(guide)/overview/quickstart.md`
still publishes at `/docs/overview/quickstart`. It exists only to give the tab
something to hang off.
- **API Reference** — `content/docs/reference/`, generated from the BitRouter
Cloud OpenAPI spec.

Two rules follow from that, and breaking either one silently deletes a tab:

1. A root folder needs a landing URL. Fumadocs takes it from `pagesIndex` in
`meta.json`, or from the folder's first direct *page* child — a root folder
whose children are all folders resolves to nothing and is dropped from the
tab bar without an error.
2. Only these two folders carry `"root": true`. Adding it to a section would
turn that section into a third tab.

Each folder under `content/docs/(guide)/` (`overview`, `usage`,
`gateway-and-routing`, `observability`, `integrations`, `guides`,
`ai-resources`) is a section within the Documentation tab. Page order within a
section is the `pages` list in that section's `meta.json`; the section order is
the `pages` list in `content/docs/(guide)/meta.json`.

`usage/` holds the CLI and MCP server references — generated, so don't
hand-author those.

## Authoring contract (plain Markdown)

Expand All @@ -34,7 +52,7 @@ Pages are plain Markdown (`.md`), not MDX with imports. The build enforces this:

## Adding a page

1. Create `content/docs/<section>/<name>.md`.
1. Create `content/docs/(guide)/<section>/<name>.md`.
2. Add `<name>` to that section's `meta.json` `pages` list in the position you
want it to appear in the nav.
3. Run `pnpm lint:docs` to check the authoring contract.
Expand All @@ -45,9 +63,11 @@ Two parts of the docs are **generated at build time** — don't hand-edit their
output:

- **API reference** (`content/docs/reference/<tag>/`) — `pnpm generate:openapi`
regenerates from `openapi.yaml`. Hand-authored top-level pages (`index.mdx`,
`mcp.mdx`) survive; the section `meta.json` is rewritten.
- **CLI reference** (`content/docs/reference/cli/`) — `pnpm generate:cli`
regenerates from `openapi.yaml`. The hand-authored `index.mdx` survives; every
other subdirectory is wiped, and the section `meta.json` is rewritten from
`REFERENCE_META` in the script — including the `"root": true` that makes it a
tab, so edit the script, not the file.
- **CLI reference** (`content/docs/(guide)/usage/cli/`) — `pnpm generate:cli`
builds the command pages from `.cli-snapshot.json` plus the hand-authored
overlays in `cli-overlays/<group>.md` (page intros, per-command examples).
When the documented binary changes, re-capture the snapshot locally with
Expand Down
17 changes: 11 additions & 6 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const finalPath = {
"managed-provider":"/docs/overview/supported-models","discounted-models":"/docs/overview/supported-models",
"payment":"/docs/overview/quickstart#self-host-or-cloud","workspaces":"/docs/reference/management/listNamespaces",
"for-providers":"/docs/guides/register-as-a-provider",
// reference
"cli":"/docs/reference/cli",
// usage (CLI + MCP moved out of reference/ into the Documentation tab, 2026-08)
"cli":"/docs/usage/cli",
};
const legacyBuckets = ["core","cloud","features","routing"]; // /docs/guides/<bucket>/<slug>
const pairs: Array<[string, string]> = [];
Expand Down Expand Up @@ -54,16 +54,16 @@ pairs.push(
["/docs/concepts/policy", "/docs/overview/quickstart#adaptive-routing"],
["/docs/concepts/tools", "/docs/gateway-and-routing/mcp-gateway"],
["/docs/concepts/agents", "/docs/gateway-and-routing/acp-gateway"],
["/docs/concepts/cli", "/docs/reference/cli"],
["/docs/concepts/mcp", "/docs/reference/mcp"],
["/docs/concepts/cli", "/docs/usage/cli"],
["/docs/concepts/mcp", "/docs/usage/mcp"],
["/docs/concepts/agent-skill", "/docs/overview/quickstart"],
// get-started consolidation (2026-07): onboarding merge, FAQs dissolved, cli/mcp → reference
["/docs/get-started/configuration", "/docs/overview/quickstart"],
["/docs/get-started/wizard", "/docs/overview/quickstart"],
["/docs/get-started/agent-skill", "/docs/overview/quickstart"],
["/docs/get-started/faqs", "/docs/overview/quickstart"],
["/docs/get-started/cli", "/docs/reference/cli"],
["/docs/get-started/mcp", "/docs/reference/mcp"],
["/docs/get-started/cli", "/docs/usage/cli"],
["/docs/get-started/mcp", "/docs/usage/mcp"],
// get-started/ section dissolved (2026-08): onboarding → overview/quickstart,
// the four set-up-* walkthroughs → the quickstart or the page that owns each topic
["/docs/get-started", "/docs/overview/quickstart"],
Expand Down Expand Up @@ -91,6 +91,11 @@ pairs.push(
["/docs/cloud/managed-models", "/docs/overview/supported-models"],
["/docs/cloud/workspaces", "/docs/reference/management/listNamespaces"],
["/docs/cloud/payment", "/docs/overview/quickstart#self-host-or-cloud"],
// CLI + MCP left the API Reference tab for Documentation → Usage (2026-08).
// These must stay above the /docs/reference wildcards below.
["/docs/reference/cli/:slug*", "/docs/usage/cli/:slug*"],
["/docs/reference/cli", "/docs/usage/cli"],
["/docs/reference/mcp", "/docs/usage/mcp"],
// reference wildcards (api-reference unwrapped into /docs/reference)
["/docs/api-reference/:slug*", "/docs/reference/:slug*"],
["/docs/reference/api-reference/:slug*", "/docs/reference/:slug*"],
Expand Down
8 changes: 7 additions & 1 deletion scripts/check-docs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ import {
} from "../lib/docs-sync/transform.mjs";
import { COMPONENT_WHITELIST } from "../lib/docs-sync/constants.mjs";

// Hand-authored sections of the Documentation tab. They live under the
// `(guide)` folder group, which fumadocs strips from the URL — `overview/` is
// still served at /docs/overview. `usage/` is excluded: its CLI pages are
// generated by scripts/generate-cli.mjs and legitimately use <APIPage>-style
// generated markup rather than the hand-authoring contract.
const SECTIONS = ["overview", "gateway-and-routing", "observability", "guides", "integrations"];
const ROOT = "content/docs";
const GUIDE_ROOT = join(ROOT, "(guide)");

async function walk(dir) {
const out = [];
Expand All @@ -45,7 +51,7 @@ function findImportLine(body) {

async function main() {
const files = [];
for (const s of SECTIONS) files.push(...(await walk(join(ROOT, s))));
for (const s of SECTIONS) files.push(...(await walk(join(GUIDE_ROOT, s))));
const docs = files.filter(isDoc);

const errors = [];
Expand Down
8 changes: 4 additions & 4 deletions scripts/generate-cli.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generates the CLI reference pages under content/docs/reference/cli/ from
// Generates the CLI reference pages under content/docs/(guide)/usage/cli/ from
// .cli-snapshot.json (captured by scripts/snapshot-cli.mjs) plus the
// hand-authored overlays in cli-overlays/<slug>.md (intro prose, per-command
// examples and notes).
Expand All @@ -14,10 +14,10 @@ import { join } from "node:path";
const ROOT = process.cwd();
const SNAPSHOT = join(ROOT, ".cli-snapshot.json");
const OVERLAY_DIR = join(ROOT, "cli-overlays");
const OUT_DIR = join(ROOT, "content/docs/reference/cli");
const OUT_DIR = join(ROOT, "content/docs/(guide)/usage/cli");

// Page groups: slug → top-level commands whose trees land on that page.
// Order here is the nav order in content/docs/reference/cli/meta.json.
// Order here is the nav order in content/docs/(guide)/usage/cli/meta.json.
const GROUPS = [
{ slug: "daemon", commands: ["serve", "start", "stop", "restart", "reload", "status"] },
{ slug: "init", commands: ["init", "config"] },
Expand Down Expand Up @@ -145,4 +145,4 @@ writeFileSync(
join(OUT_DIR, "meta.json"),
JSON.stringify({ title: "CLI", pages: metaPages }, null, 2) + "\n",
);
console.log(`generate-cli: wrote ${written} page(s) + meta.json → content/docs/reference/cli/`);
console.log(`generate-cli: wrote ${written} page(s) + meta.json → content/docs/(guide)/usage/cli/`);
Loading
Loading