# zudo-doc > Documentation base framework built with Astro and MDX. --- # Getting Started > Source: /pj/zudo-doc/docs/getting-started Welcome to the Getting Started section. Choose a topic below to begin. --- # 0.1.0 > Source: /pj/zudo-doc/docs/changelog/0.1.0 Initial release of zudo-doc. ### Features - Astro 6 with MDX content collections - Tailwind CSS v4 design token system with 16-color palette - Sidebar navigation with auto-generated tree from file structure - i18n support (English and Japanese) - Admonition components (Note, Tip, Info, Warning, Danger) - Code highlighting with Shiki - Table of contents with scroll spy - Color scheme switching with multiple built-in themes - Search functionality --- # Basic Components > Source: /pj/zudo-doc/docs/components/basic-components These are the standard Markdown and MDX elements styled through zudo-doc's design token system. No imports are needed. ## Headings Headings from `h2` to `h4` appear in the table of contents sidebar. ### Heading 3 #### Heading 4 ```mdx ## Heading 2 ### Heading 3 #### Heading 4 ``` ## Text Formatting **Bold text**, *italic text*, and ~~strikethrough~~. Combine them: ***bold and italic***. ```mdx **Bold text**, *italic text*, and ~~strikethrough~~. Combine them: ***bold and italic***. ``` ## Inline Code Use backticks to mark `inline code` within a sentence. ```mdx Use backticks to mark `inline code` within a sentence. ``` ## Code Blocks Fenced code blocks support syntax highlighting via Shiki. Specify the language after the opening backticks. ```ts function greet(name: string): string { return `Hello, ${name}!`; } ``` ````mdx ```ts function greet(name: string): string { return `Hello, ${name}!`; } ``` ```` ### Supported Languages Shiki supports a wide range of languages. Common ones include: | Language | Identifier | | -------- | ---------- | | TypeScript | `ts`, `typescript` | | JavaScript | `js`, `javascript` | | HTML | `html` | | CSS | `css` | | JSON | `json` | | Bash / Shell | `bash`, `sh`, `shell`, `zsh` | | Markdown | `md`, `markdown` | | MDX | `mdx` | | YAML | `yaml`, `yml` | | Python | `python`, `py` | | Rust | `rust`, `rs` | | Go | `go` | | SQL | `sql` | | GraphQL | `graphql` | | Diff | `diff` | | TOML | `toml` | | Astro | `astro` | | JSX / TSX | `jsx`, `tsx` | See the [full list of supported languages](https://shiki.style/languages) for all available identifiers. ## Unordered Lists - First item - Second item - Nested item - Another nested item - Deeply nested - Third item ```mdx - First item - Second item - Nested item - Another nested item - Deeply nested - Third item ``` ## Ordered Lists 1. First step 2. Second step 1. Sub-step one 2. Sub-step two 3. Third step ```mdx 1. First step 2. Second step 1. Sub-step one 2. Sub-step two 3. Third step ``` ## Blockquotes > This is a blockquote. It can span multiple lines > and supports **formatting** within it. ```mdx > This is a blockquote. It can span multiple lines > and supports **formatting** within it. ``` ## Tables | Feature | Status | Notes | | ---------- | ----------- | ------------------ | | MDX | Supported | Via `@astrojs/mdx` | | Shiki | Built-in | Code highlighting | | Tailwind | v4 | Token-based design | ```mdx | Feature | Status | Notes | | ---------- | ----------- | ------------------ | | MDX | Supported | Via `@astrojs/mdx` | | Shiki | Built-in | Code highlighting | | Tailwind | v4 | Token-based design | ``` ## Links Internal link: [Getting Started](/docs/getting-started) External link: [Astro documentation](https://docs.astro.build) ```mdx Internal link: [Getting Started](/docs/getting-started) External link: [Astro documentation](https://docs.astro.build) ``` ## Horizontal Rules Use three dashes to create a horizontal rule: --- ```mdx --- ``` --- # Develop > Source: /pj/zudo-doc/docs/develop Development notes for zudo-doc itself. --- # Introduction > Source: /pj/zudo-doc/docs/getting-started/introduction Welcome to **zudo-doc** — a documentation base framework built with Astro 6 and MDX. zudo-doc is designed to be simple and extensible. Start with the basics and add features as you need them. ## Why zudo-doc? - **Fast dev server** — Vite-powered, pages compile on demand with instant HMR - **MDX support** — write documentation with JSX components embedded in Markdown - **Tailwind CSS v4** — utility-first styling with a tight design token system - **Color scheme support** — themeable 16-color palette with light and dark defaults - **Static export** — generates pure HTML, zero JS by default (Preact islands only where needed) - **i18n ready** — English and Japanese routing built in - **llms.txt** — auto-generated AI-consumable documentation files - **Versioning** — maintain multiple documentation versions with version-prefixed URLs - **Minimal base** — extend with your own components and layouts If you're coming from Docusaurus, you'll find the MDX workflow familiar but with a lighter footprint and full control over the design system. ## How It Works zudo-doc uses [Astro's Content Collections](https://docs.astro.build/en/guides/content-collections/) to manage documentation pages. Each `.mdx` file in `src/content/docs/` becomes a page with automatic sidebar navigation, table of contents, and prev/next links. The color scheme system uses a 16-color palette. By swapping a single color scheme, the entire site's appearance changes. ## Quick Start ```bash pnpm create zudo-doc my-docs cd my-docs pnpm install pnpm dev ``` Then open [http://localhost:4321](http://localhost:4321) to see your docs. See the [Installation](/docs/getting-started/installation/) guide for all CLI options and setup details. If you want to explore the full project including these showcase docs, clone the repository directly instead: ```bash git clone https://github.com/zudolab/zudo-doc.git my-docs cd my-docs pnpm install pnpm dev ``` --- # Configuration > Source: /pj/zudo-doc/docs/guides/configuration zudo-doc is configured through a small set of files in the `src/config/` directory and the root `astro.config.ts`. ## Site Settings The main settings file is `src/config/settings.ts`: ```ts colorScheme: "Default Dark", colorMode: { defaultMode: "light", lightScheme: "Default Light", darkScheme: "Default Dark", respectPrefersColorScheme: true, }, siteName: "zudo-doc", siteDescription: "Documentation base framework...", base: "/", trailingSlash: false, docsDir: "src/content/docs", locales: { ja: { label: "JA", dir: "src/content/docs-ja" }, }, mermaid: true, noindex: false, editUrl: false, siteUrl: "", sitemap: true, designTokenPanel: true, docMetainfo: true, docTags: true, frontmatterPreview: {}, math: true, docHistory: true, claudeResources: { claudeDir: ".claude", }, headerNav: [ { label: "Getting Started", labelKey: "nav.gettingStarted", path: "/docs/getting-started", categoryMatch: "getting-started" }, { label: "Guides", labelKey: "nav.guides", path: "/docs/guides", categoryMatch: "guides" }, { label: "Reference", labelKey: "nav.reference", path: "/docs/reference", categoryMatch: "reference" }, { label: "Claude", labelKey: "nav.claude", path: "/docs/claude", categoryMatch: "claude" }, ], }; ``` ### colorScheme The active color scheme name. Must match a key in `src/config/color-schemes.ts`. Available built-in schemes: **Default Light** and **Default Dark**. You can add custom schemes in `src/config/color-schemes.ts`. See the [Color guide](/docs/reference/color) for a full list of available schemes, previews, and instructions on adding custom schemes. ### colorMode Configures light/dark mode behavior. Set to `false` to disable color mode switching entirely and use only the scheme specified in `colorScheme`. When enabled, accepts a `ColorModeConfig` object: | Property | Type | Description | |----------|------|-------------| | `defaultMode` | `"light"` \| `"dark"` | The initial color mode before any user preference is applied | | `lightScheme` | string | Color scheme name to use in light mode | | `darkScheme` | string | Color scheme name to use in dark mode | | `respectPrefersColorScheme` | boolean | When `true`, automatically matches the user's OS-level light/dark preference | ```ts colorMode: { defaultMode: "light", lightScheme: "Default Light", darkScheme: "Default Dark", respectPrefersColorScheme: true, }, // or disable color mode switching: colorMode: false, ``` ### base The base path for deploying to a subdirectory. Set this when your site is served from a subpath rather than the root domain. Default: `"/"` For example, to serve the site at `https://example.com/my-docs/`: ```ts base: "/my-docs", ``` All internal links (sidebar, navigation, prev/next, search) are automatically prefixed with the base path. This maps directly to [Astro's `base` config option](https://docs.astro.build/en/reference/configuration-reference/#base). Inline markdown links within MDX content (e.g., `[link text](/docs/some-page)`) are **not** automatically rewritten. When using a non-root base, use relative links in content files instead. ### trailingSlash Controls whether URLs end with a trailing slash. When `true`, all internal links are generated with a trailing slash (e.g., `/docs/guides/` instead of `/docs/guides`). This also sets Astro's [`trailingSlash`](https://docs.astro.build/en/reference/configuration-reference/#trailingslash) to `"always"` or `"never"` accordingly. Default: `false` ```ts trailingSlash: true, ``` Some hosting platforms (e.g., AWS Amplify, Cloudflare Pages) work better with trailing slashes enabled. If you experience 404 errors on page navigation, try setting this to `true`. When enabled, a middleware automatically redirects URLs without a trailing slash to their trailing-slash version with a **301 redirect** (e.g., `/docs/guides` → `/docs/guides/`). This ensures consistent URLs during development, since Astro's dev server does not perform this redirect on its own. Static assets (files with extensions like `.js`, `.css`, `.png`) and Astro internal paths (`/_astro/`, `/_image`) are excluded from the redirect. ### onBrokenMarkdownLinks Controls how broken internal markdown links are handled during the build. This applies to relative `[text](./page.mdx)` links resolved by zudo-doc's link resolver plugin. - `"warn"` — logs a warning but continues the build - `"error"` — throws an error and fails the build - `"ignore"` — silently skips broken links without any output Type: `"warn" | "error" | "ignore"` Default: `"warn"` ```ts onBrokenMarkdownLinks: "error", // fail the build on broken links ``` ### docsDir The directory path (relative to the project root) where English documentation MDX files are stored. This uses Astro's `glob()` loader, so it can point to any directory. Default: `"src/content/docs"` ```ts docsDir: "docs", // content in ./docs/ at project root ``` This is similar to Docusaurus' `docs.path` option — useful when using zudo-doc as a documentation tool for a larger project where docs live at the project root level. ### locales A map of additional locale configurations. Each key is a locale code, and the value is an object with `label` (display name for the language switcher) and `dir` (content directory path). Default: `{}` ```ts locales: { ja: { label: "JA", dir: "src/content/docs-ja" }, ko: { label: "KO", dir: "src/content/docs-ko" }, }, ``` For each locale entry, zudo-doc automatically: - Creates an Astro content collection (`docs-{code}`) - Registers the locale in Astro's i18n routing - Adds it to the language switcher ### versions Configures multi-version documentation. When set to an array of `VersionConfig` objects, zudo-doc creates versioned content collections and adds a version switcher to the sidebar. Each versioned section is accessible at `/v/{slug}/docs/`. Set to `false` to disable versioning. Type: `VersionConfig[] | false` Default: `false` Each `VersionConfig` object accepts: | Property | Type | Description | |----------|------|-------------| | `slug` | string | Version identifier used in the URL path (e.g., `"1.0"`, `"v1"`) | | `label` | string | Display label shown in the version switcher (e.g., `"1.0.0"`) | | `docsDir` | string | Content directory path for this version's English docs | | `locales` | Record (optional) | Per-locale content directories for this version (`{ ja: { dir: "..." } }`) | | `banner` | `"unmaintained"` \| `"unreleased"` \| `false` (optional) | Banner shown on all pages in this version | ```ts versions: [ { slug: "1.0", label: "1.0.0", docsDir: "src/content/docs-v1", banner: "unmaintained", }, ], // or disable versioning: versions: false, ``` See the [Versioning guide](/docs/guides/versioning) for details on how version switching works and how to configure banners for unmaintained or unreleased versions. ### siteName The site name displayed in the header and used in page titles. Page titles are formatted as `{page title} | {siteName}`. ### siteDescription A short description of the site. Used in meta tags for SEO and social sharing. Default: `""` ### siteUrl The full base URL of your site (e.g., `"https://example.com"`). Required for generating absolute URLs in the sitemap. Default: `""` ### noindex When `true`, adds `noindex` and `nofollow` meta tags to all pages. Useful for internal documentation sites that should not be indexed by search engines. Default: `false` ### mermaid Enables Mermaid diagram support. When `true`, fenced code blocks with the `mermaid` language identifier are rendered as diagrams. When `false`, mermaid code blocks are displayed as plain code. Default: `true` ### math Enables KaTeX math equation rendering. When `true`, inline math (`$...$`), block math (`$$...$$`), and fenced `math` code blocks are rendered as equations. Default: `true` ### cjkFriendly Enables CJK-friendly typography adjustments. When `true`, applies improved line-breaking and word-wrap behavior for Chinese, Japanese, and Korean text throughout the documentation. Type: `boolean` Default: `false` ```ts cjkFriendly: true, ``` ### editUrl Base URL for "Edit this page" links. The full URL is constructed as `editUrl + contentDir + "/" + entryId`. Set to `false` to disable edit links. Default: `false` (disabled) ```ts editUrl: "https://github.com/my-org/my-repo/edit/main/", // or disable: editUrl: false, ``` ### sitemap Enables automatic `sitemap.xml` generation during build. The sitemap includes all built HTML pages except 404. Default: `true` ### llmsTxt Enables automatic `llms.txt` generation during build. When `true`, an `llms.txt` file is generated at the site root listing all documentation pages with their titles and descriptions. This file follows the [llms.txt specification](https://llmstxt.org/) and helps AI assistants discover and index your documentation content. Type: `boolean` Default: `false` ```ts llmsTxt: true, ``` ### docMetainfo Shows metadata (created date, last updated date, author) under the page title. Dates and author are extracted from git history at build time. Default: `true` ### docTags Enables tag support for documentation pages. When `true`, pages can use the `tags` frontmatter field, and tag index pages are generated at `/docs/tags/`. See the [Tags guide](./tags.mdx) for a full walkthrough. Default: `true` ### tagPlacement Controls where the per-page tag badge row renders on doc pages. Type: `"after-title" | "before-pager"` Default: `"after-title"` - `"after-title"` — the badge row appears directly below the page title, above the main content. - `"before-pager"` — the badge row appears at the bottom of the content, immediately above the previous/next pager. ```ts tagPlacement: "before-pager", ``` Has no effect when `docTags` is `false` or when a page carries no tags. ### frontmatterPreview Displays custom frontmatter fields as a compact key/value table directly beneath the page title. Framework-managed keys (`title`, `description`, `sidebar_position`, etc.) are hidden by default so only project-specific metadata is shown. Set to `false` to disable the block on all pages. Type: `FrontmatterPreviewConfig | false` Default: `{}` (enabled with built-in ignore list) The `FrontmatterPreviewConfig` object accepts: | Property | Type | Description | |----------|------|-------------| | `ignoreKeys` | `string[]` (optional) | Completely replaces the default ignore list. When set, `extraIgnoreKeys` is ignored | | `extraIgnoreKeys` | `string[]` (optional) | Additional keys to ignore on top of the defaults. Has no effect when `ignoreKeys` is also set | ```ts // Extend the default list: frontmatterPreview: { extraIgnoreKeys: ["reviewed_by", "internal_id"], }, // Replace the default list entirely: frontmatterPreview: { ignoreKeys: ["title", "description", "sidebar_position"], }, // Disable: frontmatterPreview: false, ``` See the [Frontmatter Preview reference](../reference/frontmatter-preview.mdx) for the full default ignore list and a live demo. ### designTokenPanel Enables the interactive Design Token Panel. When `true`, a palette icon appears in the header and users can open a tabbed panel to tweak spacing, font, size, and color tokens in real-time. Changes are persisted in `localStorage` and can be exported/imported as JSON. Default: `false` (set to `true` to enable) See the [Design Token Panel reference](../reference/design-token-panel.mdx) for a full tour of the four tabs, the JSON export workflow, and the deprecated `colorTweakPanel` alias. ### aiAssistant Enables the AI assistant chat widget. When `true`, a chat button appears in the page that opens an AI-powered assistant for answering questions about your documentation. Requires a configured `ai-chat-worker` Cloudflare Worker backend. Type: `boolean` Default: `false` ```ts aiAssistant: true, ``` ### sidebarToggle Enables the sidebar collapse toggle. When `true`, a button in the sidebar header allows users to collapse the sidebar entirely, giving more horizontal space for content. The collapsed state is persisted in `localStorage`. Type: `boolean` Default: `false` ```ts sidebarToggle: true, ``` ### sidebarResizer Enables sidebar width resizing. When `true`, a drag handle appears at the sidebar's right edge, allowing users to drag and resize the sidebar width. The resized width is persisted in `localStorage`. Type: `boolean` Default: `false` ```ts sidebarResizer: true, ``` ### docHistory Enables per-document git revision history viewer. When `true`, each doc page shows a **History** button that opens a side panel with the document's git commit history and a line-by-line diff viewer for comparing revisions. In dev mode, history is served by the standalone `@zudo-doc/doc-history-server` package running on port 4322. In production, a CLI generator creates static JSON files during CI. See the [Doc History Server reference](/docs/reference/doc-history-server) for details on the server and CLI. Default: `false` (set to `true` to enable) See the [Document History guide](/docs/guides/doc-history) for usage details, keyboard shortcuts, and technical information. ### htmlPreview Global configuration for the `` component. When set, the provided `head`, `css`, and `js` are injected into every `` iframe on the site — useful for loading shared fonts, a design system stylesheet, or utility scripts without repeating them on each component demo. Set to `undefined` (the default) to disable global injection. Type: `HtmlPreviewConfig | undefined` Default: `undefined` The `HtmlPreviewConfig` object accepts: | Property | Type | Description | |----------|------|-------------| | `head` | string (optional) | Raw HTML injected into `` (e.g., `` tags for fonts or icon libraries) | | `css` | string (optional) | CSS injected as a `` block after preflight reset | | `js` | string (optional) | JavaScript injected as a `` block before `` | ```ts htmlPreview: { head: ``, css: `body { font-family: sans-serif; }`, js: `console.log("HtmlPreview loaded")`, }, // or disable global injection: htmlPreview: undefined, ``` Individual `` blocks can extend the global config by passing their own `head`, `css`, or `js` props — they are appended to the global values, not replaced. ### claudeResources Configures the Claude Code Resources Viewer, which auto-generates documentation pages from your `.claude/` directory. Set to `false` to disable. | Property | Type | Description | |----------|------|-------------| | `claudeDir` | string | Path to the `.claude` directory (relative to project root) | | `projectRoot` | string (optional) | Project root override for monorepo setups | ```ts claudeResources: { claudeDir: ".claude", }, // or disable: claudeResources: false, ``` See the [Claude Resources guide](/docs/guides/claude-resources) for details on what gets generated and how it works. ### footer Configures the site footer. When set to a `FooterConfig` object, renders a footer with link columns and optional copyright text. Set to `false` to disable the footer entirely. Type: `FooterConfig | false` Default: `false` The `FooterConfig` object accepts: | Property | Type | Description | |----------|------|-------------| | `links` | `FooterLinkColumn[]` | Array of link columns displayed in the footer | | `copyright` | string (optional) | Copyright text shown at the bottom. HTML is supported | Each `FooterLinkColumn`: | Property | Type | Description | |----------|------|-------------| | `title` | string | Column heading | | `items` | `FooterLinkItem[]` | Array of links in this column | | `locales` | Record (optional) | Per-locale title overrides (e.g., `{ ja: { title: "ドキュメント" } }`) | Each `FooterLinkItem`: | Property | Type | Description | |----------|------|-------------| | `label` | string | Link display text | | `href` | string | Link URL | | `locales` | Record (optional) | Per-locale label overrides (e.g., `{ ja: { label: "はじめに" } }`) | ```ts footer: { links: [ { title: "Docs", locales: { ja: { title: "ドキュメント" } }, items: [ { label: "Getting Started", href: "/docs/getting-started", locales: { ja: { label: "はじめに" } } }, ], }, { title: "Community", items: [{ label: "GitHub", href: "https://github.com/my-org/my-repo" }], }, ], copyright: `Copyright © ${new Date().getFullYear()} Your Name.`, }, // or disable footer: footer: false, ``` ### headerNav An array of navigation links rendered in the site header bar. Each item supports the following properties: | Property | Type | Description | |----------|------|-------------| | `label` | string | Display text shown in the header | | `labelKey` | string (optional) | i18n translation key (e.g., `"nav.gettingStarted"`) — overrides `label` when a translation is available | | `path` | string | URL path the link navigates to | | `categoryMatch` | string (optional) | Links this header tab to a sidebar category. When active, only the matched category's sidebar is shown | ```ts headerNav: [ { label: "Getting Started", labelKey: "nav.gettingStarted", path: "/docs/getting-started", categoryMatch: "getting-started" }, { label: "Guides", labelKey: "nav.guides", path: "/docs/guides", categoryMatch: "guides" }, { label: "Reference", labelKey: "nav.reference", path: "/docs/reference", categoryMatch: "reference" }, { label: "Claude", labelKey: "nav.claude", path: "/docs/claude", categoryMatch: "claude" }, ], ``` ## Astro Configuration The root `astro.config.ts` controls the build pipeline: ```ts output: "static", integrations: [mdx(), react(), searchIndexIntegration()], i18n: { defaultLocale: "en", locales: ["en", ...Object.keys(settings.locales)], routing: { prefixDefaultLocale: false }, }, vite: { plugins: [tailwindcss()], }, markdown: { shikiConfig: { theme: shikiTheme }, }, }); ``` Key aspects: - **output**: Set to `"static"` for full static HTML export - **integrations**: MDX support, Preact islands, and MiniSearch search indexing - **i18n**: English (default at `/docs/...`) and Japanese (`/ja/docs/...`). Default locale has no URL prefix - **Tailwind CSS**: Loaded via the `@tailwindcss/vite` plugin instead of an Astro integration - **Shiki theme**: Automatically derived from the active color scheme's `shikiTheme` property — no manual sync needed The Shiki code highlighting theme is tied to the color scheme. When you change `colorScheme` in settings, the syntax highlighting theme updates automatically. ## Logo The site logo is displayed on the landing page as a masked SVG shape. The file is located at `public/img/logo.svg`. The default logo is a randomly generated geometric pattern created with [`@takazudo/kumiko-gen`](https://www.npmjs.com/package/@takazudo/kumiko-gen). To regenerate it with a new random pattern: ```bash pnpm run generate:logo ``` You can also replace `public/img/logo.svg` with any custom SVG file. The logo is rendered as a CSS mask using the alpha channel, so: - The SVG **must have a transparent background** — opaque backgrounds render as a filled rectangle - The stroke or fill color in the SVG file does not matter; the displayed color is controlled by the site's color tokens ## Color Scheme Config Color schemes are defined in `src/config/color-schemes.ts`. Each scheme provides 22 color properties plus a `shikiTheme`: - `background`, `foreground`, `cursor`, `selectionBg`, `selectionFg` - `palette` — 16 color slots (`palette[0]` through `palette[15]`) - `shikiTheme` — matching Shiki theme name for syntax highlighting - `semantic` (optional) — overrides for `surface`, `muted`, `accent`, and `accentHover` For details on adding or customizing color schemes, see the [Color guide](/docs/reference/color). --- # Guides > Source: /pj/zudo-doc/docs/guides Explore our guides to learn about zudo-doc features and customization. --- # Demo: Hide Sidebar > Source: /pj/zudo-doc/docs/guides/layout-demos/hide-sidebar This page demonstrates the `hide_sidebar: true` frontmatter option. Notice that the left sidebar is hidden — the content is centered in a narrower container for a focused reading experience. ## What's Happening The left sidebar navigation is hidden on this page. Here is the frontmatter used: ```yaml --- title: "Demo: Hide Sidebar" hide_sidebar: true hide_toc: false --- ``` ## Table of Contents Still Visible The table of contents on the right side is still visible, providing quick access to sections on this page. Only the left sidebar is affected. ## Mobile Navigation On mobile devices, the hamburger menu still provides full navigation access. The `hide_sidebar` option only affects the desktop sidebar. ## When to Use Use `hide_sidebar: true` for pages where sidebar navigation is a distraction — standalone articles, landing pages, or focused content. See the [Frontmatter reference](/docs/guides/frontmatter/#hide_sidebar) for full documentation. ## See Also - [Hide Table of Contents](/docs/guides/layout-demos/hide-toc/) — hides the right-side TOC - [Hide Sidebar & Table of Contents](/docs/guides/layout-demos/hide-both/) — hides both panels --- # Design System > Source: /pj/zudo-doc/docs/reference/design-system zudo-doc uses a **tight token strategy** — instead of importing the full Tailwind framework, only `preflight` and `utilities` are loaded, skipping the default theme entirely. A small, intentional set of design tokens is defined from scratch. This page covers the full system: spacing, typography, colors, border radius, and breakpoints. ## Tight Token Strategy Tailwind CSS ships with hundreds of built-in values — colors, spacing scales, font sizes, and more. In a themeable project, these defaults cause problems: hardcoded values ignore theme changes and create inconsistency. zudo-doc solves this with selective imports in `src/styles/global.css` — only `preflight` (reset) and `utilities` (functional classes) are imported, while the default theme layer is skipped entirely: ```css @import "tailwindcss/preflight"; @import "tailwindcss/utilities"; @theme { /* Only define what we need — no resets necessary */ --color-bg: var(--zd-bg); --color-fg: var(--zd-fg); /* ... */ } ``` Because the default theme is never loaded, no `--*: initial` reset block is needed. Only explicitly defined tokens in `@theme` work. Using an undefined token (like `p-4` or `bg-gray-500`) produces no effect — the value simply doesn't exist. This gives you a guarantee: **invalid tokens cause build errors or missing styles, not visual bugs**. This is the core philosophy: define only what the project needs. Every value in the system is intentional, and accidental use of Tailwind defaults is immediately visible. ## Spacing zudo-doc separates spacing into two axes: **horizontal (hsp)** and **vertical (vsp)**. They serve different purposes in layout — horizontal spacing controls inline rhythm and gutters, while vertical spacing controls content flow and section separation. The vertical scale is stretched at larger sizes to give content more breathing room. ### Horizontal Spacing (hsp) | Token | Value | Example Class | |-------|-------|---------------| | `hsp-2xs` | `0.25rem` (4px) | `px-hsp-2xs` | | `hsp-xs` | `0.375rem` (6px) | `px-hsp-xs` | | `hsp-sm` | `0.5rem` (8px) | `gap-x-hsp-sm` | | `hsp-md` | `0.75rem` (12px) | `px-hsp-md` | | `hsp-lg` | `1rem` (16px) | `px-hsp-lg` | | `hsp-xl` | `1.5rem` (24px) | `px-hsp-xl` | | `hsp-2xl` | `2rem` (32px) | `px-hsp-2xl` | ### Vertical Spacing (vsp) | Token | Value | Example Class | |-------|-------|---------------| | `vsp-2xs` | `0.25rem` (4px) | `py-vsp-2xs` | | `vsp-xs` | `0.5rem` (8px) | `py-vsp-xs` | | `vsp-sm` | `0.75rem` (12px) | `gap-y-vsp-sm` | | `vsp-md` | `1rem` (16px) | `py-vsp-md` | | `vsp-lg` | `1.5rem` (24px) | `py-vsp-lg` | | `vsp-xl` | `2rem` (32px) | `py-vsp-xl` | | `vsp-2xl` | `3rem` (48px) | `py-vsp-2xl` | Both axes also include `0` (`0px`) and `px` (`1px`) utility values. ```html Content with asymmetric spacing Grid items ``` Notice that `hsp-lg` is `1rem` while `vsp-lg` is `1.5rem` — the vertical axis is stretched at larger sizes. This is intentional — vertical flow needs more room than horizontal rhythm. ## Typography ### Font Sizes | Token | Value | Example | |-------|-------|---------| | `caption` | `0.75rem` / 12px | `text-caption` | | `small` | `0.875rem` / 14px | `text-small` | | `body` | `1rem` / 16px | `text-body` | | `subheading` | `1.125rem` / 18px | `text-subheading` | | `heading` | `1.875rem` / 30px | `text-heading` | | `display` | `3.75rem` / 60px | `text-display` | ### Font Weights | Token | Value | Example | |-------|-------|---------| | `normal` | `400` | `font-normal` | | `medium` | `500` | `font-medium` | | `semibold` | `600` | `font-semibold` | | `bold` | `700` | `font-bold` | ### Line Heights | Token | Value | Example | |-------|-------|---------| | `tight` | `1.25` | `leading-tight` | | `snug` | `1.375` | `leading-snug` | | `normal` | `1.5` | `leading-normal` | | `relaxed` | `1.625` | `leading-relaxed` | ### Font Families | Token | Stack | Example | |-------|-------|---------| | `sans` | System sans-serif stack | `font-sans` | | `mono` | System monospace stack | `font-mono` | ```html Page Title Body text inline code ``` ## Border Radius | Token | Value | Example | |-------|-------|---------| | `DEFAULT` | `0.25rem` (4px) | `rounded` | | `lg` | `0.5rem` (8px) | `rounded-lg` | | `full` | `9999px` | `rounded-full` | ```html Default radius Card with larger radius Pill badge ``` ## Breakpoints | Token | Value | Example | |-------|-------|---------| | `sm` | `640px` | `sm:flex` | | `lg` | `1024px` | `lg:grid-cols-2` | | `xl` | `1280px` | `xl:max-w-5xl` | ```html Responsive horizontal padding ``` ## Colors Colors use a **three-tier strategy**: raw palette values (Tier 1) flow into semantic tokens (Tier 2), which feed into component-scoped tokens (Tier 3). Each tier only references the tier above it, so swapping a color scheme updates the entire site at once. See the [Color reference](/docs/reference/color) for full details on the color token system, color schemes, and customization. ## Usage Rules The default Tailwind theme is not imported — only `tailwindcss/preflight` and `tailwindcss/utilities` are loaded. Only project-defined tokens work. ### Do ```html Primary text Panel Link Proper spacing Grid gaps Heading ``` ### Don't ```html Broken Breaks on theme switch ``` All tokens are defined in `src/styles/global.css`. That file is the single source of truth for the design system. ## Interaction Rules ### hover:underline on link-like elements Elements that _navigate_ (rendered as `` or behave as a link) must show an underline on hover and on keyboard focus. Buttons, toggles, and controls do not — they use a border/background change instead. **Do** (navigational links): ```html Link Sidebar item ``` **Don't** (missing focus-visible parity): ```html Inaccessible to keyboard focus ``` **Don't** (controls): ```html Use hover:bg-accent/10 instead ``` The pair `hover:underline focus-visible:underline` is the canonical form — always add both, never one without the other. Precedents: `src/components/header.astro`, `src/components/site-tree-nav.tsx`, `src/components/footer.astro`. For the broader reasoning (light-mode / dark-mode contrast, when an underline is sufficient vs when a color shift is also needed), consult the `/css-wisdom` skill locally — its light-mode/dark-mode and three-tier token strategy sections cover the trade-offs. --- # /CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/root **Path:** `CLAUDE.md` # zudo-doc Minimal documentation framework built with Astro 6, MDX, Tailwind CSS v4, and Preact islands. ## Tech Stack - **Astro 6** — static site generator with Content Collections - **MDX** — via `@astrojs/mdx`, content directory configurable via `docsDir` setting - **Tailwind CSS v4** — via `@tailwindcss/vite` (not `@astrojs/tailwind`) - **Preact** — for interactive islands (TOC scroll spy, sidebar toggle, collapsible categories) and server-rendered content typography components, with compat mode for React API compatibility - **Shiki** — built-in code highlighting, theme set from active color scheme - **TypeScript** — strict mode via `astro/tsconfigs/strict` ## Commands - `pnpm dev` — runs Astro dev server (port 4321) and doc-history-server (port 4322) concurrently via `run-p` (predev kills stale processes) - `pnpm dev:astro` — Astro dev server only (port 4321) - `pnpm dev:history` — doc history API server only (port 4322) - `pnpm dev:stable` — alternative build-then-serve dev mode (avoids HMR crashes on content file add/remove) - `pnpm dev:network` — Astro dev server with `--host 0.0.0.0` for LAN access - `pnpm build` — static HTML export to `dist/` - `pnpm check` — Astro type checking - `pnpm b4push` — pre-push validation: format check → template drift check → tags audit (`tags:audit --ci`) → design token lint → typecheck → build → link check → E2E tests ## Key Directories ``` packages/ ├── ai-chat-worker/ # CF Worker for AI chat API ├── md-plugins/ # Shared remark/rehype plugins (link resolver, admonitions, etc.) ├── search-worker/ # CF Worker for search API ├── doc-history-server/ # Doc history REST API + CLI generator └── create-zudo-doc/ # CLI scaffold tool src/ ├── components/ # Astro + Preact components │ ├── admonitions/ # Note, Tip, Info, Warning, Danger │ └── content/ # MDX element overrides (server-rendered, no client JS) ├── config/ # Settings, color schemes ├── content/ │ ├── docs/ # English MDX content │ └── docs-ja/ # Japanese MDX content (mirrors docs/) ├── hooks/ # Preact hooks (scroll spy) ├── layouts/ # Astro layouts (doc-layout) ├── pages/ # File-based routing │ ├── docs/[...slug] # English doc routes │ └── ja/docs/[...slug] # Japanese doc routes └── styles/ └── global.css # Design tokens (@theme) & Tailwind config ``` ## Conventions ### Components: Astro vs Preact - Default to **Astro components** (`.astro`) — zero JS, server-rendered - Use **Preact islands** (`client:load`) only when client-side interactivity is needed - Preact runs in compat mode (`@astrojs/preact` with `compat: true`), so components can use React-style imports and APIs - Current Preact islands: `toc.tsx`, `mobile-toc.tsx`, `sidebar-toggle.tsx`, `sidebar-tree.tsx`, `theme-toggle.tsx`, `doc-history.tsx`, `color-tweak-panel.tsx`, `color-tweak-export-modal.tsx` - Content typography components (`src/components/content/`): Preact function components (no `client:` directive — server-rendered, zero JS) that override HTML elements in MDX via ``. Includes: headings (h2-h4), paragraph, link, strong, blockquote, lists (ul/ol), table. ### Content Collections - Schema defined in `src/content.config.ts` (Zod validation) - Uses Astro 5 `glob()` loader with configurable `base` directory from settings - Content directories: `docsDir` (default: `src/content/docs`), `docsJaDir` (default: `src/content/docs-ja`) ### Terminology: "Update docs" When we say "update docs" or "update our doc," it means updating the **showcase documentation** content in `src/content/docs/` (English) and `src/content/docs-ja/` (Japanese). Since zudo-doc is a documentation framework, its own content directories serve as the default showcase. These are the pages visible when running `pnpm dev`. ### i18n - English (default): `/docs/...` — content in `docsDir` (default: `src/content/docs`) - Japanese: `/ja/docs/...` — content in `docsJaDir` (default: `src/content/docs-ja`) - Configured in `astro.config.ts` with `prefixDefaultLocale: false` - Japanese docs should mirror the English directory structure - **Bilingual rule**: When creating or updating any doc page, update both EN and JA versions. Keep code blocks identical -- only translate prose. - **Exception**: Pages with `generated: true` in frontmatter do not require Japanese translations. ## Writing Docs ### Frontmatter Fields Schema in `src/content.config.ts`. Required: `title` (string). Key optional fields: | Field | Type | Description | |---|---|---| | `sidebar_position` | number | Sort order within category (lower = higher). Always set this | | `description` | string | Subtitle below the title | | `sidebar_label` | string | Custom sidebar text (overrides `title`) | | `tags` | string[] | Cross-category grouping | | `draft` | boolean | Exclude from build entirely | | `unlisted` | boolean | Built but hidden from sidebar/nav | | `generated` | boolean | Build-time generated content (skip translation) | | `hide_sidebar` | boolean | Hide left sidebar | | `hide_toc` | boolean | Hide right-side TOC | ### Content Rules - **No h1 in content**: The frontmatter `title` renders as the page h1. Start with `## h2`. - **Always set `sidebar_position`**: Without it, pages sort alphabetically. - **Kebab-case file names**: Use `my-article.mdx`, not `myArticle.mdx`. ### Admonitions Available in all MDX files without imports (registered globally in doc page). **Directive syntax**: `:::note[Title]` ... `:::` **JSX syntax**: ``, ``, ``, ``, `` — each accepts optional `title` prop. ### Linking Between Docs Use relative file paths with `.mdx` extension: ```markdown [Link text](./sibling-page.mdx) [Link text](../other-category/page.mdx#anchor) ``` ### Navigation Structure Navigation is filesystem-driven. Directory structure becomes sidebar navigation. - Pages ordered by `sidebar_position` (ascending) - Category index pages (`index.mdx`) control category position - `_category_.json` for category-level metadata (label, position, noPage) - Header nav defined in `src/config/settings.ts` via `headerNav` with `categoryMatch` ### Content Creation Workflow 1. Create English `.mdx` file under `src/content/docs/` with `title` and `sidebar_position` 2. Write content starting with `## h2` headings (not `# h1`) 3. Create matching Japanese file under `src/content/docs-ja/` 4. Keep code blocks and `` blocks identical -- only translate prose 5. Run `pnpm format:md` then `pnpm build` to verify ## Doc Skill (setup-doc-skill) The doc-skill (`scripts/setup-doc-skill.sh`) generates `.claude/skills//SKILL.md` and symlinks docs into it. It is gitignored -- do NOT track the generated SKILL.md in git. Run `pnpm setup:doc-skill` to regenerate. To update the skill template, edit `scripts/setup-doc-skill.sh`. This script is also the **source template** copied to downstream projects by `create-zudo-doc` when the `skillSymlinker` feature is enabled. ## Doc History Architecture Document git history is handled by a standalone package `@zudo-doc/doc-history-server` (at `packages/doc-history-server/`). It is intentionally decoupled from the Astro build pipeline so that expensive `git log --follow` calls do not block the main build. It runs in two modes: - **Server mode** (local dev) — HTTP server on port 4322, started by `pnpm dev:history`. The Astro integration at `src/integrations/doc-history.ts` proxies `/doc-history/*` requests to it. - **CLI mode** (CI) — batch-generates JSON files into `dist/doc-history/`. Used by the `build-history` CI job in parallel with the main Astro build. ### `SKIP_DOC_HISTORY` env var When `SKIP_DOC_HISTORY=1` is set, the Astro integration skips inline history generation at build time. This is the default for the CI `build-site` job — history is generated by the separate `build-history` job and merged in at deploy time. For local `pnpm build` runs, leave it unset so history is embedded inline. ## CI Pipeline Production (`main-deploy.yml`) and PR (`pr-checks.yml`) workflows use parallel build jobs: - **build-site** — shallow clone (`fetch-depth: 1`), `SKIP_DOC_HISTORY=1 pnpm build` - **build-history** — full clone (`fetch-depth: 0`), `@zudo-doc/doc-history-server generate` - **deploy/preview** — merges both artifacts, deploys to Cloudflare Pages E2E tests run with full clone and inline doc-history generation (no `SKIP_DOC_HISTORY`). ## Feature Change Checklist When adding or removing a feature from zudo-doc, update the `create-zudo-doc` generator to stay in sync: 1. **`src/config/settings.ts`** — Add/remove the setting field 2. **`packages/create-zudo-doc/src/settings-gen.ts`** — Add/remove the setting in generated output 3. **`packages/create-zudo-doc/src/features/.ts`** — Create/update feature module with injections 4. **`packages/create-zudo-doc/templates/features//files/`** — Add/remove feature-specific files 5. **`packages/create-zudo-doc/src/astro-config-gen.ts`** — Add/remove conditional imports/integrations if feature affects astro config 6. **`packages/create-zudo-doc/src/scaffold.ts`** — Add/remove dependencies in `generatePackageJson()` 7. **`packages/create-zudo-doc/src/__tests__/scaffold.test.ts`** — Update tests 8. Run `/l-update-generator` to verify no drift remains **Important**: This checklist also applies to incremental improvements (CSS token migrations, icon sizing, spacing changes, etc.) — not just new features. If you change a file that has a template counterpart, update the template too. Run `pnpm check:template-drift` to verify (note: allowlisted files like `global.css`, `header.astro`, and `doc-layout.astro` are excluded from automated checks and need manual review). ## Design Tokens & CSS See `src/CLAUDE.md` for design token system (three-tier color strategy, color rules, scheme configuration) and CSS conventions (component-first strategy, tight token strategy). --- # Admonitions > Source: /pj/zudo-doc/docs/components/admonitions Admonitions are callout blocks for highlighting important information. All five types are globally available — no imports needed. ## Note :::note This is a note — use it for general information and tips. ::: :::note[Custom Title] Notes are great for calling out important information readers shouldn't miss. ::: ```mdx :::note This is a note — use it for general information and tips. ::: :::note[Custom Title] Notes are great for calling out important information readers shouldn't miss. ::: ``` ## Tip :::tip This is a tip — use it for helpful suggestions and best practices. ::: :::tip[Pro Tip] Use keyboard shortcuts to speed up your workflow. ::: ```mdx :::tip This is a tip — use it for helpful suggestions and best practices. ::: :::tip[Pro Tip] Use keyboard shortcuts to speed up your workflow. ::: ``` ## Info :::info This is an info block — use it for additional context or background information. ::: :::info[Did You Know?] This feature was introduced in version 2.0. ::: ```mdx :::info This is an info block — use it for additional context or background information. ::: :::info[Did You Know?] This feature was introduced in version 2.0. ::: ``` ## Warning :::warning This is a warning — use it to flag potential issues or things to watch out for. ::: :::warning[Deprecation Notice] This API will be removed in the next major version. Please migrate to the new API. ::: ```mdx :::warning This is a warning — use it to flag potential issues or things to watch out for. ::: :::warning[Deprecation Notice] This API will be removed in the next major version. Please migrate to the new API. ::: ``` ## Danger :::danger This is a danger alert — use it for critical warnings about data loss or breaking changes. ::: :::danger[Breaking Change] Running this command will permanently delete all data. This action cannot be undone. ::: ```mdx :::danger This is a danger alert — use it for critical warnings about data loss or breaking changes. ::: :::danger[Breaking Change] Running this command will permanently delete all data. This action cannot be undone. ::: ``` ## Component Syntax In addition to the directive syntax shown above, you can use JSX component syntax. This is useful when you need more control or are building custom layouts. ```mdx Default note with auto-generated title. Warning with a custom title. ``` The component syntax supports the same five types (`Note`, `Tip`, `Info`, `Warning`, `Danger`) and an optional `title` prop. ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `title` | string | Type name (e.g., "Note") | Override the default title text | ## Color Reference Each admonition type uses a semantic color token for its border and title color: | Type | Color Token | Typical Color | |------|------------|---------------| | Note | accent | Blue | | Tip | success | Green | | Info | info | Cyan | | Warning | warning | Yellow | | Danger | danger | Red | --- # Components > Source: /pj/zudo-doc/docs/components Explore the components and content features available in zudo-doc. --- # Generator CLI Testing > Source: /pj/zudo-doc/docs/develop/generator-cli-testing # Generator CLI Testing The `create-zudo-doc` generator CLI scaffolds new zudo-doc projects with various feature combinations. Testing it requires verifying that each combination builds, runs, and produces the correct files and settings. Two Claude Code skills automate this workflow: | Skill | Purpose | |-------|---------| | `/l-generator-cli-tester ` | Test a single generation pattern | | `/l-run-generator-cli-whole-test` | Run all 9 patterns, fix bugs, verify everything passes | ## Test Patterns Each pattern enables a different feature combination: | Pattern | Description | |---------|-------------| | `barebone` | Everything OFF — minimal project | | `search` | Only search enabled | | `i18n` | Only i18n enabled | | `sidebar-filter` | Only sidebar filter enabled | | `claude-resources` | Only Claude Resources enabled | | `design-token-panel` | Only design token panel enabled (uses API) | | `light-dark` | Light-dark color scheme mode | | `lang-ja` | Japanese as default language | | `all-features` | Everything ON | ## Running Tests ### Full test suite Run all 9 patterns end-to-end, automatically fix any failures, and verify: ``` /l-run-generator-cli-whole-test ``` With headless browser rendering checks: ``` /l-run-generator-cli-whole-test --headless ``` ### Single pattern Test one pattern in isolation: ``` /l-generator-cli-tester barebone /l-generator-cli-tester all-features --headless ``` ## What Each Test Checks Each pattern goes through these steps: 1. **Scaffold** — Run the generator CLI (or programmatic API) with pattern-specific flags 2. **Install** — `pnpm install` in the generated project 3. **Build** — `pnpm build` to verify static export succeeds 4. **Dev server** — Start `pnpm dev`, wait 8 seconds, verify the process is still running 5. **File verification** — Check expected files are present/absent based on enabled features 6. **Settings verification** — Read the generated `settings.ts` and confirm correct values 7. **Showcase comparison** — Compare generated code against the main zudo-doc showcase 8. **Headless browser** (with `--headless`) — Render pages in a real browser, check for JS errors, verify visual elements (search icon, language switcher, theme toggle, etc.) ## The Bug-Fix Workflow The `/l-run-generator-cli-whole-test` skill has a structured bug-fix phase: 1. **Phase 1** — Run all 9 patterns and collect results 2. **Phase 2** — For each failure: diagnose which step failed, read the relevant generator source file, apply a minimal fix, rebuild the CLI and re-test the failing pattern, then commit each fix individually 3. **Phase 3** — Re-run all 9 patterns from scratch to ensure no regressions 4. **Phase 4** — Output a summary report ### Common failure categories | Failure | Likely cause | Fix location | |---------|-------------|--------------| | Missing module at build time | Dependency not in generated `package.json` | `scaffold.ts` — `generatePackageJson()` | | Import not found in astro.config | Feature integration not included | `astro-config-gen.ts` | | Type error in settings.ts | Settings field missing or wrong | `settings-gen.ts` | | Feature component missing | Feature module not copying files or injection anchor mismatch | `features/*.ts` or `templates/features/*/files/` | | Anchor remnant in output | Injection anchor not cleaned up | `compose.ts` — `cleanAnchors()` | ## Key Generator Files | File | Role | |------|------| | `packages/create-zudo-doc/src/scaffold.ts` | Orchestrates pipeline: copy base, generate configs, compose features | | `packages/create-zudo-doc/src/compose.ts` | Composition engine: injection system, anchor cleanup, feature resolution | | `packages/create-zudo-doc/src/features/*.ts` | Feature modules defining injections for each optional feature | | `packages/create-zudo-doc/src/astro-config-gen.ts` | Programmatic `astro.config.ts` generator | | `packages/create-zudo-doc/src/content-config-gen.ts` | Programmatic `content.config.ts` generator | | `packages/create-zudo-doc/src/settings-gen.ts` | Generates `settings.ts` | | `packages/create-zudo-doc/src/constants.ts` | Feature definitions, color scheme lists | | `packages/create-zudo-doc/src/cli.ts` | CLI argument parsing | | `packages/create-zudo-doc/src/api.ts` | Programmatic API | ## Notes - Test directories are created under `__inbox/` (gitignored) to avoid polluting the repo - The `barebone` pattern is the baseline — if it fails, fix it before testing others - `designTokenPanel` is exposed as the `--design-token-panel` CLI flag, but patterns like `design-token-panel` and `all-features` drive it through the programmatic API for consistency with the other test patterns - The `--headless` flag adds browser-level rendering checks on top of process-level checks - Always rebuild the CLI (`pnpm build` in `packages/create-zudo-doc`) before testing and after each fix --- # Installation > Source: /pj/zudo-doc/docs/getting-started/installation ## Prerequisites - **Node.js 18+** — required for Astro 6 - **pnpm** — recommended package manager You can also use npm, yarn, or bun, but this guide uses pnpm for all examples. ## Create a New Project The fastest way to get started is with the `create-zudo-doc` CLI. It scaffolds a new project with an interactive setup wizard. ```bash pnpm create zudo-doc ``` Or with other package managers: ```bash npm create zudo-doc yarn create zudo-doc bunx create-zudo-doc ``` For non-interactive usage (CI, automation, AI agents), use `--yes` to accept defaults or pass flags directly: ```bash pnpm create zudo-doc my-docs --yes pnpm create zudo-doc my-docs --lang ja --scheme Dracula --no-i18n --pm pnpm --install ``` See the [CLI reference](/docs/reference/create-zudo-doc) for all available flags. The CLI walks you through the following options: ### Project Name Enter a name for your project directory (default: `my-docs`). ### Default Language Choose the default language for your documentation site. Supported languages include English, Japanese, Chinese (Simplified/Traditional), Korean, Spanish, French, German, and Portuguese. ### Color Scheme Mode Choose how your site handles color schemes: - **Light & Dark (toggle)** — users can switch between light and dark themes. Pick from pre-configured pairings (GitHub, Catppuccin, Solarized, Rosé Pine, Atom One, Everforest, Gruvbox, Ayu) or choose light and dark schemes individually. - **Single scheme** — one fixed color scheme for the entire site. 40 schemes are available, including Dracula, Nord, TokyoNight, and more. When using light/dark mode, you can also set the default mode (light or dark) and whether to respect the user's system color scheme preference. ### Features Select which optional features to include: | Feature | Default | Description | |---------|---------|-------------| | [i18n (multi-language)](/docs/guides/i18n/) | Off | Add a secondary language with mirrored content directories | | [Pagefind search](/docs/guides/search/) | On | Full-text search across all documentation | | [Sidebar filter](/docs/guides/sidebar-filter/) | On | Real-time filtering of sidebar navigation items | | [Claude Resources](/docs/guides/claude-resources/) | Off | Auto-generated documentation for Claude Code components | | [Color scheme preview](/docs/guides/color-scheme-preview/) | Off | Browse 50+ preset color schemes via the Color Tweak Panel | ### Package Manager Choose your preferred package manager: pnpm (recommended), npm, yarn, or bun. After scaffolding, the CLI will ask whether to install dependencies for you. The installer generates a `src/config/settings.ts` file with your choices. You can change these settings at any time after project creation. ## Alternative: Clone the Repository You can also start by cloning the full repository directly: ```bash git clone https://github.com/zudolab/zudo-doc.git my-docs cd my-docs pnpm install ``` Cloning the repository gives you the complete project including the documentation source and all features enabled. Use this approach if you want to explore the full codebase or contribute to zudo-doc itself. ## Development ```bash pnpm dev ``` The dev server starts on port 4321 with Vite for instant hot module replacement. Make sure port 4321 is available. If another process is using it, Astro will automatically pick the next available port. ## Build ```bash pnpm build ``` This generates static HTML in the `dist/` directory. You can deploy it to any static hosting service. ## Type Checking ```bash pnpm check ``` Runs Astro's built-in type checker with strict TypeScript mode. Never commit the `dist/` directory to source control. It is already excluded via `.gitignore`. ## Project Structure After installation, your project looks like this: ``` src/ ├── components/ # Astro + Preact components │ └── admonitions/ # Note, Tip, Info, Warning, Danger ├── config/ │ ├── settings.ts # Site name, active color scheme │ ├── color-schemes.ts # All available color presets │ └── color-scheme-utils.ts ├── content/ │ ├── docs/ # English MDX content │ └── docs-ja/ # Japanese MDX content ├── layouts/ # Page layouts ├── pages/ # File-based routing └── styles/ └── global.css # Design tokens & Tailwind config ``` See the [Writing Docs](/docs/getting-started/writing-docs) guide for how to create and organize your documentation pages. --- # Frontmatter > Source: /pj/zudo-doc/docs/guides/frontmatter Every MDX file in zudo-doc starts with a YAML frontmatter block delimited by `---`. This page documents all available fields. ## Complete Example ```mdx --- title: My Documentation Page description: A comprehensive guide to something important. sidebar_position: 3 sidebar_label: My Page tags: [tutorial, setup] --- Your content here. ``` ## Minimal Example Only `title` is required: ```mdx --- title: Quick Start --- ``` ## Field Overview | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `title` | string | Yes | -- | Page title shown in heading, sidebar, and browser tab | | `description` | string | No | -- | Subtitle shown below the title | | `sidebar_position` | number | No | `999` | Sort order within the sidebar category | | `sidebar_label` | string | No | Value of `title` | Override label shown in sidebar | | `category` | string | No | Directory name | Reserved for future use | | `search_exclude` | boolean | No | `false` | Exclude the page from search indexing | | `tags` | string[] | No | -- | Tags for cross-cutting navigation | | `draft` | boolean | No | `false` | Exclude from production build (visible in dev) | | `unlisted` | boolean | No | `false` | Built but hidden from sidebar, search, and search engines | | `hide_sidebar` | boolean | No | `false` | Hide the left sidebar, center content in a narrower container | | `hide_toc` | boolean | No | `false` | Hide the right-side table of contents | | `doc_history` | boolean | No | Auto | Override doc-history button visibility (auto: hidden on `*/index` pages, shown on detail pages) | | `standalone` | boolean | No | `false` | Hidden from sidebar nav but still indexed (unlike `unlisted`) | | `slug` | string | No | Derived from file path | Custom URL slug override | | `pagination_next` | string \| null | No | Auto | Override next page link, set to `null` to hide | | `pagination_prev` | string \| null | No | Auto | Override previous page link, set to `null` to hide | ## Fields ### `title` - **Type:** `string` - **Required:** Yes The page title. It is displayed as the `h1` heading on the page, in the sidebar navigation, in the browser tab, and in search results. ```mdx --- title: Getting Started with zudo-doc --- ``` ### `description` - **Type:** `string` - **Required:** No A short description or subtitle for the page. Shown below the title on the rendered page. Also useful for SEO meta tags. ```mdx --- title: Installation description: How to install and set up zudo-doc for your project. --- ``` ### `sidebar_position` - **Type:** `number` - **Required:** No - **Default:** `999` (appears at the end) Controls the sort order of the page within its sidebar category. Lower numbers appear first. ```mdx --- title: Introduction sidebar_position: 1 --- ``` For category index pages (`index.mdx`), the `sidebar_position` also controls where the entire category appears relative to other categories in the sidebar. ### `sidebar_label` - **Type:** `string` - **Required:** No - **Default:** Value of `title` Overrides the label displayed in the sidebar navigation. Use this when you want a shorter or different label than the full page title. ```mdx --- title: Configuring Color Schemes and Themes sidebar_label: Color Schemes --- ``` ### `category` - **Type:** `string` - **Required:** No - **Default:** Derived from the directory structure (first directory segment) Reserved for future use. This field is defined in the content schema but is not currently used by the sidebar or routing logic. Categories are always derived from the directory structure. Organize pages into categories by placing them in directories under `src/content/docs/`. The directory name becomes the category name. ### `search_exclude` - **Type:** `boolean` - **Required:** No - **Default:** `false` When set to `true`, excludes the page from the search index. Useful for internal pages, changelogs, or imported content that should not appear in search results. ```mdx --- title: Internal Changelog search_exclude: true --- ``` ### `tags` - **Type:** `string[]` - **Required:** No Assigns tags to the page for cross-cutting navigation. Tags are displayed as clickable badges on the page, and tag index pages are auto-generated at `/docs/tags/` and `/docs/tags/[tag]`. See the [Tags guide](./tags.mdx) for a full walkthrough. Tagged pages surface in three places: 1. **Bottom of each doc** — a row of tag badges appears on every tagged page, just above the previous/next pager. 2. **Homepage** — an "All tags" section lists every tag used in the docs, linking to individual tag index pages. 3. **Tag index** — a shared index at [/docs/tags/](/docs/tags/) groups all pages by tag. ```mdx --- title: Deploying to Netlify tags: [deployment, netlify, hosting] --- ``` Tags are useful for grouping related pages across different sidebar categories. ### `draft` - **Type:** `boolean` - **Required:** No - **Default:** `false` When set to `true`, the page is excluded from the production build entirely. Draft pages are still visible during development (`pnpm dev`) for preview purposes. ```mdx --- title: Upcoming Feature draft: true --- ``` ### `unlisted` - **Type:** `boolean` - **Required:** No - **Default:** `false` When set to `true`, the page is built and accessible via its URL, but is hidden from the sidebar navigation, search index, and search engines (via `noindex` meta tag). ```mdx --- title: Internal Notes unlisted: true --- ``` ### `hide_sidebar` - **Type:** `boolean` - **Required:** No - **Default:** `false` When set to `true`, the left sidebar is hidden on desktop and the content is centered in a narrower container. The mobile hamburger menu still provides access to navigation. Useful for landing pages, standalone articles, or any page where a full-width reading experience is preferred. ```mdx --- title: About This Project hide_sidebar: true --- ``` See the [live demo](/docs/guides/layout-demos/hide-sidebar/) to experience this layout option in action. ### `hide_toc` - **Type:** `boolean` - **Required:** No - **Default:** `false` When set to `true`, the right-side table of contents (section navigation) and the mobile TOC are both hidden. The main content area expands to fill the available width. ```mdx --- title: Changelog hide_toc: true --- ``` See the [live demo](/docs/guides/layout-demos/hide-toc/) to experience this layout option in action. Combine `hide_sidebar` and `hide_toc` to create a clean, centered reading layout with no navigation panels: ```mdx --- title: About hide_sidebar: true hide_toc: true --- ``` See the [live demo](/docs/guides/layout-demos/hide-both/) to see both options combined. ### `standalone` - **Type:** `boolean` - **Required:** No - **Default:** `false` When set to `true`, the page is hidden from sidebar navigation and site index but remains accessible via its URL and is still indexed by search engines. Unlike `unlisted`, standalone pages do not get a `noindex` meta tag. ```mdx --- title: Terms of Service standalone: true --- ``` Use `standalone` for pages that should be publicly accessible but don't belong in the documentation navigation (e.g., legal pages, special landing pages). Use `unlisted` when you also want to prevent search engine indexing. ### `slug` - **Type:** `string` - **Required:** No - **Default:** Derived from the file path Overrides the URL slug for the page. Use this when you want a different URL than what the file path would produce. ```mdx --- title: Getting Started with zudo-doc slug: quickstart --- ``` This page would be accessible at `/docs/quickstart` instead of the default path derived from its file location. ### `pagination_next` - **Type:** `string | null` - **Required:** No - **Default:** Automatically determined by sidebar order Overrides the "Next" page link shown at the bottom of the document. Set to a document path (e.g., `"guides/configuration"`) to link to a specific page, or set to `null` to hide the next link entirely. ```mdx --- title: Final Step pagination_next: null --- ``` ### `pagination_prev` - **Type:** `string | null` - **Required:** No - **Default:** Automatically determined by sidebar order Overrides the "Previous" page link shown at the bottom of the document. Set to a document path (e.g., `"getting-started/introduction"`) to link to a specific page, or set to `null` to hide the previous link entirely. ```mdx --- title: Getting Started pagination_prev: null --- ``` --- # Demo: Hide Table of Contents > Source: /pj/zudo-doc/docs/guides/layout-demos/hide-toc This page demonstrates the `hide_toc: true` frontmatter option. The right-side table of contents is hidden, and the main content area expands to fill the available width. ## What's Happening The table of contents is hidden on this page. Here is the frontmatter used: ```yaml --- title: "Demo: Hide Table of Contents" hide_sidebar: false hide_toc: true --- ``` ## Sidebar Still Visible The sidebar on the left side is still visible for navigation. Only the right-side table of contents is affected. ## Desktop and Mobile Both the desktop table of contents (right sidebar) and the mobile table of contents are hidden when `hide_toc: true` is set. ### Subsection Example This subsection exists to demonstrate that even though the page has headings and subsections, no table of contents is generated. ## When to Use Use `hide_toc: true` for pages with few headings, short content, or when the extra content width is more valuable than section navigation. See the [Frontmatter reference](/docs/guides/frontmatter/#hide_toc) for full documentation. ## See Also - [Hide Sidebar](/docs/guides/layout-demos/hide-sidebar/) — hides the left sidebar - [Hide Sidebar & Table of Contents](/docs/guides/layout-demos/hide-both/) — hides both panels --- # Component First Strategy > Source: /pj/zudo-doc/docs/reference/component-first zudo-doc follows a **component-first strategy**: always express UI as components with Tailwind utility classes. Never create custom CSS class names with separate stylesheets. ## The Problem When projects use a utility CSS framework alongside a component framework, developers frequently fall back to traditional CSS patterns. Instead of composing utility classes inside components, they create custom CSS class names — `.profile-card`, `.btn-primary`, `.sidebar-nav` — with separate stylesheets or CSS modules. This creates a fragmented codebase: - Some components use Tailwind utilities inline - Others introduce custom CSS classes with BEM naming or CSS modules - Some mix both approaches in the same file For AI agents, this is a particularly common failure mode. Given a task like "build a profile card," an agent will often generate a `.profile-card` class with a CSS module — the pattern seen most often in training data. Over time, the codebase becomes a patchwork of conflicting styling approaches. ## The Rule **The component itself is the abstraction.** CSS class names like `.card` or `.btn-primary` are unnecessary — the component handles encapsulation, and utility classes handle styling. - Need a card? Create a `` component with utility classes - Need a button variant? Create a `` component - Need a layout pattern? Create a `` component ## In zudo-doc zudo-doc uses **Astro components** (`.astro`) and **Preact islands** (`.tsx`) with **Tailwind CSS v4** utilities. The same rule applies to both: ### Astro Components Most UI is server-rendered Astro — zero JavaScript, utility classes inline: ```astro ``` No `.footer` class. No `footer.module.css`. The component **is** the abstraction. ### Preact Islands Interactive components use Preact with `client:load`, same utility approach: ```tsx // src/components/sidebar-toggle.tsx const [open, setOpen] = useState(false); return ( setOpen(!open)} > {label} ); } ``` ### Anti-Pattern Do **not** create CSS class names in a zudo-doc project: ```css /* WRONG — don't create custom CSS classes */ .profile-card { display: flex; gap: 1rem; padding: 1.5rem; } .profile-card__name { font-size: 1.25rem; font-weight: 600; } ``` ```astro {name} ``` Instead: ```astro {name} ``` ## Component Variants via Props Instead of CSS modifier classes (`.btn--primary`, `.btn--secondary`), use component props: ```tsx function Button({ variant = "primary", children }) { const styles = { primary: "bg-accent text-bg hover:bg-accent-hover", secondary: "bg-surface text-fg border border-muted", }; return ( {children} ); } ``` Usage: ```tsx Save Cancel ``` No `.btn-primary` class to maintain. The `variant` prop is type-safe, auto-completable, and self-documenting. ## Component Composition Complex layouts are built by composing smaller components — not by adding more CSS: ```astro {users.map((user) => ( {user.name} {user.email} ))} ``` Each piece — ``, the list layout — is a component. No `.user-list__item` or `.user-list__avatar` class names needed. ## Using zudo-doc's Design Tokens Always use project tokens instead of arbitrary values: ```astro ``` See [Design System](/docs/reference/design-system) for available spacing, typography, and color tokens. ## When Custom CSS Is Acceptable The **only** place custom CSS belongs in zudo-doc is in `src/styles/global.css`: - **Content typography** — the `.zd-content` class styles rendered MDX elements (headings, paragraphs, lists) because these elements are generated by the MDX pipeline, not authored as components - **Design token definitions** — the `@theme` block that registers Tailwind tokens Everything else — every component, every layout, every UI element — uses utility classes directly. ## Rules Summary 1. **Always create components** — not CSS classes 2. **Use utility classes directly** in component markup 3. **Never create CSS module files** or custom class names 4. **Use props for variants** — not CSS modifiers 5. **Compose components** — build complex UI from smaller components, not from more CSS 6. **Use project tokens** — `text-fg`, `bg-surface`, `p-hsp-md`, not arbitrary values --- # Setup Preset Generator > Source: /pj/zudo-doc/docs/getting-started/setup-preset-generator Use this interactive tool to configure your zudo-doc project options and generate a setup preset. You can copy the output as JSON for the programmatic API, or as a CLI command for terminal usage. ## Header right items The "Header right items" section configures `settings.headerRightItems` — the right-side cluster of the site header (theme toggle, language switcher, version switcher, GitHub link, and the Design Token Panel / AI Chat triggers). Toggle each item on or off, and use the up/down buttons to change render order. Two item kinds — `link` (custom external link) and `html` (raw HTML) — are intentionally not exposed in the UI. Add those by editing `settings.ts` directly after scaffolding. See [Header Right Items](../guides/header-right-items.mdx) for the full discriminated-union schema and examples. ## Newly available features The Features section now includes a few additions worth calling out: - **Image enlarge** — click-to-zoom for content images. Enabled by default. - **Tag governance** — vocabulary-rule audit for tags. Enabled by default. See [Tag governance](../guides/tag-governance.mdx). - **Body foot util area** — slot at the bottom of the article body for util widgets like a tag list or feedback link. See [Body foot util area](../guides/body-foot-util-area.mdx). - **Footer taglist** — render the global tag cloud in the site footer. See [Footer taglist](../guides/footer-taglist.mdx). - **Design Token Panel** — renamed from "Color tweak panel". The setting key is `designTokenPanel`; the legacy `colorTweakPanel` still works as an alias. --- # /e2e/CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/e2e **Path:** `e2e/CLAUDE.md` # E2E Tests ## Architecture 5 Playwright fixtures, each with its own port, build, and `settings.ts`: | Fixture | Port | Purpose | |---|---|---| | sidebar | 4500 | Sidebar persistence, filter | | i18n | 4501 | Locale fallback, translation | | theme | 4502 | Light/dark toggle, hydration | | smoke | 4503 | General features (search, TOC, code blocks, mermaid, doc history, etc.) | | versioning | 4504 | Version switcher, banners | Configured in `playwright.config.ts`. Each fixture runs `astro preview` on its port. ## Adding Tests **No new fixture needed in most cases.** The `testMatch` pattern is `${name}*.spec.ts`, so: - `smoke-search.spec.ts` automatically runs against the smoke fixture - `sidebar-filter.spec.ts` automatically runs against the sidebar fixture To add a test: create `e2e/{fixture-name}-{feature}.spec.ts`. No config changes needed. To add content for tests: add MDX files to the fixture's `src/content/docs/` directory, then enable any needed settings in its `src/config/settings.ts`. ## Two Test Patterns **Static HTML tests** (no browser needed) — read pre-built `dist/` with `readFileSync`: ```typescript const html = readDistFile("docs/some-page/index.html"); expect(html).toContain("expected string"); ``` **Browser tests** — use Playwright `page` fixture for interactive features: ```typescript test("feature works", async ({ page }) => { await page.goto("/docs/some-page"); await expect(page.locator('[aria-label="Search"]')).toBeVisible(); }); ``` ## Fixture Setup Pipeline (`setup-fixtures.sh`) Each fixture shares framework source from repo root via **symlinks**, but has its own content and settings: - **Symlinked**: `components/`, `hooks/`, `integrations/`, `layouts/`, `plugins/`, `styles/`, `types/`, `utils/`, `node_modules/` - **Copied** (has relative imports): `astro.config.ts`, `content.config.ts`, `src/config/*.ts` (except `settings.ts`) - **Fixture-specific**: `src/config/settings.ts`, `src/content/docs/` All fixtures are pre-built sequentially before Playwright runs (`astro build`), then Playwright only runs `astro preview`. The smoke fixture also initializes a git repo for doc-history testing (2 commits). ## Commands ```bash pnpm test:e2e # Full suite (setup + all tests) pnpm test:e2e:ci # CI suite (skips @local-only tests) npx playwright test e2e/smoke-search.spec.ts --project smoke # Single test file npx playwright test --project smoke # All tests for one fixture ``` ## `@local-only` Tag Tests that are too specific for CI (flaky DOM operations, timing-sensitive UI checks) can be tagged `@local-only` in the test title: ```typescript test("HSL picker opens from color swatch @local-only", async ({ page }) => { ... }); ``` - `pnpm test:e2e` — runs everything (local dev, `b4push`) - `pnpm test:e2e:ci` — skips `@local-only` tests (CI workflows) ## Sidebar Test Helper `e2e/sidebar-helpers.ts` exports `desktopSidebar(page)` and `waitForSidebarHydration(page)` for tests that interact with the sidebar Preact island. --- # Tabs > Source: /pj/zudo-doc/docs/components/tabs Use `` and `` to create tabbed content panels. Both components are globally available — no imports needed. ## Basic Usage ```bash npm install zudo-doc ``` ```bash pnpm add zudo-doc ``` ```bash yarn add zudo-doc ``` ````mdx ```bash npm install zudo-doc ``` ```bash pnpm add zudo-doc ``` ```bash yarn add zudo-doc ``` ```` The `default` prop on `TabItem` sets which tab is initially active. If omitted, the first tab is shown. ## Synced Tabs Use `groupId` to sync tab selection across multiple tab groups on the same page. The selected tab is persisted in localStorage, so it carries across page navigations. ```js console.log("Hello"); ``` ```ts console.log("Hello" as string); ``` ```js const sum = (a, b) => a + b; ``` ```ts const sum = (a: number, b: number): number => a + b; ``` ````mdx ```js console.log("Hello"); ``` ```ts console.log("Hello" as string); ``` ```js const sum = (a, b) => a + b; ``` ```ts const sum = (a: number, b: number): number => a + b; ``` ```` ## Tabs Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `groupId` | string | — | Sync tab selection across groups with the same `groupId` (persisted in localStorage) | ## TabItem Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `label` | string | (required) | Tab button text | | `value` | string | Value of `label` | Unique identifier for the tab | | `default` | boolean | `false` | Set as the initially active tab | --- # Link Checker > Source: /pj/zudo-doc/docs/develop/link-checker # Link Checker zudo-doc includes a built-in link checker (`scripts/check-links.js`) that runs two types of validation after the site is built. ## What It Checks ### Mode 1: Built HTML scan Scans all `.html` files in `dist/` for internal `` links and verifies each target exists on disk. It handles: - Base path stripping (e.g., `/pj/zudo-doc/docs/foo` → `docs/foo`) - Trailing slash resolution (`docs/foo/` → `docs/foo/index.html`) - Extension resolution (`docs/foo` → `docs/foo/index.html` or `docs/foo.html`) - Query string and fragment stripping before resolution - Relative link resolution against the file's directory - Caching resolved paths for performance Links that are skipped (not checked): - External URLs (`https://`, `http://`) - Anchor-only links (`#section`) - `mailto:`, `javascript:`, `data:`, `tel:` URIs - Versioned docs links (`/v/*/`) — version content may be incomplete ### Mode 2: MDX source scan Scans all `.mdx` and `.md` files in the configured content directories for absolute links that bypass the base path. This catches links like: ```mdx [guide](/docs/guides/foo) link [guide](./foo.mdx) [guide](../other/bar.mdx) ``` When a base path is configured (e.g., `/pj/zudo-doc/`), absolute links starting with `/docs/` or `/ja/docs/` will break because they're missing the base path prefix. The MDX source scan catches these before they become broken links in production. Links inside fenced code blocks are ignored. ## Running the Link Checker ### Standalone Build the site first, then run the checker: ```bash pnpm build && pnpm check:links ``` ### With `--strict` flag By default, the checker exits with code 0 even when issues are found (non-strict mode). Use `--strict` to fail on issues: ```bash pnpm check:links --strict ``` ### As part of b4push The link checker runs as Step 4 of 5 in the pre-push validation (`pnpm b4push`): 1. Format check 2. Type checking 3. Build 4. **Link check** 5. E2E tests ### In CI The link checker runs in the `build-site` job of the PR checks workflow (`pr-checks.yml`), immediately after `pnpm build`. ## Output When no issues are found: ``` Checking links (base: /pj/zudo-doc/)... ✓ No broken links or absolute path issues found ``` When issues are found: ``` Checking links (base: /pj/zudo-doc/)... === Broken Links in Built HTML === dist/docs/page/index.html:42 /pj/zudo-doc/docs/missing-page === Absolute Links Bypassing Base Path (MDX Source) === src/content/docs/guides/test.mdx:15 /docs/guides/foo ✗ Found 1 broken link and 1 absolute path warning ``` Each entry shows the file path, line number, and the problematic href. ## Configuration The link checker reads its configuration from `src/config/settings.ts`: - **`base`** — The site's base path, used to strip prefixes when resolving links - **`docsDir`** — Primary content directory for MDX source scanning - **`locales`** — Locale configurations with `dir` fields pointing to additional content directories for MDX source scanning (e.g., `locales: { ja: { dir: "src/content/docs-ja" } }`) --- # Writing Docs > Source: /pj/zudo-doc/docs/getting-started/writing-docs ## Creating a Document Create an `.mdx` file in `src/content/docs/`. Use frontmatter for metadata: ```mdx --- title: My Page description: A brief summary of this page. sidebar_position: 1 --- Your content here. ``` ### Frontmatter Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | Yes | Page title (shown in sidebar and header) | | `description` | string | No | Subtitle shown below the title | | `sidebar_position` | number | No | Sort order within category (lower = first) | | `sidebar_label` | string | No | Override sidebar display label | Do not use `# h1` headings in your document content. The frontmatter `title` is automatically rendered as the page title (h1). Starting your content with `## h2` headings instead. See the [Frontmatter guide](../guides/frontmatter.mdx) for the complete reference of all available fields. ## Directory Structure Organize docs into categories using directories: ``` src/content/docs/ getting-started/ introduction.mdx # sidebar_position: 1 installation.mdx # sidebar_position: 2 writing-docs.mdx # sidebar_position: 3 guides/ configuration.mdx # sidebar_position: 1 sidebar.mdx # sidebar_position: 2 ``` Each directory becomes a collapsible sidebar category automatically. The category name is derived from the directory name (kebab-case converted to Title Case). ## Linking Between Documents You can link to other documents using **relative file paths**: ```mdx [Installation guide](./installation.mdx) [Frontmatter reference](../guides/frontmatter.mdx) [Back to index](./index.mdx) ``` These relative paths are automatically resolved to the correct URLs at build time. The `.md`/`.mdx` extension is required for the link resolver to work — it distinguishes file links from URL links. You can also include anchors and query strings: ```mdx [Frontmatter fields](../guides/frontmatter.mdx#required-fields) ``` Relative links are validated during build. If a linked file doesn't exist, you'll see a warning in the build output. This helps catch broken links early. For external links or links to non-document pages, use regular URLs: ```mdx [Astro docs](https://docs.astro.build) [API reference](/api/v1) ``` ## Admonitions zudo-doc supports Docusaurus-style admonitions. They are registered globally — no imports needed: This is a **note** — use it for general information that readers should be aware of. This is a **tip** — use it for helpful suggestions and best practices. This is an **info** block — use it for additional context or background information. This is a **warning** — use it to flag potential issues or things to watch out for. This is a **danger** alert — use it for critical warnings about data loss or breaking changes. ### Custom Titles You can provide a custom title to any admonition using the `title` prop. ### Admonition Syntax Two syntaxes are supported. No imports needed for either. **Directive syntax** (recommended for content authors): ```mdx :::note[Optional Title] Content here. ::: ``` **JSX component syntax**: ```mdx Default note with auto-generated title. Warning with a custom title. ``` Each admonition type maps to a palette slot for its border and title color: | Type | Palette Slot | Typical Color | |------|-------------|---------------| | Note | p4 | Blue | | Tip | p2 | Green | | Info | p6 | Cyan | | Warning | p3 | Yellow | | Danger | p1 | Red | For a complete list of all available components including admonitions, code blocks, and more, see the [Components](/docs/components/) reference. ## i18n (Internationalization) zudo-doc supports English and Japanese out of the box. Japanese docs mirror the English directory structure in `src/content/docs-ja/`. See the [i18n guide](/docs/guides/i18n) for detailed instructions on managing translations and language routing. ## MDX Features You can use Preact components in your documentation. Interactive components hydrate as Astro islands — the rest ships as pure HTML with zero JavaScript. ```mdx ``` Use `client:load` for components that need interactivity. Omit it for components that only render static HTML. ## Navigation zudo-doc automatically generates: - **Sidebar** — collapsible categories with items sorted by `sidebar_position` - **Table of Contents** — right sidebar with h2–h4 headings (visible on wide screens) - **Prev/Next links** — bottom navigation between docs - **Breadcrumbs** — category path shown above the title --- # Demo: Hide Sidebar & Table of Contents > Source: /pj/zudo-doc/docs/guides/layout-demos/hide-both This page demonstrates combining `hide_sidebar: true` and `hide_toc: true`. Both navigation panels are hidden, creating a clean, centered reading layout. ## What's Happening Both the sidebar and table of contents are hidden. Here is the frontmatter used: ```yaml --- title: "Demo: Hide Sidebar & Table of Contents" hide_sidebar: true hide_toc: true --- ``` ## Clean Reading Experience With both panels removed, the content is centered on the page in a narrower container. This layout is ideal for: - Landing pages - Standalone articles - Focused reading content - About pages ## Mobile Navigation Even with both panels hidden, the mobile hamburger menu still provides full navigation access. Readers are never stranded without a way to navigate. ### Subsection Example This subsection shows that headings still render normally — they simply don't appear in a table of contents. ## When to Use Combine both options when you want maximum focus on the content itself, with no surrounding navigation panels. See the [Frontmatter reference](/docs/guides/frontmatter/#hide_sidebar) for full documentation. ## See Also - [Hide Sidebar](/docs/guides/layout-demos/hide-sidebar/) — hides just the left sidebar - [Hide Table of Contents](/docs/guides/layout-demos/hide-toc/) — hides just the right-side TOC --- # Sidebar > Source: /pj/zudo-doc/docs/guides/sidebar ## Item Ordering Pages within a category are sorted by the `sidebar_position` frontmatter field. Lower values appear first. Pages without `sidebar_position` default to `999` and appear at the end of the category, sorted alphabetically by filename. ```mdx --- title: My Page sidebar_position: 3 --- ``` Always set `sidebar_position` explicitly to control page order. Without it, pages are sorted alphabetically, which may not match your intended reading order. ## Category Ordering Categories in the sidebar are ordered by the `sidebar_position` of their **index page** (`index.mdx`). If a category has no index page or no `sidebar_position`, it falls back to alphabetical ordering. For example, to make "Getting Started" appear first and "Guides" second: ``` getting-started/index.mdx → sidebar_position: 0 guides/index.mdx → sidebar_position: 1 reference/index.mdx → sidebar_position: 2 ``` ## Category Index Pages Creating an `index.mdx` inside a directory gives the category a landing page. The category name in the sidebar becomes a clickable link to this page. A typical category index page uses the `` component to automatically list all pages in the category: ```mdx --- title: Guides sidebar_position: 1 --- Explore our guides to learn about zudo-doc features. ``` The `` component renders a grid of links to all pages in the category (excluding the index itself), sorted by `sidebar_position`. Without an `index.mdx`, the category heading is a toggle-only control — clicking it expands or collapses the category, but does not navigate anywhere. ## Default Open/Closed Categories automatically open when the current page is inside them. All other categories are collapsed by default. ## Sidebar Label Use the `sidebar_label` frontmatter field to display a different label in the sidebar than the page title. This is useful when the full title is too long for the sidebar. ```mdx --- title: Getting Started with zudo-doc Installation sidebar_label: Installation sidebar_position: 2 --- ``` The `sidebar_label` only affects the sidebar. The page title and heading still use `title`. ## Directory Structure Directories inside `src/content/docs/` become sidebar categories. The directory name is converted from kebab-case to Title Case automatically. ``` src/content/docs/ getting-started/ → "Getting Started" index.mdx → category index (sidebar_position: 0) introduction.mdx → sidebar_position: 1 installation.mdx → sidebar_position: 2 writing-docs.mdx → sidebar_position: 3 guides/ → "Guides" index.mdx → category index (sidebar_position: 1) configuration.mdx → sidebar_position: 1 frontmatter.mdx → sidebar_position: 2 ``` ## Nested Categories (Sub-Groups) Subdirectories within a category directory create nested collapsible groups in the sidebar. Each sub-directory needs a `_category_.json` file to set its label and position: ``` src/content/docs/ methodology/ index.mdx architecture/ _category_.json → { "label": "Architecture", "position": 1, "noPage": true } bem-strategy.mdx component-first.mdx design-systems/ _category_.json → { "label": "Design Systems", "position": 2, "noPage": true } tight-token.mdx two-tier-size.mdx ``` With auto-generated sidebar, this renders as **3 levels deep**: ``` Methodology (depth 0) ▸ Architecture (depth 1, collapsible) BEM Strategy (depth 2) Component First (depth 2) ▸ Design Systems (depth 1, collapsible) Tight Token (depth 2) Two-Tier Size (depth 2) ``` ### Flattening with Explicit Sidebar Config For better readability, use explicit sidebar configuration in `src/config/sidebars.ts` to promote sub-groups to the top level. This reduces nesting by one level: ```ts title="src/config/sidebars.ts" const sidebars: SidebarsConfig = { methodology: [ "methodology", { type: "category", label: "Architecture", items: [{ type: "autogenerated", dirName: "methodology/architecture" }], }, { type: "category", label: "Design Systems", items: [{ type: "autogenerated", dirName: "methodology/design-systems" }], }, ], }; ``` This renders as **2 levels**: ``` Methodology (depth 0, leaf link) ▸ Architecture (depth 0, collapsible) BEM Strategy (depth 1) Component First (depth 1) ▸ Design Systems (depth 0, collapsible) Tight Token (depth 1) Two-Tier Size (depth 1) ``` Key points: - The string `"methodology"` references the category index page as a **leaf link** (children are stripped automatically — the explicit config handles structure) - `type: "autogenerated"` with `dirName` pulls articles from a specific subdirectory - Sub-group categories render at depth 0 with bold labels and border separators - Articles render at depth 1 — one level flatter than auto-generated This pattern is recommended when categories have sub-groups. The explicit config keeps the filesystem organized while making the sidebar more readable. ## Sort Order (Ascending / Descending) By default, items within a category are sorted in ascending order (lowest `sidebar_position` first, then alphabetical). You can reverse this to descending order per category using `_category_.json`: ```json title="_category_.json" { "label": "Changelog", "position": 10, "sortOrder": "desc" } ``` When `sortOrder` is `"desc"`, items are sorted in reverse — highest position first, then reverse alphabetical. This is useful for date-based content (changelogs, release notes) where the newest items should appear at the top. You can also set `sortOrder` in `src/config/sidebars.ts` for manual sidebar configurations: ```ts { type: "category", label: "Releases", sortOrder: "desc", items: [ { type: "autogenerated" }, ], } ``` For changelog-style categories, combine `sortOrder: "desc"` with date-prefixed filenames (e.g., `2026-03-10-release.mdx`) for automatic newest-first ordering without manually setting `sidebar_position`. ## Ordering Recommendations - Set `sidebar_position` on every page for predictable ordering - Use `sidebar_position: 0` on category index pages - Use sequential numbers (1, 2, 3...) for pages within each category - Leave gaps between numbers (e.g., 10, 20, 30) if you expect to insert pages later - Use `sortOrder: "desc"` in `_category_.json` for newest-first categories --- # Color > Source: /pj/zudo-doc/docs/reference/color zudo-doc uses a **three-tier color strategy** to keep every color on the site themeable. The default Tailwind theme is not imported, and the `@theme` block resets the color namespace with `--color-*: initial` before defining project tokens — only project tokens work. This ensures switching a color scheme updates the entire site at once. ## Three-Tier Color Strategy Colors are organized into three tiers. Each tier only references the tier above it: | Tier | Name | Purpose | Defined In | |------|------|---------|------------| | 1 | **Palette** | Raw color values from the active color scheme | `ColorSchemeProvider` → `:root` | | 2 | **Semantic** | Design meaning — what each color represents | `src/styles/global.css` `@theme` | | 3 | **Component** | Scoped overrides for specific components | `.zd-content` in `global.css` | This layering means you can: - **Swap the palette** (change color scheme) → entire site updates - **Remap a semantic token** (e.g. make `accent` blue instead of cyan) → every component using `accent` updates - **Override a component token** without affecting other components ## Tier 1: The 16-Color Palette zudo-doc's color system uses a **16-color palette**. Each color scheme provides values for 16 indexed color slots plus background, foreground, and selection colors. ### Palette Index Convention Every color scheme must follow this standard index mapping. This ensures components and semantic tokens work consistently across all themes: | Index | Role | Description | |-------|------|-------------| | p0 | Dark surface | Deepest surface (code blocks, overlays) | | p1 | Danger | Red family — errors, destructive actions | | p2 | Success | Green family — confirmations, tips | | p3 | Warning | Yellow/amber — caution messages | | p4 | Info | Blue family — informational highlights | | p5 | Accent | Primary interactive color (links, CTA) | | p6 | Neutral | Slate/cyan — borders, secondary elements | | p7 | Secondary | Muted accent or secondary neutral | | p8 | Muted | Gray — borders, secondary text, comments | | p9 | Background | Page background | | p10 | Surface | Elevated surface (panels, sidebars) | | p11 | Text primary | Main body text | | p12 | Accent variant | Brighter or alternate accent | | p13 | Decorative | Purple/lavender — non-semantic decoration | | p14 | Accent hover | Hover state for interactive elements | | p15 | Text secondary | Secondary text or muted foreground | The actual hex values differ between light and dark schemes, but the **role** of each index stays the same. For example, p1 is always a danger/red color — `#dd3131` in light mode, `#da6871` in dark mode. This consistency is what makes it safe to use palette tokens directly and easy to create new schemes. ### How Palette Colors Are Injected The `ColorSchemeProvider` component (`src/components/color-scheme-provider.astro`) reads the active color scheme and injects CSS custom properties on `:root` at build time: ```css :root { --zd-bg: #282a36; --zd-fg: #f8f8f2; --zd-cursor: #f8f8f2; --zd-sel-bg: #44475a; --zd-sel-fg: #ffffff; --zd-0: #21222c; /* palette slot 0 (Black) */ --zd-1: #ff5555; /* palette slot 1 (Red) */ /* ... through --zd-15 */ } ``` These `--zd-*` properties are the source of truth. Everything downstream — semantic tokens, component tokens, Tailwind utilities — resolves back to them. ## Tier 2: Semantic Tokens In `src/styles/global.css`, the `@theme` block maps palette properties into Tailwind-compatible tokens with design meaning: ```css @theme { --color-*: initial; /* reset ALL Tailwind defaults */ /* Base */ --color-bg: var(--zd-bg); --color-fg: var(--zd-fg); --color-sel-bg: var(--zd-sel-bg); --color-sel-fg: var(--zd-sel-fg); /* Raw palette access (p0–p15) */ --color-p0: var(--zd-0); --color-p1: var(--zd-1); /* ... through --color-p15 */ /* Semantic aliases */ --color-surface: var(--zd-surface); --color-muted: var(--zd-muted); --color-accent: var(--zd-accent); --color-accent-hover: var(--zd-accent-hover); --color-code-bg: var(--zd-code-bg); --color-code-fg: var(--zd-code-fg); --color-success: var(--zd-success); --color-danger: var(--zd-danger); --color-warning: var(--zd-warning); --color-info: var(--zd-info); /* Search highlight (dedicated, live-editable in Design Token Panel) */ --color-matched-keyword-bg: var(--zd-matched-keyword-bg); --color-matched-keyword-fg: var(--zd-matched-keyword-fg); } ``` Once registered in `@theme`, these become standard Tailwind utility classes: `bg-surface`, `text-accent`, `border-muted`, etc. ### Semantic Token Reference | Token | Default Palette Slot | Usage | |-------|---------------------|-------| | `bg` | `--zd-bg` (p9) | Page background | | `fg` | `--zd-fg` (p11) | Primary text | | `surface` | `p0` (Dark surface) | Panel/sidebar surfaces | | `muted` | `p8` (Muted) | Muted text, borders, comments | | `accent` | `p5` (Accent) | Links, active states, CTA | | `accent-hover` | `p14` (Accent hover) | Hover state for accent | | `sel-bg` | `--zd-sel-bg` | Selection background | | `sel-fg` | `--zd-sel-fg` | Selection foreground | | `code-bg` | `p10` (Surface) | Code block background | | `code-fg` | `p11` (Text primary) | Inline code text | | `success` | `p2` (Success) | Success states, confirmations | | `danger` | `p1` (Danger) | Errors, destructive actions | | `warning` | `p3` (Warning) | Warning messages, admonitions, find-in-page highlight | | `info` | `p4` (Info) | Informational highlights | | `matched-keyword-bg` | `p3` (Warning family) | Search result `` background — dedicated token, live-editable in Design Token Panel | | `matched-keyword-fg` | `p11` (Text primary) | Search result `` foreground — paired with `matched-keyword-bg` | ### Per-Scheme Semantic Overrides Each color scheme can override the default palette-slot mapping via the `semantic` property in `src/config/color-schemes.ts`. Values can be either a **palette index** (number) or a **direct color string**: ```ts "My Custom Scheme": { // ...palette, background, foreground... semantic: { accent: 6, // use palette[6] — no color duplication accentHover: 14, // use palette[14] surface: "#f4efdd", // direct color string (not in palette) }, }, ``` Using palette indices instead of repeating color strings eliminates duplication and ensures semantic colors stay in sync when palette colors change (e.g., via the [Design Token Panel](./design-token-panel.mdx)). The same `number | string` type (called `ColorRef`) is also supported for `cursor`, `selectionBg`, and `selectionFg`: ```ts "My Theme": { background: "#1a1a2e", foreground: "#e0e0e0", cursor: 6, // use palette[6] instead of duplicating the color selectionBg: "#3a3a5e", selectionFg: 15, // use palette[15] palette: [...], shikiTheme: "one-dark-pro", }, ``` The resolution logic lives in `src/config/color-scheme-utils.ts` — each property falls back to its default palette slot when not explicitly set. ### Search & highlight tokens (role-split) Highlight roles are deliberately split across dedicated semantic tokens — reusing one token across unrelated highlight UIs is an anti-pattern. - `matched-keyword-bg` / `matched-keyword-fg` drive the search panel `` element. Because they are dedicated tokens (not a `color-mix()` of another role), the [Design Token Panel](./design-token-panel.mdx) swatch is the single source of truth for the highlight color — what you see on the swatch is what the highlight renders as. - `warning` drives admonitions (`:::warning`), find-in-page (`.find-match`, `.find-match-active`), and any UI that is semantically a _warning_. Do **not** reuse `warning` for new UI-chrome highlights. When a new highlight role appears (new kind of ``, new pill, new callout), add a dedicated semantic token rather than bolting another responsibility onto an existing role-overloaded token. Each visible highlight color in the product should map to exactly one panel swatch. ## Tier 3: Component Tokens Some components define their own color variables that consume Tier 2 semantic tokens. These are internal implementation details. ### Content Typography The `.zd-content` class provides direct element styling using semantic tokens — no external typography plugin: ```css .zd-content { color: var(--color-fg); font-size: var(--text-body); line-height: var(--leading-relaxed); } .zd-content :where(a) { color: var(--color-accent); } .zd-content :where(code:not(pre code)) { color: var(--color-code-fg); background-color: var(--color-code-bg); } .zd-content :where(li::marker) { color: var(--color-muted); } /* ... */ ``` Tier 3 tokens are internal to their components. When building your own UI, use Tier 2 semantic tokens or Tier 1 palette tokens directly. ## Using Color Tokens ### Prefer semantic tokens Use semantic tokens for standard UI patterns: ```html Primary text Secondary text Link Page background Panel or sidebar Bordered element ``` ### Fall back to palette tokens when needed Use `p0`–`p15` when no semantic token fits — for badges, decorative elements, or status indicators: ```html Error text (red) Success text (green) Warning text (yellow) Info text (blue) ``` ## Color Schemes To change the active color scheme, edit `src/config/settings.ts`: ```ts colorScheme: "Default Dark", // Change this siteName: "zudo-doc", // ... }; ``` ### Default Themes zudo-doc includes a light and dark default theme. See `src/config/color-schemes.ts` for available schemes. ## Adding a Custom Color Scheme Add a new entry to the `colorSchemes` object in `src/config/color-schemes.ts`: ```ts "My Theme": { background: 9, foreground: 11, cursor: 6, selectionBg: 10, selectionFg: 11, palette: [ "#16213e", "#e74c3c", "#2ecc71", "#f39c12", // p0-3: dark surface, danger, success, warning "#3498db", "#9b59b6", "#1abc9c", "#ecf0f1", // p4-7: info, accent, neutral, secondary "#2c3e50", "#1a1a2e", "#2a2a4e", "#e0e0e0", // p8-11: muted, background, surface, text "#5dade2", "#bb8fce", "#48c9b0", "#c0c0c0", // p12-15: accent variant, decorative, hover, text secondary ], shikiTheme: "one-dark-pro", // Optional: override semantic defaults semantic: { accent: 12, // use p12 as accent instead of default p5 surface: "#1f1f3a", // direct color string also works }, }, ``` The `ColorScheme` interface requires: - `background`, `foreground` — `ColorRef` (palette index or color string, typically p9 and p11) - `cursor`, `selectionBg`, `selectionFg` — `ColorRef` (palette index or color string) - `palette` — 16-element tuple (color slots 0–15) - `shikiTheme` — a Shiki theme name for syntax highlighting - `semantic` (optional) — override default palette-slot mappings using `ColorRef` values (palette index or color string) ## What NOT to Do **Don't use Tailwind defaults** — they are reset to `initial`: ```html No visible color Works correctly ``` **Don't hardcode hex values** — it breaks theming: ```html Breaks on theme switch Adapts to any theme ``` **Don't reference component-scoped variables** in your own components: ```css /* WRONG — don't use hardcoded colors */ .my-component { color: #3b82f6; } /* RIGHT — use semantic tokens */ .my-component { color: var(--color-accent); } ``` --- # Reference > Source: /pj/zudo-doc/docs/reference Technical reference for zudo-doc's components, design system, and color architecture. --- # How to Structure Navigations > Source: /pj/zudo-doc/docs/getting-started/structuring-navigations ## Overview zudo-doc uses a **3-level navigation hierarchy** to organize content: 1. **Header navigation** — the broadest, top-level categories visible across the entire site 2. **Sidebar** — the next level of categorization within each header nav section 3. **Nested sidebar categories** — detailed subcategories inside sidebar sections Each level narrows the scope. The header gives users a high-level map of the site, the sidebar breaks a section into logical groups, and nested categories add fine-grained structure where needed. ## File Structure = Navigation Structure In zudo-doc, **your file and directory structure directly becomes your navigation**. There is no separate navigation config to maintain — the sidebar is generated from your `src/content/docs/` directory tree, and header nav items map to top-level directories via `categoryMatch`. This means you can understand the entire site navigation just by looking at the file tree. It is equally intuitive for humans reading the repo and for AI tools generating content. **Keep your file structure and navigation structure tightly coupled.** If you want a sidebar category called "Hardware," create a `hardware/` directory. If you want a header nav item for "Guides," create a `guides/` directory and set `categoryMatch: "guides"`. The directory name, the URL path, and the nav label should all align. This tight coupling is the recommended approach because: - **Predictability** — you always know where a page will appear in the navigation by looking at where the file lives - **No drift** — there is no separate config that can get out of sync with the actual content - **AI-friendly** — AI tools can reliably generate correct navigation by following the directory structure convention While zudo-doc supports advanced configuration for complex cases (custom sidebar labels, sort overrides, nested dropdowns), the default behavior — directories become categories, files become pages — covers most needs. Start simple and only add complexity when the default mapping is not enough. ## Header Navigation Rules The header navigation is the topmost level. It should contain only the broadest categories that define your site's major sections. - **Keep it concise** — aim for 3–6 items. More than that overwhelms users and clutters the header. - **Use dropdowns sparingly** — only for closely related sections that belong under a single umbrella (e.g., "Learn" containing "Guides" and "Components"). - **Each item maps to a content directory** — the `categoryMatch` field in your config links a header item to the sidebar content for that section. For configuration details, see [Header Navigation](../guides/header-navigation.mdx). ### Example Header Config ```ts // src/config/settings.ts headerNav: [ { label: "Getting Started", path: "/docs/getting-started", categoryMatch: "getting-started" }, { label: "Learn", path: "/docs/guides", categoryMatch: "guides", children: [ { label: "Guides", path: "/docs/guides", categoryMatch: "guides" }, { label: "Components", path: "/docs/components", categoryMatch: "components" }, ], }, { label: "Reference", path: "/docs/reference", categoryMatch: "reference" }, ], }; ``` ## Sidebar Structure Rules The sidebar provides the next level of categorization within each header nav section. It is generated automatically from your directory structure. - **Use directories for categories** — each directory becomes a collapsible sidebar group. - **Every category directory should have an `index.mdx`** — this serves as the landing page for that category and sets its `sidebar_position`. - **Keep nesting to 2–3 levels max** — deeply nested sidebars are hard to navigate and often signal that content needs reorganization. - **Set `sidebar_position` on every page** — this ensures predictable ordering instead of relying on alphabetical defaults. For configuration details, see [Sidebar](../guides/sidebar.mdx). ### Directory-to-Sidebar Mapping ``` src/content/docs/guides/ ← sidebar category "Guides" ├── index.mdx ← category landing (sidebar_position: 0) ├── configuration.mdx ← sidebar_position: 1 ├── sidebar.mdx ← sidebar_position: 2 └── header-navigation.mdx ← sidebar_position: 3 ``` Each `.mdx` file appears as a sidebar item. Subdirectories become nested collapsible categories. ## Concrete Real-World Example Imagine building a **gaming wiki**. Here is how you would structure the navigation: ### Navigation Plan ``` Header Nav: ├── Home ├── Platforms ← dropdown │ ├── Xbox │ ├── PlayStation │ └── Nintendo ├── Genres ← dropdown │ ├── RPG │ ├── Action │ └── Strategy └── Community ``` When a user clicks "Xbox" in the Platforms dropdown, the sidebar shows: ``` Sidebar (Xbox section): ├── Overview (index) ├── Hardware │ ├── Xbox Series X │ ├── Xbox Series S │ └── Accessories ├── Games │ ├── Halo Infinite │ ├── Forza Horizon 5 │ └── Starfield └── Services ├── Game Pass └── Xbox Live ``` ### Header Config for Gaming Wiki Each `categoryMatch` value must be a **single top-level directory name** — the framework resolves active state by matching only the first path segment of the current URL. ```ts // src/config/settings.ts headerNav: [ { label: "Home", path: "/docs/home", categoryMatch: "home" }, { label: "Platforms", path: "/docs/xbox", categoryMatch: "xbox", children: [ { label: "Xbox", path: "/docs/xbox", categoryMatch: "xbox" }, { label: "PlayStation", path: "/docs/playstation", categoryMatch: "playstation" }, { label: "Nintendo", path: "/docs/nintendo", categoryMatch: "nintendo" }, ], }, { label: "Genres", path: "/docs/rpg", categoryMatch: "rpg", children: [ { label: "RPG", path: "/docs/rpg", categoryMatch: "rpg" }, { label: "Action", path: "/docs/action", categoryMatch: "action" }, { label: "Strategy", path: "/docs/strategy", categoryMatch: "strategy" }, ], }, { label: "Community", path: "/docs/community", categoryMatch: "community" }, ], }; ``` ### Directory Structure for Gaming Wiki Each platform and genre gets its own top-level directory so that `categoryMatch` matches the first URL segment correctly: ``` src/content/docs/ ├── home/ │ └── index.mdx ├── xbox/ │ ├── index.mdx ← "Overview" │ ├── hardware/ │ │ ├── index.mdx │ │ ├── xbox-series-x.mdx │ │ ├── xbox-series-s.mdx │ │ └── accessories.mdx │ ├── games/ │ │ ├── index.mdx │ │ ├── halo-infinite.mdx │ │ ├── forza-horizon-5.mdx │ │ └── starfield.mdx │ └── services/ │ ├── index.mdx │ ├── game-pass.mdx │ └── xbox-live.mdx ├── playstation/ │ └── index.mdx ├── nintendo/ │ └── index.mdx ├── rpg/ │ └── ... ├── action/ │ └── ... ├── strategy/ │ └── ... └── community/ └── index.mdx ``` ## Common Mistakes Avoid these pitfalls when setting up navigation: - **Putting everything in the header** — the header should have only broad categories. If you have 10+ items, most of them belong in the sidebar instead. - **Deeply nested sidebars** — more than 3 levels of nesting makes content hard to find. Flatten your structure by splitting into separate header nav sections. - **Mixing unrelated topics in one sidebar section** — each sidebar section should cover a cohesive area. If "Hardware" and "Community Events" end up in the same sidebar, reconsider your header categories. - **Missing `sidebar_position`** — without it, pages sort alphabetically which often produces a confusing order. Always set `sidebar_position` for predictable navigation. - **Missing `index.mdx` in category directories** — without an index, the category has no landing page and may not appear correctly in the sidebar. - **Using multi-segment `categoryMatch` values** (e.g. `"platforms/xbox"`) — `categoryMatch` must be a single top-level directory name. Multi-segment values break active-state highlighting in the header nav. ## Tips for AI-Assisted Setup When using AI tools to generate zudo-doc site structures, follow these rules for consistent results: - **Follow the hierarchy strictly** — header nav for broad categories, sidebar for subcategories, nested directories for further detail. Never skip a level. - **Keep header nav items to broad categories only** — resist the urge to add specific pages to the header. That is what the sidebar is for. - **Match `categoryMatch` values to actual directory names** — a `categoryMatch: "guides"` must correspond to a `src/content/docs/guides/` directory. - **Always create `index.mdx` for every category directory** — this ensures the category has a proper landing page and sidebar entry. - **Set `sidebar_position` on every page and index** — this is the most common thing AI tools forget, leading to unpredictable ordering. - **Use kebab-case for directory and file names** — consistent naming avoids case-sensitivity issues across platforms. --- # /packages/ai-chat-worker/CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/packages--ai-chat-worker **Path:** `packages/ai-chat-worker/CLAUDE.md` # ai-chat-worker Standalone Cloudflare Worker sub-package for the AI chat API. Independent of the Astro docs site. ## Tech Stack - **Cloudflare Workers** — runtime - **Cloudflare KV** — rate limiting storage - **Anthropic Messages API** — Claude Haiku via raw `fetch` (no SDK) - **TypeScript** — strict mode, `@cloudflare/workers-types` ## Commands - `pnpm dev` — local dev server (requires `.dev.vars` with `ANTHROPIC_API_KEY`) - `pnpm run deploy` — deploy to Cloudflare Workers via Wrangler - `pnpm typecheck` — TypeScript type checking ## Architecture ``` src/ ├── index.ts # Worker entry — routing, validation, CORS ├── audit-log.ts # Audit logging + IP hashing via Web Crypto ├── claude.ts # Claude API call + docs context fetching/caching ├── cors.ts # CORS headers (exposes Retry-After) ├── input-screen.ts # Prompt injection input screening ├── rate-limit.ts # Per-IP rate limiting via KV └── types.ts # Env, request/response, Claude API types ``` ### Request Flow 1. CORS preflight → `cors.ts` 2. Method + path check → 405/404 3. Hash client IP (SHA-256 via Web Crypto) → `audit-log.ts` 4. JSON parse + message validation → 400 (audit logged as `invalid_input`) 5. Prompt injection screening → 400 (rejects obvious injection attempts before they consume rate limit quota; audit logged) 6. Rate limit check → 429 (audit logged as `rate_limit`) 7. Fetch `llms-full.txt` from docs site (cached in-memory, best-effort) → `claude.ts` 8. Call Claude API with hardened system prompt (XML-tagged context + explicit guardrails) → response (audit logged) ### Key Design Decisions - **No SDK** — raw `fetch` to Anthropic API keeps bundle small for Workers - **Fail-open rate limiting** — KV errors allow requests through (chat availability > strict enforcement) - **Best-effort caching** — module-level cache for `llms-full.txt` is ephemeral (Workers isolates are recycled) - **Rate limit after validation** — invalid requests don't consume the caller's quota - **Fire-and-forget audit logging** — every interaction logged to KV (`audit:` prefix) with 7-day TTL; IPs stored as SHA-256 hashes for privacy - **Prompt hardening** — system prompt uses XML tags for context separation and explicit guardrails against off-topic / injection attempts - **Input screening** — regex pre-filter catches common prompt injection patterns before reaching the Claude API ## Configuration See `README.md` for full setup instructions (vars, secrets, KV namespace). ## Conventions - All responses include CORS headers (including error responses) - Error responses use `{ error: string }` format - Rate limit uses `cf-connecting-ip` for client IP - History capped at 50 messages to limit API cost - KV keys use bucket pattern: `rate:min:{ip}:{bucket}` / `rate:day:{ip}:{bucket}` with TTL = 2x window - Audit log keys: `audit:{date}:{timestamp-ms}:{random}` with 7-day TTL --- # Details > Source: /pj/zudo-doc/docs/components/details Use `` for collapsible content sections. This component is globally available — no imports needed. ## Basic Usage This content is hidden by default and revealed when the user clicks the summary. ```mdx This content is hidden by default and revealed when the user clicks the summary. ``` ## Default Title When no `title` prop is provided, the summary text defaults to "Details". This collapsible section uses the default title. ```mdx This collapsible section uses the default title. ``` ## Rich Content You can include any MDX content inside a `` block, including code blocks, lists, and other components. Here is a sample configuration: ```ts export default { site: "https://example.com", output: "static", }; ``` Key points: - The `site` field sets the base URL - The `output` field controls the build mode - All fields are optional with sensible defaults ````mdx Here is a sample configuration: ```ts site: "https://example.com", output: "static", }; ``` Key points: - The `site` field sets the base URL - The `output` field controls the build mode - All fields are optional with sensible defaults ```` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `title` | string | `"Details"` | The clickable summary text | --- # Contribution > Source: /pj/zudo-doc/docs/getting-started/contribution ## Background zudo-doc was created as a documentation framework by [Takazudo](https://x.com/Takazudo). It was originally built for personal use — a lightweight alternative to Docusaurus, tailored to the specific workflow and design preferences that kept coming up across multiple projects. Over time, it grew into a standalone framework with features like the 16-color terminal palette system, MDX content collections, i18n support, and Claude Code integration. ## Development Most of the source code in this project was written with the help of [Claude Code](https://claude.com/claude-code). The framework is openly available under the MIT License. ## Maintenance This project is maintained on a best-effort basis. Active maintenance and thorough review of contributions may be limited. Rather than waiting for official support, you are encouraged to **fork the repository** and implement your own modifications as needed. In the AI era, forking and customizing is often faster than waiting for upstream changes. ## How to Contribute If you'd like to contribute: 1. Fork the repository on GitHub 2. Create a topic branch from `main` 3. Make your changes and ensure `pnpm b4push` passes (type check, build, E2E tests, format check) 4. Open a pull request with a clear description of what you changed and why Bug reports and feature requests are welcome as [GitHub Issues](https://github.com/zudolab/zudo-doc/issues). ## License zudo-doc is released under the [MIT License](https://github.com/zudolab/zudo-doc/blob/main/LICENSE). ## Author - **Takeshi Takatsudo** ([@Takazudo](https://x.com/Takazudo)) --- # Header Navigation > Source: /pj/zudo-doc/docs/guides/header-navigation zudo-doc supports a navigation hierarchy inspired by Docusaurus: - **Header nav** — top-level tabs in the site header, with optional dropdown menus - **Sidebar categories** — collapsible groups in the sidebar - **Sidebar items** — individual pages within categories This page covers the **left-side tabs**. The right-side cluster (theme toggle, GitHub link, etc.) is configured separately — see [Header Right Items](./header-right-items.mdx). ## Configuration Define header navigation items in `src/config/settings.ts`. Items can be flat links or dropdown parents with nested children: ```ts // ... headerNav: [ // Flat link (no dropdown) { label: "Getting Started", labelKey: "nav.gettingStarted", path: "/docs/getting-started", categoryMatch: "getting-started" }, // Dropdown parent with children { label: "Learn", labelKey: "nav.learn", path: "/docs/guides", categoryMatch: "guides", children: [ { label: "Guides", labelKey: "nav.guides", path: "/docs/guides", categoryMatch: "guides" }, { label: "Components", labelKey: "nav.components", path: "/docs/components", categoryMatch: "components" }, ], }, { label: "Reference", labelKey: "nav.reference", path: "/docs/reference", categoryMatch: "reference" }, ], }; ``` ## Properties ### `HeaderNavItem` | Property | Type | Description | |----------|------|-------------| | `label` | string | Display text shown in the header | | `labelKey` | string (optional) | i18n translation key that overrides `label` when a translation is available | | `path` | string | URL path the link navigates to | | `categoryMatch` | string (optional) | Links this header tab to a sidebar category | | `children` | `HeaderNavChildItem[]` (optional) | Child nav items displayed as a dropdown menu (one level only) | ### `HeaderNavChildItem` Child items use the same properties as `HeaderNavItem` except they cannot have their own `children`. This enforces a single level of nesting at the type level. | Property | Type | Description | |----------|------|-------------| | `label` | string | Display text shown in the dropdown | | `labelKey` | string (optional) | i18n translation key | | `path` | string | URL path the link navigates to | | `categoryMatch` | string (optional) | Links this child to a sidebar category | ### `labelKey` The `labelKey` property enables localized header navigation labels. When set, zudo-doc looks up the key in the current locale's translation file and uses the translated string instead of `label`. If no translation is found, `label` is used as a fallback. Translation keys follow the `nav.*` namespace convention (e.g., `"nav.gettingStarted"`, `"nav.guides"`). ### `categoryMatch` The `categoryMatch` property connects a header tab to a specific sidebar category. When a header tab with `categoryMatch` is active, the sidebar filters to show only the pages within that category. For example, `categoryMatch: "guides"` links the tab to the `guides/` content directory. When a user navigates to any page under `/docs/guides/`, the "Guides" tab becomes active and the sidebar shows only the guides category. Children can also have `categoryMatch`. For example, placing `categoryMatch: "components"` on a child ensures that visiting `/docs/components/` activates the parent dropdown and filters the sidebar to the components section. ## Dropdown Behavior Items with `children` render as dropdown menus. The parent item itself remains a clickable link — clicking it navigates to its `path`. The dropdown provides quick access to related sub-pages. - **Desktop**: opens on hover and closes when the mouse leaves the menu area. A small chevron indicates the dropdown. - **Keyboard**: opens when the trigger receives focus (via `focus-within`). Press Escape to close. Fully accessible without a mouse. - **Mobile**: nested items appear as expandable groups in the mobile sidebar menu, with a toggle chevron to expand/collapse children. - **Overflow**: when the browser window is narrow and a dropdown parent is moved to the overflow "..." menu, its children are shown as indented sub-items. ## Active State A header nav item is considered active when the current page URL starts with the item's `path`. If the current page matches any child item's `path`, the parent item also shows as active. For example, visiting `/docs/components/admonitions` activates the "Learn" dropdown because `/docs/components` matches one of its children. ## Mobile Behavior On small screens (`< 1024px`), the desktop header nav is hidden. The sidebar toggle provides access to all navigation on mobile. Nested navigation items appear as expandable groups in the mobile sidebar — tap the chevron to expand/collapse children. --- # Sidebar Filter > Source: /pj/zudo-doc/docs/guides/sidebar-filter ## Overview The sidebar filter is a text input at the top of the sidebar that filters navigation items in real time as you type. It helps users quickly find pages in large documentation sites without scrolling through the full sidebar tree. The sidebar filter is enabled by default in projects created with `create-zudo-doc`. ## How It Works The filter input appears at the top of the sidebar, above the navigation tree. As you type: - Both category names and individual page titles are matched against your query - Matching is case-insensitive - When a category name matches, all of its child pages are shown - When only child pages match, the parent category is shown with just the matching children - All matching categories are automatically expanded during filtering - Clearing the input restores the full sidebar tree ## Keyboard Shortcut Press `Ctrl+/` (Windows/Linux) or `Cmd+/` (macOS) to focus the filter input from anywhere on the page. This works when the sidebar is visible (desktop viewport or mobile sidebar open). ## Enabling or Disabling ### With create-zudo-doc The sidebar filter is on by default. To disable it during project creation: ```bash pnpm create zudo-doc my-docs --no-sidebar-filter ``` To explicitly enable it: ```bash pnpm create zudo-doc my-docs --sidebar-filter ``` ### In an Existing Project The sidebar filter is built into the `SidebarTree` Preact component (`src/components/sidebar-tree.tsx`). It is always rendered as part of the sidebar — there is no separate setting in `settings.ts` to toggle it. To remove it from a scaffolded project, you would need to modify the component directly. For most documentation sites, the sidebar filter is useful even with a small number of pages. It becomes essential as your content grows beyond a few dozen pages. --- # CJK-Friendly Markdown > Source: /pj/zudo-doc/docs/reference/cjk-friendly ## The Problem Standard CommonMark has a known issue with emphasis (bold and italic) adjacent to CJK (Chinese, Japanese, Korean) punctuation. Consider this Markdown source: ```md **テスト。**テスト ``` Without the fix, this renders literally as `**テスト。**テスト` — the bold markers are not recognized. The same problem affects other CJK punctuation characters and also applies to italic (`*` and `_`). ## Root Cause The CommonMark spec classifies certain characters as "Unicode punctuation" for the purpose of emphasis parsing. CJK punctuation such as `。` (ideographic period) falls into this category. The spec's flanking rules then prevent an emphasis delimiter from opening when it is directly followed by punctuation, which means the closing `**` cannot match the opening `**`. Affected characters include, but are not limited to: | Character | Description | |-----------|-------------| | `。` | Ideographic full stop | | `、` | Ideographic comma | | `」` | Right corner bracket (closing) | | `)` | Fullwidth right parenthesis | | `』` | White right corner bracket | | `】` | Right black lenticular bracket | | `》` | Right double angle bracket | Any CJK closing punctuation appearing immediately before `**`, `*`, `__`, or `_` can trigger the issue. ## How remark-cjk-friendly Fixes It The [`remark-cjk-friendly`](https://github.com/tats-u/remark-cjk-friendly) plugin patches the emphasis parsing rules in `micromark` so that CJK punctuation is **not** treated as Unicode punctuation for flanking detection. This lets bold and italic work correctly when adjacent to CJK text, matching the intuitive expectation. ### Before and After Input Markdown: ```md **テスト。**テスト ``` | | Output | |---|--------| | Without plugin | `**テスト。**テスト` (rendered literally) | | With plugin | **テスト。**テスト (bold applied correctly) | The same fix applies to italic markers and all affected CJK punctuation characters. ## Enabling and Disabling The plugin is controlled by the `cjkFriendly` setting in `src/config/settings.ts`: ```ts // ... cjkFriendly: true, // set to false to disable }; ``` When `cjkFriendly` is `true` (the default), `remark-cjk-friendly` is added to the MDX remark plugin chain automatically. Set it to `false` if your content is English-only and you prefer the standard CommonMark behavior. You can also configure this option when scaffolding a new project with the [Setup Preset Generator](/docs/getting-started/setup-preset-generator) or the [create-zudo-doc CLI](/docs/reference/create-zudo-doc) using `--cjk-friendly` / `--no-cjk-friendly`. --- # Header Right Items > Source: /pj/zudo-doc/docs/guides/header-right-items The right side of the header — search, theme toggle, language switcher, version switcher, GitHub link, and the Design Token / AI Chat triggers — is rendered from a single array: `settings.headerRightItems`. Each entry is a discriminated-union item rendered in order. This complements [Header Navigation](./header-navigation.mdx), which configures the **left-side tabs**. Both live in `src/config/settings.ts` and are independent of each other. ## Default configuration ```ts // ... githubUrl: "https://github.com/zudolab/zudo-doc", headerRightItems: [ { type: "trigger", trigger: "design-token-panel" }, { type: "trigger", trigger: "ai-chat" }, { type: "component", component: "version-switcher" }, { type: "component", component: "github-link" }, { type: "component", component: "theme-toggle" }, { type: "component", component: "language-switcher" }, ], }; ``` The built-in `Search` component is always rendered at the end of the cluster and is not configured through this array. ## Item types ### `component` References a built-in UI piece that is gated by its own feature flag. | Component | Feature flag | |---|---| | `theme-toggle` | `colorMode` | | `language-switcher` | `locales` (needs at least one non-default locale) | | `version-switcher` | `versions` | | `github-link` | `githubUrl` | ```ts { type: "component", component: "github-link" } ``` When the associated feature is disabled the entry is skipped — you can leave it in the array unconditionally. ### `trigger` Opens a built-in modal or panel. | Trigger | Feature flag | |---|---| | `design-token-panel` | `designTokenPanel` (or legacy `colorTweakPanel`) | | `ai-chat` | `aiAssistant` | ```ts { type: "trigger", trigger: "ai-chat" } ``` ### `link` A custom external or internal link, rendered with an optional icon. ```ts { type: "link", href: "https://discord.example", ariaLabel: "Discord", icon: "github", // only "github" is bundled today } ``` External URLs open in a new tab automatically. ### `html` Escape hatch for injecting arbitrary markup. ```ts { type: "html", html: 'beta' } ``` Use sparingly — the html string is rendered verbatim. ## Customisation recipes ### Point the GitHub icon at your repo Set `githubUrl` at the top level of `settings.ts`. The `github-link` component reads that value, so you don't duplicate the URL inside `headerRightItems`. ```ts githubUrl: "https://github.com/your-org/your-repo", }; ``` Setting `githubUrl: false` removes the icon without having to edit the array. ### Drop an item Remove it from the array — don't rely on the feature flag to hide it. For example, to hide the AI chat trigger: ```ts headerRightItems: [ { type: "trigger", trigger: "design-token-panel" }, // { type: "trigger", trigger: "ai-chat" }, // removed { type: "component", component: "version-switcher" }, { type: "component", component: "github-link" }, { type: "component", component: "theme-toggle" }, { type: "component", component: "language-switcher" }, ], ``` ### Add a custom link ```ts headerRightItems: [ // ...existing items { type: "link", href: "https://discord.gg/your-server", ariaLabel: "Discord", icon: "github", // until more icons are bundled }, ], ``` ## Ordering Items render in the order they appear in the array. The cluster is right-aligned, so the first entry sits closest to the nav and the last entry sits closest to the viewport edge. --- # /packages/create-zudo-doc/CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/packages--create-zudo-doc **Path:** `packages/create-zudo-doc/CLAUDE.md` # create-zudo-doc CLI scaffold tool for creating new zudo-doc documentation sites. Generates a project with configurable features, color schemes, and i18n support. ## Architecture The generator uses an **additive composition** approach: 1. Copy a minimal **base template** (`templates/base/`) — core files with injection anchors 2. **Generate** `astro.config.ts`, `content.config.ts`, `settings.ts`, `package.json` programmatically 3. **Compose** selected features — copy feature files + inject code into shared files at anchor points 4. Clean up unused anchors This replaces the old "copy everything then strip" approach. Features are added, not removed — so dead code cannot remain. ## Key Files | File | Role | |------|------| | `src/scaffold.ts` | Orchestrates the scaffold pipeline: copy base, generate configs, compose features | | `src/compose.ts` | Composition engine: injection system, anchor cleanup, feature resolution | | `src/features/*.ts` | Feature modules defining injections for each optional feature (10 modules) | | `src/astro-config-gen.ts` | Programmatic `astro.config.ts` generator (conditional imports/integrations) | | `src/content-config-gen.ts` | Programmatic `content.config.ts` generator (locale/version collections) | | `src/settings-gen.ts` | Generates `src/config/settings.ts` with user-chosen options | | `src/constants.ts` | Feature definitions, color scheme lists, light-dark pairings | | `src/utils.ts` | Shared utilities (patchFile, patchDefaultLang, getSecondaryLang) | | `src/cli.ts` | CLI argument parsing (commander) | | `src/api.ts` | Programmatic API (`createZudoDoc()`) | | `src/prompts.ts` | Interactive prompts (inquirer) | | `src/index.ts` | Entry point | ### Template Directories | Directory | Role | |-----------|------| | `templates/base/` | Minimal project with injection anchors (73 files, no optional feature code) | | `templates/features/*/files/` | Feature-specific files copied when a feature is selected | ### Injection Anchors Shared files in `templates/base/` have anchor comments where features inject code: - `src/layouts/doc-layout.astro` — 16 anchors (imports, head scripts, sidebar, breadcrumb, footer, body-end) - `src/components/header.astro` — 4 anchors (imports, actions, after-theme-toggle) - `src/styles/global.css` — 2 anchors (theme tokens, feature styles) ## Testing ### Unit tests ```bash pnpm test ``` Runs vitest tests in `src/__tests__/`. ### Generator CLI integration tests Two Claude Code skills test the full scaffold-build-run cycle: - `/l-generator-cli-tester ` — Test a single generation pattern - `/l-run-generator-cli-whole-test` — Run all 9 patterns, fix bugs, verify everything #### Test patterns | Pattern | Description | |---------|-------------| | `barebone` | Everything OFF — minimal project | | `search` | Only search enabled | | `i18n` | Only i18n enabled | | `sidebar-filter` | Only sidebar filter enabled | | `claude-resources` | Only Claude Resources enabled | | `design-token-panel` | Only design token panel enabled (API only, no CLI flag) | | `light-dark` | Light-dark color scheme mode | | `lang-ja` | Japanese as default language | | `all-features` | Everything ON | Always rebuild the CLI before testing: ```bash pnpm build ``` ## Adding a New Feature When adding a feature to the main zudo-doc project that the generator should support: 1. **`src/constants.ts`** — Add feature to `FEATURES` array if it needs a CLI flag 2. **`src/features/.ts`** — Create a feature module defining injections for shared files 3. **`src/features/index.ts`** — Register the feature module 4. **`templates/features//files/`** — Add feature-specific files to copy 5. **`src/scaffold.ts`** — Add dependencies in `generatePackageJson()` if needed 6. **`src/astro-config-gen.ts`** — Add conditional imports/integrations if the feature affects `astro.config.ts` 7. **`src/content-config-gen.ts`** — Add collections if the feature affects content config 8. **`src/settings-gen.ts`** — Add the setting field to generated `settings.ts` 9. **`src/__tests__/scaffold.test.ts`** — Update tests After changes, run `/l-update-generator` to verify no drift remains between the main project and the generator. --- # Math Equations > Source: /pj/zudo-doc/docs/components/math-equations When `math` is enabled in settings (default: `true`), you can render mathematical equations using KaTeX. ## Inline Math Use single dollar signs to embed math within a line of text. The formula $E = mc^2$ is inline. ```mdx The formula $E = mc^2$ is inline. ``` ## Block Math Use double dollar signs to render a centered display equation. $$ \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi} $$ ```mdx $$ \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi} $$ ``` ## Fenced Code Block Use a fenced code block with the `math` language identifier. ```math \nabla \times \mathbf{E} = -\frac{\partial \mathbf{B}}{\partial t} ``` ````mdx ```math \nabla \times \mathbf{E} = -\frac{\partial \mathbf{B}}{\partial t} ``` ```` ## Configuration Math support is controlled by the `math` setting in `src/config/settings.ts`: ```ts // ... math: true, // enabled by default }; ``` ## More Examples ### Quadratic Formula $$ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$ ```mdx $$ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$ ``` ### Summation $$ \sum_{i=1}^{n} i = \frac{n(n+1)}{2} $$ ```mdx $$ \sum_{i=1}^{n} i = \frac{n(n+1)}{2} $$ ``` ### Matrix $$ \begin{bmatrix} a & b \\ c & d \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} ax + by \\ cx + dy \end{bmatrix} $$ ```mdx $$ \begin{bmatrix} a & b \\ c & d \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} ax + by \\ cx + dy \end{bmatrix} $$ ``` --- # Site Search > Source: /pj/zudo-doc/docs/guides/search ## How Search Works zudo-doc uses [MiniSearch](https://lucaong.github.io/minisearch/) for full-text site search. A search index is generated from your content files (title, description, and body text) and served as a single JSON file. The browser loads this index and performs all searching client-side — no server or external service required. ## Using Search Open the search dialog with: - **Keyboard shortcut**: `Ctrl+K` (Windows/Linux) or `Cmd+K` (macOS) - **Search button**: Click the search icon in the header Type your query and results appear instantly. Click a result to navigate to that page. ## Works Everywhere Search works in all environments: - **Dev mode** (`pnpm dev`): The search index is generated on-the-fly via Vite middleware - **Production build** (`pnpm build`): The index is written to `search-index.json` alongside the static site - **Electron**: Loads the same index file — no special handling needed No need to build before testing search. It works immediately with `pnpm dev`. ## Excluding Pages from Search Pages with `search_exclude: true`, `draft: true`, or `unlisted: true` in their frontmatter are automatically excluded from the search index. To explicitly exclude a page, add `search_exclude: true` to its frontmatter: ```mdx --- title: My Internal Page search_exclude: true --- ``` This is useful for pages like changelogs, imported content (e.g., CLAUDE.md), or internal-only pages that would clutter search results. ## Search Features MiniSearch provides: - **Fuzzy matching**: Tolerates typos (up to 20% character difference) - **Prefix search**: Finds results as you type (e.g., "conf" matches "Configuration") - **Field boosting**: Title matches rank 3x higher than body text, descriptions 2x - **Instant results**: The full index is loaded once and queried in-memory ## Server-Side Search (Search Worker) For large documentation bases or programmatic API access, zudo-doc provides an optional **Search Worker** -- a Cloudflare Worker that runs MiniSearch server-side. ### When to Use the Search Worker The built-in client-side search works well for small-to-medium documentation sites. The Search Worker becomes useful when: - **Large index size** -- the full `search-index.json` is too large for comfortable browser download - **API consumers** -- bots, CLI tools, or integrations need search access without a browser - **Server-side processing** -- you want to keep the search index off the client ### How It Works The Search Worker fetches the same `search-index.json` from your deployed site and caches it with a 5-minute TTL. It uses identical MiniSearch configuration (prefix, fuzzy, boost settings), so results are consistent with client-side search. ### Comparison | Feature | Client-Side Search (built-in) | Search Worker | | -------------------------- | ----------------------------- | -------------------------- | | Runtime | Browser (in-memory) | Cloudflare Workers | | Setup required | None (enabled by default) | Cloudflare account + KV | | Index download | Full index sent to browser | Index stays server-side | | Best for | Small-to-medium doc bases | Large doc bases, API consumers | | Latency | Instant (local) | Network round-trip | For setup instructions, API documentation, and deployment details, see the [Search Worker API reference](/docs/reference/search-worker). --- # Tags > Source: /pj/zudo-doc/docs/guides/tags ## Overview Tags provide a **cross-cutting** way to group documentation pages that share a topic, even when they live in different sidebar categories. A page about deployment under Guides and a troubleshooting page under Reference can both carry the `deployment` tag, so readers exploring that topic find them together. Use tags when: - A topic spans multiple categories (e.g., `deployment`, `i18n`, `accessibility`). - You want readers to discover related pages that would not naturally appear side-by-side in the sidebar. - You are adding secondary navigation without restructuring directories. Tags are a complement to — not a replacement for — the sidebar. The sidebar reflects structure; tags reflect topic. ## Adding Tags Declare tags in the page's frontmatter as an array of strings: ```mdx --- title: Deploying to Cloudflare Pages sidebar_position: 3 tags: [deployment, hosting] --- ``` A single page can carry any number of tags. Tags are case-sensitive strings, so pick a form and stick with it (see [Naming Conventions](#naming-conventions) below). ## Where Tags Appear Tags surface in three places across the built site. ### Per-Page Badge Row Every page with one or more tags renders a row of tag badges that links each badge to its tag index page. This is powered by the `DocTags` component (`TagNav` with `variant="page"`). The row can appear in one of two positions, chosen by the [`tagPlacement`](./configuration.mdx#tagplacement) setting: - `"after-title"` (default) — the badge row sits directly below the page title, above the main content. Use this when tags describe the page at a glance and should be visible without scrolling. - `"before-pager"` — the badge row sits at the bottom of the content, immediately above the previous/next pager. Use this when tags are reference metadata that readers reach for after finishing the page. Only one placement is active at a time; the setting is global. ### Homepage "All Tags" Section When `settings.docTags` is `true` and at least one tagged page exists in the current locale, the homepage shows an "All Tags" section listing every tag in use, each linked to its tag index page. If no pages are tagged, the section is hidden automatically. ### Tag Index and Per-Tag Pages - [/docs/tags/](/docs/tags/) — the global tag index, listing every tag with a page count. - `/docs/tags/[tag]` — one page per tag, listing every page that carries that tag. Both routes are generated automatically from frontmatter; you do not create them by hand. ## Naming Conventions Consistent tag names keep the tag index tidy and prevent near-duplicates like `deployment` and `Deployment` showing up as separate tags. - **Lowercase** — treat tags as slugs, not display text. - **Kebab-case** for multi-word tags — `color-scheme`, not `colorScheme` or `color_scheme`. - **Short and specific** — prefer `i18n` over `internationalization-and-localization`. - **Consistent across locales** — use the same tag slug in EN and JA frontmatter (see below). Before introducing a new tag, skim the existing tag index at [/docs/tags/](/docs/tags/) to see whether a suitable tag already exists. ## i18n Behavior Tags are **shared identifiers** across locales, not translated labels. - The same tag slug appears in both EN and JA frontmatter — e.g., `tags: [deployment]` in both files. - Each locale has its own tag index. The EN route `/docs/tags/deployment` lists EN pages. The JA route `/ja/docs/tags/deployment` lists JA-translated pages first and **falls back to the EN page** for any slug that has not yet been translated — so untranslated EN docs still appear under the JA tag page rather than silently disappearing. - **Do not translate tag slugs.** Translating `deployment` to `デプロイ` in the JA file would split the tag into two unrelated buckets and break the cross-locale model. If you need a locale-specific display label for a tag, that is a future enhancement — today, the slug is both the identifier and the label. ## Configuration Tag rendering is controlled by the `docTags` setting in `src/config/settings.ts`. When enabled (the default), the per-page tag badge row and the "All Tags" homepage section are rendered. When set to `false`, those surfaces are hidden — but the `/docs/tags/` index and `/docs/tags/[tag]` pages are still built and remain reachable by URL. See [Configuration](./configuration.mdx) for the full settings reference. ## Scaling to a Team Project Once a doc base has more than a handful of authors, free-form tags drift. zudo-doc ships a vocabulary-aware governance workflow to keep the tag index tidy as the project grows: - [Tag governance](./tag-governance.mdx) — curate a canonical vocabulary and enforce it via `tagGovernance: "warn" | "strict"`. - [Tag audit](./tags-audit.mdx) — `pnpm tags:audit` reports unknowns, deprecations, aliases, near-duplicates, and orphans, with a byte-stable `--fix` for alias rewrites and `--ci` integration for `pnpm b4push`. - [Tag suggestions](./tags-suggest.mdx) — optional local-LLM helper (`pnpm tags:suggest`) that proposes canonical tags for new pages. - [Footer taglist](./footer-taglist.mdx) — opt-in footer column that surfaces the vocabulary to readers. ## Related Frontmatter Fields A couple of other frontmatter fields interact with tag navigation: - **`unlisted: true`** — unlisted pages are excluded from tag indexes (they don't appear under `/docs/tags/[tag]`) and from the homepage "All Tags" section, matching their exclusion from the sidebar. The tag badge row still renders on the unlisted page itself when accessed directly by URL. - **`draft: true`** — draft pages are excluded from the production build entirely, so they never appear on any tag page in production. For the full list of fields, see [Frontmatter](./frontmatter.mdx#tags). --- # AI Assistant API > Source: /pj/zudo-doc/docs/reference/ai-assistant-api API specification for the AI assistant chat endpoint. ## Endpoint ``` POST /api/ai-chat Content-Type: application/json ``` ### Request Body ```ts interface AiChatRequest { message: string; history: ChatMessage[]; } interface ChatMessage { role: "user" | "assistant"; content: string; } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `message` | `string` | Yes | The user's current message. Must be non-empty. | | `history` | `ChatMessage[]` | Yes | Previous conversation messages. Invalid entries are filtered out silently. | ### Success Response (200) ```ts interface AiChatResponse { response: string; } ``` The `response` field contains the assistant's reply as a markdown string. **Example:** ```json // Request { "message": "How do I add a new page?", "history": [] } // Response { "response": "Create an MDX file in `src/content/docs/`:\n\n1. Add frontmatter with `title`\n2. Write your content in MDX\n3. The page appears in the sidebar automatically" } ``` ### Error Response (400 / 500) ```ts interface AiChatErrorResponse { error: string; } ``` | Status | Condition | |--------|-----------| | 400 | Invalid JSON body | | 400 | `message` is not a non-empty string | | 400 | `history` is not an array | | 500 | `AI_CHAT_MODE` is not set to `"local"` or `"remote"` | | 500 | Claude CLI or Anthropic API call failed | ## Environment Variables | Variable | Required | Description | |----------|----------|-------------| | `AI_CHAT_MODE` | Yes | `"local"` or `"remote"` | | `ANTHROPIC_API_KEY` | Remote mode only | Anthropic API key | | `PUBLIC_ENABLE_MOCKS` | No | Set to `"true"` to enable MSW mock responses (dev mode only, ignored in production) | ## Backend Modes ### Local Mode (`AI_CHAT_MODE=local`) Spawns `claude -p --model haiku` as a subprocess. Documentation context is piped via stdin. 60-second timeout. ### Remote Mode (`AI_CHAT_MODE=remote`) Calls the Anthropic Messages API with `claude-haiku-4-5-20251001`. Documentation content is included in the system prompt. ## Documentation Context The endpoint loads `llms-full.txt` (generated by the [llms.txt integration](/docs/guides/llms-txt)) as context. The file is loaded once and cached in memory. --- # Tag Governance > Source: /pj/zudo-doc/docs/guides/tag-governance ## Why Governance A free-form tag list is fine for the first ten pages. By the hundredth, you will have `deployment`, `Deployment`, `deploy`, and `deploys` all pointing at the same topic — splitting your tag index into useless near-duplicates and making discovery worse, not better. Tag governance fixes this by promoting the project's tag set to a **canonical vocabulary** defined in `src/config/tag-vocabulary.ts`. Every tag used in frontmatter is validated against this file. Typos and drift surface as audit findings; legitimate additions are intentional edits to the vocabulary. The goal is to keep discovery sharp as the doc base grows. Start loose; tighten as you scale. ## The Vocabulary File `src/config/tag-vocabulary.ts` is an array of `TagVocabularyEntry` objects. Each entry declares a canonical tag id, an optional display label, a group, and any aliases content is allowed to use: ```ts { id: "deployment", label: "Deployment", description: "Hosting, CI, and release workflows.", group: "topic", aliases: ["deploy", "deploys"], } ``` To phase out a tag without breaking existing pages, either mark it deprecated or redirect it to a replacement. Never silently delete an entry — see the inline comments in `src/config/tag-vocabulary.ts` for the full phase-out contract. ### Extending the Vocabulary in Your Project After scaffolding with `create-zudo-doc`, **`tag-vocabulary.ts` belongs to your project.** It is a normal source file, not a framework internal. Edit it directly — no forking or monkey-patching required. A typical addition looks like this: ```ts // ...existing entries { id: "topic:auth", label: "Auth", description: "Authentication, sessions, SSO.", group: "topic", }, { id: "type:runbook", label: "Runbook", description: "Incident response and on-call procedures.", group: "type", }, ]; ``` After editing, run [`pnpm tags:audit`](./tags-audit.mdx) to verify nothing unexpected broke, and add tags to pages the usual way: ```mdx --- title: OAuth 2.0 Setup tags: [topic:auth, type:runbook] --- ``` ## Two Orthogonal Settings Governance is controlled by two independent settings in `src/config/settings.ts`: | Setting | Type | Purpose | | ---------------- | --------- | ------------------------------------------------------------------------------------------ | | `tagVocabulary` | `boolean` | Whether the vocabulary file is consulted at all (alias resolution, deprecation, grouping). | | `tagGovernance` | `"off" \| "warn" \| "strict"` | Enforcement level when the vocabulary is consulted. | They are **orthogonal**. `tagVocabulary: false` disables vocabulary entirely, regardless of `tagGovernance`. Leave `tagVocabulary: true` (the default) and tune enforcement via `tagGovernance`. ### The Three Modes - `"off"` — vocabulary-aware enforcement is skipped. Tags stay completely loose. Useful for legacy migrations or prototypes. - `"warn"` (default) — `pnpm tags:audit` reports unknown tags, deprecations, and near-duplicates, but `pnpm build` still passes. Fixes can land at your own pace. - `"strict"` — unknown tags fail Zod validation at `pnpm check` / `pnpm build` time. The vocabulary becomes the single source of truth. ### Recommended Escalation Path 1. **Seed the vocabulary** from the tags you already use — `tag-vocabulary.ts` ships pre-filled with every tag that appears in the default showcase content. 2. **Run `warn` until the audit is clean.** Fix unknowns, resolve aliases with `pnpm tags:audit --fix`, and retire near-duplicates by adding them as aliases of the canonical id. 3. **Flip to `strict`** once the audit is green. From that point on, new tags require a matching vocabulary entry — which is exactly the friction you want to prevent drift. Adopt `strict` as soon as the backlog is empty; the earlier the constraint is in place, the cheaper it stays. ## Faceted Tag Pattern Flat tag soups (`deployment`, `advanced`, `tutorial`, `i18n`, ...) scale poorly because the reader cannot tell what a tag answers. A faceted prefix names the axis: - `type:` — what kind of page is it? `type:guide`, `type:reference`, `type:tutorial`, `type:runbook`. - `level:` — who is it for? `level:beginner`, `level:advanced`. - `topic:` — what subject does it cover? `topic:auth`, `topic:ai`, `topic:search`. The default vocabulary uses this pattern for `type:*` and `level:*` facets. You can extend it with your own `topic:*` entries, or introduce new facets (`product:*`, `team:*`) as your doc base grows. Facets stay separate in the grouped footer taglist — see [Footer taglist](./footer-taglist.mdx). ## Next Steps - [Tag audit](./tags-audit.mdx) — what `pnpm tags:audit` reports and how to read it. - [Tag suggestions](./tags-suggest.mdx) — opt-in local LLM suggester for new pages. - [Footer taglist](./footer-taglist.mdx) — surface the vocabulary to readers. --- # Tag Audit > Source: /pj/zudo-doc/docs/guides/tags-audit ## What the Audit Reports `pnpm tags:audit` walks `src/content/docs/**` and every configured locale directory, collects the `tags:` arrays from each page's frontmatter, and reports five kinds of finding against `src/config/tag-vocabulary.ts`. - **Unknown tags** — the string is neither a canonical id nor an alias. Either add a vocabulary entry, or remove the tag from the page. Under [`tagGovernance: "strict"`](./tag-governance.mdx), unknowns fail the build. - **Deprecated tags** — the id resolves to an entry marked `deprecated`. If the entry has a `redirect`, the reader's traffic still resolves to the replacement; you should still update the content. - **Alias usage** — the string is a known alias, not the canonical id. The page will render correctly but drift away from canonical form. Fix in-place with `--fix`. - **Near-duplicates** — two distinct canonical tags that look like variants of each other (high string similarity, or the same singular form). Usually a vocabulary problem: pick one, make the other an alias. - **Orphan vocabulary entries** — vocabulary ids that no page references. Consider retiring them via `deprecated: true`. A clean run prints `✓ No tag issues found`. ## Reading the Report The default output is colorized text grouped by category. For machine consumption, pass `--json`: ```sh pnpm tags:audit --json > audit.json ``` The JSON payload is an `AuditReport` with `unknowns`, `deprecated`, `aliases`, `nearDuplicates`, `orphans`, `filesScanned`, and a canonical-id `frequency` map. This is what CI dashboards and auto-fix tools should consume. ## `--fix`: Byte-Stable Alias Rewrites `pnpm tags:audit --fix` rewrites alias tags to their canonical id directly in the frontmatter, one file at a time: ```sh pnpm tags:audit --fix ``` Behaviour worth knowing: - **Only aliases are rewritten.** Unknown tags, deprecated tags, and near-duplicates are never touched — those require editorial decisions, not search-and-replace. - **Byte-stable outside the tags block.** The rewrite preserves every other byte of the file, including indentation style, quoting, and line endings (both LF and CRLF). - **Both YAML sequence styles are supported** — flow (`tags: [foo, bar]`) and block (`tags:\n - foo`). Run `--fix` on a clean working tree, then review the diff before committing. ## CI Integration via `b4push` The project's pre-push validation script ([`pnpm b4push`](./development-workflow.mdx)) runs the audit with `--ci`: ```sh pnpm tags:audit --ci ``` `--ci` forces a non-zero exit on any **hard issue** (unknowns or deprecations) regardless of the configured `tagGovernance` mode. This means: - Under `tagGovernance: "warn"` — `pnpm build` still passes with unknowns present (intentional, so migrations aren't blocked), but `pnpm b4push` refuses to push them. - Under `tagGovernance: "strict"` — the build already fails on unknowns; `--ci` keeps b4push aligned when enforcement is later relaxed. This two-layer setup — lenient build, strict push — is the sweet spot for multi-author doc bases: drafts can experiment locally without fighting Zod, but no broken tags make it onto `main`. ## Related - [Tag governance](./tag-governance.mdx) — vocabulary file, governance modes, the faceted tag pattern. - [Tag suggestions](./tags-suggest.mdx) — opt-in LLM helper for picking canonical tags on new pages. --- # Tag Suggestions > Source: /pj/zudo-doc/docs/guides/tags-suggest ## Opt-in, Not Automatic `pnpm tags:suggest` is a developer-facing helper, not part of the build. It reads one or more doc files, asks a locally-running LLM to propose up to three tag ids from the project vocabulary, and either shows them in an interactive prompt or appends them to a JSON Lines file for batch review. Three deliberate choices about this tool: - **Local model, no cloud.** The prompt and the doc body never leave your machine. - **Vocabulary-aware.** The full `tag-vocabulary.ts` is passed to the model as context, so suggestions always come from the canonical set. - **Never in CI.** Suggestions are advisory. The tool is not wired into `pnpm b4push`, `pnpm build`, or any pre-commit hook. If you want stricter enforcement, that's what [`tagGovernance: "strict"`](./tag-governance.mdx) and [`pnpm tags:audit`](./tags-audit.mdx) are for. ## Ollama Setup The suggester talks to a local [Ollama](https://ollama.com/) daemon over HTTP. 1. Install Ollama from [ollama.com](https://ollama.com/). 2. Pull a model: ```sh ollama pull qwen2.5:7b ``` 3. Make sure the daemon is reachable at `http://localhost:11434` (the default). The default model is `qwen2.5:7b` — a reasonable balance of quality, speed, and disk footprint (~5 GB). Override with `--model`: ```sh pnpm tags:suggest --model llama3.1:8b src/content/docs/guides/i18n.mdx ``` Lighter models (`qwen2.5:3b`, `llama3.2:3b`) are faster but noisier. The tool will exit non-zero if the daemon is unreachable or returns unusable output. ## Interactive Flow The default (TTY) flow reviews one file at a time: ```sh pnpm tags:suggest src/content/docs/guides/deployment.mdx ``` The suggester prints the current tags, the LLM's suggested ids, and a y/n prompt to accept. Accepted suggestions are written back to the file's frontmatter; rejected suggestions are discarded. This is the flow for routine authoring. ## Batch Flow For larger jobs or non-TTY contexts (CI, editor plugins, piped stdin), pass `--batch`: ```sh pnpm tags:suggest --batch src/content/docs/guides/*.mdx ``` Suggestions are appended to `.tag-suggestions.jsonl` at the repo root — one JSON object per file containing `file`, `current`, and `suggested`. Nothing is written to the doc files. Review the JSONL, apply what looks good by hand (or script it), and delete the file when done. `--batch` also auto-engages when stdout is not a TTY, so piping or redirecting to a log file does the right thing by default. ## Why Pass the Vocabulary as Context The whole `tag-vocabulary.ts` — ids, labels, descriptions, groups — is included in the prompt every time the suggester runs. That's the single biggest factor in output quality: - The model picks from the canonical set, so you don't waste a round trip filtering unknowns. - Group and description context lets the model distinguish close topics (`content` vs `customization`). - Adding a new vocabulary entry is the primary way to "teach" the tool about a new facet — no fine-tuning, no embeddings store. The doc body is truncated to 1500 characters to keep the prompt small. Most frontmatter-driven suggestion lives in the first page or two of prose, so the truncation is rarely limiting. ## Related - [Tag governance](./tag-governance.mdx) — defining the vocabulary the suggester draws from. - [Tag audit](./tags-audit.mdx) — what catches suggestions that slip through. --- # Footer Taglist > Source: /pj/zudo-doc/docs/guides/footer-taglist ## Overview The footer taglist is an optional index of tags rendered inside the existing footer grid. It gives readers a persistent way to reach every tag page without leaving the current doc. It is **off by default** — the footer looks the same as before until `footer.taglist.enabled` is set to `true` in `src/config/settings.ts`. Nothing about the per-page tag badge row or the homepage "All Tags" section changes. ## Enabling Add a `taglist` block to the existing `footer` config: ```ts footer: { links: [ // ...existing footer link columns ], copyright: `Copyright © ${new Date().getFullYear()} Your Project.`, taglist: { enabled: true, title: "Tags", groupBy: "group", groupTitles: { topic: "By topic", type: "By type", level: "By level", }, locales: { ja: { title: "タグ", groupTitles: { topic: "トピック別", type: "種類別", level: "レベル別", }, }, }, }, }, ``` Each field is optional except `enabled`. Sensible defaults take over for anything you omit. ## `groupBy: "group"` vs `"flat"` The taglist can render in two modes. - **`groupBy: "group"`** — one column per vocabulary `group`, in the order the groups first appear in [`src/config/tag-vocabulary.ts`](./tag-governance.mdx). This is the default whenever the vocabulary is active (`tagVocabulary: true` and `tagGovernance !== "off"`). Each column's title comes from `groupTitles[]`, falling back to a capitalised version of the group name (`topic` → `Topic`). - **`groupBy: "flat"`** — a single alphabetised column titled `title`. Forced when the vocabulary is inactive, and a useful choice when you have fewer than a handful of tags and grouping would look sparse. Pick `"group"` once you have enough tags that a grouped view actually separates topics from types. Stick with `"flat"` until then. ## Locale Overrides Column titles are the only strings the taglist shows, so locale overrides stay small. The `locales` map keys on locale code (matching [`locales` in settings](./configuration.mdx)): ```ts taglist: { enabled: true, title: "Tags", groupTitles: { topic: "By topic", type: "By type", level: "By level" }, locales: { ja: { title: "タグ", groupTitles: { topic: "トピック別", type: "種類別", level: "レベル別" }, }, }, }, ``` Only keys present in the locale object override the default-locale strings; missing keys fall back. Tag ids themselves are **not** translated — see the [i18n section of the tags guide](./tags.mdx#i18n-behavior) for the reasoning. ## What Readers See Every vocabulary tag that at least one non-draft, non-unlisted page references appears as a link to that tag's index page (`/docs/tags//` or `/{locale}/docs/tags//`). Deprecated entries are skipped. Empty groups collapse — you only pay for the columns you fill. ## Related - [Tag governance](./tag-governance.mdx) — the vocabulary and group declarations that shape the taglist. - [Tags](./tags.mdx) — the per-page tag badge row and homepage tag index. - [Footer](./footer.mdx) — the rest of the footer configuration. --- # /packages/doc-history-server/CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/packages--doc-history-server **Path:** `packages/doc-history-server/CLAUDE.md` # doc-history-server Standalone package for document git history with dual modes: REST API server for local dev, CLI batch generator for CI builds. Extracted from `src/utils/doc-history.ts` to decouple expensive git operations from the Astro build pipeline. ## Tech Stack - **Node.js** — HTTP server + CLI (no framework dependency) - **Git** — `execFileSync` calls for log, show, follow - **TypeScript** — strict mode, ESM ## Commands - `pnpm dev -- --port 4322 --content-dir --locale :` — start REST API server - `pnpm generate -- --content-dir --locale : --out-dir ` — batch generate JSONs - `pnpm typecheck` — TypeScript type checking - `pnpm build` — build via tsup (ESM + DTS) ## Architecture ``` src/ ├── index.ts # Server entry — parses args, starts HTTP server ├── cli.ts # CLI entry — parses args, batch generates JSONs ├── args.ts # Shared argument parsing with bounds checking ├── server.ts # HTTP server (GET /doc-history/{slug}.json, /health) ├── git-history.ts # Core git logic (log, show, follow, rename tracking) ├── shared.ts # Shared helpers (getContentDirEntries) └── types.ts # DocHistoryEntry, DocHistoryData types ``` ### Server Mode (Local Dev) - Runs on configurable port (default 4322) - `GET /doc-history/{slug}.json` — returns full history for a document - `GET /doc-history/{locale}/{slug}.json` — locale-prefixed history - `GET /health` — health check - File index refreshes every 10 seconds (picks up new/renamed files) - CORS headers for cross-origin dev access ### CLI Mode (CI Build) - Generates `{slug}.json` files in the output directory - Reports progress and timing - Used by CI `build-history` job (parallel with Astro build) ### Astro Integration In dev mode, `src/integrations/doc-history.ts` proxies `/doc-history/*` requests to this server. In build mode, the Astro integration falls back to inline generation when `SKIP_DOC_HISTORY` is not set. Root `pnpm dev` runs both Astro and this server via `run-p`. ## Key Design Decisions - **Synchronous git** — `execFileSync` is acceptable for dev server (same as original integration). CI uses the CLI which is inherently sequential - **Repo-relative paths** — API responses use relative file paths to avoid leaking absolute server paths - **`--follow` for renames** — tracks file history across renames with multiple fallback strategies - **pnpm --filter paths** — when run via `pnpm --filter`, CWD is the package dir, so content paths need `../../` prefix for repo-relative resolution in CI ## CLI Arguments | Flag | Required | Default | Description | |------|----------|---------|-------------| | `--content-dir` | Yes | — | Content directory to scan | | `--locale :` | No | — | Additional locale (repeatable) | | `--out-dir` | CLI only | — | Output directory for JSONs | | `--port` | Server only | 4322 | Server port | | `--max-entries` | No | 50 | Max commits per file | --- # Mermaid Diagrams > Source: /pj/zudo-doc/docs/components/mermaid-diagrams Use fenced code blocks with the `mermaid` language to render diagrams. Mermaid is loaded on-demand — pages without mermaid blocks incur no overhead. ## Flowchart ```mermaid graph LR A[Start] --> B{Decision} B -->|Yes| C[Action] B -->|No| D[Other Action] C --> E[End] D --> E ``` ````mdx ```mermaid graph LR A[Start] --> B{Decision} B -->|Yes| C[Action] B -->|No| D[Other Action] C --> E[End] D --> E ``` ```` ## Sequence Diagram ```mermaid sequenceDiagram participant User participant App participant API User->>App: Click button App->>API: Fetch data API-->>App: JSON response App-->>User: Render result ``` ````mdx ```mermaid sequenceDiagram participant User participant App participant API User->>App: Click button App->>API: Fetch data API-->>App: JSON response App-->>User: Render result ``` ```` ## State Diagram ```mermaid stateDiagram-v2 [*] --> Draft Draft --> Review : Submit Review --> Published : Approve Review --> Draft : Request Changes Published --> Archived : Archive Archived --> [*] ``` ````mdx ```mermaid stateDiagram-v2 [*] --> Draft Draft --> Review : Submit Review --> Published : Approve Review --> Draft : Request Changes Published --> Archived : Archive Archived --> [*] ``` ```` ## Configuration Mermaid support is controlled by the `mermaid` setting in `src/config/settings.ts`: ```ts // ... mermaid: true, // enabled by default }; ``` See the [Mermaid documentation](https://mermaid.js.org/) for all supported diagram types. --- # Document History > Source: /pj/zudo-doc/docs/guides/doc-history ## Overview zudo-doc can show the git revision history for each documentation page. When enabled, a **History** button appears on each detail page, opening a side panel where you can browse past revisions and compare any two versions with a line-by-line diff viewer. This is useful for: - Reviewing how a document has evolved over time - Understanding what changed between revisions - Auditing content changes across your documentation This feature requires that your documentation is tracked in a git repository. History is extracted from git commits that touched each file. ## Enabling Document History Set `docHistory` to `true` in `src/config/settings.ts`: ```ts // ... docHistory: true, }; ``` The feature is **disabled by default** to keep builds fast for projects that don't need it. ## Per-Page Visibility Once `docHistory` is on globally, each page decides whether to display the **History** button via two rules: 1. **Auto-suppression on category-index pages** — pages whose entry id ends with `/index` (for example, `guides/index.mdx`) hide the button by default. These pages are typically navigation hubs or landing pages, where a revision history is rarely useful. 2. **Detail pages show the button by default** — every concrete leaf page (anything that is not a category index) shows the button. You can override the default per page with the `doc_history` frontmatter flag: ```yaml --- title: My Page doc_history: false # always hide the button on this page --- ``` ```yaml --- title: My Category Index doc_history: true # opt back in even though this is an index page --- ``` The frontmatter override wins in both directions: | Page type | No frontmatter | `doc_history: true` | `doc_history: false` | | --- | --- | --- | --- | | Detail page (leaf) | shown | shown | hidden | | Category-index page (`*/index`) | hidden | shown | hidden | Use `doc_history: true` on an index page when its content is substantial enough to warrant version tracking (for example, a category landing page with significant prose). Use `doc_history: false` on a detail page that is generated, ephemeral, or otherwise should not surface its commit log. ## Using the History Viewer Once enabled, each detail page shows a **History** button inside the [body foot util area](./body-foot-util-area.mdx) — the small right-aligned strip between the article and the footer. Category-index pages hide the button by default; see [Per-Page Visibility](#per-page-visibility) above. ### Browsing Revisions Click the button to open the revision panel. It slides in from the right and shows all git commits that modified the current document, newest first. Each entry displays: - **Commit hash** (short form) - **Date** of the commit - **Author** name - **Commit message** (first line) ### Comparing Revisions To view a diff between two revisions: 1. Select **A** (the older revision) by clicking the **A** button next to a commit 2. Select **B** (the newer revision) by clicking the **B** button next to another commit 3. Click the **Compare** button The diff viewer shows a **side-by-side comparison** — the older revision on the left, the newer revision on the right: - **Green background** — lines added in the newer revision (right side) - **Red background** — lines removed from the older revision (left side) - **Gray cells** — empty placeholders where one side has no corresponding line - **Unchanged lines** — context shown on both sides By default, A is set to the second-most-recent commit and B to the most recent, so you can immediately compare the latest change by clicking **Compare**. ### Keyboard Shortcuts - **Escape** — closes the history panel - The panel also closes automatically when navigating to another page ## How It Works Git history extraction is handled by a standalone package: `@zudo-doc/doc-history-server` (located at `packages/doc-history-server/`). This package operates in two modes, one for development and one for production builds. ### Build Mode In production, a standalone CLI generator batch-processes all content files and writes JSON history files to `dist/doc-history/`. This runs as an independent CI job, parallel to the Astro site build: - **build-site** job: shallow clone, builds the Astro site with `SKIP_DOC_HISTORY=1` - **build-history** job: full clone, runs `@zudo-doc/doc-history-server generate` - **deploy** job: merges both artifacts and deploys This parallel strategy reduces total CI build time because the history generation (which requires a full git clone and walks every commit) runs independently of the Astro build. The output structure mirrors the content directory: ``` dist/doc-history/ getting-started.json guides/writing-docs.json guides/color.json ja/getting-started.json # locale-prefixed ja/guides/writing-docs.json ``` ### Dev Mode In dev mode (`pnpm dev`), the doc-history-server runs as a standalone REST API server on port 4322, launched concurrently with the Astro dev server via `run-p`. An Astro integration proxies `/doc-history/*` requests from the browser to this server. When you open the history panel, it runs git commands on the fly -- no pre-generation needed. The server's file index refreshes every 10 seconds, so new or renamed files are picked up automatically. This means history is always up-to-date with your latest commits. Build time increases when `docHistory` is enabled because git history must be extracted for every content file. For large documentation sites with many files and deep history, this can add noticeable time to the build. The default limit is **50 revisions** per document. ### Production Output Size Since zudo-doc generates fully static sites with no server-side runtime, there is no server available to run `git` commands at request time. Instead, the build step pre-extracts all history data and ships it as static JSON files in the `dist/doc-history/` directory. Each JSON file contains the **full file content at every revision** (up to 50 commits). This means the total output size scales with: - Number of documentation files - Number of git commits per file - Size of each file at each revision For a small-to-medium documentation site this is typically negligible. For large sites with deep history, the `doc-history/` directory can grow significantly. These files are only fetched on demand (when a user clicks the History button), so they do not affect initial page load — but they do add to your deployment size. If deployment size is a concern, consider keeping `docHistory` disabled for production builds (`docHistory: false`) and only using it during local development, where history is served dynamically by the doc-history-server with no pre-generation cost. ## Localization Support Document history works with all configured locales. Each locale's content directory produces its own set of history files, prefixed with the locale code: - English docs: `/doc-history/{slug}.json` - Japanese docs: `/doc-history/ja/{slug}.json` - German docs: `/doc-history/de/{slug}.json` The history panel automatically requests the correct locale-specific history based on the current page. ## Technical Details ### Data Format Each document's history is stored as a JSON file with the following structure: ```ts interface DocHistoryData { slug: string; // Document route path filePath: string; // File path in the repository entries: Array<{ hash: string; // Full commit hash date: string; // ISO 8601 date author: string; // Commit author name message: string; // Commit message (first line) content: string; // Full file content at this revision }>; } ``` ### Diff Algorithm The diff viewer uses the [`diff`](https://www.npmjs.com/package/diff) library's `diffLines` function to compute line-level differences between two revisions, displayed in a side-by-side table layout similar to GitHub's split diff view. Adjacent removed and added blocks are paired into the same rows so you can see what changed at a glance. ### Rename Tracking The history extraction uses `git log --follow` to track file renames. If a document was moved or renamed, its full history (including commits before the rename) is preserved. The History component is a Preact island loaded with `client:idle`, meaning it only hydrates after the page becomes idle. This ensures it doesn't impact initial page load performance. ## The doc-history-server Package The history extraction logic lives in a standalone package (`packages/doc-history-server/`) rather than inside the Astro build pipeline. This design enables: - **Parallel CI builds** -- history generation and site build run as independent jobs - **Independent development** -- the server can be developed, tested, and versioned separately - **Flexible usage** -- the same package provides both a dev server and a CI generator For full details on the REST API endpoints, CLI arguments, data types, and architecture, see the [Doc History Server reference](/docs/reference/doc-history-server). --- # AI Chat Worker (Cloudflare) > Source: /pj/zudo-doc/docs/reference/ai-chat-worker Standalone Cloudflare Worker that provides an AI chat API endpoint, independent of the Astro documentation site. ## Overview The AI Chat Worker is a sub-package at `packages/ai-chat-worker/` that deploys as a Cloudflare Worker. It provides the same chat functionality as the built-in [AI Assistant API](/docs/reference/ai-assistant-api), but runs as a standalone service on Cloudflare Workers runtime. This is useful when: - You want to host the chat API independently from the documentation site - You're deploying the docs as a static site (no server-side rendering) - You want to use Cloudflare Workers runtime for the API backend The Worker fetches `llms-full.txt` from your deployed documentation site and uses it as context for Claude API calls. ## Endpoint ``` POST / Content-Type: application/json ``` The Worker responds at its root URL. ### Request Body ```ts interface AiChatRequest { message: string; history: ChatMessage[]; } interface ChatMessage { role: "user" | "assistant"; content: string; } ``` | Field | Type | Required | Description | | --------- | ---------------- | -------- | ------------------------------------------------------------------------ | | `message` | `string` | Yes | The user's current message. Must be non-empty, max 4000 characters. | | `history` | `ChatMessage[]` | Yes | Previous conversation messages. Invalid entries are filtered out silently. | ### Success Response (200) ```ts interface AiChatResponse { response: string; } ``` The `response` field contains the assistant's reply as a markdown string. ### Error Responses | Status | Condition | | ------ | ------------------------------ | | 400 | Invalid JSON body | | 400 | `message` is not a non-empty string | | 400 | `message` exceeds 4000 character limit | | 400 | Message rejected by input screening | | 405 | Request method is not POST | | 429 | Rate limit exceeded (includes `Retry-After` header) | | 500 | Anthropic API call failed | ## Security The Worker includes layered defenses against prompt injection and abuse: ### Hardened System Prompt The system prompt uses XML tags (``, ``) to clearly separate instructions from user-supplied content. Explicit guardrails instruct the model to: - Only answer questions about the provided documentation - Never reveal system instructions, configuration, or API keys - Reject attempts to override its instructions - Redirect off-topic questions back to documentation ### Input Screening Before a message reaches Claude, a lightweight regex-based filter (`src/input-screen.ts`) checks for common prompt injection patterns such as requests to ignore previous instructions, reveal configuration, or bypass restrictions. Matched messages are rejected with a 400 response. This filter runs before rate limiting so that injection attempts do not consume the caller's rate limit quota. ### API Key Isolation The `ANTHROPIC_API_KEY` is stored as a Cloudflare Worker secret and never included in the prompt context. Claude cannot leak what it does not know. ### Message Length Limit Messages are capped at 4000 characters. Longer messages are rejected with a 400 response before reaching the Claude API. ## Environment Setup ### Variables Set `DOCS_SITE_URL` in `wrangler.toml` to point at your deployed documentation site: ```toml title="packages/ai-chat-worker/wrangler.toml" [vars] DOCS_SITE_URL = "https://your-docs-site.example.com" RATE_LIMIT_PER_MINUTE = "10" RATE_LIMIT_PER_DAY = "100" ``` | Variable | Default | Description | | -------- | ------- | ----------- | | `DOCS_SITE_URL` | — | Your deployed documentation site URL | | `RATE_LIMIT_PER_MINUTE` | `10` | Max requests per IP per minute | | `RATE_LIMIT_PER_DAY` | `100` | Max requests per IP per day | The Worker fetches `${DOCS_SITE_URL}/llms-full.txt` to load documentation context. ### KV Namespace Rate limiting uses a Cloudflare KV namespace. Create it before deploying: ```sh cd packages/ai-chat-worker npx wrangler kv namespace create RATE_LIMIT ``` Update the `id` in `wrangler.toml` `[[kv_namespaces]]` with the returned namespace ID. ### Rate Limiting Behavior The Worker enforces per-IP rate limits using the `cf-connecting-ip` header provided by Cloudflare. - **Best-effort enforcement** — KV reads and writes are not atomic, so concurrent requests from the same IP may slightly exceed the configured limits - **Fail-open** — if KV is unavailable (outage, misconfiguration), requests are allowed through. Chat availability takes priority over strict rate enforcement - **Invalid config** — non-numeric values for `RATE_LIMIT_PER_MINUTE` or `RATE_LIMIT_PER_DAY` fall back to the defaults (10/min, 100/day) - **429 response** — includes a `Retry-After` header (seconds until the current window resets), exposed via CORS for browser access ### Audit Logging Every chat interaction is logged to KV for security analysis. This enables detection of prompt injection attempts and abuse patterns. **Logged fields:** | Field | Description | | ----- | ----------- | | `timestamp` | ISO 8601 timestamp | | `ipHash` | SHA-256 hash of the client IP (raw IP is never stored) | | `message` | User's message, truncated to 500 characters | | `responsePreview` | First 200 characters of the response | | `blocked` | Whether the request was rejected | | `blockReason` | `"rate_limit"`, `"invalid_input"`, or `"prompt_injection"` (when blocked) | **Storage details:** - Uses the same `RATE_LIMIT` KV namespace with `audit:` key prefix (separate from `rate:` keys) - Logs expire automatically after 7 days - Logging is fire-and-forget — failures do not affect the API response - IP addresses are hashed with SHA-256 via the Web Crypto API before storage ### Secrets Add the Anthropic API key as a Cloudflare Worker secret: ```sh cd packages/ai-chat-worker npx wrangler secret put ANTHROPIC_API_KEY ``` ## Deployment ### Manual ```sh cd packages/ai-chat-worker pnpm install pnpm run deploy ``` ### CI/CD The repository includes a GitHub Actions workflow (`.github/workflows/ai-chat-worker-deploy.yml`) that automatically deploys the Worker on push to `main` when files in `packages/ai-chat-worker/` change. Required GitHub secrets: - `CLOUDFLARE_API_TOKEN` — Cloudflare API token with Workers write permission - `CLOUDFLARE_ACCOUNT_ID` — Your Cloudflare account ID The workflow can also be triggered manually via `workflow_dispatch`. ## Relationship to AI Assistant The built-in [AI Assistant](/docs/guides/ai-assistant) runs as part of the Astro site using the `@astrojs/node` adapter. The AI Chat Worker is a standalone alternative that provides the same chat capability without requiring server-side rendering in the docs site. | Feature | Built-in AI Assistant | AI Chat Worker | | -------------------------- | ---------------------------- | -------------------------- | | Runtime | Node.js (Astro SSR) | Cloudflare Workers | | Deployment | Part of the docs site | Independent service | | Docs site requirement | Hybrid mode (SSR) | Static site is sufficient | | Documentation context | Loaded from local file | Fetched from deployed site | ## Sub-Package Location ``` packages/ai-chat-worker/ ├── src/ │ ├── index.ts # Worker entry point │ ├── audit-log.ts # Audit logging + IP hashing │ ├── claude.ts # Claude API integration + docs context fetching │ ├── cors.ts # CORS header handling │ ├── input-screen.ts # Prompt injection input screening │ ├── rate-limit.ts # Per-IP rate limiting via KV │ └── types.ts # Type definitions ├── wrangler.toml # Cloudflare Worker configuration ├── package.json ├── tsconfig.json └── README.md ``` --- # Body Foot Util Area > Source: /pj/zudo-doc/docs/guides/body-foot-util-area Every doc page renders a small utility strip between the article body and the footer. The strip hosts the Document History trigger and a "View source on GitHub" link, both configured through one settings block: `bodyFootUtilArea`. The History button used to render as a floating element. It now lives inside this area — see [Document History](./doc-history.mdx) for the feature itself. ## Default configuration ```ts // ... githubUrl: "https://github.com/zudolab/zudo-doc", docHistory: true, bodyFootUtilArea: { docHistory: true, viewSourceLink: true, }, }; ``` Both entries default to `true`. Set the whole block to `false` to hide the strip entirely: ```ts bodyFootUtilArea: false, ``` ## Fields | Field | Type | Description | |---|---|---| | `docHistory` | boolean | Show the Document History trigger inside the strip. Requires top-level `docHistory: true`. | | `viewSourceLink` | boolean | Show the "View source on GitHub" link. Requires top-level `githubUrl` to be set. | The strip is only rendered if at least one item is visible. If `docHistory: false` AND `viewSourceLink: false` (or both dependencies are off), the strip disappears and the page footer flows directly under the article. ## View source on GitHub When `viewSourceLink` is enabled and `githubUrl` is set, each page shows a link to its source `.mdx` file on GitHub. The URL is built as: ``` ${githubUrl}/blob/HEAD/${contentDir}/${entryId} ``` where `contentDir` is the page's content directory (e.g. `src/content/docs` for English, `src/content/docs-ja` for Japanese, or the `docsDir` of the active version) and `entryId` is the page's file path within that directory. | Page | Resolved URL | |---|---| | `/docs/guides/header-navigation` | `https://github.com/…/blob/HEAD/src/content/docs/guides/header-navigation.mdx` | | `/ja/docs/guides/header-navigation` | `https://github.com/…/blob/HEAD/src/content/docs-ja/guides/header-navigation.mdx` | The URL pins to `HEAD` — not a specific branch. GitHub resolves `HEAD` to your repo's default branch, so the link always points at the latest version without hard-coding a branch name. ### Japanese fallback pages If a Japanese page falls back to English content, the link still points to the actual file that rendered — the English `src/content/docs/` file in that case. ## Hiding a single item Turn off only the pieces you don't want: ```ts // Keep history, hide source link bodyFootUtilArea: { docHistory: true, viewSourceLink: false, }, ``` ```ts // Hide history, keep source link bodyFootUtilArea: { docHistory: false, viewSourceLink: true, }, ``` Toggling `docHistory` here is independent from the top-level `docHistory` flag: the top-level flag controls the overall feature (including build-time generation); the `bodyFootUtilArea.docHistory` flag just controls whether the trigger button appears inside the strip. --- # /packages/search-worker/CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/packages--search-worker **Path:** `packages/search-worker/CLAUDE.md` # search-worker Cloudflare Worker sub-package providing a server-side search API. Additional option for large doc bases — the primary search remains client-side MiniSearch (`src/components/search.astro`). ## Tech Stack - **Cloudflare Workers** — runtime - **Cloudflare KV** — rate limiting storage - **MiniSearch** — full-text search (same engine as client-side) - **TypeScript** — strict mode, `@cloudflare/workers-types` ## Commands - `pnpm dev` — local dev server (requires `wrangler.toml` with correct `DOCS_SITE_URL`) - `pnpm run deploy` — deploy to Cloudflare Workers via Wrangler - `pnpm typecheck` — TypeScript type checking ## Architecture ``` src/ ├── index.ts # Worker entry — routing, validation, CORS ├── cors.ts # CORS headers (exposes Retry-After) ├── rate-limit.ts # Per-IP rate limiting via KV (60/min, 1000/day) ├── search.ts # MiniSearch index loader + search logic └── types.ts # Env, request/response types ``` ### Request Flow 1. CORS preflight → `cors.ts` 2. Method + path check → 405/404 3. Hash client IP (SHA-256 via Web Crypto) 4. JSON parse + query validation → 400 (query required, max 500 chars) 5. Rate limit check → 429 with `Retry-After` 6. Fetch `search-index.json` from docs site (cached with 5-minute TTL) → `search.ts` 7. MiniSearch search with prefix, fuzzy, and boost → results ### Key Design Decisions - **Additive, not replacement** — client-side MiniSearch handles most users. Worker is for API consumers and huge doc bases - **Index from deployed site** — fetches `${DOCS_SITE_URL}/search-index.json`, same data as client-side - **5-minute cache TTL** — balances freshness with performance. Isolate recycle also clears cache - **Same search config as client** — `prefix: true, fuzzy: 0.2, boost: { title: 3, description: 2 }` ## Configuration - `DOCS_SITE_URL` — base URL of the deployed docs site (set in `wrangler.toml` `[vars]`) - `RATE_LIMIT` — KV namespace for rate limiting (create via `wrangler kv namespace create`) - `RATE_LIMIT_PER_MINUTE` / `RATE_LIMIT_PER_DAY` — configurable in `wrangler.toml` ## Conventions - All responses include CORS headers (including error responses) - Error responses use `{ error: string }` format - Rate limit uses `cf-connecting-ip` for client IP - Query length capped at 500 characters - Default result limit: 20, max: 100 --- # SiteTreeNav > Source: /pj/zudo-doc/docs/components/site-tree-nav ## Overview `SiteTreeNav` is a Preact island component that renders a responsive grid of expandable category cards showing the full documentation tree. It provides a sitemap-style index of all documentation pages, with categories that can be expanded and collapsed. This component is **not** directly available in MDX content. It requires server-side data fetching, so it must be imported in Astro page templates. The live demo below uses `SiteTreeNavDemo`, an Astro wrapper registered as a global MDX component. ## Live Example Below is `SiteTreeNav` rendered with the full documentation tree (same as the [home page](/)): ## Usage `SiteTreeNav` is used in `src/pages/index.astro` to display the home page sitemap: ```astro --- const categoryOrder = getCategoryOrder(); const { allDocs, categoryMeta } = await loadLocaleDocs("en"); const navDocs = allDocs.filter((doc) => !doc.data.unlisted); const tree = buildNavTree(navDocs, "en", categoryMeta); const groupedTree = groupSatelliteNodes(tree, categoryOrder); --- ``` The component requires `client:idle` (or another Astro client directive) because it uses React state for expand/collapse interactivity. ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `tree` | `NavNode[]` | (required) | The navigation tree built from content collection | | `categoryOrder` | `string[]` | — | Custom ordering of top-level categories | | `categoryIgnore` | `string[]` | — | Category slugs to hide from the tree | | `ariaLabel` | `string` | `"Site index"` | Accessibility label for the nav element | ## Features - **Responsive grid layout** — uses `repeat(auto-fill, minmax(min(18rem, 100%), 1fr))` to adapt from single-column on mobile to multi-column on wider screens - **Expandable categories** — each category card is collapsible, open by default - **Hierarchical tree** — nested pages are indented with connector lines showing the tree structure - **Category links** — top-level categories display an arrow icon and link to their index page - **Leaf pages** — individual doc pages are rendered as clickable links ## Source The component is defined at `src/components/site-tree-nav.tsx`. --- # Internationalization (i18n) > Source: /pj/zudo-doc/docs/guides/i18n zudo-doc supports multiple languages using Astro's built-in i18n routing. ## How It Works - **English (default):** `/docs/...` — content lives in `src/content/docs/` - **Japanese:** `/ja/docs/...` — content lives in `src/content/docs-ja/` The language switcher in the site header lets users toggle between available languages. ## Settings-Based Locale Configuration Locales are configured in `src/config/settings.ts`: ```ts // ... locales: { ja: { label: "JA", dir: "src/content/docs-ja" }, }, }; ``` For each locale entry, zudo-doc automatically: - Creates an Astro content collection (`docs-{code}`) - Registers the locale in Astro's i18n routing - Generates the appropriate page routes - Adds the locale to the language switcher This means adding a new locale only requires a settings entry and a content directory — no manual collection or route setup needed. ## Directory Structure Localized docs mirror the English directory structure: ``` src/content/ ├── docs/ │ ├── getting-started/ │ │ ├── introduction.mdx │ │ └── installation.mdx │ └── guides/ │ └── configuration.mdx └── docs-ja/ ├── getting-started/ │ ├── introduction.mdx │ └── installation.mdx └── guides/ └── configuration.mdx ``` ## Language Switcher The language switcher appears in the header's right edge. It shows the current language code (EN or JA) and a link to switch to the alternate language. The switcher automatically computes the alternate URL by adding or removing the `/ja/` prefix. ## UI Translation Keys zudo-doc uses a translation key system for built-in UI strings. Keys are organized by namespace: - `nav.*` — Header navigation labels (e.g., `nav.gettingStarted`, `nav.guides`) - `toc.*` — Table of contents labels - `search.*` — Search dialog text - `code.*` — Code block UI (copy button, etc.) - `doc.*` — Document-level UI (edit link, metadata labels, etc.) These keys allow the entire UI (not just content) to be localized across all configured languages. ## Astro Configuration i18n routing is configured in `astro.config.ts`: ```ts i18n: { defaultLocale: "en", locales: ["en", ...Object.keys(settings.locales)], routing: { prefixDefaultLocale: false, }, }, ``` With `prefixDefaultLocale: false`, English pages are served without a language prefix (`/docs/...`) while Japanese pages use the `/ja/` prefix (`/ja/docs/...`). The `locales` array in `astro.config.ts` is automatically derived from `settings.locales`, so you only need to maintain locales in one place. ## Adding a New Language To add a new language (e.g., Korean): 1. Add the locale to `settings.ts`: ```ts locales: { ja: { label: "JA", dir: "src/content/docs-ja" }, ko: { label: "KO", dir: "src/content/docs-ko" }, }, ``` 2. Create a content directory: `src/content/docs-ko/` 3. Mirror the English directory structure with translated files 4. Add a new page route at `src/pages/ko/docs/[...slug].astro` (copy from an existing locale route) 5. Add UI translations for the new locale Step 1 is the key step — adding the locale to `settings.ts` automatically creates the content collection and registers the i18n routing. The remaining steps provide the content and page routes. --- # Frontmatter Preview > Source: /pj/zudo-doc/docs/reference/frontmatter-preview The frontmatter preview block appears automatically below the page title whenever a document contains non-ignored frontmatter keys. It renders those custom fields as a compact key/value table — useful for surfacing document metadata such as authorship, status, or version without embedding it inline in the prose. This very page demonstrates the feature: the `author` and `status` fields you see rendered above were declared in its own frontmatter. ## When the Block Appears The block appears only when at least one frontmatter key survives filtering. Framework-managed keys (`title`, `description`, `sidebar_position`, `tags`, etc.) are stripped by default. If every key is on the ignore list, nothing is rendered. Set `frontmatterPreview: false` in `src/config/settings.ts` to disable the feature entirely for all pages. ## Default Ignore List The following keys are ignored by default. They correspond to every field defined in the content schema and would be redundant to show again: | Key | Reason | |-----|--------| | `title` | Rendered as the page `h1` | | `description` | Shown as a subtitle below the title | | `sidebar_position` | Internal navigation metadata | | `sidebar_label` | Internal navigation metadata | | `category` | Internal navigation metadata | | `tags` | Rendered separately as tag badges | | `search_exclude` | Build-time flag | | `pagination_next` | Build-time flag | | `pagination_prev` | Build-time flag | | `draft` | Build-time flag | | `unlisted` | Build-time flag | | `hide_sidebar` | Layout flag | | `hide_toc` | Layout flag | | `standalone` | Layout flag | | `slug` | URL override | | `generated` | Build-time flag | Any key not in this list will be shown automatically. ## Customizing the Ignore List The `frontmatterPreview` setting in `src/config/settings.ts` accepts two mutually exclusive knobs. ### `extraIgnoreKeys` — extend the defaults Add keys to the ignore list without discarding the defaults. Use this when you have a project-wide custom frontmatter field that should remain hidden everywhere: ```ts frontmatterPreview: { extraIgnoreKeys: ["reviewed_by", "internal_id"], }, ``` ### `ignoreKeys` — replace the defaults Completely replaces the built-in ignore list. Use this when you want full control over which keys are hidden. When `ignoreKeys` is present, `extraIgnoreKeys` is ignored. ```ts frontmatterPreview: { ignoreKeys: ["title", "description", "sidebar_position"], }, ``` Using `ignoreKeys` without including the standard schema keys can expose framework-internal fields like `draft` or `unlisted` in the rendered table. In most cases, `extraIgnoreKeys` is the safer choice. ## Disable the Feature Set `frontmatterPreview: false` to remove the block from all pages at once: ```ts frontmatterPreview: false, ``` ## Example The following frontmatter produces a preview table with `author` and `status` visible, while `title`, `description`, and `sidebar_position` are filtered out: ```mdx --- title: My Release Notes description: What changed in v2. sidebar_position: 5 author: Jane Doe status: released --- ``` The rendered table shows: | Key | Value | |-----|-------| | `author` | Jane Doe | | `status` | released | For the full configuration reference, see [Configuration — frontmatterPreview](../guides/configuration.mdx#frontmatterpreview). ## Custom Renderers By default, frontmatter values render as plain text. To replace a value with a styled component — a colored pill, a link, an icon — register a custom renderer in `src/config/frontmatter-preview-renderers.tsx`. ### Component contract Each renderer is a Preact component that receives `FrontmatterRendererProps`: | Prop | Type | Description | |------|------|-------------| | `value` | `NonNullable` | The frontmatter value (null/undefined values are never passed) | | `entryKey` | `string` | The frontmatter key name | | `data` | `Record` | Full frontmatter of the current page | | `locale` | `Locale \| undefined` | Active locale | ### Registering a renderer Open `src/config/frontmatter-preview-renderers.tsx` and add a key to the exported `frontmatterRenderers` map. Each value is a component function that receives `FrontmatterRendererProps` and returns JSX or `null`: ```tsx discount: ({ value }) => { if (value !== true) return null; return ( ON SALE ); }, ``` This page's own frontmatter includes `discount: true`, `status: "stable"`, and `difficulty: "beginner"` — the metadata table at the top of this page shows them rendered as colored pills. ### Ignore-list precedence A renderer registered for an ignored key has no effect. The ignore list is applied _before_ renderer lookup — if a key is suppressed, the row is never rendered and the renderer is never called. To surface a framework-managed key (such as `draft`) with a custom renderer, first remove it from the ignore list in `src/config/settings.ts`: ```ts frontmatterPreview: { // Must explicitly list all keys to keep hidden — ignoreKeys replaces the defaults ignoreKeys: ["title", "description", "sidebar_position", "tags"], }, ``` `ignoreKeys` replaces the entire default ignore list. Omitting standard schema keys like `tags`, `slug`, or `unlisted` will expose them in the preview table. In most cases, use `extraIgnoreKeys` to extend the defaults instead. Register your renderers in `src/config/frontmatter-preview-renderers.tsx` in your project. The empty map ships by default. --- # /src/CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/src **Path:** `src/CLAUDE.md` # Source Code Rules ## Design Token System Uses a 16-color palette system. ### Three-Tier Color Strategy **Tier 1 — Palette** (injected by `ColorSchemeProvider` on `:root`): - `--zd-bg`, `--zd-fg`, `--zd-sel-bg`, `--zd-sel-fg`, `--zd-cursor` - `--zd-0` through `--zd-15` (16 palette slots) **Tier 2 — Semantic tokens** (in `global.css` `@theme`, resolved per scheme): - Palette access: `p0`–`p15` → `bg-p0`, `text-p8`, `border-p1`, etc. - Base: `bg`, `fg` → `bg-bg`, `text-fg` - UI: `surface`, `muted`, `accent`, `accent-hover`, `sel-bg`, `sel-fg` - Content: `code-bg`, `code-fg`, `success`, `danger`, `warning`, `info` **Tier 3 — Component tokens** (scoped to specific components): - Content: `.zd-content` direct element styling in `global.css` (consumes Tier 2) Each tier only references the tier above it. ### Color Rules - **NEVER** use Tailwind default colors (`bg-gray-500`, `text-blue-600`) — they are reset to `initial` - **NEVER** use hardcoded color values (`rgba()`, `#hex`, `rgb()`) — use semantic tokens or `color-mix()` with tokens - **ALWAYS** use project tokens: `text-fg`, `bg-surface`, `border-muted`, `text-accent`, etc. - Prefer semantic tokens (`text-accent`, `bg-code-bg`, `text-danger`) for standard UI - Use palette tokens (`p0`–`p15`) only when no semantic token fits - For overlays/backdrops: use `bg-overlay/{opacity}` (e.g., `bg-overlay/50`) or `color-mix(in oklch, var(--color-overlay) 50%, transparent)` in CSS - For highlights (search, find-in-page): use `color-mix()` with `var(--color-warning)` at varying opacity levels - Acceptable exceptions: CSS fallback values (`var(--color-fg, #fff)`), color manipulation code (e.g., color-tweak-panel), intentional theme-independent colors (e.g., white iframe canvas with a comment explaining why) ### Changing Scheme - Edit `colorScheme` in `src/config/settings.ts` - Available: Dracula, Catppuccin Mocha, Nord, TokyoNight, Gruvbox Dark, Atom One Dark - Add schemes in `src/config/color-schemes.ts` (22 color props + `shikiTheme`) - `ColorRef` type: `background`, `foreground`, `cursor`, `selectionBg`, `selectionFg`, and semantic overrides accept `number | string` — number = palette index, string = direct color ### Color Tweak Panel - Enabled via `colorTweakPanel: true` in settings - Interactive panel at page bottom for live color editing (palette, base, semantic tokens) - Export button generates `ColorScheme` TypeScript code for clipboard copy - State persisted in `localStorage` (`zudo-doc-tweak-state`) ### Three-Tier Font-Size Strategy Uses the same three-tier approach as colors: abstract scale → semantic roles → component usage. **Tier 1 — Abstract scale** (`--text-scale-*` in `:root`, NOT `@theme`): - Raw size values only: `2xs` (12px), `xs` (14px), `sm` (16px), `md` (19.2px), `lg` (22.4px), `xl` (48px), `2xl` (60px) - Kept in `:root` intentionally — avoids generating Tailwind `text-scale-*` utility classes that would bypass the semantic layer - **NEVER** use scale tokens directly in components — they exist only as a single source of truth for Tier 2 **Tier 2 — Semantic tokens** (`--text-*` in `@theme`, reference Tier 1): - `micro` (2xs/12px), `caption` (xs/14px), `small` (sm/16px), `body` (md/19.2px), `subheading` (lg/22.4px), `heading` (xl/48px), `display` (2xl/60px) - Use these via Tailwind classes: `text-body`, `text-caption`, `text-micro`, `text-heading`, etc. **Tier 3 — Component usage** (Tailwind classes in markup): - Components consume Tier 2 tokens: ``, `` - `.zd-content` typography in `global.css` also references Tier 2 tokens To add a new font size: add the raw value to Tier 1, then create a semantic token in Tier 2 that references it. ## Two-Tier Size Strategy Element dimensions (icons, toggles, etc.) follow a two-tier approach: **Tier 1 — Semantic tokens** (in `global.css` `@theme`): shared design decisions with meaningful names. - Icon sizes: `icon-xs` (12px), `icon-sm` (16px), `icon-md` (20px), `icon-lg` (24px) - Usage: `w-icon-sm h-icon-sm`, `w-icon-md h-icon-md`, etc. - Add new tokens only when a size is used in 2+ unrelated components with the same semantic role **Tier 2 — Arbitrary values**: one-off component dimensions that don't recur. - Example: `w-[1.575rem]` for a breadcrumb home icon, `h-[3rem]` for a toggle button height - Keep as arbitrary values until the pattern recurs enough to justify a token **Rules:** - No abstract numeric scale (no `size-4`, `size-8`) — semantic names only - Tokenize when 2+ components share the same size for the same purpose (e.g., "standard icon") - Keep arbitrary values for layout dimensions, modal sizes, and component-specific one-offs ## CSS & Components - Before writing or editing CSS, Tailwind classes, color tokens, or component markup, invoke `/zudo-doc-design-system` to load project-specific rules - Tailwind v4: imports `tailwindcss/preflight` + `tailwindcss/utilities` (no default theme) - No `--*: initial` resets needed — default theme is simply not imported - Content typography: component-first approach — major HTML elements (h2-h4, p, a, strong, blockquote, ul, ol, table) are overridden via Preact components in `src/components/content/` registered through `component-map.ts`. Minor elements (li, th/td, code, pre, hr, img, h5/h6, dt/dd, etc.) and structural rules (flow-space, consecutive heading tightening, hash-links) remain in `.zd-content` in `global.css`. - **Component-first strategy**: always use Tailwind utility classes directly in component markup — never create CSS module files or custom CSS class names. The component itself is the abstraction. - **Tight token strategy**: prefer existing spacing (`hsp-*`, `vsp-*`), typography (`text-caption`, `text-small`, etc.), and color tokens. Avoid arbitrary values (`text-[0.8rem]`, `py-[0.35rem]`) when an existing token is close enough. --- # CategoryNav > Source: /pj/zudo-doc/docs/components/category-nav ## Overview `CategoryNav` is an Astro component that automatically lists child pages of a category as a card grid. It is designed for use in category `index.mdx` files to create landing pages that link to all child pages. `CategoryNav` is globally available in all MDX doc pages — no import needed. It is registered alongside the admonition components (`Note`, `Tip`, etc.) in the doc page route. ## Live Example Here is `CategoryNav` rendering the Getting Started category: ## Usage Use `CategoryNav` in a category's `index.mdx` to create a landing page: ```mdx --- title: Getting Started sidebar_position: 0 --- Welcome to the Getting Started section. Choose a topic below to begin. ``` Since `CategoryNav` is an Astro component (zero client-side JavaScript), no `client:` directive is needed. ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `category` | `string` | (required) | The category slug (e.g., `"guides"`, `"reference"`) | | `lang` | `Locale` | auto-detected | Override the locale for the content collection | The `lang` prop is automatically detected from the current URL path, so you typically don't need to set it. ## Features - **2-column grid** — displays `sm:grid-cols-2` on desktop, single column on mobile - **Card display** — each card shows the page title (as a link) and description (from frontmatter) - **Hover effect** — card border highlights on hover; the title link is always underlined - **Auto-excludes index** — the category's own index page is automatically filtered out - **Sorted by position** — cards follow `sidebar_position` ordering from frontmatter ## Where to Use Add `CategoryNav` to any category's `index.mdx` to generate a landing page. For example, the Getting Started section uses it: ``` src/content/docs/ getting-started/ index.mdx ← uses introduction.mdx installation.mdx writing-docs.mdx ``` ## Source The component is defined at `src/components/category-nav.astro`. --- # Claude Code Resources > Source: /pj/zudo-doc/docs/guides/claude-resources ## Overview zudo-doc can automatically generate documentation pages from your project's Claude Code resources — CLAUDE.md files, custom commands, skills, and agents. When enabled, these appear under the **Claude** header navigation section. ## Enabling Claude Resources Set `claudeResources` in `src/config/settings.ts`: ```ts claudeResources: { claudeDir: ".claude", }, ``` Set to `false` to disable: ```ts claudeResources: false, ``` ## What Gets Generated The integration scans your `.claude/` directory and generates MDX documentation pages for: | Resource | Source | Output | |----------|--------|--------| | CLAUDE.md files | Project root and subdirectories | `claude-md/` | | Commands | `.claude/commands/*.md` | `claude-commands/` | | Skills | `.claude/skills/*/SKILL.md` | `claude-skills/` | | Agents | `.claude/agents/*.md` | `claude-agents/` | Each resource type gets its own index page with a list of all items, plus individual detail pages. ## How It Works The integration runs at build time during the `astro:config:setup` hook: 1. Scans the configured `.claude/` directory 2. Discovers CLAUDE.md files, commands, skills (with references), and agents 3. Generates MDX files in `src/content/docs/claude-*/` directories 4. These files are picked up by Astro's content collections and rendered as documentation pages The generated pages appear under the **Claude** header navigation tab, configured via `headerNav` with `categoryMatch: "claude"`. Generated files are written to `src/content/docs/` and are recreated on every build. Do not edit them manually — your changes will be overwritten. ## Configuration Options | Option | Type | Description | |--------|------|-------------| | `claudeDir` | string | Path to the `.claude/` directory (relative to project root) | | `projectRoot` | string | Optional project root override for resolving CLAUDE.md file paths | The `projectRoot` option is useful in monorepo setups where the `.claude/` directory is not at the project root. Skills with a `references/` subdirectory will have their reference documents included as separate linked pages, making it easy to browse bundled knowledge bases. --- # llms.txt > Source: /pj/zudo-doc/docs/guides/llms-txt zudo-doc can automatically generate `llms.txt` and `llms-full.txt` files, making your documentation easily consumable by AI tools and large language models. The [llms.txt standard](https://llmstxt.org/) is an emerging convention that helps AI tools discover and read your documentation. Many AI-powered tools already look for these files. ## How It Works When enabled, zudo-doc generates two files at build time: - **`/llms.txt`** — An index listing all documentation pages with their titles, descriptions, and URLs - **`/llms-full.txt`** — The full content of all documentation pages combined into a single file These files provide a machine-friendly view of your documentation that AI tools can consume without parsing HTML. ## Enabling llms.txt The feature is **enabled by default**. To explicitly control it, set `llmsTxt` in `src/config/settings.ts`: ```ts // ... llmsTxt: true, // enabled by default }; ``` To disable it: ```ts llmsTxt: false, ``` ## Generated Output ### llms.txt (Index) The index file lists each documentation page with its title, description, and URL: ``` # My Documentation > Site description from settings ## Docs - [Introduction](/docs/getting-started/introduction): Learn what the project is and how it works. - [Configuration](/docs/guides/configuration): Global settings and configuration reference. ``` ### llms-full.txt (Full Content) The full content file includes the complete text of every documentation page, separated by headings: ``` # My Documentation > Site description from settings ## Introduction Welcome to the project... ## Configuration The project is configured through... ``` ## Page Exclusion Pages with any of the following frontmatter fields are automatically excluded from both generated files: - `draft: true` — Draft pages excluded from build - `unlisted: true` — Built but hidden pages - `search_exclude: true` — Pages excluded from search ```mdx --- title: Internal Notes search_exclude: true --- ``` ## Multi-Locale Support When locales are configured, zudo-doc generates separate llms.txt files for each locale: - **English (default):** `/llms.txt` and `/llms-full.txt` - **Japanese:** `/ja/llms.txt` and `/ja/llms-full.txt` Each locale's files contain only the pages from that locale's content directory. URLs in each file use the appropriate locale prefix. ## HTML Discovery When `llmsTxt` is enabled, zudo-doc adds `` tags to the `` of every page, helping AI tools discover the files even when the site is deployed at a subpath: ```html ``` This supplements the standard path-based discovery (`/llms.txt` at the domain root) which may not work when your site uses a `base` path like `/pj/zudo-doc/`. ## Dev Mode The generated files are available during development via Vite middleware. When running `pnpm dev`, requests to `/llms.txt` and `/llms-full.txt` are handled dynamically — no build step needed. No need to build before testing. The llms.txt files work immediately with `pnpm dev`, just like search. ## Production Build During `pnpm build`, the files are written to the `dist/` directory as static text files alongside the rest of your site. --- # Design Token Panel > Source: /pj/zudo-doc/docs/reference/design-token-panel The Design Token Panel is an in-page editor for every design token the theme ships with. It replaces the old single-purpose Color Tweak Panel with a tabbed UI that covers the four token families — **Spacing**, **Font**, **Size**, and **Color** — and adds a unified JSON export/import workflow so you can round-trip a whole design through an AI assistant and back. ## Enabling the Panel Set `designTokenPanel` to `true` in `src/config/settings.ts`: ```ts title="src/config/settings.ts" designTokenPanel: true, // ... }; ``` When enabled, a palette icon appears in the header (to the left of the search icon). Clicking it toggles the panel open/closed; the same keyboard shortcut that opened the old Color Tweak Panel still works. `designTokenPanel` replaces the previous `colorTweakPanel` setting. The old key is still accepted as a deprecated alias for one release — set either one to `true` and the panel is enabled. New projects should use `designTokenPanel`. ## The Four Tabs The panel is divided into four tabs, each focused on a single token family. Tabs expose nothing but the tokens they own — you never have to scroll past colors to reach spacing. ### Spacing Sliders for the horizontal (`hsp-*`) and vertical (`vsp-*`) spacing scale. Each step (`3xs` → `2xl`) has its own slider with a live preview strip that shrinks and grows as you drag. Values are clamped to sensible minimums so layouts never collapse. ### Font Controls for the typography scale: - Font family selector (stack preset or custom) - Raw scale tokens (`2xs` → `2xl`) via numeric text inputs with CSS-value sanitization - Semantic role mapping (`caption`, `body`, `heading`, …) that maps each role back to a scale step The font family input accepts any valid CSS `font-family` value, including multi-stack fallbacks. Invalid values (unbalanced quotes, raw JavaScript, etc.) are rejected on change so pasted snippets cannot break the page. ### Size Pill-style sliders for the semantic icon-size tokens (`icon-xs` → `icon-lg`) and any component dimensions that graduated from arbitrary values to semantic tokens. Each slider snaps to the px value expected by the two-tier size strategy. ### Color The Color tab is the previous Color Tweak Panel, now scoped to its own tab. It contains: - The 16-color palette (`p0`–`p15`) - Base theme colors (`bg`, `fg`, `cursor`, `sel-bg`, `sel-fg`) - Semantic token overrides (`accent`, `code-bg`, `success`, `danger`, …) - The **Scheme…** dropdown that loads any of the 50+ bundled presets #### Matched-keyword tokens Two semantic color tokens drive how matched keywords are highlighted. They live alongside the other semantic overrides in the Color tab and are consumed by `src/components/search.astro` to style hits in the search UI. - `matchedKeywordBg` — background color painted behind a matched keyword. - `matchedKeywordFg` — foreground (text) color of the matched keyword on top of that background. ## Keyboard Accessibility The tab strip implements the standard WAI-ARIA tabs pattern: | Key | Action | |---|---| | `←` / `→` | Move focus to the previous / next tab | | `Home` | Focus the first tab | | `End` | Focus the last tab | | `Enter` / `Space` | Activate the focused tab | Tabs use automatic activation — moving focus with the arrow keys also switches the panel. Within each tab, all sliders, selects, and text inputs are reachable via `Tab`, and every control exposes a visible focus ring. ## JSON Export — diff-only by default Click **Export** in the panel header to open the export modal. The modal shows a single JSON document describing every tab's current state. By default the export is a **diff against the defaults** — only tokens you have actually changed are included. Untouched families are omitted entirely, and within a changed family only the individual tokens you edited are kept. The result is small, easy to diff, and stable enough to paste into a commit. ```json { "version": 2, "spacing": { "hsp": { "md": "1.25rem" } }, "color": { "palette": { "6": "#7aa2f7" }, "semanticMappings": { "accent": 6 } } } ``` Flip the **Show defaults too** toggle in the modal header to emit the full token tree (every family, every token, including untouched defaults). Use this form when you want a complete snapshot to hand to a designer or AI that does not know your theme. ## Load-from-JSON The **Load…** button opens the import modal. Paste a JSON document — either from an earlier export or one produced by an AI — and the panel: 1. Validates the shape (version, known keys, well-formed values). 2. Reports any unknown keys or malformed values as non-fatal warnings. 3. Merges the document over the current defaults. Missing tokens keep their defaults, explicit tokens override them. 4. Persists the result to `localStorage` and re-applies every CSS custom property immediately. Invalid JSON is rejected with a specific error message; partial imports (e.g. a JSON that only changes `spacing.hsp.md`) are a first-class use case. ## AI Workflow The diff-only export and Load-from-JSON together enable a simple round-trip with any chat-based AI: 1. **Tweak** anything you want to hand off — or leave the panel untouched. 2. **Export** the diff and copy the JSON. 3. **Paste** the JSON into an AI chat with a natural-language request, for example: > Here is my current design token diff. Rework this into a warmer, higher-contrast typographic scale while keeping the color palette untouched, and return only the JSON. 4. **Paste** the AI's response into **Load…**. 5. The panel validates, merges, and applies the new tokens instantly. Hit **Reset all** in the panel header to roll back if you don't like the result. Because the default export is a diff, the AI only sees the tokens you care about — it doesn't have to reason around dozens of unchanged defaults. Turn on **Show defaults too** only when the AI needs the complete picture (for example, producing a new theme from scratch). ## Persistence and Migration State is persisted to `localStorage` under the key `zudo-doc-tweak-state-v2`. The v2 format is a single object containing every tab's state side-by-side, so a single read restores the whole panel. Older projects that persisted the v1 Color-Tweak state under `zudo-doc-tweak-state` are migrated automatically: - On load, the panel reads v2 first. If absent, it falls back to parsing the v1 state (which only contained color tokens) and promotes it into the v2 color tab. - The FOUC-prevention script injected into `` by `doc-layout.astro` performs the same v2-then-v1 lookup so saved colors apply before first paint on old devices too. - Clicking **Reset all** in the panel header clears both keys, restoring the color scheme declared in `src/config/settings.ts`. ## Settings Reference | Setting | Type | Description | |---|---|---| | `designTokenPanel` | `boolean` | Enables the panel. Default `false`. | | `colorTweakPanel` | `boolean \| undefined` | **Deprecated** alias for `designTokenPanel`. When either is truthy the panel is enabled. Will be removed in a future release. | The tokens themselves — spacing scale, font scale, icon sizes, palette — are still configured in `src/styles/global.css` (`@theme`) and `src/config/color-schemes.ts`. The panel only tweaks the in-browser copy of those tokens; it never writes to source files. When you've found a combination you like, export the diff and paste it into a commit message or design doc — it's the smallest reproducible description of the change you just made. --- # /src/config/CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/src--config **Path:** `src/config/CLAUDE.md` # src/config Project-level configuration: `settings.ts`, color schemes, tag vocabulary, sidebars, and i18n. ## Tag Vocabulary `tag-vocabulary.ts` is the canonical list of tags for this project. Every tag used in `src/content/docs/**/*.mdx` (and locale mirrors) should have an entry — otherwise strict mode rejects it at `pnpm check` time. Two settings control behaviour, and they are **orthogonal**: | Setting | Controls | Default | |---|---|---| | `tagVocabulary` | whether `tag-vocabulary.ts` is consulted at runtime (alias resolution, deprecation filtering, grouped footer). `false` ignores the file entirely. | `true` | | `tagGovernance` | enforcement level when the vocabulary is consulted. `"off"` disables, `"warn"` lets builds pass but audit reports unknowns, `"strict"` rejects unknowns at Zod validation. | `"warn"` | ### Entry shape ```ts { id: string; // canonical tag id label?: string; // display label (defaults to id) description?: string; // short description for tooling group?: string; // "topic" | "type" | "level" | ... aliases?: string[]; // alternate strings content may use deprecated?: true | { redirect?: string }; } ``` ### Phasing out a tag Never silently delete an entry — existing docs may still reference it. Options: - **Rename**: add the old id as an alias of the new canonical entry. - **Redirect**: keep the old entry, set `deprecated: { redirect: "" }`. - **Retire entirely**: set `deprecated: true`. The tag is dropped from aggregation; the id is still accepted by the Zod schema so builds keep passing. ### resolveTag / resolvePageTags `src/utils/tags.ts` exports `resolveTag(raw)` and `resolvePageTags(raw[])`. Both are no-ops when the vocabulary is inactive (`tagVocabulary: false` or `tagGovernance: "off"`). Covered by `src/utils/__tests__/tags.test.ts`. --- # CategoryTreeNav > Source: /pj/zudo-doc/docs/components/category-tree-nav ## Overview `CategoryTreeNav` is an Astro component that displays child pages of a category as a nested tree of links (`ul > li > ul > li` pattern). It is an alternative to `CategoryNav` which uses a card-grid style — `CategoryTreeNav` is more compact and suited to categories with deeper nesting. `CategoryTreeNav` is globally available in all MDX doc pages — no import needed. It is registered alongside the admonition components (`Note`, `Tip`, etc.) in the doc page route. ## Live Example Here is `CategoryTreeNav` rendering the Guides category: ## Usage Use `CategoryTreeNav` in a category's `index.mdx` to create a compact tree-style listing: ```mdx --- title: Getting Started sidebar_position: 0 --- Welcome to the Getting Started section. ``` Since `CategoryTreeNav` is an Astro component (zero client-side JavaScript), no `client:` directive is needed. ## Comparison with CategoryNav | Feature | CategoryNav | CategoryTreeNav | |---------|-------------|-----------------| | Layout | 2-column card grid | Nested tree list | | Description | Shown in each card | Shown inline after title | | Nesting | Flat (children only) | Up to 3 levels deep | | Best for | Landing pages, overviews | Deep hierarchies, compact listings | ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `category` | `string` | (required) | The category slug (e.g., `"guides"`, `"reference"`) | | `lang` | `Locale` | auto-detected | Override the locale for the content collection | The `lang` prop is automatically detected from the current URL path, so you typically don't need to set it. ## Source The component is defined at `src/components/category-tree-nav.astro`. --- # HtmlPreview > Source: /pj/zudo-doc/docs/components/html-preview `` renders live HTML/CSS demos inside an isolated iframe with viewport presets (Mobile / Tablet / Full) and a collapsible source code panel with syntax highlighting. It is globally available in all MDX files without imports. ## Basic Usage Hello, world! `} css={` .box { padding: 24px; background: #3b82f6; color: #fff; border-radius: 8px; font-family: system-ui, sans-serif; text-align: center; } `} /> ````mdx Hello, world! `} css={` .box { padding: 24px; background: #3b82f6; color: #fff; border-radius: 8px; font-family: system-ui, sans-serif; text-align: center; } `} /> ```` ## Responsive Layout Use the viewport buttons (Mobile / Tablet / Full) to see how content reflows at different widths. Try resizing with the drag handle at the bottom-right of the preview area. Card 1 Card 2 Card 3 Card 4 `} css={` .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; padding: 16px; font-family: system-ui, sans-serif; } .card { padding: 20px; background: #f1f5f9; border: 1px solid #e2e8f0; border-radius: 6px; text-align: center; color: #334155; } `} /> ## HTML Only When no `css` prop is provided, only the HTML source is shown. First item Second item Third item `} /> ## Default Open Code Use `defaultOpen` to show the source code expanded by default. Click me `} css={` .btn { padding: 10px 20px; background: #8b5cf6; color: #fff; border: none; border-radius: 6px; font-size: 14px; font-family: system-ui, sans-serif; cursor: pointer; } .btn:hover { background: #7c3aed; } `} /> ## Fixed Height Use the `height` prop to set a fixed iframe height instead of auto-sizing. This preview has a fixed height of 300px. Content that exceeds this height will scroll within the iframe. `} css={` .scroll-content { padding: 16px; font-family: system-ui, sans-serif; color: #334155; line-height: 1.6; } `} /> ## External Resources You can inject external resources (CSS frameworks, webfonts, scripts) into previews. Configure globally via `settings.ts` or per-component via props. ### Global Configuration Set `htmlPreview` in `src/config/settings.ts` to apply resources to all previews: ```ts htmlPreview: { head: ``, css: `body { font-family: 'Noto Sans JP', sans-serif; }`, js: `console.log('preview loaded');`, } ``` ### Per-Component Props Use `head` and `js` props to add resources to individual previews. These are merged after global values. Waiting... `} css={` #output { padding: 16px; font-family: system-ui, sans-serif; color: #334155; } `} js={` document.getElementById('output').textContent = 'Hello from JS!'; `} /> ````mdx Waiting... `} css={` #output { padding: 16px; font-family: system-ui, sans-serif; color: #334155; } `} js={` document.getElementById('output').textContent = 'Hello from JS!'; `} /> ```` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `html` | string | (required) | HTML content to render inside the preview iframe | | `css` | string | `undefined` | CSS styles applied inside the preview iframe | | `head` | string | `undefined` | Raw HTML injected into `` (links, meta, fonts) | | `js` | string | `undefined` | JavaScript executed inside the preview iframe | | `title` | string | `undefined` | Title displayed in the preview header bar | | `height` | number | auto | Fixed iframe height in pixels. When omitted, height auto-adjusts to content | | `defaultOpen` | boolean | `false` | Show the source code panel expanded by default | ## Supported Languages The source code panel uses a lightweight Shiki instance with the following languages: - `html` — for the HTML and Head panels - `css` — for the CSS panel - `javascript` — for the JS panel Unsupported languages fall back to plain text (no highlighting). This is a smaller subset than the [full language list](/docs/components/basic-components#supported-languages) available in standard code blocks. ## Notes - The preview renders inside an isolated `` with a CSS reset (Tailwind v4 preflight), so styles do not leak in or out. - Previews run without a `sandbox` attribute. The `srcdoc` content is author-controlled MDX, so no additional isolation is needed. - Global resources from `settings.htmlPreview` are injected before per-component props. - The `html`, `css`, and `js` props support template literals with indentation. Leading whitespace is automatically stripped (dedented) in the source code display. - Client-side hydration is handled automatically by the component wrapper — no `client:load` directive needed in MDX. --- # Documentation Skill Symlinker > Source: /pj/zudo-doc/docs/guides/doc-skill-symlinker ## Overview The **doc skill symlinker** creates a [Claude Code skill](https://docs.anthropic.com/en/docs/claude-code/skills) from your documentation content. Once set up, you can invoke the skill inside any Claude Code session to look up your docs — making all your written documentation available as AI context. This is especially useful when: - Working on another project that depends on this one - Answering questions about configuration, components, or usage patterns - Sharing institutional knowledge with your team through Claude Code ## How It Works The setup script: 1. Creates a `.claude/skills//` directory in your project 2. Generates a `SKILL.md` that tells Claude how to use your docs 3. Symlinks the `src/content/docs/` directory into the skill folder 4. Symlinks the skill into `~/.claude/skills/` so it's available globally After setup, the skill appears as a slash command in any Claude Code session. ## Setup Run the setup script: ```bash pnpm run setup:doc-skill ``` You'll be prompted for a skill name. The default is `-wisdom` (e.g., `zudo-doc-wisdom`). ``` === zudo-doc Skill Setup === Skill name [zudo-doc-wisdom]: Created docs symlink: ... Created docs-ja symlink: ... Generated SKILL.md Done! Skill 'zudo-doc-wisdom' is ready. ``` ## Using the Skill Once set up, use the skill in any Claude Code session: ``` /zudo-doc-wisdom sidebar ``` Claude will look up the relevant documentation article and use it to answer your question. This works from any project directory — the skill is symlinked globally. ### Example Workflow Suppose you're building a site with zudo-doc and want to know how to configure the footer: ``` /zudo-doc-wisdom footer configuration ``` Claude reads the footer guide from your docs and provides an accurate, documentation-backed answer. ## What Gets Created After running the setup script, the following files are created: ``` .claude/skills// ├── SKILL.md # Skill definition with frontmatter and instructions ├── docs -> src/content/docs/ # Symlink to English docs └── docs-ja -> src/content/docs-ja/ # Symlink to Japanese docs (if present) ``` And a global symlink: ``` ~/.claude/skills/ -> .claude/skills/ ``` The `.claude/skills//` directory contains symlinks and a generated `SKILL.md`. It is safe to add to `.gitignore` if you prefer, or commit it to share the skill definition with your team. ## Re-running Setup You can re-run the setup script at any time to regenerate the skill. Existing symlinks are replaced automatically. ```bash pnpm run setup:doc-skill ``` If you rename your project or change the docs directory structure, re-run the setup script to update the skill. --- # Versioning > Source: /pj/zudo-doc/docs/guides/versioning zudo-doc supports maintaining multiple versions of your documentation side by side. When versioning is enabled, older (or pre-release) versions are served under version-prefixed URLs, and a version switcher appears in the layout for users to navigate between versions. ## Overview Versioning lets you: - Keep the **latest** documentation at `/docs/...` as usual - Serve older versions at `/v/{version}/docs/...` - Show a **version switcher** so users can navigate between versions - Display **version banners** to indicate outdated or unreleased content ## Enabling Versioning Versioning is **disabled by default**. To enable it, add a `versions` array in `src/config/settings.ts`: ```ts // ... versions: [ { slug: "1.0", label: "1.0.0", docsDir: "src/content/docs-v1", locales: { ja: { dir: "src/content/docs-v1-ja" }, }, banner: "unmaintained", }, ], }; ``` Set `versions` to `false` (or omit it) to disable versioning: ```ts versions: false, ``` ## Configuration Reference Each version entry accepts the following properties: | Property | Type | Required | Description | |----------|------|----------|-------------| | `slug` | string | Yes | Version identifier used in URL paths (e.g., `"1.0"`, `"v2"`) | | `label` | string | Yes | Display label shown in the version switcher (e.g., `"1.0.0"`) | | `docsDir` | string | Yes | Content directory for this version's English docs | | `locales` | object | No | Per-locale content directories (same shape as main `locales` setting) | | `banner` | `"unmaintained"` \| `"unreleased"` \| `false` | No | Banner type displayed on versioned pages | ## Directory Structure Each version's content lives in its own directory. The latest (current) version uses the default `docsDir`, while older versions use the directories specified in their configuration: ``` src/content/ ├── docs/ # Latest version (default) ├── docs-ja/ # Latest version — Japanese ├── docs-v1/ # Version 1.0 — English ├── docs-v1-ja/ # Version 1.0 — Japanese ├── docs-v0.9/ # Version 0.9 — English └── docs-v0.9-ja/ # Version 0.9 — Japanese ``` Each version directory must exist and contain valid MDX content files. zudo-doc creates a separate Astro content collection for each version, so missing directories will cause build errors. ## URL Structure The latest version is served at the default docs URL. Older versions use a `/v/{slug}/` prefix: - **Latest:** `/docs/getting-started/introduction` - **Version 1.0:** `/v/1.0/docs/getting-started/introduction` - **Version 0.9:** `/v/0.9/docs/getting-started/introduction` With locales, the locale prefix comes after the version prefix: - **Latest (Japanese):** `/ja/docs/getting-started/introduction` - **Version 1.0 (Japanese):** `/v/1.0/ja/docs/getting-started/introduction` ## Version Switcher When one or more versions are configured, a **version switcher** automatically appears in both the header bar and the doc content area. It displays the current version label and provides links to switch between all available versions, including the latest. The switcher preserves the current page slug when navigating between versions, so users land on the same topic in the selected version. If a page does not exist in an older version, that version's link is automatically **disabled** (greyed out and not clickable) instead of linking to a 404 page. This is determined at build time by scanning each version's content directory. The dropdown also includes an **"All versions"** link at the bottom, which navigates to the [versions listing page](/docs/versions/). ## Versions Listing Page A dedicated versions page is automatically generated at `/docs/versions/` (and `/ja/docs/versions/` for Japanese). This page lists all configured versions with their labels, status badges, and documentation links — similar to [Docusaurus' versions page](https://docusaurus.io/versions). The page is auto-generated from the `versions` array in `settings.ts`. No manual maintenance is needed — adding or removing versions in the configuration automatically updates the listing page. ## Version Banners Each version can display a banner at the top of every page to inform users about the version's status. Two banner types are available: ### Unmaintained ```ts banner: "unmaintained", ``` Shows a warning-styled banner indicating the user is viewing documentation for an older version, with a link to the latest version. ### Unreleased ```ts banner: "unreleased", ``` Shows an info-styled banner indicating the user is viewing documentation for an unreleased version, with a link to the latest stable version. ### No Banner ```ts banner: false, ``` Disables the banner for that version. This is the default when `banner` is omitted. Use `"unmaintained"` for archived versions that are no longer updated, and `"unreleased"` for documentation of upcoming features that haven't shipped yet. ## Multi-Locale Support Each version can define its own locale directories, independent of the main locale configuration: ```ts versions: [ { slug: "1.0", label: "1.0.0", docsDir: "src/content/docs-v1", locales: { ja: { dir: "src/content/docs-v1-ja" }, }, banner: "unmaintained", }, ], ``` For each version and locale combination, zudo-doc creates a separate content collection (e.g., `docs-v-1.0-ja`) and generates the corresponding page routes. ## Creating a New Version When you're ready to archive the current documentation as a new version: 1. **Copy the current content directory** to a new versioned directory: ```bash cp -r src/content/docs src/content/docs-v2 ``` 2. **Copy locale directories** if applicable: ```bash cp -r src/content/docs-ja src/content/docs-v2-ja ``` 3. **Add the version to settings**: ```ts versions: [ { slug: "2.0", label: "2.0.0", docsDir: "src/content/docs-v2", locales: { ja: { dir: "src/content/docs-v2-ja" }, }, banner: "unmaintained", }, // ...existing versions ], ``` 4. **Update the current docs** — The files in `src/content/docs/` now represent the latest version. Update them as needed for the new release. The `versions` array defines older or pre-release versions only. The current/latest version always uses the main `docsDir` and is not listed in the array. --- # Changelog > Source: /pj/zudo-doc/docs/changelog Track the changes, improvements, and fixes in each release of zudo-doc. --- # /vendor/design-token-lint/CLAUDE.md > Source: /pj/zudo-doc/docs/claude-md/vendor--design-token-lint **Path:** `vendor/design-token-lint/CLAUDE.md` # CLAUDE.md — packages/design-token-lint ## Package `@zudolab/design-token-lint` — lint Tailwind CSS class names against design system tokens. Enforces semantic spacing and color tokens instead of raw numeric utilities. ## Commands Run from this directory, or use the workspace shortcuts from the root. ```bash pnpm build # Compile TypeScript to dist/ pnpm test # Run tests (vitest run) pnpm test:watch # Watch mode pnpm lint # prettier --check . pnpm lint:fix # prettier --write . ``` ## Source Layout ``` src/ cli.ts # CLI entry point (#!/usr/bin/env node) config.ts # Config loading and pattern compilation extractor.ts # Class name extraction from source files rules.ts # Rule matching against compiled config linter.ts # Main linter combining extraction + rules index.ts # Public API exports *.test.ts # Tests (colocated) ``` ## API Shapes (Important) - `LintResult` is **flat**: `{ filePath, line, className, reason }` — NOT `{ filePath, violations: [...] }` - `lintFile()` and `lintContent()` return `LintResult[]` (array, not single object) - `Violation` has only `{ className, reason }` — no `line` or `column` - `checkClass()` returns `Violation | null` — not `undefined` - `ExtractedClass` has `{ className, line }` — no `column` Keep the public documentation (`src/content/docs/api/`) in sync when changing these shapes. ## Publishing Triggered by pushing a `v*.*.*` tag to main. The `.github/workflows/publish.yml` workflow runs tests + build + `pnpm publish --access public`. Requires `NPM_TOKEN` secret. The `repository.directory` field in `package.json` points npm at this subdirectory within the monorepo. ## Dogfooding `.design-token-lint.json` in this directory configures the linter on its own source code. Run `pnpm dlx @zudolab/design-token-lint` (after publish) or `node dist/cli.js` to lint. --- # Image Enlarge > Source: /pj/zudo-doc/docs/components/image-enlarge When an image's intrinsic width is greater than its rendered width (accounting for device pixel ratio), a small expand button appears in the corner and the image gets a subtle border to signal interactivity. Clicking anywhere on the image — not just the corner button — opens it in a fullscreen dialog. ## Wide Image — Button Appears The image below is 2400 px wide, so it is larger than the typical content column. The expand button becomes visible once the browser measures the rendered size. ![A wide placeholder image](./image-wide.png) ## Small Image — No Button This image is only 300 px wide, which fits inside the column. The expand button stays hidden because the image is not larger than its container. ![A small placeholder image](./image-small.png) ## Opt-Out Add `"no-enlarge"` as the Markdown title attribute to disable the expand button for a specific image, even if it is wide enough to qualify. The title attribute is also stripped from the rendered `` so it never shows as a browser tooltip. ```md ![alt text](./image.png "no-enlarge") ``` ![A wide image with opt-out applied](./image-opt-out.png "no-enlarge") ## How It Works The feature has two parts: - **Build-time** — a rehype plugin wraps each lone `` inside a paragraph in a `` and injects a hidden ``. Images mixed with surrounding inline text are not wrapped. - **Runtime** — a Preact island (`ImageEnlarge`) uses `ResizeObserver` to compare `naturalWidth` against `clientWidth × devicePixelRatio` and toggles the button's `hidden` attribute accordingly. While that attribute is unset, the entire image becomes the click target and shows a hover affordance, so any click on the image opens it in a ``. Press **ESC**, click the close button at the top-right of the viewport, or click outside the image to close it. :::note On high-DPI displays (e.g. 2× retina), eligibility accounts for device pixel ratio. A 2000 px image rendered at 1000 CSS px on a 2× screen is **not** eligible because `2000 ≤ 1000 × 2`. ::: ## Settings Image enlarge is enabled in `src/config/settings.ts`: ```ts imageEnlarge: true, }; ``` Set `imageEnlarge: false` to disable the feature entirely. ## Customizing overlay colors The enlarge button and the dialog's close button share a single token pair that controls their appearance: - `image-overlay-bg` — bg layer color, rendered at 80% opacity behind the icon. - `image-overlay-fg` — icon color, rendered at 100% opacity on top of the bg. The right overlay color depends on the content photos, which vary between sites. The default values work for most cases, but any color scheme can override them per the three-tier color strategy (see [Color](../reference/color.mdx)): ```ts // src/config/color-schemes.ts "My Scheme": { // ...palette, background, foreground, ... semantic: { imageOverlayBg: 0, // palette index — deep surface imageOverlayFg: 11, // palette index — light text // or a direct color string: imageOverlayBg: "#222" }, }, ``` The Color Tweak Panel (when enabled) exposes these tokens for live tweaking without editing code. --- # create-zudo-doc CLI > Source: /pj/zudo-doc/docs/reference/create-zudo-doc ## Usage ```bash create-zudo-doc [project-name] [options] ``` When run without flags, the CLI launches an interactive wizard. All options can be specified via flags for non-interactive (CI/agent) usage. You can also use the [Setup Preset Generator](/docs/getting-started/setup-preset-generator) to interactively build a configuration and copy it as a JSON preset or CLI command. ## Options ### Project | Flag | Description | Default | |------|-------------|---------| | `--name ` | Project name (or first positional arg) | `my-docs` | | `--lang ` | Default language code | `en` | | `--pm ` | Package manager: `pnpm`, `npm`, `yarn`, `bun` | `pnpm` | | `--[no-]install` | Install dependencies after scaffolding | prompt | ### Color Scheme | Flag | Description | Default | |------|-------------|---------| | `--color-scheme-mode ` | `single` or `light-dark` | `light-dark` | | `--scheme ` | Color scheme (single mode) | `Dracula` | | `--light-scheme ` | Light scheme (light-dark mode) | `Default Light` | | `--dark-scheme ` | Dark scheme (light-dark mode) | `Default Dark` | | `--default-mode ` | `light` or `dark` (light-dark mode) | `dark` | | `--[no-]respect-system-preference` | Respect OS color scheme preference | `true` | ### Features | Flag | Description | Default | |------|-------------|---------| | `--[no-]i18n` | Multi-language support | off | | `--[no-]search` | Pagefind full-text search | on | | `--[no-]sidebar-filter` | Real-time sidebar filtering | on | | `--[no-]design-token-panel` | Interactive tabbed panel for tweaking spacing, font, size, and color tokens | off | | `--[no-]sidebar-resizer` | Draggable sidebar width | off | | `--[no-]sidebar-toggle` | Show/hide desktop sidebar | off | | `--[no-]versioning` | Multi-version documentation support | off | | `--[no-]claude-resources` | Claude Code docs generation | off | | `--[no-]doc-history` | Document edit history | off | | `--[no-]llms-txt` | Generate llms.txt for LLM consumption | off | | `--[no-]skill-symlinker` | Symlink documentation skills | off | | `--[no-]footer-nav-group` | Navigation links in the footer | off | | `--[no-]footer-copyright` | Copyright notice in the footer | off | | `--[no-]changelog` | Changelog page | off | ### Preset | Flag | Description | |------|-------------| | `--preset ` | Load settings from a JSON preset file (use `"-"` for stdin) | The `--preset` flag accepts the JSON output from the [Setup Preset Generator](/docs/getting-started/setup-preset-generator). When a preset is loaded, all prompts are skipped (same as `--yes`). Individual CLI flags override preset values. ### General | Flag | Description | |------|-------------| | `-y, --yes` | Use defaults for unspecified options, skip all prompts | | `-h, --help` | Show help message | ## Supported Languages The `--lang` flag accepts any of the following language codes: | Code | Language | |------|----------| | `en` | English | | `ja` | Japanese | | `zh-cn` | Chinese (Simplified) | | `zh-tw` | Chinese (Traditional) | | `ko` | Korean | | `es` | Spanish | | `fr` | French | | `de` | German | | `pt` | Portuguese | The default language determines the locale used for root pages (`/docs/...`). When i18n is enabled, a secondary language is added automatically (English when the default is non-English, Japanese when the default is English). ## Examples ### Interactive mode ```bash pnpm create zudo-doc ``` ### Non-interactive with all defaults ```bash pnpm create zudo-doc my-docs --yes ``` ### Japanese site with Dracula theme ```bash pnpm create zudo-doc my-docs --lang ja --scheme Dracula --no-i18n --pm pnpm --install ``` ### Light/dark mode with custom schemes ```bash pnpm create zudo-doc my-docs \ --color-scheme-mode light-dark \ --light-scheme "GitHub Light" \ --dark-scheme "GitHub Dark" \ --default-mode dark \ --yes ``` ### Using a preset file Generate a preset JSON from the [Setup Preset Generator](/docs/getting-started/setup-preset-generator), save it to a file, then pass it to the CLI: ```bash pnpm create zudo-doc --preset setup.json --install ``` Or pipe JSON directly via stdin: ```bash cat setup.json | pnpm create zudo-doc --preset - --install ``` ### CI/automation usage ```bash pnpm create zudo-doc my-docs \ --lang en \ --scheme Nord \ --no-i18n \ --search \ --no-claude-resources \ --pm pnpm \ --install \ --yes ``` ## Programmatic API The package also exports a programmatic API. The options object accepts the same fields as the JSON preset, plus an `install` option: ```ts await createZudoDoc({ projectName: "my-docs", defaultLang: "en", colorSchemeMode: "light-dark", lightScheme: "GitHub Light", darkScheme: "GitHub Dark", defaultMode: "dark", respectPrefersColorScheme: true, features: [ "search", "sidebarFilter", "sidebarResizer", "sidebarToggle", "docHistory", "footerCopyright", ], packageManager: "pnpm", install: true, }); ``` --- # Smart Break Utility > Source: /pj/zudo-doc/docs/reference/smart-break Long URLs and filesystem paths — for example `packages/create-zudo-doc/templates/base/src/utils/smart-break.tsx` — do not wrap at arbitrary character boundaries. In narrow UI (sidebar labels, breadcrumbs, inline code, search snippets) they overflow the container or push layout wider than it should be. The `smart-break` utility at `src/utils/smart-break.tsx` solves this by injecting `` (word-break opportunity) hints after each delimiter in strings that look path-like. The browser then decides where to break based on available width. ## Automatic Behavior Several zudo-doc surfaces already apply smart-break automatically — no configuration needed: - **Sidebar tree labels** — category and page labels - **Breadcrumbs** — current-page trail - **MDX links** — anchor text inside prose - **MDX inline code** — backtick spans inside prose - **Search results** — titles and snippets in the search dialog - **Doc history** — commit message rendering You only need to invoke the utility yourself when building a custom component that renders user-provided path-like strings. ## Manual Opt-In: `SmartBreak` Component For Preact islands (or any `.tsx` component in the project) use the `SmartBreak` component. It stringifies its children and injects `` where appropriate: ```tsx return ( {path} ); } ``` If `path` is not path-like (plain prose, `and/or`, `state-of-the-art`, etc.) it is returned unchanged — no stray wbrs are injected. ## Manual Opt-In: `smartBreakToHtml` Helper Astro components and any code path that renders an HTML string (for example `set:html` or `dangerouslySetInnerHTML`) cannot mount Preact VNodes directly. Use `smartBreakToHtml` instead — it returns an HTML-escaped string with literal `` tags injected: ```astro --- const { label } = Astro.props; --- ``` The returned string is already HTML-escaped, so it is safe to pass to `set:html`. ## How It Decides Smart-break does two things: it classifies the input with an `isPathLike` heuristic, and — only when the heuristic says yes — it splits on a fixed delimiter set and re-joins with `` between segments. ### Delimiter Set Breaks are inserted after each of these characters: ``` / \ - _ . : ? # & = ``` These cover URL structure (`://`, `?query=value&other=1`, `#anchor`), filesystem paths (`/`, `\`), and compound identifiers (`snake_case`, `kebab-case`, `file.name.ext`). ### The `isPathLike` Heuristic `isPathLike` returns `true` for strings that look like URLs, paths, or similar delimited structures: - Contains `://` (any URL) - Starts with `/`, `./`, or `../` (POSIX paths) - Starts with a Windows drive prefix like `C:\` or `C:/` - Has at least two slashes between alphanumerics (nested paths) - Has a domain-like `a.b` pattern combined with a slash somewhere It returns `false` for prose-y fragments that happen to contain delimiters: `and/or`, `well-known`, `state-of-the-art`, `1.2.3-beta.4`, `UI/UX`. These pass through unmodified so normal prose wrapping still applies. ### A Realistic Sample For an input like: ``` packages/create-zudo-doc/templates/base/src/utils/smart-break.tsx ``` `isPathLike` returns `true` and `smartBreakToHtml` emits (conceptually): ```html packages/create-zudo-doc/templates/base/src/utils/smart-break.tsx ``` The browser keeps the line intact when the container is wide enough, and breaks at the nearest `` when it must — always between segments, never mid-segment. ## When Not to Use It - Short labels (one or two words) — there is nothing to break. - Intentional single-token strings where any visual break is wrong (for example a version tag you must never wrap). - CJK prose — the utility targets Latin-alphabet path-like strings; CJK line-wrapping is handled by the [CJK-friendly markdown](./cjk-friendly.mdx) plugin and native browser behavior. --- # Color Scheme Preview > Source: /pj/zudo-doc/docs/guides/color-scheme-preview ## Overview The color scheme preview lets you browse 50+ preset color schemes and apply them to your site in real-time. This functionality lives on the **Color** tab of the [Design Token Panel](/docs/reference/design-token-panel), via a "Scheme..." dropdown in the tab header. ## How It Works When the Design Token Panel is enabled, switch to the **Color** tab and use the **"Scheme..."** dropdown. It lists all available preset schemes. Selecting a scheme: - Loads all of the preset's palette, base, and semantic colors into the tweak state - Applies the colors to the page immediately - Persists the choice in `localStorage` so it survives page reloads and navigation - Does not modify any source files — the change is client-side only After loading a preset, you can further tweak individual colors as needed using the tab's color pickers. ## Enabling the Design Token Panel Set `designTokenPanel` to `true` in `src/config/settings.ts`: ```ts title="src/config/settings.ts" designTokenPanel: true, // ... }; ``` When enabled, a palette icon appears in the header bar. Clicking it toggles the panel open/closed. The "Scheme..." dropdown is on the **Color** tab. ## Available Color Schemes The panel includes 50+ preset color schemes such as Dracula, Nord, Solarized Dark, Solarized Light, Catppuccin Mocha, Catppuccin Latte, Tokyo Night, Gruvbox Dark, GitHub Dark, GitHub Light, and many more. Bundled schemes (those included in your project's `color-schemes.ts`) appear first in the dropdown, followed by a separator and the remaining presets. Each scheme specifies a 16-color palette, background/foreground colors, selection colors, a Shiki code highlighting theme, and optional semantic color overrides. The scheme preview is particularly useful during development to quickly evaluate how your content looks across different themes before committing to a final scheme. ## See Also - [Design Token Panel](/docs/reference/design-token-panel) — interactive tabbed editor for spacing, font, size, and color tokens --- # Footer > Source: /pj/zudo-doc/docs/guides/footer The footer appears at the bottom of every page and is configurable through `src/config/settings.ts` under the `footer` property. It supports a multi-column link layout and optional copyright text. ## Configuration The `footer` property in `src/config/settings.ts` accepts a `FooterConfig` object: ```ts footer: { links: [ { title: "Docs", items: [ { label: "Getting Started", href: "/docs/getting-started" }, { label: "Guides", href: "/docs/guides" }, ], }, { title: "Community", items: [ { label: "GitHub", href: "https://github.com/zudolab/zudo-doc" }, ], }, ], copyright: `Copyright © ${new Date().getFullYear()} Your Name. Built with zudo-doc.`, } satisfies FooterConfig as FooterConfig | false, ``` ### links An array of columns displayed in the footer. Each column has a `title` and an `items` array of links. | Property | Type | Description | |----------|------|-------------| | `title` | string | Column heading displayed above the links | | `items` | `FooterLinkItem[]` | Array of link items in the column | Each item in the `items` array has: | Property | Type | Description | |----------|------|-------------| | `label` | string | Display text for the link | | `href` | string | URL the link points to | External links (URLs starting with `http://` or `https://`) automatically open in a new tab with `rel="noopener noreferrer"`. Internal links are resolved with the site's configured base path. ### copyright Optional copyright text displayed centered below the link columns. When link columns are present, the copyright is separated from them by a top border. When no links are configured, the copyright displays without a border. The copyright field supports **HTML content** — you can include `` tags for links. Links in the copyright are automatically styled with `text-accent` color and underlined. ```ts copyright: `Copyright © ${new Date().getFullYear()} Your Name. Built with zudo-doc.`, ``` ## Adding Columns Add more columns by appending objects to the `links` array. The footer uses a responsive grid — columns stack on small screens and flow horizontally on larger screens. ```ts footer: { links: [ { title: "Docs", items: [ { label: "Getting Started", href: "/docs/getting-started" }, { label: "Guides", href: "/docs/guides" }, ], }, { title: "Community", items: [ { label: "GitHub", href: "https://github.com/zudolab/zudo-doc" }, { label: "Discord", href: "https://discord.gg/example" }, ], }, { title: "More", items: [ { label: "Blog", href: "https://example.com/blog" }, { label: "Changelog", href: "/docs/changelog" }, ], }, ], copyright: `Copyright © ${new Date().getFullYear()} My Project.`, }, ``` The grid layout uses `repeat(auto-fit, minmax(12rem, 1fr))` on large screens, so columns will automatically adjust their width based on available space. ## Disabling the Footer To remove the footer entirely, set `footer` to `false` in settings: ```ts footer: false as FooterConfig | false, ``` When set to `false`, the footer component is not rendered on any page. ## i18n (Localized Labels) Footer columns and link items support locale-specific overrides via the optional `locales` field. When a page is rendered for a non-default locale, the footer resolves localized titles and labels automatically. Internal link hrefs are also prefixed with the locale path (e.g. `/ja/docs/guides`). ```ts footer: { links: [ { title: "Docs", locales: { ja: { title: "ドキュメント" } }, items: [ { label: "Getting Started", href: "/docs/getting-started", locales: { ja: { label: "はじめに" } }, }, { label: "Guides", href: "/docs/guides", locales: { ja: { label: "ガイド" } }, }, ], }, ], }, ``` Each `FooterLinkColumn` accepts an optional `locales` field with per-locale `{ title }` overrides. Each `FooterLinkItem` accepts an optional `locales` field with per-locale `{ label }` overrides. When no locale override exists, the default `title` or `label` is used. External links are never locale-prefixed — only internal hrefs (those that don't start with `http://` or `https://`) get the locale prefix. ## TypeScript Types The footer configuration uses these interfaces defined in `src/config/settings-types.ts`: ```ts interface FooterLinkItem { label: string; href: string; locales?: Record; } interface FooterLinkColumn { title: string; items: FooterLinkItem[]; locales?: Record; } interface FooterConfig { links: FooterLinkColumn[]; copyright?: string; } ``` The `footer` property in settings is typed as `FooterConfig | false`, allowing it to be either a configuration object or `false` to disable. ## Styling The footer uses the project's design token colors for consistent theming: - `border-muted` — top border separating the footer from content, and the divider above the copyright - `bg-surface` — footer background - `text-fg` — column titles - `text-muted` — link text and copyright text - `text-accent` — link hover color The layout is responsive: a single column on mobile, two columns on small screens, and auto-fit columns on large screens. See `src/components/footer.astro` for implementation details. --- # Changelog > Source: /pj/zudo-doc/docs/guides/changelog zudo-doc includes a changelog section for tracking release notes and version history. The changelog uses descending sidebar sort so the newest entries always appear at the top. ## Directory Structure Changelog entries live in a dedicated content directory: ``` src/content/docs/ └── changelog/ ├── _category_.json # Category config with desc sort ├── index.mdx # Category index page ├── 0.2.0.mdx # Newer entry (sidebar_position: 2) └── 0.1.0.mdx # Older entry (sidebar_position: 1) ``` ## Category Configuration The `_category_.json` file configures descending sort order so newer entries appear first in the sidebar: ```json title="_category_.json" { "label": "Changelog", "position": 10, "sortOrder": "desc" } ``` With `sortOrder: "desc"`, entries with higher `sidebar_position` values appear first. This means newer versions naturally sort to the top. ## Adding a New Entry To add a new changelog entry: 1. Create a new MDX file in `src/content/docs/changelog/` named after the version (e.g., `0.2.0.mdx`) 2. Set the `sidebar_position` to a value higher than the previous entry 3. Mirror the file in the Japanese content directory (`src/content/docs-ja/changelog/`) ```mdx title="src/content/docs/changelog/0.2.0.mdx" --- title: "0.2.0" description: Short summary of this release. sidebar_position: 2 --- Summary of changes in this release. ### Features - Feature A - Feature B ### Bug Fixes - Fix for issue X ``` Use incrementing `sidebar_position` values for each new version. Combined with `sortOrder: "desc"` in the category config, this ensures the newest entry always appears at the top of the sidebar. ## Entry Format Each changelog entry is a standard MDX file. A recommended structure: - **Title**: The version number (e.g., `"0.2.0"`) - **Description**: A brief summary of the release - **Content sections**: Features, Bug Fixes, Breaking Changes, etc. There is no enforced format — use whatever structure fits your project. The examples above follow a common convention similar to [Keep a Changelog](https://keepachangelog.com/). ## Version Bump Script zudo-doc includes a `scripts/version-bump.sh` script that automates version management: ```bash # Bump version and create changelog entry ./scripts/version-bump.sh 0.2.0 # Bump version, create changelog entry, and snapshot current docs ./scripts/version-bump.sh 1.0.0 --snapshot ``` The script performs the following steps: 1. Updates the `version` field in `package.json` 2. Creates a changelog entry MDX file in both English and Japanese directories 3. Sets the correct `sidebar_position` automatically (incremented from existing entries) ### Doc Snapshots When called with `--snapshot`, the script also archives the current documentation as a versioned snapshot before bumping. This integrates with zudo-doc's [versioning system](/docs/guides/versioning): 1. Copies `src/content/docs/` to `src/content/docs-v{old}/` 2. Copies `src/content/docs-ja/` to `src/content/docs-v{old}-ja/` 3. Prints the version config entry to add to `src/config/settings.ts` The `--snapshot` flag archives the **old** version's docs, not the new version. After the script runs, `src/content/docs/` represents the new version and the snapshot preserves the previous state. ## Version Bump Skill For [Claude Code](https://claude.com/claude-code) users, zudo-doc includes a `/zudo-doc-version-bump` skill that orchestrates the entire release workflow. Instead of running the script manually, the skill handles everything end-to-end: 1. Analyzes commits since the last git tag and categorizes them (breaking, features, fixes, other) 2. Proposes a version bump type (major/minor/patch) based on the changes 3. Runs `version-bump.sh` to update `package.json` and create changelog entries 4. Fills in the changelog templates with actual commit details (both EN and JA) 5. Runs `pnpm b4push` to validate the build 6. Commits, pushes, and waits for CI 7. Creates a git tag and GitHub release 8. Guides you through npm publishing (or skips it for private packages) ```bash # Run the skill in Claude Code /zudo-doc-version-bump # Or skip the proposal step by specifying the bump type /zudo-doc-version-bump patch ``` The skill requires at least one `v*` tag to exist. If this is the first release, create the initial tag manually: `git tag v0.1.0 && git push --tags`. ## Header Navigation The changelog section is linked from the header navigation. This is configured in `src/config/settings.ts`: ```ts headerNav: [ // ...other items { label: "Changelog", labelKey: "nav.changelog", path: "/docs/changelog", categoryMatch: "changelog" }, ], ``` ## i18n Mirror changelog entries in the Japanese content directory (`src/content/docs-ja/changelog/`) to provide translated release notes. The `_category_.json` and directory structure should match the English version. --- # AI Assistant > Source: /pj/zudo-doc/docs/guides/ai-assistant ## Overview The AI assistant adds a chat dialog to your documentation site. Users can click the sparkle icon in the header to open the dialog and ask questions about the documentation content. The assistant uses the full documentation text (generated by the [llms.txt integration](/docs/guides/llms-txt)) as context, so it can answer questions about any page on the site. ## Enabling the Assistant Set `aiAssistant` to `true` in `src/config/settings.ts`: ```ts title="src/config/settings.ts" aiAssistant: true, // ... }; ``` When enabled: - A sparkle icon appears in the header bar - The `@astrojs/node` adapter is added automatically (the site switches from static to hybrid mode) - The `POST /api/ai-chat` endpoint becomes available ## Environment Setup Create a `.env` file (see `.env.example`): ```env title=".env" # "local" uses Claude Code CLI, "remote" uses Anthropic API AI_CHAT_MODE=local ``` ### Local Mode Uses the [Claude Code CLI](https://code.claude.com/docs/en/overview) (`claude -p`) as the backend. No API key needed — uses your existing Claude Code authentication. ```env AI_CHAT_MODE=local ``` This is the simplest setup for local development. ### Remote Mode Calls the Anthropic Messages API directly. Requires an API key. ```env AI_CHAT_MODE=remote ANTHROPIC_API_KEY=sk-ant-... ``` Uses `claude-haiku-4-5-20251001` for fast, low-cost responses. ## Chat Dialog The dialog is a Preact island (`src/components/ai-chat-modal.tsx`) using the native `` element. ### Layout - **Narrow viewports** (below `lg`/1024px): Full viewport width and height - **Wide viewports** (1024px and above): Centered, 90vw/90vh with a max width of 52.5rem, with a border ### Features - Balloon-style message bubbles (user on right, assistant on left) - Markdown rendering in assistant responses (bold, italic, code, lists, links) - "Thinking..." indicator during API calls - Error messages displayed inline - Backdrop click or Escape to close - Conversation resets on close ## MSW Mock (Development Only) For UI development without a real backend, enable MSW (Mock Service Worker). MSW is **dev-only** — it never runs in production builds. **Setup:** 1. Generate the service worker file (one-time): ```bash npx msw init public/ --save ``` 2. Set the environment variable: ```env title=".env" PUBLIC_ENABLE_MOCKS=true ``` 3. Run the dev server (`pnpm dev`). The mock handler (`src/mocks/handlers.ts`) returns a predefined response with a simulated delay. This is useful for styling and testing the chat UI without consuming API credits. MSW requires `aiAssistant: true` in settings. The generated `public/mockServiceWorker.js` file is gitignored — each developer generates it locally. ## API Reference For the full endpoint specification (request/response types, error codes, environment variables), see the [AI Assistant API reference](/docs/reference/ai-assistant-api). ## Cloudflare Worker (Standalone API) For deploying the chat API as a standalone Cloudflare Worker (separate from the Astro site), see the [`packages/ai-chat-worker/`](/docs/reference/ai-chat-worker) sub-package. The Worker fetches `llms-full.txt` from your deployed documentation site and uses it as context for Claude API calls — useful when you want to host the API independently or deploy the docs as a static site. ## File Structure ``` src/ ├── components/ │ ├── ai-chat-modal.tsx # Preact island — chat dialog UI │ └── mock-init.tsx # MSW initializer (dev only) ├── mocks/ │ ├── handlers.ts # MSW mock response handlers │ ├── browser.ts # MSW browser worker setup │ ├── server.ts # MSW node server setup │ └── init.ts # Conditional MSW initialization ├── pages/ │ └── api/ │ └── ai-chat.ts # Server endpoint (local/remote modes) ├── types/ │ └── ai-chat.ts # ChatMessage, AiChatRequest/Response types └── utils/ └── render-markdown.ts # Lightweight markdown-to-HTML renderer ``` --- # Development Workflow > Source: /pj/zudo-doc/docs/guides/development-workflow ## Development Commands These commands start the local development servers. | Command | What it does | |---|---| | `pnpm dev` | Runs Astro dev server (port 4321) and doc-history-server (port 4322) concurrently | | `pnpm dev:astro` | Astro dev server only (port 4321) | | `pnpm dev:history` | Doc history API server only (port 4322) | | `pnpm dev:stable` | Alternative build-then-serve mode (avoids HMR crashes on content file add/remove) | | `pnpm dev:network` | Astro dev server with `--host 0.0.0.0` for LAN access | Use `pnpm dev:stable` when you're adding or removing content files frequently. Standard `pnpm dev` can crash on file system changes due to Vite HMR. ## Build & Quality Commands | Command | What it does | |---|---| | `pnpm build` | Static HTML export to `dist/` | | `pnpm check` | Astro type checking | | `pnpm b4push` | Pre-push validation: format check → typecheck → build → link check → E2E tests | | `pnpm format` | Format MDX and Astro files | | `pnpm test:unit` | Run unit tests (Vitest) | | `pnpm test:e2e` | Run E2E tests (Playwright) | Run `pnpm b4push` before pushing to verify the full site builds, all links resolve, and E2E tests pass. This catches broken cross-doc links and TypeScript errors that the dev server tolerates. ## Component Development zudo-doc uses three component tiers. Choose the right tier based on what your component needs to do. ### Astro Components `.astro` files are the default choice — zero JavaScript, server-rendered at build time. Use Astro components for: - Layout wrappers - Static UI elements (headers, footers, sidebars) - Anything that doesn't require user interaction ### Preact Islands `client:load` islands are used only when client-side interactivity is required. Preact runs in compat mode (`@astrojs/preact` with `compat: true`), so components can use React-style imports and APIs. Current islands in the project: `toc.tsx`, `mobile-toc.tsx`, `sidebar-toggle.tsx`, `sidebar-tree.tsx`, `theme-toggle.tsx`, `doc-history.tsx`, `color-tweak-panel.tsx`, `color-tweak-export-modal.tsx`. Use Preact islands for: - Scroll spy (TOC highlighting) - Toggle/drawer components - Live editing panels ### Content Typography Components These live in `src/components/content/` and are Preact function components **without** a `client:` directive — they are server-rendered with zero client JavaScript. They override standard HTML elements in MDX via ``. Current overrides: headings (h2–h4), paragraph, link, strong, blockquote, lists (ul/ol), table. Use content typography components when you need to apply project design tokens to standard MDX prose elements. ## Content Development Cycle Follow these steps when adding new documentation pages: 1. Create a `.mdx` file under `src/content/docs/` with `title` and `sidebar_position` in the frontmatter. 2. Write content starting with `## h2` headings. Do not add `# h1` — the frontmatter `title` renders as the page h1. 3. Create a matching file under `src/content/docs-ja/` with translated prose. 4. Keep all code blocks identical between EN and JA — only translate the surrounding prose. 5. Run `pnpm format:mdx` to format MDX files. 6. Run `pnpm build` to verify the site builds correctly. Always set `sidebar_position` in frontmatter. Without it, pages sort alphabetically, which is rarely the intended order. See the [Frontmatter reference](./frontmatter.mdx) for all available frontmatter fields. --- # Deployment > Source: /pj/zudo-doc/docs/guides/deployment ## Overview zudo-doc is a fully static site generator — the output of `pnpm build` is a directory of HTML, CSS, and JavaScript files that can be deployed anywhere. The default deployment target is **Cloudflare Pages**, using GitHub Actions for CI/CD automation. The CI pipeline is split into parallel jobs to keep total build time low. Site generation and document history extraction run as separate jobs and are merged before deployment. ## Cloudflare Pages The built site is deployed to Cloudflare Pages using `wrangler pages deploy`. The deploy artifact includes: - The Astro build output (`dist/`) - Pre-generated doc history JSON files (if `docHistory` is enabled) - A redirect rule from `/` to the site's base path The redirect from `/` is a static `_redirects` file included in the build output. Cloudflare Pages processes it natively — no Worker or server logic is required. ## CI Pipeline Structure The project uses two GitHub Actions workflows. ### Production (`main-deploy.yml`) Triggered on push to `main`. Runs four jobs: 1. **build-site** — shallow clone (`fetch-depth: 1`), builds the Astro site with `SKIP_DOC_HISTORY=1 pnpm build` 2. **build-history** — full clone (`fetch-depth: 0`), runs `@zudo-doc/doc-history-server generate` to pre-extract git history for all content files 3. **deploy** — merges the site and history artifacts, then deploys to Cloudflare Pages (production) 4. **notify** — sends an IFTTT webhook notification with the deploy status **Concurrency:** `group: production-deploy, cancel-in-progress: false` — earlier deploys are allowed to finish rather than being cancelled. ### PR Checks (`pr-checks.yml`) Triggered on pull requests targeting `main`. Runs five jobs: 1. **typecheck** — runs `pnpm check` (Astro type checking) 2. **build-site** — shallow clone, `SKIP_DOC_HISTORY=1 pnpm build`, then `check:links` 3. **build-history** — full clone, doc history generation 4. **e2e** — runs Playwright end-to-end tests (full clone, no `SKIP_DOC_HISTORY` so history is embedded inline) 5. **preview** — merges artifacts and deploys a preview to Cloudflare Pages, then posts the URL as a PR comment **Concurrency:** `group: pr-checks-{pr-number}, cancel-in-progress: true` — outdated runs are cancelled to save CI minutes. ### Preview Deployments Each PR gets a preview deployment at: ``` https://pr-{number}.zudo-doc.pages.dev ``` The URL is posted automatically as a comment on the PR by the **preview** job. ## SKIP_DOC_HISTORY When `SKIP_DOC_HISTORY=1` is set, the Astro integration skips inline history generation during the build. This flag exists because history generation requires a full git clone and walks every commit — it is too slow to run inside the main build job. | Context | Value | Why | |---|---|---| | CI `build-site` job | `SKIP_DOC_HISTORY=1` | History is generated by the separate `build-history` job | | CI `e2e` job | unset | E2E tests verify history features inline | | Local `pnpm build` | unset | History is embedded directly in the build output | If you run `pnpm build` locally without `SKIP_DOC_HISTORY=1`, the history generation runs inline. This is slower but produces a self-contained `dist/` that works without a separate history artifact. ## Inter-Job Data Sharing Build artifacts are shared between jobs using **`actions/cache`** rather than `actions/upload-artifact`. Cache keys are scoped to `github.run_id` to prevent cross-run contamination: ```yaml key: dist-${{ github.run_id }} key: doc-history-${{ github.run_id }} ``` The **deploy** (or **preview**) job restores both caches and merges the `dist/` directories before calling `wrangler pages deploy`. Using `actions/cache` for job-to-job data transfer (rather than artifacts) avoids the overhead of uploading and downloading large ZIP archives. Cache entries are cheaper for temporary intra-run data. ## Required Secrets Configure these in your repository's **Settings → Secrets and variables → Actions**: | Secret | Description | |---|---| | `CLOUDFLARE_API_TOKEN` | Cloudflare API token with Pages deployment permissions | | `CLOUDFLARE_ACCOUNT_ID` | Your Cloudflare account ID | | `IFTTT_PROD_NOTIFY` | IFTTT webhook URL for deploy notifications (optional) | The `CLOUDFLARE_API_TOKEN` needs the **Cloudflare Pages: Edit** permission. Scoping it to the specific Pages project is recommended over granting account-wide access. --- # Search Worker API > Source: /pj/zudo-doc/docs/reference/search-worker Standalone Cloudflare Worker that provides a server-side search API for zudo-doc, designed for large documentation bases or API consumers. ## Overview The Search Worker is a sub-package at `packages/search-worker/` that deploys as a Cloudflare Worker. It uses the same [MiniSearch](https://lucaong.github.io/minisearch/) engine and search index as the built-in client-side search, but runs server-side on Cloudflare Workers runtime. This is useful when: - Your documentation base is too large for comfortable client-side search (the full index must be downloaded to the browser) - You want to provide a search API for external consumers (bots, integrations, CLI tools) - You need server-side search for programmatic access The Worker fetches `search-index.json` from your deployed documentation site and caches it in memory with a 5-minute TTL. The Search Worker is an **additive option**, not a replacement. The primary search experience remains client-side MiniSearch via the search dialog (`Ctrl+K` / `Cmd+K`). See [When to Use Each](#when-to-use-each) for guidance. ## Endpoint ``` POST / Content-Type: application/json ``` The Worker responds at its root URL. ### Request Body ```ts interface SearchRequest { query: string; limit?: number; } ``` | Field | Type | Required | Description | | ------- | -------- | -------- | --------------------------------------------------------- | | `query` | `string` | Yes | Search query string. Must be non-empty, max 500 characters. | | `limit` | `number` | No | Maximum results to return. Default: 20, max: 100. | ### Success Response (200) ```ts interface SearchResponse { results: SearchResult[]; query: string; total: number; } interface SearchResult { id: string; title: string; url: string; description: string; score: number; } ``` Example: ```json { "results": [ { "id": "guides/adding-pages", "title": "Adding Pages", "url": "/docs/guides/adding-pages", "description": "Learn how to add new documentation pages", "score": 12.5 } ], "query": "how to add a page", "total": 3 } ``` ### Error Responses | Status | Condition | | ------ | ---------------------------------------- | | 400 | Invalid JSON body | | 400 | `query` is not a non-empty string | | 400 | `query` exceeds 500 character limit | | 404 | Request path is not `/` | | 405 | Request method is not POST | | 429 | Rate limit exceeded (includes `Retry-After` header) | | 500 | Internal server error (index fetch failed, etc.) | All error responses use the format `{ "error": string }`. ## Environment Setup ### Variables Set `DOCS_SITE_URL` in `wrangler.toml` to point at your deployed documentation site: ```toml title="packages/search-worker/wrangler.toml" [vars] DOCS_SITE_URL = "https://your-docs-site.example.com" RATE_LIMIT_PER_MINUTE = "60" RATE_LIMIT_PER_DAY = "1000" ``` | Variable | Default | Description | | -------- | ------- | ----------- | | `DOCS_SITE_URL` | -- | Your deployed documentation site URL | | `RATE_LIMIT_PER_MINUTE` | `60` | Max requests per IP per minute | | `RATE_LIMIT_PER_DAY` | `1000` | Max requests per IP per day | The Worker fetches `${DOCS_SITE_URL}/search-index.json` to load the search index. ### KV Namespace Rate limiting uses a Cloudflare KV namespace. Create it before deploying: ```sh cd packages/search-worker npx wrangler kv namespace create RATE_LIMIT ``` Update the `id` in `wrangler.toml` `[[kv_namespaces]]` with the returned namespace ID. ### Rate Limiting Behavior The Worker enforces per-IP rate limits using the `cf-connecting-ip` header provided by Cloudflare. - **Best-effort enforcement** -- KV reads and writes are not atomic, so concurrent requests from the same IP may slightly exceed the configured limits - **Fail-open** -- if KV is unavailable (outage, misconfiguration), requests are allowed through. Search availability takes priority over strict rate enforcement - **Invalid config** -- non-numeric values for `RATE_LIMIT_PER_MINUTE` or `RATE_LIMIT_PER_DAY` fall back to the defaults (60/min, 1000/day) - **429 response** -- includes a `Retry-After` header (seconds until the current window resets), exposed via CORS for browser access ## Deployment ### Manual ```sh cd packages/search-worker pnpm install pnpm run deploy ``` ### CI/CD You can add a GitHub Actions workflow similar to the AI Chat Worker deployment. The workflow should deploy the Worker on push to `main` when files in `packages/search-worker/` change. Required GitHub secrets: - `CLOUDFLARE_API_TOKEN` -- Cloudflare API token with Workers write permission - `CLOUDFLARE_ACCOUNT_ID` -- Your Cloudflare account ID ## When to Use Each | Feature | Client-Side Search (built-in) | Search Worker | | -------------------------- | ----------------------------- | -------------------------- | | Runtime | Browser (in-memory) | Cloudflare Workers | | Deployment | Part of the docs site | Independent service | | Index download | Full index sent to browser | Index stays server-side | | Best for | Small-to-medium doc bases | Large doc bases, API consumers | | Setup required | None (enabled by default) | Cloudflare account + KV | | Latency | Instant (local) | Network round-trip | | Search config | `prefix: true, fuzzy: 0.2, boost: { title: 3, description: 2 }` | Same | Both use the same search index (`search-index.json`) and the same MiniSearch configuration, so results are consistent. ## Request Flow 1. CORS preflight handling 2. Method check (POST only) and path check (`/` only) 3. JSON parse and query validation (required, max 500 chars) 4. Client IP hashed with SHA-256 via Web Crypto API 5. Rate limit check against KV 6. Fetch `search-index.json` from docs site (cached with 5-minute TTL) 7. MiniSearch query with prefix, fuzzy, and boost settings 8. Return results ## Sub-Package Location ``` packages/search-worker/ ├── src/ │ ├── index.ts # Worker entry point — routing, validation, CORS │ ├── cors.ts # CORS header handling │ ├── rate-limit.ts # Per-IP rate limiting via KV │ ├── search.ts # MiniSearch index loader + search logic │ └── types.ts # Type definitions ├── wrangler.toml # Cloudflare Worker configuration ├── package.json ├── tsconfig.json └── README.md ``` --- # Doc History Server > Source: /pj/zudo-doc/docs/reference/doc-history-server Standalone package for serving and generating document git history, with dual modes: REST API server for local development and CLI batch generator for CI builds. ## Overview The Doc History Server is a sub-package at `packages/doc-history-server/` that handles all git history extraction for the [Document History](/docs/guides/doc-history) feature. It was extracted from the Astro build pipeline to decouple expensive git operations from the site build. It operates in two modes: - **Server mode** -- runs an HTTP server during local development, serving history on demand - **CLI mode** -- batch-generates all history JSON files for production builds in CI This separation enables a parallel CI strategy where the Astro site build and history generation run as independent jobs, reducing total build time. ## Server Mode (Local Development) The server runs on a configurable port (default 4322) and serves document history via a REST API. In the standard `pnpm dev` setup, Astro and the doc-history-server run concurrently via `run-p`. ### Starting the Server ```sh pnpm dev -- --content-dir src/content/docs --locale ja:src/content/docs-ja --port 4322 ``` ### Endpoints #### GET /doc-history/\{slug\}.json Returns the full git history for a document. ``` GET /doc-history/guides/writing-docs.json ``` #### GET /doc-history/\{locale\}/\{slug\}.json Returns history for a localized document. ``` GET /doc-history/ja/guides/writing-docs.json ``` #### GET /health Health check endpoint. ```json { "status": "ok" } ``` ### Server Options | Flag | Required | Default | Description | | ---- | -------- | ------- | ----------- | | `--content-dir` | Yes | -- | Content directory to scan for documents | | `--locale :` | No | -- | Additional locale directory (repeatable) | | `--port` | No | `4322` | HTTP server port | | `--max-entries` | No | `50` | Maximum git commits per document | ### Server Behavior - File index refreshes every 10 seconds, automatically picking up new or renamed files - CORS headers are included for cross-origin dev access - Requests for unknown slugs return 404 with `{ "error": "No doc found for slug: ..." }` ### Astro Integration In dev mode, the Astro integration at `src/integrations/doc-history.ts` proxies `/doc-history/*` requests to this server. This means the history panel in the browser transparently fetches from the standalone server through the Astro dev server. The root `pnpm dev` command runs both Astro (port 4321) and the doc-history-server (port 4322) concurrently using `run-p`. ## CLI Mode (CI Builds) The CLI generates static JSON files for all documents, intended for CI pipelines. ### Usage ```sh pnpm generate -- --content-dir src/content/docs --locale ja:src/content/docs-ja --out-dir dist/doc-history ``` ### CLI Options | Flag | Required | Default | Description | | ---- | -------- | ------- | ----------- | | `--content-dir` | Yes | -- | Content directory to scan | | `--locale :` | No | -- | Additional locale directory (repeatable) | | `--out-dir` | Yes | -- | Output directory for generated JSON files | | `--max-entries` | No | `50` | Maximum git commits per document | ### CI Pipeline Integration The CI pipeline runs history generation as a parallel job alongside the Astro site build: - **build-site** job: shallow clone (`fetch-depth: 1`), builds the Astro site with `SKIP_DOC_HISTORY=1` - **build-history** job: full clone (`fetch-depth: 0`), runs `@zudo-doc/doc-history-server generate` - **deploy** job: merges both artifacts and deploys to Cloudflare Pages This parallelization is possible because the history generation is fully independent of the Astro build. The `build-history` job requires a full git clone (`fetch-depth: 0`) because it needs the complete commit history to extract revision data. The `build-site` job can use a shallow clone since it only needs the current file contents. ## Data Format ### DocHistoryEntry A single git revision entry for a document: ```ts interface DocHistoryEntry { /** Full commit hash (use .slice(0, 7) for display) */ hash: string; /** ISO 8601 date string */ date: string; /** Commit author name */ author: string; /** First line of commit message */ message: string; /** Full file content at this revision */ content: string; } ``` ### DocHistoryData Complete history data for a single document: ```ts interface DocHistoryData { /** Document slug (route path) */ slug: string; /** Relative file path in the repository */ filePath: string; /** Git revision entries, newest first */ entries: DocHistoryEntry[]; } ``` ### Output Structure Generated files mirror the content directory structure: ``` dist/doc-history/ getting-started.json guides/writing-docs.json guides/color.json ja/getting-started.json ja/guides/writing-docs.json ``` ## Architecture ### Git Operations History extraction uses synchronous git calls (`execFileSync`) for: - `git log` -- commit listing with `--follow` for rename tracking - `git show` -- file content at each revision The `--follow` flag tracks file history across renames with multiple fallback strategies, so documents that have been moved or renamed retain their complete history. ### Key Design Decisions - **Synchronous git** -- `execFileSync` is acceptable for the dev server (same-process, sequential). The CI CLI is inherently sequential as well - **Repo-relative paths** -- API responses use relative file paths to avoid leaking absolute server paths - **Standalone package** -- decoupled from Astro to enable parallel CI builds and independent versioning ## Sub-Package Location ``` packages/doc-history-server/ ├── src/ │ ├── index.ts # Server entry — parses args, starts HTTP server │ ├── cli.ts # CLI entry — parses args, batch generates JSONs │ ├── args.ts # Shared argument parsing with bounds checking │ ├── server.ts # HTTP server (REST API endpoints) │ ├── git-history.ts # Core git logic (log, show, follow, rename tracking) │ ├── shared.ts # Shared helpers (getContentDirEntries) │ └── types.ts # DocHistoryEntry, DocHistoryData types ├── package.json ├── tsconfig.json └── README.md ``` --- # Layout Pattern Demos > Source: /pj/zudo-doc/docs/guides/layout-demos Live examples of different layout configurations using `hide_sidebar` and `hide_toc` frontmatter options. --- # Claude > Source: /pj/zudo-doc/docs/claude Claude Code configuration reference. ## Resources --- # doc-reviewer > Source: /pj/zudo-doc/docs/claude-agents/doc-reviewer **Model:** `sonnet` # Doc Reviewer Agent You are a documentation reviewer agent. Your job is to review MDX documentation files for quality. ## Review Checklist - Is the title clear and descriptive? - Does the content match the title? - Are code examples correct and runnable? - Is the writing concise and free of jargon? ## Output Format Provide feedback as a numbered list of suggestions, grouped by severity: - **Critical**: Errors that would confuse readers - **Suggestion**: Improvements that would help clarity --- # check-docs > Source: /pj/zudo-doc/docs/claude-skills/check-docs # Check Docs Scan documentation files for common issues and report problems found. ## When to Use - Before publishing or merging documentation changes - As part of a review workflow to catch broken links - When reorganizing docs structure to verify no links broke ## How It Works 1. Build the site with `pnpm build` 2. Run the link checker with `pnpm check:links` to verify all internal links in the built output 3. Scan all `.mdx` and `.md` files in the configured `docsDir` 4. Check for broken internal links (references to pages that don't exist) 5. Check for missing frontmatter fields (`title` is required) 6. Report findings as a summary The recommended command to run all checks: ```bash pnpm build && pnpm check:links ``` ## Example Usage ```bash /check-docs ``` This skill runs `pnpm build && pnpm check:links` to build the site and verify all links. ## Checks Performed - **Broken links (build output)** — `pnpm check:links` scans the built HTML for broken internal links - **Broken links (source)** — Internal `[text](./path)` links that point to non-existent files - **Missing title** — MDX files without a `title` in frontmatter - **Empty files** — Files with no content after frontmatter - **Duplicate sidebar_position** — Multiple files in the same category with the same position value --- # l-generator-cli-tester > Source: /pj/zudo-doc/docs/claude-skills/l-generator-cli-tester # Generator CLI Pattern Tester Test a single `create-zudo-doc` CLI generation pattern by scaffolding a project, building it, running the dev server briefly, and verifying the expected files and settings. ## Usage ``` /l-generator-cli-tester /l-generator-cli-tester --headless ``` Where `` is one of the test patterns listed below. ### Options - `--headless` — After standard checks, also run headless browser verification using `/headless-browser` to confirm pages actually render (Step 8.5). Without this flag, headless checks are skipped. ## Test Patterns | Pattern | Description | |---------|-------------| | `barebone` | Everything OFF — minimal project | | `search` | Only search enabled | | `i18n` | Only i18n enabled | | `sidebar-filter` | Only sidebar filter enabled | | `claude-resources` | Only claude resources enabled | | `design-token-panel` | Only design token panel enabled (uses API) | | `light-dark` | Light-dark color mode | | `lang-ja` | Japanese as default language | | `all-features` | Everything ON | ## Step 0: Build the CLI Before running any test, set `REPO_ROOT` and build the CLI: ```bash REPO_ROOT=$(git rev-parse --show-toplevel) cd packages/create-zudo-doc && pnpm build ``` If the build fails, stop and report the error. ## Step 1: Create Temp Directory ```bash mkdir -p __inbox/generator-test- ``` ## Step 2: Run the Generator Set `REPO_ROOT` to the repository root (absolute path). Run the generator from within the temp directory. Always use `--no-install` to handle installation separately. ### CLI Commands per Pattern **barebone:** ```bash cd __inbox/generator-test-barebone && \ node $REPO_ROOT/packages/create-zudo-doc/dist/index.js test-project --yes \ --no-search --no-sidebar-filter --no-i18n --no-claude-resources \ --color-scheme-mode single --scheme "Default Dark" --no-install ``` **search:** ```bash cd __inbox/generator-test-search && \ node $REPO_ROOT/packages/create-zudo-doc/dist/index.js test-project --yes \ --search --no-sidebar-filter --no-i18n --no-claude-resources \ --color-scheme-mode single --scheme "Default Dark" --no-install ``` **i18n:** ```bash cd __inbox/generator-test-i18n && \ node $REPO_ROOT/packages/create-zudo-doc/dist/index.js test-project --yes \ --no-search --no-sidebar-filter --i18n --no-claude-resources \ --color-scheme-mode single --scheme "Default Dark" --no-install ``` **sidebar-filter:** ```bash cd __inbox/generator-test-sidebar-filter && \ node $REPO_ROOT/packages/create-zudo-doc/dist/index.js test-project --yes \ --no-search --sidebar-filter --no-i18n --no-claude-resources \ --color-scheme-mode single --scheme "Default Dark" --no-install ``` > Note: Sidebar filter stripping is not yet implemented (TODO in strip.ts). The filter is built into `sidebar-tree.tsx` and is always included regardless of the flag. This test mainly verifies the flag doesn't cause errors. **claude-resources:** ```bash cd __inbox/generator-test-claude-resources && \ node $REPO_ROOT/packages/create-zudo-doc/dist/index.js test-project --yes \ --no-search --no-sidebar-filter --no-i18n --claude-resources \ --color-scheme-mode single --scheme "Default Dark" --no-install ``` **design-token-panel:** > `designTokenPanel` has NO CLI flag. Use the programmatic API instead: ```bash cd __inbox/generator-test-design-token-panel && \ node --input-type=module -e " await createZudoDoc({ projectName: 'test-project', colorSchemeMode: 'single', singleScheme: 'Default Dark', features: ['designTokenPanel'], packageManager: 'pnpm', install: false, }); console.log('Scaffolding complete.'); " ``` **light-dark:** ```bash cd __inbox/generator-test-light-dark && \ node $REPO_ROOT/packages/create-zudo-doc/dist/index.js test-project --yes \ --no-search --no-sidebar-filter --no-i18n --no-claude-resources \ --color-scheme-mode light-dark --light-scheme "Default Light" --dark-scheme "Default Dark" \ --no-install ``` **lang-ja:** ```bash cd __inbox/generator-test-lang-ja && \ node $REPO_ROOT/packages/create-zudo-doc/dist/index.js test-project --yes \ --no-search --no-sidebar-filter --no-i18n --no-claude-resources \ --lang ja --color-scheme-mode single --scheme "Default Dark" --no-install ``` **all-features:** ```bash cd __inbox/generator-test-all-features && \ node --input-type=module -e " await createZudoDoc({ projectName: 'test-project', colorSchemeMode: 'light-dark', lightScheme: 'Default Light', darkScheme: 'Default Dark', defaultMode: 'dark', respectPrefersColorScheme: true, features: ['i18n', 'search', 'sidebarFilter', 'claudeResources', 'designTokenPanel'], packageManager: 'pnpm', install: false, }); console.log('Scaffolding complete.'); " ``` > Note: `all-features` uses the API because `designTokenPanel` has no CLI flag. ## Step 3: Install Dependencies ```bash cd __inbox/generator-test-/test-project && pnpm install ``` If installation fails, report the error and stop. ## Step 4: Build ```bash cd __inbox/generator-test-/test-project && pnpm build ``` If the build fails, report the error and stop. ## Step 5: Dev Server Smoke Test Start the dev server, wait for startup, check it didn't crash, then kill it: ```bash cd __inbox/generator-test-/test-project && \ timeout 15 pnpm dev 2>&1 & DEV_PID=$! sleep 8 if kill -0 $DEV_PID 2>/dev/null; then echo "DEV_SERVER: OK — process still running" kill $DEV_PID 2>/dev/null wait $DEV_PID 2>/dev/null else wait $DEV_PID EXIT_CODE=$? echo "DEV_SERVER: FAILED — process exited with code $EXIT_CODE" fi ``` If the dev server crashed, report the error. ## Step 6: Verify Files Check that expected files exist or don't exist in `__inbox/generator-test-/test-project/`. ### File Expectations per Pattern Use these tables to verify. Check each file with `test -e `. > **Note on `theme-toggle.tsx`**: this component always ships on disk as part of the base template — whether it renders at runtime is gated by the `colorMode` setting. The expectation tables below therefore only assert PRESENT for the `light-dark` and `all-features` patterns and do not assert ABSENT elsewhere. **barebone** — minimal, everything stripped: | File | Expected | |------|----------| | `src/components/search.astro` | ABSENT | | `src/components/language-switcher.astro` | ABSENT | | `src/pages/[locale]/` | ABSENT | | `src/content/docs-ja/` | ABSENT | | `src/integrations/claude-resources/` | ABSENT | | `src/components/design-token-tweak/` | ABSENT | | `src/components/doc-history.tsx` | ABSENT | | `src/components/ai-chat-modal.tsx` | ABSENT | | `src/integrations/doc-history.ts` | ABSENT | | `src/integrations/llms-txt.ts` | ABSENT | | `src/integrations/sitemap.ts` | ABSENT | | `src/pages/docs/` | PRESENT | | `src/content/docs/` | PRESENT | | `src/config/settings.ts` | PRESENT | | `astro.config.ts` | PRESENT | **search:** | File | Expected | |------|----------| | `src/components/search.astro` | PRESENT | | `src/components/language-switcher.astro` | ABSENT | | `src/integrations/claude-resources/` | ABSENT | | `src/components/design-token-tweak/` | ABSENT | **i18n:** | File | Expected | |------|----------| | `src/components/search.astro` | ABSENT | | `src/components/language-switcher.astro` | PRESENT | | `src/pages/[locale]/` | PRESENT | | `src/content/docs-ja/` | PRESENT | | `src/integrations/claude-resources/` | ABSENT | | `src/components/design-token-tweak/` | ABSENT | **sidebar-filter:** | File | Expected | |------|----------| | `src/components/search.astro` | ABSENT | | `src/components/sidebar-tree.tsx` | PRESENT | | `src/components/language-switcher.astro` | ABSENT | | `src/components/design-token-tweak/` | ABSENT | **claude-resources:** | File | Expected | |------|----------| | `src/integrations/claude-resources/` | PRESENT | | `src/components/search.astro` | ABSENT | | `src/components/language-switcher.astro` | ABSENT | | `src/components/design-token-tweak/` | ABSENT | **design-token-panel:** | File | Expected | |------|----------| | `src/components/design-token-tweak/index.tsx` | PRESENT | | `src/components/design-token-tweak/export-modal.tsx` | PRESENT | | `src/config/color-tweak-presets.ts` | PRESENT | | `src/utils/color-convert.ts` | PRESENT | | `src/utils/design-token-serde.ts` | PRESENT | | `src/components/search.astro` | ABSENT | | `src/components/language-switcher.astro` | ABSENT | **light-dark:** | File | Expected | |------|----------| | `src/components/theme-toggle.tsx` | PRESENT | | `src/components/search.astro` | ABSENT | | `src/components/language-switcher.astro` | ABSENT | | `src/components/design-token-tweak/` | ABSENT | **lang-ja:** | File | Expected | |------|----------| | `src/components/search.astro` | ABSENT | | `src/components/language-switcher.astro` | ABSENT | | `src/pages/docs/` | PRESENT | | `src/content/docs/` | PRESENT | **all-features:** | File | Expected | |------|----------| | `src/components/search.astro` | PRESENT | | `src/components/language-switcher.astro` | PRESENT | | `src/pages/[locale]/` | PRESENT | | `src/content/docs-ja/` | PRESENT | | `src/integrations/claude-resources/` | PRESENT | | `src/components/theme-toggle.tsx` | PRESENT | | `src/components/design-token-tweak/index.tsx` | PRESENT | | `src/components/design-token-tweak/export-modal.tsx` | PRESENT | | `src/config/color-tweak-presets.ts` | PRESENT | ## Step 7: Verify Settings Read `__inbox/generator-test-/test-project/src/config/settings.ts` and check: ### Settings Expectations per Pattern **barebone:** - `colorScheme: "Default Dark"` - `colorMode: false` - `locales: {}` (empty) - `designTokenPanel: false` - `claudeResources: false` **search:** - `colorScheme: "Default Dark"` - `colorMode: false` **i18n:** - `locales:` should contain `ja` entry with `dir: "src/content/docs-ja"` **sidebar-filter:** - Same as barebone but with `sidebarFilter` defaulting to included **claude-resources:** - `claudeResources:` should be truthy (object with `claudeDir`) **design-token-panel:** - `designTokenPanel: true` **light-dark:** - `colorMode:` should be an object with `defaultMode`, `lightScheme: "Default Light"`, `darkScheme: "Default Dark"` **lang-ja:** - `colorScheme: "Default Dark"` - Check `src/config/i18n.ts` for `defaultLocale = "ja"` - Check `astro.config.ts` for `defaultLocale: "ja"` **all-features:** - `colorMode:` should be an object (light-dark mode) - `locales:` should contain `ja` entry - `claudeResources:` should be truthy - `designTokenPanel: true` ## Step 8: Compare Against Showcase For the feature being tested, briefly compare the generated project against the main zudo-doc showcase: - Read the equivalent component/config in `src/` (the showcase) and in the generated project - Verify they share the same structure (the generated version may have stripped imports/features, but the enabled feature's code should match) This is a sanity check, not a full diff. Focus on the feature under test. ## Step 8.5: Headless Browser Check (only with `--headless`) **Skip this step unless `--headless` was passed.** Start the dev server and use `/headless-browser` (Tier 1: headless-check.js) to verify pages actually render in a browser. ### 8.5a. Start dev server ```bash cd __inbox/generator-test-/test-project npx astro dev --port 14350 & DEV_PID=$! sleep 6 ``` ### 8.5b. Check pages with headless browser Check the index page and a docs page: ```bash HC=~/.claude/skills/headless-browser/scripts/headless-check.js node $HC --url "http://localhost:14350/" --screenshot viewport --no-block-resources node $HC --url "http://localhost:14350/docs/getting-started" --screenshot viewport --no-block-resources ``` For **i18n** and **all-features** patterns, also check the Japanese page: ```bash node $HC --url "http://localhost:14350/ja/docs/getting-started" --screenshot viewport --no-block-resources ``` ### 8.5c. Verify results - All pages should return `statusCode: 200` - `pageErrors` should be empty (no JS errors) - `networkErrors.failedRequests` — ignore `net::ERR_ABORTED` (Vite HMR re-optimization, normal in dev). Flag any other failures. - **Read the screenshots** with the Read tool and visually confirm: - **search**: search icon (magnifying glass) visible in header - **i18n**: "EN / JA" language switcher in header - **light-dark**: theme toggle icon in header - **design-token-panel**: design token icon in header - **claude-resources**: page renders without errors - **all-features**: all icons present (search, theme toggle, language switcher, color tweak) - **barebone**: no extra icons in header (no search, no theme toggle, no language switcher) - **lang-ja**: Japanese content ("ようこそ" title) ### 8.5d. Kill dev server ```bash kill $DEV_PID 2>/dev/null; wait $DEV_PID 2>/dev/null ``` ## Step 9: Clean Up ```bash rm -rf ./__inbox/generator-test- ``` Always use relative path with `./` prefix for cleanup. ## Step 10: Report Results Provide a clear pass/fail report: ``` ## Pattern: ### Scaffold: PASS/FAIL ### Install: PASS/FAIL ### Build: PASS/FAIL ### Dev Server: PASS/FAIL ### File Verification: PASS/FAIL - [list any unexpected files present/absent] ### Settings Verification: PASS/FAIL - [list any mismatches] ### Showcase Comparison: PASS/FAIL - [notes] ### Headless Browser: PASS/FAIL/SKIPPED - [only if --headless was passed] ### Overall: PASS/FAIL ``` ## Important Notes - Always `cd` back to the repo root between major steps (use absolute paths) - The `--yes` flag auto-fills all unspecified options with defaults. Feature defaults with `--yes`: search=true, sidebarFilter=true, i18n=false, claudeResources=false, designTokenPanel=false - Use `--no-install` with CLI to prevent auto-install, then install manually for better error visibility - Sidebar filter stripping is TODO — the filter is always included regardless of the `--sidebar-filter` flag - `designTokenPanel` has no CLI flag — use the API approach for `design-token-panel` and `all-features` patterns - The dev server smoke test uses `pnpm dev` (generated projects have a single `dev` script) - If any step fails, still report all steps attempted before stopping - The `--headless` flag enables Step 8.5 (headless browser visual check). Without it, only process-level checks are performed --- # l-run-generator-cli-whole-test > Source: /pj/zudo-doc/docs/claude-skills/l-run-generator-cli-whole-test # Generator CLI Whole Test Runner Run ALL `create-zudo-doc` generator patterns end-to-end, fix any failures, and verify everything passes. ## When to Use - Before releasing a new version of `create-zudo-doc` - After modifying generator source files (`scaffold.ts`, `strip.ts`, `settings-gen.ts`) - After adding/removing features from the main zudo-doc project - User says "run all generator tests", "whole test", "l-run-generator-cli-whole-test" ### Options - `--headless` — Pass `--headless` to each `/l-generator-cli-tester` invocation, enabling headless browser checks (visual rendering verification via `/headless-browser`). Without this flag, only process-level checks are performed. ## Prerequisites Build the CLI before testing: ```bash cd packages/create-zudo-doc && pnpm build ``` If the build fails, fix the TypeScript errors first before proceeding. ## Phase 1: Run All Test Patterns Run each pattern by invoking `/l-generator-cli-tester `. Start with `barebone` as the baseline. ### Test order Run in this order (CLI flags and details are defined in `/l-generator-cli-tester`): 1. **`barebone`** — All optional features OFF. Must pass first — if this fails, fix it before testing others. 2. **`search`** — Only search enabled 3. **`i18n`** — Only i18n enabled 4. **`sidebar-filter`** — Only sidebar filter enabled 5. **`claude-resources`** — Only Claude Resources enabled 6. **`design-token-panel`** — Only design token panel enabled (uses API, no CLI flag) 7. **`light-dark`** — Light-dark color scheme mode 8. **`lang-ja`** — Japanese as default language 9. **`all-features`** — Everything ON, maximum complexity (uses API) ### Running each pattern For each pattern, invoke the companion skill: ``` /l-generator-cli-tester /l-generator-cli-tester --headless # if --headless was passed to this skill ``` This skill handles scaffold generation, `pnpm install`, `pnpm build`, `pnpm dev` smoke test, feature verification, and optionally headless browser rendering checks for one pattern. ### Collect results Track results in a summary table as you go: ``` | Pattern | Build | Dev | Features | Status | |--------------------|-------|-----|----------|--------| | barebone | PASS | PASS| PASS | ok | | search | FAIL | - | - | FAIL | | i18n | PASS | PASS| PASS | ok | | ... | ... | ... | ... | ... | ``` Record the first error message for any failing pattern. ## Phase 2: Fix Bugs For each failing pattern: ### 2a. Diagnose the failure - Read the error output from `/l-generator-cli-tester` - Determine which phase failed: scaffold, build, dev, or feature check - Common failure categories: - **Build error: missing module** — dependency not in generated `package.json` → fix `scaffold.ts` `generatePackageJson()` - **Build error: import not found** — import not stripped for disabled feature → fix `strip.ts` - **Build error: type error in settings.ts** — settings field missing/wrong → fix `settings-gen.ts` - **Build error: component references stripped component** — template usage not stripped → fix `strip.ts` patch patterns - **Dev server crash** — runtime error in generated code → read the generated file and trace the issue to the source - **Feature check fail** — feature file exists when it should be removed, or missing when it should exist → fix `strip.ts` ### 2b. Read the generator source files The key files to examine: | File | Role | |------|------| | `packages/create-zudo-doc/src/scaffold.ts` | Copies template, generates `package.json` | | `packages/create-zudo-doc/src/strip.ts` | Removes features/imports based on options | | `packages/create-zudo-doc/src/settings-gen.ts` | Generates `settings.ts` | | `packages/create-zudo-doc/src/constants.ts` | Feature definitions and color schemes | | `packages/create-zudo-doc/src/cli.ts` | CLI argument parsing | | `packages/create-zudo-doc/src/api.ts` | Programmatic API | ### 2c. Apply the fix - Edit the appropriate generator source file - Target the root cause in the generator, not the generated output - Keep fixes minimal and focused ### 2d. Rebuild and re-test After each fix: ```bash cd packages/create-zudo-doc && pnpm build ``` Then re-run the failing pattern: ``` /l-generator-cli-tester ``` ### 2e. Commit each fix Commit each fix individually with a descriptive message: ``` fix(create-zudo-doc): fix generation — ``` Examples: - `fix(create-zudo-doc): fix barebone generation — strip remark-directive import when unused` - `fix(create-zudo-doc): fix i18n generation — add missing content.config.ts patching` - `fix(create-zudo-doc): fix light-dark generation — include theme-toggle.tsx in dependencies` ## Phase 3: Final Verification After all fixes are applied: ### 3a. Re-run ALL patterns Run every pattern again from scratch to ensure fixes didn't break other patterns: ``` /l-generator-cli-tester barebone /l-generator-cli-tester search /l-generator-cli-tester i18n /l-generator-cli-tester sidebar-filter /l-generator-cli-tester claude-resources /l-generator-cli-tester design-token-panel /l-generator-cli-tester light-dark /l-generator-cli-tester lang-ja /l-generator-cli-tester all-features ``` ### 3b. Run existing unit tests ```bash cd packages/create-zudo-doc && pnpm test ``` All tests must pass. If any fail, fix them and commit. ### 3c. Build the CLI one final time ```bash cd packages/create-zudo-doc && pnpm build ``` ## Phase 4: Summary Output a final report: ``` ## Generator CLI Whole Test Results ### Test Results | Pattern | First Run | After Fixes | Status | |--------------------|-----------|-------------|--------| | barebone | PASS | PASS | ok | | search | FAIL | PASS | fixed | | i18n | PASS | PASS | ok | | sidebar-filter | PASS | PASS | ok | | claude-resources | FAIL | PASS | fixed | | design-token-panel | PASS | PASS | ok | | light-dark | PASS | PASS | ok | | lang-ja | PASS | PASS | ok | | all-features | FAIL | PASS | fixed | ### Summary - Patterns tested: 9 - Passed on first try: 6 - Needed fixes: 3 - Unit tests: PASS ### Fixes Applied 1. `scaffold.ts`: Added missing `minisearch` dependency for search pattern 2. `strip.ts`: Fixed claude-resources import stripping regex 3. `strip.ts`: Fixed all-features light-dark theme-toggle handling ### Final Status: ALL PASS ``` ## Important Notes - Always build the CLI (`pnpm build` in `packages/create-zudo-doc`) before testing and after each fix - Fix the generator source code, never the generated output - The `barebone` pattern is the baseline — if it fails, fix it before testing others - Test directories should be placed in `__inbox/` (gitignored) to avoid polluting the repo - Each fix should be a separate commit for clear git history - If a fix for one pattern breaks another, investigate the interaction before committing --- # l-update-generator > Source: /pj/zudo-doc/docs/claude-skills/l-update-generator # Update create-zudo-doc Generator Detect and fix drift between the main zudo-doc project and the `create-zudo-doc` CLI generator. ## Architecture Overview The generator uses **additive composition** — not copy-then-strip: 1. A minimal **base template** (`templates/base/`) is copied first 2. **Programmatic generators** create config files from user choices (`astro-config-gen.ts`, `content-config-gen.ts`, `settings-gen.ts`) 3. **Feature modules** (`src/features/*.ts`) inject code into anchor points (`@slot:`) in shared files and copy feature-specific files 4. `compose.ts` orchestrates feature file copying, injection, and anchor cleanup 5. Some features have **postProcess hooks** that mutate or remove generated files (e.g., `i18n.ts` patches locale strings and may delete `src/pages/ja/`) ## When to Use - After adding or removing a feature from zudo-doc - When the drift-detection test fails - Periodically to verify generator health - User says "update generator", "sync generator", "check generator drift", "l-update-generator", or the old name "l-sync-create-zudo-doc" **Quick pre-check**: Run `pnpm check:template-drift` first to get an automated summary of base template drift. Then proceed with the full workflow below for settings, dependency, astro config, and feature composition drift. ## Step 1: Analyze Drift Compare the main project's source files with what the generator produces. ### 1a. Settings drift Read both files and extract setting field names: - **Main**: `src/config/settings.ts` — the canonical settings object - **Generator**: `packages/create-zudo-doc/src/settings-gen.ts` — what gets generated Compare field names. Any field in the main settings that is missing from the generator output is drift. ### 1b. Dependency drift Compare dependencies: - **Main**: `package.json` — root dependencies - **Generator**: `packages/create-zudo-doc/src/scaffold.ts` `generatePackageJson()` — generated deps Check for packages used in base template files and feature files that are not included in the generated package.json. ### 1c. Astro config drift Compare the main project's `astro.config.ts` with what the generator produces: - **Main**: `astro.config.ts` — all imports and integrations - **Generator**: `packages/create-zudo-doc/src/astro-config-gen.ts` — programmatic generation For each integration in the main astro.config.ts, verify that either: 1. It is unconditionally included in `astro-config-gen.ts`, OR 2. It is conditionally included based on the correct feature selection ### 1d. Feature composition drift Check that features in the main project have corresponding feature modules: - **Main**: Feature-gated files across `src/components/`, `src/integrations/`, `src/pages/`, `src/config/`, `src/utils/`, `src/scripts/`, `src/hooks/` - **Generator**: `packages/create-zudo-doc/src/features/*.ts` — feature modules with injections and postProcess hooks - **Templates**: `packages/create-zudo-doc/templates/features/*/files/` — feature-specific files For each feature-gated file in the main project, verify: 1. A feature module exists in `src/features/` 2. The feature's files are in `templates/features//files/` 3. Injection anchors (`@slot:`) exist in the base template for the feature's injections 4. If the feature has a `postProcess` hook, verify the mutations it performs still match the main project's behavior ### 1e. Base template drift Compare shared files between the main project and the base template: - **Main**: `src/components/`, `src/config/`, `src/hooks/`, `src/layouts/`, `src/pages/`, `src/plugins/`, `src/scripts/`, `src/styles/`, `src/types/`, `src/utils/` - **Template**: `packages/create-zudo-doc/templates/base/` Check that non-feature-specific files in the base template reflect the latest changes from the main project (layout updates, plugin changes, utility functions, type definitions, etc.). **Automated first check**: Run `pnpm check:template-drift` before doing manual analysis. This runs `scripts/check-template-drift.sh` and quickly identifies files that differ between the main project and the base template. **Allowlist note**: Some files are listed in `.template-drift-allowlist` (e.g., `global.css`, `header.astro`, `doc-layout.astro`) and are skipped entirely by the automated script because they contain slot sections that intentionally differ. These files **still require manual review** — check that any non-slot-section changes in the main project are reflected in the template counterpart. ## Step 2: Report Findings Present a clear drift report: ``` ## Settings Drift - Missing in generator: fieldA, fieldB - Extra in generator (not in main): fieldC ## Dependency Drift - Missing from generated package.json: packageX (used by featureY) - Unnecessary in generated package.json: packageZ (feature disabled) ## Astro Config Drift - Missing integration: integrationX (present in main, absent from astro-config-gen.ts) - Missing conditional: integrationY should be gated by feature "Z" ## Feature Composition Drift - Missing feature module: feature "X" exists in main but has no feature module - Missing template files: feature "X" module exists but templates/features/X/files/ is missing - Missing anchor: feature "X" injects at @slot:Y but anchor not found in base template ## Base Template Drift - Stale file: templates/base/src/components/foo.astro differs from main src/components/foo.astro ## No Drift Detected (if everything is in sync) ``` ## Step 3: Apply Fixes For each drift item found: 1. **Settings drift** → Update `settings-gen.ts` to add missing fields with sensible defaults 2. **Dependency drift** → Update `scaffold.ts` `generatePackageJson()` to add/remove deps 3. **Astro config drift** → Update `astro-config-gen.ts` to add/remove imports and integrations 4. **Feature composition drift** → Create/update feature module in `src/features/`, add template files, add anchors to base template 5. **Base template drift** → Update the stale file in `templates/base/` After fixes: 1. Run `cd packages/create-zudo-doc && pnpm build` to verify TypeScript compiles 2. Run `cd packages/create-zudo-doc && pnpm test` to verify tests pass (including drift-detection test) 3. Commit with message: `fix(create-zudo-doc): sync generator with main project` ## Key Files | File | Role | |------|------| | `src/config/settings.ts` | Canonical settings (source of truth) | | `astro.config.ts` | Main project astro config (reference for generator) | | `packages/create-zudo-doc/src/settings-gen.ts` | Generates settings.ts | | `packages/create-zudo-doc/src/astro-config-gen.ts` | Generates astro.config.ts programmatically | | `packages/create-zudo-doc/src/content-config-gen.ts` | Generates content.config.ts programmatically | | `packages/create-zudo-doc/src/compose.ts` | Additive composition engine (injections, anchors) | | `packages/create-zudo-doc/src/features/*.ts` | Feature modules (injections + file lists) | | `packages/create-zudo-doc/src/scaffold.ts` | Orchestrates generation pipeline, generates package.json | | `packages/create-zudo-doc/templates/base/` | Minimal base template with `@slot:` anchors | | `packages/create-zudo-doc/templates/features/` | Feature-specific files copied when feature is enabled | | `packages/create-zudo-doc/src/__tests__/scaffold.test.ts` | Integration tests | --- # zudo-doc-design-system > Source: /pj/zudo-doc/docs/claude-skills/zudo-doc-design-system # zudo-doc CSS & Component Rules **IMPORTANT**: These rules are mandatory for all code changes in this project that touch CSS, Tailwind classes, color tokens, or component markup. Read the relevant section before making changes. ## How to Use Based on the topic, read the specific reference doc: | Topic | File | |-------|------| | Spacing, typography, layout tokens | `src/content/docs/reference/design-system.mdx` | | Component-first methodology | `src/content/docs/reference/component-first.mdx` | | Color tokens, palette, schemes | `src/content/docs/reference/color.mdx` | Read ONLY the file relevant to your task. Apply its rules strictly. ## Quick Rules (always apply) ### Component First (no custom CSS classes) - **NEVER** create CSS module files, custom class names, or separate stylesheets - **ALWAYS** use Tailwind utility classes directly in component markup - The component itself is the abstraction — `.card`, `.btn-primary` are forbidden - Use props for variants, not CSS modifiers ### Design Tokens (no arbitrary values) - **NEVER** use Tailwind default colors (`bg-gray-500`, `text-blue-600`) — they are reset to `initial` - **NEVER** use arbitrary values (`text-[0.875rem]`, `p-[1.2rem]`) when a token exists - **ALWAYS** use project tokens: `text-fg`, `bg-surface`, `border-muted`, `p-hsp-md`, `text-small` - Spacing: `hsp-*` (horizontal), `vsp-*` (vertical) — see design-system.mdx for full list - Typography: `text-caption`, `text-small`, `text-body`, `text-heading` etc. ### Color Tokens (three-tier system) - **Tier 1** (palette): `p0`–`p15` — raw colors, use only when no semantic token fits - **Tier 2** (semantic): `text-fg`, `bg-surface`, `border-muted`, `text-accent` — prefer these - **NEVER** use hardcoded hex values in components - Palette index convention (consistent across all themes): - p1=danger, p2=success, p3=warning, p4=info, p5=accent - p8=muted, p9=background, p10=surface, p11=text primary ### Search & highlight tokens (role-split) Highlight roles are deliberately split across dedicated semantic tokens — do **not** share one token across unrelated highlight UIs. - `matched-keyword-bg` / `matched-keyword-fg` — background and foreground of the search panel `` element. Driven by `--color-matched-keyword-bg` / `--color-matched-keyword-fg`; live-editable in the Design Token Panel. This is the single source of truth for "why is this color yellow in the search results" — the panel swatch matches the rendered highlight 1:1. - `warning` — drives admonitions (`:::warning`), find-in-page (`.find-match`, `.find-match-active`), and any UI that is semantically a warning. Do **not** reuse it for new UI-chrome highlights. **Rule**: when a new highlight role appears (new kind of mark, new pill, new callout), add a dedicated semantic token rather than bolting it onto `--color-warning` or another existing token. Each visible highlight color should map to exactly one panel swatch. ### hover:underline on link-like elements Any element that navigates (rendered as `` or behaves as a link) MUST have `hover:underline focus-visible:underline`. Keyboard users need the same affordance as mouse users — never add `hover:underline` without the `focus-visible:underline` pair. - **Links (do underline)**: doc content links, sidebar items, header main-nav, header overflow menu items, color-tweak panel unselected tabs, search result rows, footer links, doc history entries, breadcrumb trails, mobile TOC entries. - **Controls (do NOT underline)**: buttons, toggles, sidebar resizer, palette selectors, color swatches, close icons. These use border/bg hover instead. Precedents to copy the pattern from: `src/components/header.astro`, `src/components/site-tree-nav.tsx`, `src/components/footer.astro`. See also: `/css-wisdom` for light-mode / dark-mode contrast rules and the broader three-tier token strategy. ### Astro vs React - Default to **Astro components** (`.astro`) — zero JS, server-rendered - Use **React islands** (`client:load`) only when client-side interactivity is needed - Both follow the same utility-class approach --- # zudo-doc-navigation-design > Source: /pj/zudo-doc/docs/claude-skills/zudo-doc-navigation-design # zudo-doc Navigation Design Rules **IMPORTANT**: Consult these rules before creating new doc pages, adding categories, or designing the site's navigation. AI tools tend to pattern-match to personal preference and produce chaotic structures; this skill encodes the correct approach. ## Reference Documentation The full guide lives at `src/content/docs/getting-started/structuring-navigations.mdx`. Read it for examples and the gaming-wiki walkthrough. This skill is the short version. ## The 3-Level Hierarchy zudo-doc has exactly three levels. Do not invent more: 1. **Header nav** — the biggest categories (3–6 items max) 2. **Sidebar** — all pages in the active category (generated from the filesystem) 3. **Nested sidebar categories** — subsections within a sidebar (2–3 levels of nesting max) Each level narrows scope. Never jump levels — do not put a specific page directly in the header, and do not hide a whole category in a nested sidebar fold. ## File Structure IS the Navigation **This is the single most important rule.** The directory tree under `src/content/docs/` *is* the navigation. There is no separate config to keep in sync. - A directory becomes a sidebar category. Its `index.mdx` is the category landing page. - A file becomes a sidebar item. - Subdirectories become nested collapsible categories. - The `headerNav` setting maps top-level directories to header items via `categoryMatch`. **Consequence**: design the filesystem with navigation in mind from the start. Do not reorganize the sidebar via config hacks — reorganize the files. ## Header Navigation Rules The header is the topmost level. It holds only the biggest categories. - **3–6 items max.** More than 6 overwhelms users and wraps the header. - **Each item is a broad section, not a page.** "Guides" ✓, "How to install" ✗. - **`categoryMatch` must be a single top-level directory name.** Multi-segment values (e.g. `"platforms/xbox"`) break active-state highlighting. If you need nested grouping, use a header-level dropdown with children — not a path. - **Dropdowns are for closely related sections only.** "Learn > Guides, Components" makes sense. "Everything > 7 miscellaneous items" does not — split into separate header items instead. ## Sidebar Structure Rules The sidebar is generated from the filesystem. The rules are about how you organize files: - **Every category directory MUST have an `index.mdx`.** Without it, the category has no landing page and may not appear correctly. The index's `sidebar_position` sets the category's position. - **Every page MUST set `sidebar_position`.** Without it, pages sort alphabetically — which is almost never what you want. This is the single most common AI mistake. - **Nesting depth: 2–3 levels max.** Deep nesting hides content. If you need more, the header nav is probably missing a category — split it out. - **Each sidebar section should cover one cohesive topic.** Do not mix unrelated themes in the same sidebar (e.g. "Hardware" and "Community Events" under the same section). - **Use kebab-case directory and file names.** `my-article.mdx`, not `myArticle.mdx`. ## Checklist for Adding a New Page Before creating any new doc page, check this list: - [ ] Which header category does it belong under? (If none fits, either add a header category or rethink whether this page is really needed.) - [ ] Which sidebar section inside that header category? - [ ] Does the target directory exist? If yes, does it have an `index.mdx`? - [ ] Have you set `sidebar_position` in the frontmatter? - [ ] Is the file name in kebab-case? - [ ] For bilingual projects: have you created the matching file under `src/content/docs-ja/` (or added `generated: true` if it is skipped)? ## Checklist for Adding a New Category Before creating any new category directory: - [ ] Is this really a new category, or does it belong under an existing one? Default to "fewer categories." - [ ] Does it need a header nav entry, or is it a nested sidebar category inside an existing header entry? - [ ] Have you created `index.mdx` with a descriptive landing page and `sidebar_position`? - [ ] If you added a header entry, does its `categoryMatch` value equal the new top-level directory name (single segment)? - [ ] Does the new category have at least 3 pages? Fewer than 3 usually means the pages belong under a broader category instead. ## Common Mistakes (Do Not Do) - **Putting specific pages in the header nav.** The header is for categories, not pages. "Getting Started" ✓, "Installation tutorial" ✗. - **Deep nesting (4+ levels).** If you find yourself nesting deeply, the header is probably missing a category. - **Mixing unrelated topics in one sidebar section.** Split them. - **Missing `sidebar_position` on pages.** Leads to unpredictable alphabetical order. - **Missing `index.mdx` in category directories.** Category has no landing page. - **Multi-segment `categoryMatch`** (e.g. `"platforms/xbox"`). Breaks active highlighting — use a single top-level directory name. - **Reorganizing via config instead of filesystem.** If you are tempted to add a custom sidebar config override, the underlying filesystem is probably wrong. Fix the files instead. ## When in Doubt If you are not sure whether a page belongs in header, sidebar, or nested sidebar: **ask for the current navigation tree first**, draw it out, and place the new page where it fits the hierarchy. Never add pages to "whatever directory is closest" without considering the whole structure. --- # zudo-doc-translate > Source: /pj/zudo-doc/docs/claude-skills/zudo-doc-translate # zudo-doc Translation Skill Translate documentation between English and Japanese following project-specific conventions. ## i18n Structure - English docs: `src/content/docs/` — routes at `/docs/...` - Japanese docs: `src/content/docs-ja/` — routes at `/ja/docs/...` - Directory structures must mirror each other exactly (same filenames, same folder hierarchy) - Locale settings: `locales` in `src/config/settings.ts` - Astro i18n config: `astro.config.ts` with `prefixDefaultLocale: false` (English has no prefix, Japanese uses `/ja/`) ## Translation Rules ### Keep in English (do NOT translate) - Component names: ``, ``, ``, ``, ``, ``, ``, `` - Code blocks — code is universal - File paths: `src/content/docs/...`, `.claude/skills/...`, etc. - CLI commands: `pnpm dev`, `pnpm build`, etc. - Technical terms that are standard in English (e.g., component, props, frontmatter, slug) - Frontmatter field keys (`title`, `description`, `sidebar_position`, `category`) ### Translate - Frontmatter field values (e.g., the `title` value, the `description` value) - The `title` prop of admonition components (e.g., ``) - Prose content, headings, list items, table cells (except as noted below) ### Table conventions - In tables with a "Required" column: use **"Yes"** / **"No"** directly, NOT "はい" / "いいえ" — Japanese conversational yes/no is unnatural in technical documentation ### Internal links - Adjust link paths when translating: - En→Ja: `/docs/getting-started` → `/ja/docs/getting-started` - Ja→En: `/ja/docs/getting-started` → `/docs/getting-started` ## File Naming - Japanese files use the **same filenames** as English (e.g., `writing-docs.mdx`) - Only the parent directory differs: `docs/` vs `docs-ja/` - Example: `src/content/docs/guides/writing-docs.mdx` → `src/content/docs-ja/guides/writing-docs.mdx` ## Workflow ### En→Ja Translation 1. Read the English source file from `src/content/docs/` 2. Check if the corresponding Japanese file already exists in `src/content/docs-ja/` - If it exists, read it first — use it as a base and update from the English source rather than overwriting from scratch - If it does not exist, create the file at the equivalent path in `src/content/docs-ja/` 3. Translate the content following the rules above 4. Verify internal links point to `/ja/docs/...` ### Ja→En Translation 1. Read the Japanese source file from `src/content/docs-ja/` 2. Check if the corresponding English file already exists in `src/content/docs/` - If it exists, read it first — use it as a base and update from the Japanese source rather than overwriting from scratch - If it does not exist, create the file at the equivalent path in `src/content/docs/` 3. Translate the content following the rules above 4. Verify internal links point to `/docs/...` (no `/ja/` prefix) ### Post-Translation Checks - Frontmatter keys are unchanged (only values translated) - All admonition component names remain in English - Code blocks are untouched - Internal links use the correct locale prefix - Directory structure mirrors the source language --- # zudo-doc-version-bump > Source: /pj/zudo-doc/docs/claude-skills/zudo-doc-version-bump # /zudo-doc-version-bump Bump the version, generate changelog doc pages, commit, tag, and create a GitHub release. ## Preconditions Before doing anything else, verify ALL of the following. If any check fails, stop and tell the user. 1. Current branch is `main` 2. Working tree is clean (`git status --porcelain` returns empty) 3. At least one `v*` tag exists (`git tag -l 'v*'`). If no tag exists, tell the user to create the initial tag first (e.g. `git tag v0.1.0 && git push --tags`). Find the latest version tag: ```bash git tag -l 'v*' --sort=-v:refname | head -1 ``` ## Analyze changes since last tag Run: ```bash git log ..HEAD --oneline ``` and ```bash git diff ..HEAD --stat ``` Categorize each commit by its conventional-commit prefix: - **Breaking Changes**: commits with an exclamation mark suffix (e.g. `feat!:`) or BREAKING CHANGE in body - **Features**: `feat:` prefix - **Bug Fixes**: `fix:` prefix - **Other Changes**: everything else (`docs:`, `chore:`, `refactor:`, `ci:`, `test:`, `style:`, `perf:`, etc.) ## Propose version bump Based on the changes: - If there are breaking changes → propose **major** bump - If there are features (no breaking) → propose **minor** bump - Otherwise → propose **patch** bump If the user passed an argument (`major`, `minor`, or `patch`), use that directly instead of proposing. Present the proposal to the user: ``` Proposed bump: {current} → {new} ({type}) Breaking Changes: - description (hash) Features: - description (hash) Bug Fixes: - description (hash) Other Changes: - description (hash) ``` Only show sections that have entries. **Wait for user confirmation before proceeding.** If this is a **major** version bump, ask the user whether they want to archive the current docs as a versioned snapshot (i.e. run with `--snapshot`). Explain that this copies the current docs to a versioned directory for the old version. ## Run version-bump.sh Run the existing version bump script to update package.json and create changelog entry files: ```bash ./scripts/version-bump.sh {NEW_VERSION} # Or with snapshot for major bumps: ./scripts/version-bump.sh {NEW_VERSION} --snapshot ``` This script: 1. Updates `version` in `package.json` 2. Creates `src/content/docs/changelog/{NEW_VERSION}.mdx` (EN) 3. Creates `src/content/docs-ja/changelog/{NEW_VERSION}.mdx` (JA) 4. With `--snapshot`: copies current docs to versioned directories and prints settings.ts entry to add ## Fill in changelog content After the script creates the template files, **replace the placeholder content** with the actual categorized changes from the commit analysis. ### English changelog (`src/content/docs/changelog/{NEW_VERSION}.mdx`) ```mdx --- title: {NEW_VERSION} description: Release notes for {NEW_VERSION}. sidebar_position: {value from script} --- Released: {YYYY-MM-DD} ### Breaking Changes - Description (commit-hash) ### Features - Description (commit-hash) ### Bug Fixes - Description (commit-hash) ### Other Changes - Description (commit-hash) ``` ### Japanese changelog (`src/content/docs-ja/changelog/{NEW_VERSION}.mdx`) ```mdx --- title: {NEW_VERSION} description: {NEW_VERSION}のリリースノート。 sidebar_position: {value from script} --- リリース日: {YYYY-MM-DD} ### 破壊的変更 - Description (commit-hash) ### 機能 - Description (commit-hash) ### バグ修正 - Description (commit-hash) ### その他の変更 - Description (commit-hash) ``` Rules: - Only include sections that have entries - Use today's date for the release date - Each entry should be the commit subject with the short hash in parentheses ## Build and test Run the full build and test suite to make sure everything is good: ```bash pnpm b4push ``` If anything fails, fix the issue and re-run. Do not proceed with committing until all checks pass. ## Commit changes Stage and commit **all** version bump changes — include any files modified by b4push formatting fixes: ```bash git add package.json src/content/docs/changelog/{NEW_VERSION}.mdx src/content/docs-ja/changelog/{NEW_VERSION}.mdx # Also stage any other modified files (e.g. formatting fixes from b4push) git diff --name-only | xargs git add git commit -m "chore: Bump version to v{NEW_VERSION}" ``` ## Push and wait for CI Push the commits first (without the tag) and wait for CI to pass: ```bash git push ``` Then check CI status. Use `gh run list --branch main --limit 1 --json status,conclusion,headSha` and verify the `headSha` matches the pushed commit. Poll every 30 seconds, with a **maximum of 10 minutes**. If CI is still running after 10 minutes, ask the user whether to keep waiting or proceed. If CI fails, investigate the failure with `gh run view --log-failed`, fix the issue, commit, and push again. **Do not tag or publish until CI is green.** ## Tag, push tag, and create GitHub release **Ask the user for confirmation before tagging.** ```bash git tag v{NEW_VERSION} git push --tags ``` After pushing the tag, create a GitHub release. Use `awk` to strip only the YAML frontmatter (first `---` to second `---`) from the changelog file: ```bash NOTES=$(awk 'BEGIN{f=0} /^---$/{f++; next} f>=2' src/content/docs/changelog/{NEW_VERSION}.mdx) gh release create v{NEW_VERSION} --title "v{NEW_VERSION}" --notes "$NOTES" ``` ## Publish to npm (if applicable) If the package is **not** marked as `"private": true` in `package.json`, tell the user to publish: ``` The package is ready for npm publishing. Run: pnpm publish (This requires browser-based 2FA and must be done manually.) ``` If the package is `"private": true`, skip this step and inform the user: ``` Package is marked as private — skipping npm publish. ``` ## Done Report the summary: - Version bumped: `{OLD_VERSION}` → `{NEW_VERSION}` - Changelog created (EN + JA) - Git tag: `v{NEW_VERSION}` - GitHub release: link to the release - npm publish status (published / skipped for private package) --- # zudo-doc-writing-rules > Source: /pj/zudo-doc/docs/claude-skills/zudo-doc-writing-rules # zudo-doc Doc-Writing Rules **IMPORTANT**: Consult these rules before writing or editing any mdx content under `src/content/docs/` or `src/content/docs-ja/`. These rules exist to prevent common AI mistakes that silently break layout, navigation, or i18n. ## Reference For navigation structure rules (which category, filesystem layout), see the companion skill `zudo-doc-navigation-design`. This skill covers content-level mistakes, not structural ones. ## Hard Rules (never violate) ### No h1 in content - **Frontmatter `title` is the page h1.** It is rendered automatically by the doc layout. - **Content MUST start with `## h2`**, not `# h1`. - Writing `# Something` inside the body produces a duplicate h1 and breaks heading hierarchy / TOC / a11y. ```mdx --- title: Installation sidebar_position: 1 --- ## Prerequisites ← start here, NOT with a "# Installation" heading ... ``` ### Always set `sidebar_position` - Without it, pages sort alphabetically within their category — almost never what you want. - Set it in the frontmatter of **every page**, including category `index.mdx` files (which control the category's position in the parent sidebar). - Small integers are fine; leaving gaps (1, 2, 3, 10) makes later insertion easier. ### Kebab-case file names - `my-article.mdx` ✓ - `myArticle.mdx` ✗ — breaks on case-sensitive filesystems - `my_article.mdx` ✗ — inconsistent with the rest of the site The URL slug is derived from the filename, so kebab-case also keeps URLs clean. ### Bilingual rule - When creating or updating any doc page, **update both EN (`src/content/docs/`) and JA (`src/content/docs-ja/`) versions.** - Keep **code blocks identical** — only translate prose. - Keep `` and other JSX blocks identical. - The JA directory should mirror the EN directory tree exactly. - **Exception**: pages with `generated: true` in frontmatter are skipped (they are generated at build time). ## Frontmatter Schema The schema is defined in `src/content.config.ts` (Zod validation). Required and key optional fields: | Field | Type | Required? | Notes | |---|---|---|---| | `title` | string | yes | Renders as page h1 | | `sidebar_position` | number | recommended | Without it, pages sort alphabetically | | `description` | string | no | Subtitle below the h1 | | `sidebar_label` | string | no | Overrides `title` in the sidebar only | | `tags` | string[] | no | Cross-category grouping | | `draft` | boolean | no | Excludes the page from the build entirely | | `unlisted` | boolean | no | Built but hidden from sidebar/nav | | `generated` | boolean | no | Build-time generated; skip JA translation | | `hide_sidebar` | boolean | no | Hide the left sidebar on this page | | `hide_toc` | boolean | no | Hide the right-side table of contents | Unknown fields are rejected by the Zod schema — do not invent new ones without updating the schema. ## Linking Between Docs Use **relative paths with the `.mdx` extension**. The framework's link resolver rewrites them to the correct URLs at build time and validates them. ```markdown [Link text](./sibling-page.mdx) [Link text](../other-category/page.mdx#anchor) ``` - Do NOT use absolute paths like `/docs/foo/bar` in prose — the resolver cannot verify them. - Do NOT omit the `.mdx` extension — the resolver needs it to find the target. For the companion skill that audits links: `/check-docs`. ## Admonitions Admonitions are available globally — no imports needed. **Directive syntax** (preferred in prose-heavy content): ```mdx :::note[Optional Title] Body content. ::: ``` **JSX syntax** (preferred when nesting other JSX): ```mdx Body content. ... ... ... ... ``` All five components accept an optional `title` prop. ## Common Mistakes (Do Not Do) - **Starting content with `# Foo`** — duplicates the h1. Start with `## Foo`. - **Forgetting `sidebar_position`** — produces alphabetical chaos in the sidebar. - **camelCase or PascalCase file names** — breaks on some filesystems. - **Updating only the EN or only the JA version** — breaks the bilingual mirror. - **Absolute `/docs/...` links in prose** — the resolver cannot verify them; use relative `./page.mdx` instead. - **Omitting the `.mdx` extension in links** — the resolver requires it. - **Inventing frontmatter fields** — the Zod schema will reject them at build time. - **Translating code inside code blocks** — the bilingual rule says code stays identical. ## Recommended Workflow 1. Decide *where* the page belongs (see `zudo-doc-navigation-design` for the category decision). 2. Create the EN file under `src/content/docs//.mdx` with `title` and `sidebar_position`. 3. Write content starting with `## h2` headings only. 4. Use `./` relative links with `.mdx` extension for cross-refs. 5. Create the matching JA file under `src/content/docs-ja//.mdx` — same code blocks, translated prose. 6. Run `pnpm format:md` then `pnpm build` to verify — the build will catch missing required fields, broken links, and unknown frontmatter.