docs: fix reference sidebar ordering and group the subsystem pages

The VitePress config declared no position for the subsystem or other-interface
sections, so `indexOf` returned -1 and sorted them ahead of every declared
group: the reference landing page's own sidebar entry sat 1549px below the
fold. Four subsystem pages also shared `order` values with pages in the same
section, resolved only by sort stability and array concatenation order.

Section placement and collapse move into the manifest as a per-locale
declaration, and `sectionSpec` throws for an undeclared section instead of
sorting it silently to the top. Subsystem pages are grouped by concern, the
six topical groups collapse until one holds the page being read, and page
order derives from array position.

The projector drops the language-switcher line and repository badge the
canonical pages carry for their GitHub readers. The navigation bar gains the
DeepSeek wordmark, a release-stage tag, and a favicon; the sidebar scrollbar
rests invisible and appears while scrolling. Subsystem pages carry a two-level
outline, and the two plugin-development tracks now cross-link.
This commit is contained in:
Yichen Jiang
2026-08-12 13:52:27 +08:00
parent 15c84cdc4b
commit 0a2ac90617
18 changed files with 477 additions and 117 deletions

View File

@@ -144,7 +144,7 @@ const develop = pairedPages([
{
source: 'docs/user/develop/basic/index.md',
route: 'develop/basic/index.md',
label: { root: '第一个插件', en: 'First plugin' },
label: { root: '第一个 Harness 插件', en: 'Your first Harness plugin' },
sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: '基础', en: 'Basics' },
order: 1,
@@ -219,7 +219,7 @@ const develop = pairedPages([
])
const cordisTutorial = pairedPages(([
['index.md', 'Cordis 教程', 'Cordis tutorial'],
['index.md', '总览', 'Overview'],
['01-first-plugin.md', '1. 第一个插件', '1. Your first plugin'],
['02-lifecycle-and-effects.md', '2. 生命周期与副作用', '2. Lifecycle and effects'],
['03-services.md', '3. 服务', '3. Services'],
@@ -232,7 +232,7 @@ const cordisTutorial = pairedPages(([
route: `develop/cordis-tutorial/${file}`,
label: { root: rootLabel, en: enLabel },
sidebar: { root: 'zh-develop', en: 'en-develop' },
section: { root: 'Cordis 教程', en: 'Cordis tutorial' },
section: { root: 'Cordis 框架教程', en: 'Cordis framework tutorial' },
order,
...(file === 'index.md' ? { sourceAliases: ['docs/cordis-tutorial'] } : {}),
})))
@@ -248,55 +248,84 @@ const cordisPrimerReference = pairedPages([
},
])
const subsystemsReference = pairedPages(([
['README.md', '子系统', 'Subsystems', 0],
['core.md', '核心', 'Core', 1],
['scope.md', '作用域', 'Scopes', 2],
['typert.md', 'TypeRT', 'TypeRT', 39],
['session.md', '会话', 'Sessions', 3],
['session-query.md', '会话查询', 'Session query', 4],
['session-reference.md', '会话引用', 'Session references', 5],
['session-title.md', '会话标题', 'Session titles', 6],
['settings.md', '用户设置', 'User settings', 7],
['credentials.md', '用户凭据', 'User credentials', 8],
['system-prompt.md', '系统提示词', 'System prompts', 9],
['tools.md', '工具', 'Tools', 10],
['llm-streaming.md', 'LLM 流式响应', 'LLM streaming', 11],
['token-meter.md', 'Token 计量', 'Token metering', 12],
['bash.md', 'Bash 执行', 'Bash execution', 13],
['subprocess.md', '子进程', 'Subprocesses', 14],
['tasks.md', '后台任务', 'Background tasks', 15],
['filesystem.md', '文件系统', 'Filesystem', 16],
['lsp.md', 'LSP 导航', 'LSP navigation', 17],
['code-runtime.md', '代码运行时', 'Code runtime', 18],
['compaction.md', '上下文压缩', 'Compaction', 19],
['subagent.md', '子代理', 'Subagents', 20],
['workflow.md', '工作流', 'Workflows', 21],
['skills.md', '技能', 'Skills', 22],
['approval.md', '审批', 'Approvals', 23],
['permission.md', '权限预设', 'Permission presets', 24],
['plan.md', '计划模式', 'Plan mode', 25],
['user-interaction.md', '用户交互', 'User interaction', 26],
['sandbox.md', '沙箱', 'Sandboxing', 27],
['web.md', 'Web 访问', 'Web access', 28],
['spill.md', 'Spill 存储', 'Spill storage', 29],
['persistence.md', '会话持久化', 'Session persistence', 30],
['storage.md', '存储', 'Storage', 31],
['workspace.md', '工作区', 'Workspaces', 32],
['http-server.md', 'HTTP 服务器', 'HTTP server', 33],
['client-modules.md', '客户端模块', 'Client modules', 34],
['invariants.md', '运行时不变式', 'Runtime invariants', 36],
['session-projection.md', '会话投影', 'Session projections', 37],
['telemetry.md', '遥测', 'Telemetry', 38],
] as const).map(([file, rootLabel, enLabel, order]): PairedPage => ({
source: `docs/subsystems/${file}`,
route: file === 'README.md' ? 'reference/subsystems/index.md' : `reference/subsystems/${file}`,
label: { root: rootLabel, en: enLabel },
sidebar: { root: 'zh-reference', en: 'en-reference' },
section: { root: '子系统', en: 'Subsystems' },
order,
...(file === 'README.md' ? { sourceAliases: ['docs/subsystems'] } : {}),
})))
/**
* Subsystem pages grouped by the concern they document, as `[Chinese section,
* English section, pages]`. One flat list of every subsystem pushed the rest of
* the reference sidebar below the fold.
*/
const subsystemGroups = [
['总览', 'Overview', [
['README.md', '子系统', 'Subsystems'],
]],
['内核与作用域', 'Core and scopes', [
['core.md', '核心', 'Core'],
['scope.md', '作用域', 'Scopes'],
['invariants.md', '运行时不变式', 'Runtime invariants'],
]],
['会话与持久化', 'Sessions and persistence', [
['session.md', '会话', 'Sessions'],
['session-query.md', '会话查询', 'Session query'],
['session-reference.md', '会话引用', 'Session references'],
['session-title.md', '会话标题', 'Session titles'],
['session-projection.md', '会话投影', 'Session projections'],
['persistence.md', '会话持久化', 'Session persistence'],
['spill.md', 'Spill 存储', 'Spill storage'],
['telemetry.md', '遥测', 'Telemetry'],
]],
['模型与上下文', 'Model and context', [
['llm-streaming.md', 'LLM 流式响应', 'LLM streaming'],
['token-meter.md', 'Token 计量', 'Token metering'],
['system-prompt.md', '系统提示词', 'System prompts'],
['compaction.md', '上下文压缩', 'Compaction'],
]],
['执行与工具', 'Execution and tools', [
['tools.md', '工具', 'Tools'],
['bash.md', 'Bash 执行', 'Bash execution'],
['subprocess.md', '子进程', 'Subprocesses'],
['pty.md', 'PTY 会话', 'PTY sessions'],
['tasks.md', '后台任务', 'Background tasks'],
['filesystem.md', '文件系统', 'Filesystem'],
['lsp.md', 'LSP 导航', 'LSP navigation'],
['code-runtime.md', '代码运行时', 'Code runtime'],
['web.md', 'Web 访问', 'Web access'],
['skills.md', '技能', 'Skills'],
['workflow.md', '工作流', 'Workflows'],
['subagent.md', '子代理', 'Subagents'],
]],
['策略与交互', 'Policy and interaction', [
['approval.md', '审批', 'Approvals'],
['permission.md', '权限预设', 'Permission presets'],
['sandbox.md', '沙箱', 'Sandboxing'],
['plan.md', '计划模式', 'Plan mode'],
['user-interaction.md', '用户交互', 'User interaction'],
['commands.md', '命令', 'Human commands'],
['goal.md', '目标', 'Goals'],
['schedule.md', '定时提醒', 'Scheduled reminders'],
]],
['平台与接入', 'Platform and access', [
['http-server.md', 'HTTP 服务器', 'HTTP server'],
['typert.md', 'TypeRT', 'TypeRT'],
['client-modules.md', '客户端模块', 'Client modules'],
['storage.md', '存储', 'Storage'],
['workspace.md', '工作区', 'Workspaces'],
['settings.md', '用户设置', 'User settings'],
['credentials.md', '用户凭据', 'User credentials'],
]],
] as const
const subsystemsReference = subsystemGroups.flatMap(([rootSection, enSection, files]) => pairedPages(
files.map(([file, rootLabel, enLabel], order): PairedPage => ({
source: `docs/subsystems/${file}`,
route: file === 'README.md' ? 'reference/subsystems/index.md' : `reference/subsystems/${file}`,
label: { root: rootLabel, en: enLabel },
sidebar: { root: 'zh-reference', en: 'en-reference' },
section: { root: rootSection, en: enSection },
order,
// Subsystem pages carry long third-level sections a two-level outline reaches.
outline: [2, 3],
...(file === 'README.md' ? { sourceAliases: ['docs/subsystems'] } : {}),
})),
))
const reference = [
...pairedPages(([
@@ -359,19 +388,6 @@ const reference = [
section: { root: 'Cordis API', en: 'Cordis Core API' },
order: order + 5,
}))),
...pairedPages(([
['goal.md', '目标', 'Goals', 14],
['schedule.md', '定时提醒', 'Scheduled reminders', 15],
['pty.md', 'PTY 会话', 'PTY sessions', 26],
['commands.md', '命令', 'Human commands', 38],
] as const).map(([file, rootLabel, enLabel, order]): PairedPage => ({
source: `docs/subsystems/${file}`,
route: `reference/subsystems/${file}`,
label: { root: rootLabel, en: enLabel },
sidebar: { root: 'zh-reference', en: 'en-reference' },
section: { root: '子系统', en: 'Subsystems' },
order,
}))),
...pairedPages(([
['adding-a-package.md', '新增 Package', 'Adding a package'],
['adding-a-tool.md', '新增 Tool', 'Adding a tool'],
@@ -395,6 +411,64 @@ const reference = [
}]),
]
/** A sidebar group, matched to pages by `label`. */
export interface DocsSection {
/** Group heading, equal to the `section` field of every page it holds. */
label: string
/** Render the group collapsed until it holds the page being read. */
collapsed?: boolean
}
/**
* Every sidebar group, in the order its locale renders it.
*
* The subsystem groups collapse because together they outnumber the rest of the
* reference sidebar; expanded, they push every other group below the fold.
*/
const sections: Record<DocsLocale, readonly DocsSection[]> = {
root: [
{ label: '入门' }, { label: '其他接口' },
{ label: '基础' }, { label: '框架能力' }, { label: '实战' }, { label: 'Cordis 框架教程' },
{ label: '概念' }, { label: '生成参考' }, { label: 'Cordis API' }, { label: '开发手册' },
{ label: '总览' },
{ label: '内核与作用域', collapsed: true },
{ label: '会话与持久化', collapsed: true },
{ label: '模型与上下文', collapsed: true },
{ label: '执行与工具', collapsed: true },
{ label: '策略与交互', collapsed: true },
{ label: '平台与接入', collapsed: true },
],
en: [
{ label: 'Guide' }, { label: 'Other interfaces' },
{ label: 'Basics' }, { label: 'Framework' }, { label: 'Practice' }, { label: 'Cordis framework tutorial' },
{ label: 'Concepts' }, { label: 'Generated reference' }, { label: 'Cordis Core API' }, { label: 'Cookbook' },
{ label: 'Overview' },
{ label: 'Core and scopes', collapsed: true },
{ label: 'Sessions and persistence', collapsed: true },
{ label: 'Model and context', collapsed: true },
{ label: 'Execution and tools', collapsed: true },
{ label: 'Policy and interaction', collapsed: true },
{ label: 'Platform and access', collapsed: true },
],
}
/**
* Placement and collapse behavior of one sidebar group.
*
* @param locale - Route tree whose sidebar is being built.
* @param label - Section label carried by the pages in the group.
* @returns The declared group, plus its zero-based position in the locale.
* @throws When the locale declares no placement for the label. Ranking by list
* membership alone would sort an undeclared group silently ahead of every
* declared one.
*/
export function sectionSpec(locale: DocsLocale, label: string): DocsSection & { index: number } {
const declared = sections[locale]
const section = declared.find(candidate => candidate.label === label)
if (section === undefined) throw new Error(`Sidebar section "${label}" has no placement in the ${locale} locale.`)
return { ...section, index: declared.indexOf(section) }
}
/** Every canonical page published by the documentation website. */
export const docsPages: DocsPage[] = [
...homeAndGuide,