# PR #2489 完整报告

- 仓库：`radixark/miles`
- 标题：docs: label-only sidebar groups, explicit Overview pages, scoped link color
- 合并时间：2026-08-14 05:20
- 原文链接：http://prhub.com.cn/radixark/miles/pull/2489

---

# 执行摘要

- 一句话：文档导航统一为纯标签分组、Overview 落地页、链接色收窄
- 推荐动作：值得快速浏览：本 PR 是文档工程化的不错样板 —— 把导航语义写成『生成器 + 钩子 + 约定文档 + 单测』四件套，并用页面数与 broken links 守恒作为自证回归证据。不需要精读源码；值得关注的决策点包括：用 `sidebarTitle` 分离 h1/SEO 标题与侧边栏短标签、以纯标签分组消灭『可点击与不可点击标题』的不一致、以及 pygrep 钩子一行代码锁死配置契约。后续维护者需留意 SECTION_DIRS 硬编码与生成器 / 手写双轨配置的扩展点。

# 功能与动机

PR body 给出的核心动机是让侧边栏行为遵循一条可读的规则："Makes the docs sidebar behave by one legible rule: section headers are labels, rows are pages, and the only collapsible items are the Models family catalogs." 具体要解决三个问题：未加作用域的 a 链接颜色规则压过 Mintlify 的 layered Tailwind 颜色，把侧边栏 / 标签栏 / 页内 TOC 全刷成橙色并掩盖当前页高亮；带 root 的分组标题会跳转、无 root 的只折叠，点击前无法区分；导航结构层级不统一（单分组 tab 也套了一层 groups）。无关联 Issue，设计说明全部由 PR body 承担。

# 实现拆解

1. **链接颜色作用域收窄（docs/style.css）**：三条规则 `a`、`a:hover`、`a[href^="http"]::after` 分别改为 `.mdx-content a`、`.mdx-content a:hover`、`.mdx-content a[href^="http"]`。原因是未作用域的 `a` 规则属于 unlayered 层，会压过 Mintlify 的 layered Tailwind 颜色，把侧边栏、tabs、页内 TOC 全部刷成品牌橙 `#d55816`；收窄后自定义颜色与站外链接 ↗ 标记只作用于正文容器。
2. **导航配置重构（docs/docs.json，+97/-143）**：移除所有导航分组 `root` 键，模型家族各组（DeepSeek、Thinking Machines、Qwen、GLM、Kimi、Nemotron、Gemma）的 `index` 页降为各组 `pages` 首项，`gpt-oss` 等单页直接挂到 Models 根组之下；Welcome、Resources、FAQ 三个单分组 tab 从 `groups` 包装扁平化为顶层 `pages`；User Guide 的 Launch Script 两页分组被扁平化（页面标题从 "Structure and Usage" 改回 "Launch Script"）；Advanced Features 与 Developer Guide 内的 Performance、Scale & Reliability、MoE & Routing、CI 等子组全部静态展开，仅 Models 家族保留 `expanded: false` 折叠。附带变更：navbar 新增 "Contact" 主按钮（mailto:miles@radixark.ai）。
3. **同步脚本语义升级（scripts/tools/sync_example_docs.py）**：新增 `SECTION_DIRS = ("infra_features",)` 常量标记纯标签分组目录；`render_page` 通过 `landing = rel_dir == "" or rel_dir in SECTION_DIRS` 判定 landing 页，命中则注入 `sidebarTitle: "Overview"`，`title` 保留完整值供 h1/SEO；`build_navigation` 从返回带 `root`/`expanded` 的 group 字典改为返回 entries 列表（`examples/index` 恒为首项，随后是不带 `root` 的 "Recipes"、"Infra Features" 分组，且 infra landing 页作为分组第一行）；`main()` 相应改为 `tab.pop("groups", None)` 后写 `tab["pages"]`。
4. **Landing 页批量打标（各 index.md）**：`docs/index.md`（title 从 "Miles Documentation" 改为 "Welcome" 并新增 sidebarTitle）、`docs/models/{deepseek,gemma,glm,kimi,nemotron,qwen,thinkingmachines}/index.md`、`docs/advanced/index.md`、`docs/developer/index.md`、`docs/examples/index.md` 等 frontmatter 统一追加 `sidebarTitle: Overview`。
5. **回归防护三件套**：`docs/README.md` 新增两段约定（landing 页 title 面向 h1/ 浏览器 / 搜索、`sidebarTitle` 是恒定字段名；禁止分组 `root`，正确做法是 landing 页作为 `pages` 首项）；`.pre-commit-config.yaml` 新增 pygrep 钩子 `ban-docs-nav-group-root`（entry `"root":`，files 限定 `^docs/docs\.json$`）；`tests/fast/doc/test_sync_example_docs.py` 更新 `test_navigation_follows_bullet_order` 断言并新增 `test_section_directory_becomes_rootless_group_with_landing_first`，既有漂移门禁 `test_docs_examples_matches_the_readmes`（--check）保持不变，保证生成结果与提交内容一致。

