diff --git a/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.i18n.yaml b/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.i18n.yaml
index d0e78c1c71..c5b67aa400 100644
--- a/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.md
-2026-08-12-documentation-site-navigation-and-chrome.md: 1b1868a011744decf1c0a25a825fd022ea3609ab
-2026-08-12-documentation-site-navigation-and-chrome.zh.md: 0fd685ec8c6b7f3f2fd93013ebaf9266a70b2c81
+2026-08-12-documentation-site-navigation-and-chrome.md: 07f88d303a96676806cce0801bce5d478fb5406e
+2026-08-12-documentation-site-navigation-and-chrome.zh.md: ca605e2a890bac4bcff9f22a6c6ab8928d1a8aa8
diff --git a/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.md b/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.md
index 1b1868a011..07f88d303a 100644
--- a/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.md
+++ b/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.md
@@ -8,6 +8,8 @@ English | [中文](2026-08-12-documentation-site-navigation-and-chrome.zh.md)
The reference sidebar rendered its 43 subsystem pages first, ahead of every other group: `sectionOrder` in the VitePress config listed no position for `子系统`/`Subsystems` — nor for `其他接口`/`Other interfaces` — so `indexOf` returned `-1` and sorted them ahead of the ordered sections. Clicking the `参考` navigation item landed on the architecture page whose own sidebar entry was link 44 of 62, 1549px down a 2478px sidebar — outside the viewport. Four subsystem pages carried `order` values already taken by other pages in the same section, resolved only by `Array.prototype.sort` stability and the order the manifest's arrays happened to be concatenated.
+The navigation bar named `/guide/` while the manifest published the guide's first page at `guide/quickstart.md`, so that item served a 404: written-down navigation targets drift from the routes the manifest publishes.
+
Separately, every canonical page carries lines written for its GitHub reader — a language switcher under the heading, and for some, a repository badge — which the site projected verbatim even though its navigation bar already offers both.
## Decision
@@ -16,6 +18,8 @@ Separately, every canonical page carries lines written for its GitHub reader —
Subsystem pages are grouped by concern — overview, core and scopes, sessions and persistence, model and context, execution and tools, policy and interaction, platform and access — and the six topical groups render collapsed until one holds the page being read. The groups sort last within the reference sidebar: expanded, they outnumber every other group combined, so anything placed after them is reachable only by scrolling past the whole list. Page `order` derives from array position rather than a hand-written number.
+`landingLink(locale, collection)` derives each navigation item's target from `orderedPages`, the same ordering the sidebar renders, so an item always opens its collection's first published page.
+
`projectedPageContent` in [scripts/project-doc-site.ts](../../../../scripts/project-doc-site.ts) drops the language-switcher line and the repository badge. The switcher match is confined to the first eight lines so a tutorial that shows the convention still renders its example.
The navigation-bar title is the DeepSeek wordmark inlined into `siteTitle`, which VitePress renders as HTML. Inlining is what lets the mark's `currentColor` fills follow the active theme; `themeConfig.logo` renders an `
`, which freezes the mark at the colors its file declares and would need one asset per theme. The sidebar scrollbar rests invisible and appears while scrolling, marked by a `data-` attribute rather than a class because Vue rewrites `class` wholesale when it patches the element.
diff --git a/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.zh.md b/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.zh.md
index 0fd685ec8c..ca605e2a89 100644
--- a/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.zh.md
+++ b/.agents/notes/implemented/process/2026-08-12-documentation-site-navigation-and-chrome.zh.md
@@ -8,6 +8,8 @@ Status: implemented
参考侧边栏把 43 个子系统页排在了所有其他分组之前:VitePress 配置中的 `sectionOrder` 既没有为 `子系统`/`Subsystems` 也没有为 `其他接口`/`Other interfaces` 声明位置,`indexOf` 返回 `-1`,于是它们排到了所有已排序分区的前面。点击 `参考` 导航项落在架构页,而该页自己的侧边栏条目是 62 条中的第 44 条,位于 2478px 侧边栏的 1549px 处——在视口之外。四个子系统页所用的 `order` 值已被同一分区内的其他页占用,只靠 `Array.prototype.sort` 的稳定性和 manifest 数组恰好的拼接顺序才没有错乱。
+顶栏把 `入门` 指向 `/guide/`,而 manifest 已把入门首页发布在 `guide/quickstart.md`,该导航项因此返回 404:写死的导航目标会与 manifest 实际发布的路由脱节。
+
另外,每个规范页面都带有写给 GitHub 读者的行——标题下的语言切换行,部分页面还有仓库徽章——站点原样投影了它们,尽管其导航栏已经提供了这两者。
## 决定
@@ -16,6 +18,8 @@ Status: implemented
子系统页按关注点分组——总览、内核与作用域、会话与持久化、模型与上下文、执行与工具、策略与交互、平台与接入——其中六个主题组保持折叠,直到某一组包含正在阅读的页面。这些分组排在参考侧边栏的最后:展开时它们的数量超过其余所有分组之和,因此排在它们之后的任何内容都只能靠滚过整个列表才能到达。页面 `order` 由数组位置推导,不再手写数字。
+`landingLink(locale, collection)` 依据 `orderedPages`——即侧边栏所用的同一套排序——推导每个导航项的目标,因此导航项始终打开该分区已发布的首个页面。
+
[scripts/project-doc-site.ts](../../../../scripts/project-doc-site.ts) 中的 `projectedPageContent` 会丢弃语言切换行和仓库徽章。切换行的匹配被限制在前八行内,因此展示该约定的教程仍能渲染出它的示例。
导航栏标题是内联进 `siteTitle` 的 DeepSeek 字标,VitePress 会将其按 HTML 渲染。内联正是让字标的 `currentColor` 填充跟随当前主题的原因;`themeConfig.logo` 渲染为 `
`,会把字标固定为文件声明的颜色,并且需要为每套主题各准备一份资源。侧边栏滚动条平时不可见,滚动时出现,通过 `data-` 属性而非 class 标记,因为 Vue 在 patch 该元素时会整体重写 `class`。
diff --git a/scripts/project-doc-site.spec.ts b/scripts/project-doc-site.spec.ts
index 439664ddd0..e52017cbd8 100644
--- a/scripts/project-doc-site.spec.ts
+++ b/scripts/project-doc-site.spec.ts
@@ -5,7 +5,7 @@ import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, realpathSyn
import { tmpdir } from 'node:os'
import { basename, join, resolve } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
-import { docsPages, sectionSpec, type DocsPage } from '../website/docs.ts'
+import { docsPages, landingLink, routeLink, sectionSpec, type DocsPage } from '../website/docs.ts'
import {
addProjectionFrontmatter, projectedPageContent, publishableImage, rewriteMarkdown,
} from './project-doc-site.ts'
@@ -386,6 +386,19 @@ describe('sidebar ordering', () => {
expect(() => sectionSpec('root', 'Guide')).toThrow()
})
+ it('lands every navigation item on a page the manifest publishes', () => {
+ // The navigation bar named `/guide/` while the manifest published the guide's
+ // first page at `guide/quickstart.md`, so the item served a 404.
+ const collections = [
+ ['root', 'zh-guide'], ['root', 'zh-develop'], ['root', 'zh-reference'],
+ ['en', 'en-guide'], ['en', 'en-develop'], ['en', 'en-reference'],
+ ] as const
+ const published = new Set(docsPages.map(page => routeLink(page.route)))
+ for (const [locale, collection] of collections) {
+ expect(published, `${locale}/${collection}`).toContain(landingLink(locale, collection))
+ }
+ })
+
it('collapses the subsystem groups and leaves the smaller ones open', () => {
expect(sectionSpec('root', '执行与工具').collapsed).toBe(true)
expect(sectionSpec('en', 'Execution and tools').collapsed).toBe(true)
diff --git a/website/.vitepress/config.ts b/website/.vitepress/config.ts
index f611804927..e451ecb68c 100644
--- a/website/.vitepress/config.ts
+++ b/website/.vitepress/config.ts
@@ -5,33 +5,30 @@ import { resolve } from 'node:path'
import type { DefaultTheme, PageData } from 'vitepress'
import type { ViteDevServer } from 'vite'
import { withMermaid } from 'vitepress-plugin-mermaid'
-import { docsPages, sectionSpec, type DocsLocale, type DocsPage } from '../docs.ts'
+import { landingLink, orderedPages, routeLink, sectionSpec, type DocsLocale, type DocsPage } from '../docs.ts'
import { docsSourceFiles, projectDocs } from '../../scripts/project-doc-site.ts'
projectDocs()
-function sidebar(locale: DocsLocale, collection: DocsPage['sidebar']): DefaultTheme.SidebarItem[] {
- const pages = docsPages.filter(page => page.locale === locale && page.sidebar === collection)
+function sidebar(locale: DocsLocale, collection: NonNullable): DefaultTheme.SidebarItem[] {
+ // `orderedPages` already sorts by section placement, so insertion order
+ // carries the group order and each group keeps its pages in sequence.
const groups = new Map()
- for (const page of pages) {
+ for (const page of orderedPages(locale, collection)) {
const entries = groups.get(page.section) ?? []
entries.push(page)
groups.set(page.section, entries)
}
- return [...groups.entries()]
- .sort(([left], [right]) => sectionSpec(locale, left).index - sectionSpec(locale, right).index)
- .map(([text, entries]) => {
- const { collapsed } = sectionSpec(locale, text)
- return {
- text,
- // A present `collapsed` is what makes the default theme render the
- // group as collapsible at all, so an open group must omit the key.
- ...(collapsed === undefined ? {} : { collapsed }),
- items: entries
- .sort((left, right) => left.order - right.order)
- .map(page => ({ text: page.label, link: `/${page.route.replace(/(?:index)?\.md$/, '')}` })),
- }
- })
+ return [...groups.entries()].map(([text, entries]) => {
+ const { collapsed } = sectionSpec(locale, text)
+ return {
+ text,
+ // A present `collapsed` is what makes the default theme render the
+ // group as collapsible at all, so an open group must omit the key.
+ ...(collapsed === undefined ? {} : { collapsed }),
+ items: entries.map(page => ({ text: page.label, link: routeLink(page.route) })),
+ }
+ })
}
function watchCanonicalDocs(server: ViteDevServer): void {
@@ -199,9 +196,9 @@ export default withMermaid({
themeConfig: {
siteTitle: siteTitle('技术预览'),
nav: [
- { text: '入门', link: '/guide/', activeMatch: '^/guide/' },
- { text: '开发', link: '/develop/basic/', activeMatch: '^/develop/' },
- { text: '参考', link: '/reference/', activeMatch: '^/reference/' },
+ { text: '入门', link: landingLink('root', 'zh-guide'), activeMatch: '^/guide/' },
+ { text: '开发', link: landingLink('root', 'zh-develop'), activeMatch: '^/develop/' },
+ { text: '参考', link: landingLink('root', 'zh-reference'), activeMatch: '^/reference/' },
],
sidebar: {
'/guide/': sidebar('root', 'zh-guide'),
@@ -226,9 +223,9 @@ export default withMermaid({
themeConfig: {
siteTitle: siteTitle('Preview'),
nav: [
- { text: 'Guide', link: '/en/guide/', activeMatch: '^/en/guide/' },
- { text: 'Develop', link: '/en/develop/basic/', activeMatch: '^/en/develop/' },
- { text: 'Reference', link: '/en/reference/', activeMatch: '^/en/reference/' },
+ { text: 'Guide', link: landingLink('en', 'en-guide'), activeMatch: '^/en/guide/' },
+ { text: 'Develop', link: landingLink('en', 'en-develop'), activeMatch: '^/en/develop/' },
+ { text: 'Reference', link: landingLink('en', 'en-reference'), activeMatch: '^/en/reference/' },
],
sidebar: {
'/en/guide/': sidebar('en', 'en-guide'),
diff --git a/website/docs.ts b/website/docs.ts
index 7615952d0e..12280585cc 100644
--- a/website/docs.ts
+++ b/website/docs.ts
@@ -11,7 +11,7 @@
export type DocsLocale = 'root' | 'en'
/** Sidebar collection rendered for one locale and top-level module. */
-type DocsSidebar =
+export type DocsSidebar =
| 'zh-guide'
| 'zh-develop'
| 'zh-reference'
@@ -478,3 +478,47 @@ export const docsPages: DocsPage[] = [
...subsystemsReference,
...reference,
]
+
+/**
+ * Pages of one sidebar collection, in the order the sidebar lists them.
+ *
+ * @param locale - Route tree whose sidebar is being built.
+ * @param collection - Sidebar collection to read.
+ * @returns The collection's pages, ordered by section placement then by `order`.
+ */
+export function orderedPages(locale: DocsLocale, collection: DocsSidebar): DocsPage[] {
+ return docsPages
+ .filter(page => page.locale === locale && page.sidebar === collection)
+ .sort((left, right) => (
+ sectionSpec(locale, left.section).index - sectionSpec(locale, right.section).index
+ || left.order - right.order
+ ))
+}
+
+/**
+ * Site-relative link for a published route.
+ *
+ * @param route - Manifest route, including its `.md` suffix.
+ * @returns The link VitePress serves the route at.
+ */
+export function routeLink(route: string): string {
+ return `/${route.replace(/(?:index)?\.md$/, '')}`
+}
+
+/**
+ * Where a top-level navigation item lands.
+ *
+ * The target is derived rather than written down: a collection whose first page
+ * is renamed or reordered would otherwise leave the navigation bar pointing at
+ * a route the manifest no longer publishes.
+ *
+ * @param locale - Route tree the navigation item belongs to.
+ * @param collection - Sidebar collection the item opens.
+ * @returns Site-relative link of the collection's first page.
+ * @throws When the collection publishes no page.
+ */
+export function landingLink(locale: DocsLocale, collection: DocsSidebar): string {
+ const first = orderedPages(locale, collection)[0]
+ if (first === undefined) throw new Error(`Sidebar collection "${collection}" publishes no page.`)
+ return routeLink(first.route)
+}