# 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

...
```

### Do not use horizontal rules as section separators

- Headings and whitespace already express document structure; do not add body-level `---` merely to divide sections.
- Use a horizontal rule only when the rule itself has semantic meaning, such as documentation that demonstrates horizontal-rule syntax.

### 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 `<HtmlPreview>` and other JSX blocks identical.
- The JA directory should mirror the EN directory tree exactly.
- **Exception**: files with `generated: true` in frontmatter are generator-owned; do not hand-edit or translate them.
- **Exception**: the Claude/Codex resource generators own their overview and category `index.mdx` targets in the default content directory and every configured locale directory. They generate localized indexes from `resource.*` translations, so do not create hand-written locale stubs. If an authored file already occupies a target path, the generator refuses to overwrite it; remove or rename the file and express its title, description, and labels through `ZudoDocConfig.translations`.
- **Exception**: Claude/Codex resource detail files stay in the default content directory and use body fallback at other locale routes. Do not copy those generated detail files into locale directories.
- **Exception**: pages whose paths are explicitly listed under `settings.defaultLocaleOnlyPrefixes` are default-locale-only by design — do not create a JA mirror for them. The source of truth is `settings.defaultLocaleOnlyPrefixes` in `src/config/settings.ts`; this repository currently configures no such prefixes.

## Frontmatter Schema

The schema is package-owned (`@takazudo/zudo-doc/docs-schema`, source `packages/zudo-doc/src/docs-schema/index.ts` — the default `buildDocsSchema`, wired in via `zudoDoc()`; there is no more host `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 |
| `category_shape` | `"note-tray"` | no | Declares a top-level, flat note-tray category on its `index.mdx` |
| `note_tray_dated` | boolean | no | Requires `date` on every tray item, including unlisted items |
| `note_tray_sidebar` | `"index" \| "year" \| "month"` | no | Tray sidebar style; grouped styles require a dated tray |
| `category_sort_order` | `"asc" \| "desc"` | no | Category or tray display direction; defaults to `"asc"` |
| `date` | `"YYYY-MM-DD"` | no | Required on every item in a dated tray; quoting is optional because zfb keeps scalar values as strings |
| `updated` | `"YYYY-MM-DD"` | no | Optional update date on any doc; quoting is optional because zfb keeps scalar values as strings |
| `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 |

The schema ends with `.passthrough()`, so custom keys (e.g. `author`, `status`) are preserved rather than rejected — they simply carry no built-in behavior. Consequence: a typo in a framework field name (`sidebar_postion`) does NOT error at build time; double-check spellings against the table above.

## 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
<Note title="Optional Title">Body content.</Note>
<Tip>...</Tip>
<Info>...</Info>
<Warning>...</Warning>
<Danger>...</Danger>
<Caution>...</Caution>
```

All admonition components accept an optional `title` prop. (`caution` is danger-adjacent, used by GitHub-style `[!CAUTION]` alerts; JSX additionally registers `<Important>` for `[!IMPORTANT]`.)

## Common Mistakes (Do Not Do)

- **Starting content with `# Foo`** — duplicates the h1. Start with `## Foo`.
- **Adding `---` between headed sections** — headings and whitespace already provide the separation.
- **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 (except for generator-owned content and pages explicitly covered by `defaultLocaleOnlyPrefixes`).
- **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.
- **Misspelling framework frontmatter fields** — the schema passes unknown keys through, so a typo'd `sidebar_position` silently does nothing instead of erroring.
- **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/<category>/<page>.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/<category>/<page>.mdx` — same code blocks, translated prose.
6. Run `pnpm format` then `pnpm build` to verify — the build will catch missing required fields, broken links, and unknown frontmatter.