关键文件：
- `scripts/tools/sync_example_docs.py`（模块 同步脚本；类别 source；类型 core-logic；符号 SECTION_DIRS, render_page, build_navigation, main）: Examples tab 导航生成器，随本次约定同步升级：build_navigation 改为输出无 root 的 pages 列表（index 首项 + 纯标签分组），render_page 对 landing 页注入 sidebarTitle: Overview，main() 从写 groups 改为写 pages。
- `docs/docs.json`（模块 导航配置；类别 config；类型 configuration）: 导航契约本体，改动量最大（+97/-143）：移除全部导航分组 root、扁平化单分组 tab、各 landing 页提升为 pages 首项，仅 Models 家族保留可折叠嵌套分组，并附带 navbar 新增 Contact 按钮。
- `tests/fast/doc/test_sync_example_docs.py`（模块 同步测试；类别 test；类型 test-coverage；符号 test_section_directory_becomes_rootless_group_with_landing_first, test_navigation_follows_bullet_order）: 更新 build_navigation 断言以适配新的 entries 结构（index 首项 + 无 root 分组），并新增 test_section_directory_becomes_rootless_group_with_landing_first 验证 section 目录 landing 页作为分组第一行出现。
- `.pre-commit-config.yaml`（模块 提交钩子；类别 config；类型 configuration）: 新增 ban-docs-nav-group-root pygrep 钩子，从提交层面禁止 docs.json 中任何导航分组 root 键回归，是约定能否长期执行的保障。
- `docs/style.css`（模块 站点样式；类别 other；类型 core-logic）: 修复全局 a 规则把侧边栏、tabs、页内 TOC 刷成橙色并掩盖活动页高亮的问题：链接颜色与站外链接 ↗ 标记限定在 .mdx-content 正文容器内。
- `docs/README.md`（模块 文档约定；类别 docs；类型 documentation）: 固化导航约定的规范源：landing 页命名与 sidebarTitle 约定、禁止 root 的说明，是同步脚本与 pre-commit 钩子背后的设计依据。
- `docs/index.md`（模块 站点首页；类别 docs；类型 documentation）: 站点首页作为 landing 页约定的示范：title 从 Miles Documentation 改为 Welcome，并新增 sidebarTitle: Overview。

关键符号：build_navigation, render_page, main, parse_index, test_section_directory_becomes_rootless_group_with_landing_first, test_navigation_follows_bullet_order

## 关键源码片段

### `scripts/tools/sync_example_docs.py`

Examples tab 导航生成器，随本次约定同步升级：build_navigation 改为输出无 root 的 pages 列表（index 首项 + 纯标签分组），render_page 对 landing 页注入 sidebarTitle: Overview，main() 从写 groups 改为写 pages。

```python
# 需要渲染为纯标签侧边栏分组的目录：目录自己的 README 作为分组第一行
# （侧边栏显示 Overview），而不是作为 group `root` —— root 会使分组标题
# 变成可点击链接，与纯折叠分组外观无法区分（已被 pre-commit 禁止）。
SECTION_DIRS = ("infra_features",)


def render_page(title, description, rel_dir, body):
    source = f"{repo_dir_of(rel_dir)}/README.md"
    # 只有 tab 首页（rel_dir == ""）与 section 目录首页写入 sidebarTitle：
    # 页面保留完整 title（供 h1 / SEO），侧边栏统一显示短标签 Overview。
    landing = rel_dir == "" or rel_dir in SECTION_DIRS
    sidebar = 'sidebarTitle: "Overview"\n' if landing else ""
    return (
        "---\n"
        f"title: {json.dumps(title, ensure_ascii=False)}\n"
        f"{sidebar}"
        f"description: {json.dumps(description, ensure_ascii=False)}\n"
        f"# Generated from {source} by scripts/tools/sync_example_docs.py. Edit that README, not this file.\n"
        "---\n"
        f"{body}\n"
    )


def build_navigation(pages, bullet_order):
    """生成 Examples tab 的 pages 列表：index 最前，随后是纯标签分组。

    侧边栏顺序遵循 examples/README.md 的 bullet 顺序（README 拥有排序、
    标题与描述）；没有 bullet 的目录排最后、按字母序。返回的 entries
    不含任何 group `root` —— Examples tab 在 main() 里直接写入
    tab["pages"]，而不再包一层 groups。
    """
    rank = {rel_dir: i for i, rel_dir in enumerate(bullet_order)}
    recipes, infra = [], []
    for rel_dir in sorted(pages, key=lambda d: (rank.get(d, len(rank)), d)):
        if not rel_dir:
            continue
        page = f"examples/{slug_for(rel_dir)}"
        # infra_features 目录归入 Infra Features 分组，其余全部归入 Recipes
        (infra if rel_dir.startswith("infra_features") else recipes).append(page)
    entries = ["examples/index"]
    entries.append({"group": "Recipes", "pages": recipes})
    if infra:
        infra_root = "examples/infra-features"
        children = [p for p in infra if p != infra_root]
        # landing 页作为分组第一行出现，分组自身不设 root、不设 expanded
        entries.append({"group": "Infra Features", "pages": [infra_root] + children})
    return entries

```

