mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
docs(website): derive navigation targets from the publication manifest
The navigation bar named `/guide/` while the manifest published the guide's first page at `guide/quickstart.md`, so the item served a 404 in both locales. `landingLink` resolves each item against `orderedPages`, the ordering the sidebar already renders, and a test asserts every navigation target is a route the manifest publishes.
This commit is contained in:
+2
-2
@@ -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
|
||||
|
||||
+4
@@ -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 `<img>`, 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.
|
||||
|
||||
+4
@@ -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` 渲染为 `<img>`,会把字标固定为文件声明的颜色,并且需要为每套主题各准备一份资源。侧边栏滚动条平时不可见,滚动时出现,通过 `data-` 属性而非 class 标记,因为 Vue 在 patch 该元素时会整体重写 `class`。
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<DocsPage['sidebar']>): 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<string, DocsPage[]>()
|
||||
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'),
|
||||
|
||||
+45
-1
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user