diff --git a/AGENTS.md b/AGENTS.md index fb573ea..4744de4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,18 +51,21 @@ Monorepo providing shared code style configurations for PleaseAI projects. ## Docs Site -- Located at `docs/` — Docus (Nuxt) deployed to Cloudflare Pages -- Bun workspace member; build works because of `bunfig.toml` hoisted linker (see Gotchas) -- Build: `cd docs && bun run build` → outputs to `docs/dist/` -- Deploy: `cd docs && bun run deploy` (wrangler) -- ADR: `docs/adr/0001-choose-documentation-framework.md` +- Located at `apps/docs/` (`@pleaseai/code-style-docs`) — [Nimbus](https://github.com/cloudflare/nimbus) (Astro 7 + Tailwind v4), static output deployed to the Cloudflare Pages project `pleaseai-code-style` +- Bun workspace member (`apps/*`); `bun run build` (turbo) builds it to `apps/docs/dist/` +- Dev / build / deploy: `bun run docs:dev`, `bun run docs:build`, `bun run docs:deploy` (`wrangler pages deploy`) +- Checks inside `apps/docs`: `bun run typecheck` (`astro check`), `bun run lint:docs` (Nimbus prose/MDX lint) +- Content: `apps/docs/src/content/docs/{en,ko}/` (MDX). Every page must exist in both locales at the same path; order pages with `sidebar.order` frontmatter +- i18n is wired by hand with Astro's i18n routing: `apps/docs/src/i18n/` (locales, UI strings, translation lookup), `` + `hreflang` in `BaseLayout.astro`, language switcher in `Header.astro` +- Nimbus is pre-1.0 — keep its dependencies pinned to exact versions; upgrade from `apps/docs` with `npx @cloudflare/nimbus-docs migrate` / `outdated` / `diff` (see `apps/docs/AGENT.md`) +- ADRs: `docs/adr/` (current: `0003-migrate-docs-to-nimbus.md`) ## Gotchas - `packages/perttier-config` directory is intentionally(?) misspelled — do not rename without coordinating release-please config, CI workflows, and npm package name -- `bunfig.toml` sets `linker = "hoisted"` — required for Docus to build. Bun 1.3.2+ defaults to `isolated` linker in workspaces, which puts Docus's raw `.ts` server files under `.bun/` symlinks where Nitro's `rollup-plugin-inject` chokes on them. Hoisted linker (npm-style flat `node_modules`) avoids this. -- Docs build requires `mcp: { enabled: false }` in `docs/nuxt.config.ts` — `@nuxtjs/mcp-toolkit` imports `agents/mcp` (Cloudflare Workers AI Agents) which breaks the `cloudflare_pages` preset. -- Docus `cloudflare_pages` preset outputs to `dist/`, not `.output/public/` — `wrangler.jsonc` `pages_build_output_dir` must match. +- `bunfig.toml` sets `linker = "hoisted"` — introduced for the former Docus site (Bun 1.3.2+ defaults to the `isolated` linker in workspaces, which broke Nitro's `rollup-plugin-inject` on Docus's raw `.ts` files). The Nimbus site has not been verified without it, so keep it until removing it is tested across all workspace packages. +- `apps/docs/src/components/ui/**` and `apps/docs/src/layouts/DocsLayout.astro` are kept as close to upstream Nimbus as possible and excluded from ESLint, so `nimbus-docs diff --apply` / `nimbus-docs add --overwrite` can update them. Local edits are limited to `aside`, `card`, `pagination`, and `search` (links and i18n strings from `src/i18n/locales.ts`); merge those by hand on upgrade. +- Docs URLs are file-style (`build.format: 'file'`, `trailingSlash: 'never'`): `/en/cli` is served from `en/cli.html`. Don't switch to directory format — Cloudflare Pages would redirect `/en/cli` to `/en/cli/`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c9b4db..db05f12 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ This is a [bun](https://bun.sh) monorepo orchestrated by [Turborepo](https://tur The `packages/perttier-config` directory is intentionally misspelled. Do not rename it without coordinating the release-please config, CI workflows, and the npm package name. -The documentation site lives in [`docs/`](./docs) (Docus on Nuxt). +The documentation site lives in [`apps/docs/`](./apps/docs) (Nimbus on Astro). ## Getting started diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 0000000..46f044c --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,22 @@ +node_modules/ +dist/ +.astro/ +.wrangler/ +.nimbus/ + +.env +.env.local +.env.*.local +.env.production +.dev.vars +.dev.vars.* + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +.DS_Store +.idea/ +.vscode/* +!.vscode/extensions.json diff --git a/apps/docs/AGENT.md b/apps/docs/AGENT.md new file mode 100644 index 0000000..b5ecade --- /dev/null +++ b/apps/docs/AGENT.md @@ -0,0 +1,140 @@ +# This Nimbus docs site + +> `CLAUDE.md` delegates here. Keep project instructions canonical in this file. + +Astro-based docs. The `nimbus-docs` package handles content schemas, sidebar/TOC, MDX→markdown, build hooks, and the `nimbus-docs` CLI. Install dependencies first, then run it as `npx @cloudflare/nimbus-docs `: npx uses the project's installed version. Before an install, npx fetches the latest release instead, which may differ from the site's version and give different results. The scoped name keeps npx from fetching the unrelated `nimbus-docs` package. Everything in `src/` is yours to edit. + +## File layout + +``` +astro.config.ts # imports nimbus + defineNimbusConfig +nimbus.json # records the last reviewed Nimbus package version +src/ +├── components.ts # MDX globals — components every .mdx can use without an import +├── components/ # AgentDirective, Header, Render + ui// +├── content/ +│ ├── docs/*.mdx +│ └── partials/*.mdx # referenced via +├── content.config.ts # registers docsCollection() + partialsCollection() +├── layouts/ # BaseLayout (NimbusHead), DocsLayout (sidebar/TOC/breadcrumbs) +├── lib/cn.ts # Tailwind className merger +├── pages/ +│ ├── [...slug].astro +│ ├── [...slug]/index.md.ts # Markdown version of every page, all collections +│ ├── [...slug]/index.mdx.ts # authored source of every page, all collections +│ ├── [section]/llms.txt.ts # per-section llms.txt +│ ├── 404.astro +│ ├── index.astro # home page (the empty starter uses content/docs/index.mdx) +│ ├── llms.txt.ts +│ ├── llms-full.txt.ts +│ ├── nimbus-api/coordinates.json.ts # API citation manifest +│ ├── og.png.ts # site-level OG card +│ ├── og/ +│ │ ├── _og-card-config.ts # shared OG theme tokens (underscore = not a route) +│ │ └── [...slug].ts # per-page OG cards +│ └── robots.txt.ts +└── styles/ # globals.css, prose.css +``` + +Cloudflare deploys also have `wrangler.jsonc` at the project root. + +## Writing docs + +Frontmatter validates against the schema from `docsCollection()` (`@cloudflare/nimbus-docs/content`). Required: `title`. + +```mdx +--- +title: My page +description: One-line summary. +--- + +Content here. The page H1 comes from `title` — don't repeat it in the body. + +## Section heading +``` + +Rules: + +- **Components are PascalCase.** Register one in `src/components.ts` to use it in every `.mdx` without an import, or import it in the file that uses it. A pre-build validator catches unresolved names with a "did you mean" hint. +- **Partials use ``.** Don't import `.mdx` directly. Shared content lives in `src/content/partials/.mdx`. +- **Icons use Nimbus's `Icon` + Phosphor.** `` from `@cloudflare/nimbus-docs/components/Icon.astro`. Glyphs: [phosphoricons.com](https://phosphoricons.com). +- **Don't remove `` from `BaseLayout.astro`.** It points agents at `/llms.txt`. + +## Adding things + +| Goal | Action | +|---|---| +| New doc page | Create `src/content/docs/en/.mdx` and `src/content/docs/ko/.mdx` (every page exists in both locales at the same path). Sidebar picks them up. | +| New partial | Create `src/content/partials/.mdx`. Use via ``. | +| UI from registry | `npx @cloudflare/nimbus-docs add `. Register in `src/components.ts` if used in MDX. | +| Feature recipe | `npx @cloudflare/nimbus-docs add --print`. Prints the recipe for you to follow; it changes no files itself. | +| Check it builds | `npx @cloudflare/nimbus-docs check` — build-free preflight (env + structure + authoring + types). `--json` for an agent loop, `--fix` to repair what's safe. | +| Custom page route | Add a file under `src/pages/`. | +| Custom OG style | Edit `src/pages/og/_og-card-config.ts`. | +| Check for updates | `npx @cloudflare/nimbus-docs outdated` — starter files behind their tag + registry components behind. | +| Upgrade Nimbus | Update the package, then run `npx @cloudflare/nimbus-docs migrate --dry-run --diff`. Review every change and required manual step before applying. | +| Upgrade a starter file | `npx @cloudflare/nimbus-docs diff ` to review, `diff --apply ` to pull a clean upstream change. | +| Upgrade a registry component | `npx @cloudflare/nimbus-docs add --overwrite`, then review with `git diff`. | + +Sätteri is Nimbus's default Markdown and MDX processor. Extend it using `markdown.mdastPlugins` for Markdown AST transformations or `markdown.hastPlugins` for HTML AST transformations. +If the site replaces Sätteri with another processor, set `admonitions: false` and keep that processor's existing callout implementation. + +List installable items: `npx @cloudflare/nimbus-docs list`. + +## Agent interfaces + +Read the release-matched guide at [`node_modules/@cloudflare/nimbus-docs/dist/docs-for-agents.md`](node_modules/@cloudflare/nimbus-docs/dist/docs-for-agents.md), especially for preview packages. Nimbus publishes discovery automatically; add project-owned Skills under `skills//SKILL.md`. Keep `` in `BaseLayout.astro` for browser-agent search with default Pagefind. Markdown negotiation applies to request-rendered pages; static hosting and response headers follow the deployment target's setup. + +## Upgrading Nimbus + +Keep `nimbus.json` committed. Its `lastReviewedNimbusVersion` is the baseline Nimbus uses to select the versioned reviews crossed by a package upgrade; state-detected migrations come from the current project files. It is not a package pin and should not be edited by hand. + +1. Update `@cloudflare/nimbus-docs` with the project's package manager. +2. Preview the complete plan with `npx @cloudflare/nimbus-docs migrate --dry-run --diff`. If no baseline exists yet, add `--from `. +3. Review every versioned entry and resolve each blocked/manual item. +4. Apply safe edits only with explicit consent: `npx @cloudflare/nimbus-docs migrate --yes`. Review the resulting diff, then rerun the preview. +5. When no migration remains, run `npx @cloudflare/nimbus-docs migrate --yes` again to record the completed review in `nimbus.json`. +6. Pull in starter fixes: `npx @cloudflare/nimbus-docs outdated` lists starter files that changed upstream. For each one, review it with `npx @cloudflare/nimbus-docs diff `, and take a clean update with `diff --apply `. Merge files you've edited by hand. `migrate` doesn't cover these. +7. Run the project's typecheck and production build, then run `npx @cloudflare/nimbus-docs check` again for post-build coverage. + +Except for task-printing mode (`--print`), `migrate` exits nonzero while work or review remains; that is a pending-upgrade signal, not necessarily a command failure. Never skip versions by changing `nimbus.json` directly. + +## Audit this site + +First set `site` in `astro.config.ts` to the production URL; a fresh site still has the placeholder. Ask the user for the URL if you don't know it, and never make one up. + +Then run `npx @cloudflare/nimbus-docs check --json`. It runs the environment, structural, authoring, and type checks build-free — config validity, `site` placeholder, route collisions, MDX component resolution, the lint rules, and a `tsc` type-check — and returns three top-level signals plus per-scope detail: + +- **`status`** (`passed` | `failed` | `partial`) and **`readiness`** (`buildable` | `blocked` | `unknown`) are the primary signals. `status` is the whole-run verdict; `readiness` answers "does env + structure say it builds?". `ok` (=== zero errors) is kept for back-compat only. +- **`findings[{scope,code,severity,file,line,message,fixable,fix}]`** are problems we evaluated. Apply each `fix` (or `check --fix`). +- **`scopes[].notes[{code,reason,requiresBuild?,requiresInput?}]`** are checks we *couldn't* evaluate yet (e.g. types before a build). A note is never a finding and never carries a `fix` — you resolve it by making the missing thing exist (usually a build), not by `--fix`. `summary.notes` counts them. + +Loop until `status !== "failed"` and no finding has a `fix` without `fix.requiresInput` — `summary.fixable` also counts fixes that need input (a placeholder `site`), which `--fix` can't apply for you. An error with no `fix` (such as `nimbus/dependencies-missing`) ends the loop too: do what its message says (install dependencies), then run `check` again, or stop and report it. A `partial` run with nothing left to fix is a **stop** (optionally build, then re-check), not a `--fix` retry. Exit is `1` only when `status` is `"failed"`. For full coverage (types + link-checking) run a build first, then `check` again. With server output, `check` stays `partial` with the note `nimbus/request-rendering-build-required` even after a build: it doesn't verify request-rendered pages, so a passing production build is the gate. + +Then walk the categories below for what `check` doesn't cover yet — route-file existence, registry hygiene, the AI surface, post-build search, and Cloudflare config. Emit findings as: + +``` +- [error|warn|info] FILE:LINE — what + why + fix. +``` + +End with `Summary: N errors, N warnings.` + +- **Config** — `astro.config.ts` calls `nimbus(defineNimbusConfig({ ... }))`; `site` is set; `editPattern` (if set) contains `{path}`; `output:` matches the deploy target. +- **Content** — `content.config.ts` registers `docsCollection()` (and `partialsCollection()` if used); every `.mdx` is inside a registered collection; frontmatter validates. +- **Sidebar** — every sidebar ref resolves to a content entry; no orphans; no slug collisions. +- **MDX** — every PascalCase component in `*.mdx` is registered or imported; every `` resolves; code-fence languages are valid. +- **Routes** — `llms.txt.ts`, `robots.txt.ts`, `[...slug]/index.md.ts`, `og.png.ts`, `og/[...slug].ts` all exist. +- **Registry hygiene** — every `src/components/ui//` is either MDX-registered or imported in `src/`; transitive deps (`lib/cn.ts`, etc.) exist. +- **AI surface** — `` renders in `BaseLayout.astro`; doc `` has ``. +- **Search** — `data-pagefind-body` is on the docs main wrapper; after a production build, `dist/pagefind/` exists with ≥1 indexed page. +- **Cloudflare** (if applicable) — `wrangler.jsonc` has `name` and `compatibility_date`. This site deploys static output to Cloudflare Pages: `pages_build_output_dir = "dist"` (no `assets` block). Static output on Workers instead: `assets.directory = "./dist"` and `assets.not_found_handling = "404-page"`. Server output (`@astrojs/cloudflare`): no `assets.directory` or `main`; the adapter writes the deploy config to `dist/server/wrangler.json` at build time, so check that file after a build. + +## Don't + +- Hand-add components under `src/components/ui/` that exists in the Nimbus registry — use `npx @cloudflare/nimbus-docs add ` so deps resolve. +- Import `.mdx` files directly — use ``. +- Remove `` unless asked. + +## Project home + +[nimbus-docs.com](https://nimbus-docs.com) diff --git a/apps/docs/CLAUDE.md b/apps/docs/CLAUDE.md new file mode 100644 index 0000000..a16fea4 --- /dev/null +++ b/apps/docs/CLAUDE.md @@ -0,0 +1,3 @@ +# This Nimbus docs site + +Read and follow [AGENT.md](./AGENT.md). It is the canonical project guidance for editing, auditing, upgrading, and validating this Nimbus site. diff --git a/apps/docs/astro.config.ts b/apps/docs/astro.config.ts new file mode 100644 index 0000000..d12169f --- /dev/null +++ b/apps/docs/astro.config.ts @@ -0,0 +1,117 @@ +import nimbus, { + defineConfig as defineNimbusConfig, +} from '@cloudflare/nimbus-docs' +import { tableScroll } from '@cloudflare/nimbus-docs/markdown' +import tailwindcss from '@tailwindcss/vite' +import { defineConfig } from 'astro/config' +import { defaultLocale, locales } from './src/i18n/locales' + +const nimbusConfig = defineNimbusConfig({ + // Canonical origin (no trailing slash). Drives canonical URLs, absolute OG + // image URLs, robots.txt, sitemap, and the links in /llms.txt. + site: 'https://code-style.pages.dev', + title: 'PleaseAI Code Style', + description: 'Shared code style configurations for PleaseAI projects', + // Fallback only: BaseLayout sets per page from the URL locale. + locale: defaultLocale, + github: 'https://github.com/pleaseai/code-style', + editPattern: 'https://github.com/pleaseai/code-style/edit/main/apps/docs/{path}', + socialImageAlt: 'PleaseAI Code Style documentation preview', + sidebar: { + // Content lives under `src/content/docs//`, so each locale is a + // top-level section. Scoping the rail to the active section keeps Korean + // pages from listing English pages (and vice versa). + scope: 'section', + }, +}) + +/** Joins Markdown blocks with blank lines, skipping empty ones. */ +function joinBlocks(...blocks: Array): string { + return blocks + .filter((block): block is string => typeof block === 'string' && block.trim() !== '') + .map(block => block.trim()) + .join('\n\n') +} + +export default defineConfig({ + // nimbus:adapter + output: 'static', + // Keep the URLs the Docus site served: `/en/cli` (not `/en/cli/`), emitted + // as `en/cli.html` so Cloudflare Pages answers the bare path with a 200. + trailingSlash: 'never', + build: { + format: 'file', + }, + // Astro's built-in i18n routing. Every locale is prefixed (`/en/...`, + // `/ko/...`) and `/` redirects to the default locale, matching the URL + // scheme of the previous site. Pages are not under `src/pages/[locale]`; + // the locale is the first segment of each content entry id instead. + i18n: { + locales: [...locales], + defaultLocale, + routing: { + prefixDefaultLocale: true, + redirectToDefaultLocale: true, + }, + }, + // `/` has no content of its own: send readers to the default locale's + // landing page. Static output emits this as a meta-refresh page, like the + // previous site did. + redirects: { + '/': `/${defaultLocale}`, + }, + // Tailwind v4 via its Vite plugin (the integration Astro recommends for + // Tailwind v4 — replaces the PostCSS plugin, which doesn't build under + // Astro 7's Vite 8 bundler). + vite: { + plugins: [tailwindcss()], + }, + // Hover-prefetch link targets so full-page navigations feel instant without + // a client-side router. + prefetch: { + prefetchAll: true, + defaultStrategy: 'hover', + }, + integrations: [ + nimbus(nimbusConfig, { + // Authoring rules are opt-in by design — your repo, your taste. The + // two below are the load-bearing pair: frontmatter has to validate + // against the content schema for the page to render properly, and + // broken internal links are 404s for your readers. Add the others + // (heading hierarchy, code-block language, style, etc.) when you're + // ready to enforce them — see `nimbus-docs lint --help`. + rules: { + 'nimbus/frontmatter-shape': 'error', + 'nimbus/internal-link': 'error', + }, + // Wrap wide tables so they scroll instead of overflowing the page + // (styled by `.nb-table-scroll` in src/styles/prose.css). + markdown: { + hastPlugins: [tableScroll()], + // How the site's own MDX components read in the generated Markdown + // (per-page `.md`, `llms-full.txt`) that agents consume. + componentMap: { + Hero: { + revision: 'hero-v1', + render: ({ attrs, children }) => joinBlocks(attrs.description, children), + }, + Section: { + revision: 'section-v1', + render: ({ attrs, children }) => joinBlocks(`## ${attrs.title}`, attrs.description, children), + }, + LinkButton: { + revision: 'link-button-v1', + render: ({ attrs, children }) => `- [${children.trim()}](${attrs.href})`, + }, + Card: { + revision: 'card-v1', + render: ({ attrs, children }) => { + const title = typeof attrs.href === 'string' ? `[${attrs.title}](${attrs.href})` : attrs.title + return `- **${title}** — ${children.trim()}` + }, + }, + }, + }, + }), + ], +}) diff --git a/apps/docs/nimbus.json b/apps/docs/nimbus.json new file mode 100644 index 0000000..02996dd --- /dev/null +++ b/apps/docs/nimbus.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://nimbus-docs.com/schema/nimbus.json", + "version": "0.7.11", + "lastReviewedNimbusVersion": "0.17.0", + "templatesTag": "templates-v0.7.11", + "variant": "empty", + "registry": "https://nimbus-docs.com/registry", + "install": { + "root": "src", + "aliases": { + "@/*": "src/*" + } + }, + "components": [ + { + "slug": "code-group", + "type": "registry:ui", + "version": "0.17.0", + "source": "https://nimbus-docs.com/registry", + "hash": "sha256:b1b709858534d8d582d23aacb654e09ab7efa7680c364036829d35b04cb9739d", + "files": [ + "src/components/ui/code-group/CodeGroup.astro", + "src/components/ui/code-group/code-group.client.ts", + "src/components/ui/code-group/index.ts" + ] + } + ] +} diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 0000000..1c7632c --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,38 @@ +{ + "name": "@pleaseai/code-style-docs", + "type": "module", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "typecheck": "astro check", + "lint:docs": "nimbus-docs lint", + "lint:docs:fix": "nimbus-docs lint --fix", + "prepreview:cf": "astro build", + "preview:cf": "wrangler pages dev", + "predeploy": "astro check && astro build", + "deploy": "wrangler pages deploy" + }, + "dependencies": { + "@cloudflare/nimbus-docs": "0.17.0", + "@fontsource-variable/inter": "5.3.0", + "@fontsource-variable/jetbrains-mono": "5.3.0", + "@iconify-json/ph": "1.2.2", + "astro": "7.3.1", + "astro-og-canvas": "0.13.2", + "canvaskit-wasm": "0.41.1", + "clsx": "2.1.1", + "tailwind-merge": "3.7.0" + }, + "devDependencies": { + "@astrojs/check": "0.9.10", + "@astrojs/language-server": "2.16.13", + "@tailwindcss/vite": "4.3.3", + "pagefind": "1.5.2", + "tailwindcss": "4.3.3", + "typescript": "5.9.3", + "wrangler": "4.149.0" + } +} diff --git a/apps/docs/public/_redirects b/apps/docs/public/_redirects new file mode 100644 index 0000000..1113c03 --- /dev/null +++ b/apps/docs/public/_redirects @@ -0,0 +1,3 @@ +# Cloudflare Pages redirects. +# The previous (Docus) site published its sitemap at /sitemap.xml. +/sitemap.xml /sitemap-index.xml 301 diff --git a/apps/docs/public/favicon.ico b/apps/docs/public/favicon.ico new file mode 100644 index 0000000..5ba3ef4 Binary files /dev/null and b/apps/docs/public/favicon.ico differ diff --git a/apps/docs/public/fonts/Inter-Bold.ttf b/apps/docs/public/fonts/Inter-Bold.ttf new file mode 100644 index 0000000..9fb9b75 Binary files /dev/null and b/apps/docs/public/fonts/Inter-Bold.ttf differ diff --git a/apps/docs/src/components.ts b/apps/docs/src/components.ts new file mode 100644 index 0000000..06a0e4f --- /dev/null +++ b/apps/docs/src/components.ts @@ -0,0 +1,33 @@ +/** + * MDX globals registry — components available inside MDX without `import`. + * Wired via `` in `[...slug].astro`. + * Add new components here as you build (or install) them. + */ + +import Hero from './components/landing/Hero.astro' +import Section from './components/landing/Section.astro' +import Render from './components/Render.astro' +import { Aside } from './components/ui/aside' +import { Card } from './components/ui/card' +import { CardGrid } from './components/ui/card-grid' +import { CodeGroup } from './components/ui/code-group' +import { LinkButton } from './components/ui/link-button' +import { PackageManagers } from './components/ui/package-managers' +import { Step, Steps } from './components/ui/steps' +import { TabItem, Tabs } from './components/ui/tabs' + +export const components = { + Aside, + Card, + CardGrid, + CodeGroup, + Hero, + LinkButton, + PackageManagers, + Render, + Section, + Step, + Steps, + TabItem, + Tabs, +} diff --git a/apps/docs/src/components/AgentDirective.astro b/apps/docs/src/components/AgentDirective.astro new file mode 100644 index 0000000..8b208a2 --- /dev/null +++ b/apps/docs/src/components/AgentDirective.astro @@ -0,0 +1,29 @@ +--- +interface Props { + /** Absolute or site-relative URL for this page's markdown version, or + * `null` when the page has no per-page Markdown (a non-default + * query-mode API version) — the directive then points at the index only. */ + markdownUrl?: string | null + /** Absolute or site-relative URL for the top-level llms.txt index. */ + llmsUrl: string +} + +const { markdownUrl, llmsUrl } = Astro.props; +--- + + diff --git a/apps/docs/src/components/Header.astro b/apps/docs/src/components/Header.astro new file mode 100644 index 0000000..4d275a4 --- /dev/null +++ b/apps/docs/src/components/Header.astro @@ -0,0 +1,109 @@ +--- +import type { SidebarSection } from '@cloudflare/nimbus-docs/types' +import Icon from '@cloudflare/nimbus-docs/components/Icon.astro' +import { getSidebarSections, stripBase, withBase } from '@cloudflare/nimbus-docs/runtime' +import { config } from 'virtual:nimbus/config' +import { getLocaleFromPath, locales } from '@/i18n/locales' +import LanguageSwitcher from './LanguageSwitcher.astro' +import { Button } from './ui/button' +import { LinkButton } from './ui/link-button' +import SearchTrigger from './ui/search/SearchTrigger.astro' +import { ThemeToggle } from './ui/theme-toggle' + +interface Props { + /** Astro collection id for the current page, forwarded from DocsLayout. */ + collection?: string + /** Astro entry id for the current page, forwarded from DocsLayout. */ + entryId?: string + /** + * Whether the page has a sidebar to open. When `false`, the mobile + * menu button is hidden — pages that opted out via `sidebar: false` + * shouldn't show a button that opens an empty dialog. Default `true`. + */ + showSidebar?: boolean + sections?: SidebarSection[] +} + +// `entryId` is part of Props as a forward-compat hook (the version-switcher +// recipe reads it) but the base Header doesn't use it — leave it off the +// destructure to avoid an unused-var warning. +const { collection, showSidebar = true, sections: sectionsProp } = Astro.props + +// Normalize trailing slash so isActive matches sidebar hrefs. +const currentSlug = stripBase(Astro.url.pathname, import.meta.env.BASE_URL).replace(/\/$/, '') || '/' +// i18n: each locale is a top-level sidebar section (`/en`, `/ko`). Those +// are switched by , not shown as section tabs. +const sections = ( + sectionsProp ?? (await getSidebarSections(currentSlug, { collection })) +).filter(section => !(locales as readonly string[]).includes(section.href.split('/')[1] ?? '')) +const showSections = sections.length >= 2 +const homeHref = withBase(`/${getLocaleFromPath(currentSlug)}`, import.meta.env.BASE_URL) +function resolveSectionHref(href: string): string { + return withBase(href, import.meta.env.BASE_URL) +} +--- + +
+
+
+ + + + {config.title} + + + + + {showSections && ( + + )} +
+ +
+ + + {config.search !== false && } + {config.github && ( + + + + )} + + {showSidebar && ( +
+
+
diff --git a/apps/docs/src/components/LanguageSwitcher.astro b/apps/docs/src/components/LanguageSwitcher.astro new file mode 100644 index 0000000..68495a1 --- /dev/null +++ b/apps/docs/src/components/LanguageSwitcher.astro @@ -0,0 +1,41 @@ +--- +/** + * LanguageSwitcher — links the current page to its translations. Renders + * nothing on pages without a counterpart in another locale (e.g. `/404`). + * + * Links force a full page load (`data-astro-reload`): Pagefind picks its + * per-language index from `` once per document, so a client-side + * swap into another locale would keep searching the previous language. + */ +import Icon from '@cloudflare/nimbus-docs/components/Icon.astro' +import { stripBase, withBase } from '@cloudflare/nimbus-docs/runtime' +import { getLocaleAlternates } from '@/i18n/alternates' +import { getLocaleFromPath, localeLabels, ui } from '@/i18n/locales' + +const pagePath = stripBase(Astro.url.pathname, import.meta.env.BASE_URL) +const alternates = await getLocaleAlternates(pagePath) +const { language } = ui[getLocaleFromPath(pagePath)] +--- + +{alternates.length > 1 && ( + +)} diff --git a/apps/docs/src/components/Render.astro b/apps/docs/src/components/Render.astro new file mode 100644 index 0000000..7162ce1 --- /dev/null +++ b/apps/docs/src/components/Render.astro @@ -0,0 +1,119 @@ +--- +/** + * Render — include a reusable partial in any docs page. + * + * + * + * + * Partials live in `src/content/partials/` as MDX. Declare params in + * partial frontmatter (`params: [runtime, version?]`); required params + * fail at build time, optional use a `?` suffix. + */ +import { getVisibleEntries, getVisibleEntry } from '@cloudflare/nimbus-docs/runtime' +import { render } from 'astro:content' +import { components } from '../components' + +interface Props { + /** Partial ID — path relative to src/content/partials/ without extension. */ + file: string + /** Parameters passed to the partial as props. */ + params?: Record +} + +const { file, params } = Astro.props +const page = Astro.url.pathname +const partial = await getVisibleEntry('partials', file) + +if (!partial) { + const allPartials = await getVisibleEntries(['partials']) + const partialIds = allPartials.map(p => p.id) + const hint = closest(file, partialIds) + const shortList = partialIds.sort().slice(0, 10).join(', ') + const tail + = partialIds.length > 10 + ? ` (and ${partialIds.length - 10} more)` + : partialIds.length === 0 + ? 'none' + : '' + throw new Error( + `[Render] Partial "${file}" not found, included on "${page}".${ + hint ? ` Did you mean "${hint}"?` : '' + } Available: ${shortList}${tail}`, + ) +} + +const declaredParams = partial.data.params +if (declaredParams) { + const required = declaredParams.filter((param: string) => !param.endsWith('?')) + const optional = declaredParams.filter((param: string) => param.endsWith('?')) + const allNames = [...required, ...optional.map((param: string) => param.slice(0, -1))] + const received = Object.keys(params ?? {}) + + const missing = required.filter((param: string) => !received.includes(param)) + if (missing.length > 0) { + throw new Error( + `[Render] Missing required params ${JSON.stringify(missing)} for "${file}" on "${page}". ` + + `Expected: ${JSON.stringify(declaredParams)}, received: ${JSON.stringify(received)}`, + ) + } + + const unexpected = received.filter(param => !allNames.includes(param)) + if (unexpected.length > 0) { + const unexpectedHints = unexpected + .map((u) => { + const h = closest(u, allNames) + return h ? `"${u}" (did you mean "${h}"?)` : `"${u}"` + }) + .join(', ') + throw new Error( + `[Render] Unexpected params ${unexpectedHints} for "${file}" on "${page}". ` + + `Declared: ${JSON.stringify(declaredParams)}`, + ) + } +} + +const { Content } = await render(partial) + +/** Levenshtein distance — inlined to keep this component dep-free. */ +function distance(a: string, b: string): number { + if (a === b) { + return 0 + } + if (!a.length) { + return b.length + } + if (!b.length) { + return a.length + } + const v0 = Array.from({ length: b.length + 1 }).fill(0) + const v1 = Array.from({ length: b.length + 1 }).fill(0) + for (let i = 0; i <= b.length; i++) { + v0[i] = i + } + for (let i = 0; i < a.length; i++) { + v1[0] = i + 1 + for (let j = 0; j < b.length; j++) { + const cost = a[i] === b[j] ? 0 : 1 + v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost) + } + for (let j = 0; j <= b.length; j++) { + v0[j] = v1[j] + } + } + return v1[b.length] +} + +function closest(target: string, candidates: string[], maxDist = 3): string | null { + const t = target.toLowerCase() + let best: { name: string, dist: number } | null = null + for (const c of candidates) { + const d = distance(t, c.toLowerCase()) + if (d <= maxDist && (!best || d < best.dist)) { + best = { name: c, dist: d } + } + } + return best?.name ?? null +} +--- + + diff --git a/apps/docs/src/components/WebMCP.astro b/apps/docs/src/components/WebMCP.astro new file mode 100644 index 0000000..45369fe --- /dev/null +++ b/apps/docs/src/components/WebMCP.astro @@ -0,0 +1,25 @@ +--- +import { capabilities, options } from 'virtual:nimbus/agent-capabilities'; +--- + +{capabilities.search === 'pagefind' && ( + +)} + + diff --git a/apps/docs/src/components/landing/Hero.astro b/apps/docs/src/components/landing/Hero.astro new file mode 100644 index 0000000..1bbad17 --- /dev/null +++ b/apps/docs/src/components/landing/Hero.astro @@ -0,0 +1,34 @@ +--- +/** + * Hero — landing page heading block (title, lead paragraph, call-to-action + * buttons passed as children). + * + * + * Get started + * + */ +interface Props { + title: string + description?: string +} + +const { title, description } = Astro.props +--- + +
+
+

+ {title} +

+ {description && ( +

+ {description} +

+ )} +
+ +
+
+
diff --git a/apps/docs/src/components/landing/Section.astro b/apps/docs/src/components/landing/Section.astro new file mode 100644 index 0000000..1574fb4 --- /dev/null +++ b/apps/docs/src/components/landing/Section.astro @@ -0,0 +1,32 @@ +--- +/** + * Section — a titled landing page band. Children render as regular docs + * prose, so Markdown, code groups, and cards work inside it. + * + *
+ * … + *
+ */ +interface Props { + title: string + description?: string +} + +const { title, description } = Astro.props +--- + +
+
+
+

{title}

+ {description && ( +

+ {description} +

+ )} +
+
+ +
+
+
diff --git a/apps/docs/src/components/ui/aside/Aside.astro b/apps/docs/src/components/ui/aside/Aside.astro new file mode 100644 index 0000000..3a1cd09 --- /dev/null +++ b/apps/docs/src/components/ui/aside/Aside.astro @@ -0,0 +1,90 @@ +--- +import Icon from "@cloudflare/nimbus-docs/components/Icon.astro"; +import { cn } from "@/lib/cn"; +import type { HTMLAttributes } from "astro/types"; +import { stripBase } from "@cloudflare/nimbus-docs/runtime"; +import { getLocaleFromPath, ui } from "@/i18n/locales"; + +interface Props extends HTMLAttributes<"aside"> { + // Uses `type` (not `variant`) to match the conventional