### `docs/style.css`

修复全局 a 规则把侧边栏、tabs、页内 TOC 刷成橙色并掩盖活动页高亮的问题：链接颜色与站外链接 ↗ 标记限定在 .mdx-content 正文容器内。

```css
/*
 * 链接颜色只作用于正文容器 .mdx-content：未加作用域的 a 规则属于
 * unlayered 层，会压过 Mintlify 的 layered Tailwind 颜色，把侧边栏、
 * tab 栏、页内 TOC 全部刷成橙色，并掩盖当前页高亮。收窄后自定义
 * 颜色只出现在正文内容区。
 */
.mdx-content a {
  color: #d55816;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.mdx-content a:hover {
  text-decoration-thickness: 2px;
}

/* 站外链接自动追加 ↗ 符号，同样限定在正文容器内 */
.mdx-content a[href^="http"]:not([href*="radixark.com"])::after {
  content: "↗";
  margin-left: 0.15em;
  font-size: 0.7em;
}

```

# 评论区精华

该 PR 全程零讨论：无 issue 评论、无 PR 评论、无行内 review 评论，reviewer Zhichenzzz 直接给予空 body 的 APPROVED。设计决策由作者在 PR body 中完整自述，并以四重机制（同步脚本 + pre-commit 钩子 + docs/README.md 约定 + 单元测试）固化，弥补了缺乏评审交锋的审查盲区。回归证据同样由 PR body 自我声明：页面总数 96 不变，`mint broken-links` 与 main 持平（25 个存量）。

- 暂无高价值评论线程

# 风险与影响

- 风险：
 - **pre-commit 规则一刀切（.pre-commit-config.yaml）**：`ban-docs-nav-group-root` 以 pygrep 精确匹配 `"root":` 键，当前不会误伤其他键；但若未来 Mintlify 导航 schema 或文档站自身需要合法 `root` 用途，该钩子会直接拦截，需要调整匹配范围。
 - **生成器与手写配置双轨（scripts/tools/sync_example_docs.py vs docs/docs.json）**：Examples tab 完全由生成器接管，`main()` 现在显式 `tab.pop("groups", None)` 并写入 `pages`，手改 Examples 段会被 `--check` 漂移门禁拦截；而 User Guide/Advanced 等 tab 仍为手写，两套来源并存，语义必须保持一致。
 - **SECTION_DIRS 硬编码**：`("infra_features",)` 写死，未来新增 section 类目录（如把某组升级为纯标签分组）必须同步改代码，否则该目录会被静默归入 Recipes 分组，产生与期望不符的导航。
 - **CSS 依赖 Mintlify DOM（docs/style.css）**：`.mdx-content` 是 Mintlify 生成的容器类名，若平台改版导致类名变化，正文链接颜色与 ↗ 标记会静默失效（不破坏布局但丢失定制样式）。
 - **导航语义变化对读者的影响**：所有分组标题不再可点击、落地页变成显式 "Overview" 行，习惯点击分组标题跳转的读者需要适应；URL 全部不变，无书签 / 外链破坏。
 - 影响：影响范围限定在 docs 站点与文档维护工具链，不涉及训练、rollout、session 等任何运行时模块。对读者：侧边栏语义统一（标签不跳转、行才是页面）、活动页高亮恢复、站外链接 ↗ 与橙色仅出现在正文，导航可预期性明显提升。对团队：docs 贡献者必须遵守新约定（禁止 root、landing 页 `sidebarTitle: Overview`、Examples tab 由生成器独占），否则提交被 pre-commit 拦截或漂移测试失败；导航结构的增删改成本被工具化。页面数与 broken links 均无变化，说明这是一次无页面增删的纯结构重构。
 - 风险标记：零 review 讨论 , pre-commit 规则一刀切 , SECTION_DIRS 硬编码 , 生成器与手写配置双轨

# 关联脉络

- PR #2491 docs: nest Environments under User Guide, rename to Agentic Environments: 与本 PR 直接衔接：前者把环境文档迁入 User Guide 并更名 Agentic Environments，本 PR 将该组变为 User Guide 下的静态纯标签分组并保留其 landing 页语义。
- PR #2477 fix(docs): correct agentic rollout guidance: 同样大范围修改 docs/docs.json 与 user-guide 页面，本 PR 延续了同一导航文件的结构演进。
- PR #2484 docs: add disaggregated RL rollout guide: 新增 advanced 页面并同步 docs.json/advanced 索引，本 PR 将 advanced 下的子组扁平化，两者同处一个导航体系。
- PR #2383 [readme]: rewrite the README: 仓库近期文档体系整体整理（README 重写、examples 迁移、CI 文档化）脉络中的一环，本 PR 把导航语义变成可执行的工具化约定。