使用 OINK Starter,在定制前建立本地预览基线。
这是本节的多页打印视图。 .
OINK 文档
-
1: OINK 是什么
- 1.1: 特性
- 1.2: Case 导览
- 1.3: 开源许可与致谢
- 1.4: 参与 OINK 贡献
-
2: 快速上手
- 2.1: 使用 OINK Starter
- 2.2: Starter 仓库导览
- 2.3: 从零建站与其它安装方式
- 3: 创作内容
- 4: 组件总览
- 5: 定制站点
- 6: 维护管理
-
7: 设计与开发
- 7.1: 架构契约
- 7.2: 组件契约
- 7.3: 外壳与导航契约
- 7.4: 落地页契约
- 7.5: Expo 布局契约
- 7.6: OINK 迁移边界
-
7.7: 设计决策
- 7.7.1: 警告与安全回退
- 7.7.2: 配置模型
- 7.7.3: Markdown 优先创作
- 7.7.4: 生成式配置 Schema
- 7.7.5: 可选 CLI 与结果契约
- 7.7.6: Paper 与 Slate 视觉预设
- 7.7.7: 四种视觉预设的组件精修
- 7.7.8: Expo 图片收藏布局
- 7.7.9: 将博客文章呈现为画廊
- 7.7.10: 显式开启表格阅读
- 7.7.11: 译文对照
- 7.7.12: 统一图表阅读体验
- 7.7.13: 本地搜索改进
-
7.8: 设计研究
- 7.8.1: Goldmark 块属性实测
- 7.8.2: OINK 1.3.2 与 Hugo 0.168 消费站点盘点
- 7.8.3: Ink 与 Terminal 实验,2026-10-05
- 7.8.4: OINK 1.2 发布前审查,2026-10-05
- 7.8.5: 视觉预设验收,2026-10-05
- 7.8.6: 消费站与迁移证据
- 7.8.7: OINK 全面审查(2026-08-26)
- 7.8.8: 社区 Issue 与 PR 调研,2026-09-19
- 7.8.9: OINK 1.1 发布审查,2026-09-20
- 7.8.10: 2026-10-03 CLI 维护验收
- 7.8.11: 2026-10-03 CLI 维护验收:R1–R5
- 7.8.12: 2026-10-03 CLI 维护验收:R6–R8 与 A18
- 7.8.13: 文档对照研究,2026-10-29
- 7.8.14: 2026-09-29 CLI 验收快照
- 7.8.15: 组件精修验证,2026-10-25
- 7.8.16: 审查修复,2026-11-06
- 7.8.17: Ink 与 Terminal 仍需阅读验收
- 7.8.18: OINK 1.5.1 发布就绪审查,2026-11-06
- 7.8.19: 2026-10-13 主题审查
- 7.8.20: 为大表格腾出阅读空间
-
7.9: 设计提案与 PRD
- 7.9.1: 反向链接与知识图谱
- 7.9.2: 媒体收敛
- 7.9.3: OINK CLI 与下一阶段产品路线
- 7.9.4: OINK CLI 文档维护路线图
- 7.9.5: 视觉预设与外观切换
- 7.9.6: 让 Ink 与 Terminal 成为稳定的阅读风格
- 8: OINK CLI
第一次使用 OINK,请从 Starter 预览一个可运行的站点,再替换成 自己的内容。正文用 Markdown 编写,Hugo Extended 及其内置 Sass 编译器负责构建;采用 Hugo Modules 时还需要 Go 解析主题模块。主题内置资源无需 CDN,也不需要 npm 构建流程。
当前发布版本为 v1.5.1。已有站点可查看 1.5.1 发布说明与升级指南。
五条入口
- 快速上手 — 创建 OINK Starter 仓库,建立本地基线,分层定制并部署。
- 组件总览 — 每个组件一页,先给源码再给渲染效果。
- 使用 OINK 创作优美的内容 — 六章动手实战教程,带你从克隆 Starter 一直走到双语发布上线。
- 案例 — 把生产站点拆解成可复用的设计与迁移模式。
- 设计与开发 — 面向 OINK 维护者的契约、已接受决策、研究证据与候选提案。
按任务导航
| 你要做的事 | 去哪 |
|---|---|
| 判断是否适用 | OINK 是什么 |
| 安装并预览 | 快速上手 |
| 写一页文档 | 编写页面 |
| 把目录树变成侧栏 | 组织内容 |
| 查组件写法 | 组件总览 |
| 制作图片相册或浏览在线画廊 | Expo 相册 · 在线画廊 |
| 改站名、Logo、配色与字体 | 品牌外观 |
| 查某个配置键的默认值 | 配置总览 |
| 做双语或多语言站 | 多语言 |
| 跟随练习建站与写作 | 使用 OINK 创作优美的内容 |
| 研究生产环境实现 | 案例 |
| 部署到线上 | 发布上线 |
| 升级版本或从 Docsy 迁移 | 版本升级 |
| 维护主题、审查契约或编写 PRD | 设计与开发 |
侧栏前六个栏目(简介、快速上手、创作内容、组件、定制站点 与 维护管理)面向站点作者与运营者;底部的 设计 和 CLI 则供 OINK 主题维护者查阅。
1 - OINK 是什么
OINK 是一款独立的 Hugo 主题,用于搭建中大型技术文档站。它从 Docsy 演化而来:保留 Docsy 的内容模型与多语言行为,替换外壳、导航、搜索与内容组件。
当前发布版本为 OINK v1.5.1,使用 Hugo Extended 0.168.0 及其内置 Sass 编译器构建。采用 Hugo Module 时还需要 Go 解析模块,首次下载需要可访问的模块来源。主题资源不依赖 Node.js、npm、PostCSS 或 CDN。Bootstrap、Font Awesome、字体、本地搜索、图表与 API 文档运行时都提交在主题仓库里,只在页面用到时下发。
组件不是另一套模板语言:> [!NOTE] 是提示块,表格加一行 {.fields} 是参数表,图片下面加 {caption="..."} 就有图注。当前有十五个生产站点在用它,本站是其中之一。

主题的职责
- 文档与博客外壳:导航、侧栏树、目录、面包屑、翻页、深色模式、打印视图与无障碍交互。
- 多语言框架:译文路由、缺译回退、语言权重、RTL、32 份完整界面语言包,以及按需开启的双列视图(
params.ui.compare: true)。 - 本地浏览器功能:Mermaid、Markmap、Swagger UI、Redoc、Asciinema、ECharts、Infographic 与全文检索。数学公式由 Hugo 在构建时渲染,使用本地 KaTeX 样式。
- 内容组件:提示块、标签页、步骤、卡片、参数表、文件树、画廊、徽章、按键等,多数有 Markdown 原生形态。
- 内容类型:普通文档之外,还内置书籍编号与交叉引用、发布与下载页、数据驱动的 Landing 首页、OpenAPI 文档页。
主题不负责源码托管与部署:站点可以放在 GitHub、GitLab 或私有 Git 上,Hugo 生成的静态文件可用任何托管平台发布。站点自己的内容、品牌与业务组件仍归站点管理,主题只提供通用外壳与可复用组件。
适用范围
| 这些情况适合 | 这些情况不适合 |
|---|---|
| 页面多、内容类型杂:文档、博客、书、发布页与 API 参考共处一个站点 | 只有一两页内容、不需要结构化导航;README 或更轻的 Hugo 主题更简单 |
| 需要完整的多语言,而不是给英文站挂一个翻译入口 | 站点主体是应用界面而不是文档:可以用 OINK 承载文档部分,业务组件留在站点层 |
| 对可复现构建与网络隔离有要求,构建机不能出网 | 需要在正文里写交互组件(React / MDX) |
| 多个站点共享同一套外壳,不必复制布局与 shortcode | 想用一个开关换成另一套视觉:主题没有品牌开关,改外观要走 CSS token 与 partial 覆盖 |
| 团队没有前端,也不维护 Node 工具链 | 需要主题内置内容管理后台或所见即所得编辑器 |
与其它文档方案的差别
先选择团队愿意维护的工具链与创作模型,再比较单项功能。同一个项目可能适合不同方案:
| 优先需求 | 应重点比较什么 |
|---|---|
| 延续现有 Hugo 内容流程 | 用自己的内容树、模板覆盖与语言需求比较 OINK、Docsy 和 Hextra |
| 不维护 Node 工具链 | OINK 随主题分发浏览器资源;Hugo Module 安装方式仍需 Go 解析模块 |
| 在文档中编写 React 组件 | 考察 Docusaurus 一类基于 MDX 的方案;OINK 主要使用 Markdown、属性与短代码 |
| 出版书籍、下载页或数据驱动落地页 | 先用一篇代表性页面验证 OINK 内置模式,再扩展到全站 |
选型时核对各项目当前的安装与扩展文档。OINK 的搜索、图片缩放、评论与反馈需要显式启用,
Markdown 与 Agent 输出也由站点在 outputs 中选择。
OINK 不是叠在 Docsy 上的皮肤,而是 fork 之后独立演化的主题。Docsy 的源码历史、Apache-2.0 义务与署名完整保留,细节见开源许可与致谢。
入口
特性按能力逐条列出主题提供的东西,每条链接到讲它的指南页。
报告问题或提交修改请参阅贡献指南; 1.5.1 发布说明介绍双列视图修复、输出正确性、响应式阅读与当前构建要求。
1.1 - 特性
本页逐条列出 OINK 与普通 Hugo 主题的差别,每条末尾给出讲它的指南页。要立即安装,见快速上手。
组件写在 Markdown 里
提示块是 > [!NOTE] 块引用(十种语义类型加一个中性折叠块),参数表是表格加一行 {.fields},步骤与卡片是列表加 {.steps} / {.cards},图注是图片下面一行 {caption="…"}。标签页是几个相邻围栏各带一个 {tab="…"};文件树、画廊、Mermaid、ECharts 是以语言命名的数据围栏。这些写法在 GitHub 或普通 Markdown 阅读器中退化为块引用、表格、列表与代码块,内容不丢失。
29 个 shortcode 覆盖原生形态表达不了的场景:卡片带图标与图片、参数表条目正文是多段 Markdown。
→ 组件总览
用 Hugo 构建
Hugo Extended 0.168.0 或更新版本负责构建站点资源。SCSS 由 Hugo 内置 LibSass 编译,主题不调用 postCSS,也不需要 npm 或 webpack。用 Hugo Module 方式安装主题还需要 Go,并能通过网络或本地缓存取得模块依赖。离线归档或 submodule 准备就绪后,仅使用 Hugo Extended 即可构建站点,无需 Go 或外部 Sass 编译器。
界面交互在浏览器中执行 JavaScript:搜索、命令面板、图表、标签页都是页面脚本。这些脚本随主题分发,按页面用到的功能下发。
→ 快速上手
本地优先
浏览器需要的资源全部提交在主题仓库里:Bootstrap、Font Awesome、四款字体、Lunr、Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts、Infographic,以及本地 PlantUML 引擎。VENDOR.json 逐项记录各依赖的版本、来源、许可证文件与 SHA-256 校验值,更新某个运行时要同时更新产物、许可证与校验值。
OINK 1.4.0 启用 PlantUML 后,无需端点即可在浏览器内渲染;显式配置端点时继续使用服务端模式。
对可能引起网络请求的功能,主题让它保持关闭而不是静默连出去:Diagrams.net 缺 params.drawio.drawio_server、Algolia 缺 appId / apiKey / indexName,都会告警并保持禁用。
本地优先不覆盖作者自己添加的内容。以下都是显式的网络选择:外部链接、远程图片与视频、iframe、远程 API 规范;Algolia、Google 自定义搜索这类托管搜索;分析、评论与其它 SaaS 集成;作者主动配置远程渲染器的 PlantUML 与 Diagrams.net。启用这些功能的页面在运行时需要访问网络。
一份内容,四种输出
每个组件在四种输出下都有确定的形态:交互式 HTML;去掉缩放与复制控件、折叠块完全展开的打印页;纯 Markdown;RSS。打印视图按栏目整份生成(本栏目是 /zh/_print/docs/about/),Markdown 版本是同一页面地址加 index.md。
站点在 outputs 里显式选择需要哪几种,主题不替站点决定。
双语与 32 个界面语言
多语言走 Hugo 原生机制:译文路由、按权重排序的语言选择器、缺译回退、RTL,
以及 canonical 与 alternate 元数据。界面文案有 32 份语言包,共用 235 条消息
schema:Docsy 支持的 31 个 locale 文件名,再加通用 zh。每份语言包都使用目标
语言覆盖完整 OINK 界面,不再保留英文占位块;zh 与 zh-cn 使用简体中文,
zh-tw 使用繁体中文。开启 params.ui.compare: true 后,读者还可以在当前页面旁打开已有译文,并排同步阅读两个语言版本。
全文检索不出站
打开 params.offline_search 后,Hugo 为每种语言生成一份索引,浏览器用本地 Lunr 检索拉丁文字、用子串回退检索中日韩文本,查询内容不发给任何第三方。页面可以用 search_boost 调权重、用 search_keywords 补同义词。
→ 全文检索
命令面板
Cmd/Ctrl + K 打开命令面板;裸按 / 进入搜索态,裸按 \ 进入纯命令态。面板里同时有页面、命令与页面动作(切换语言、切换主题、复制 Markdown 等),搜索与操作共用一个入口。
→ 命令面板
键盘导航
默认开启,可按站点或按栏目关闭。w s 在侧栏树上下移动,a d 折叠展开,q e 上一篇下一篇,j k 沿页面目录跳转,t 切换深浅色,l 切换语言,有可用译文时按 b 打开或关闭双列视图,h 隐藏阅读外壳。输入框、文本域获得焦点或输入法处于组字状态时,单键快捷键全部让行。页脚最底层栏的问号按钮打开速查卡。
→ 键盘导航
反向链接
打开 params.ui.backlinks 后,每一页都会列出有哪些页面链接到它——构建时从你本来就写的普通链接派生,没有新语法,也没有 JavaScript。本站全站开启:看本页右栏的「反链」组,越常被引用的页面列表越长,超过八条会折叠。
→ 反向链接
文档之外的四种内容
主题还内置四类需要额外结构的页面:
- 书籍:章节编号,图 / 表 / 式 / 例用
{#id num=}编号、用xref交叉引用,book-toc、book-figures一类 shortcode 生成索引,整本可打印。 - 发布与下载页:
data/download/*.yaml生成发布卡片、资产表与校验和,发布状态可控。 - Landing 首页:
data/home/<lang>.yaml拼装首页分区;任意页面加layout: landing也能用data/landing/的数据。 - API 文档:Swagger UI 与 Redoc 都是本地运行时,spec 放站内即可。
→ 书籍出版 · 发布与下载页 · 首页与落地页 · API 文档
面向 AI 助手的输出
outputs 里加上 markdown,每个页面就多一份 .md,HTML 的 <head> 里带 rel="alternate" 指过去,页面动作里也多出「复制 Markdown」与「查看源码」。LLMS 输出格式在站点根目录生成 llms.txt 内容清单(本站是 https://oink.pgsty.com/zh/llms.txt)。
0.8.0 再加两种:栏目开启 LLMSFULL 后整个栏目拼成一份 llms-full.txt,agent 一次抓完;站点开启 NAVJSON 后每种语言发布一份 navigation.json,侧栏那棵树直接当数据读。两者都在本站开着:https://oink.pgsty.com/zh/docs/llms-full.txt 与 https://oink.pgsty.com/zh/navigation.json 就是真实产物。
「在 ChatGPT / Claude 中打开」默认关闭:读者点击时会把当前 URL 交给第三方,需要站点显式打开 params.ui.page_context_menu.assistant_links。
→ Agent 支持
多版本
配置 params.versions 后顶栏出现版本菜单,旧版本站点顶部显示归档横幅,提示读者查看最新版本;菜单是否逐页跳转由站点决定。多个版本是分别构建、分别部署的静态站点,不需要运行时支持。
→ 多版本
自己验证
本站启用了上面多数特性,四条自查:
- 在任意页面按
Cmd/Ctrl + K,输入postgres查看本地搜索结果;按\进入纯命令态。 - 在当前页面地址后加
index.md,得到这一页的 Markdown 版本。 - 打开 https://oink.pgsty.com/zh/llms.txt,那是给 AI 助手的站点清单;顺着它能找到整个文档栏目的
llms-full.txt与navigation.json。 - 看本页右栏的「反链」组,它列出链接到本页的页面。
相关
1.2 - Case 导览
案例库介绍十五个采用 OINK 的站点项目及其实现模式,本站也包含在内。 案例页链接到线上成果或项目源码;涉及定制实现时,区分站点自有代码与主题能力。
当你已经知道自己要搭建哪类站点时,可以从这里开始:先通过案例了解架构与 取舍,再沿页面链接进入具体配置文档。案例中的数量统计基于盘点时的快照。
发行版文档
pigsty.io
大型英文站,把发行版手册、博客、扩展目录、分类、版本导航与价格落地页放在 同一个站点中。
pigsty.cc
独立部署的中文对等站;当两种语言的语料都已成为完整产品时,拆成两个单语站 是一种清晰的取舍。
pgsty.pro
双语版本档案站,从可复用的结构化发布数据渲染大量版本页面。
产品文档
PIG
紧凑的双语命令行工具手册,配有数据驱动首页与体量更大的博客。
SOW
双语运维手册,使用独立下载内容类型展示发布元数据与产物。
SILO
大型上游迁移案例,通过受检查的清单生成双语文档导航。
PG Exporter
把生成导航、结构化指标目录与系统字体组合起来的指标手册。
书籍
《设计数据密集型应用》
多语言、多版本书籍,也是编号图表、交叉引用、章节导航与索引最完整的案例。
《The Product-Minded Engineer》
只需要 OINK Book 外壳的聚焦型双语出版物。
《PG 技术内幕》
已完稿的中文译本,刻意做成单语 Book:没有文档树,也没有可切换的第二语言。
汇编、落地页与自定义站点
PostgreSQL 组件文库
聚合型运维文库,让多个上游手册与完成度不一的翻译树共享搜索和视觉体系。
pgsty.com
小型双语公司站,展示 OINK 也可以主要作为数据驱动的落地页系统。
Capslock
每种语言只有两页,其中自定义外壳承载数据驱动交互配置生成器。
oink.pgsty.com
完整参考站:公开文档、实时组件示例、设计契约、多种内容外壳与回归覆盖都在 同一个仓库中。
pgext.cloud
PostgreSQL 扩展目录:把可检索的数据集作为站点主体呈现,收录 2,241 个扩展、 576 个已打包版本,覆盖 16 个 Linux 平台。
如何选择起点
- 常规产品手册:从 PIG 或 SOW 开始。
- 大型迁移:对比 SILO 与 PostgreSQL 组件文库。
- 书籍:对比精简的 TPME 与更复杂的 DDIA, 单语场景可参考 《PG 技术内幕》。
- 落地页或交互站:参考 pgsty.com 或 Capslock。
- 最完整的参考实现:使用 OINK Docs。
- 如果读者是来查询数据集而不是来阅读的,看看 ext.pgsty.com 如何把数据集作为站点主体呈现。
主题仓库的 tests/site/ 是内部 CI 夹具,而不是起步模板;其中页面的职责是
触发渲染行为。上面的生产案例更适合作为架构与设计参考。
1.3 - 开源许可与致谢
OINK 由三层材料组成:主题源码、文档内容、随主题分发的第三方资源。三者各自的许可证不会被重新授权成一份统一作品。下面每张表都指向仓库里的权威文件,摘要与许可证原文不一致时以文件为准。
许可证对应关系
| 范围 | 许可证 | 权威文件 |
|---|---|---|
| OINK 主题源码(布局、partial、 shortcode、SCSS、JS、i18n) | Apache License 2.0 | 主题 LICENSE、NOTICE |
| 本站的站点代码、构建脚本与源自 Docsy 的材料 | Apache License 2.0 | 站点 LICENSE、NOTICE |
| 本站的原创文档内容(另有声明的除外) | Creative Commons Attribution 4.0 International | 站点 LICENSE-CC-BY-4.0 |
| 随主题分发的浏览器库、字体与图标 | 各组件自己的许可证 | 主题 VENDOR.json 与资源旁的许可证文件 |
两条边界要分清:CC BY 4.0 只覆盖原创文档内容,不覆盖主题代码、商标、截图与第三方资源;主题采用 Apache-2.0,也不会把随附依赖变成 Apache 许可的作品。
社区贡献者
OINK 由 PGSTY 维护。感谢 @imbajin 报告并贡献以下 阅读体验修复,同时提供夹具、回归测试与双语文档。这些改动在 1.3.1 引入, 1.5.1 继续保留。
| 贡献 | Pull request |
|---|---|
| Markmap 节点文字适配明暗模式与视觉预设 | 主题 #48 |
| 固定 TOC 的页脚避让 | 主题 #50 |
完整 FileTree 注释,最终通过 wrap=true 显式开启 |
主题 #52 |
| 显式开启的技术表格可读宽度布局 | 主题 #54 |
| 双语契约、发布文档与伴生站回归覆盖 | 文档 #11 |
发布说明记录了集成细节。参与纠错、翻译或修复, 请参阅贡献指南。
上游:Docsy
主题 NOTICE 记录的事实:
- OINK 派生自 Docsy,Copyright 2018 Google LLC and Docsy contributors。
- OINK 自身的主题工作 Copyright 2026 PGSTY contributors。
- 项目与上游同为 Apache License 2.0;第三方浏览器依赖的许可、来源、版本与校验值记录在
VENDOR.json,各自要求的 NOTICE 文件与对应资源放在一起分发。 - Docsy 名称与 Google 商标归各自权利人所有,此处引用只用于标识上游项目,不表示背书。
本站也派生自 Docsy 项目网站,这段渊源记录在站点自己的 NOTICE 里。Docsy 是 OINK 唯一的代码上游:源码历史、Apache-2.0 义务与版权声明完整保留,按 Apache-2.0 的要求,修改过的文件需要标注。
随主题分发的第三方运行时
主题把浏览器要用的资源全部提交在仓库里(assets/third_party/、assets/js/third_party/、static/webfonts/)。这些资源无需 npm 步骤或构建期下载;Hugo Module 解析仍需要 Go 与可用的模块来源。VENDOR.json 是这批资源的机器可读清单,逐项记录名称、固定版本、来源 URL、许可证文件路径,以及每个选取产物的 SHA-256;清单里还有三棵资源目录的整体校验值。
下表对应 OINK v1.5.1 清单(VENDOR.json 生成于 2026-10-25,schema 1,共 29 项)。版本会随主题发布变动,以所固定主题版本的清单为准。全部来源都是 npm registry(https://registry.npmjs.org/…)。
| 项目 | 版本 | 许可证 | 在主题里做什么 |
|---|---|---|---|
| bootstrap | 5.3.8 | MIT | 栅格、组件与 RTL 样式基础 |
| @popperjs/core | 2.11.8 | MIT | Bootstrap 的浮层定位 |
| @fortawesome/fontawesome-free | 7.3.1 | CC-BY-4.0 AND OFL-1.1 AND MIT | 全站图标 |
| @fontsource-variable/inter | 5.3.0 | OFL-1.1 | 界面与正文字体 |
| @fontsource-variable/ibm-plex-sans | 5.3.0 | OFL-1.1 | Paper / Terminal 预设的正文字体 |
| @fontsource/chakra-petch | 5.3.0 | OFL-1.1 | 品牌展示字体 |
| @fontsource/ibm-plex-mono | 5.3.0 | OFL-1.1 | 代码字体 |
| lunr | 2.3.9 | MIT | 本地全文检索 |
| @docsearch/js | 5.0.4 | MIT | 可选的 Algolia DocSearch 前端 |
| @docsearch/css | 5.0.4 | MIT | 同上的样式 |
| mermaid | 11.17.2 | MIT | Mermaid 图表 |
| katex | 0.18.4 | MIT | 数学公式 |
| markmap-autoloader | 0.18.12 | MIT | 思维导图 |
| markmap-lib | 0.18.12 | MIT | 思维导图 |
| markmap-view | 0.18.12 | MIT | 思维导图 |
| markmap-toolbar | 0.18.12 | MIT | 思维导图工具条 |
| d3 | 7.9.0 | ISC | Markmap 依赖 |
| @highlightjs/cdn-assets | 11.12.0 | BSD-3-Clause | Markmap 依赖 |
| webfontloader | 1.6.28 | Apache-2.0 | Markmap 依赖 |
| swagger-ui-dist | 5.32.14 | Apache-2.0 | OpenAPI 文档页 |
| redoc | 2.5.3 | MIT | OpenAPI 文档页 |
| asciinema-player | 3.17.0 | Apache-2.0 | 终端录像回放 |
| echarts | 6.1.0 | Apache-2.0 | 图表 |
| @antv/infographic | 0.2.19 | MIT | 信息图 |
| pako | 3.0.1 | MIT AND Zlib | 解压(图表数据) |
| external-svg-loader | 1.7.1 | MIT | 内联外部 SVG |
| idb-keyval | 6.2.0 | Apache-2.0 | 浏览器端缓存 |
OINK 1.4.0 的本地 PlantUML 集成包含 @plantuml/core 1.2026.9(MIT)与
Viz.js 3.24.0(MIT);后者包含 Graphviz 14.1.1(EPL-1.0)与 Expat(MIT)。
许可证、声明和上游源码链接随两份脚本放在 assets/third_party/plantuml/,
VENDOR.json 记录具体产物。
许可证原文与各资源放在一起:例如 assets/third_party/bootstrap/LICENSE、assets/third_party/katex/LICENSE;Swagger UI、Redoc 与 ECharts 还随包带了各自的 NOTICE 或打包声明文件。Lunr 是唯一的例外,代码在 assets/js/third_party/,许可证在 assets/third_party/lunr/LICENSE。
再分发主题时,这些许可与声明材料必须一并保留。更新某个运行时意味着在同一次变更里同时更新产物、许可证文件、来源与校验值。
字体与图标
Inter、IBM Plex Sans、Chakra Petch 与 IBM Plex Mono 四种字体采用 SIL Open Font License 1.1。OINK 1.5.1 在 static/webfonts/ 下包含 33 个文件:14 个 Inter 子集、12 个 IBM Plex Sans 子集、4 个品牌字体文件,以及 3 个 Font Awesome 文件。Font Awesome Free 7.3.1 是复合许可:图标图形 CC BY 4.0、字体文件 SIL OFL 1.1、代码 MIT,原文在 assets/third_party/Font-Awesome/LICENSE.txt。
主题不向远程字体服务发请求:仓库里没有 Google Fonts 之类的外链,字体一律由站点自身 baseURL 下发。更换字体或改用系统字体栈见品牌外观。
设计参考
代码上游只有 Docsy 一个。下面这些项目是设计语言上的参考,既不是代码来源也不是运行时依赖,OINK 没有移植它们的代码:
| 项目 | 借鉴之处 |
|---|---|
| Fumadocs | 以内容为中心的呈现、信息层级、文件树与参数表一类的写作组件(主题 NOTICE 记录了这条致敬) |
| Nextra | 精炼的文档外壳、代码块的文件名与复制交互、按页布局开关 |
| Hextra | Hugo 原生的实现取向、文件树、徽章、标签页 |
| Mintlify | 结构化导航分层、同步的代码分组、API 参考的阅读体验 |
Hugo 是构建平台,Go 在 Hugo Module 安装方式下负责解析模块。两者都是前提条件,主题不重新分发它们的可执行文件。
引用这些名字用于说明传承、依赖或灵感来源,不表示相关项目为 OINK 背书;各项目与产品名称归其权利人所有。
复用这份文档
CC BY 4.0 允许任何目的的分享与演绎,条件是给出署名、提供许可证链接、说明是否做过修改,并且不得暗示 OINK、PGSTY 或上游项目为改编内容背书。一段合格的署名可以是:
本文改编自 PGSTY 贡献者编写的 OINK 文档,采用 CC BY 4.0 许可,并做了修改。
页面里单独署名的图片或引文,要保留它们各自的署名与许可;删掉页脚不会免除署名义务。
复用这个主题
Apache-2.0 允许按条款使用、修改与分发主题源码及编译产物,条件是保留许可证、版权与归属声明,保留 NOTICE 内容,并在分发修改后的源码时标明改过哪些文件。主题发行包应当包含 LICENSE、NOTICE、VENDOR.json,以及清单引用的全部第三方许可证文件。
Apache-2.0 不授予商标使用权,也不会把第三方资源变成 Apache 许可的作品。
相关
1.4 - 参与 OINK 贡献
欢迎纠错、翻译、报告问题和提交范围明确的 pull request。当前主题版本为 OINK v1.5.1。发布说明介绍双列视图修复、输出正确性、 响应式阅读与无障碍支持。
选择贡献仓库
| 改动 | 仓库 |
|---|---|
| 模板、样式、浏览器运行时、默认配置与主题专项检查器 | pgsty/oink |
| 文档、翻译、示例、集成测试与浏览器回归覆盖 | pgsty/oink.pgsty.com |
| 可选 CLI 命令与原生站点检查 | pgsty/oink-cli |
CLI 拥有独立版本与发布周期,普通 Hugo 站点不需要安装。请在负责相应行为的仓库报告问题。
提供可复现的问题报告
报告中请提供 OINK 版本、hugo version、模块构建使用的 Go 版本、相关配置与最小内容示例。
显示问题还需注明页面、视口宽度、浏览器、视觉预设及明暗模式,说明预期效果与实际表现。
提交报告和复现材料前,移除凭据与私有内容。
构建与检查
维护基线统一为 OINK v1.5.1、Hugo Extended 0.168.0 和 Go 1.27.3; 模块声明的 Go 最低版本为 1.27.3。Hugo 使用内置 LibSass,普通站点构建无需额外安装 Sass 编译器、Node.js 或 npm。
禁止启用 --panicOnWarning 或 HUGO_PANICONWARNING。告警保持可见并接受审阅;
允许出现上游 LibSass 弃用告警。开发与 CI 使用固定 Hugo 基线,不维护旧版本测试矩阵。
开发主题时,把主题与本站克隆到同级目录。阅读各仓库的 AGENTS.md、本站
TRANSLATION.md 及相关设计契约。本站回归测试需要
Node >= 24 和 npm >= 11.16.0:
先运行负责该改动的最小检查器。make check 使用同级主题运行非浏览器套件;
渲染或交互发生变化时使用 make browser。检查受影响中英文页面在桌面、手机及
明暗模式下的效果。仓库 README
同时说明了如何单独验证公开固定版本。
准备 pull request
保持改动范围明确,说明问题、修改后的行为及验证结果。中英文页面同步更新,保留稳定标题 ID。 主题公共行为变化时,一并更新实现、归属检查器与相关契约的两个语言版本。 新设计提案放在双语设计提案专栏。
共享工作树中保留无关改动。只提交源码与审阅过的测试预期;生成文件、缓存、二进制文件及 文件系统模块替换不得进入 Git。历史发布说明和带日期的研究记录应忠实保留当时的证据。
署名与许可
致谢页将社区修复与作者和 pull request 关联起来。1.3.1 发布说明记录了近期阅读体验修复; 1.5.1 继续保留这些修复,以及 1.3.2 恢复的仅依赖 Hugo 的构建方式。
主题代码与站点工具使用 Apache-2.0,原创文档使用 CC BY 4.0。请保留第三方许可证和 NOTICE 文件,具体范围见开源许可与致谢。
2 - 快速上手
新建站点推荐从公开的 GitHub 模板
pgsty/oink-starter 开始。该模板固定了已发布的 OINK 版本,开箱即可构建,仅包含干净的示例内容与部署 workflow,不夹带本站的回归测试套件。
OINK 站点使用 Git、Go 1.27.3 或更高版本,以及 Hugo Extended 0.168.0 或更高版本构建(使用 Hugo 内置 Sass 编译器,无需安装 Node.js、npm 或外部 Sass)。
选择起点
| 当前情况 | 推荐路径 | 得到什么 |
|---|---|---|
| 新建文档站或项目站 | OINK Starter | 一套精简的三语 Docs、Blog、Book 站点与两条部署 workflow |
| 已有 Hugo 站点 | 从零安装 | 不替换内容,只补 OINK 模块与 Goldmark 前置配置 |
| 已有 Docsy 或旧版 OINK 站点 | 版本升级 | 保留内容,迁移受支持的语法,并审查站点覆盖 |
五分钟建立基线
-
安装工具
安装 Git、Go 1.27.3 或更新版本,以及 Hugo Extended 0.168.0 或更新版本。Hugo 输出必须包含
extended:macOS 可以执行
brew install git go hugo。Linux 与 Windows 请按官方 Hugo 安装指南和 Go 下载页安装,并确认选择 Hugo Extended。 -
创建或克隆站点
准备长期维护时,请打开 Starter 仓库并点击 Use this template,然后克隆 GitHub 为你创建的新仓库。只想在本机评估原始模板时执行:
-
打开基线
打开 http://localhost:1313/。默认 Starter 还在
/zh/发布中文,在/fr/发布法语。开始修改前,先确认 Docs、Blog、Book、本地搜索、语言切换与深浅色 模式都能工作。 -
完成一个可见修改
修改
hugo.yaml顶部的站名与规范 URL,再修改data/home/en.yaml中的一句话。 浏览器刷新后能同时看到两处变化,才算证明配置、内容与固定版本的主题已经正确连通。
由浅入深地定制
- 使用 OINK Starter — 先选语言,再依次处理身份、首页、 内容、导航、品牌、集成与部署。
- Starter 仓库导览 — 每个文件负责什么,哪些要替换, 哪些可以删除。
- 编写页面 — front matter、标题、链接、图片、草稿与页尾控件。
- 组件总览 — 内容树稳定后,再增加表达能力。
- 品牌外观 — Logo、强调色、字体、页宽与 CSS 扩展点。
- 发布上线 — 使用内置 GitHub Pages 或 Cloudflare Pages workflow,再验证真实公开路由。
先跑通基础构建与内容树,再逐层修改语言、导航、CSS、统计与托管配置,遇到问题时更容易排查。
发布门禁
首次推送前,先在本地执行与 Starter workflow 一致的生产构建:
确认命令正常退出并输出 Total in …,且 public/ 下已生成各语言根目录以及代表性的 Docs、Blog、Book 页面。构建时请保持告警可见以便审阅,请勿启用 --panicOnWarning 或 HUGO_PANICONWARNING(Hugo 内置 LibSass 的弃用提示属预期现象)。推送后,再进一步核对 CI workflow 状态与线上公开地址的渲染结果。
用 AI agent 建站
OINK 发布了一个 agent 技能 Oink 建站(oink-site):一个 Markdown 文件,汇集了建站流程、OINK 自家站点的审阅标准,以及案例库中的经验。语法以本文档为准,并要求 agent 在浏览器中验证自己的工作。
保存为技能后,开启一个新的 agent 会话:
然后描述你想要的站点,例如“用 oink-site 技能为这个项目搭建一个中英双语文档站”。没有技能目录的 agent,把地址交给它即可直接按文件执行。想要英文版,把地址换成 skill.md。重新下载即可更新;文件中写明了它对应的 OINK 版本。
下一步
继续阅读完整 Starter 教程。如果模板有你不需要的结构, 按仓库导览安全删减。只有在给既有站点接入 OINK,或者 明确想亲手组装每个文件时,才走从零建站路径。
2.1 - 使用 OINK Starter
pgsty/oink-starter 是新建 OINK
站点的官方模板。它保持精简干净,不夹带 oink.pgsty.com 的主题文档、统计账号、评论仓库、浏览器回归套件或 PGSTY 品牌资产。
你只需准备 Git、Go 1.27.3+ 以及 Hugo Extended 0.168.0+(内置 Sass 编译器,无需安装 Node.js 或外部 Sass)。
模板包含什么
| 内容区 | 内置基线 | 第一个决定 |
|---|---|---|
| 语言 | 英语、简体中文、法语 | 保留三语,或选择内置单语 / 双语 profile |
| 内容 | Docs、Blog 与一本简短 Book 教程 | 重写示例;确认整个内容区不需要时才整棵删除 |
| 首页 | 每种语言一份精简 data/home/<lang>.yaml |
替换项目承诺与入口 |
| 品牌 | 中性 Logo 与 favicon | 有正式项目图形之前先保留 |
| 集成 | 仓库、Giscus、分析、分享、反馈示例均被注释 | 只启用你准备长期运营的完整配置 |
| 部署 | GitHub Pages 与 Cloudflare Pages Direct Upload workflow | 选择一条生产路径并验证真实 URL |
Starter 自己的 /book/ 是一份从预览到部署的四章短教程。本页是维护者级版本:
说明修改顺序、各层边界,以及每层之后应执行的检查。
创建自己的仓库
推荐使用 GitHub 模板
打开 Starter 仓库,点击 Use this template → Create a new repository,再克隆 GitHub 在你的账号或组织下 创建的仓库:
这样站点从一开始就有自己的 Git 历史,原始 Starter 只是上游参考,不会成为一个 可能误推送的 remote。
克隆原始仓库进行评估
只做一次性本地评估时执行:
真实项目不要从删除这个 clone 的 .git 目录开始。GitHub 模板操作已经创建了清晰的
项目边界,并保留可审计的初始提交。
修改前先预览
依次打开:
/、/zh/、/fr/:三个首页;/docs/、/blog/、/book/:三种内容区;- 任意一组译文,再操作语言切换器;
- 本地搜索、深浅色切换,以及一个窄屏视口。
同时记录实际解析的模块:
先检查所用 Starter 快照在 go.mod 中固定的版本。要使用当前发布版本,请在定制内容前更新到 github.com/pgsty/[email protected]:
分层定制
第一层:语言配置
根配置默认启用英语、中文和法语。如果这不是目标语言组合,请在其它配置修改之前 选择内置 profile。以下两条命令二选一:
这两份是完整的最小配置,不是可以叠加的片段;复制会覆盖根文件里那些被注释的集成
示例。因此应在最开始做;hugo.yaml 已有项目修改时,只合并 languages 与
disableLanguages,不要整文件覆盖。
如果已按快速上手修改站名与 URL,不必复制整份 profile。默认三语改为英中双语时,
只需在现有 hugo.yaml 顶层加入 disableLanguages: [fr];仅英文则使用 [zh, fr]。
这样可以保留已完成的身份与集成配置。
未启用语言仍保留声明,让 Hugo 能识别 .zh.md 与 .fr.md 是译文并安全忽略。
要永久移除一种语言,先确认所选 profile 能构建,再删除对应内容与首页数据。
第二层:站点身份
修改 hugo.yaml 顶部标有 CHANGE ME 的两个值:
标题的 YAML 锚点会把站名带进所有已启用语言。接着修改版权人,并在新仓库已存在后 取消仓库链接的注释:
重新运行 hugo server,检查浏览器标题、页脚、编辑 / 历史链接与 canonical URL。
项目图形尚未定稿时先不要改 Logo;文字身份更容易先完成评审。
第三层:首页
首页内容保存在 data/home/ 下的数据文件中:
先修改一种语言。每个文件中的 sections 决定区块顺序,hero、cards、cta 提供具体内容。保留原有结构,替换标语、目标 URL 与示例卡片文案;待第一种语言定稿后,再同步翻译到其他已启用语言。
需要更多区块类型时,请参考首页与落地页中的完整区块列表;主题直接读取数据文件渲染首页,无需覆盖模板。
第四层:内容与导航
重写或删除 content/ 下的示例叶子页面。确定整个内容区不属于你的项目之前,先保留
栏目根:
内容树就是侧栏。顶部导航写在各语言 _index 根页的 menus.main 里,因此给 Docs、
Blog 或 Book 改名时,修改发生在它所描述的内容旁边,而不是另一棵全局菜单树。译文
并排放置,对应标题使用相同的显式 ID:
新增自定义导航数据之前,先读组织内容;大多数站点使用生成树 已经足够。
第五层:品牌与阅读功能
正式图形准备好后,替换 assets/icons/logo.svg 与 static/favicon.svg。随后一次只启用
一组最小而有用的配置:
自定义本地字体时,用 params.ui.fonts 写字体族,或者在站点 CSS 中声明字体文件。
布局、侧栏、搜索与组件配置应查询配置总览,不要复制
oink.pgsty.com 那份大得多的站点配置。
第六层:外部集成
Starter 默认关闭或注释了仓库操作、Giscus、Google Analytics、反馈与分享。只有 必需事实全部明确时才启用:
- 仓库链接需要真实 owner、repository 与 branch;
- Giscus 需要仓库 / 分类名称和不可变 ID;
- Google Analytics 需要项目自己的 measurement ID;
- 反馈只有在分析存在时才记录结构化
gtag事件; - 助手链接会把当前 URL 发送给第三方,因此必须做显式策略选择。
不完整的可选块应继续保持注释。各集成的运营边界见启用评论、 分析与 SEO和仓库与页面信息。
构建与部署
本地生产构建
启用托管 workflow 前执行:
构建时请保持告警可见以便审阅,请勿启用 --panicOnWarning 或 HUGO_PANICONWARNING(上游 LibSass 弃用提示属预期现象)。提交 hugo.yaml、go.mod 与 go.sum;不要提交生成的 public/、resources/、模块
缓存或本地模块替换。
GitHub Pages
Starter 已包含 .github/workflows/github-pages.yaml。在
Settings → Pages 中选择 GitHub Actions 作为 Source。推送到 main 后,
workflow 使用固定工具链构建,向 GitHub 查询正确的项目子路径,再通过 Pages 部署
API 发布 public/。
Cloudflare Pages
内置 .github/workflows/cloudflare-pages.yaml 使用 Direct Upload。创建 Pages
Direct Upload 项目,添加 CLOUDFLARE_ACCOUNT_ID 与 CLOUDFLARE_API_TOKEN,再手动
运行一次 workflow。设置仓库变量 CLOUDFLARE_PAGES_ENABLED=true 后才会自动部署;
规范地址不是默认 pages.dev 域名时,再设置 CLOUDFLARE_SITE_URL。
同一个项目只选 Direct Upload 或 Cloudflare Git integration 其中一种。完整托管对比
与 baseURL 规则见发布上线。
验证并删除示例
宣布站点完成前:
- 搜索
Project Name、example.org、OWNER、PROJECT等占位符,逐项确认剩余位置 是否有意保留。 - 在桌面与移动端打开每种已启用语言的根,以及代表性的 Docs、Blog、Book 页面。
- 确认语言切换落到对页,而不是首页。
- 验证搜索、深色模式、一个组件、Markdown 输出、打印、404、canonical URL 与仓库操作。
- 把部署 workflow 和公开 URL 与本地构建分开检查。
删除示例 Book 或 Blog 之前,要同时移除对应顶部菜单根,以及首页上指向它的卡片。每整棵 删除一个内容区就严格重建一次,才能让失败归因到单一改动。
下一步
用 Starter 仓库导览查询文件职责,再继续阅读 编写页面与配置总览。已有站点不应 继承 Starter 内容模型时,改走从零建站路径。
2.2 - Starter 仓库导览
本页梳理从 pgsty/oink-starter
初始化后的仓库目录结构。go.mod 以 Hugo Module 方式固定 OINK 主题版本,Hugo 会将解析后的主题源码保存在 Go 模块缓存中,不会复制进站点仓库。
顶层地图
oink-starter/
- oink-starter/
- hugo.yaml身份、语言、输出、参数与模块导入
- go.mod站点模块与精确 OINK 版本
- go.sum模块校验和
- examples/
- hugo.single.yaml仅英文的完整 profile
- hugo.bilingual.yaml英文 + 中文的完整 profile
- data/
- home/
- en.yaml每种语言一份精简落地页
- zh.yaml
- fr.yaml
- home/
- content/
- _index.md各语言首页根
- _index.zh.md
- _index.fr.md
- docs/简介、快速上手、教程、参考
- blog/文章、设计记录、发布说明
- book/介绍 Starter 的连续教程
- assets/
- icons/logo.svg经 Hugo 处理的项目 Logo
- static/
- favicon.svg原样复制到站点根
- i18n/
- fr.yamlStarter 自有法语界面覆盖
- .github/workflows/
- github-pages.yaml生产构建与 GitHub Pages 部署
- cloudflare-pages.yaml生产构建与 Cloudflare Direct Upload
- README.md面向仓库维护者的操作摘要
- LICENSE模板源码许可证
生成的 public/、resources/、.hugo_build.lock 与模块缓存是被忽略的构建状态,
不是源码。
最先修改什么
| 路径 | 职责 | 第一次操作 |
|---|---|---|
hugo.yaml |
身份、规范 URL、语言、输出、主题功能、可选集成 | 修改两个标记值;其它修改前先选择语言 profile |
data/home/ |
首页承诺、卡片与行动入口 | 一种语言确认后,再重写所有已启用语言 |
content/ |
全部读者可见内容 | 替换示例叶子;确认整个内容区不要时才删除栏目根 |
assets/icons/logo.svg |
经处理的 Logo | 有正式图形后再替换 |
static/favicon.svg |
浏览器图标 | 与 Logo 一起评审后替换 |
hugo.yaml 中的 params.github_* |
编辑、历史、新建页面与 issue 链接 | 目标仓库已存在后才取消注释 |
哪些必须保留
go.mod与go.sum:两者共同固定并校验模板选定的 OINK 版本,都要提交;这条基线与后续升级分开记录。hugo.yaml中三项 Goldmark 设置:原生 Steps、Cards、Fields、图片属性与 Book 目标都依赖它们。outputs:删除markdown、LLMS或print,会有意删除对应的 Markdown、 Agent 索引或打印内容区。- workflow 中的
fetch-depth: 0:保留enableGitInfo时,最后修改与贡献者事实需要 完整 Git 历史。 - CI 中的
GOWORK: off与HUGO_MODULE_WORKSPACE: off:开发者本地 workspace 不得 替换 CI 正在验证的公开版本。
可选内容区
Docs、Blog 与 Book 是彼此独立的顶层内容区。安全删除其中一个的顺序是:
- 删除对应的
content/<surface>/内容树; - 删除首页指向它的卡片或链接;
- 确认其它页面不再链接它;
- 生产构建,并检查剩余顶部导航。
不要只删除某种语言的栏目根:那会形成难以区分「有意不对称」与「漏译」的语言专属导航 和回退行为。要么在所有已启用语言中删除整个内容区,要么明确记录这种不对称。
完成语言选择后,examples/ 下两个配置 profile 可以删除,也可以作为参考保留;真正
生效的站点配置只有根目录 hugo.yaml。
内容与导航
Docs 与 Book 下的目录结构和 weight 共同形成侧栏与翻页顺序。顶部导航来自栏目根的
menus.main。译文根重复相同的 identifier、parent 与 weight,只翻译可见标签。
Starter 刻意演示 Documentation System 内容模型:
- 简介回答是什么、为什么;
- 快速上手帮助新用户得到结果;
- 教程带领读者完成端到端任务;
- 参考记录精确的受支持行为。
可以按项目需要改名或重组,但应保留不同学习路径之间的分工,不要把所有答案混进一棵树。
语言模型
英文源码以 .md 结尾,中文和法语对页分别以 .zh.md、.fr.md 结尾。首页数据按
data/home/ 下的语言键分文件。根 profile 声明语言、locale、顺序与站点描述。
单语与双语 profile 仍声明被禁用的语言,这是有意设计:Hugo 会把未使用后缀识别为 译文,而不会把多个文件渲染到同一个英文 URL。只在项目配置开始前复制 profile;之后 应手工合并。
OINK 在哪里
两个文件建立模块边界:
此示例固定当前发布版本 v1.5.1。请检查所用 Starter 快照的固定版本;若版本较早,按
升级指南更新。
hugo mod graph 显示实际解析的版本。生产构建使用 go.mod 中固定的标签;联调主题时可通过环境变量 HUGO_MODULE_REPLACEMENTS 临时指向本地目录,切勿将该覆盖配置提交进 Git。
部署文件
GitHub Pages workflow 在推送 main 后自动运行;仓库设置必须选择 GitHub Actions
作为 Pages Source。Cloudflare workflow 默认手动运行,只有仓库变量
CLOUDFLARE_PAGES_ENABLED=true 存在时才自动执行;所需账号 ID 与 API token 始终
保存在仓库 secrets 中。
只保留实际运营的部署路径。Cloudflare Direct Upload 与 Cloudflare Git integration 是同一个项目的两种所有权模型,不是应当同时运行的两道关卡。
安全的定制顺序
- 证明未修改的预览可用。
- 先选择语言,再修改身份。
- 替换一种首页,再补齐译文。
- 替换内容并验证导航。
- 品牌与阅读功能一次只改一组。
- 启用完整的外部集成。
- 执行生产构建。
- 部署,再独立验证生产环境。
仓库已经属于自己后,每层之间做一次提交。小边界能让后续回归与回滚明确归因到一个决定。
验证
模块图应显示固定发布,构建成功并完成告警审阅,上游 LibSass 弃用告警允许出现。
Git 状态只包含源码修改而没有 public/ 或缓存。之后打开所有已启用语言的根,以及代表性的 Docs、Blog、Book 路由,再进入部署。
相关
- 使用 OINK Starter — 完整分层流程
- 从零建站 — 不采用这套内容模型,只接入 OINK
- 组织内容 — 侧栏、翻页与菜单权威
- 配置总览 — 当前全部站点参数
- 发布上线 — 托管商配置与生产检查
2.3 - 从零建站与其它安装方式
这是推荐路径 OINK Starter 的手工替代方案。本页从空目录
搭建一个最小 OINK 站点:一份精简 hugo.yml 加一条 hugo mod get,得到一个可预览
的单语站点。代价是首页、示例内容、部署 workflow 与每种组件用法都要自己组装。
已有 Hugo 站点时,按下方接入现有站点操作;已有 Docsy 站点见版本升级。
后半部分对比四种安装方式:Hugo Module、Git submodule、离线归档、固定版本源码副本。 当前开发与构建统一使用 Go 1.27.3 与 Hugo Extended 0.168.0(内置 Sass 编译器,无需外部 Sass)。
接入现有站点
在保留现有配置与内容的分支中操作。跳过 hugo new site,继续使用原配置文件名。
- 只有站点没有
go.mod时,才用自己的仓库模块路径执行hugo mod init;已有模块声明保持不变。 - 执行
hugo mod get github.com/pgsty/[email protected]。 - 用下方的 OINK
module.imports替换旧主题引用,保留无关导入与配置。合并示例中的三项markup.goldmark设置与markup.highlight.noClasses: false,不要整份覆盖原配置。 - 检查站点自有
layouts/、资源、旧主题短代码,以及页面的type/layout:这些覆盖和约定可能仍然选择旧主题行为。保留内容,只做必要适配。 - 执行
hugo,再用hugo server打开一篇已有的代表性页面。先核对导航、图片与代码块,再启用可选 OINK 功能。最后按验证完成检查。
从空目录到第一页
-
建骨架并获取主题
hugo mod init后面跟的是你自己站点的模块路径,通常就是仓库地址。hugo mod get会写出go.mod与go.sum,两个都要提交。构建前创建
.gitignore,避免把生成文件加入 Git。完成首个提交前,保持enableGitInfo关闭:.gitignore最新版本号可在 GitHub Releases 查看;本页显示的
v1.5.1是本站当前固定的版本。生产站点应在go.mod中固定明确的发布标签,避免直接跟踪main分支。 -
写
hugo.yml仅对这个新站:把生成的
hugo.yaml改名为hugo.yml(Hugo 两者都接受),再用下面内容替换。已有站点应合并所需配置,不要整份覆盖:hugo.yml五段分别管什么:
段 管什么 少了会怎样 顶层 + languages站名、域名、语言与顶栏菜单 baseURL不对,线上所有绝对链接指错markup.goldmark三项组件前置 属性行变成正文里的一行 {.steps}params搜索、仓库链接、外壳开关 交互功能默认关闭,主题不替站点决定 outputs每页的 .md、llms.txt、打印页页面菜单里没有「复制 Markdown」,也没有打印视图 module引用主题、声明 Hugo 下限 构建时找不到主题 -
写第一页
content/下的每个一级目录是一个分区,目录结构就是侧栏结构。文档分区至少要有一个_index.md:content/docs/_index.mdcontent/docs/install.md标题写显式
{#id}:后续加译文时两种语言的锚点才能对应。页面写法见编写页面。 -
预览
打开 http://localhost:1313/docs/,Docs 分区中应列出 Install。添加首页内容之前,根地址的首页仍为空。修改 Install 页面,确认预览随之更新。
其它安装方式
上面用的是 Hugo Module。另外三种方式面向特定约束:网络隔离、平台要求构建输入包含完整主题树、组织内部需要评审主题副本。除 hugo mod vendor 之外,它们都不建立 Go 模块,站点用 theme: oink 而不是 module.imports 引用主题;共同的代价是版本解析与完整性校验由你自己负责。
Hugo Module(推荐)
唯一能让 Hugo 自己解析版本、校验 checksum、并在 go.sum 里留下审计记录的方式。hugo mod graph 看实际解析结果,hugo mod get -u 升级。需要本机有 Go。
Git submodule
在站点仓库里记录准确的主题 commit:
CI 必须在运行 Hugo 之前初始化 submodule,否则 themes/oink 是空目录:
离线归档
网络隔离环境使用。两条路径,都先在联网机器上准备,再整体搬入。
用 hugo mod vendor:把已解析的主题源码固化进站点目录,之后构建既不联网也不需要 Go。
_vendor/ 存在时 Hugo 优先使用它(hugo mod graph 输出 +vendor),hugo.yml 里的 module.imports 保持不变。这一步需要 Go,之后的构建不需要。升级主题要回到联网环境重新执行 hugo mod get 与 hugo mod vendor。
_vendor/ 只收主题挂载出来的目录(assets data i18n layouts static)以及 hugo.yaml 与 theme.toml,不含 LICENSE、NOTICE 与 VENDOR.json。要对外分发这份归档,把这三个文件从主题仓库一并取来。
用 tag 源码归档:不建 Go 模块,直接把某个版本的主题解压到 themes/oink/。
主题仓库的根目录就是模块根目录,解压出来直接是 layouts/、assets/、i18n/、static/ 这一层,不需要再进入下一级。重新分发时必须保留 LICENSE、NOTICE 与 VENDOR.json。最后一个记录了每个第三方运行时的版本、来源、许可证路径与 SHA-256,是离线审计的依据。
跨机器传输时,在联网侧从不可变标签生成归档与校验值:
把归档与 .sha256 一起传入隔离环境,先校验再解压:
离线构建前,请确认以下 11 项顶层文件与目录完整无缺:
themes/oink/
- oink/
- go.mod模块路径声明,Hugo Module 方式解析用
- hugo.yaml主题默认参数与 Hugo 版本下限
- theme.toml主题元数据,theme: oink 方式需要
- LICENSEApache-2.0
- NOTICE上游署名,再分发时必须保留
- VENDOR.json第三方运行时清单:版本、来源、许可证路径、SHA-256
- assets/SCSS、JS 与随主题分发的第三方运行时
- layouts/模板、partial、shortcode、render hook
- static/字体文件,原样发布
- i18n/32 份界面语言文件
- data/页尾出处行用的 SPDX 许可证表
固定版本源码副本
托管平台要求站点仓库包含主题文件时,按上方tag 归档步骤准备并解压到
themes/oink/。配置 theme: oink,把解压后的文件连同已验证的标签与校验值记录一起提交。
直接 git clone ... themes/oink 会保留嵌套 .git 目录,加入父仓库时记录的是 Git 引用,
而非主题文件,因此不能得到这里所需的完整源码副本。希望用 Git 引用跟踪主题时,应使用 submodule。
四种方式对比
| 方式 | 需要 Go | 版本可审计 | 主题源码进你的仓库 | 适用 |
|---|---|---|---|---|
| Hugo Module | 是 | go.sum 自动校验 |
否 | 默认推荐 |
| Git submodule | 否 | 仓库记录 commit | 以引用形式 | 需要主题源码在库内 |
| 离线归档 | 否 | 手工核对 checksum | 是 | 网络隔离 |
| 固定版本源码副本 | 否 | 记录标签与校验值 | 是 | 平台要求完整树 |
Bootstrap、Font Awesome、字体、搜索与图表运行时全部随主题分发。站点不需要 node_modules、PostCSS、RTLCSS,也不需要 CDN。为 Docsy 站点安装 npm 依赖的教程属于上游 Docsy 的流程,不适用于 OINK。
用本地主题 checkout 开发
同时修改主题与站点时才需要这一节。把两个仓库克隆为同级目录:
用环境变量 HUGO_MODULE_REPLACEMENTS 把模块临时替换为本地 checkout,go.mod 不变:
文档站仓库的 Makefile 就是这几条命令的别名,make dev 与 make check 要求主题 checkout 在同级目录 ../oink:
Go workspace(go work init + HUGO_MODULE_WORKSPACE=go.work)是等价的另一种做法。两种做法都只作用于本机:CI 与生产构建用的是 go.mod 里的版本,go.work 不要提交。
验证
构建以 Total in … 结束且无意外 WARN / ERROR 即通过(上游 LibSass 弃用提示属预期现象;请保持告警可见并审阅,请勿启用 --panicOnWarning 或 HUGO_PANICONWARNING)。再确认:
/docs/打得开,侧栏里有你写的页面- 顶栏有搜索框,搜得到刚写的标题
- 深浅色切换按钮在,切换后代码块配色跟着变(说明
markup.highlight.noClasses: false生效) git status --short只列出源码修改,生成产物已被忽略。Module 方式提交go.mod与go.sum;其它安装方式保留各自的主题源码或 submodule 记录。
相关
- 快速上手 — 在 Starter、既有 Hugo 站点与迁移之间选择
- OINK Starter — 推荐的新站点路径
- Starter 仓库导览 — 模板各目录的职责
- 配置总览 —
hugo.yml每个键的含义与默认值 - 编写页面 — 第一页之后怎么继续写
- 版本升级 — 升级主题模块、从 Docsy 迁移
3 - 创作内容
本栏覆盖 OINK 支持的全部内容类型:文档页、博客文章、书籍、Expo 图片相册、发布下载页与 OpenAPI 参考。它们共用同一套 Markdown 与 front matter,各自在此基础上增加专属排版约定。
一页文档的构成
每页文档都是一个独立的 Markdown 文件。文件开头两行 --- 之间是 YAML front matter,用于声明页面元数据(title、linkTitle、description、weight),下方正文直接使用标准 Markdown 与 OINK 原生组件:
存为 content/docs/install.zh.md,运行 hugo server 后页面出现在 /zh/docs/install/,左侧栏会出现「安装」条目。
常用排版速查
日常最常用的组件均基于原生 Markdown 与 Goldmark 属性块,无需记忆复杂的短代码:
| 常用能力 | 写法示例 | 详细指南 |
|---|---|---|
| 显式标题锚点 | ## 前提条件 {#prerequisites} |
编写页面 |
| 提示框 / 折叠块 | > [!NOTE](及 TIP、IMPORTANT、WARNING、CAUTION、DETAILS) |
提示块 |
| 代码标签页 | 相邻代码围栏带 ```bash {tab="macOS"} |
标签页 |
| 步骤列表 | 有序列表紧跟一行 {.steps} |
步骤列表 |
| 导航卡片 | 链接无序列表紧跟一行 {.cards} |
卡片网格 |
| 参数字典表 | Markdown 表格紧跟一行 {.fields} |
参数表 |
| 带图注或尺寸的图片 |  紧跟一行 {width="800" caption="..."} |
图片 |
| 目录文件树 | ```filetree 代码围栏内写缩进列表 |
文件树 |
内容类型与对应页面
3.1 - 编写页面
本页覆盖一页文档的完整写法:文件位置、front matter、标题锚点、链接、图片、草稿与页尾。前提是站点已能本地构建,尚未搭起时先看十分钟上手。
新建一页
页面是 content/ 下的 Markdown 文件,URL 由它在 content/ 里的位置决定:content/docs/install.md 发布为 /docs/install/。中文译文是同目录下的 .zh.md 同名文件,与英文页共享同一条逻辑路径。
没有附带资源的页面写成单个文件。页面带图片、cast、示例配置这类资源时改成一个目录,页面本身命名为 index.md,资源与它同放,这是 Hugo 的页面包(page bundle):
content/ 里的两种页面形态
- content/
- docs/
- _index.md栏目首页,英文
- _index.zh.md栏目首页,中文
- install.md单文件页面 → /docs/install/
- install.zh.md它的中文译文
- anatomy/页面包 → /docs/anatomy/
- index.md
- index.zh.md
- shell.webp页面资源,两种语言共用
- docs/
hugo new content docs/install.md 用 archetype 生成一个带 front matter 的空文件,见 Hugo 文档;手写文件同样可行。
中文页没有英文对等页时,Hugo 不会把无语言后缀的资源分给它。这种情况下资源文件名要带 .zh.(shell.zh.webp),正文里仍然写 shell.webp。
必要的 front matter
文件开头两行 --- 之间是 YAML front matter。四个键每页都应写上:
description 用一句话说清这页让读者做成什么。它出现在栏目首页的卡片、搜索结果与社交卡片中。weight 决定侧栏顺序,weight 相同时才退回字母序。
其余的键可选:图标、草稿、搜索权重、评论开关、页面外壳等,全表见页面参数。
标题层级与稳定锚点
正文用 ## 开始分节,# 留给 title。主题已渲染页面大标题,正文里再写一个 # 会出现两个一级标题。右栏的页面目录从 ## 开始收,收到第几级由 Hugo 的 markup.tableOfContents 决定,本站是 ####。
每个 ## 与 ### 都要手写英文锚点 {#id}:
理由有两条:
- 中英对齐。Hugo 从标题文字生成 ID,中文标题生成中文 ID:
/docs/install/#prerequisites与/zh/docs/install/#前提条件指向同一个语义位置,却是两个锚点,翻译审计无法比对。译文标题写上英文页的 ID,两边即同一个片段。 - 链接稳定。标题文字会随措辞调整而改变,公开链接不应随之失效。显式 ID 一旦发布即视为公开路由;需要改名时保留旧 ID 的空锚点:
ID 用短横线小写英文,全页唯一。本站的翻译审计脚本会比对英文页与中文页渲染出的标题 ID,不一致就报错。
链接写法
三种写法,用途不同:
| 写法 | 例子 | 什么时候用 |
|---|---|---|
| 站内绝对路径 | [配置总览](/zh/docs/customize/config/) |
默认写法。指向已发布的路由,便于审计与全站替换,不受源码文件移动影响 |
| 相对路径 | [另一页](../organize/)、 |
同一页面包内的资源,或有意跟着源码目录走的相邻页面 |
ref / relref shortcode |
[配置总览]({{</* ref "/docs/configure/overview" */>}}) |
需要构建期校验目标存在时;目标缺失时构建失败,不会留下死链 |
三种写法都带尾部斜杠,指向目录形式的路由(/zh/docs/write/pages/),与 Hugo 的默认永久链接一致。
主题没有链接渲染钩子,链接原样交给 Goldmark:外链不会自动加 target="_blank",需要新标签页时写成 HTML,或在站点自己的 layouts/_markup/render-link.html 里处理。
普通 Markdown 链接不做存在性检查。因此:
- 站内链接优先写绝对路径,改结构后用
grep全站替换; - 移动页面时给旧路径加
aliases,同时把站内链接改到新路由,不要让 alias 长期承担导航; - 拿不准的目标用
ref,让构建替你检查。
双语页面链接到逻辑页面(/zh/docs/write/pages/),不要链接 .zh.md 文件名;片段 ID 保持语言中立。
图片位置
页面自己的截图放页面包,多页共用的图放 assets/images/,不需要处理的大文件放 static/。三处在源码里都写成 ,属性行控制图注、尺寸、缩放与编号,见图片。
草稿与发布
draft: true 的页面不会进入构建产物:
预览时用 hugo server -D 显示草稿(-D 即 --buildDrafts)。date 写在未来的页面同样被排除,用 -F 显示。生产构建不加这两个开关,hugo 默认只发布已定稿的内容。
OINK 的 Markdown 扩展一览
正文是标准 Markdown(Goldmark),加上下面这些原生形态。它们都是普通 Markdown 语法加一行属性,在 GitHub 上按源码阅读同样可读:
| 组件 | 最短语法 | 页面 |
|---|---|---|
| 提示块 | 块引用首行写 > [!NOTE] |
提示块 |
| 标签页 | 相邻的两个围栏各加 {tab="Homebrew"} |
标签页 |
| 步骤 | 有序列表后面跟一行 {.steps} |
步骤 |
| 卡片 | 链接列表后面跟一行 {.cards} |
卡片 |
| 参数表 | 表格后面跟一行 {.fields meta="type default"} |
参数表 |
| 表格增强 | 表格后面跟一行 {.matrix}、{caption="…"} |
表格 |
| 代码块 | 围栏信息行写 {title="hugo.yml" copy=false} |
代码块 |
| 图片 | 独立成段的图片后面跟一行 {caption="…" width="600"} |
图片 |
| 文件树 | filetree 围栏,每行一个 - 名字/ # 注释 |
文件树 |
| 公式 | math 围栏,或用 $$ 包住的块级公式 |
公式 |
| 图表 | mermaid 围栏(还有 plantuml、markmap、echarts) |
Mermaid |
剩下的少数组件(徽章、按键、引用文件、终端录像、Book 的图表式例)用 shortcode,语法与参数见组件总览。
组合例子:步骤里放代码围栏与提示块。
- 安装 Hugo Extended,最低 0.168.0:
- 克隆 OINK Starter 并预览:
提示
加
-D连草稿一起预览。
页尾的自动内容
页面末尾的四块内容由主题按固定顺序生成,不必在正文里写:
| 位置 | 是什么 | 默认 | 怎么改 |
|---|---|---|---|
| 1 | 反馈:「这页有帮助吗」两个按钮 | 关 | 仓库与页面信息 |
| 2 | 最后修改:时间加最近一次提交的标题,链到 GitHub | 有 Git 信息时开 | 仓库与页面信息 |
| 3 | 翻页器:上一页 / 下一页,顺序与侧栏树一致 | docs / book / blog 开 | 导航与菜单 |
| 4 | 评论:giscus | 配置完整且开启时 | 启用评论 |
标题旁边的操作菜单(复制 Markdown、编辑本页、查看历史、提 issue、打印)也是自动的,同样在仓库与页面信息里配置。
单页关闭其中某一块用 front matter:feedback: false、annotation: false、pager: false、comments: false。键的含义见页面参数。
验证
写完一页,运行一次生产构建:
- 构建应以
Total in …结束且无 ERROR。属性行包含非法键或组件参数无效时,主题会输出带源码位置的警告并使用安全回退;若ref指向不存在的目标,Hugo 会直接报错。 --printPathWarnings会提示两个页面指向同一输出路径的冲突(多见于多语言站点或修改permalinks之后)。
在浏览器里确认三项:
- 侧栏里出现了这一页,位置符合
weight; - 右栏目录列出了你写的
##,点击后 URL 里的锚点是英文; - 中英两个版本的同名标题锚点一致(本站有
node scripts/check-doc-translations.mjs --public public做这项审计)。
相关
3.2 - 组织内容
_index.md 与 weight、栏目首页样式、图标与折叠、隐藏页面、把文档放在任意路径。OINK 不需要单独配置导航:content/ 下的目录结构就是侧栏树。本页覆盖目录与文件的摆放、栏目首页、排序、图标、折叠、隐藏,以及多根侧栏。
目录就是侧栏
一个目录是一个栏目(Hugo 称 section),目录里的 Markdown 文件是它的页面,嵌套目录是它的子栏目。侧栏按这棵树逐层渲染,顺序由 weight 决定,标签取 linkTitle,缺省时取 title。左侧这棵树的源码如下:
content/docs/ 的前两层
- content/
- docs/
- _index.zh.md栏目根:type: docs + cascade
- about/简介
- _index.zh.md
- features.zh.md
- start/快速上手
- _index.zh.md
- write/创作内容(本栏目)
- _index.zh.mdweight: 30
- pages.zh.mdweight: 10
- organize.zh.mdweight: 20
- frontmatter.zh.mdweight: 30
- components/组件
- _index.zh.md
- docs/
每个目录都要有 _index.md
栏目首页是目录里的 _index.md(中文为 _index.zh.md)。缺少它时 Hugo 仍会生成栏目,但没有标题、描述、图标与 weight:侧栏那一行显示目录名,排序不受控制。
栏目 _index.md 另有一项专属能力:用 cascade 把共享设置一次下推给整棵子树,不必每页重复。
排序:weight 用 10 的倍数
同一栏目里的页面按 weight 升序排列,weight 相同时才退回日期与 linkTitle 字母序。一律用 10 的倍数(10、20、30),此后往中间插页不必改动其它页。栏目自身的 weight 决定它在父级里的位置。
没写 weight 的页面视为 0,Hugo 把它们排在所有写了 weight 的页面之后,彼此按日期与标题排列。这个顺序会随内容改动漂移,因此每页都写上 weight。
单文件还是页面包
没有自身资源的页面用单文件 slug.md;带图片、cast、示例文件的页面改成目录加 index.md,资源与它同放。两种形态在侧栏里没有区别,URL 也相同。详见编写页面。
栏目首页显示子页列表还是卡片
_index.md 的正文之后,主题自动接上子页索引,两种样式:
list 是主题默认,每个子页一行标题加描述;cards 是链接卡片网格,读取子页的 icon、linkTitle 与 description。本站用 cards,本栏目首页即是例子。单个栏目需要另一种样式时在它的 front matter 里覆盖:
两个页面级开关不受样式影响:simple_list: true 渲染紧凑的项目符号列表,no_list: true 不生成索引,用于正文自行手写导航的场合。
卡片样式下 description 即卡片正文。描述控制在一句话、单行可显示。
侧栏图标
在页面或栏目的 front matter 里写一对 Font Awesome class:
图标密度是站点级策略,用于避免叶子页全部带图标:
| 取值 | 效果 |
|---|---|
all |
每个写了 icon 的条目都显示(未设置时的兼容默认值) |
groups |
只有根节点和有子页的节点显示图标,普通叶子页不显示 |
none |
侧栏不显示任何条目图标 |
新站点建议显式写 groups:保留分组的语义标识,去掉叶子层的图标。本站使用这个设置,左侧只有六个栏目带图标。
展开与折叠
有子页的栏目在侧栏里带一个折叠箭头。OINK 保存整栏折叠、宽度和滚动位置;各分支的读者选择可由站点通过侧栏运行时 API 自行持久保存。默认行为:当前页所在的那条路径展开,其余收起;博客类栏目默认展开。
站点级的折叠、紧凑模式、初始展开层数、宽度与截断在布局与页面类型里配;键的完整定义见配置总览。
从侧栏里藏起来
| front matter | 效果 |
|---|---|
toc_hide: true |
页面不出现在侧栏树里(页面本身照常发布,链接照常可用) |
hide_summary: true |
页面不出现在栏目首页的子页索引里 |
sidebar_divider: true |
这一项不再是链接,而是侧栏里的一条分组标题 |
manual_link: https://… |
侧栏这一行指向别处;配 manual_link_title、manual_link_target: _blank 用 |
toc_hide 与 hide_summary 控制两个不同的入口,两处都不该出现时才同时设置。
不发布目录页的分组
自 OINK 1.1 起,分隔分区保留子页,同时让标题不再跳转,修复了 v1.0.0
子页丢失的问题。目录只负责组织子页时,使用这样的 _index.md:
标题显示为分组标签,启用侧栏折叠时按钮负责展开子页;子页仍进入翻页、搜索、导航 JSON 和 Print。
没有 JavaScript 时分组保持展开。省略 build,即可继续发布分区页面,同时保留侧栏的
非链接标题。叶子分隔项保持原来的外观。
toc_hide 会隐藏分区的整棵子树;no_list 只移除分区正文的子页列表,hide_summary
只移除父页面列表中的一项,都不能代替分组。build.render: link 保留 permalink,却不
发布页面;不发布的分组应使用 never,避免其他导航把它当成可访问的目标。
外壳由 type 决定,不是路径
文档外壳(侧栏、目录、面包屑、翻页器)不取决于目录名,只取决于页面的 type 是否在 params.ui.shell_types 里:
文档因此可以放在任意路径,用 cascade 指定 type 即可。例如把一套手册放在 content/handbook/,栏目根的写法如下:
文档目录不叫 docs 时,type: docs 之外还要写 sidebar_root_for: self。否则侧栏会按 params.ui.docs_section(默认 docs)去找根,读者在 /handbook/ 下却看到 /docs/ 的树。
多根侧栏
侧栏树默认以读者所在的顶层栏目为根,树上方一行标出当前的根。规模较大的子树可以自己成为一个根,例如带版本的 API 参考或一本独立的手册:
| 取值 | 语义 |
|---|---|
self |
这个栏目的首页及其全部后代都以它为侧栏根 |
children |
首页仍留在父级树里,只有后代以它为根 |
根节点上方的切换器是全站的:它列出所有顶层栏目,加上站内所有 sidebar_root_for: self 的栏目。只有一个入口时它退化成一个普通链接,两个及以上才是下拉菜单。在顶层栏目或嵌套自根的 _index.md 中写 sidebar_root_menu: false,可将其排除在全站候选之外;浏览该分区时,当前根仍保留为位置提示。
切换器下方,栏目首页仍是树里的第一个链接:切换器选择一棵树,根链接指向一篇文档。sidebar_root_link_self: false 让根那一行改为指向父级栏目。
验证
构建应以 Total in … 结束且无 ERROR。调整目录结构时,--printPathWarnings 可以及时发现两个页面指向同一输出路径的冲突。
在浏览器里逐项确认:
- 侧栏里的顺序与写下的
weight一致,新栏目出现在预期位置; - 栏目首页的子页索引齐全(缺项来自
hide_summary或缺少_index.zh.md); - 面包屑与翻页器的顺序与侧栏一致,翻页器读的是同一棵树;
- 换语言之后树的形状相同(每个
_index.md都要有.zh.md对等文件)。
侧栏条目超过 params.ui.sidebar_menu_truncate 时构建给出警告,并指出应调到多少。这个警告不可忽略:被截断的条目不会出现在侧栏里。
通过站点代码控制分支
window.OinkSidebar 自 OINK 1.1 起提供;v1.0.0 不提供该 API。
站点代码应在主题脚本之后加载,例如使用 layouts/_partials/hooks/body-end.html,
读取或恢复分支状态前先等待 ready。以下示例展开侧栏的第一个分组:
区域 ID 使用按钮现有的 aria-controls 值;getState(id) 返回 {id, expanded}
或 null。按钮、区域与无障碍状态一致后,变化会在 document 上触发一次
oink:sidebar-disclosure 事件,detail 为 {id, expanded, source};重复写入相同
状态不发事件。通过 API 恢复时,当前页面的祖先分组保持展开,读者仍可主动折叠。
主题不保存单个分支的偏好。站点自行增加这项功能时,应为存储键区分语言与导航版本, 容忍存储不可用,并忽略当前页面不存在的 ID。完整生命周期见 侧栏契约。
相关
3.3 - 页面参数
本页是页面级参数的全表,包含 OINK 主题会读取的键,以及 1.x 明确保留的兼容
no-op。主题仅为提示「已重命名或已移除」而读取的旧键不在此列——它们在
迁移里,也不会出现在生成的编辑器 Schema 中。Hugo
自身的 front matter 字段(slug、url、build、sitemap、expiryDate 等)
照常可用,语义见 Hugo 文档。
站点级参数(hugo.yml 里的 params.*)见配置总览。
表格说明
优先级从高到低:
- 页面自己的 front matter;
- 最近一层
cascade(多层 cascade 都设了同一个键时,离页面最近的那一层生效); hugo.yml里的站点参数。
「默认」列标「站点值」的键,未写时回落到同名的站点参数。
页面键一律写在 front matter 顶层,键名是站点键去掉 ui. 前缀:站点的 params.ui.section_index 对应页面的 section_index。front matter 里不写 ui: 段,键一律在顶层。写在 ui: 段里的键不会被读取,也不会有任何提示——某个设置看着没生效时,先对照本页核一遍键名。
放进 cascade 时键名不变,多包一层:
非法值不会中断构建:主题会输出一条警告,说明键名、传入值和回退值,并按表中的默认值渲染页面,避免单个笔误导致 hugo server 全站返回 HTTP 500。
如果简单回退可能发布出误导性的内容(例如残缺不全的上游署名),主题会在告警后整块略去该区域。唯一会中断构建的是 Hugo 自身检查到的未解析引用(ref / relref)。
基本
title, ,- 页面大标题、浏览器标题、搜索结果标题。每页必写
linkTitle, ,- 侧栏、面包屑、翻页器、卡片里的短名
description, ,- 一句话摘要:栏目卡片、搜索摘要、
meta description;博客页里渲染成正文上方的导语 weight, ,- 同级排序,用 10 的倍数;
0(不写)排在所有写了 weight 的页面之后,见组织内容 draft, ,- 草稿不进构建产物,
hugo server -D可预览,见编写页面 date, ,- 博客日期、发布页排序依据;未来日期默认不构建
lastmod, ,- 页尾「最后修改」;站点启用
enableGitInfo时不必手写 aliases, ,- 旧路径重定向到本页;用于页面迁移,不用于日常导航
type, ,- 决定模板与外壳:
docsbookblogswagger,见组织内容 layout, ,- 为单个页面指定布局:
landing、releases cascade, ,- 把下面这些键下推给整棵子树
侧栏与导航
指南在组织内容。
icon, ,- 侧栏、栏目卡片与搜索结果的图标,例如
fa-solid fa-rocket toc_hide, ,- 将本页及其整棵子树排除在侧栏与翻页序列之外;内容树与显式导航树都遵循这一规则
hide_summary, ,- 不出现在栏目首页的子页索引里
sidebar_divider, ,- 不带链接的分组标题,自身不进入翻页序列;1.1 的实现保留分区子页。配合
build.render: never可只分组、不发布自身页面 sidebar_expanded, ,- 这个栏目在侧栏里默认展开
sidebar_root_for, ,- 让这个栏目成为侧栏树的根;
self连同栏目首页,children只管后代。其它取值告警并忽略 sidebar_root_link_self, ,- 根那一行链接自身;
false改为链接父栏目。非布尔值告警并使用true sidebar_root_menu, ,- 顶层栏目或嵌套自根是否进入全站切换器候选;被排除的当前根仍保留为位置提示。1.1 修复了嵌套自根的排除设置
toc_root, ,- 侧栏根是站点首页时,把这个顶层栏目整个排除在树与翻页序列之外
manual_link, ,- 侧栏与栏目索引里这一行指向别处
manual_link_relref, ,- 同上,但用
relref解析;目标不存在时构建失败 manual_link_title, ,- 手动链接的悬停标题
manual_link_target, ,- 例如
_blank,主题自动补noopener no_list, ,- 栏目首页不生成子页索引
simple_list, ,- 子页索引渲染成紧凑的项目符号列表
section_index, ,- 子页索引的样式。非法值告警并回退
section_index_columns, ,- 卡片样式的列数
notoc, ,- 不显示右栏页面目录
pager, ,false关闭本页的上一页 / 下一页。非布尔告警并忽略该覆盖navbar_enabled, ,- 这一页是否渲染顶栏
navbar_autohide, ,- 顶栏在指针设备上自动隐藏
breadcrumb, ,- 本页是否渲染面包屑;Docs/Book 默认开启,Blog 默认关闭
theme_color, ,#rgb/#rrggbb十六进制色,为本页的强调底着色。写在分区根的cascade里就给整个分区一个身份 —— 见品牌外观theme_color_dark, ,- 强调色的暗色一半。若上层 cascade 同时设了这个键,只覆盖
theme_color的页面会继承那个暗色,所以要两个一起写。theme_color: false可让页面整体退出继承的栏目色 page_context_menu, ,- 标题行的页面操作菜单(复制 Markdown、编辑本页、打印……)
page_context_menu.assistant_links, ,- ChatGPT / Claude 交接项,写成
page_context_menu: { assistant_links: false }。页面只能收窄站点策略,不能单独开启
页面外壳
站点级的默认值与效果说明在布局与页面类型。
page_width, ,- 正文栏宽度。非法值告警并回退
reading_width, ,- Book 页的阅读行宽,只对
type: book生效 footer_style, ,- 页脚形态。非法值告警并回退
body_class, ,- 追加到
<body>上的 class,供站点自己的 CSS 使用 reading_time, ,- 本页是否显示阅读时长;写
false关掉 sidebar_enabled, ,- 这一页是否显示左侧栏;写
false关掉 scroll_spy, ,- 1.x 静默兼容 no-op;普通外壳运行时始终提供当前标题跟踪
keyboard_nav, ,- 单键键盘导航,见键盘导航。非布尔告警并回退
lastmod_commit, ,- 「最后修改」后面怎么显示提交。非法值告警并回退
sidebar_expand_levels、sidebar_menu_compact、sidebar_menu_foldable、sidebar_item_overflow, ,- 侧栏行为也可以逐页覆盖;取值见配置总览
sidebar_width_min、sidebar_width_max, ,- 本页桌面侧栏拖拽宽度的上下限;下限大于上限时告警并恢复站点值
code_copy, ,- 本页代码块复制控件的默认值;围栏显式
copy=仍然优先 toc_style, ,- 固定右栏面板,或从内容流开始的较宽右栏
toc_taxonomies, ,- 分类词云是否与页面目录共同进入右栏
taxonomy_icons, ,- 为本页或分区 cascade 覆盖各分类法图标
搜索
指南在全文检索。
search_keywords, ,- 附加检索词,包含中英文与同义词
search_boost, ,- 排序乘数,最终得分为文本匹配分乘以该值。非数字、非有限、零或负值告警并回退
1.0 search_exclude, ,- 不进本地索引
输出形态
指南在 Agent 支持(.md 与 llms.txt)与打印支持。
outputs, ,- 这一页生成哪些输出格式;写
[HTML]时不再生成.md no_print, ,- 不进入整章 / 整书的聚合打印输出
页尾:评论、反馈与出处
顺序固定为反馈 → 出处 → 翻页器 → 评论,见编写页面。
上游出处
页面改写自别处的材料时,用 upstream_link 声明来源,页尾出处行会给出作品、版权人、许可证与完整声明的链接。这一族键的解析顺序是站点参数 → data/upstreams 中由 upstream_source 指名的条目 → 本页 front matter,最具体的声明胜出。
upstream_link 只从 front matter 读取(cascade 有效,站点参数无效)——站点级的值会让每一页都声称同一个来源。没有 upstream_link 却写了任何一个同族键,告警并略去署名。
upstream_link, ,- 本页据以改写的材料地址。写空串退出 cascade 继承来的值
upstream_name, ,- 上游作品名,按上游自己的写法。设了
upstream_link即必填 upstream_copyright, ,- 版权声明,保留上游原文。必填
upstream_license, ,- 必须能在
data/licenses中查到,否则告警并略去署名。必填 upstream_notice, ,- 承载完整声明(许可证全文、免责声明、上游 NOTICE、快照版本)的页面。必填
upstream_ref, ,- 快照对应的 tag 或 commit,显示在作品名后的括号里
upstream_source, ,data/upstreams中的条目名,用于集中声明多页共用的上游事实;条目不存在时告警并略去署名upstream_modified, ,- 把署名动词改成「改编自」,站点配了仓库信息时在同一句里带上「查看历史」链接——是一句话,不是多加一行。非布尔值告警并按未修改处理
四个必填键(upstream_name、upstream_copyright、upstream_license、upstream_notice)缺一即告警并略去整条署名:残缺的署名比明显的缺失更糟。主题自带一份 SPDX 表 data/licenses.yaml,站点用同名文件补充或覆盖条目。
图片缩放
image_zoom, ,- 本页的图片是否可点击放大,见图片。非布尔告警并回退
博客与文章
指南在博客与文章。
列表布局对比了四种呈现方式。gallery 与
blog_gallery 随 OINK v1.4.0 提供;博客索引支持 list、cards、table 与 gallery。
author, ,- 文章署名,支持行内 Markdown。页面写了
authors时忽略它 authors, ,authorstaxonomy 的 term,顺序即署名顺序,见作者与署名。需要在taxonomies:下声明author: authorsseries, ,seriestaxonomy 的 term。正文上方的横幅取第一个,见系列series_weight, ,- 在系列中的位置。带权重的成员按升序排在前,其余按日期升序跟在后
tags, ,- 标签,见分类体系
categories, ,- 分类,同上
images, ,- 第一项作为文章封面与分享卡片;写进栏目
_index.md的cascade即为栏目级默认。images: []让这一页不继承 cascade 里的值,但不会屏蔽页面 bundle 里已有的featured、cover或thumbnail图片 byline, ,- 解析到的题图实际渲染时显示的图片署名
featured_image, ,- 本文正文里怎么渲染自己的题图;
hero使用沉浸式通栏外壳。非法值告警并回退 blog_index, ,- 博客栏目索引布局;仅
blog_index_toggle: false时的独立table不分页、列出整个栏目。非法值告警并回退 blog_index_columns, ,- 宽视口下的卡片列数;中等与窄视口仍保留响应式限制
blog_index_size, ,list、cards、gallery及启用切换时四种视图共享的每页文章数;独立table忽略此值blog_index_toggle, ,- 输出四种布局并记住读者选择;设为 false 时只输出配置的布局,不受已保存布局选择影响
blog_gallery, ,- 整体替换站点画廊配置块;缺失字段使用内置默认值,无效字段告警并回退。读者保存的排布和大小优先
blog_gallery.mode, ,- 保留 6px 间隙的等高拼接、始终显示文字的瀑布流或无间隙行布局;独立于外层
blog_index布局 blog_gallery.size, ,- 目标画廊格子的 CSS 像素大小;独立于每页文章数设置
share, ,- 页尾分享目标,整体替换继承来的列表;
false让本页退出,见分享。未知目标告警并丢弃 summary, ,- 画廊条目及标签 / 分类页文章行的摘要回退来源,
description优先
Book
指南在书籍出版。整本书通过栏目 cascade 设 type: book。
book_number, ,- 章节编号,显示在页面标题与侧栏条目前面
book_status, ,- 标记草稿章节:侧栏与目录里带草稿标记,索引里默认不列
sidebar_headings, ,- 在侧栏当前条目下展开 h2–h4 分支。超出范围告警并回退
book_draft_banner, ,- 草稿章节正文开头加一条横幅。非布尔告警并回退
Expo 相册
OINK 1.3.0 通过 type: expo 选择相册布局。
用法见 Expo 相册。
expo, ,- 整体替换站点配置块;缺失字段使用下列内置默认值
expo.images, ,- 页面包内图片资源的匹配模式
expo.mode, ,- 读者未保存布局选择时的初始模式
expo.size, ,- 160 至 360 CSS 像素的目标尺寸;读者保存的尺寸优先
图片标题、说明、署名、权重、显式 ID 和独立原图使用 Hugo 原生 resources 元数据。
字段示例见图片元数据。
Landing
指南在首页与落地页。任意页面写 layout: landing 就用落地页外壳。
landing, ,- 数据取自
data/landing/<key>/<语言>.yaml sections, ,- 在 front matter 里内联分区定义,优先于
landing。不是数组时告警,不渲染任何分区
发布页
指南在发布与下载页。栏目写 layout: releases 后忽略 weight,按发布日期与 SemVer 倒序排列。
release_url, ,- 一个 GitHub 发布地址,
https://github.com/<owner>/<repo>/releases/tag/<tag>。主题从中提取 owner、项目名与标签,并生成源码归档链接;日期取页面的date,下载资产由发布正文中的checksums块提供(见发布与下载页)。其它写法告警并跳过发布区块
相关
3.4 - 博客与文章
博客文章与文档页的正文写法相同,区别在外壳:文章带日期、作者、标签与封面图,列表按日期倒序排列,栏目带 RSS。本页覆盖博客栏目的建立、文章 front matter、封面图、列表布局、分页与 Feed。
博客目录结构
博客是 content/ 下的一个栏目,type: blog 使它使用博客外壳。子目录按发布方与受众划分,文章平铺其中。无需建立年份目录,列表会按文章的 date 排序:
本站的 content/blog/
- content/
- blog/
- _index.mdtype: blog + cascade
- _index.zh.md
- oink/工程实践与公告
- _index.zh.mdcascade: images: [/images/oink.webp]
- oink-announcement.md
- oink-announcement.zh.md
- release/带版本号的发布注记
- _index.zh.mdcascade: images: [/images/releasenote.webp]
- 0.4.0.md
- 0.4.0.zh.md
- blog/
栏目根把类型下推给整棵子树,并设定该栏目共用的行为:
params.ui.blog_section(默认 blog)指明博客根的位置。目录另起名字时改这个参数,或按上面的写法用 sidebar_root_for: self。
侧栏里博客栏目默认展开,条目按日期倒序;给某篇文章写上 weight 会把它固定在最前。
一篇文章的 front matter
与文档页不同的几点:
date必填。它决定文章在列表里的位置与 RSS 时间。写在未来的日期默认不构建,hugo server -F可以预览。description渲染成正文上方的导语,不只是搜索摘要,因此写成给读者阅读的一句话。author支持行内 Markdown,可以写成[Vonng](https://vonng.com)。需要多位作者、头像或作者主页时,改用下面的authorstaxonomy;两者互不干扰,没写authors的文章照旧渲染author。- 日期显示格式由
params.time_format_blog决定,可以按语言分别设置(本站英文是Monday, January 02, 2006,中文是2006年1月2日)。
双语文章成对存放,两种语言的 date、author、weight、aliases 保持一致;标题、描述、标签要翻译,提交 ID、版本号、命令和 URL 不翻译。
封面图
列表页与标签页的每一行左侧有一张缩略图,按以下顺序解析,第一个命中的生效:
- 文章 front matter 的
images,取第一项; - 页面包里文件名匹配
featured或feature的图片,其次是cover或thumbnail(会被裁切成缩略图,图片资源自己的byline会作为图注); - 从祖先栏目
cascade继承来的images,就近生效。
栏目级默认封面用 Hugo 原生的 cascade 覆盖整棵子树,本站两个子栏目各设一张:
要取消某篇文章继承来的封面,在它的 front matter 写 images: [];整个子栏目取消继承,就写进那一层的 cascade。这不会抑制页面包自身提供的图片。只想关闭文章里的题图时,用 featured_image: none;列表缩略图与分享卡片仍各自生效。站点级的 params.images 只做分享卡片,不会渲染成列表缩略图。
渲染到文章正文里
默认情况下,解析出来的图片显示在列表行与社交卡片里。设置 params.ui.featured_image,即可把同一张图显示在文章里:
| 模式 | 文章里显示什么 |
|---|---|
none |
文章不显示题图,主题默认值 |
banner |
标题上方一张固定 16:9 的图,连着读一串文章时节奏统一 |
wash |
图片淡化后铺在文章头部背后,在正文开始前渐隐 |
hero |
占满页面宽度的沉浸式图片头部 |
页面键是 featured_image,所以某个子栏目的 cascade 可以只为那棵树打开它,单篇文章也可以退出。没有图片的文章在任何模式下都不显示题图,因此只有部分文章配图的栏目也可以整体开启。这些模式无需额外脚本。
列表布局与分页
栏目 _index.md 的正文之后,主题自动接上文章索引,按日期倒序排列,不按年份
分组。用 blog_index 选择布局:
| 值 | 布局 | 读者看到什么 |
|---|---|---|
list |
水平条目(默认) | 缩略图、标题、日期、所属子栏目、标签与摘要 |
cards |
规则卡片网格 | 上方是 16:9 裁切封面,下方是标题、元数据与摘要 |
table |
紧凑表格 | 每篇文章占一行,显示日期、标题与标签 |
gallery |
照片画廊 | 按比例排布封面,附带标题与描述;点击进入文章 |
OINK v1.4.0 包含 list、cards、table 与 gallery。
在 hugo.yml 中使用 params.ui.blog_index 设置站点默认布局,或在博客栏目的
_index.md front matter 中使用 blog_index 覆盖它。栏目 cascade 可将设置
传给下级栏目。这些配置控制原生博客栏目索引;分类法与分类项页保留自己的
布局,自定义首页需要在模板中单独集成。
列表、卡片与画廊默认每页 12 篇,用主题的 blog_index_size 在 hugo.yml 里调整:
博客栏目 front matter 中的同名键可以覆盖站点值。主题会把这个大小显式传给 Hugo 分页器,因此 pagination.pagerSize 不控制这里的列表。
卡片形态
params.ui.blog_index: cards 把同一份列表渲染成内容卡片网格而不是行列表:文章题图的 16:9 裁切在上,标题、日期与子栏目行居中,下面三行摘要。
列数只在 xl 断点以上生效;md 到 xl 之间为两列,md 以下一列。卡片图片资源可
处理时,Hugo 使用 .Fill 生成裁切预览,读者无需下载原图。
表格与布局切换
单独使用 blog_index: table 且 blog_index_toggle: false 时,表格列出整个栏目,
不分页。设置 blog_index_toggle: true,标题旁就会出现布局切换按钮。切换顺序是
list → cards → table → gallery → list:
所有布局都保留日期顺序与 manual_link 目标。开启切换时,四种布局显示同一页
文章,都使用 blog_index_size;只有独立表格才显示整个栏目。切换器会记住
读者的选择。设为 blog_index_toggle: false 时,只输出配置的布局,不受读者
之前保存的选择影响。隐藏视图使用延迟加载,但输出标记与生成缩略图仍需要构建
时间。v1.4.0 的切换器在四种布局之间循环。
画廊形态
OINK 1.4.0 的 gallery:每篇文章成为一张可点击的封面,使用现有 images
字段或页面包中的特色图片。默认展墙将图片按比例拼成行;悬停或键盘聚焦时显示
标题和描述,触屏设备则把文字放在图片下方。点击进入文章,没有封面的文章仍
保留标题块。
画廊工具栏提供三种内部排布:
blog_gallery.mode |
排布 | 文字显示 |
|---|---|---|
wall(默认) |
等高拼接,保留 6px 间隙 | 悬停或键盘聚焦时显示;触屏上放在图片下方 |
seamless |
相同的行布局,去掉间隙 | 悬停或键盘聚焦时显示;触屏上放在图片下方 |
cards |
瀑布流 | 始终显示在图片下方 |
blog_index: cards 选择规则卡片网格;blog_index: gallery 配合
blog_gallery.mode: cards 才是画廊内的瀑布流。blog_gallery.size 设置目标格子
大小,范围是 160–360 CSS 像素,默认 240;它与控制每页文章数的
blog_index_size 相互独立。读者可以调整排布和大小,选择独立于 Expo 相册记忆。
站点默认值使用 params.ui 下的同名 blog_gallery 块,页面或 cascade 块会
整体替换它。未给出的字段使用 wall 与 240;无效值告警并回退到这些默认值。
这些配置随 OINK v1.4.0 提供。完整参数表见
站点参数与页面参数。
RSS
哪些页面产出 Feed 由 outputs 决定。给 section 加上 RSS,每个栏目就有自己的 Feed:
outputs 一旦写出就整体替换 Hugo 的默认值,RSS 必须显式写回。漏写等于关闭该类页面的 Feed,构建不会报错。
本站因此有 /zh/blog/index.xml(整个博客)与 /zh/blog/release/index.xml(只有发布注记)。栏目 Feed 递归包含所有子栏目的文章,订阅 /zh/blog/ 即可收到全部。单篇文章没有自己的 .xml。
每种语言有各自的 Feed,地址是该语言路由加 index.xml。条数上限由 Hugo 的 services.rss.limit 控制。在博客根与它的一级子栏目页上,标题行右侧操作按钮的首位是 RSS 链接,读者不必手拼地址。
全站不需要 Feed 时用 disableKinds 关闭这一类输出,比逐个页面类型删除 RSS 更彻底:
组件在 Feed 里退化成静态形态:折叠块展开、交互控件去掉。四态输出的规则对博客与文档一致。
分类与标签
tags 与 categories 是 Hugo 的分类体系,主题把它们渲染成文章头部的 chip、右栏的标签云和顶栏的筛选菜单。启用、双语标签与按内容类型开关见分类体系。
发布注记
带版本号的发布公告写成普通文章,惯例放在 blog/release/ 下,linkTitle 带版本号(Oink v0.4.0)。需要发布卡片、资产表与校验和的下载页见发布与下载页。
文章里用组件
提示块、标签页、代码块、图片、表格的用法与文档页相同,语法见组件总览。文章正文的标题同样写显式英文 {#id}。
文章末尾的反馈 / 最后修改 / 翻页器 / 评论四块与文档页一致,见编写页面。博客通常关闭反馈、保留评论。
作者与署名
声明这个 taxonomy 就是全部开关,主题不为此增加任何参数:
文章按顺序写出作者:
文章头部按这个顺序显示头像与带链接的名字,列表行显示名字;博客 Feed 除了站点级的 managingEditor,还会包含文章的每位作者。
作者主页就是分类项页,无需另建 data/authors 文件:
显示名取的是 term 页的链接标题——写了 linkTitle 就用它,否则用 title——所以主页可以挂全名、署名处用短昵称。description 是一句话介绍,正文是长介绍,头像则是题图解析器为这一页选中的那张——images: 与页面包里的肖像文件,走的是文章题图那套同样的规则。双语主页就是旁边一个 _index.zh.md。文章写了、但没人给它建主页的名字照样出署名:链接标题、一个首字母,以及指向归档页的链接。
0.4 的 author: 字符串在没有 authors 的地方原样保留,两种写法互不告警。
系列
系列是一条穿过若干篇各自独立成文的文章的阅读路径。编号、交叉引用与聚合输出属于书籍,这里是更轻的那个东西。声明 taxonomy 同样就是全部开关:
文章写出系列名,也可以给自己定个位置:
它的正文上方就会出现一条横幅,写明系列名、自己是第几篇、下一篇是哪篇,以及折在 <details> 里的完整列表——不用 JavaScript,也不增加打包成员。term 页 content/series/<name>/_index.md 是系列的引言,旁边放一个 _index.zh.md 就成双语。
写了 series_weight 的文章按该值升序排在前,其余按日期从旧到新排列,同序时按内容路径排序。系列横幅与分类项页使用相同的阅读顺序;实现规则见作者与系列。
一篇文章属于多个系列时只显示一条横幅,取它写在最前面的那个系列。只有一篇的系列不显示横幅。
authors 与 series 都不出现在文章的通用 taxonomy 标签行里,因为它们各自有专门的呈现面。想把某一个放回去,就在 params.taxonomy.page_header 里写上它的名字。
分享
params.ui.share 在页尾最前面放一条分享栏。它默认为空,所以在站点写出目标之前什么都不渲染;写出来的顺序就是渲染顺序:
可选的目标有十六个:x、bluesky、mastodon、facebook、linkedin、reddit、hackernews、telegram、whatsapp、line、pinterest、weibo、chatgpt、claude、email、copy。未知的名字告警并丢弃。Discord 是故意没有的:它根本没有公开的 share-intent URL,与其让主题去猜一个私有 scheme,不如用 copy 顶上。
页面键是 share,所以 cascade 可以把这条栏限定在一棵树里,页面自己的列表会整体替换继承来的那份,share: false 则让单页退出:
只有普通页面渲染分享栏——列表页、term 页与首页没有「唯一被分享的那个东西」——打印、Markdown 与 RSS 一概不带。
分享栏由携带页面永久链接与标题的链接,以及一个本地复制按钮组成。它不加载第三方脚本或样式表,只有读者点击链接时才会访问对应目标。实现规则见分享契约。
chatgpt 与 claude 是把同一个构建期 permalink 交给助手,附一句「请读这一页」。它们不是页面操作菜单里的「在 ChatGPT 中打开」/「在 Claude 中打开」——那两条由运行时在激活时改写成浏览器里的实时 URL,因此留在 page_context_menu.assistant_links 后面。
复制按钮就是内置的 copy_link 动作,也就是说不管有没有配分享栏,命令面板在每个站点的每一页上都带着它。
验证
构建应以 Total in … 结束且无 ERROR。随后确认:
- 文章在
/zh/blog/中按正确的日期顺序排列,日期显示为中文格式; public/zh/blog/index.xml存在,里面有这篇文章,链接是完整的绝对地址;- 缩略图出现在列表里(缺失说明三条封面来源都没命中);
- 标签 chip 能点进对应的标签页。
相关
3.5 - 书籍出版
type: book 把一棵目录树变成一本书:章节编号、图表式例编号、交叉引用、生成式索引与整本打印。一本书是一棵 type: book 的内容树:目录决定章节顺序,front matter 决定章节编号,图 / 表 / 式 / 例各带一个手写编号与稳定锚点。交叉引用在四种输出里都能解析,书根页面可以生成整本打印 HTML。
前提两条:站点的 markup.goldmark 已开启属性行与 passthrough(见组件总览);params.ui.shell_types 保留 book(主题默认包含)。
新建一本书时,从下面的目录结构开始;已有书稿可先看迁移既有书稿。
一本书的目录
书根是一个普通的 Hugo section,章是它的子目录,节是章里的页面。没有第二份章节清单:侧栏、翻页器、生成的目录读的都是这棵树。
content/handbook/ 一本书
- content/handbook/
- _index.md书首页:type: book + cascade,放 book-toc 与各类索引
- ch01/
- _index.md第 1 章章首页:book_number: 1
- install.md1.x 节
- bootstrap.md
- ch02/
- _index.md第 2 章:编号 2(book_number),草稿可标 draft
- replication.md
- failover.md
- appendix.md不编号的附录,照样进侧栏与翻页顺序
章节编号手写:book_number 写什么就显示什么,主题不按目录顺序自动编号。图 / 表 / 式 / 例的 num 同理,是作者掌握的字符串(2-1、5.3、A-2 均合法),不是渲染时计算的序号。重排目录因此不会让已经印出去的编号漂移。
书首页与章首页
书根声明类型、级联给后代,并显式请求 print 输出。这项聚合输出构建代价高,主题不替消费站开启:
分区书对应 Hugo 的 section 输出类型,书位于站点根时才用 home:
章首页只需要编号与顺序:
book_number 显示在页面标题、侧栏与生成目录里。book_status: draft 是可见的编辑状态标签,不改变 Hugo 的发布状态:草稿章节照常构建、照常发布。
sidebar_headings 默认为 false,也接受 true(只到 h2)或 2–4 的最大层级。页面或分区 cascade 可覆盖站点设置。关闭左侧标题分支不影响右侧目录。要被引用的标题一律写显式 ID,如 ## 同步复制 {#sync-replication}:自动生成的 slug 适合导航,不适合作为长期引用目标。
编号:原生形态
四种编号对象各有一种原生形态:一个 Markdown 块,紧跟其后一行属性行。属性行里 num= 是编号,#id 是锚点,caption= 是纯文本题注。
图
图片块后面跟属性行。#id 省略时默认是 fig-<num>。

原生图形态要求站点设置 markup.goldmark.parser.wrapStandAloneImageWithinParagraph: false,否则属性行会挂到段落上被忽略。替代文字取自 Markdown 图片本身,不会被题注替代。
表
管道表后面跟属性行,默认 ID 是 tbl-<num>。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read Committed | 不可能 | 可能 | 可能 |
| Repeatable Read | 不可能 | 不可能 | 可能 |
| Serializable | 不可能 | 不可能 | 不可能 |
式
$$ 块后面跟属性行,默认 ID 是 eq-<num>。宽屏上,编号与题注排在公式右侧;窄屏上移到公式下方并换行,过宽的公式可以横向滚动。题注简短些更方便阅读。
原生形态依赖站点开启 Goldmark passthrough。未开启时用下面的 eq shortcode,它走本地服务端 KaTeX。
例
代码围栏加 num= 与 caption= 即编号例,默认 ID 是 eg-<num>。围栏里写的 #id
命名外层 <figure>(即引用目标),而非代码块本身。例的题注必填:只写 caption 时忽略题注,只写 num 时丢弃编号并告警。编号例渲染为一个整体:题注作为外框表头,正文置于框内;当正文恰好是单个代码块时直接贴合外框,不再叠加内层边框。
编号:shortcode 形态
四个短代码 fig、tbl、eq、eg 渲染出与原生形态一致的 <figure>,注册到同一个目标表,按源码位置排序。原生形态做不到时再使用:一幅图含多张图片或其他 Markdown、一个编号下放多张表、站点未开 passthrough,或示例包含多个围栏与说明。单张编号图片可以在原生属性行里写 link,无需仅为了外链改用 fig。
fig 用 src=(也接受内部 Markdown 内容,二者互斥),并额外支持 link alt width height class 与迁移用的 title 别名:
tbl 把标签、表格、题注与锚点包进一个语义 figure:
| 输出 | 标签 | 锚点 |
|---|---|---|
| HTML | 可见 | 稳定 |
| 打印 | 可见 | 稳定 |
eq 的内容交给本地服务端 KaTeX,因此不依赖 passthrough:
不带参数的 {{< eq >}} 是无编号的块级公式兜底:不注册目标,不能被 xref 引用,也不出现在公式索引里。
eg 是包装型 shortcode,正文按页面的 Markdown 策略渲染,通常装一个或多个围栏:
同一页内 ID 必须唯一,同一类型下一个编号也只能对应一个 ID。遇到重复时,主题会保留首次注册的条目,并在告警中指出首次占用的行号。
Hugo 将每个 shortcode 的正文作为独立的 Goldmark 文档渲染,而脚注属于页面级状态。若在 tbl、eg、fig、card、tab、field 或 include 的正文中出现 [^label],主题会输出包含文件名、行号与标签的警告:若脚注定义写在页面正文中,引用会原样输出为 [^label];若定义写在 shortcode 内部,则会生成第二份脚注列表,导致 fn:N 与页面自身的脚注 ID 冲突。
需要脚注的表格或代码块改用原生形态:表格、图片、围栏加 {num=… caption=…},内容留在页面文档里,脚注照常编号、跳转与回链。渲染出来的图表与 shortcode 形态一致,所以这通常是一行改动。代码里形似脚注的文本(列表里的 [^0-9] 字符类、行内代码)不受影响。
交叉引用
引用同页目标可以用普通 Markdown 链接:表 2-1 指向上面那张隔离级别表。代价是标签与编号手写,改编号时需要自己检索。
xref 把标签、编号与锚点合成一处,并支持跨页与跨语言:
参见 图 2-2 与 示例 2-1; 显式锚点:图 2-1。
规则:
- 最多一个类型键(
figtbleqeg)。类型提供本地化标签(图 / 表 / 公式 / 示例)并推导出默认锚点<kind>-<num>。 anchor=覆盖推导出的锚点,用于目标写了显式#id的情况。page=跨页引用,走 Hugo 当前语言的页面查找,源码里不必硬编码/zh/前缀。- 不给类型时必须同时给
anchor=和内部链接文字:{{< xref page="../ch01/install" anchor="sync-replication" >}}同步复制{{< /xref >}}。 - 引用可以出现在目标之前,渲染时不读注册表,因此前向引用合法。
跨页的普通 Markdown 链接在整本打印里仍然是站点 URL。需要在聚合文档里也能跳转的引用写成 xref。
索引:目录与图表清单
五个索引 shortcode 遍历同一棵书树,触发后代内容并聚合注册结果。它们通常放在书首页(_index.md)或专门的「插图目录」页上。
这五个 shortcode 在本页只给源码。它们从当前页所在的导航根向下遍历,放在一棵普通文档树里会把整棵 docs 树当作书列出。真实效果见《使用 OINK 创作优美的内容》,源码位于
content/book/_index.md。
book-toc的depth取 1–3:1 列章,2 加入嵌套分区,3 再投射每页的标题树;drafts=false只把book_status: draft的行从这份生成列表里滤掉,不影响页面发布。book-figures/book-tables/book-equations/book-examples不接受任何参数,各列一类,条目形如「图 2-1 — 题注」并链到稳定 ID。- 整本打印时,这些链接全部变成文档内片段。
顺序阅读与草稿
翻页器默认对 docs、book、blog 三种类型开启,顺序是侧栏那棵树的前序遍历:分区首页在前,子页按 weight。关闭整类改 params.ui.pager_types,关闭单页写 pager: false。
toc_hide、manual_link 纯链接占位、sidebar_divider 分隔行都不会成为翻页目的地。
草稿章节除了侧栏上的「草稿」标签,还可以开启页首横幅:
横幅只在 type: book 且 book_status: draft 的页面出现,文案来自本地化键 book_draft_notice。
打印整本
书根有了 print 输出后,按可见的阅读顺序生成封面、本地目录、根页面正文与每个后代章节,全部装在一个 HTML 文档里。no_print: true 的页面、纯链接节点、分隔行与隐藏占位不会成为章节。
聚合文档里,编号组件的 ID 逐字节保留。页面内的 Markdown 标题与脚注 ID
会加上来源页面前缀,避免多章共有 summary 这类锚点、或都从 fn:1 开始时冲突;
生成的链接同步改写。页面单独渲染为 Print 时,与普通 HTML 保持相同的页面局部
ID——只有多页分区或整书聚合才增加命名空间。
产物是面向打印的 HTML。可选的 BookManifest 输出会把同一份阅读顺序记成 JSON,
主题另外提供 bin/book-epub.py 与 bin/book-pdf.py,把清单与打印 HTML 打包成
EPUB 和 PDF。
具体开关与整章打印见打印支持。
迁移既有书稿
已有的中文书稿通常用站点自己的 figure shortcode、加粗的假题注、指向 #fig_* 的裸链接来表示图表编号。主题仓库带一个迁移脚本,把这些旧形态改写成 fig、tbl 与 xref,并保留原有的公开锚点。站点先固定到一个包含 Book 组件的已发布 OINK 版本,再迁移内容。
四个配方对应三份真实书稿的旧约定(DDIA 的 v1 与 v2 各一个),只识别在那些书稿里观测到的形态:
--profile |
识别的旧形态 |
|---|---|
tpme |
假 h6 题注加相邻图片、题注加相邻表格、/en/...#fragment 裸链接 |
ddia-v2 |
站点自有的 figure shortcode,按编号图 / 表 / 代码例分类 |
ddia-v1 |
裸图片加相邻的一条加粗编号题注,ID 由图片文件名推导 |
pg-internal |
加粗或斜体的中英文「图 N」题注紧邻一张图片,编号表题注紧邻一张表格 |
--profile- 必填,取上表四个值之一
--root- 必填,消费站仓库根目录
--path- 限定
--root下的文件或目录,可重复;默认扫描整棵内容树 --write- 应用改写。默认是干跑,不写任何文件
--no-diff- 不打印 diff,仍输出摘要与报告
--report- 写出机器可读的 JSON 报告
diff 走标准输出,摘要走标准错误,报告含 files_scanned、files_changed、counts、skipped、idempotent 五项。脚本只改写能唯一确定的目标:无法确定编号、题注不唯一、标记形态不认识的地方原样保留,逐条记进 skipped 供人工处理。旧题注里的粗体、行内代码与公式会降级为纯文本,因为 Book 的题注契约是纯文本。
审阅 diff 之后在专用分支上应用,再运行第二遍确认幂等:
第二份报告应当是 files_changed: 0、counts 为空、idempotent: true;脚本以退出码 0 表示幂等。
配方只识别这三份书稿里实际观测到的旧形态;书稿的旧约定不在这四个配方之内时,脚本不适用,需要按编号:原生形态手工改写。主题仓库的 bin/check-book-migrations.py 用干跑与幂等两项检查覆盖这四个配方。
验证
- 构建生产:
hugo --printPathWarnings。编号写错、ID 重复、题注缺失都在这一步失败。 - 页面上应看到「图 2-1」这样的本地化标签、可点的
xref链接,以及点击后正确跳转的锚点。 - 对比侧栏、翻页器、
book-toc与整本打印四处的章节顺序是否一致。 - 检查 Markdown 输出:
curl -s http://localhost:1313/zh/handbook/ch02/index.md。shortcode 形态应退化成**图 2-2.** 题注加原始正文,原生形态原样保留源码块与属性行。 - 从主题仓库对构建产物跑一遍锚点检查:
它校验每个引用的目标锚点存在、类型与编号匹配、页内 ID 唯一,以及编号图片有与题注相称的替代文字。
Book shortcode 参数
num, ,- 必填(
eq无参形态除外)。匹配[0-9A-Za-z.-]+,要加引号 id, ,- 匹配
[A-Za-z][A-Za-z0-9_.:-]*,逐字节保留 caption, ,eg必填;figtbleq可选。不是 Markdownclass, ,- 追加到
<figure>;需要num src, ,- 仅
fig。与内部内容互斥,走共享图片解析顺序 linkaltwidthheight, ,- 仅
fig。宽高是正整数 title, ,- 仅
fig。caption的迁移别名,二者互斥
xref:
figtbleqeg, ,- 至多一个。提供本地化标签并推导锚点
anchor, ,- 无类型时必填,且必须有内部链接文字
page, ,- 走当前语言的页面查找,找不到时告警并渲染无链接文字
book-toc:
depth, ,- 1 章 / 2 含嵌套分区 / 3 含标题树
drafts, ,false时从生成列表里滤掉草稿章节
book-figures、book-tables、book-equations、book-examples 不接受任何参数。
限制与常见问题
- 没有自动编号。章节号、图号、表号都手写;改编号是一次有意的编辑,不是构建的副作用。
- 属性行必须紧贴块,中间不能有空行。被 Prettier 之类工具移动过的属性行静默失效,图退化成普通图片。
book_kind与book_part是契约认可的元数据键,当前主题模板不渲染它们;有视觉效果的是book_number与book_status。- 索引 shortcode 会触发后代内容渲染,在超大树上明显拉长构建时间。整本
print需要显式开启也是同一原因。 - shortcode 正文内不支持脚注引用;出现时会触发告警并提示改用原生形态,详见上文编号:shortcode 形态。
- 打包是可选的,且在构建之外运行。
BookManifest加上bin/book-epub.py/bin/book-pdf.py可以产出 EPUB 与 PDF,但没有任何一次 Hugo 构建会自己生成这两个文件;专业排版的分页、字体嵌入与索引编制仍在契约之外。
相关
3.6 - 用 Expo 创建图片相册
Expo 用相册展示图片收藏。侧栏组织相册,主区域显示所选相册中的图片。 点击图片即可打开查看器。
Expo 随 OINK v1.3.0 提供。
打开示例相册即可体验这些控件。若要在文字页面中插入图片网格, 使用 Gallery 组件。
创建相册
每个相册使用一个目录。图片放在目录内的 images/ 文件夹中,用一个 index.md
描述相册。图片默认使用文件名生成标题,无需逐图创建 Markdown 或 YAML 文件。
-
创建分区入口:
content/photos/_index.md -
为每组相册创建主题入口:
content/photos/nature/_index.md -
创建相册页面:
content/photos/nature/field-notes/index.md -
将图片放入相册的
images/文件夹。
分区页面列出下级主题与相册,侧栏沿用同一棵内容树。窄屏上打开导航抽屉选择相册。 相册正文可以省略;提供正文时,它显示在图片上方。
大型收藏请拆成多个相册。Expo 不会自动给图片分页。分区中的相册卡片用排序后的 第一张图片作为封面;没有自身图片的主题会从下级相册中选取首张可用图片。 封面填满 4:3 的画框,因此预览可能被裁切。
添加图片元数据
图片需要标题、说明、署名或指定顺序时,在相册 front matter 中添加 resources
条目。src 路径相对于相册目录。
| 字段 | 用途 |
|---|---|
title |
图片标题;省略时由文件名生成 |
params.alt |
描述图片的替代文字;省略或为空时使用标题 |
params.caption |
纯文本说明 |
params.byline |
纯文本署名 |
params.weight |
图片排序数值;越小越靠前,默认为 0 |
params.id |
相册内稳定的图片 ID |
params.original |
可选,指向同一相册页面包内另一张图片的路径 |
权重相同时沿用资源名称顺序。需要完整指定顺序时,为每张图片都设置权重。 没有权重的图片排在正权重图片之前。
ID 必须以 ASCII 字母或数字开头,后续字符还可使用 _ 和 -。同一相册内的 ID 必须唯一。
示例生成 /photos/nature/field-notes/#img-fern。只要相册 URL 和显式 ID 不变,
修改文件名后这条链接仍然有效。未指定 ID 时,Expo 使用文件名哈希;重命名文件会改变链接。
若要提供独立原图,将其放入 originals/,并设置
params.original: originals/fern.jpg。预览图与原图应保持相同宽高比。
原图路径应在 expo.images 匹配范围之外,以免重复列出。
Expo 读取页面包内的图片,不会为相册抓取远程文件。
设置默认值
在 hugo.yml 中设置站点默认值:
| 键 | 可选值 | 内置默认值 |
|---|---|---|
images |
相册页面包内图片资源的匹配模式 | images/* |
mode |
wall、cards 或 seamless |
wall |
size |
160 至 360 的整数,单位为 CSS 像素 |
240 |
在相册 front matter 顶层写入 expo 块,覆盖站点配置:
此设置替换整个配置块。省略的字段使用内置默认值,不继承站点块中的对应值。
分区也可通过 cascade 提供这个配置块。size 是布局计算的目标尺寸,不是每张图片
固定的宽度和高度。
浏览器按站点来源保存读者选择的布局和尺寸。这些选择优先于作者默认值,刷新页面、 切换相册或切换外观后仍然保留。浏览器存储不可用时,页面加载后使用作者默认值。
选择布局
| 模式 | 显示方式 |
|---|---|
wall |
展墙:按行排列图片块,间距为 6 CSS 像素 |
cards |
卡片:按列排列,显示标题、说明与署名 |
seamless |
无缝:按行排列图片块,块之间没有间距 |
工具栏依次提供三个布局图标、播放/暂停按钮和图片尺寸滑杆。在有鼠标的设备上, 展墙与无缝模式在悬浮或键盘聚焦时显示说明。宽高比极端的图片可能在图片块内留白。 相册中的图片块会完整容纳图片。
GIF 默认播放。点击暂停可切换到静态预览。若系统要求减少动态效果,Expo 默认使用 静态预览,读者仍可点击播放。没有 GIF 的相册会禁用播放按钮。图片在接近可见区域时 加载;体积较大的 GIF 仍需传输完整文件。
使用图片查看器
点击图片打开查看器。小图以原尺寸显示,大图缩小到宽、高都能放入可用显示区域。
| 控件 | 操作 |
|---|---|
| 重置 | 恢复初始尺寸,并将图片居中 |
| 放大至适屏 | 缩放图片,直到受限边与显示区域对应边等长 |
| 缩小/放大 | 将当前缩放比例除以或乘以 1.25 |
| 打开原图 | 在新标签页中打开原图文件 |
| 在相册中定位 | 关闭查看器,移动到相册中的这张图片 |
| 上一张/下一张 | 显示同一相册中的相邻图片 |
| 关闭 | 关闭查看器,将焦点返回图片链接 |
放大至适屏会完整显示图片,不会切换浏览器全屏。手动放大可以让图片超出查看区域。 按住鼠标左键拖动,即可移动放大后的图片,移到图片边界后停止。触摸屏上的放大图片 保留原生滚动。图片完整放入显示区域时,单指横向滑动可切换图片;双指缩放保留 浏览器原生行为。双击在初始适应尺寸与近看之间切换:缩小显示的图片以 100% 显示,并保持指针下的位置;已按原尺寸显示的图片则放大至适屏。
切换图片会重置缩放比例和位置。上一张/下一张到达相册首尾后停止。 缩放百分比按 CSS 像素计算:100% 时,一个原图像素对应一个 CSS 像素。
使用快捷键
以下快捷键在查看器打开时生效,优先于页面的单键导航。它们不接管文字输入、输入法 组合输入或 Ctrl/Command/Alt 组合键。
| 按键 | 操作 |
|---|---|
| W / A / S / D | 将放大后的图片向上/左/下/右移动 |
| Q 或 H | 上一张 |
| E 或 L | 下一张 |
| J 或 − | 缩小 |
| K 或 + | 放大 |
| R 或 0 | 重置尺寸和位置 |
| F | 放大至适屏 |
| Escape | 关闭查看器 |
WASD 每次移动图片 80 CSS 像素,只沿超出显示区域的方向生效。不需要横向滚动时,
左右方向键用于切换图片;图片宽于显示区域时,方向键保留原生滚动行为。
keyboard_nav 设置控制页面快捷键,不会禁用查看器控件。
使用站点外观
Expo 采用 Paper、Slate、Ink、Terminal 四种预设,并沿用站点的亮色或暗色模式。 这些选项位于共享导航中。相册主体使用图片工具栏,不显示文章操作菜单。
要提供四种预设并保持顶部导航栏可见,使用:
Expo 默认使用页面的全部可用宽度,没有右侧大纲。需要较窄的主体时,在页面
front matter 中设置 page_width。若站点覆盖了 params.ui.shell_types,请在列表中
保留 expo,以启用它的侧栏。
翻译与导出相册
在 index.md 旁添加 index.zh.md,即可创建中文相册。每个翻译后的分区也要添加
_index.zh.md。两种语言可以共用图片文件。分别翻译各页的标题、描述与资源元数据。
同一张图片在两种语言中应使用相同 ID。
没有 JavaScript 时,HTML 页面仍然可用:图片链接直接打开原图,说明保持可见。 启用对应输出后,Print 使用静态预览,Markdown 与 LLMSFULL 包含图片链接及元数据, RSS 包含静态图片说明。输出配置见打印与 智能体输出。
检查相册
- 使用包含 Expo 的主题版本运行
hugo --printPathWarnings。 - 打开每个相册,确认图片顺序、标题与说明。
- 检查小图、宽图、长图,以及相册中的 GIF。
- 检查重置、放大至适屏、缩放、拖动、快捷键导航和相册定位。
- 检查两种语言、窄屏、四种外观预设,以及亮暗模式。
相册为空时,检查 type、页面包结构和 expo.images 匹配模式。默认值似乎未生效时,
检查浏览器保存的读者选择,以及配置块整体替换规则。具体行为与维护者检查见
Expo 契约和设计决策。
3.7 - 发布与下载页
OINK 将发布元数据存放在两处本地文件中:页面 front matter 的 release_url 指明对应的 GitHub Release,data/download/<key>.yaml 记录安装渠道。发布卡片、资产表、下载区块与索引页均从这两份本地记录生成链接,构建期不请求 GitHub。
front matter 里放了一个 release_url(OINK v0.4.0),下面的卡片、资产表与下载区块都是真实渲染。校验和与资产文件名是构造的:URL 由组件按仓库与标签本地推导,指向的文件在真实发布里不存在,不要用这里的哈希校验产物。
组件与事实来源
| 你要的 | 用什么 | 事实来自 |
|---|---|---|
| 版本摘要卡片(标签、日期、归档、仓库) | release-card |
页面的 release_url |
| 校验和资产表 | checksums 围栏 / release-assets |
正文里的 sha*sum 行 |
| 多渠道下载区块 | download |
data/download/<key>.yaml |
| 按时间排序的发布索引页 | layout: releases |
各页的 release_url,没有则用标题 |
页面拥有发布事实
发布页 front matter 里的一个键就是全部记录——精确到标签的 GitHub 发布 URL:
owner、项目名与标签从 URL 里解析出来,日期用页面自己的 date。不是精确
标签形式的 GitHub 发布 URL 会警告并跳过发布区块。0.5 的 release 映射(product / version / repo / tag / date /
prev / checksums)及其字符串简写已移除;仍携带它的页面会收到指名
release_url 的警告。
在需要摘要的位置放一个不带参数的 shortcode,调用里不接受任何事实:
v0.4.0 ·
卡片带着仅凭 URL 就能推导的四个链接——发布页、两种源码归档、仓库——全部本地推导。校验和文件放在正文下方的资产表里,版本对比在 GitHub 上看。
发布索引页
一个分区可以改用发布索引布局。它列出小节里的每一个常规页面,从新到旧 ——按页面日期排序,同一天内以标签里的版本号决胜(SemVer 优先级,非 SemVer 标签用确定的字典序兜底):
release_url 可解析的条目读作「项目名 + 标签」——如 oink v0.4.0——下一行
是页面描述;没有它的页面保留自己的标题,版本之间夹一篇普通短文是合法条目,
不是警告。0.5 的 release_products 过滤与 release_group_by_product 分组
已移除;写了会警告。
本站的版本发布目前用普通博客列表。需要严格时间序时改用 layout: releases。
校验和资产
checksums 围栏是校验和表的原生形态,围栏里写 sha*sum 命令的原样输出:
| 文件 | 校验和 |
|---|---|
| oink-0.4.0-linux-amd64.tar.gz Linuxamd64SHA-256 | 1e2f4c8a9d05b7361f8ac25d0e7b4913a6c8df215047eb9c3a1d6b8250f9e7c4 |
| oink-0.4.0-darwin-arm64.tar.gz macOSarm64SHA-256 | 7b3d9e0c145a8f26d0b7e93c48156aa2f0d9c7b31e846a5029df1b6c7a3e8250 |
只接受两种行格式:<十六进制><两个空格><文件名> 与 <十六进制><空格>*<文件名>,空行与 # 开头的注释行会被忽略。算法由哈希长度自动识别(MD5 / SHA-1 / SHA-256 / SHA-512),同一个代码块内只允许使用同一种算法;格式错误的行会输出带行号的警告并跳过。文件名必须是单个路径段;包类型、操作系统与架构徽章根据文件名自动推断,未命中时不显示徽章。
资产链接的基址:页面有 release_url front matter 时推导为 https://github.com/<repo>/releases/download/<tag>/;没有发布事实的页面必须显式写 base=。两者同时存在时报错。
release-assets 是同一个解析器与渲染器的 shortcode 形态。它多一个围栏没有的 src=,可以把校验和文件本身提交为页面资源或全局资产(src 与围栏内容互斥);group="auto" 按平台与架构分组:
.rpm
| 文件 | 校验和 |
|---|---|
| oink-0.4.0-1.el9.x86_64.rpm Linuxamd64SHA-256 | 5a0c7d1e93b4826f0ad35c9e17b6402d8f1c95ae63d70b28c4e19a5f38207db6 |
| oink-0.4.0-1.el9.aarch64.rpm Linuxarm64SHA-256 | c93f16a8d052b7e41ac68d3907b25fe0a41d8c7362b95e0187ac4d63f9520ea8 |
HTML 里哈希截断显示,完整哈希保留在无障碍名称与复制源里,复制按钮由按需加载的本地运行时提供。禁用 JavaScript 时仍是一张完整的带链接表格。打印展开完整哈希且不带控件,Markdown 与 RSS 是完整哈希的管道表。
下载渠道数据
安装方式属于产品,不属于某一次发布,因此存放在 data/download/<key>.yaml。本站真实的记录是 data/download/prd5.yaml:
顶层记录只允许 version、repo、tag、published、channels 五个字段(出现未知字段时会告警并跳过整条记录)。version 也可省略,改由站点的 params.version 统一提供。
version, ,- 两处都没有时告警并跳过区块
repo, ,- 固定版本渠道有链接或资产时必填
tag, ,- 只允许 URL 安全字符
published, ,false表示不可变发布还不存在channels, ,- 非空
每个渠道:
id, ,- 记录内唯一,用作锚点
kind, ,- 决定能不能插值版本事实
title, ,- 必须能解析出非空值
note, ,- 渠道下方的一行说明
icon, ,- 例如
fa-solid fa-bolt url, ,- 仅
pinned可插值 steps[], ,- 代码步骤走 OINK 的增强代码渲染器
checksums, ,- 仅
pinned;与checksums_src互斥 checksums_src, ,- 把校验和文件当作 Hugo 资产读入
两条规则:
- 本地化按后缀解析:
<字段>_<精确语言>→<字段>_<主语言>→<字段>。中文站解析title_zh_cn、title_zh、title。不接受 camelCase 别名。 - 只有固定版本渠道的
url与steps[].code能插值${version}与${tag}。滚动渠道拒绝插值,避免稳定版安装命令被绑定到某个版本。标题与说明不插值。
渲染下载区块
download 接受恰好一个位置参数,即数据键:
安装脚本
滚动渠道刻意不插入版本号。
源码归档
发布资产
| 文件 | 校验和 |
|---|---|
| oink-0.4.0.tar.gz SHA-256 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
HTML 渲染一排锚点 chip 加各渠道分区,代码步骤复用增强代码块与按需加载的复制运行时,校验和渠道复用上面那张资产表。打印静态展开同样的内容,Markdown 输出标题、源码围栏与完整哈希,RSS 不输出这个组件。
标签未打、资产未上传时,把记录标为未发布:
滚动渠道照常可用。固定版本渠道变成不可点击的「待发布」状态,省略固定版本命令,禁用资产链接与复制控件。标签与资产可解析之后再翻转这个开关,不要先在正文里写入推测出来的链接。
同一份记录也能被 Landing 页面的 download 分区消费,不需要第二套版本模型,见首页与落地页。
与博客发布注记的关系
两者分工:
- 博客里的发布注记(本站在
content/blog/release/)是叙事:这一版改了什么、怎么升级、有什么破坏性变更。它的 front matter 里带release_url,页首可以放一张release-card。写法见博客与文章。 - 下载数据是操作:选哪个渠道、运行哪条命令、校验哪个哈希。它与版本号解耦,升级时只改一处。
一次发布的顺序:更新 data/download/<key>.yaml 的 version → 新写一篇 content/blog/release/<version>.md 并填 release_url → 标签与资产就绪后把 published 翻成 true。
验证
- 构建生产:
hugo --printPathWarnings。哈希行格式、算法混用、缺base、渠道字段拼错都在这一步失败。 - 页面上:卡片显示的标签与日期与仓库一致;资产表每行都能点开真实的下载 URL。
- 逐条核对哈希与实际产物:组件只负责排版,不验证内容。
- 检查非 HTML 输出里哈希是完整的:
- 发布前先用
published: false走一遍,标签与资产确实存在后再改成true;每种语言、子路径部署各测一次。
相关
3.8 - API 文档
一页接口文档由一份 OpenAPI 规范加一个短代码构成。需要让读者试发请求时选 Swagger UI,以阅读端点说明与数据结构为主时选 Redoc。两个运行时都随主题分发,只在用到它们的页面的 HTML 输出中加载,不依赖 CDN。Swagger UI 的在线校验器已关闭;远程规范与 API 请求仍会访问各自配置的主机。
三个步骤:把规范文件放进 static/,新建一页写上 shortcode,需要专用外壳时把页面 type 改成 swagger。
规范文件的位置
规范文件放在 static/ 下,原样发布到站点根,两个 shortcode 得到的都是浏览器可取的 URL:
规范文件的位置
- static/
- openapi/
- docs-demo.yaml发布为 /openapi/docs-demo.yaml
- openapi/
- content/
- docs/
- write/
- openapi.zh.md这一页
- write/
- docs/
不要把规范文件放在页面旁边。两个 shortcode 都把本地值视为 static/ 下的路径,
都不解析页面资源。内容页面旁边的 .yaml 属于页面资源,仅在 shortcode 中写出
它的名字并不会让 Hugo 发布它,浏览器因此会得到 404。
远程规范(https://… 开头)两个 shortcode 都接受,但那是一项网络依赖,还会把读者的元数据暴露给那台主机。内网部署与有 CSP 的站点应当使用同源规范。只接受 http 与 https:其它 scheme、协议相对的 //host 或空值都会告警,shortcode 不渲染。
试用下面的例子时,下载 docs-demo.yaml,保存为自己站点的 static/openapi/docs-demo.yaml。它描述一份演示用的集群管理 API,没有可访问的服务端。
Swagger UI
swagger 只有一个具名参数 src,值是从站点根开始的 URL。它经过主题的 URL 校验,子路径部署同样正确:
在本地预览中,页面会显示可展开的 API 操作、请求参数与响应数据结构。“Try it out” 会向规范中的 servers 地址发送请求;示例没有可用的后端服务。
本页展示 Swagger UI 源码,下方提供 Redoc 实效。两个控件都有已知的无障碍限制,见限制。
Redoc
redoc 只接受一个位置参数,即规范路径。多写一个参数会告警,shortcode 不渲染。
OpenAPI 规格文件 — https://oink.pgsty.com/openapi/docs-demo.yaml
http 或 https URL 保持为远程地址。其它通过校验的值都是 static/ 下的路径,
开头有无斜杠等价。例如站点 baseURL 为 https://example.com/preview/ 时,
openapi/docs-demo.yaml 与 /openapi/docs-demo.yaml 都会变成
https://example.com/preview/openapi/docs-demo.yaml。与 swagger 不同,Redoc
接收的是这个基于 baseURL 的绝对 URL。
主题固定了 hide-hostname hide-logo suppress-warnings lazy-rendering native-scrollbars 五个属性,并用 CSS 隐藏 Redocly 品牌图标。Redoc 的其余属性目前不开放给作者,需要它们时在站点里覆盖 layouts/_shortcodes/redoc.html。
专用页面外壳
接口文档页通常较宽较长,可以用 swagger 页面类型:
swagger 是主题默认的外壳类型之一(params.ui.shell_types 默认是 [docs, book, blog, swagger],站点覆盖这个列表时需要保留它)。它与 docs 外壳的差别只有两处:<body> 上多一个 td-swagger class 供样式挂钩,以及不显示版本横幅。侧栏、目录、面包屑、翻页器与页尾都照常。
外壳与页宽的完整说明见布局与页面类型。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 完整的交互式 Swagger UI / Redoc;运行时按需加载,本地文件,无 CDN,且只在这一种输出里 |
| 打印 | 一行带标题的静态链接,规范地址可见;两套运行时都不加载 |
| Markdown | 一个纯 Markdown 链接 [OpenAPI 规格文件](/openapi/example.yaml),不会退化成接口清单 |
| RSS | 同样的纯链接 |
在 HTML 之外,接口文档是一个指路牌而不是一份参考。要让打印或 Agent 输出里也有接口信息,在同一页用正文写关键端点的说明;shortcode 之外的正文在四种输出里都完整保留。
限制与常见问题
- 两个组件的容器 ID 都按「页面地址 + shortcode 序号」推导,同一页放多个互不冲突。
- 两者可以同页共存,但页面会很长,HTML 输出也会同时加载两套运行时。正式站点选一个。
- 这两套第三方嵌入界面均存在上游无障碍限制:Swagger UI 包含未命名的服务器控件及无法通过键盘聚焦的滚动区域,Redoc 的接口说明文字对比度低于 AA 标准。若站点有严格的无障碍合规要求,建议同时在页面正文中提供关键端点的 Markdown 说明。
redoc不接受额外属性参数:写第二个位置参数会告警,shortcode 不渲染。- 本地
redoc路径以static/为根,开头的/可有可无;它不解析页面资源。 - 规范文件必须能被浏览器取到:放
static/,构建后确认public/下存在该文件。 - 没有服务端 mock:Swagger UI 的 “Try it out” 会向
servers里写的地址发起真实请求,示例规范里的地址不可访问。
验证
- 构建生产:
hugo --printPathWarnings。 - 规范确实发布了:
ls public/openapi/docs-demo.yaml,或访问http://localhost:1313/openapi/docs-demo.yaml。 - 页面上能展开端点、看到 schema;浏览器控制台没有 404 或跨域报错。
- 断开外部网络,但保持本地预览服务器可访问,再刷新页面:运行时与规范都来自本地时,界面应照常出现。
相关
4 - 组件总览
这一栏回答一个问题:某个组件在 Markdown 里怎么写。每页的顺序相同:最简例子、逐步深入的例子、输出形态、参数表、限制。查语法见下面的速查表。
两种形态
组件的第一形态是 Markdown 语法本身:块引用、列表、表格、图片、围栏,加上紧跟其后的一行 {…} 属性。原生形态在 GitHub 与任意 Markdown 编辑器中仍然可读,Markdown 输出保留的也是源码。
原生形态表达不了的场景使用 shortcode:正文标签页、带块级描述的参数表、带图标与徽章的卡片、终端录像。规则有五条:
- 所有 shortcode 都写
{{</* 名字 */>}},只有{{%/* steps */%}}用%分隔符,因为它的正文是页面级 Markdown。 - 嵌套名字(
tab、card、field)只在各自的父 shortcode 里有效。 - 作者参数写错不会静悄悄降级。普通预览会发出带源码位置的警告,并采用文档规定的 回退或略去不安全部分;发布前应审阅对应告警与降级产物。
- 公开字符串参数(图注、标签、标题)一律是纯文本,不解析 Markdown。只有正文是 Markdown:
tab、card、field的正文,include引入的文件,以及 Book 的fig、tbl、eg正文。 - 页面没用到的组件不下发运行时。HTML 只引用这一页真正需要的稳定能力分片,打印、 Markdown 与 RSS 不加载交互运行时。
站点前置配置
组件依赖三项 Goldmark 设置。OINK Starter 已经配好;从零建站时照抄以下片段:
renderer.unsafe: true:Goldmark 默认丢弃内容里的原始 HTML,关闭时组件正文里嵌套的 HTML 会消失。parser.attribute.block: true:属性行的总开关。关闭时{.steps}、{caption="…"}只是正文里的一行字符串。parser.wrapStandAloneImageWithinParagraph: false:独立成段的图片不再包进<p>,图片才能成为带图注的 figure,属性行才跟得上去。
个别组件另有前置条件:公式需要开启 Goldmark 的 passthrough,Draw.io 需要显式配置编辑器服务,各页分别说明。完整的配置键见配置总览。
OINK 1.4.0 启用 PlantUML 后,使用两份随主题提供的脚本在浏览器内本地渲染; 填写端点则继续使用服务端模式。两种模式见 PlantUML 指南。
速查表
「形态」列的取值:原生 = Markdown 语法加属性行;围栏 = 带语言标记的代码围栏;shortcode = {{</* … */>}}。「运行时」列说明这个组件是否往页面上下发 JavaScript。
| 组件 | 一句话 | 最短写法 | 形态 | 运行时 |
|---|---|---|---|---|
| 提示块 | 把前提、警告与折叠说明从正文中分离 | > [!NOTE] |
原生 | 无 |
| 图片 | 图注、尺寸、缩放、编号与构建期图片处理 |  |
原生 | 需站点开关 |
| 代码块 | 高亮、标题、复制、折叠、行链接 | ```sh |
围栏 | 按页加载 |
| 标签页 | 同一件事的多个平台或语言版本 | 属性行 {tab="Linux"} |
原生 + shortcode | 按页加载 |
| 表格 | 普通表格,加满宽、矩阵、标题与编号 | {.full-width} |
原生 | 标签页或 .expand 时加载 |
| 参数表 | 参数清单,带类型 / 必填 / 默认值芯片 | {.fields meta="type default"} |
原生 + shortcode | 无 |
| 步骤 | 有先后的流程 | {.steps} |
原生 + shortcode | 无 |
| 卡片 | 一组并列的去处 | {.cards} |
原生 + shortcode | 无 |
| 文件树 | 目录结构与对齐的注释列 | ```filetree |
围栏 | 按页加载 |
| 公式 | KaTeX 行内与块级公式 | $$ … $$ |
原生 | 无 |
| Mermaid | 流程图、时序图、甘特图 | ```mermaid |
围栏 | 按页加载 |
| PlantUML | UML 图;OINK 1.4.0 支持本地渲染 | ```plantuml |
围栏 | 需站点开关 |
| 思维导图 | Markdown 列表变成思维导图 | ```markmap |
围栏 | 需站点开关 |
| Draw.io | 可回编辑的图;需要自建服务 |  |
原生 | 需站点开关 |
| ECharts | 声明式数据图表 | ```echarts |
围栏 | 按页加载 |
| Infographic | AntV 信息图 | ```infographic |
围栏 | 按页加载 |
| 画廊 | 一组图片共用一个缩放对话框 | ```gallery |
围栏 | 需站点开关 |
| 徽章 | 行内状态标记 | {{</* badge text="Beta" */>}} |
shortcode | 无 |
| 按键 | 键位与组合键 | {{</* kbd "Ctrl" "K" */>}} |
shortcode | 无 |
| 引用 | 引入文件、插入站点参数、构建期注释 | {{</* include file="parts/x.md" */>}} |
shortcode | 无 |
| Asciinema | 终端录像 | {{</* asciinema file="images/x.cast" */>}} |
shortcode | 按页加载 |
「运行时」列的四条细则:
- 代码块只在块上有复制或折叠按钮时加载
code-block.js;文件树只在树带注释列时加载filetree.js,它负责拖动那条分栏线。 - 图片与画廊共用一个缩放对话框运行时,需要站点开启
ui.image_zoom,且页面上确有候选图。 - 公式在构建期由 KaTeX 渲染成 HTML 与 MathML,页面上只多一份 KaTeX 样式表与字体,没有脚本。
- Draw.io 只在渲染内容含 PNG 或 SVG 候选图的页面加载,并且每个不同的图片 URL 只检查一次。
每个组件在 HTML、打印、Markdown、RSS 四种输出下都有确定形态,见各页的「输出形态」一节。
4.1 - 提示块
> [!NOTE] 这样的块引用写出带颜色、图标与标题的提示、警告与折叠块,不需要短代码。提示块(Callout)是 GitHub / Obsidian 风格的块引用:> [!TYPE] 起头,正文跟在后面。用于把提示、警告、前提条件从正文中分离出来;正文一句话能说清的内容不必使用提示块。
最简例子
Hugo Module 需要本机安装 Go;只用离线归档时不需要。
不写标题时使用本地化的类型名(中文站显示「注意」,英文站显示 “Note”)。源码在 GitHub 上按 GitHub 的提示块渲染,在普通 Markdown 阅读器中显示为块引用,内容都不会丢失。
十种类型
前五种与 GitHub 一致,后五种是 OINK 追加的语义类型。每种类型有默认图标与强调色。
用 hugo server -D 可以预览草稿。
主题下限是 Hugo Extended 0.168.0,低于它构建直接失败。
hugo --cleanDestinationDir 会清空 public/。
删除 resources/_gen 后第一次构建会慢很多。
构建通过、生产——可以推上线了。
不要把 go.work 提交进仓库。
站点要不要开评论?看启用评论。
pgsty.com 就是一个只用了提示块与表格的纯文档站。
Documentation is a love letter that you write to your future self.
类型名不区分大小写。
自定义标题
标记同一行的后续文字是标题,支持行内 Markdown(代码、粗体、链接)。
public/生产构建前先确认 baseURL 指向正式域名,否则所有绝对链接都会指错。
正文内容
正文是页面级 Markdown:列表、代码围栏、表格、图片、嵌套的提示块。每一行都以 > 开头,围栏也不例外。
- 克隆:
git clone https://github.com/pgsty/oink-starter my-docs - 进入目录并预览:
- 打开 http://localhost:1313/
| 端口 | 用途 |
|---|---|
| 1313 | Hugo 开发服务器 |
折叠
类型后加 - 默认收起,加 + 默认展开;两者都渲染为原生 <details>,不加载 JavaScript。适用于完整输出、备选方案、背景说明这类不必默认展示的内容。
Hugo 通过 Go 的模块系统下载主题(hugo mod get)。用 submodule 或离线归档时可以不装 Go。
收起状态不会被记住,刷新后回到默认。
中性折叠块 DETAILS
[!DETAILS] 是没有语义颜色的折叠块:不加符号默认收起,[!DETAILS]+ 默认展开。用于冗长输出、完整配置文件等需要折叠的内容。
hugo version 输出自定义图标
块引用结束后的下一行写属性 {icon="fa-solid fa-xxx"}(一对 Font Awesome class),替换该类型的默认图标。属性行紧接块引用,中间不能有空行。
从 Pigsty v4 起默认安装 PostgreSQL 18。
嵌套
提示块可以嵌套(每层多一个 >),也可以放在列表项或步骤中。建议最多嵌套一层。
升级主题版本可能改变渲染结果。
git tag pre-upgrade 就够了——回滚只是 git checkout pre-upgrade。
未知类型与易错写法
未知的类型名不会导致构建失败,也不会丢失内容:该块渲染为普通块引用,[!TYPE] 标记原样可见。
[!NOTICE] 这不是合法类型
标记会保留在页面上提醒你。
其它常见问题:
- 正文与标题合并:经过 Prettier 等格式化工具的文件,在标题行下保留一个空的
>行,否则工具会把标题并入正文。 - 属性行被格式化工具移动:把
{icon=…}这类标记行放在<!-- prettier-ignore-start -->/<!-- prettier-ignore-end -->之间。 style、onclick与不支持的属性会告警并忽略:属性行只接受icon与class(见下表)。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 静态类型是 <div class="td-callout" role="note">;折叠类型是原生 <details> + <summary> |
| 打印 | 全部静态展开,折叠块带 data-td-callout-collapsible 标记 |
| Markdown | 保留源码块引用(含 [!TYPE] 标记与标题) |
| RSS | 与打印相同,静态展开 |
提示块不加载脚本。
参数参考
标记行 > [!TYPE]± 标题:
TYPE, ,NOTETIPIMPORTANTWARNINGCAUTIONSUCCESSDANGERQUESTIONEXAMPLEQUOTEDETAILS;大小写不敏感;未知值渲染为普通块引用±, ,-折叠默认收起,+折叠默认展开;DETAILS不加符号即收起标题, ,- 与标记同一行
属性行 {…}(块引用之后紧接的一行):
icon, ,- 例如
fa-solid fa-database;DETAILS默认无图标 class, ,- 原样透传给站点 CSS
style、on* 与其它键会告警并忽略。
限制与常见问题
- 不能自定义颜色:颜色由类型决定,需要新语义时选最接近的类型并自定义标题。
- 折叠状态不持久化。
- 提示块可以放在
{.steps}列表项与{{%/* steps */%}}步骤中(见步骤),块引用的每一行都以>开头,缩进与列表项对齐。
相关
4.2 - 图片
图片只有一种写法:Markdown 的 。独立成段的图片可以在下一行跟一行 {…} 属性,成为带图注的 figure、缩放候选、编号图或经 Hugo 处理的派生图。主题没有图片 shortcode。
最简例子

这张图与本页放在同一目录(页面包)中,主题读取它的固有尺寸并写入 width/height,页面加载时不发生跳版;所有图片懒加载。替代文字供屏幕阅读器与搜索引擎使用,应当始终填写;空 alt 表示装饰性图片,缩放会跳过它。
图片来源
来源按以下顺序解析,写法相同:
| 放法 | 源码里怎么写 | 适合 |
|---|---|---|
与页面同目录(页面包 index.md + 图片) |
 |
只有这一页用的截图;随页面一起移动、翻译共用 |
当前分区的页面包(_index.md 及其资源) |
 |
分区内的图片;路径相对于该分区的资源目录 |
全局资源 assets/images/… |
 |
多页共用、还要做处理(缩放 / 裁切)的图 |
静态目录 static/images/… |
 |
不需要处理的大图、下载物;主题拿不到尺寸时可以用 width/height 补 |
| 远程 URL |  |
少用:构建期不会下载,也不能处理 |
相对路径依次按页面资源、当前分区资源、全局资源查找,都找不到时按静态路径输出;主题不检查
静态路径与远程 URL 是否存在。要求处理(command=)却解析不到可处理资源时,普通
预览告警并保留未处理图片;发布前应审阅告警并检查回退结果。
Markdown 中的替代文字优先于资源 metadata,包括明确表示装饰图的空 alt。资源的
params.alt 必须是字符串;无效 metadata 会告警并被忽略,保留正文中编写的 alt。
发布前应审阅告警并检查回退结果。
行内与块级
位于文字中间的是行内图片,渲染为一个 <img>,不能带属性;独立成段的是块级图片,可以带属性行。
这一枚小图
夹在句子里,是行内图片。

行内图片按自身尺寸显示(这里是 50×32)。没有固有尺寸的 SVG 行内插入时会被拉伸到容器宽度,SVG 应作为块级图片使用并给出 width/height。
块级图片依赖站点设置 markup.goldmark.parser.wrapStandAloneImageWithinParagraph: false(本站已配置;见配置总览)。缺少它时 Goldmark 会把独立图片包进 <p>,属性行也会被当作正文。
图注
属性行加 caption="…",图片渲染为 <figure> + <figcaption>。图注是纯文本,不解析 Markdown。

Markdown 里的 "标题" 保持原义(悬停提示),不会成为图注。
尺寸
width/height 是正整数,覆盖资源自身的尺寸:为静态或远程图片提供占位框以避免跳版,或把大图缩小显示(浏览器缩放,不改文件)。

处理型图片
页面资源与全局资源可以在构建期由 Hugo 处理:command 与 options 必须同时给出,命令是 Fit Resize Fill Crop 之一,选项是 Hugo 的图片处理字符串。渲染出的 src 是派生图;启用缩放时对话框打开原图。


静态路径、远程 URL 与 SVG 不能处理。对它们写 command 时告警并保留未处理图片。选项语法(锚点、质量、格式转换,如
300x150 webp q80)见 Hugo 图片处理。
链接图片
两种写法,用途不同:
- 没有图注、图片本身是链接:用 Markdown 的链接包图
[](href)。 - 有图注的 figure 整体可点:属性行加
link="…"(必须同时有caption或num)。

带链接的图不参与缩放。没有图注只写 link= 时告警并丢弃链接,消息提示改用 [](…)。
编号图
编号图用于书籍与长篇手册:属性行加 num,可选 #id。编号是作者书写的字符串(2-1、3.4),主题不自动计数;图注前加本地化的「图 2-1」前缀,#id 缺省为 fig-<num>。正文用普通链接 [图 2-1](#fig-2-1) 或 xref shortcode 引用;全书图目录见书籍出版。

见图 2-1。
编号图可以同时是处理型图片(num + command),也可以带 link。
缩放
图片缩放默认关闭。站点开启后,块级图片、figure、画廊中带 alt 的图成为可点击的按钮,在原生 <dialog> 中查看大图(Esc 关闭,焦点回到原处)。本页在 front matter 中开启了它,上面的图都可以点击。
自 OINK 1.1 起,预览操作和图片描述保留在按钮的无障碍名称中,不会给复制的
文章增加辅助文字;复制得到的富文本 HTML 保留图片和作者写的图注。v1.0.0 的隐藏预览
标签可能在粘贴后出现。升级之前,可设置 image_zoom: false 并重新构建来避免这个标签。
不缩放的图:行内图、alt 为空的装饰图、带链接的图、data-no-zoom 标记的图。运行时只在页面确有候选图时加载;打印 / Markdown / RSS 中没有对话框。

深浅色图片
主题没有按深浅色切换图片的参数。需要两张图时,各写一个 class,在站点 CSS 中按 [data-bs-theme="dark"] 显示其一:
把 sidebar-light.webp 与 sidebar-dark.webp 换成自己的浅色、深色图片。主题把 data-bs-theme 设在 html 上,上面的选择器让对应图片单独显示;class 由主题原样透传给站点 CSS。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 行内 <img>;块级 <img class="td-image">;有图注 / 编号时 <figure class="td-figure"> + <figcaption>;缩放候选带 data-td-image-zoom |
| 打印 | 同 HTML,去掉缩放控件 |
| Markdown | 原样输出  与属性行 |
| RSS | 图片 src 改为绝对地址;无缩放 |
参数参考
属性行 {…}(块级图片之后紧接的一行):
caption, ,- 有它就渲染成 figure;不解析 Markdown
#id, ,[A-Za-z][A-Za-z0-9_.:-]*;作为锚点与 Book 目标 IDnum, ,[0-9A-Za-z.-]+;注册为 Book 图目标,图注加「图 N.」前缀width/height, ,- 覆盖尺寸;静态 / 远程图靠它避免跳版
command, ,FitResizeFillCrop;必须与options同给;仅页面 / 全局资源options, ,- Hugo 图片处理选项,如
600x300、300x150 Left、800x webp q80 link, ,- 把 figure 包进链接;需要
caption或num;带链接的图不缩放 class, ,- 透传给站点 CSS
data-*/aria-*, ,- 透传
style、on*、alt、title、src 与不支持的键出现在属性行时告警并忽略。alt、title、src 属于 Markdown 图片本身。
限制与常见问题
- 图注不含 Markdown:所有公开字符串参数都是纯文本;富文本说明写在图片下方的段落中。
title不是图注:的c是悬停提示。- 处理型图片只对资源生效:
static/中的图需要处理时移到页面包或assets/。 - 构建期不下载远程图片。
- 缩放不支持拖拽、平移、上一张 / 下一张;一组相关图片使用画廊。
相关
4.3 - 代码块
代码块是普通的 Markdown 围栏,高亮由 Hugo 内置的 Chroma 在构建期完成,浏览器里没有高亮器。用于命令、配置片段与源码:围栏信息行上的 {…} 属性决定标题栏、复制行为、行号与行锚点。图示类围栏(mermaid、echarts、filetree 等)不走这条路径,它们各有渲染钩子。
可选呈现
单行围栏加上 {class="td-code-command"} 后显示为紧凑命令条,复制按钮独占一列。此变体随 OINK 1.4.0 提供,参见示例与回退规则。多行、带行号或可折叠代码块会给出警告并保留普通呈现。
最简例子
没有属性的围栏同样有完整外壳与复制按钮。无标题栏时不渲染空白横条,复制按钮浮在右上角,鼠标悬停或焦点进入块内时出现,触屏设备上始终可见。外壳不显示语言名,lexer 名字只写入 data-language,供样式表与测试使用。
语言标记就是 Chroma 的 lexer 名。diff 围栏用 Chroma 的增删行样式呈现补丁,不需要额外组件:
文件名标题
title 给块加一条可见标题栏,通常写文件名或路径。它同时成为这个块的无障碍名称。
filename 是 title 的历史别名,两个一起写时告警并使用 filename。
行号、起始行与高亮
lineNos 取 inline(行号与代码同一列)或 table(行号独立成列,可单独选中不被复制)。lineNoStart 改显示的起始编号。hl_lines 标记要强调的行,计数按围栏内的源码行,从 1 开始,与 lineNoStart 无关。
lineNos="table" 把行号放进独立的一列(两种模式下复制按钮都会剔除行号):
tabWidth 决定制表符展开成几个空格,与 style 一样原样转交 Chroma。本站使用基于 class 的 Chroma 调色板(深浅色各一套),style 只在把 Hugo 切回内联样式模式时才生效。
长行换行
wrap=true 只改变显示:源码不变,复制出来的文本也不变。不加它时长行横向滚动。
wrap=true 与表格行号不能共存:行号列与代码列是两个表格单元格,换行后会错位。
写在一起时告警并关闭换行,提示改用 lineNos="inline" 或去掉换行。
折叠长代码
collapse=N 让块初始只显示 N 行,底部给一个「显示全部 N 行」按钮。服务器输出完整代码,折叠是浏览器量出第 N 行位置后的视觉裁切:没有 JavaScript 时、读屏器中、打印时代码都是完整的。
行数不超过 collapse 时按钮不出现。换行与折叠可以一起用:折叠测量的是第 N 个源码行节点的底边,换行的行不会被截断。
复制内容
默认复制整块源码。终端会话(console 与 shell-session 两个 lexer)默认只复制命令:带提示符的行留下,提示符本身与输出行去掉。下面这个块复制出来只有两条命令,没有 $ 也没有输出。
要连提示符与输出一起复制就写 copy="all"。把 copy="command" 用在 bash、sh
之类普通 lexer 上时告警并使用 copy="all",因为它们分不出提示符、命令与输出。
多行命令请在续行里写出续行提示符(通常是 >),否则
那一行会被当成输出而排除。
会话 lexer 的块里一行提示符都没有时,复制按钮报失败:图标转为错误状态,控制台留一条错误,剪贴板不变。它不会退化成复制全文。
copy=false 关掉这一块的复制按钮,用于不应被抄走的反例片段:
用 params.ui.code_copy: false 默认关闭复制,围栏显式写出的 copy 会覆盖这个默认值(见配置总览)。复制按钮只有图标,成功与失败会换图标并播报本地化状态;复制内容保留缩进、空行与 Unicode,去掉行号,末尾只留一个换行。
行链接与稳定 ID
把「看第 3 行」做成链接需要两步:给围栏一个明确的 id,再打开 anchorLineNos=true。行号随即变成锚点链接,锚点是 #<id>-<行号>。
跳到 第 4 行。
不写 id 时主题也会生成一个页面内唯一的 ID,但它依赖围栏在页面里的顺序,前面
插入一个新围栏就会变。只有作者书写的 id 才是永久链接。ID 不能含空白与控制字符,
也不能与页面上其它块的 viewport、标签、面板、标题、行锚点 ID 重复;无效或重复 ID
会告警并改用自动生成的 ID。
编号例
写书或长手册时给代码片段编号:num 加 caption,这个围栏就成了一条 Book「示例」目标,可以被 xref 引用,也会进入全书的示例目录。编号由作者书写,主题不自动计数;id 默认是 eg-<num>。
参见 示例 4-1。
num 与 caption 必须成对出现。只写 caption 时忽略它,只写编号时丢弃编号并告警。
num 与标签页属性 tab 互斥。图、表、公式的编号写法与
索引见书籍出版。
一组围栏做成标签页
连续几个带 tab 的围栏会在浏览器里合成一个标签页集,第一个围栏上的 group 让它可分享、可同步、可记住选择。
完整规则(分组语法、URL hash、跨组同步、正文标签页)在标签页。
易错写法
- 在文档里展示 shortcode:围栏不阻止 Hugo 解析,写在代码块里的
{{< tabs >}}仍会执行。要让它原样显示,在两侧定界符的内侧各加一对注释符号,写成{{</* tabs */>}},百分号形式对应{{%/* steps */%}}。本页每一处展示 shortcode 的地方都是这么写的。 - 围栏里套围栏:外层用四个反引号、内层三个,本页每一段「源码」都是这么写的;内层还有围栏时外层再加一个。
- 属性写在信息行上:围栏的属性跟在开栏那一行的语言后面,表格与图片的属性才写在块的下一行。写到下一行会变成正文里一段可见的花括号。
- 未知、不安全与主题保留属性会告警并忽略,消息列出允许的名字。
- 列表项里的围栏:缩进要与列表项内容对齐(
1.之后恒定三个空格),否则围栏会脱离列表。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <div class="td-code"> 外壳 + Chroma 的 .highlight/.chroma;复制、折叠按钮在服务器输出里是 hidden,脚本确认可用后才显示 |
| 打印 | 完整代码,去掉复制、折叠、渐隐;长块允许跨页;标题栏保留 |
| Markdown | 原样输出源码围栏,连 {…} 属性一起 |
| RSS | 静态代码块,无按钮 |
没有复制或折叠控件的页面不加载 code-block.js;打印、Markdown 与 RSS 输出不加载。
参数参考
开栏那一行、语言之后的 {…} 里,OINK 自己的属性:
title, ,- 可见标题栏(通常是文件名),同时是无障碍名称
filename, ,title的历史别名;两者同时出现时告警并使用filenamecopy, ,true等价于all;command只允许console/shell-sessionwrap, ,- 视觉换行,不改源码;与表格行号互斥
collapse, ,- 初始显示的最大行数;行数不足时不生效
label, ,- 无障碍名称,不显示在页面上;与
aria-label互斥 id, ,- 稳定的块 ID 与行锚点前缀;不能含空白
tab, ,- 标签名,见标签页;与
num互斥 group, ,- 写在一组的第一个围栏上,启用 hash / 同步 / 持久化;需要
tab value, ,- 分组内每个围栏必填,无分组时禁止;需要
tab num, ,- 编号示例(Book
eg);必须与caption同时出现 caption, ,- 编号示例的说明;必须与
num同时出现 class, ,- 追加到
.td-code根元素 data-*/aria-*/role, ,- 透传到根元素
title、filename 与 label 已经为块生成了无障碍名称与 role="group"。它们中的
任意一个与 aria-label、aria-labelledby 或 role 同时出现时告警并忽略冲突属性。
这三个属性只在块没有标题也没有 label 时可以透传。
同一行还能写 Chroma 选项,主题原样转交 Hugo:
lineNos, ,- 行号形态;
table与wrap=true互斥 lineNoStart, ,- 显示的起始行号,不影响
hl_lines的计数 hl_lines, ,- 如
"2 4-5",按围栏内源码行计数 anchorLineNos, ,- 行号变成锚点链接,前缀取自块的
id tabWidth, ,- 制表符展开的空格数
限制与常见问题
- 不换高亮器:没有 Shiki、Twoslash、浏览器端高亮,也没有可执行的代码演练场。补丁用
diff围栏,Chroma 的.gi/.gd就是增删行的样式。 copy="command"只认会话 lexer:用于其他语言时会告警并回退到copy="all"。- 自动生成的 ID 不是永久链接:要发链接就写
id。 mermaid、math、chem、markmap、plantuml、echarts、infographic、checksums、filetree、gallery不是代码块:它们有各自的渲染钩子,不套这层外壳,也没有复制按钮。
相关
4.4 - 标签页
{tab=} 属性就得到标签页;加上 group 之后可分享链接、跨组同步、记住读者的选择。标签页并列等价的几种写法:包管理器、发行版、YAML / TOML / JSON、环境变量与配置项。有先后的步骤、互不相关的内容不适合标签页,读者一次只看见其中一个。
原生形态是给相邻的块加 tab 属性。正文(多个段落、列表、提示块)要做成标签页时才用 tabs/tab shortcode。两种形态共用一个运行时、一套 DOM 与一样的键盘行为。
最简例子
连着写两个带 tab 的围栏,中间只隔空行。
服务器输出两个带标题的代码块,没有面板被隐藏;页面加载后运行时把相邻的同类块重组为标签页。在 GitHub 上、打印时、关闭 JavaScript 时,读者看到的是连续两块完整内容。
分组:链接、同步与记忆
只在第一个块上写 group,这一组就有了公开的 URL hash #<group>-<value>、页内同步与浏览器持久化;分组内的每个块都要写 value。
value 是机器值(^[a-z0-9][a-z0-9_-]*$),tab 是给人看的标签名,两者互不相干。上面这组的 pnpm 面板对应的 hash 是 #pkgmgr-pnpm,带这个 hash 访问本页会直接选中它。
同组联动
下面这组用了同一个 group="pkgmgr"。在上面那组切换包管理器,这组会跟着切;在这组切换,上面那组也跟着切。选择写入 localStorage 的 td-tabs:v1:pkgmgr 键,在其它页面同组的标签页上仍然生效。
这组没有 yarn 面板。同步时缺哪个值就保持不动,不会出现「一组没有选中项」的状态。初始选哪个的优先级是:URL hash,存储的值,shortcode 的 default 或第一个块,第一个标签。带 hash 打开页面只切换,不覆盖读者已经存下的偏好。
表格也能做标签页
同一套属性写在表格的属性行上,连着的表格就组成一组标签页。
| 参数 | 默认值 |
|---|---|
shared_buffers |
25% RAM |
max_connections |
100 |
| 参数 | 默认值 |
|---|---|
shared_buffers |
128MB |
max_connections |
100 |
围栏与表格是两种块类型,相邻也不会合成同一组:一组标签页里只能全是围栏或全是表格。两者混排使用下面的 shortcode 形态。
标签名与文件名共存
围栏的 tab 和 title 可以一起写:标签名进标签栏,文件名标题栏留在面板里。
单独一个块只是带标题的块
一个块要凑够两个相邻的同类块才会变成标签页。落单的块保留标题,不会变成只有一个标签的标签栏。
块之间只允许空行。三种情况会断开一组:中间隔了正文(段落、标题、列表都算);中间有一条 HTML 注释,<!-- prettier-ignore-end --> 是常见的一处;后一个块自己写了 group,一组里只有第一个块可以带 group。
正文标签页
面板里要放段落、列表、提示块或多个块时,用 tabs/tab shortcode。正文是完整的 Markdown。
仓库自带 .github/workflows/,推到 main 就会构建并发布。
baseURL 要写成仓库的 Pages 地址。
在 Cloudflare 控制台里连接仓库,构建命令:
default 指定初始选中的面板,它必须是某个子项的 value,并且需要 group。没有 group 时不能写 value,主题自动生成 tab1、tab2 等值,这组标签页只在本地切换,不动 URL 也不写存储。shortcode 形态比属性形态严格:写错的地方在构建期就报出来,不留到浏览器里。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <div class="td-tabs"> + role="tablist" 的按钮与面板;运行时接管前所有面板都可见 |
| 打印 | 连续的带标题静态分节,没有标签栏 |
| Markdown | 围栏形态保持源码围栏(含 {tab=} 属性);shortcode 形态输出 **标签名** 加正文 |
| RSS | 与打印相同,堆叠的带标题分节 |
只有用到标签页的页面才加载 tabs.js;打印、Markdown 与 RSS 输出不加载。
参数参考
写在围栏信息行或表格属性行上的属性:
tab, ,- 可见标签名;单独出现时就是这个块的标题
group, ,- 写在一组的第一个块上,启用 hash、页内同步与持久化;需要
tab value, ,- 分组内每个块必填,无分组时禁止;需要
tab
tabs shortcode:
group, ,- 同上,启用 hash、同步与持久化
default, ,- 初始选中的面板;需要
group label, ,- 标签栏的无障碍名称,不显示在页面上
tab shortcode:
label, , required- 可见标签名
value, , required- 无分组时禁止书写,自动生成
tab1、tab2等值
行为约定:面板 ID 在分组里是 <group>-<value>,同一页出现第二组同名 group 时后续各组的 ID 加 -2、-3 后缀(深链目标始终是第一组),未分组时由主题生成;存储键是 td-tabs:v1:<group>;用户点击或按键会用 replaceState 更新 hash 并写入存储,带 hash 访问只切换不写入。键盘上左右方向键(感知 RTL)与 Home/End 移动并激活标签,焦点停留在标签上。
限制与常见问题
- 无效分组与组合会在 Hugo 构建中告警并采用安全回退:丢弃不可用的 group/value/default、忽略夹杂正文、保留后出现的重复项,或不渲染空集合,消息带源码位置。
- 属性形态没有可用
value时失去同步能力,只保留本地标签页;分组不会静默编造身份。 - 围栏与表格不会混成一组,正文与代码混排请用 shortcode 形态。
- 标签页不是折叠块。只想收起长输出用
> [!DETAILS](见提示块)。 - 同名
group是全站共享的:读者在 A 页选了 pnpm,B 页同组的标签页也会是 pnpm。这是它的用途,也意味着group名要按含义取,不用tabs1这种。
相关
4.5 - 表格
表格是普通的 GFM 管道表格。主题的表格渲染钩子把每张表包进一块可横向滚动的区域,表格下面那一行 {…} 属性决定它是哪一种表:带标题的表、兼容矩阵、参数表、编号表或标签页。合并单元格、排序与筛选不在能力范围内,需要它们的场景请改换呈现方式。
最简例子
不写属性行就是一张普通表。对齐方式照旧来自分隔行,表头单元格是 th scope="col"。
| 组件 | 端口 | 用途 |
|---|---|---|
| PostgreSQL | 5432 | 数据库 |
| Pgbouncer | 6432 | 连接池 |
| Patroni | 8008 | 高可用编排 |
宽表格自己滚动
列太多的表不会把页面撑宽,它在自己的区域里横向滚动。这块区域可以用键盘聚焦:Tab 停入后方向键滚动,无障碍名称是本地化的「可横向滚动的表格」。
| 集群 | 角色 | 版本 | 状态 | 延迟 | 连接数 | 大小 | 备份 |
|---|---|---|---|---|---|---|---|
| pg-meta | primary | 18.1 | running | — | 42 | 12 GB | 2026-08-17 |
| pg-test | replica | 18.1 | streaming | 12 ms | 8 | 12 GB | 2026-08-17 |
表格标题
{caption="…"} 加一个可见的 <caption>,纯文本,不给表编号。
标题在表格框内保留上下留白,并与下方列名的起始位置对齐。
| 条目 | 取值 |
|---|---|
| 主题版本 | v0.8.1 |
| Hugo 下限 | 0.168.0 Extended |
| 许可证 | Apache-2.0 |
兼容矩阵
{.matrix} 用于「行 × 列 = 支持与否」的对照表:第一列成为行表头(th scope="row"),滚动时表头行与第一列吸附不动,其余单元格居中,分隔行另有对齐时以分隔行为准。✅ 与 ❌ 是作者写的字符,主题不解析它们。
| OS / PG | PG18 | PG17 | PG16 | PG15 | PG14 |
|---|---|---|---|---|---|
| EL 9 | ✅ | ✅ | ✅ | ✅ | ✅ |
| EL 8 | ✅ | ✅ | ✅ | ✅ | ✅ |
| Debian 13 | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ubuntu 24.04 | ✅ | ✅ | ✅ | ✅ | ❌ |
用整个画布
{.full-width} 让表格越出正文栏宽,占满文章可用的宽度。适合列多但每列都短的表。
| 语言 | 代码 | 侧栏 | 搜索 | 目录 | 打印 | 状态 |
|---|---|---|---|---|---|---|
| 简体中文 | zh |
✅ | ✅ | ✅ | ✅ | 已审校 |
| English | en |
✅ | ✅ | ✅ | ✅ | 已审校 |
技术宽表格
.wide这个简短标记随 v1.4.0 发布,沿用已有技术宽表样式并兼容旧写法。
包含长配置键、环境变量或长标识符的技术表格,若默认压在正文列宽内排版,
容易导致标识符异常断行或描述列被极度压缩。在属性行中显式添加 {.wide .expand},
内部表格会采用按内容最大尺寸(width: max-content)排版,并在超出视口时触发
横向滚动。
| 变量 | 必填 | 默认值 | 对应配置项 | 描述 |
|---|---|---|---|---|
HG_PD_GRPC_HOST |
是 | 无 | grpc.host |
对外宣告的 gRPC 主机名或 IP 地址;容器网络内部应使用容器主机名。 |
HG_PD_INITIAL_STORE_COUNT |
否 | 1 |
pd.initial-store-count |
集群可用时所需的最少存储节点数量。 |
HG_PD_ACTUATOR_EXPOSURE |
否 | health,metrics,prometheus |
management.endpoints.web.exposure.include |
对外开放的监控端点白名单,不允许使用通配符。 |
与 .full-width 的区别
.full-width控制「外层画布」:让表格外层容器突破正文栏宽限制,扩展至整页可用宽度;适合列数较多但各列内容较短的表格。.wide控制「内部表格排版」:让内部<table>以自身内容的最大自然宽度(width: max-content)排版,依靠横向滚动容纳长内容;适合包含长代码标识符的技术表格。- 两者可以结合使用:例如
{.full-width .wide}既让外层容器占满全宽画布,又让内部列保持宽松的自然宽度。
适用边界与排版策略
- 显式开启:普通表格保持现有布局。只有
.wide表格使用overflow-wrap: break-word、word-break: normal,并以max-width: 40ch作为正文单元格的软宽度约束。特别长的标识符仍可换行,该约束不是固定列宽。 - 静态输出:此策略只作用于屏幕。Print 与 RSS 使用静态包装并保留原有布局;
Markdown 保留作者的 GFM 表格及属性行。该标记对
.fields定义列表没有作用。
展开指定表格
.expand此功能随 v1.4.0 发布,为少数需要更大空间的表格提供角落阅读入口。
只给需要更大阅读空间的表格加 .expand。小展开图标位于表格右上角:桌面端
悬停或键盘聚焦时显示,触屏设备常显。点击即可展开,使用关闭按钮或 Escape
回到原来的阅读位置。字号、链接和文字选择保持不变;小屏幕上的宽内容仍可横向滚动。
| 扩展包 | 版本 | 用途 |
|---|---|---|
pgvector |
0.8.1 |
向量相似度检索 |
pg_stat_statements |
1.11 |
语句统计 |
Goldmark 支持多个 class:{.wide .expand} 与 {class="wide expand"} 等价。
需要开启 markup.goldmark.parser.attribute.block: true,属性行紧接表格最后一行,
中间不要留空行。
.wide 控制布局,.expand 提供阅读入口,两者均可独立使用。
.expand 还可以与 .full-width、.matrix、标题、编号或标签页组合。
没有自动判断大小的阈值,也没有全站开关。原有的 .td-table--wide 写法继续兼容 .wide。
只有包含显式标记表格的页面才加载小型阅读脚本。没有 JavaScript、脚本未加载或
浏览器不支持原生对话框时,保留完整的正文表格,不显示无效按钮。
Print 和 RSS 保持静态表格,Markdown 保留作者语法;浏览器打印前会先将展开的表格
还原到正文。headers、aria-labelledby 或 aria-describedby 引用了表格外节点时,
保持正文阅读;依赖文章祖先元素的自定义样式,需要检查阅读器中的效果。
参数表
{.fields} 把表格变成定义列表:第一列是名称,最后一列是说明,中间列是元数据。它是记录配置项、命令参数、API 字段的形态,写法见参数表。
offline_search, ,- 构建本地搜索索引
page_width, ,- 正文栏宽度
编号表
写书或长手册时给表编号:num 加可选的 #id 与 caption。表格会被包进一个带本地化「表 N.」标签的 <figure>,并注册成 Book 目标,可以被 xref 引用、进入全书表格目录。编号由作者书写,主题不自动计数;id 缺省是 tbl-<num>。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读已提交 | 否 | 是 | 是 |
| 可重复读 | 否 | 否 | 是 |
| 可串行化 | 否 | 否 | 否 |
参见 表 9-1。
表格做成标签页
连着的表格加 {tab="…"} 就组成一组标签页,规则与相邻围栏一致:第一张表上的 group 启用 hash、同步与持久化,此后每张表都要 value。完整规则见标签页。
| 目录 | 内容 |
|---|---|
content/ |
页面 |
data/ |
首页与发布数据 |
| 目录 | 内容 |
|---|---|
assets/ |
SCSS 与图片资源 |
static/ |
原样拷贝的文件 |
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <div class="td-table-scroll"> 可聚焦滚动区 + <table>;矩阵与全宽是这个包装器上的修饰 class |
| 打印 | 保留完整静态内容;浏览器预览提供可键盘滚动的区域,打印媒体样式在实际打印时展开区域 |
| Markdown | 原样输出源码表格与属性行 |
| RSS | 完整静态表格 |
普通表格不加载脚本;.expand 仅在 HTML 中加载阅读器,标签页表格还会加载标签页运行时。
参数参考
表格下一行的属性行:
.full-width, ,- 越出正文栏宽,占满文章画布
.wide, ,- 内部表格采用 max-content 宽度排版,适合配置项等技术宽表
.expand, ,- 在角落提供可选的表格阅读对话框入口
.matrix, ,- 第一列作行表头,表头与首列吸附,其余单元格居中
.fields, ,- 渲染成定义列表,见参数表
caption, ,- 可见表格标题;在
.fields上是列表的标签 meta, ,- 命名
.fields中间列的语义,取值typerequireddefault-;必须与.fields同用 #id, ,[A-Za-z][A-Za-z0-9_.:-]*;写在<table>(编号表则写在<figure>)上num, ,[0-9A-Za-z.-]+;注册为 Book 表目标,标题前加「表 N.」tab/group/value, ,- 相邻表格组成标签页
class, ,- 站点 CSS 用,原样留在
<table>上 data-*/aria-*, ,- 透传
style、on* 与其它键会告警并忽略。
限制与常见问题
- 互斥规则:
.fields不能和.matrix、.full-width、.expand或num一起用;num与tab互斥;group/value需要tab;meta需要.fields。 - 属性行必须紧贴表格:中间空一行,它就变成正文里一段可见的花括号。Markdown 格式化工具常移动这一行,把它包进
<!-- prettier-ignore-start -->/<!-- prettier-ignore-end -->。 - 没有合并单元格、没有排序、没有筛选:GFM 管道表格能表达的就是全部。需要合并表头的复杂表请拆成两张表或改成一张矩阵。
- 单元格里放不下块内容:多段说明、列表、围栏要用
fields/fieldshortcode。 .matrix的居中由 CSS 实现:分隔行里写了对齐就以分隔行为准。
相关
4.6 - 参数表
{.fields} 记录配置项、命令参数与 API 字段:名称、类型、默认值、说明各就各位,窄屏不挤,每条都能单独链接。参数表(Fields)把「一串具名值 + 元数据 + 说明」渲染成响应式定义列表:名称独占一行,类型、是否必填、默认值是名称旁边的小字,说明另起一行,每一条自带锚点。用于配置项、命令参数与 API 字段。要按同一批列横向比较很多行时用普通表格,内容是操作顺序时用步骤。
写法有两种:普通表格加 {.fields}(默认选它),以及 fields/field shortcode(说明需要多个段落、列表或代码块时才用)。两种形态渲染出相同的条目。
最简例子
一张至少两列的管道表格,下一行写 {.fields}。第一列是名称,最后一列是说明,中间每一列都是元数据,标签就是表头文字本身。
offline_search, ,- 构建本地搜索索引并启用命令面板
offline_search_max_results, ,- 搜索结果条数上限
page_width, ,- 正文栏宽度,可选
normalwidefull
这里的元数据显示成「表头: 值」。主题不推断表头的含义,类型 只是一个标签;要让它变成标准标签见下一节。单元格接受行内 Markdown(代码、强调、链接),空的中间单元格省略。
语义列 meta=
meta 按顺序说明每一个中间列扮演什么角色:type(类型)、required(必填)、default(默认值),或者 -(保留表头当标签)。有了它,表格形态渲染出的标签与 shortcode 形态一致。
baseURL, , required- 站点地址,含子路径
title, , required- 站点名,出现在顶栏与页签
defaultContentLanguage, ,- 默认语言,决定无前缀路径属于哪种语言
规则:
meta应为每一个中间列写一个角色,个数等于总列数减二;写多写少时告警并忽略meta,发布前应审阅告警并检查回退结果。required列是「非空即真」:单元格里写「是」「yes」「✔」都一样,渲染出来的是不翻译的required标签;可选项留空就不显示,写「否」或no同样会被视为必填。type与default单元格如果本身没有行内标记,会自动套上代码格式,与 shortcode 形态对齐。- 三种语义标签按
type、required、default的顺序显示,与列的顺序无关;-列跟在后面,按列顺序排。
- 可以和语义角色混用,用来保留一列自定义标签:
HUGO_MODULE_WORKSPACE, ,- 指向
go.work,让主题从本地 checkout 解析 HUGO_ENV, ,- 选择生产模式,OINK 为 CSS/JS 资源生成指纹;HTML 压缩使用
--minify
标签与容器 ID
caption 给整张表加一个可见标签(同时是无障碍名称),id 命名外层容器,方便从别处链接过来或写站点 CSS。
params.ui
image_zoom, ,- 打开图片缩放
featured_image, ,- 文章题图模式:
none、banner、wash或hero
每一条都能单独链接
每个条目获得一个 field-<名称> 形式的锚点,鼠标移上去时名称右边出现自链接图标。上面第一张表里的 page_width 就是 #field-page_width,回答问题时可以把这一行的链接单独发出去。
同一页里重名的字段按 -2、-3 顺延,规则与 Goldmark 处理重名标题一致。锚点只在 HTML 里生成:打印和 RSS 会把很多页拼成一个文档,页内锚点在那里会冲突。
shortcode 形态
说明需要多个段落、列表或代码块时,表格单元格装不下,改用 fields/field:
pig 命令常用参数
--config, , required配置文件路径。相对路径按当前工作目录解析。
如果同时设置了
PIG_CONFIG环境变量,命令行参数优先。--log-level, ,日志级别,从低到高:
debug:打印每一次远程调用info:默认值error:只在失败时输出
--dry-run, ,只打印将要执行的动作,不改任何东西:
required=true 与 default=false 是布尔值,不加引号。default 接受任何标量:default=0、default="" 都会如实显示(空字符串显示成 ""),不写 default 就不显示这一项。每个 field 必须有非空正文,并且必须是 fields 的直接子项。
两种形态的选择
| 情况 | 用法 |
|---|---|
| 每条说明一句话,能放进表格单元格 | 表格 + {.fields} |
| 说明要分段、带列表或代码块 | fields/field shortcode |
| 读者需要按同一批列横向比较很多行 | 用普通表格,不转成参数表 |
| 内容是操作顺序 | 用步骤 |
表格形态在 GitHub 上仍然是一张可读的表,OINK 的 Markdown 输出也保持表格原样,这是默认选它的理由。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <div class="td-fields"> + 语义 <dl>;条目带 #field-<名称> 锚点与自链接 |
| 打印 | 完整定义列表,不带条目锚点 |
| Markdown | 表格形态保留源码表格;shortcode 形态输出「名称 — 类型;required;default: 值」加缩进说明的项目符号列表 |
| RSS | 完整静态 <dl>,不带条目锚点 |
不加载任何脚本。
参数参考
表格属性行(写在表格下一行):
.fields, ,- 必需;把表格渲染成参数表
meta, ,- 空格分隔,取值
typerequireddefault-;个数等于中间列数;语义角色不可重复 caption, ,- 可见标签,同时是列表的无障碍名称
id, ,- 外层容器的 ID
class, ,- 透传给站点 CSS
data-*/aria-*, ,- 透传
fields shortcode:
label,- 可见标签,作用同表格的
caption id,- 外层容器 ID;不能含空白、引号、
<、>、& class/data-*/aria-*,- 与表格属性行同一套策略
field shortcode:
name, , required- 字段名
type,- 类型标签,如
booleanstring[]duration required,true时显示不翻译的required标签,默认falsedefault,- 字符串 / 布尔 / 整数 / 浮点;
false、0、""都会显示
限制与常见问题
- 第一列必须非空,且在同一张表内唯一:重名或空名时告警并跳过该行。
.fields不能与.matrix、.full-width、num组合,meta不能用在没有.fields的表上。- 表格单元格里放不下块内容:需要段落、列表、围栏就换 shortcode 形态。
required与default是不翻译的 API 词汇,在所有语言下都显示英文,它们是契约词,不是界面文案。- 暂不支持
kind、since、deprecated、location、字段级链接与嵌套结构,也不会在构建时解析 TypeScript 或 OpenAPI schema。
相关
4.7 - 步骤
{.steps} 就是带编号圆点与竖线的操作步骤;步骤要带标题、要进目录时改用 steps shortcode。步骤(Steps)是带编号圆点与竖线的有序列表:一个普通有序列表,加一行 {.steps} 标记,编号圆点与串起它们的竖线由 CSS 绘制,不加载脚本。用于有先后的操作流程。并列而无先后的内容用普通列表或卡片。
写法有两种:有序列表加 {.steps}(默认选它),以及 {{% steps %}} shortcode,每一步要有自己的标题、标题还要进右侧目录时用它。
可选呈现
简短流程使用 {.steps .td-steps-compact},带标题的完整形式使用 steps variant="compact"。这些可选变体随 OINK 1.4.0 提供,参见对照示例。编号、嵌套和标题锚点保留原有含义。
最简例子
每一项都写 1.,让 Markdown 自己数。这样插入、删除、调换步骤都不用手改编号,而且内容缩进恒定是三个空格。
- 安装 Hugo Extended
- 克隆 OINK Starter
- 启动本地预览
{.steps} 必须紧贴列表最后一行,中间空一行它就会变成正文里一段可见的花括号。
步骤内容
列表项里可以放任何块级内容:段落、代码围栏、提示块、表格、嵌套列表、图片。缩进对齐到列表项的内容列(三个空格)即可。
-
克隆 OINK Starter,它是面向项目的精简模板。
-
启动本地服务器。
说明首次构建会通过 Go 模块代理拉取主题,需要本机安装 Go。
-
替换三处内容,它就是你的站点。
位置 替换为 hugo.yml的title你的站名 hugo.yml的baseURL你的域名 content/你的内容
{{< … >}} 形式的 shortcode(标签页、卡片、徽章等)也可以写在列表项里;{{% … %}} 形式不行,见下面的限制。
一步里按平台分开
某一步在不同平台上命令不同时,把带 {tab=} 的围栏并排写进那个列表项,它们照样会合成标签页。
-
安装 Hugo Extended。
-
安装依赖:
EL / RHELDebian / Ubuntu -
运行
hugo server预览。
接着上一组往下编号
正文隔断了一组步骤时,把新一组的第一项写成它实际的序号,Markdown 会输出 start,编号从那里继续(支持到 40)。
- 配置
baseURL与部署工作流。 - 推送到
main,等待 GitHub Actions 构建完成。
带标题的步骤
步骤本身很长、每一步该有个能被链接和被目录收录的标题时,用 {{% steps %}}:它的正文是页面级 Markdown,里面的每一个直接子标题就是一步,正文不用缩进。下面三步的标题就在这一页的右侧目录里。
安装工具链
需要 Hugo Extended ≥ 0.168.0 与 Go。
启动服务器
brew install hugo go
sudo apt install hugo golang-go
发布
推送到 main,仓库自带的工作流会构建并发布。
它是主题里唯一的 {{% … %}} shortcode。百分号形式的正文交给 Goldmark 当页面级 Markdown 处理:只有这样,里面的标题才能进目录,里面才能放 tabs、cards、fields 这些容器 shortcode。代价是它自己不能嵌进列表项,也不能嵌进另一个百分号容器。
同一组步骤的标题保持同一层级,不要把一个 steps 套进另一个里。
两种形态的选择
| 情况 | 用法 |
|---|---|
| 步骤是一两句话加一段命令 | 有序列表 + {.steps} |
| 每一步需要标题、需要被链接、需要进目录 | {{% steps %}} |
步骤里要放 tabs、cards、fields 容器 |
{{% steps %}} |
| 步骤本身要嵌在另一个列表项里 | 有序列表 + {.steps} |
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 原生形态是 <ol class="steps">,编号与竖线由 CSS 画;shortcode 形态是 <div class="td-steps"> 加各级标题 |
| 打印 | 编号与内容照旧,竖线保留 |
| Markdown | 原样输出源码:有序列表加 {.steps},或标题加正文 |
| RSS | 静态列表 / 标题分节 |
不加载脚本;关闭 JavaScript 后呈现不变。
参数参考
OINK 1.4.0 的完整形式支持可选的 variant="compact"。其他写法约定如下:
{.steps},- 必需;写在无序列表上不生效
1.,- 让 Markdown 自己数;内容缩进恒为三个空格
,4.(首项)- 输出
<ol start="4">,编号从 4 接着走,支持 2–40 {{% steps %}},- 直接子标题(
##–######)就是步骤;正文不缩进
限制与常见问题
- 列表项里不能写
{{% … %}}:百分号 shortcode 的多行输出会把列表截断。要在步骤里放容器就整组改用 shortcode 形态。 {{% steps %}}不能放进列表项,也不能套在另一个百分号容器里。- 标记要紧贴列表:
{.steps}与列表之间不能有空行;经过 Prettier 之类的格式化工具时,把它包进<!-- prettier-ignore-start -->/<!-- prettier-ignore-end -->。 {.steps}只对有序列表有效:写在-开头的无序列表上不会有编号。- 步骤不折叠、不记进度:没有「已完成」状态,也没有展开收起。
相关
4.8 - 卡片
{.cards} 的链接列表排出导航卡片网格;需要图标、徽章、图片时改用 shortcode。卡片(Cards)是一组并列的链接:每张卡片一个链接标题加一句描述,网格随容器宽度自适应。适合栏目首页、「接下来读什么」与几条并列路径的入口。不适合排版正文段落(用普通段落)或做图片墙(用画廊)。
可选呈现
使用 {.cards .td-cards-list} 呈现共用分隔线的单列资源列表,完整形式使用 cards variant="list"。这些可选变体随 OINK 1.4.0 提供,参见对照示例。默认仍是响应式网格。
最简例子
带 {.cards} 的链接列表就是卡片。链接是标题,— 之后是描述。
整张卡片是点击热区,不只是标题文字。没有 columns 参数:列数由容器宽度决定,窄屏收成一列。
只有标题的卡片
描述可以省略。一行一个链接,{.cards} 收尾。
松散列表与多段描述
一句话装不下时改用松散列表:链接单独一段,描述另起一段,列表项之间空一行。标题独占一行,描述在标题下方。{.cards} 仍然紧贴最后一段,中间 不能有空行。
图标与徽章
链接列表不支持图标、徽章、图片与多段描述,这些用 cards / card shortcode。icon 是恰好一对 Font Awesome class,badge 是一段纯文本。
图标不是一对有效的 Font Awesome class 时,普通预览告警并丢弃图标;发布前应审阅告警并检查回退结果。
Markdown 正文
card 的正文按页面级 Markdown 渲染:行内代码、强调、链接、列表都可以。title、badge 这些参数是纯文本,不解析 Markdown。
hugo mod get github.com/pgsty/oink。推荐方式,升级只需改一行版本号。
无需安装 Go:
git submodule add- 主题落在
themes/oink
不写 link 的卡片渲染成加粗标题,不生成链接。
带图片的卡片
image 与  的解析顺序一致:页面资源 → 当前分区资源 → 全局资源 assets/ → 静态路径 /images/… → 远程 URL。本地资源带上固有尺寸,避免加载跳版。
image 需要一个替代文字来源:image_alt="…"(有信息的图)或
decorative=true(纯装饰)。两个都写时告警并保留 alt;两个都不写时告警并按装饰图
渲染。发布前应审阅告警并检查回退结果。
卡片图片不参与图片缩放,整张卡片本身已经是链接。
栏目首页的自动卡片
栏目首页(_index.md)不需要手写卡片列表:主题读子页的 title、description、icon 自动生成一组卡片。本站在 hugo.yml 中全局启用:
单个栏目可以在自己的 front matter 里覆盖,也可以用 cascade 把选择推给整棵子树:
自动卡片与手写卡片使用同一套 td-content-card 样式,区别只在数据来源。栏目首页不要手写子页清单:手写清单会与侧栏不同步。要排的内容不是本栏目的子页时(例如混合站外链接、跨栏目推荐),才在正文里手写卡片。相关键的完整定义见配置总览。
两种形态的选择
| 你要的 | 用哪种 |
|---|---|
| 一句话描述的链接网格 | {.cards} 链接列表 |
| 图标、徽章、图片 | cards / card shortcode |
| 描述里要列表、代码、多段 | cards / card shortcode |
| 没有链接的卡片 | cards / card shortcode |
| 本栏目的子页 | 什么都不写,靠 section_index: cards |
链接列表在 GitHub 上仍是一个链接列表,shortcode 不是。能用原生形态时用原生形态。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 原生形态是 <ul class="cards">;shortcode 形态是 <div class="td-content-cards"> + 每张 <article class="td-content-card">。两者都是纯 CSS 网格,不加载脚本 |
| 打印 | 原生形态竖排,shortcode 形态收成两列;两者的单张卡片都避免跨页断开 |
| Markdown | 原生形态原样输出链接列表;shortcode 形态输出 - [标题](链接) (徽章) — 描述 |
| RSS | 与 HTML 同样的标记(没有站点 CSS 时是一份可读的链接清单) |
参数参考
原生形态:
{.cards}, ,- 写在无序列表 之后 的一行;只对无序列表生效
列表项首个链接, ,- 卡片标题,同时是整张卡片的点击目标
其余内容, ,- 描述。紧凑列表里跟在
—后面,松散列表里另起一段
card 的参数(cards 另支持可选的 variant="list"):
title, ,- 必填,非空。卡片标题
link, ,- 站内路径、相对路径、
http(s):、mailto:;外链自动加rel="noopener" icon, ,- 例如
fa-solid fa-rocket;格式不符时告警并丢弃 badge, ,- 标题右侧的小标签
image, ,- 页面资源 / 全局资源 / 静态路径 / 远程 URL
image_alt, ,- 有
image时与decorative二选一 decorative, ,true表示装饰图,输出空 alt正文, ,- 卡片描述
没有 cols、columns、accent、desc、color 参数。未知参数会告警并忽略。
限制与常见问题
{.cards}只认无序列表:有序列表加了这个标记不会变成卡片。{.cards}必须紧贴列表:中间空一行、或缩进进列表项,标记被静默丢弃,构建不报错,列表仍是列表。渲染结果不是卡片时先检查这一行。card只能待在cards里:单独使用、或放进别的 shortcode 时告警并跳过。- 列数不可配:网格按容器宽度自适应,只有栏目首页的自动卡片能用
params.ui.section_index_columns指定列数。 - 卡片不放长文:描述超过两行时改用正文段落或提示块。
相关
4.9 - 文件树
filetree 围栏画带注释的目录结构:对齐的注释列、逐条目图标、可折叠目录、可拖动的分栏。文件树(FileTree)是一个 filetree 围栏,围栏正文就是目录清单:缩进表示层级,结尾的 / 表示目录,# 之后是注释。适合解释一份目录结构里与读者有关的那部分,并逐条加上说明。需要读者逐字复制的清单用普通代码块。
最简例子
- content/
- _index.zh.md
- docs/
- blog/
- hugo.yml
- go.mod
项目符号(-、*、+)可以省略,效果相同。有子项的条目是目录;没有子项时,结尾的 / 告诉主题它是目录。
加注释
每行第一个前面带空白的 # 之后是注释,渲染成对齐的右列。注释是纯文本,里面的 Markdown 按字面显示;要一个字面井号就写 \#。
- content/全部页面,中英双语同目录
- docs/你正在读的这棵文档树
- blog/发布说明与文章
- assets/scss/站点自己的 SCSS,覆盖主题变量
- layouts/站点级模板覆盖,越少越好
- static/images/不需要构建期处理的图
- hugo.yml站点配置:语言、菜单、params.ui
注释列的起点在构建期算出,由最宽的一行决定,因此每行的 # 从同一列开始,与源码里是否对齐无关。注释列最多占面板的右半边,最少占三成。中间的虚线是分隔条,可以拖动,也可以用 Tab 聚焦后按方向键调整(Home / End 到两端)。
默认(wrap=false)情况下,过长的名称与注释各自在本列内用省略号截断,鼠标悬停时由 title 提示完整文本。分隔条是文件树唯一的 JavaScript,只有 带注释 的树才加载它。
两列都发生截断
- runbooks/
- a-deliberately-long-runbook-filename-for-a-failover-drill.md同样超长的注释,写在一行里,因此必须在注释列内截断
- restart.md短名字
长注释换行
在 filetree 围栏上设置 wrap=true,即可在注释列中完整显示长注释,包括没有
空格的长标识符;行高随内容增长。默认的 wrap=false 保持紧凑行与省略号。
名称、目录折叠、分隔条操作、移动布局与全部展开的打印输出保持原有行为。
完整存储注释
- storage/
- metadata/元数据定义:属性键、顶点标签、边标签、索引和计数器。
- graph/图数据:顶点、边、索引表,以及 OLAP 分析任务使用的数据。
无效 wrap 值会告警并回退到 false。发布前应审阅告警并检查回退结果。
标题栏
围栏属性 {title="…"} 在树上方渲染一条标题栏;不写时没有标题栏。
oink.pgsty.com 仓库根目录
- content/页面
- assets/参与构建的资源
- data/首页、Landing、下载页的数据
- layouts/模板覆盖
- static/原样拷贝的文件
- tests/Playwright 与 node --test
- hugo.yml
- go.mod用 Hugo Module 引入主题
- Makefilemake d / make b / make c
缩进与层级
层级由缩进决定。两个空格、四个空格、制表符(按四列计算)都可以,同一棵树内不要求统一,条件是每次退回的层级此前已经打开过。tree 命令的输出可以整段粘贴,包括开头的根目录行与结尾的统计行,统计行会被丢弃。
- content/docs
- about
- _index.zh.md
- features.zh.md
- components
- filetree.zh.md
- image
- index.zh.md
- _index.zh.md
- about
退回到未打开过的缩进层级时告警并跳过该行,消息带围栏内的行号。
折叠与显式类型
有子项的目录默认展开,{open=false} 使其初始收起。目录用原生 <details> 渲染,键盘可操作,不需要 JavaScript。open 只能写在目录上。没有子项、名字也不以 / 结尾的条目按文件处理,{type=dir} 覆盖这个判断,{type=file} 同理。
内容目录
- content/
- docs/新文档树
- components/22 个组件页
- callout.zh.md
- filetree.zh.md
- image/页面包:正文 + 图
- customize/站点级配置
- config.zh.md
- components/22 个组件页
- blog/
- release.zh.md
- docs/新文档树
图标与配色
图标默认按名字推断:目录用文件夹图标,随开合切换;文件先按完整文件名匹配(LICENSE、Makefile、go.mod、package.json、.gitignore 等),再按扩展名匹配(md yml toml json sh py go js sql css png svg pdf zip 等),都不匹配时用普通文件图标。
{icon=…} 覆盖它,取值是恰好一对 Font Awesome class。{tone=…} 给图标上色,取值与徽章相同:neutral info success warning danger。
部署目录:权限与要点
- /etc/pigsty/0755 root:root · 配置根目录
- pigsty.yml0644 root:root · 集群清单
- ca/0700 root:root · 自签 CA,不要提交进 Git
- ca.key0600 root:root
- /var/lib/pgsql/18/data/0700 postgres:postgres · 数据目录
- postgresql.conf0600 postgres:postgres
- /usr/bin/pig0755 root:root · 命令行工具
tone 只给图标上色,不改文字。颜色是补充,含义写在名字或注释里。
条目链接
条目名写成 [名字](链接) 即为链接。站内路径、相对路径、http(s): 都可以,URL 校验与其它组件是同一套。
本站的组件页
- content/docs/components/
- callout.zh.md提示块
- filetree.zh.md当前页面
- gallery.zh.md画廊
- image/页面包
- hugo.yml站点配置(GitHub)
按平台分成标签页
围栏带 tab=(以及 group= value=)时成为一组标签页中的一页,可以与代码围栏混排。
- /etc/pigsty/配置
- /var/lib/pgsql/数据
- /usr/bin/pig可执行文件
- ~/Library/Application Support/pigsty/配置
- /opt/homebrew/bin/pig可执行文件
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <div class="td-filetree">,可选标题栏,目录是原生 <details>;带注释时多一条可拖动分隔条(唯一的运行时) |
| 打印 | 同一棵树,全部展开,没有分隔条,注释换行不截断 |
| Markdown | 原样输出 filetree 围栏 |
| RSS | 围栏源码放进 <pre> |
窄屏(小于 sm 断点)时布局收成单列:注释移到名称下方,不再截断,分隔条隐藏。不带注释的树是单列,也不加载任何脚本。
参数参考
围栏属性(写在 ```filetree 后面):
title, ,- 树上方的标题栏;不写就不画;不能为空
wrap, ,- 长注释换行,行高随内容增长;文件名继续省略
tab, ,- 让这棵树成为一个标签页
group/value, ,- 标签页分组与同步值;必须与
tab同时出现 class, ,- 透传给站点 CSS
条目属性(写在每行末尾的 {…} 里):
icon, ,- 例如
fa-solid fa-lock;格式不符时告警并使用默认图标 tone, ,neutralinfosuccesswarningdanger,只给图标上色open, ,- 仅目录;
false表示初始收起 type, ,dir或file,覆盖自动判断
行语法本身:
缩进- 两个空格 / 四个空格 / 制表符 /
tree的│ ├── └──连线都行 - name- 项目符号可省略;
-*+等价 name/- 结尾斜杠表示目录;名字原样渲染,斜杠保留
[name](url)- 带链接的条目
# 注释- 第一个前面带空白的
#之后的内容;\#是字面井号 N directories, M filestree的统计行,自动丢弃
未知属性、未知取值、写在文件上的 open、格式错误的 {…}、退回到未打开过的
缩进层级,都会告警并采用安全回退或跳过坏行,消息给出围栏内行号。
限制与常见问题
- 只有
filetree围栏这一种形态:没有{.filetree}列表标记,也没有 shortcode。 - 注释与名字都是纯文本:写
**粗体**会原样显示,围栏源码在任何环境里都读得通。 - 不读取磁盘:树是手写或粘贴的静态内容,不随仓库变化。
- 不提供搜索、多选、复制整棵树:需要逐字复制时用普通代码块。
- 分栏宽度不持久化:拖动过的位置刷新后回到构建期算出的默认值。
相关
4.10 - 公式
公式由 KaTeX 在构建期渲染成 HTML + MathML,页面只额外加载一份本地 KaTeX 样式表,没有 JavaScript,也不请求远程数学服务。行内公式写 \(…\),块级公式写 $$…$$、\[…\],另有 math 与 chem 两种围栏。需要 TikZ 绘图或 KaTeX 不支持的宏包时,改用预渲染的图片。
生成的公式按 Hugo Extended 0.168.0 与内置样式表验证,保留 TeX 与 MathML; 无需浏览器数学运行时或额外的站点开关。
站点前置配置
math 与 chem 围栏无需配置。$$、\[…\]、\(…\) 这些分隔符依赖 Goldmark 的 passthrough 扩展。Hugo 不合并主题的 markup 配置,这段必须写在站点自己的配置文件里。本站使用下面这份:
各键的完整定义见配置总览。分隔符不能与站点正文冲突:单个 $ 没有配进去,避免「$5」这样的价格被当成公式。
最简例子
行内公式写在句子中,前后的空格与标点留在分隔符外面。
共享缓冲区命中率是 ,其中 是 blks_hit, 是 blks_read。
块级公式
独占一段的公式用 $$ 包起来,居中显示,字号更大。\[…\] 是等价写法。
一棵扇出为 、共 个键的 B 树,其高度为:
一行装不下的长公式在正文列内横向滚动,不会把版面撑宽;打印时保持静态。
math 围栏
math 围栏是块级公式的另一种写法,不依赖站点的 passthrough 配置。源码在 GitHub 上是一个普通代码块。
上式是 Little 定律在连接池上的形式:稳态下需要的并发连接数等于到达速率乘以平均响应时间。连接池大小通常远小于客户端数量。
化学式与单位
chem 围栏使用 KaTeX 的 mhchem 扩展,正文写 \ce{…}。同一个扩展也能排物理单位。
语法见 mhchem 手册。
编号公式
块级公式下面跟一行属性即成为编号公式。num 是作者书写的字符串(3-1、5.3),主题不自动计数;#id 不写时默认为 eq-<num>。编号显示在公式右侧,前缀「公式」按站点语言本地化。
见公式 3-1:乘上保留天数就是归档盘容量的下限。
caption(纯文本)可以省略。#id 与 caption 必须与 num 同时出现,不存在
「半编号」的公式。不完整或重复目标会告警,并丢弃不可用部分或保留第一项。窄屏中的长公式标题在阅读列内换行,不会撑宽整页。
交叉引用
正文可以用普通链接引用编号公式,上一节即是这种写法。跨页引用、或需要自动带上「公式 N」标签时用 xref:
容量规划从 公式 3-1 开始。
xref 可以写在目标之前,前向引用合法。整本书的公式目录、book-equations 索引见书籍出版。
eq shortcode
eq 供无法开启 passthrough 的站点使用,正文交给同一个 KaTeX 渲染器。不带参数时是一个不注册编号的块级公式;带 num 时与上一节的属性行形态等价。
本站已开启 passthrough,日常写作用 $$。eq 用于迁移来的书稿与不能修改 hugo.yml 的场合。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 构建期渲染好的 KaTeX HTML + MathML;本页额外加载一份本地 katex.min.css,没有公式的页面不加载 |
| 打印 | 同 HTML,静态,长公式不滚动 |
| Markdown | 原样输出源码:$$ 块(连同下面的属性行)、math / chem 围栏、\(…\);eq shortcode 输出 **公式 3-2.** 说明 + 一个 $$ 块 |
| RSS | 与 Markdown 相同的静态文本 |
任何形态都不加载 JavaScript。
参数参考
四种写法:
\(…\),- 由站点 passthrough 配置决定;不能带属性
$$…$$/\[…\],- 同上;可以跟一行属性变成编号公式
```math,- 不依赖 passthrough 配置;不接受属性
```chem,- 同上,正文写
\ce{…}
块级公式的属性行 {…}:
num, ,[0-9A-Za-z.-]+;注册为编号公式,右侧显示「公式 N」#id, ,[A-Za-z][A-Za-z0-9_.:-]*;锚点与交叉引用目标caption, ,- 编号后面的说明;需要
num
eq shortcode 的参数:
num, ,- 同上;不写就是一个不编号的普通块级公式
id, ,- 需要
num caption, ,- 需要
num class, ,- 需要
num;透传给站点 CSS 正文, ,- 必填,非空
TeX 写错时普通预览告警并保留原表达式。消息带 KaTeX 详情与源码位置;发布前应审阅告警并检查回退结果。
限制与常见问题
- 分隔符由站点配置决定:
$$、\[…\]、\(…\)是否渲染只取决于站点markup.goldmark的 passthrough 扩展。front matter 里写math: true主题不读,缺少配置时$$仍然原样显示;改用math围栏或eq可以绕开。 - 只有
$$块和eq能编号:math围栏不接受属性行,需要编号就换写法。 - 编号是手写的:主题不自动计数,也不重排;调整章节顺序要自己改
num。 - 行内公式不能带属性:属性行只对块级公式有效。
caption是纯文本:里面的 Markdown 不解析。
相关
4.11 - Mermaid
mermaid 围栏把文本写成流程图、时序图、甘特图、类图与状态图,本地渲染、跟随深浅色、diff 友好。mermaid 围栏把一段文本渲染成流程图、时序图、甘特图、类图、ER 图与状态图。图以源码形式存在,可以进 Git、可以 review diff、可以被搜索命中;渲染由主题自带的 Mermaid 在读者浏览器里完成,不请求外部服务。需要像素级控制的示意图画成 SVG,按图片使用。
最简例子
flowchart LR
内容["content/"] --> Hugo
配置["hugo.yml"] --> Hugo
主题["OINK 主题"] --> Hugo
Hugo --> 站点["public/"]围栏语言写 mermaid 即可,没有其它开关。主题检测到这个围栏后才把 Mermaid 运行时加入这一页,同一页里画十张图也只加载一次。
时序图
sequenceDiagram 描述参与者之间按时间发生的消息,适合说明请求链路与加载顺序。
sequenceDiagram
autonumber
participant 读者 as 读者浏览器
participant CDN as 静态托管
participant JS as 页面脚本包
读者->>CDN: GET /zh/docs/components/mermaid/
CDN-->>读者: HTML(一个 figure 加围栏源码)
读者->>CDN: GET 本页的脚本包
CDN-->>读者: mermaid.min.js
JS->>JS: 把围栏源码渲染成 SVG
Note over JS: 未使用的运行时不下载甘特图
gantt 画时间区间。下面是 PostgreSQL 各大版本从发布日算起的五年社区支持期,1825d 即五年。
gantt
title PostgreSQL 大版本的五年社区支持期
dateFormat YYYY-MM-DD
axisFormat %Y
section PG 15
发布于 2022-10-13 :2022-10-13, 1825d
section PG 16
发布于 2023-09-14 :2023-09-14, 1825d
section PG 17
发布于 2024-09-26 :2024-09-26, 1825d
section PG 18
发布于 2025-09-25 :active, 2025-09-25, 1825d类图与 ER 图
classDiagram 画类型与关系,erDiagram 画实体与基数。两者都常用来解释数据模型。
classDiagram
class Page {
+string Title
+string Description
+int Weight
+Content()
+OutputFormats()
}
class Resource {
+string Name
+string RelPermalink
+Resize(spec)
}
class OutputFormat {
+string Name
+string MediaType
}
Page "1" --> "0..*" Resource : 页面包资源
Page "1" --> "1..*" OutputFormat : html / print / markdown / rsserDiagram
pg_database ||--o{ pg_namespace : "包含模式"
pg_namespace ||--o{ pg_class : "包含关系"
pg_class ||--o{ pg_attribute : "包含列"
pg_class ||--o{ pg_index : "被索引"
pg_class {
oid oid PK
name relname
char relkind
}
pg_attribute {
oid attrelid FK
name attname
smallint attnum
}状态图
stateDiagram-v2 画状态与迁移条件。下面是 OINK 主题发布依次经过的五个阶段。
stateDiagram-v2
[*] --> 源码完成
源码完成 --> 已验证 : 主题检查脚本 + 站点测试套件全绿
已验证 --> 已发布 : 推送不可变的签名 vX.Y.Z 标签
已发布 --> 已文档化 : 站点 go.mod 钉住该标签
已文档化 --> 已部署 : 生产构建上线
已部署 --> [*]
已发布 --> 源码完成 : 发现问题只能出新补丁版本,标签不移动单张图的标题与配置
围栏正文最前面可以写 Mermaid 自己的 YAML 头,它不是 Hugo front matter。title 给图加标题,config 覆盖这一张图的 Mermaid 配置。写死 config.theme 的图不再跟随站点深浅色。
---
title: 只有用到的运行时才会进包
config:
flowchart:
curve: linear
---
flowchart TD
页面 --> 判断{用了什么组件?}
判断 -->|Mermaid 围栏| M[mermaid.min.js]
判断 -->|ECharts 围栏| E[echarts.min.js]
判断 -->|都没用| B[只有基础包]深浅色
页面初始化时主题读取当前配色模式:深色模式下用 Mermaid 的 dark 主题,浅色模式下用站点配置的主题。读者切换配色时图会就地重绘,页面不会重载;重绘期间每张图保持原有高度,页面不会在读者眼皮底下跳动。
站点级默认写在 hugo.yml 里,键名小写,主题按 Mermaid 的默认配置匹配回正确的大小写:
完整键表见配置总览,可用值以 Mermaid 配置文档为准。
放进标签页与步骤
mermaid 围栏没有 tab 属性,相邻围栏标签页只对普通代码围栏生效。并排比较两张图用 tabs shortcode。
flowchart LR
Markdown --> Goldmark --> 渲染钩子 --> HTMLflowchart LR
页面 --> HTML
页面 --> 打印
页面 --> Markdown
页面 --> RSS{{% steps %}} 里的每一步是页面级 Markdown,其中可以写 mermaid 围栏,用法见步骤。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 一个 figure,保留可读源码,图表接近视口后在舞台中渲染 SVG |
| 打印 | <pre class="td-mermaid-source"> 包着的源码,静态输出,不跑运行时 |
| Markdown | 原样保留 mermaid 围栏与它的源码 |
| RSS | <pre class="td-mermaid-source"> 包着的源码,订阅端看到的是文本 |
参数参考
围栏属性:没有。mermaid 围栏不读属性行,写 {height=…}、{class=…} 之类既不生效也不报错;尺寸由图自身与容器宽度决定,并在其中居中。
站点参数(hugo.yml):
params.mermaid, ,- 整个映射按 Mermaid 的
initialize()配置传入;键名写小写,主题按 Mermaid 默认配置匹配回正确大小写 params.mermaid.theme, ,- 浅色模式下的主题;深色模式下被强制为
dark
单张图的配置写在围栏正文最前面的 YAML 头里(title、config),属于 Mermaid 语法,不是主题参数。
放大查看
以下共用查看器与按需加载行为已随 v1.4.0 发布。
Mermaid 与本地 PlantUML 共用查看器。通过图表角上的按钮打开,直接使用已渲染
结果,无需再次运行引擎。拖动或用方向键平移;滚轮、双指捏合、+ 和 - 缩放。
点击“适应窗口”或按 0 显示整张图,按 Esc 关闭并把焦点还给打开按钮。
很宽的图初次打开时保持原始尺寸,便于读字;手机上的按钮保持可触控大小。
深浅色切换保留查看器中的阅读位置,减少动态效果偏好不会触发动画。
点击“源码”查看原始文本,点击“复制代码”复制。箭头、引号、和号及中文保留原样。 “下载 SVG”导出带样式的独立图片,无需远程资源。复制或导出失败时显示提示; 有外部图片或样式依赖的 SVG 会拒绝导出。页面下方的源码折叠区也可查看和复制。
按需加载与打印
距离视口不超过 320px 的可见图表才加载引擎并渲染。同页多图共用引擎,两种 图表串行渲染。隐藏标签页、折叠区和宽度为零的容器在恢复可见后处理;没有 IntersectionObserver 的浏览器使用滚动和尺寸检查。快速切换主题时丢弃过期结果。 没有图表的页面不加载查看器或图表引擎。此行为不会增加安装依赖或构建步骤。
禁用 JavaScript、引擎受阻或语法错误时保留可读源码。OINK 1.4.0 打印普通 HTML 页面时,已完成的浅色图按页面宽度缩放;暗色图和未渲染的图输出黑字白底、可换行 的源码,无须等待异步引擎,也不要求开启背景打印。需要全是图片时,先切换浅色 模式,浏览并展开需要的图,再打印。打印后恢复源码折叠状态,不改变阅读外观偏好。 独立 Print、Markdown、RSS 继续输出源码。
限制与常见问题
- 图不能编号:Mermaid 输出的是内联 SVG,不是
<img>,{#id num=}编号不适用;需要编号时导出成图片,按图片的编号写法使用。 - 围栏属性无效:宽度在图里控制(
flowchart的方向、classDiagram的布局),或者用 CSS。也没有对齐属性——图总是居中。 - 语法错误只在浏览器里可见:Hugo 不解析 Mermaid 语法,写错的图在页面上显示一条带解析错误与图源码的提示,构建照样通过,发布前要在浏览器里确认。
- RSS、Markdown 与打印输出里是源码而不是图:结论要写在正文里,不要只画在图上。
相关
4.12 - 组件呈现
使用外观菜单切换 Paper、Slate、Ink、Terminal 及明暗模式,比较同一批内容的呈现。以下示例继续沿用通常的 Markdown 写作方式。
本页的可选变体已随 OINK 1.4.0 发布。
相关页面
紧凑的资源目录可以使用 {.cards .td-cards-list}。分隔列表共用一个外框,说明文字可以自然换行。
- 创作内容 — 组织文章、指南和发布说明,另见 配置指南。
- 标题较长的组件参考入口 — 选择原生 Markdown 或组合短代码。
需要图标、徽章或块级说明时,完整形式支持 cards variant="list":
提示与折叠块
除了桌面效果,也要检查窄屏上的页面。
发布新版本之前,确认目标位置正确。
折叠块使用原生 HTML details 元素,禁用脚本后仍然可以操作。
标题、箭头与焦点轮廓位于同一个尺寸稳定的摘要行中。
每个折叠块独立展开,其他内容保持原有状态。
标签页与命令
单行围栏代码块加上 {class="td-code-command"} 后显示为命令条。复制按钮独占一列,命令可以滚动或换行,不会被按钮遮挡。
wrap、copy 和无障碍标签保持原有含义。多行、带行号或可折叠的输入会给出警告并回退到普通代码块。没有该修饰类的单行代码块保留通常的呈现。
短流程
在简短有序列表后添加 {.steps .td-steps-compact}。较长教程仍可使用常规 .steps 布局。
- 检查配置。
- 运行本地预览。
- 检查生成结果。
需要标题的流程使用百分号分隔的 steps variant="compact":
预览
运行开发服务器,打开修改的页面。
检查
按读者使用的语言和外观检查页面。
表格与元信息
| 视图 | 用途 | 输入 |
|---|---|---|
| 卡片 | 相关资源 | 链接列表 |
| 步骤 | 有序流程 | 有序列表 |
| 标签页 | 等价选项 | 相邻内容块 |
title,- 简洁的标题,允许自然换行。
description,- 与主要标签分开的辅助信息。
预览 已验证 待检查
用 Ctrl 加 K 打开搜索,用方向键切换结果。
兼容性
这些变体不加载额外运行时。已有链接、键盘操作、复制文字、打印内容与 Markdown 输出保持原有含义。不支持的 cards 或 steps 变体会给出警告并使用常规呈现。视觉精修不改变默认预设或字体政策。
4.13 - PlantUML
在 plantuml 围栏中写图表源码,OINK 使用随主题提供的两份脚本,在读者的浏览器中
完成渲染。无需安装 Java、Graphviz 可执行程序或 npm 包,也不依赖 CDN 或渲染服务。
使用本地渲染时,图表源码留在浏览器内。
启用 PlantUML 且不配置端点时使用随主题提供的本地资源;显式端点继续选择服务端模式。
在站点配置中启用:
plantuml: { enable: true } 同样有效。功能默认关闭;启用后,不含 PlantUML 围栏
的页面也不会加载这两份引擎脚本。
时序图
将源码放入 plantuml 围栏。下面的示例完全在本地渲染:
@startuml
actor 读者
participant 浏览器
读者 -> 浏览器 : 打开页面
浏览器 -> 浏览器 : 本地渲染
浏览器 --> 读者 : SVG 图表
@enduml
类图
声明类、字段与关系基数。
@startuml
class Publication {
+ pubname : name
+ puballtables : bool
+ pubinsert / pubupdate / pubdelete : bool
}
class Subscription {
+ subname : name
+ subconninfo : text
+ subslotname : name
}
class ReplicationSlot {
+ slot_name : name
+ plugin : name
+ confirmed_flush_lsn : pg_lsn
}
Publication "1" -- "0..*" Subscription : 被订阅
Subscription "1" -- "1" ReplicationSlot : 绑定
@enduml
组件图
用包组织组件,用箭头表示依赖关系。
@startuml
package "监控节点" {
[Grafana] as grafana
[Prometheus] as prom
[Alertmanager] as alert
}
package "数据库节点" {
[node_exporter] as node
[pg_exporter] as pgexp
[PostgreSQL] as pg
}
pg --> pgexp : 查询统计视图
node --> prom : /metrics
pgexp --> prom : /metrics
prom --> alert : 规则命中
grafana --> prom : PromQL
@enduml
活动图
使用判断与分支说明工作流程。
@startuml
start
:写 content/docs/**/*.zh.md;
:补英文对等页,抄渲染出来的标题 ID;
if (hugo 通过?) then (是)
:npm test;
else (否)
:按报错的文件名与行号修正;
stop
endif
if (测试全绿?) then (是)
:提交 PR;
stop
else (否)
:返回修改;
stop
endif
@enduml
用例图
通过参与者与用例说明各类读者能做什么。
@startuml
left to right direction
actor 读者 as reader
actor 作者 as author
actor 维护者 as maintainer
rectangle 文档站 {
reader --> (全文搜索)
reader --> (切换中英文)
reader --> (导出打印版)
author --> (新增页面)
author --> (本地预览)
maintainer --> (升级主题版本)
maintainer --> (发布上线)
}
@enduml
深色模式
本地图表跟随深浅色切换,作者的 skinparam 设置仍然有效。切换时丢弃旧主题的
过期渲染结果;已打开的查看器保留缩放和平移位置。
放大、源码与下载
本地 PlantUML 与 Mermaid 使用同一查看器。
点击角上的放大按钮,拖动或用方向键平移,滚轮、双指、+ 和 - 缩放,
“适应窗口”或 0 显示全图,Esc 关闭并恢复焦点。查看器复用 Blob 图片,
不会把 PlantUML 的 SVG 标记或链接插入页面 DOM,也不重复渲染。
“源码”和“复制代码”保留箭头、引号、和号及中文。“下载 SVG”导出带样式的 独立文件;有外部资源依赖时拒绝导出,失败有明确提示。页面的源码折叠区同样可查看和复制。显式服务端模式沿用旧行为。
按需加载与打印
图表距离视口不超过 320px 时才加载两份本地引擎;同页仅加载一次。隐藏标签页、 折叠区或宽度为零的容器在显示后处理。缺少 IntersectionObserver 时使用滚动和 尺寸检查。Mermaid 与 PlantUML 在主线程串行执行,没有新增安装依赖或构建步骤。
普通浏览器打印保留已渲染图片,并为尚未渲染的图显示源码。它不会等待异步引擎; 需要全是图片时,先浏览并展开需要的图。独立 Print、Markdown 与 RSS 保持源码回退。
可选渲染服务
已有站点可以继续使用显式配置的端点:
非空的 svg_image_url 选择服务端渲染,删除该设置则使用本地渲染。
端点应保留所需的末尾路径分隔符,使用包含主机的 HTTP(S) URL 或同站路径。
非法端点会告警并关闭渲染,不会静默切换模式。OINK 不提供默认公共端点。
服务端模式会把编码后的图表源码发送到端点,并保留原有的 svg 选项。
本地模式忽略 svg:生成的 SVG 通过图片展示,图内链接与标记不会进入页面 DOM。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 启用后显示本地 SVG 图片;渲染成功前保留可读源码 |
| 浏览器打印 HTML | 已渲染图片按纸张宽度缩放;未渲染的图显示源码 |
| 独立 Print 输出 | 可读源码,不加载 PlantUML 运行时 |
| Markdown 与 RSS | 原始图表源码,不发送渲染请求 |
JavaScript 关闭、引擎下载失败、输入不受支持或渲染出错时,源码仍然可读。 一张图失败不会阻止同页其它图表渲染。
参数参考
params.plantuml, ,true启用本地渲染params.plantuml.enable, ,- 使用 map 形式时的开关
params.plantuml.svg_image_url, ,- 可选端点,填写后选择原有服务端模式
params.plantuml.svg, ,- 仅服务端模式有效:使用内联 SVG 而非图片
围栏不接受代码块的标题、复制按钮、行号、编号或缩放属性。本地渲染会为省略
@startuml / @enduml 的源码补上包围标记;显式写出的图表起止标记保持不变。
限制与常见问题
- 浏览器资源只包含核心引擎与 Graphviz 布局库,不会下载
!include、!includeurl、 外部主题、C4、AWS 或其它可选图标库。不支持的资源会保留源码。 此类内容请改写为自包含图表、使用预渲染图片,或显式配置服务端。 - 固定引擎为
@plantuml/core1.2026.9,布局库为 Viz.js 3.24.0。两份脚本未压缩时 合计约 4.8 MB,gzip 后约 1.7 MB;只在启用且图表接近视口时加载,可正常使用 HTTP 缓存。 - 渲染使用浏览器的 JavaScript 和 WebAssembly。设有严格 CSP 的站点需要允许
同站脚本、WebAssembly 编译(
wasm-unsafe-eval)与blob:图片;资源被拦截时保留源码。 - 大图仍然会消耗浏览器 CPU 和内存。运行时逐张渲染,不在后台 Worker 中执行。 若引擎发生不可取消的超时,剩余 PlantUML 图保留源码,刷新页面后才能重试。
- 图表以图片呈现,SVG 超链接不可点击。本集成不提供编号。
相关
4.14 - 思维导图
markmap 围栏把一段 Markdown 大纲变成可展开、可缩放的思维导图,源码本身就是能读的提纲。markmap 围栏的正文是一段普通的 Markdown 大纲:标题与列表决定层级,浏览器把它画成一棵可展开、可折叠的树。适合把「这一节讲了什么」的层级一次呈现。节点之间有方向、有条件的流程用 Mermaid。
最简例子
先在站点配置中启用 Markmap,默认关闭;未启用时,围栏保留为可读的代码块。
再把大纲写进 markmap 围栏:
# OINK
## 本地优先
- 运行时全部随主题分发
- 不依赖任何 CDN
## Markdown 原生
- 组件是围栏和属性行
- 不写 shortcode 也能用
## 四态输出
- HTML
- 打印
- Markdown
- RSS
一级标题是根节点,其余标题与列表项按缩进挂在它下面。点击节点上的圆点折叠或展开这一支,鼠标滚轮缩放,拖动平移。 OINK 1.4.0 中,导图下方的 适应窗口 按钮恢复完整大纲,并重新启用阅读列 宽度变化时的自动适配。拖动或缩放后会暂停自动适配,保留阅读位置。按钮也可通过 Tab 和 Enter 操作。
多层级
层级越深字号越小,画布自动排布。下面是本站文档的六个栏目与它们的页数。
# OINK 文档
## 简介(4 页)
### 它是什么
### 功能一览
### 案例
### 许可
## 快速上手(4 页)
### 选择起点
### OINK Starter
### 仓库导览
### 从零开始
## 创作内容(8 页)
### 组织内容
### 编写页面
### 页面参数
### 博客
### 书籍
### 发布与下载
### OpenAPI
## 组件(22 页)
### 提示块 / 标签页 / 步骤 / 卡片
### 图片 / 画廊 / 表格 / 参数表
### 图表:Mermaid / PlantUML / 思维导图 / ECharts
## 定制站点(15 页)
### 品牌 / 导航 / 搜索 / 多语言
### 首页 / 版本 / 分类 / 打印
## 维护管理(7 页)
### 预览 / 部署 / 升级
### 评论 / 统计 / 排错
链接、代码与强调
节点里可以写行内 Markdown:链接可点击,行内代码用等宽字体,粗体与斜体照常生效。
# 日常命令
## 预览
- `hugo server` — 打开 [localhost:1313](http://localhost:1313/)
- `hugo server -D` — **连草稿一起**预览
## 构建
- `hugo --printPathWarnings`
- `hugo --gc --minify` — 发布用
## 主题
- `hugo mod get -u github.com/pgsty/oink`
- [主题仓库](https://github.com/pgsty/oink)
- [本站源码](https://github.com/pgsty/oink.pgsty.com)
节点里的公式
Markmap 运行时带了一份本地 KaTeX,节点里的 $…$ 会被渲染成公式。
# 常看的几个 PostgreSQL 指标
## 缓存命中率
- $\frac{blks\_hit}{blks\_hit + blks\_read}$
- 低于 0.99 时检查 shared_buffers
## 复制延迟
- $lsn_{primary} - lsn_{replica}$
## 事务吞吐
- $TPS = \frac{\Delta xact\_commit}{\Delta t}$
控制初始展开层数
围栏正文最前面可以写一段 Markmap 自己的 YAML 头,它不是 Hugo front matter。initialExpandLevel 只展开前几层,其余分支由读者点开。colorFreezeLevel 指定从第几层起同一分支使用同一种颜色。
---
markmap:
initialExpandLevel: 2
colorFreezeLevel: 2
---
# 主题仓库的检查脚本
## 源码级契约
### check-i18n.py
### check-taxonomy.py
### check-font-tokens.py
## 输出级检查
### check-output.py
### check-goldens.py
### check-code-blocks.py
### check-content-primitives.py
### check-media-primitives.py
## 浏览器运行时
### node --test tests/js/**/*.test.js
折进折叠块
每张导图固定 300 像素高,正文里连着放三张会占掉大量版面。把全景图折进 > [!DETAILS],由读者自己展开。折叠块里的每一行都要以 > 开头,围栏也不例外。
# pgsty/oink
## layouts/
- baseof.html 与各类型的壳
- _partials/shell/
- _markup/ 渲染钩子
- _shortcodes/
## assets/
- scss/ 令牌与组件样式
- js/ 浏览器运行时
- third_party/ 随主题分发的库
## i18n/
- 32 个语言文件,键完全对齐
## docs/
- 冻结契约文档
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 先输出 <pre><code class="language-markmap">,运行时把它换成 <div class="markmap"> 并画出 SVG |
| 打印 | 与 HTML 相同:打印视图同样加载运行时 |
| Markdown | 原样保留 markmap 围栏与它的大纲源码 |
| RSS | 只有大纲源码,订阅端看到的是一段可读的提纲 |
大纲本身就是内容:拿不到 JavaScript 的地方读到的仍是完整层级。
参数参考
围栏属性:没有。markmap 围栏不读属性行,高度由主题固定为 300px(.markmap > svg),宽度撑满正文栏。
站点参数(hugo.yml):
params.markmap, ,- 关闭时围栏保持为代码块,不加载任何运行时
键的完整定义见配置总览。每张图的行为写在围栏正文最前面的 markmap: YAML 头里(initialExpandLevel、colorFreezeLevel、maxWidth 等),属于 Markmap 语法,可用键以 Markmap 文档为准。
限制与常见问题
- 输出是固定 300px 高的内联 SVG:高度由一条
.markmap > svg规则统一,围栏改不了,层级太多时用initialExpandLevel收起或拆成两张图;内联 SVG 也不适用{#id num=}编号与图片缩放。 - 节点文字跟随站点正文颜色,连线保留 Markmap 自己的调色板;自定义颜色需在两种模式下检查对比度。
- 没开
params.markmap就只是代码块:不用这个组件的站点不加载任何运行时。 - OINK 1.4.0 在每张导图下提供适配按钮,打印时隐藏该按钮。
- 大纲里避开
<、>、&、":当前主题版本的markmap围栏会把这几个字符二次转义,节点上会出现>、"这样的字面文本;写链接用[文字](URL),不要用尖括号自动链接。
相关
4.15 - Draw.io
.drawio.svg 当普通图片放进页面,读者通过编辑按钮打开 Draw.io 编辑器改图。Draw.io 集成没有围栏也没有 shortcode,用的是普通 Markdown 图片。Draw.io 导出时勾上「Include a copy of my diagram」,SVG 或 PNG 里会带一份 mxfile 源码;主题的运行时识别这份副本后,给图片加一个编辑按钮。适合需要读者取走修改的图;只用于展示的图按普通图片处理。
最简例子
要显示编辑按钮,先设置 params.drawio.enable: true,并按编辑器地址一节配置 params.drawio.drawio_server。未配置时,图仍按普通图片显示,不提供编辑功能。
写法与普通图片相同,文件名不受限制,.drawio.svg 只是惯例。
这张图嵌着一份 mxfile 副本,因此被包进了 .drawio 容器。鼠标悬停或键盘聚焦时,
右下角显示铅笔按钮;在触摸设备和强制颜色模式下,按钮始终可见。激活按钮后,
在当前页面盖一层全屏 iframe,加载站点配置的编辑器。启用图片缩放时,编辑与缩放
是同级的独立按钮,激活编辑不会同时打开缩放对话框。
副本检测
运行时的判断依据只有一条:文件内容里有没有 mxfile 字样,与文件名无关。下面这张同样是 SVG、同样是块级图片,但它是手写的,没有副本,也就没有按钮。
带图注
Draw.io 图片走的是普通图片渲染钩子,图片的属性照常可用。加 caption 得到带图注的 figure,编辑按钮仍然出现在图上。
编号成书里的图
加 {#id num=…} 得到一张可交叉引用的编号图,与别的图片一样能被 xref 引用、进入图目录。
编号与交叉引用的完整规则见书籍出版。
SVG 还是 PNG
两种都识别。Draw.io 导出 PNG 时同样能带上副本,存在 PNG 的文本块里,运行时的判断逻辑相同。

文档里优先用 SVG:缩放不失真,文字是真实文本(可被搜索、可被读屏器读取),改动的 diff 也读得懂。图特别复杂、或目标平台不支持 SVG 时用 PNG。只有 PNG 能走 Hugo 的图片处理;SVG 上的处理操作会告警并保留原图。
编辑流程
按钮依次做三件事。
盖一层遮罩
OINK 1.3.0 使用全屏的 <dialog class="drawioframe">,其中包含 iframe。
编辑器 URL
保留 drawio_server 已有的查询参数和片段,并设置 embed=1、ui=atlas、
proto=json、saveAndEdit=1 与 noSaveBtn=1。
模态对话框包含本地化的关闭按钮,并约束键盘焦点。点击关闭按钮或背景可关闭。
焦点在宿主对话框时,Escape 也可关闭;嵌入编辑器自行处理内部按键。关闭后焦点
返回编辑按钮;若其他界面已经接管焦点,则保持该界面的焦点。
把图送进编辑器
编辑器就绪后,运行时把这张图片的内容(含 mxfile 副本)作为 data URL 发进 iframe。这一步不经过你的服务器。
保存与回写
在编辑器里点保存,运行时让编辑器按原格式(SVG 或 PNG)导出,由浏览器下载成同名文件。运行时不写回仓库:把下载到的文件覆盖 content/ 里那一份,再自行提交。
编辑按钮供读者取走图去改,不是站点的在线编辑功能。
编辑器地址
enable: true却没写drawio_server时会告警并关闭编辑;主题不代替站点选择公共服务。- 地址必须是 HTTP(S) URL 或本地路径;本地路径遵循
baseURL中的部署子路径。 空白、控制字符、原始反斜杠、协议相对 URL(//host/)与其他 scheme 会告警并 关闭编辑。 - 编辑过程必须留在组织内部时,部署一份自托管编辑器,把地址指向它。
- 公共端点
https://embed.diagrams.net/可用,读者的图会进入第三方页面。
这两个键的完整定义在配置总览。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 普通 <img>(或 <figure>);启用后运行时把带副本的图包进 <div class="drawio"> 并加按钮 |
| 打印 | 图片照常打印;该输出不加载 Draw.io 运行时,也不创建编辑按钮 |
| Markdown | 普通 Markdown 图片语法 |
| RSS | 普通 <img>,绝对 URL,没有按钮 |
图片本身在四态里都在,编辑按钮是增量能力。
参数参考
没有专属的围栏或 shortcode 参数。图片属性行沿用图片那一套(caption width height link #id num command options)。
站点参数(hugo.yml):
params.drawio.enable, ,- 关闭时不加载任何脚本,图片就是图片
params.drawio.drawio_server, ,- 编辑器地址;
enable: true时必填
限制与常见问题
- 运行时只在渲染内容含
.svg或.png候选图的页面加载;同一 URL 的图片合并检查,只读取一次以查找mxfile。 - 导出时忘了勾「Include a copy of my diagram」,图就只是一张图,没有按钮。
- 编辑依赖编辑器,且不写回仓库:离线环境里图片正常显示,按钮点了没有反应;编辑器保存等于浏览器下载,替换文件与提交都要手动做。
- 配色不跟随深浅色:导出的 SVG 颜色是固定的;把填充设成
none、线条与文字用中性灰,两种模式下都能看(本页这两张图就是这么做的)。
相关
4.16 - ECharts
echarts 围栏里用 YAML 或 JSON 写图表选项,Hugo 构建期校验,浏览器用本地 ECharts 画出跟随深浅色的统计图。echarts 围栏的正文是一段 YAML 或 JSON 的 ECharts 选项对象,不是代码。适用于需要
坐标轴、序列与图例的定量图表;只表达关系与流程时用
Mermaid,只表达顺序与层级时用
Infographic。Hugo 在构建期解析选项,无效输入会告警并保留可读源码。浏览器用随主题分发的
ECharts 绘图,只有用到它的页面加载运行时。
最简例子
一个柱状图只需要三段:xAxis、yAxis、series。下面是本站文档六个栏目各有多少页。
tooltip:
trigger: axis
xAxis:
type: category
data: [简介, 快速上手, 创作内容, 组件, 定制站点, 维护管理]
yAxis:
type: value
name: 页数
series:
- name: 页数
type: bar
data: [4, 4, 8, 22, 15, 7]两种格式都接受,YAML 不需要引号与逗号,写起来更短。缩进写错、正文解析成数组而不是映射时,会告警并保留源码,不输出空白图。
多序列折线
series 是数组,多一项就是多一条线;legend 让读者单独隐藏其中一条。下面是 PostgreSQL 各大版本的发布年份,以及按社区五年支持策略推算的终止年份。
tooltip:
trigger: axis
legend:
data: [发布年份, 支持终止]
grid:
left: 56
right: 24
top: 48
bottom: 40
xAxis:
type: category
name: 大版本
data: ["9.6", "10", "11", "12", "13", "14", "15", "16", "17", "18"]
yAxis:
type: value
min: 2015
max: 2031
name: 年份
series:
- name: 发布年份
type: line
smooth: false
data: [2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]
- name: 支持终止
type: line
lineStyle:
type: dashed
data: [2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030]版本号要加引号:YAML 里不带引号的 10 是数字,9.6 也是;作为分类轴的标签它们必须是字符串。
饼图与环形图
radius 给两个值就是环形图。下面是 OINK 的 29 个 shortcode 按用途的构成。
tooltip:
trigger: item
formatter: "{b}:{c} 个({d}%)"
legend:
bottom: 0
series:
- type: pie
radius: [42%, 70%]
itemStyle:
borderRadius: 6
borderWidth: 2
label:
formatter: "{b} {c}"
data:
- { value: 14, name: 核心组件 }
- { value: 10, name: Book 编号与索引 }
- { value: 3, name: 发布与下载 }
- { value: 2, name: OpenAPI }{b} {c} {d} 是 ECharts 的模板占位符(名称 / 数值 / 百分比),写在字符串里即可,不需要函数。
高度与通栏
height 默认 400px,接受 px rem em vh vw %;full=true 去掉正文的宽度限制,让图铺满内容区。适用于数据点多、标签长的图。
tooltip:
trigger: axis
grid:
left: 40
right: 16
top: 24
bottom: 32
xAxis:
type: category
data: [i18n, 分类法, 字体令牌, 内容契约, 导航, 运行时, 侧栏图标, 搜索, 动作, 命令面板, 双语文档, 阅读, 发布物, 下载, Landing, Book, 迁移, 键盘, 页尾, 输出, 金样本]
yAxis:
type: value
name: 脚本数
series:
- type: bar
data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]无效高度(360、36pt)会告警并使用默认值 400px。
深浅色
不写 theme 时,图按读者当前的配色模式初始化;切换配色时图原地重绘,不刷新页面。容器尺寸变化时自动 resize。把本页切到深色,上面每张图的底色与文字随之改变。
写定 theme 则固定配色,两种模式下都是同一套:
xAxis:
type: category
data: [HTML, 打印, Markdown, RSS]
yAxis:
type: value
series:
- type: bar
data: [1, 1, 1, 1]运行时内置的只有 dark;其它 ECharts 主题要先用 echarts.registerTheme() 注册才能在这里引用。没有品牌要求时不写 theme,让图跟随站点配色。
回调:$fn:
围栏是数据,不能带 JavaScript。某个选项需要函数时(提示框格式化、数据驱动的颜色),在选项里写字符串 "$fn:名字",再把这个名字注册到 window.OinkEchartsFunctions:
tooltip:
trigger: axis
formatter: "$fn:pageShare"
xAxis:
type: category
data: [简介, 快速上手, 创作内容, 组件, 定制站点, 维护管理]
yAxis:
type: value
series:
- type: bar
data: [4, 4, 8, 22, 15, 7]鼠标悬停在任意一根柱子上,提示框里是该函数拼出的句子。名字未注册时该选项解析为 undefined,图按未设置该项绘制,构建与运行都不报错。脚本与围栏放在同一页的相邻位置,便于一起改动。
这段脚本属于站点代码,按代码审查对待。字符串模板({b} {c} {d})能表达的格式不写成函数。
数据位置
围栏正文是字面量。Hugo 不在其中展开 shortcode、front matter 变量或 data/ 目录里的文件,数字写在围栏里。代价是数据不能共享,收益是图表源码与数据一起进入 Git,diff 能看出改动了哪个数值。
数据经常变动(版本矩阵、发布物清单)时不做成图:改用表格,或发布与下载页中由 data/ 驱动的组件。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <div class="td-echarts"> 里一个画布容器加一段 application/json 选项,本地 ECharts 画图 |
| 打印 | 不画图,输出 <pre class="td-echarts-source"> 包着的围栏源码 |
| Markdown | 原样保留 echarts 围栏与选项源码 |
| RSS | 与打印相同,只有源码 |
图上的结论要在正文里写一遍:打印与 RSS 输出里没有图。
参数参考
围栏属性行(```echarts {…}):
height, ,- 只接受非负数字加
pxrememvhvw%;其它写法告警并使用默认值 theme, ,- 固定使用某个 ECharts 主题,从此不再跟随站点配色;内置只有
dark full, ,true去掉正文宽度限制,图铺满内容区class, ,- 透传给容器,交给站点 CSS
style、on* 与未知属性会告警并忽略。围栏正文不能解析成 YAML/JSON 映射时告警
并渲染为源码。选项键本身是 ECharts 的,以
官方选项手册为准。
没有站点级参数:ECharts 不需要在 hugo.yml 里开关,用到时才加载。
限制与常见问题
- 围栏里不能写 JavaScript:需要函数时通过
$fn:桥接,未注册的名字解析为undefined,没有报错。 - 围栏不读外部数据:
data/目录、front matter 与 shortcode 都引用不到,数字写在围栏里。 - 打印与 RSS 里只有源码,结论要写进正文。
- YAML 的类型转换:分类轴上的
10、9.6、on、yes会被解析成数字或布尔值,需要引号。 - 颜色不是唯一的区分手段:多序列图同时区分线型或标记形状,两种配色模式下都要检查图例对比度。
相关
- Infographic — 表达结构与顺序的信息图,不是统计图
- 表格 — 数据少、需要精确读数时用表格
- Mermaid — 关系图与流程图
- 代码块 — 围栏属性行的通用规则
4.17 - Infographic
infographic 围栏挑一个 AntV 模板,把标题与条目渲染成流程、时间线、漏斗、网格或层级信息图。infographic 围栏挑一个 AntV 模板,把「标题 + 一串条目」渲染成信息图。适用于表达顺序、层级与对比这类结构。需要坐标轴与数值精度时用 ECharts,需要条件分支的流程时用 Mermaid。围栏正文是数据,在 GitHub 上仍是一段可读的文本。
最简例子
第一行是 infographic 模板名,其后是一个 data 块:title 是标题,items 下面每个条目至少要有 label。
infographic list-row-simple-horizontal-arrow
data
title 一次文档改动的三步
items
- label 写
desc 先写中文 .zh.md
- label 校
desc 构建生产,例子真渲染
- label 发
desc 补英文对等页,提交 PR缩进决定结构,两个空格一级。标签要短,说明放 desc。
时间线
sequence-timeline-* 系列把条目排成一条时间轴,label 是时间点,desc 是事件。
infographic sequence-timeline-simple
data
title PostgreSQL 近五个大版本
items
- label 2021
desc 14:并行查询与逻辑复制的一轮改进
- label 2022
desc 15:MERGE 语句
- label 2023
desc 16:逻辑复制可以从备库进行
- label 2024
desc 17:增量备份与 JSON_TABLE
- label 2025
desc 18:异步 IO 子系统漏斗
sequence-funnel-simple 画逐步收窄的阶段。下面是主题发布的五个阶段。
infographic sequence-funnel-simple
data
title 一次主题发布要经过的五个状态
items
- label 源码完成
desc 代码写完,仅此而已
- label 已验证
desc 主题检查脚本与站点测试套件全绿
- label 已发布
desc 不可变的签名标签,能从 Go 代理拉到
- label 已文档化
desc 文档站钉住了这个标签
- label 已部署
desc 生产环境运行的就是这个版本网格卡片
条目之间没有先后关系时用 list-grid-*,它把条目排成网格而不是队列。
infographic list-grid-compact-card
data
title 同一页内容的四种输出
desc 每个内容组件都要在这四态里给出可用的结果
items
- label HTML
desc 交互式,按需加载运行时
- label 打印
desc 折叠展开,去掉缩放与复制按钮
- label Markdown
desc 纯文本,按字节比对金样本
- label RSS
desc 静态,与打印同源带数值的条目
条目上加 value,能表达比例的模板(饼、环、进度)会用到它。
infographic chart-pie-donut-plain-text
data
title 29 个 shortcode 的构成
items
- label 核心组件
value 14
- label Book 编号与索引
value 10
- label 发布与下载
value 3
- label OpenAPI
value 2层级与手绘风格
条目下面可以再嵌 children,hierarchy-mindmap-* 把它画成两层的结构图。顶层的 theme 块换整张图的风格,type 取 light、dark 或 hand-drawn。
infographic hierarchy-mindmap-level-gradient-compact-card
theme
type hand-drawn
data
root
label 主题仓库
children
- label layouts
desc 模板
children
- label _markup
desc 渲染钩子
- label _partials
desc 外壳与工具
- label assets
desc 资源
children
- label scss
desc 令牌与组件样式
- label js
desc 浏览器运行时
- label third_party
desc 随主题分发的库theme 属于 DSL。OINK 1.4.0 为每张图提供独立底面:默认透明图和手绘图使用
白底,作者指定的背景优先。type dark 图在浅色页面上仍保留深底。站点明暗切换
保留色板、文字颜色与笔触,不重新绘图。显式透明背景或自定义文字颜色仍须在两种
模式下检查对比度。
挑模板
模板名是 结构-变体 的组合,同一个结构有多个视觉变体。常用的几类:
| 结构前缀 | 表达什么 | 例子 |
|---|---|---|
list-row-* list-column-* |
一排 / 一列并列的条目 | list-row-simple-horizontal-arrow |
list-grid-* |
网格,条目之间无先后 | list-grid-compact-card list-grid-badge-card |
list-pyramid-* sequence-funnel-* |
逐层收窄 | sequence-funnel-simple |
sequence-timeline-* sequence-roadmap-vertical-* |
时间线与路线图 | sequence-timeline-simple |
sequence-steps-* sequence-snake-steps-* |
有序步骤 | sequence-steps-simple |
compare-binary-horizontal-* compare-quadrant-* |
二元对比与四象限 | compare-binary-horizontal-simple-vs |
hierarchy-mindmap-* hierarchy-structure-* |
层级(配合 children) |
hierarchy-mindmap-level-gradient-compact-card |
chart-pie-* chart-bar-* chart-column-* |
带 value 的示意图 |
chart-pie-donut-plain-text |
relation-network-* relation-dagre-flow |
网络与流向(配合 relations) |
relation-dagre-flow |
选能表达清楚关系的最小形式。完整图库见 AntV Infographic 图库,模板名与随主题分发的版本一一对应。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <div class="td-infographic"> 里一个画布容器加一段 DSL,本地 AntV 运行时画成 SVG |
| 打印 | 不画图,输出 <pre class="td-infographic-source"> 包着的 DSL 源码 |
| Markdown | 原样保留 infographic 围栏与 DSL |
| RSS | 与打印相同,只有源码 |
图上的信息要在正文里写一遍:打印与 RSS 输出里只有那段 DSL。
参数参考
围栏属性行(```infographic {…}):
height, ,- 非负数字加
pxrememvhvw%;其它写法告警并使用auto full, ,true去掉正文宽度限制class, ,- 透传给容器
style、on* 与未知属性会告警并忽略;空 DSL 正文告警并不渲染。
DSL 的顶层键(属于 AntV,不是主题):
infographic/template- 模板名,第一行
datatitle、desc、items(也可以是sequencescomparesnodesvaluesrelationsroot,取决于模板结构)、orderthemetype(light/dark/hand-drawn)、palette、colorPrimary、stylize等width/height- DSL 层的画布尺寸,一般交给围栏属性
height design- 逐部件的细调,少用
items 里每个条目可用 label、desc、value、icon、children、group、id。DSL 的完整定义以 AntV Infographic 文档为准;随主题分发的版本与校验值记在主题仓库的 VENDOR.json 里。
限制与常见问题
- 模板名写错不会让构建失败:Hugo 只检查围栏属性,DSL 由浏览器运行时解析,模板不存在时容器里显示一行错误文字。改动模板名后在页面上确认。
- 不跟随深浅色:
theme写在 DSL 里,两种配色模式下都要检查对比度。 - 打印与 RSS 里只有 DSL,关键结论要写进正文。
- SVG 不是语义结构:屏幕阅读器读到的顺序未必是排版顺序。标题、列表、表格能表达的内容优先用它们。
- 标签要短:长文本在窄屏下会被截断或挤压,改动后在手机宽度下确认。
相关
4.18 - 画廊
gallery 围栏把一组相关截图排成响应式网格,每张可带说明或链接,并复用页面的图片缩放对话框。画廊(Gallery)把一组相关图片排成响应式网格,围栏里每行一张图。适用于同一件事的几个视图:几张截图、几种状态、几套配色。单张图用图片;相互之间没有顺序与对比关系的图片不适合放进同一个画廊。
最简例子
围栏里一行一张图,语法是 Markdown 的 。
替代文字必须写:它是这一项的标题、读屏器唯一能读到的文字,也决定这张图是否参与缩放。列数没有参数,网格随容器宽度自适应,窄屏减列。
加说明
图片后面用 # 起头写说明,显示在图下方。说明是纯文本,里面的 Markdown 按字面显示;要一个字面井号写 \#。

默认外壳:侧栏、正文、目录

OINK 的上游 Docsy,内容模型一脉相承

发布卡片使用 release_url 与 date,checksums 块列出下载资产
说明长短可以不一致:网格按最高的一项对齐,说明换行不影响相邻的图。图片先被解析,替代文字与路径里的 # 不需要转义。
每项一个链接
行尾的 {link=…} 让这一项成为链接,站内路径、相对路径、http(s): 都可以。
带链接的项不参与缩放,点击已有别的含义。同一个画廊里两种项可以混排:有链接的打开页面,没有链接的打开大图。
图片来源
来源解析顺序与普通图片一致:页面资源(页面包里的同目录文件)→ 全局资源 assets/ → 静态路径 /images/… → 远程 URL。本地资源带上固有尺寸,加载时不跳版;远程图构建期不下载,也取不到尺寸。

assets/images/… 下的图,可以做构建期处理

static/images/… 下的图,原样发布
页面 / 全局资源无法解析时按静态路径保留,与显式静态路径相同;主题不检查静态路径 与远程 URL 是否存在。
装饰图与缩放
替代文字留空表示这是装饰性图片:没有标题,读屏器跳过,也不参与缩放。
图片缩放是站点级开关,默认关闭。本页在 front matter 中开启了它,上面每张有替代文字、没有链接的图都可以点开看大图(Esc 关闭,焦点回到原处)。

装饰性配图,不参与缩放

有替代文字,可以点开
画廊没有自己的缩放运行时,复用整页共用的那个对话框。页面上没有可缩放的图时,运行时不加载。细节见图片 · 缩放。
加 class 与分标签页
class 可以加在整个围栏上(写在语言后面)或某一项上(行尾),主题不解释它,原样透传给站点 CSS。围栏带 tab=(以及 group= value=)时成为一组标签页里的一页。

默认配色

跟随系统或手动切换
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <ul class="td-gallery">,每项一个 <li>;符合条件的图带 data-td-image-zoom 标记;全部懒加载 |
| 打印 | 同一组图堆叠排列,没有缩放标记 |
| Markdown | 原样输出 gallery 围栏 |
| RSS | 与打印相同的静态堆叠 |
画廊不加载 JavaScript。
参数参考
行语法  [# 说明] [{key=value …}]:
,- 必须顶在行首。
alt是这一项的标题;留空表示装饰图 src,- 页面资源 / 全局资源 / 静态路径 / 远程 URL
# 说明,- 纯文本,显示在图下方;
\#是字面井号;不能为空 {link=…},- 让这一项成为链接,因而不可缩放
{class=…},- 给这一项加站点 CSS class
围栏属性:
tab, ,- 让这个画廊成为一个标签页
group/value, ,- 标签页分组与同步值;必须与
tab同时出现 class, ,- 透传给站点 CSS
没有 columns、caption、title 属性。坏行或坏属性会告警,只丢弃无效部分或该行,
并给出围栏内行号。
限制与常见问题
- 只有围栏一种形态:没有
{.gallery}列表标记,也没有 shortcode。代价是源码在 GitHub 上不渲染成图片,收益是四态输出与缩放资格由主题保证。 - 不能指定列数,也不裁成统一宽高比:网格按视口自适应,图片按原始比例排列。
- 没有幻灯片、轮播与上一张 / 下一张:缩放对话框一次显示一张。
- 不下载远程图:构建期没有网络请求,远程图在浏览器加载前尺寸未知,可能跳版。
- 说明不解析 Markdown:需要富文本时写在画廊下方的段落里。
需要侧栏相册导航、逐图翻页与拖动缩放时,使用 OINK 1.3.0 新增的
Expo 布局。gallery 围栏用于文字页面中的图片组。
相关
4.19 - 徽章
徽章(Badge)是紧跟在名字旁边的行内状态标签:Beta、已弃用、v0.5、需自建服务。适用于一两个词能说完的状态;作者只选语义 tone,颜色由主题决定,浅色与深色模式下的对比度都有保证。状态需要解释、操作步骤或截止日期时,改用正文或提示块。
最简例子
text 是唯一必填参数,必须是非空字符串。
五种 tone
只有这五个取值,没有自定义颜色。
默认 信息 已支持 实验性 已弃用
不写 tone 时使用 neutral。其它取值在普通预览中告警并使用 neutral;警告带
源码位置,发布前应审阅告警并检查回退结果。
夹在句子里
徽章是行内元素,跟在名字后面,不占单独一行。
params.ui.image_zoom 默认关闭 打开后,
有替代文字的块级图片可以点开看大图。PlantUML 按需启用
使用独立的站点开关。Draw.io 需自建服务
没有配置服务端点时告警并保持关闭。
标题旁边
标题里不要写 shortcode。 Hugo 先生成目录、后替换 shortcode,所以徽章在标题上渲染正常,目录里却会留下一段 Hugo 的内部占位符文本。把状态写进标题下面的第一段:
OpenAPI 页面
0.5 新增 徽章紧跟在标题下方,目录保持干净, 锚点链接分享出去也不会带上徽章文字。
表格单元格里
对照表里用徽章标状态,比整列写「是」「否」更容易扫读。
| 组件 | 形态 | 状态 |
|---|---|---|
| 提示块 | > [!NOTE] |
稳定 |
| 画廊 | ```gallery 围栏 |
稳定 |
| PlantUML | ```plantuml 围栏 |
按需启用 |
image shortcode |
— | 已移除 |
列表与步骤里
- 安装 Hugo Extended ≥ 0.168.0
- 从 OINK Starter 创建站点,修改
hugo.yaml里的baseURL hugo server预览 1313 端口
卡片里
卡片有自己的 badge 参数(纯文本,固定在标题右侧);卡片正文里可以放徽章 shortcode。
一行 hugo mod get 完成安装 需要 Go
不联网的机器也能构建 手动升级
可点击的徽章
加 link 后徽章变成链接(<a>),站内路径、相对路径、http(s):、mailto: 都可以。
链接非法时告警并丢弃链接,保留普通徽章。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 无链接时 <span class="td-badge td-badge--<tone>">,有链接时 <a class="td-badge …"> |
| 打印 | 同 HTML,静态行内元素 |
| Markdown | **Beta**,有链接时 [**Beta**](/…) |
| RSS | 同打印 |
不加载 JavaScript。徽章不是实时状态区域,新增徽章不会触发读屏器播报。
参数参考
text, ,- 必填,非空。读者看到的文字
tone, ,neutralinfosuccesswarningdangerlink, ,- 设置后徽章变成链接
只接受命名参数。没有 icon、class、color、outline、size 参数。无效输入
会告警并采用安全回退:未知参数忽略,空 text 不渲染,非法 tone 回退
neutral,不安全链接被丢弃。
限制与常见问题
- 颜色不是唯一的含义载体:tone 是补充,文字要自己说清楚。
{{< badge text="🔴" >}}对读屏器没有信息。 - 没有图标参数:需要图标时改用卡片或提示块。
- 文字要短:徽章不换行地跟在名字后面,超过五六个字的内容写进正文。
- 同一处不超过三枚:连排的徽章会盖过它修饰的名字。
- 徽章只有 shortcode 一种形态,没有原生 Markdown 写法;纯 Markdown 阅读器里它退化成加粗文字。
相关
4.20 - 按键
kbd 写快捷键:一个 shortcode 接一串按键名,输出语义化的按键序列,打印与 Markdown 输出里同样可读。按键(Kbd)把读者要按下的键与正文区分开。适用于快捷键与组合键:一个按键一个位置参数,主题负责画框、补分隔符,并给读屏器一个可读的序列。命令名、选项名与要输入的文本用行内代码,它们不是物理按键。
最简例子
按 Ctrl 加 K 打开命令面板。
参数必须加引号,一个按键一个位置参数。缺少、空白或命名参数会告警,普通预览不渲染 无效按键;发布前应审阅告警并检查回退结果。
单个按键
一个参数对应一个键,符号键按原样写。
Escape 关闭对话框; / 进入搜索; t 切换亮色 / 暗色; l 循环切换语言。
组合键
多个参数按顺序渲染,中间补 +。这个加号对辅助技术隐藏,读屏器读到的是本地化的连接词。
⌘ 加 Shift 加 P 与 Ctrl 加 Shift 加 P 是同一个动作。 需要按字面的加号时,把它当成独立的一个按键:Ctrl 加 + 放大页面。
平台差异
按键名写读者键盘上印的标签:macOS 写 ⌘,Windows / Linux 写 Ctrl。不要把两个平台合进同一个序列,Ctrl/⌘ 这类写法读屏器无法正确朗读。在句子里说明平台,或分成标签页。
macOS 按 ⌘ 加 K,Windows 与 Linux 按 Ctrl 加 K。
快捷键表
速查表是按键最常见的位置。下面是本站生效的一部分全局键:
| 按键 | 作用 |
|---|---|
| Ctrl 加 K | 打开命令面板(macOS 是 ⌘ 加 K) |
| / | 面板的完整搜索态 |
| t | 切换亮色 / 暗色 |
| q / e | 上一篇 / 下一篇 |
| w s a d | 在侧栏树里上下移动、折叠、展开 |
| Escape | 从侧栏树回到正文 |
全站快捷键的完整清单见键盘导航。
步骤里
- 按 Ctrl 加 K 打开命令面板
- 输入
>进入纯命令态,或输入关键词搜索 - 用 ↑ ↓ 选中一项,Enter 前往
- Escape 关闭,焦点回到按下之前的位置
原始 <kbd> 标签
Markdown 里写原始的 <kbd> 标签得到同样的样式,GitHub 也这么渲染。区别是分隔符与无障碍序列要自己维护:单个键两种写法都可以,组合键用 shortcode。
按 F5 刷新;在编辑器里按 Ctrl+S 保存。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <span class="td-kbd-sequence"> 包着每个键一个 <kbd>;可见的 + 对读屏器隐藏,另有一个本地化连接词 |
| 打印 | 同 HTML,静态 |
| Markdown | 纯文本 Ctrl + K、⌘ + Shift + P |
| RSS | 同打印 |
没有 CSS 与 JavaScript 时,操作说明仍然可读。
参数参考
位置参数 1..n, ,- 至少一个,每个都必须非空且加引号;顺序就是显示顺序
只接受位置参数。没有 separator、label、platform、class、size 这些命名参数:Hugo 的 shortcode 不允许在一次调用里混用位置参数与命名参数。
限制与常见问题
- 一个序列表示同时按下的一组键:先按 A 再按 B 这类连续操作写成两个 kbd 加一句说明(先按 Escape,再按 Enter)。
- 不做平台检测:页面不会按访客的操作系统把
Ctrl换成⌘。 - 不做按键映射与录制:菜单路径、手势、游戏杆不在范围内。
- 漏写引号会让构建失败:
{{< kbd Ctrl K >}}里的Ctrl不是字符串参数。 - 不用它标命令:
hugo server写成行内代码,Ctrl是按键。
相关
4.21 - 引用
三个 shortcode 各做一件事:include 把另一个文件的内容放进当前页面,param 打印一个页面或站点参数,comment 丢弃一段内容。适用于跨页复用的片段与散落在多页的常量:同一段安装步骤出现在三页时用 include,版本号出现在几十页时用 param,改一处即可。只在一页出现的内容写在那一页。
最简例子
include 只有一个必填参数 file:
被引的文件是一段普通 Markdown,放在 assets/ 下:
渲染结果与写在本页里相同:代码块有复制按钮,提示块是提示块。
把 OINK 安装到一个已有的 Hugo 站点,三条命令:
hugo mod get 需要本机安装 Go;用离线归档或 submodule 时不需要。
当前发布版本是 v1.5.1。
被引的文件不是一篇独立页面:它不出现在侧栏、不参与翻译配对、没有自己的 URL。
文件位置
file 按下面的顺序解析,第一个命中的胜出:
| 顺序 | 找哪里 | 写法 |
|---|---|---|
| 1 | 当前页面的页面资源(页面包里的文件) | file="config.yaml" |
| 2 | 全局资源 assets/ 下的文件 |
file="snippets/dsn.txt" |
| 3 | content/ 下的文件:/ 开头是内容根目录,否则相对当前页面所在目录 |
file="notes/caveat.md"、file="/shared/notice.md" |
三处都找不到,或路径里含 .. 时,引用会告警并不输出。发布前应审阅告警并检查回退结果:
引用只能在 content/ 与 assets/ 中取文件。
引 Markdown 片段时写文件在磁盘上的真名。有一个陷阱只属于第 1 步:Hugo 把带语言后缀的页面资源(如 notice.zh.md)按去掉后缀的名字挂在页面上,向页面包索取 notice.md 拿到的是已渲染的 HTML 而不是源码,Markdown 输出里会出现 <div class="td-code">。assets/ 与 content/ 下写什么名字就取什么文件,没有这层转换。非 Markdown 文件(.yaml、.sh、.txt)也没有这个区别。
本页两种语言各引一份自己的片段:中文引 assets/parts/install-oink.zh.md,英文引 assets/parts/install-oink.md。片段放在 assets/ 下而不是页面包里,两种语言就都按写下的名字取到源码。
引入代码文件
加 code=true 让文件按代码块渲染,lang= 指定高亮语言。引用仓库里的真实配置文件,文档与实际文件不会不一致。
代码块与围栏走同一条渲染管线:高亮、行号、复制按钮都有。围栏属性(title=、collapse、hl_lines=)传不进来,需要它们时把文件内容写成普通代码块。
片段内容
片段是页面级 Markdown,在当前页面的上下文里渲染:提示块、表格、列表、图片、步骤与 shortcode 都可以用。上面那段片段结尾的「当前发布版本是 v0.8.1」,是片段里的 {{< param version >}} 在本页展开的结果。
一个片段被两页引用时,两页各自渲染一遍,各自生成标题锚点与代码块 ID,互不冲突。
安装命令、连接串、支持矩阵、法务声明:会变动、且变动时必须处处同步的内容。只在一页出现的内容写在那一页。
插入站点参数
param 打印一个参数:先查本页 front matter,查不到再查站点配置(Hugo 的 .Param 规则)。
本站发布版本 v1.5.1,版权起始年 2026,
本页 front matter 里写了 pigsty_pg_major: 18,这里取到 18。
嵌套键用 . 连接,copyright.from_year 取的是 params.copyright.from_year。参数不存在,
或者值是 map / 列表而不是标量时,告警并不输出;发布前应审阅告警并检查回退结果。
在命令、表格与链接里插参数
param 的输出是转义后的纯文本,可以放进代码围栏、表格单元格与链接地址。安装命令里的版本号适合这么写:
| 项目 | 值 |
|---|---|
| 当前版本 | v1.5.1 |
| Hugo 下限 | 0.168.0 |
站点参数在哪里定义、有哪些可用,见配置总览;页面参数见页面参数。
构建期删除的注释
comment 的内容在 HTML、打印、Markdown、RSS 四种输出里都不出现。HTML 注释不同:它留在页面源码里,也会进入 llms.txt。
PostgreSQL 18 起 pg_stat_io 拆分了 WAL 统计。
升级前先在测试库上验证监控面板。
上面两段之间有一段注释,查看页面源码也找不到它。
输出形态
| 输出 | include(Markdown) |
include code=true |
param |
comment |
|---|---|---|---|---|
| HTML | 片段渲染成正常内容 | 高亮代码块 + 复制按钮 | 转义后的纯文本 | 无 |
| 打印 | 同 HTML | 同 HTML,无复制按钮 | 同 HTML | 无 |
| Markdown | 片段的源码原样输出 | 源码围栏 | 值本身 | 无 |
| RSS | 同 HTML | 同 HTML | 同 HTML | 无 |
Markdown 输出里片段是源码而不是 HTML,片段里的 shortcode 保持 {{< param version >}} 的原样。这与「Markdown 输出保留源码」一致,不是漏渲染。三个 shortcode 都不加载脚本。
参数参考
include(只接受具名参数):
file, ,- 解析顺序见文件放在哪;含
..、文件缺失、空值时告警并不输出 code, ,true时按代码块渲染;带引号的code="true"会告警并按普通内容引入lang, ,- 代码语言;没有
code=true时告警并忽略
其它参数名会告警并忽略,消息带文件名与行号。
param(一个位置参数):
参数名, ,- 嵌套键用
.连接;先页面 front matter 后站点params;缺失或非标量时告警并不输出
comment 没有参数,成对使用,{{< comment >}} 与 {{< /comment >}} 之间的内容整段丢弃。
限制与常见问题
include不是模板:不能向片段传变量、不能条件引入、不能给引入的代码块加围栏属性(title=、collapse)。按平台分版本时写两个片段配标签页。- 片段的语言要自己维护:
include不做语言回退。中文页引中文片段,英文页引英文片段,两份文件并列存放(install-oink.zh.md与install-oink.md)。 param只打印标量:结构化数据(版本矩阵、下载列表)用data/目录里的数据配对应组件渲染。comment不是「暂时不发布」:内容每次构建都被丢弃,临时下线整页用draft: true。- 不把
include当目录页:一页引入十个片段时,读者需要的是十条链接。
相关
4.22 - Asciinema
asciinema 把一段 .cast 录像渲染成页面里的终端播放器。适用于命令行流程的演示:终端里的文字仍然是文字,可以选中复制,本页六分多钟的安装录像约 196 KB。图形界面的操作用截图或视频,本组件只播放终端录像。播放器与样式随主题分发,构建期不下载、运行期不连 CDN,只有用到它的页面、且只在 HTML 输出里加载这套运行时。
最简例子
只有 file 是必填的:
images/install.cast — /images/install.cast
这段录像是 Pigsty 在一台 Debian 机器上的单机安装,120×36 的终端,约 6 分 42 秒。下载示例录像,保存为自己站点的 static/images/install.cast,引用时写站点根路径。放在 assets/ 下也写相对路径:主题先在资源里查找,找不到再当成站点根路径。不写 title 时,窗口标题显示 file 的值。
窗口标题与主题
title 设置窗口标题,theme 设置配色:
Pigsty 单机安装 — /images/install.cast
theme 默认 auto:跟随站点的深浅色,浅色用 td-light,深色用 td-dark,读者切换配色时播放器就地重挂一次。要固定成某套终端配色时,可选值是播放器自带的 asciinema、dracula、gruvbox-dark、monokai、nord、seti、solarized-dark、solarized-light、tango,以及主题提供的 td-light / td-dark。固定的主题不跟随深浅色,深色站点配 solarized-light 的对比度不合适。终端字体不用单独设置:播放器使用站点的代码字体,与页面上的代码块一致。
速度、起点与封面
长录像用三个参数控制起点:speed 设倍速,startAt 跳过开头,poster 决定未播放时定格的画面。
从第 60 秒开始,两倍速 — /images/install.cast
speed 与 startAt 是数字(秒),poster 用播放器的 npt: 记法定位时间点,npt:1:30 是第 1 分 30 秒。上面这个播放器停在第 90 秒的画面,点播放从第 60 秒开始。
idleTimeLimit 把静默段压缩到最多 N 秒。这段录像在录制时已经压缩过(.cast 头里是 idle_time_limit: 0.5),此处不必再设。只有录制时没有限制静默时长的文件才需要它。
尺寸与适配
播放器默认按容器宽度缩放(fit="width"),终端的行列数来自 .cast 文件头。cols / rows 可以覆盖它:
只留 16 行高 — /images/install.cast
比录像本身小的行列数会裁掉内容,上面这个只显示 36 行里的 16 行。cols / rows 用于修正录像头里的错误尺寸,不是排版工具。要让播放器变矮,重录一次小终端。
fit 的四个值:width(默认,按宽度缩放)、height(按高度)、both(两个方向都装下)、none(不缩放,按字号原样显示,宽终端会溢出)。
循环与预加载
loop 播完自动重播,preload 在页面加载时取回 .cast,点播放不必等待:
循环播放:登录后的第一分钟 — /images/install.cast
autoplay="true" 让页面打开即播。不建议使用:系统的「减少动态效果」偏好只关闭播放器控件的过渡动画,不阻止自动播放。确实需要自动播放时,配上 loop、很短的内容,并且一页只放一个。
放进步骤里
录像放在某一步旁边:文字说明要做什么,录像展示实际输出。
-
安装依赖,获取安装脚本:
-
执行安装,录像以四倍速播放安装过程:
pig install — /images/install.cast
-
打开
http://<节点地址>:3000,用admin / pigsty登录 Grafana。
一页可以放多个播放器,脚本与样式只加载一次。
录制 cast 文件
主题只负责播放。用 asciinema 的 asciinema rec --idle-time-limit=2 --cols=100 --rows=28 install.cast 录制,asciinema play install.cast 本地回放确认。
- 终端宽度控制在 100 列以内,窄屏上仍可读;录制前先
clear。 - 录制前清理密钥:
.cast是纯文本,录像里的每个字符都能grep到,提交前检查一遍。 - 文件放在
static/images/install.cast,用file="images/install.cast"引用;放在assets/images/install.cast时写法相同。主题不解析页面包资源。把录像提交进仓库,不引用外站的.castURL。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | <div class="td-asciinema"> 窗口外框 + 播放器;播放器 CSS/JS 与运行时按需加载,一页一次,且只在这一种输出里 |
| 打印 | 一行带标题的静态链接,地址可见;不加载播放器,也不加载任何运行时 |
| Markdown | 一个纯 Markdown 链接 [标题](/images/install.cast)——没有组件标记,也没有配置块 |
| RSS | 同样的纯链接 |
录像不能是唯一的信息来源。关键命令与关键输出要在录像旁边用文字或代码块写一遍:离线读者、llms.txt 的抓取方与打印读者拿到的是这个链接和你写的文字,而不是终端会话本身。
参数参考
file, ,- 具名或第一个位置参数;先按全局资源找,找不到当站点根路径;
http/https地址原样使用,其它 scheme 告警并不渲染 title, ,- 窗口标题
theme, ,auto跟随站点深浅色;或td-lighttd-darkasciinemadraculagruvbox-darkmonokainordsetisolarized-darksolarized-lighttangofit, ,widthheightbothnone;其它值告警并使用widthcols/rows, ,- 覆盖终端行列数;比录像小会裁掉内容
speed, ,- 播放倍速
startAt, ,- 起播位置
idleTimeLimit, ,- 静默段最多播这么久
poster, ,- 未播放时定格的画面,
npt:分:秒 autoplay, ,- 页面加载即播;不建议
loop, ,- 循环播放
preload, ,- 页面加载时就取回
.cast pauseOnMarkers, ,- 播到章节标记处暂停
markers, ,- 章节标记;见下面的限制,标签目前到不了播放器
布尔类参数比较的是文本 true:loop="true" 与 loop=true 都表示开启,其它值表示关闭。其余参数非法时会告警并回退:fit 非法时用 width,speed 非数字时用 1,startAt 非数字时用 0,cols、rows、idleTimeLimit 与标记时间非数字时忽略。
限制与常见问题
markers的标签会丢失:主题把时间:标签的列表拼成一维数组交给播放器,播放器只接受成对写法,时间轴上会多出没有标签的标记点。标记时间不是数字时会告警并跳过该标记。需要章节时用录像旁边的文字列表。- 播放器需要 JavaScript:浏览器禁用脚本时只剩窗口外框。打印、Markdown 与 RSS 给的是链接,见输出形态。
- 录像不进搜索:站内搜索索引页面文字,录像里出现过的命令搜不到。
- 不引用远程
.cast:http与https地址会被接受,页面因此依赖一个外站;其它 scheme、协议相对的//host或空值都会告警,组件不渲染。 - 控制单段长度:超过五六分钟的录像少有人看完,长流程拆成几段短录像,各配一段文字。
相关
5 - 定制站点
本栏目覆盖站点级配置:hugo.yml 里的参数、data/ 下的数据文件、assets/ 下的样式入口。单个页面的写法与 front matter 见创作内容。
按改动目标查找
| 改动目标 | 对应页面 |
|---|---|
| 站名、Logo、favicon | 品牌外观 |
| 配色、深浅色模式、字体 | 品牌外观 |
| 顶栏菜单与下拉 | 导航与菜单 |
| 侧栏宽度、图标密度、目录深度 | 布局与页面类型 |
| 首页与落地页 | 首页与落地页 |
| 全文检索与索引范围 | 全文检索 |
| 命令面板里的条目 | 命令面板 |
| 快捷键 | 键盘导航 |
| 新增一门语言 | 多语言 |
| 多版本站点与归档横幅 | 多版本 |
| 标签与分类 | 分类体系 |
| 编辑本页、最后修改、贡献者 | 仓库与页面信息 |
| 打印与整章导出 | 打印支持 |
llms.txt 与每页 .md 输出 |
Agent 支持 |
| 某个参数的类型与默认值 | 配置总览 |
评论、分析与部署需要接入外部服务,见维护管理。
5.1 - 配置总览
站点参数的唯一归属页。主题读取的每个键在下面某张表里有一行,给出类型、默认值与一句说明,并链接到讲它的指南页。指南页只给可粘贴的片段,不重复定义。页面级参数(front matter)见页面参数。
表格按功能分组,每组一个 ##,锚点可以引用,例如 /zh/docs/customize/config/#sidebar。默认值一栏空着表示主题没有默认值:不配置该功能就不生效。
hugo.yml 的分层
OINK 站点配置有四类键,改哪一层取决于改动目标:
| 层 | 例子 | 谁定义的 |
|---|---|---|
| Hugo 原生顶层键 | baseURL title languages markup outputs taxonomies module |
Hugo 本身,行为见 gohugo.io |
params 顶层 |
logo offline_search github_repo version page_width comments |
主题读取的站点级选项 |
params.ui.* |
navbar_enabled sidebar_width_min typography pager_types |
外壳、导航与阅读界面 |
params.<运行时> |
mermaid plantuml drawio markmap |
各内容运行时自己的开关与端点 |
最小的可用配置只需要前两层(包含支撑 OINK 原生 Markdown 组件的三项 Goldmark 设置):
配置原则
-
主题默认保守,只写要改的键。交互功能(本地搜索、图片缩放、评论、反馈、深浅色菜单)默认关闭,主题不替站点做策略决定。从一份「完整配置」逐条删减,比按需添加更容易留下用不上的键。
-
没有主题总开关。不存在
oink.enabled,也没有params.oink.*命名空间,更没有在「Docsy 外壳」与「OINK 外壳」之间切换的选项。这一页查不到的开关即不存在。 -
非法值告警并回退到文档里写明的默认值。
params.ui.typography: solarized报invalid params.ui.typography "solarized" (allowed: technical | system) -- using "technical",站点照常构建;footer_style: thin、page_width: huge、section_index: grid同理。一个笔误因此只降级一个设置,而不是让hugo server下每个 URL 都返回 HTTP 500。 -
有一条警告保留取值而不是丢弃它。主题读出的
theme_color若在默认画布上低于 AA 正文对比度(4.5:1),颜色照常生效——自定义画布或品牌强制色由作者决定——同时打印可以静音该告警的ignoreLogsid。要么换个更深的颜色,要么加一行配置静音。只有解析不出来的十六进制才会被丢弃并回退到默认配色。 -
主题自身从不中断构建。它的模板里没有任何
errorf:每个非法值都走上面的告警并回退。Draw.io 与 Algolia 缺少必需的服务配置时告警并保持关闭。OINK 1.4.0 的 PlantUML 本地渲染无需端点;显式填写的非法端点会告警并关闭渲染。主题不会代为连接公共服务。残缺的上游署名告警并略去整条声明,因为半条读起来和完整的一模一样。真正会中断构建的来自 Hugo 而非主题:解析不到目标的内容引用,以及低于module.hugoVersion.min的 Hugo 版本。
页面级覆盖优先级
Hugo 的 .Param 查找让大部分参数可以逐页覆盖,优先级从高到低:
- 页面自己的 front matter;
- 祖先分区
_index.md里的cascade(离页面越近越优先); - 站点
params。
写进 front matter 时要去掉 ui. 前缀。
站点上的 params.ui.reading_time 在页面里就写成 reading_time。front matter 里出现 ui:
块的话,里面的键没有人读,也没有人报错——某个设置看着没生效时,先对照页面参数核一遍键名。
分区级用 cascade 一次设定整棵子树:
覆盖用于真实的内容差异。逐页重建一套视觉系统的配置,会在主题升级后失配。
三项 goldmark 前置
Hugo 不会 把主题模块的 markup 配置合并进站点,这三项必须写在站点自己的 hugo.yml 里,否则属性行、组件 HTML 与数学公式都不工作:
缺 attribute.block 时,{.fields} {.steps} {caption=…} 会原样显示成文字;缺 passthrough 时 \(x\) 不会变成公式;缺 unsafe 时步骤与卡片的结构会被转义。
renderer.unsafe: true 同时允许 Markdown 正文里的原始 HTML 通过,面向的是受信任的作者,不是投稿过滤器。内容来自不可信来源时,审查应放在提交流程里。
站点身份与品牌
Hugo 原生顶层键:
title,- 站名,显示在顶栏、
<title>与页脚 baseURL,- 生产域名;子路径部署时带上路径段
copyright,- 版权行的兜底值,
params.copyright未设时按 HTML 原样渲染 enableGitInfo, ,- 打开后才有「最后修改」与 commit 信息
enableRobotsTXT, ,- 生成
robots.txt enableEmoji, ,- 允许
:smile:简码
主题参数:
params.logo, ,- 品牌图标,可指向
assets/资源或static/路径,见品牌外观 params.wordmark,- 横向字标;设置后顶栏用它替代「图标 + 站名」
params.description,- 站点描述,页面没有
description时作为 meta 兜底 params.copyright,- 字符串按 Markdown 渲染;map 接受
authorsfrom_yearto_year(present表示今年) params.footer_center_info, ,- 页脚中间的行内 Markdown,设为空字符串即隐藏
params.author,- RSS 的作者;map 接受
name与email params.ui.theme_color,#rgb/#rrggbb十六进制色,为外壳的强调底着色;正文链接与行内代码不受影响 —— 见品牌外观params.ui.theme_color_dark, ,- 强调色的暗色一半;省略时从
theme_color提亮派生,直到在暗色画布上达到 AA
favicon 没有参数:主题按约定名扫描 static/(favicon.ico favicon.svg favicon-NxN.png apple-touch-icon.png apple-touch-icon-NxN.png),见品牌外观。
外壳类型与栏目根
外壳按 页面 type 生效,不看路径。文档可以放在任意目录,再用 cascade 给它 type: docs。
params.ui.shell_types, ,- 哪些 type 使用带侧栏的阅读外壳,见布局与页面类型
params.ui.docs_section, ,- 文档栏目的根目录名,只用于导航解析
params.ui.blog_section, ,- 博客栏目的根目录名
params.ui.docs_sidebar_root, ,section时 docs 页的侧栏根是文档栏目;home时是站点首页。非法值告警并回退params.ui.quick_links, ,- 命令面板空查询时列出的顶层菜单 identifier,见命令面板
params.ui.sidebar_root_enabled, ,- 允许子分区用
sidebar_root_for: self自成一棵侧栏树 params.ui.sidebar_root_menu, ,- 侧栏顶部显示栏目切换器;只有一个入口时退化为普通链接
params.ui.section_index, ,- 栏目首页子页列表样式:
list或cards,可按分区覆盖 params.ui.section_index_columns, ,section_index: cards时的列数
Expo 相册
OINK 1.3.0 为默认 shell_types 列表添加了 expo,
并支持以下设置:
params.ui.expo.images, ,- 页面包内的图片资源匹配模式
params.ui.expo.mode, ,wall、cards或seamlessparams.ui.expo.size, ,- 160 至 360 CSS 像素的目标尺寸
页面 expo 块整体替换站点块,未给出的字段使用内置默认值。
浏览器保存的布局与尺寸优先于作者默认值。完整规则见
Expo 默认值。
博客
以下设置控制博客栏目的呈现,作用于 params.ui.blog_section 指定的栏目,
可通过博客根目录的 front matter 或 cascade 按栏目覆盖。
布局指南对比了水平条目、卡片、表格与画廊。
下面的 gallery 布局与 blog_gallery 配置已随 OINK v1.4.0 发布;
四种布局都可以配置和切换。
params.ui.featured_image, ,- 文章正文里怎么渲染自己的题图:
none不渲染,banner在标题上方框出一张 16:9 的图,wash把它铺在文章头部背后、只留十分之一的不透明度,hero把它作为外壳自己的通栏背景铺开并把开头下移——单页与栏目列表页都一样。用的就是这一页在卡片与og:image里已经在用的那张图,两处不会打架。没有题图的文章在任何模式下都不渲染任何东西 params.ui.blog_index, ,list是水平条目,cards是规则图文卡片,table是紧凑表格,gallery是可点击封面组成的画廊。均按日期倒序排列;仅blog_index_toggle: false时的独立table不分页、列出整个栏目params.ui.blog_index_columns, ,blog_index: cards时的列数;md 到 xl 之间恒为两列,md 以下一列,不受此值影响params.ui.blog_index_size, ,list、cards、gallery及启用切换时四种视图共享的每页文章数;独立table忽略此值params.ui.blog_index_toggle, ,- 输出四种布局,让读者按
list→cards→table→gallery循环切换并记住选择;设为 false 时只输出配置的布局 params.ui.blog_gallery.mode, ,- 画廊内部排布:
wall保留 6px 间隙,seamless无间隙,cards为始终显示文字的瀑布流;独立于外层blog_index布局 params.ui.blog_gallery.size, ,- 目标画廊格子大小,范围 160–360 CSS 像素;不控制每页文章数
params.ui.toc_style, ,- 右栏的呈现方式:
fixed是钉在视口上的面板,flow是跟随内容流、从文章开头处开始、滚动后才钉住的宽面板 params.ui.toc_taxonomies, ,- 右栏的分类词云。既没有目录也没有词云的右栏不会渲染任何东西
页面或 cascade 的 blog_gallery 块整体替换站点块。缺失字段使用 wall 与
240;无效值告警并回退到这些默认值。读者保存的排布和大小优先于作者默认值,
并独立于 Expo 偏好。详见画廊形态。
作者与系列是 taxonomy 而不是参数,见分类法与写博客。
顶栏与页脚
params.ui.navbar_enabled, ,- 是否渲染站点顶栏,可用页面顶层
navbar_enabled覆盖,见导航与菜单 params.ui.navbar_autohide, ,- 顶栏收到视口上方,指针进入唤醒区才出现;小于 768px 或粗指针时不生效
params.ui.footer_style, ,fat多列网格 + 版权行,slim只有版权行,none不渲染。非法值告警并回退params.ui.dark_mode, ,true同时启用深色调色板与主题控件;只要控件写dark_mode: { show_menu: true }params.ui.breadcrumb, ,- 面包屑;设为
false关闭。顶层分区本来就省略只有一级的面包屑 params.ui.page_context_menu.enable, ,- 标题旁的页面操作拆分按钮
params.ui.page_context_menu.assistant_links, ,- 显示「在 ChatGPT / Claude 中打开」;读者点击时完整 URL 会离开本站
params.ui.page_context_menu.links, ,- 自定义外部操作,
url支持{url}{title}{markdown_url}占位符 params.ui.github_stars,- 顶栏 GitHub 徽标上的星数,本地常量,不发请求
params.ui.alt_site,- 单语言站在页脚显示的姊妹站链接,必填
label与绝对http(s)的url
胖页脚的列数据来自 data/footer/<语言>.yaml,不是参数,见导航与菜单。
侧栏
params.ui.sidebar_menu_compact, ,- 只展开当前分支与邻近条目
params.ui.sidebar_menu_foldable, ,- 允许读者展开/折叠分区
params.ui.sidebar_menu_truncate, ,- 一个分区最多渲染的条目数,超出截断
params.ui.sidebar_cache_limit, ,- 页数达到此值后,按语言、导航根与有效设置复用可见的中性导航标记;浏览器补 active 状态
params.ui.sidebar_width_min, ,- 桌面端拖拽调宽的下限,像素
params.ui.sidebar_width_max, ,- 拖拽调宽的上限,像素
params.ui.sidebar_item_overflow, ,ellipsis长标题省略,wrap换行params.ui.sidebar_icon_policy, ,- 图标密度:
all全部、groups只有根与有子页的节点、none全不显示。非法值警告并回落all params.ui.sidebar_expand_levels, ,- 默认展开的树层级数
params.ui.sidebar_headings, ,- 只对
type: book生效:在侧栏当前行下展开标题分支;整数取值 2–4,true等于 2 params.ui.sidebar_enabled, ,- 左侧栏;设为
false关掉,通常按页面而不是按站点设置 params.ui.taxonomy_icons,- 按分类复数名指定右栏分组图标,例如
tags: fa-solid fa-tags
侧栏怎么用见布局与页面类型;目录树本身由 content/ 的结构决定,见组织内容。
目录 TOC
右栏大纲的层级由 Hugo 原生配置决定,主题只控制跟踪行为:
markup.tableOfContents.startLevel, ,- Hugo 原生:收录的最高标题级别
markup.tableOfContents.endLevel, ,- Hugo 原生:收录的最低标题级别
params.ui.scroll_spy, ,- 1.x 静默兼容 no-op;普通外壳运行时始终跟踪当前大纲标题,此键不加载资源
单页隐藏大纲用 front matter notoc: true,见页面参数。
翻页与页尾
页尾组件顺序固定为分享 → 反馈 → 页面信息 → 翻页 → 评论,五者独立开关;反向链接在右栏目录旁。
params.ui.share, ,- 页尾分享目标,按给定顺序渲染,取值来自
xblueskymastodonfacebooklinkedinreddithackernewstelegramwhatsapplinepinterestweibochatgptclaudeemailcopy。为空则不出现分享栏。每一项都是纯粹的 intent 链接——没有 SDK、没有 iframe、没有第三方脚本、没有分享计数,见写博客。未知目标告警并丢弃 params.ui.pager_types, ,- 哪些 type 显示上一页/下一页;单页用 front matter
pager: false退出。未知 type 告警并丢弃 params.ui.annotation, ,- 正文末尾的「最后修改」与出处区块;上游署名由页面的
upstream_link一族键驱动,见页面参数 params.ui.backlinks, ,- 在右栏目录旁以「反链」组列出链接到本页的页面,构建时从普通链接派生,见导航与菜单
params.ui.compare, ,- 为存在同源 HTML 译文的 Docs、Book 和博客页面提供双列视图;单个语言可设为
false退出,见双列视图 params.ui.translation_notice, ,- 权威版本的语言代码,译文页据此显示一条指回原文的说明;页面写
translation_notice: false退出 params.ui.reading_time, ,- 页面标题下显示阅读时长
params.ui.book_draft_banner, ,- Book 草稿页开头额外加一条横幅
搜索与命令面板
本地搜索默认关闭;打开后命令面板才会出现(顶栏放大镜、Cmd/Ctrl 加 K、/、\)。
params.offline_search, ,- 生成每语言一份本地索引并启用命令面板,见全文检索
params.offline_search_on_serve, ,hugo server预览时也构建索引,预览行为与线上一致;站点极大时设false跳过以加快本地重建params.offline_search_index, ,- 索引范围,逐级累加:
titleheadingsummarycontent。非法值告警并使用content params.offline_search_summary_length, ,summary档摘录截断的字数params.offline_search_max_results, ,- 结果条数上限,同时约束 Lunr 与中文子串兜底
params.ui.landing_search, ,layout: landing页面是否保留搜索入口params.ui.command_palette.commands, ,- 自定义命令,每条二选一:
url或内置action;见命令面板 params.gcs_engine_id,- Google 可编程搜索引擎 ID,启用后引入外部服务
params.search.algolia,- Algolia DocSearch,必须显式给出
appIdapiKeyindexName,缺一则告警并保持 DocSearch 关闭
自定义命令的每条记录只接受 id title description icon keywords url action 七个键;id 必须匹配 ^[a-z][a-z0-9_-]*$,且不能与内置动作 ID 重名。分语言的标题写在 languages.<lang>.params.ui.command_palette.commands。
键盘
params.ui.keyboard_nav, ,- 单键导航(WASD/方向键走树、j/k 跳标题、q/e 翻页、面板与外壳开关)。设为
false后运行时不进包,见键盘导航
图片缩放
params.ui.image_zoom, ,- 允许正文图片点击放大;页面用 front matter
image_zoom覆盖。非布尔告警并回退
哪些图片会成为缩放候选见图片。
字体排版
params.ui.preset, ,- 站点级视觉预设:
paper、slate,或显式选择的实验ink、terminal。1.2.0 新增;选择slate保留原有外观 params.ui.preset_menu, ,true提供 Paper、Slate 与站点默认值;列表显式开启实验且必须包含站点默认值;与dark_mode独立params.ui.typography, ,technical使用所选预设的本地字体(Paper:Plex Sans;Slate:Inter);system只用平台字体栈,不请求品牌字体。非法值告警并回退params.ui.fonts,- 为
uibodyheadingcodedisplaymetabrandprint八个角色指定字体族。主题校验名称但不加载字体文件;每份列表都应以通用字体族收尾 params.page_width, ,- 外壳整体宽度:
normalwidefull,可逐页覆盖 params.reading_width, ,- Book 页正文的阅读行宽:
slimnormalwide,不影响外壳
读者系统已有字体,或者站点已经用 @font-face 声明时,可以直接写
params.ui.fonts。随站点分发字体文件与更底层的排版调整仍走 SCSS/CSS 入口,见
品牌外观。
评论与反馈
params.comments.enable, ,- 站点级评论开关,页面用 front matter
comments覆盖,见启用评论 params.comments.type, ,- 目前只有
giscus会真正渲染 params.comments.giscus.repo,- 承载讨论的 GitHub 仓库,必填
params.comments.giscus.repoId,- 仓库 ID,必填
params.comments.giscus.category,- 讨论分类名,必填
params.comments.giscus.categoryId,- 讨论分类 ID,必填
params.comments.giscus.mapping, ,- 页面与讨论的映射方式
params.comments.giscus.term,mapping为specific或number时的讨论标题或编号;不设置时不输出这个属性params.comments.giscus.strict, ,- 严格标题匹配
params.comments.giscus.reactionsEnabled, ,- 显示主贴表情
params.comments.giscus.emitMetadata, ,- 向父页面发送讨论元数据
params.comments.giscus.inputPosition, ,- 输入框在评论列表上方还是下方
params.comments.giscus.theme, ,- giscus 主题,
auto跟随站点深浅色 params.comments.giscus.lightTheme, ,- 浅色模式下使用的 giscus 主题或自定义 CSS URL
params.comments.giscus.darkTheme, ,- 深色模式下使用的 giscus 主题或自定义 CSS URL
params.comments.giscus.loading, ,- iframe 加载策略
params.comments.giscus.lang, ,- giscus 界面语言。不设置时中文站解析为
zh-CN/zh-TW/zh-HK,其它语言取主语言代码,giscus 不支持则回落en params.comments.giscus.ariaLabel, ,- 评论区容器的
aria-label;默认值是英文,多语言站点需按语言各写一份 params.comments.giscus.errorMessage, ,- 加载失败时显示的文字;默认值是英文,多语言站点需按语言各写一份
params.ui.feedback.enable, ,- 页尾「这页有帮助吗」两个按钮;无后端,有
gtag时记录结构化事件 params.ui.feedback.reasons, ,- 选「否」后展开四个可选原因
四个 giscus 必填项缺任意一个,都会告警并跳过评论区。必填字段见评论。
仓库链接与页面信息
params.github_repo,- 内容仓库 URL,解析「编辑本页」「查看历史」「新建子页」「提文档 issue」,见仓库与页面信息
params.github_project_repo, ,- 产品仓库 URL,用于「提项目 issue」与顶栏 GitHub 入口
params.github_branch, ,- 编辑链接指向的分支
params.github_subdir,- 内容站在 monorepo 里的子目录
params.path_base_for_github_subdir,- 重写统一为
/的源码路径;map 接受from与to。外部挂载必须显式映射为仓库相对路径,见仓库链接。 params.github_url, ,- 已移除,改写
params.github_repo。那份负责提示替代键名的迁移登记表已经删掉,所以旧键现在只是一个没人读的键 params.ui.lastmod_commit, ,- 「最后修改」后面附什么:
subjectcommit 标题、hash短哈希、none不附。非法值告警并回退 params.images, ,- 站点级社交卡片:页面自己没有封面时用它填
og:image;只进元数据,不会渲染成列表缩略图 params.upstream_source, ,- 声明了
upstream_link的页面默认使用哪个data/upstreams记录;页面 front matter 可以覆盖 params.upstream_modified, ,- 上游材料是否经过改编的站点默认值;页面可以覆盖,没有
upstream_link时不渲染署名 params.default_featured, ,- 已移除,改写
params.images或栏目cascade里的images。同上,旧键现在只是一个没人读的键
内容运行时
Mermaid、KaTeX、ECharts、Infographic、Asciinema、Swagger UI 与 Redoc 按内容自动检测,只有用到它们的页面、且只在该页的 HTML 输出里加载,没有站点开关。需要开关或外部端点的只有这几个:
启用 PlantUML 且不配置端点时使用本地渲染;填写 svg_image_url 才选择服务端模式。
Mermaid 与本地 PlantUML 按图表是否接近视口加载引擎,共用查看器和源码导出,
无需新增配置。普通浏览器打印未渲染图时保留源码。
params.markmap, ,- 站点级启用思维导图围栏,见思维导图
params.mermaid,- 透传给
mermaid.initialize()的配置;键名全小写,深色模式自动覆盖theme params.plantuml, ,true启用本地渲染;false关闭所有 PlantUML 渲染params.plantuml.enable, ,- 启用 PlantUML 本地渲染,见 PlantUML
params.plantuml.svg_image_url,- 可选 PlantUML 服务端点;省略时在浏览器本地渲染
params.plantuml.svg, ,- 仅服务端模式有效:使用内联 SVG 而非
<img> params.drawio.enable, ,- 启用
.drawio.svg图片的编辑按钮,见 Draw.io params.drawio.drawio_server,- Draw.io 编辑器地址,启用时必填,缺失则告警并保持 Diagrams.net 关闭
params.highlight_classes, ,- 代码高亮输出 Chroma class;设
false回到 Hugo 的行内样式 params.ui.code_copy, ,- 代码块的复制按钮;设为
false全局去掉,围栏上的copy=仍然优先
数学公式不需要参数,只需要 passthrough 前置。
输出格式
主题声明自定义输出格式,但 不替站点打开:要哪种就在 outputs 里写哪种。成本
较高的聚合输出与机器可读输出始终需要显式选择。
| 格式 | 产物 | 说明 |
|---|---|---|
HTML |
index.html |
交互形态,必选 |
markdown |
index.md |
每页的纯 Markdown 版本,页面操作里的「复制 Markdown」「查看源码」依赖它,见 Agent 支持 |
LLMS |
llms.txt |
主题声明的纯文本格式,通常只挂在 home |
LLMSFULL |
llms-full.txt |
顶层栏目 opt-in:按侧栏阅读顺序拼接同一份逐页 Markdown,每种语言一份全文包 |
NAVJSON |
navigation.json |
首页 opt-in:每种语言把侧栏 / 翻页使用的导航权威序列化一次,由 schema/nav.v1.schema.json 校验 |
print |
_print/index.html |
主题声明的整分区打印页,见打印支持 |
BookManifest |
book.json |
Book 根 opt-in,向 EPUB/PDF 打包工具交接的 JSON;本身不是电子书 |
RSS |
index.xml |
Hugo 原生,挂在 section 上让每个栏目都有订阅源 |
LLMSFULL 与 BookManifest 写在对应顶层栏目的 front matter outputs 中,
NAVJSON 写在 outputs.home。完整示例与限制见 Agent 支持
和书籍出版。
打印输出的两个参数:
params.print.toc, ,- 打印页开头生成目录;设为
false不生成 params.print.section_break_wordcount, ,- 打印页中一节多少词以上才另起一页
多语言与版本
语言用 Hugo 原生的 languages 块定义,主题只读它建立的翻译关系:
defaultContentLanguage, ,- 不带路径前缀的首要语言
languages.<lang>.label,- 该语言的自称,显示在语言菜单里
languages.<lang>.locale,- 完整 locale,用于
<html lang>与 SEO languages.<lang>.weight,- 语言顺序,也是点击语言图标时的循环顺序
languages.<lang>.title,- 该语言的站名
languages.<lang>.direction, ,- RTL 语言设为
rtl
写作侧的对等文件、锚点对齐与缺译回退见多语言。
版本相关参数:
params.version,- 当前站点变体的版本标识(不一定是 Git ref),见多版本
params.version_menu, ,- 版本菜单的标题
params.version_menu_pagelinks,- 切版本时先尝试目标站点的同一路径
params.versions,- 版本条目:
versionurlkind,name: '---'是分隔线 params.archived_version,- 顶部显示「这是归档版本」横幅
params.url_latest_version,- 归档横幅里指向最新版的链接
params.time_format_blog, ,- 博客日期格式,按语言覆盖
params.time_format_default, ,- 其它日期格式,按语言覆盖
其它
通过生成式 Schema 获得编辑器补全
主题在其 schema/ 目录下携带两个生成的 JSON Schema:校验站点 hugo.yaml 的
site-params.schema.json 与校验页面 front matter 的
front-matter.schema.json。它们是主题自身 hugo.yaml 默认值(注释即悬浮文档)
与参数扫描注册表的投影;主题 CI 会重新生成并检查漂移。使用时应选择与主题固定版本
相同标签下的 Schema。
配合 VS Code YAML 扩展,在设置中映射站点 Schema。下面以 OINK v1.5.1 为例,
请将标签换成 go.mod 中固定的版本;两种常见 YAML 配置文件名都已覆盖:
验证关联是否生效时,可暂时把 params.offline_search 这类已知布尔键写成字符串,
确认编辑器提示类型不匹配后恢复正确值。front matter 补全取决于你的 Markdown
工具链,用同样方式指向 front-matter.schema.json 即可。
front-matter Schema 刻意不带类型约束,因为 share、theme_color 这类键在常规
类型之外还接受裸布尔退出。
验证配置变更
改完配置跑一次生产构建:
输出 Total in … 且没有 ERROR 即可。常见告警与原因:
| 告警片段 | 原因 |
|---|---|
invalid params.ui.typography |
预设只有 technical 与 system |
invalid footer_style … (allowed: fat | slim | none) |
页脚形态写错,告警会指出是哪个页面 |
invalid page_width … (allowed: normal | wide | full) |
页宽写错 |
invalid params.ui.section_index … (allowed: list | cards) |
栏目首页样式写错 |
invalid params.offline_search_index |
索引范围只有 title heading summary content |
params.drawio.enable requires an explicit params.drawio.drawio_server |
开了 Draw.io 却没给服务地址 |
params.search.algolia requires explicit appId, apiKey, and indexName |
Algolia 三项必须齐全 |
params.ui.image_zoom must be a boolean |
写成了字符串 "true" |
theme_color … is not a #rgb or #rrggbb hex color |
值不是十六进制颜色,保留默认配色 |
theme_color … reads at about N:1 against the theme's … canvas |
建议性告警:颜色照常生效,消息里带着让它闭嘴的 id |
theme_color_dark … has no theme_color to pair with |
只设了暗色一半而没有有效的 theme_color;该值被忽略,两种模式都保留默认配色 |
command … must define exactly one of url or action |
自定义命令同时给了 url 和 action,或两个都没给 |
invalid params.ui.sidebar_icon_policy …; using all |
只是警告,但取值拼错了 |
配置改动还要至少验证三件事:每种语言各一页、缺译页的回退、生产 baseURL 下的链接(子路径部署容易漏)。
主题要求 Hugo Extended 0.168.0 及以上版本。配置改动请使用固定基线验证:
最低版本写在主题的 hugo.yaml 与 theme.toml 里,站点自己的
module.hugoVersion.min 应与它一致。
相关
5.2 - 品牌外观
本页的前提是站点已能构建(十分钟上手)。站名、Logo、页宽和页脚
先在 hugo.yml 中设置。选择系统已有或站点已加载的字体,用
params.ui.fonts 即可;分区强调色也有对应配置键。
图标放在 static/ 下。需要修改 SCSS 变量时再使用
assets/scss/_variables_project.scss;自定义样式或新增 @font-face 声明放在
assets/scss/_styles_project.scss。不要改主题目录里的文件:升级会替换它们。
视觉预设
OINK 1.2.0 默认使用 Paper:暖纸色背景、墨色正文、蓝色链接、Plex Sans 与外框表格。Slate 保留 OINK 原有的冷灰蓝外观。开启读者选择:
主题的 preset_menu 默认值为 false。设置 preset: slate 即可保留原有外观。
风格与明暗分别保存;选择站点默认项(悬停提示中注明)恢复跟随站点。所有风格共用一个
样式表,字体全部本地加载。
OINK 1.2.0 起提供 Ink 与 Terminal 实验预设,需要显式开启:
Ink 使用黑白表面、红色标记、直角面板和带下划线的正文链接。Terminal 使用等宽
控件与标题、青色链接、琥珀强调和紧凑的桌面导航,长文正文仍为无衬线字体。两者
均支持明暗切换并复用现有字体。菜单使用两列排列的四个简洁按钮,各带主题色图标,
不加实验标记。preset_menu: true 提供 Paper、
Slate 和站点默认值,不会自动开启所有实验。设置 preset: ink 或 preset: terminal
可将实验风格设为站点默认值,不依赖读者菜单。测试范围与后续设计工作见
实验记录。
只使用 [data-bs-theme='dark'] 的站点自定义深色规则,优先级低于 Paper 深色色板。
可以保留 Slate,或改用 [data-td-preset='paper'][data-bs-theme='dark'] 限定规则。
字体配置与分区 theme_color 在所有预设下继续优先。打印始终使用浅色与白纸背景。
站名
站名出现在顶栏、浏览器标题与页脚。多语言站每种语言各写一个:
顶层 title 是兜底,languages.<lang>.title 优先。
Logo 与字标
主题默认使用自带的 assets/icons/logo.svg。替换步骤是把图标文件放进站点的 assets/ 或 static/,再在配置里指向它。
params.logo是方形图标,顶栏、侧栏与页脚共用。放在assets/下会经过 Hugo 资源管线(可指纹化),放在static/下按原样发布;两种写法都是相对assets/、static/根的路径。params.wordmark是横向字标。设置后顶栏用它替代「图标 + 站名」,窄屏放不下时回落到params.logo。不设置则保持「图标 + 站名」。
源 SVG 应紧贴图形边缘裁切,否则各处尺寸对不齐。SVG 必须带 viewBox,颜色继承 currentColor,或者在深浅色下都有足够对比度。
本站两个参数都不设:顶栏用主题自带的 assets/icons/logo.svg 搭配以展示字体渲染的站名。
favicon
favicon 没有参数。主题扫描站点 static/ 目录里的约定文件名,发现哪个就在每个页面输出对应的 <link>:
| 文件 | 生成的链接 |
|---|---|
static/favicon.ico |
rel="icon" |
static/favicon.svg |
rel="icon" type="image/svg+xml" |
static/favicon-32x32.png |
rel="icon" 带 sizes,按尺寸升序输出 |
static/apple-touch-icon.png |
rel="apple-touch-icon" |
static/apple-touch-icon-180x180.png |
rel="apple-touch-icon" 带 sizes |
够用的最小组合是 favicon.ico + favicon.svg + apple-touch-icon.png。带尺寸后缀的文件必须是正方形(NxN),否则不会被识别。
这些文件用任意图形工具生成即可。主题不需要 Node.js,Hugo 只发布 static/ 里已经存在的文件。
Web App Manifest 一类的额外 head 元数据不在扫描范围内,用 layouts/_partials/hooks/head-end.html 钩子自行输出;要改变发现规则本身(换目录、增加文件名),在站点 layouts/ 下覆盖 layouts/_partials/favicons.html。
主色与配色
配色分两层:Bootstrap 的语义色(编译期 Sass 变量)和 OINK 的品牌层(运行期 CSS 自定义属性)。
先改语义色,它决定按钮、链接、提示块的色调:
这个文件在 Bootstrap 与 OINK 默认值 之前 加载,是覆盖 Sass 变量的位置。需要引用 Bootstrap 已定义的变量或 map 时,改用 _variables_project_after_bs.scss。
品牌层是一组 CSS 自定义属性,浅色和深色 必须成对覆盖,否则一种模式下会漏色:
可覆盖的品牌属性有 --td-brand-elev(浮层底色)、--td-brand-silk(次要文字)、--td-brand-copper 与 --td-brand-copper-dim(强调色与它的弱化版)、--td-brand-line-strong(分隔线)、--td-brand-header-bg(顶栏背景)、--td-brand-shadow-sm / --td-brand-shadow-md(阴影)、--td-brand-mark-from / --td-brand-mark-to / --td-brand-mark-gradient(品牌渐变)。
分区主题色
上面的品牌配色决定整站外观。theme_color 用一个十六进制颜色调整当前分区的
界面强调色,让读者通过选中状态和导航提示辨认所在分区。
它按分区写比按站点写有用得多。写进分区根的 cascade,整个分区就有了身份 ——
藏青的文档、紫色的博客、橙色的教程 —— 而站点默认仍是品牌色:
Hugo 会把这些 cascade 值同时解析到分区首页与子页,因此只声明这一对即可。同一对 解析结果既驱动页面强调色,也驱动根切换器里该分区的图标。
它作用于:侧栏选中行与悬停背景、当前目录项的背景及其指示线和位置标记、 悬停或聚焦的 Book 章节小标题、标签与徽章的悬停状态、卡片的悬停边框、分享按钮的 悬停背景、文本选中背景、焦点环,以及侧栏根切换器中的分区图标。
它刻意不作用于:正文链接、外链、行内代码。这些是阅读约定,不是品牌表面 —— 一页密集的标识符在任何分区都该读成「代码与正文」,链接在哪里都该看起来像链接。 这也是强调色单独占一个自定义属性、而不是去重刷 Bootstrap 链接色的原因。
暗色一半是可选的。省略时,从亮色向白提亮,直到在暗色画布上达到 AA 正文对比度,
所以只填一个颜色的作者不可能产出不可读的暗色配色。派生结果不再符合期望的品牌
色相时,再自己指定暗色一半。亮色才是主键:单独设置 theme_color_dark,或者把它放在
一个非法的 theme_color 旁边,两种模式都不会着色 —— 主题会发出警告并保留默认配色,
而不是只给暗色模式上色。
要让某一页恢复默认配色,在 front matter 中设置 theme_color: false。
这会同时取消继承的亮色与暗色分区配色,不产生警告。其他非十六进制取值
(数字、true、颜色名)都会告警。
主题按默认页面背景检查颜色,低于 AA 正文对比度(4.5:1)时告警,颜色仍会生效。 若使用自定义背景或必须保留品牌色,可根据实际对比度选择是否忽略该告警。 发布前检查实际背景上的对比度,按需调整颜色;无需将全部告警改为构建失败。
这个检查是拿颜色对着页面画布读的。有些交互表面会同时把它用作文字与半透明淡铺; 例如可点击的实心徽章在 hover 时,是强调色文字压在 12% 的同色淡铺上。这一对比 画布检查更紧。如果颜色只是刚好过线,还要检查这些表面,必要时再调深一档。
Hugo 按键合并参数:某一页在同时设了 theme_color_dark 的分区里只覆盖
theme_color,会继承那个暗色。要么两个都覆盖,要么都不覆盖。
深浅色模式
主题默认 不显示 深浅色控件。开启方式:
太阳/月亮图标表示当前状态:浅色显示太阳,深色显示月亮。
点击顶栏或底栏的「外观」,打开浅色、深色、跟随系统单选组,支持触屏与键盘,
手机上显示为底部表单。选择保存在浏览器本地并同步到其他标签页,没有选择时跟随
prefers-color-scheme。head 内联脚本在样式表加载前应用实际明暗状态。
只要深色调色板、不要控件时写 dark_mode: { show_menu: false, enable: true };dark_mode: false(默认)两者都不启用。
自定义组件在两种模式下都要给出可读的悬停、聚焦、禁用、选中状态,正文对比度至少 4.5:1、大号文字 3:1。
字体
字体策略在构建期选择,切换视觉预设时使用对应的内置字体:
technical(默认):Paper 的界面、正文与展示标题使用 IBM Plex Sans;Slate 的界面与正文使用 Inter,展示标题使用 Chakra Petch。两者的字标使用 Chakra Petch,代码使用 IBM Plex Mono。中文与 emoji 落到平台字体。Plex Sans 与 Inter 包含本地拉丁、西里尔、希腊与越南语子集,不请求 Google Fonts。- 实验字体:Ink 的界面、正文与标题使用 Inter;Terminal 的控件与标题使用 IBM Plex Mono,正文使用 Plex Sans。两者复用现有代码字体。显式
fonts.ui仍控制主字体;需要独立正文字体时设置fonts.body。 system:界面、展示、元数据、打印与等宽角色全部回到平台字体栈,浏览器不请求品牌字体。字体文件仍随主题分发,只是不被引用。
非法取值告警并回落到 technical。选中的值写入 <html data-td-typography="…">,可在浏览器中确认。
自定义字体
字体角色是八个 CSS 自定义属性,覆盖它们即可,不必查找组件选择器:
| 属性 | 配置键 | 用在哪 |
|---|---|---|
--td-ui-font-family |
ui |
导航、控件与界面文字 |
--td-body-font-family |
body |
正文与博客 |
--td-heading-font-family |
heading |
正文标题 |
--td-code-font-family |
code |
代码与终端 |
--td-display-font-family |
display |
展示型大标题 |
--td-meta-font-family |
meta |
技术标签与元数据 |
--td-brand-font-family |
brand |
字标 |
--td-print-font-family |
print |
打印正文 |
ui 是主字体:body 经它解析,heading 又经 body 解析,所以只写 ui 一行,界面、正文与标题一起换掉。
在配置里换
只是想换一套字体族,不必碰 SCSS,写 params.ui.fonts 即可:
这里写的是字体族名,不是字体文件。主题不会因为这个键去下载或加载任何字体:所写的族必须是读者机器上已有的,或者站点自己在样式表里 @font-face 声明过的。所以每个列表都要以通用族(sans-serif、monospace、serif)收尾——读者没有你写的字体时,落到那里。
取值只放行纯粹的字体族语法:带引号的名字、裸标识符、允许前导连字符(-apple-system),以及任何文字系统写成的名字(苹方 合法)。分号、花括号、括号、url()、尖括号一律不过关。未知角色或不合法取值只告警并单独丢弃,同一份 map 里其余的行照常生效。什么都不设时,<head> 里连这个 style 元素都不会出现。
该块在样式表之后输出,这正是作者字体能在同等优先级下压过 typography 预设的原因。
在样式表里换
要自带字体文件,或者只给某一类内容换字体,仍然走样式表。把 .woff2 放进站点 static/webfonts/,在项目样式中声明字体,再指定各角色使用的字体族:
角色按 CSS 规则继承,只给某类内容换字体也不必复制组件选择器:
等宽字体要带中文兜底,否则中英混排的代码块会对不齐:
从 Docsy 迁移过来的站点不必改写法。旧的 Sass 变量仍然喂进对应角色,写在 _variables_project.scss 里照样生效,优先级高于预设默认值:
| 旧 Sass 变量 | 喂给的字体角色 | 说明 |
|---|---|---|
$td-fonts-serif |
--td-ui-font-family / --td-body-font-family |
Docsy 的界面字体栈,赋值给 $font-family-sans-serif |
$font-family-sans-serif |
--td-ui-font-family / --td-body-font-family |
项目给出自己的栈时,technical 预设不再把预设内置的无衬线字体放在它前面 |
$font-family-base |
--td-ui-font-family / --td-body-font-family |
Bootstrap 的正文变量,经 --bs-body-font-family 进入角色 |
$headings-font-family |
--td-heading-font-family |
不设置时标题继承正文角色 |
$font-family-code |
--td-code-font-family |
代码、终端与 pre / code / kbd |
$td-font-family-monospace |
--bs-font-monospace |
赋值给 $font-family-monospace |
$font-family-monospace |
--bs-font-monospace |
system 预设下,项目的显式取值优先于平台等宽栈 |
Docsy 的三个 Google Fonts 变量 $td-enable-google-fonts、$td-google-font-name 与 $td-web-font-path 主题已不再读取。它们留在 _variables_project.scss 里不影响构建,也不产生任何效果:随主题分发的是 IBM Plex Sans、Inter、Chakra Petch 与 IBM Plex Mono,所有预设都不向 Google Fonts 发请求。打印角色 --td-print-font-family 跟随正文角色,主题不为纸张单独提供字体。
YAML 里只接受字体族名。远程字体 URL 与任意 CSS 都不接受:字体文件与样式必须是可审查的本地输入,一次普通构建不会因为字体发出任何网络请求。
页宽
page_width 控制外壳整体宽度,可逐页或按分区 cascade 覆盖。Book 页另有一个
reading_width(slim / normal / wide),改的是正文阅读行宽,不是外壳。
两个键取值非法都会告警并回退。
页脚
fat(默认):多列链接网格 + 版权行;slim:只有版权行;none:不渲染页脚。
页面 front matter(含分区 cascade)可以覆盖它,本站的文档栏目用的是
footer_style: slim。无法识别的取值会告警并回退到 fat。
多列网格的数据在 data/footer/<语言>.yaml,写法见导航与菜单。配了 fat 但没有数据时自动降级成 slim,可以先开启再补内容。
params.copyright 接受 Markdown 字符串,或 authors / from_year / to_year 三键的 map(present 表示今年)。footer_center_info 是页脚中间的行内 Markdown,显式设为空字符串即隐藏中间区域。
SCSS 入口与不该做的事
站点的 SCSS 覆盖进入主题的同一个样式包,生产构建仍然只有一份带指纹与完整性校验的样式表。三个入口文件放在站点 assets/scss/ 下:
| 文件 | 什么时候用 |
|---|---|
_variables_project.scss |
在 Bootstrap 与 OINK 默认值之前设置 Sass 变量($primary、字体变量) |
_variables_project_after_bs.scss |
设置依赖 Bootstrap 已有定义的变量或 map |
_styles_project.scss |
在主题组件样式之后写选择器与 CSS 自定义属性 |
编译顺序是:Bootstrap 函数 → 项目变量 → OINK 默认值与 Bootstrap → Bootstrap 之后的项目变量 → OINK 组件与品牌层 → 项目样式。
CSS 接口有明确边界。字体那一节的八个字体角色与 --td-brand-* 品牌属性是公开接口,主题在小版本之间保持它们的名字与含义。组件别名(如 --td-asciinema-font-family)只承诺在该组件范围内有效,未在文档中记录的 --td-shell-* 一类变量是实现细节,随时可能改名或消失。
不该做的事:
- 不改主题目录里的任何文件(
hugo mod会覆盖); - 不单独
@import主题的内部 partial,它们不是公开的 Sass 接口,导入顺序可能变化; - 不为了改一个颜色去覆盖
baseof.html。有设计变量就用变量,没有再写作用域尽量小的选择器; - 不引用远程样式表或字体 CDN。
需要额外的第三方 CSS 时,用 layouts/_partials/hooks/head-end.html 钩子发布本地资源,不在 Markdown 里写 <link>。
验证
- 构建输出
Total in …且没有 ERROR; - 页面源码里
<html>上有data-td-typography="technical"(或所选的预设); - 浏览器中顶栏显示自己的 Logo 与站名,标签页图标是自己的 favicon;
- 切到深色模式再看一遍正文、表格、提示块、代码块与焦点框。配色改动容易只在一种模式下验证过;
- 换一种语言,确认站名随之切换。
字体是否已替换,用浏览器开发者工具查任意一段正文的 font-family:应当包含自己设置的字体族;再查看实际渲染字体,确认文件加载或系统字体回退符合预期。
相关
5.3 - 首页与落地页
首页不是模板,是一份数据:data/home/<语言>.yaml 里的 sections 列表决定页面从上到下有哪些分区,每个分区的内容在同一份文件里按名字取。普通页面加 layout: landing 也能用同一套分区。
分区全部在服务端渲染。价格、star 数、截图、头像、下载状态都必须在 Hugo 启动前就存在于仓库中,没有分区会在浏览器里取数据。
从 Docsy 的 blocks/* 首页迁移过来的站点要重写首页:主题没有 blocks/cover、blocks/section、blocks/feature 这些 shortcode,保留它们会让构建报 template for shortcode "blocks/cover" not found。两条出路是本页讲的 data/home/<语言>.yaml,或者给一个普通页面加 layout: landing。
首页的数据来源
首页的内容文件只留标题与描述:
分区数据按语言分文件:
首页数据
- data/
- home/
- en.yaml英文站首页
- zh.yaml中文站首页
- home/
查找顺序是 data/home/<当前语言>.yaml → data/home/en.yaml → 单语言站点的 data/home.yaml。
文件结构只有两层:一个 sections 列表,加上被列表引用的同名键。
这是本站首页的写法,完整文件见仓库的 data/home/zh.yaml。
最小可用首页
创建下面的数据文件,把文字和链接换成自己的页面。两张首屏图片分别放在
static/images/hero-light.webp 与 static/images/hero-dark.webp;暂时没有图片时,
删去整个 hero.image 块即可使用纯文字首屏。链接写成不带前导斜杠的站内路径,
主题会补上当前语言前缀(docs/start/ → /zh/docs/start/)。
Hero
Hero 是首屏,唯一一个带大标题与配图的分区。
不写 title_lines 时用 title,两者都没有时用站点标题。配图是 CSS 背景图,alt 有值时容器带 role="img",无值时对辅助技术隐藏。
align: center 是纯文字的居中首屏:文案块加宽居中,标题自动平衡换行,note
挪到按钮下方。两者同时出现时,主题会告警并回退到 start 以保留图片。
分区注册表
22 种分区,名字用连字符(旧数据里的下划线会被规范化)。除 Hero 之外,每种都共用 eyebrow / title / desc(或 text)三个抬头字段与一个 class。
| 类型 | 放什么 |
|---|---|
hero |
首屏:大标题、按钮、跟随主题的配图 |
metrics |
数字事实,可选计数动画与来源链接 |
capabilities |
左右交替的能力叙事 + 专用视觉面板 |
principles |
编号的产品原则 |
cards |
通用卡片集合:功能、场景、入口 |
logo-wall |
工具与伙伴,网格或纯 CSS 跑马灯 |
gallery |
截图墙 |
testimonials |
引语与署名 |
contributors |
人、角色、头像与链接 |
faq |
折叠或平铺的问答 |
markdown |
一段自由 Markdown |
cta |
结尾的行动号召 |
pricing |
价格档位卡片 |
pricing-compare |
档位功能对比矩阵 |
command-box |
一条可复制的命令 |
steps |
有序流程,可带命令 |
timeline |
带日期的里程碑 |
code-plate |
展示面板里的代码 |
preview |
一段 Markdown 源码与它渲染出来的样子并排 |
case-study |
案例:指标 + 引语 + 出处 |
download |
一个或多个 data/download/ 记录 |
bar-chart |
不用图表 JS 的数值对比 |
写错类型名不会静默消失:构建时给一条 unknown section type 警告并跳过该分区。
常用分区的最小写法
卡片与能力面板是最常用的两种。cards 用 columns 控制列数:
capabilities 是一屏一条能力,右边配一块结构化的视觉面板,visual.type 只能是 shell、components、code、image、card 五种之一:
这些片段摘自主题仓库的可执行测试样例
tests/site/data/landing/demo/en.yaml,字段名可照抄。
download 分区消费的就是发布与下载页里那份 data/download/<key>.yaml,不引入第二套版本模型。
任意页面做落地页
普通内容页加两行 front matter 即成为落地页:全宽画布,保留顶栏、命令面板与页脚,去掉侧栏与目录。
数据放在与首页平行的目录下,同样按语言分文件:
落地页数据
- data/
- landing/
- pricing/
- en.yaml
- zh.yaml
- pricing/
- landing/
非首页落地页按这个顺序查找数据。全部找不到时,主题告警并渲染没有分区的 Landing 外壳:
- 页面 front matter 里的
sections; data/landing/<key>/<精确语言>.yaml;- 单文件
data/landing/<key>.yaml里的精确语言条目; - 英文或无语言后缀的记录。
数据量小时可以写在 front matter 里,但 landing: 与 sections: 互斥:
分区条目写法
sections 的每一项可以是一个类型名字符串,也可以是一个 Map:
| 键 | 作用 |
|---|---|
type |
分区类型;省略时用 key 当类型 |
key |
从哪个键取数据,默认与 type 同名;同一种分区用两次时用它区分 |
data |
内联数据,不再到顶层查找键 |
id |
分区的锚点 ID,默认由 key / type 生成 |
enabled: false |
停用这个分区,保留数据 |
partial |
换成站点自己的 partial。属于本地模板约定,不是可移植的 Landing 数据 |
多语言与本地事实
叙事文字优先分语言文件(zh.yaml / en.yaml)。共享的事实记录也可以在字段级回退:<字段>_<精确语言> → <字段>_<主语言> → <字段>,语言标签里的 - 规范化成 _。中文站解析 title_zh_cn、title_zh、title。不接受 camelCase 后缀。
分区里的显示文字是站点数据,不是主题的 i18n 字符串。只有跑马灯暂停、定价状态这类主题自带控件用翻译键。多语言站点的整体配置见多语言。
落地页外壳上的几个可选事实也是本地的,写在 hugo.yml 里,运行时不会去取它们:
页脚不属于首页数据:它读 data/footer/<语言>.yaml(单语言站点用
data/footer.yaml),本站两种语言各一份。data/home/<语言>.yaml 里残留的
footer 键会在普通预览中告警并忽略,告警会提示新位置。
写法见导航与菜单。
输出形态
| 输出 | 呈现 |
|---|---|
| HTML | 完整的静态分区内容,再按需加载 landing.js 做渐显、计数、复制与主题图片切换 |
| 打印 | 内容保留,跑马灯之类的动态面变成静态网格,控件移除 |
| Markdown | 标题、正文、列表、表格与代码,不带组件 class |
| RSS | 不输出 Landing 分区 |
禁用 JavaScript 或 landing.js 加载失败时,服务端渲染的所有区块仍然可见。数字
指标已包含配置指定的数字格式、前缀和后缀;计数动画结束时使用相同的显示文本。跑马灯的
副本轨道不进无障碍树,暂停用的是不依赖 JavaScript 的复选框;读者开启减少动态
效果偏好时,移动与渐显关闭。
验证
- 构建生产:
hugo --printPathWarnings。类型写错、数据键不存在、landing与sections同时出现都在这一步暴露。 - 打开首页与落地页,逐个分区对照数据文件,每种语言各看一遍。
- 禁用 JavaScript 后刷新:内容仍在,只是没有动效。
- 深浅色各看一遍,确认
image.light/image.dark都给对。 - 部署到子路径时,确认站内链接与图片都带上了前缀。
相关
5.4 - 导航与菜单
本页覆盖读者在页面之间移动的入口:顶栏菜单、栏目切换器、面包屑、页面操作、上一页 / 下一页与页脚。侧栏树与目录属于布局与页面类型。
顶栏来自 Hugo 的 menus.main。Docs 与 Book 默认按 content/ 的目录结构组织;
导入既有阅读顺序时,也可使用 data/docs_nav.json。
侧栏、翻页器与分区索引共享选定的顺序。
顶栏菜单
顶层入口写在各语言的 menus.main 里:
weight 越小越靠前。pageRef 指向站内页面,url 指向外链;外链自动加 target="_blank" 与 rel="noopener noreferrer",并带一个外链角标。identifier 是配置里引用这个入口的稳定标识(quick_links、sidebar_root_menu 按它匹配),name 按语言翻译,identifier 不翻译。
菜单项也可以挂在页面 front matter 上,适用于「这一页本身就是一个顶层入口」:
顶栏右侧的 GitHub 入口 不是 菜单项,它来自 params.github_project_repo(未设时回落 params.github_repo)。标识为 github 的菜单项会被菜单区跳过,写了也不显示。改变这个入口的目标要改仓库参数,见仓库与页面信息。
下拉菜单
用 Hugo 的 parent 建立父子关系,只支持一级子项:
- 每个条目都是独占一行的一个图标加一个标题,整个面板是一列宽度适中的
纵向列表。子项的
params.description只是配置数据,面板不会渲染它。 - 父级本身是一个普通链接:悬停或键盘聚焦展开面板,点击或回车进入父级页面。没有单独的展开箭头,触屏读者落到父级页面,该页正文同样列出这些链接。
- 键盘:向下箭头展开并聚焦第一项,Esc 关闭并把焦点还给链接,点击面板外部关闭。
- 0.5 的
params.columns参数已退役:设置它会发出构建警告,面板保持单列。 - 再深一层会发出构建警告并降级成静态分组标题,不会 生成三级悬浮菜单。更深的层级放进侧栏。
菜单图标
小于 lg 时菜单项只剩图标,每个顶层入口都应有一个。图标按这个顺序解析:
- 目标页面 front matter 里的
icon; - 菜单项自己的
params.icon; - 按 identifier / 分区名匹配的内置默认值(
docsblogexamplescommunityaboutdownloadgithub等); - 都没有时用
fa-solid fa-link。
图标写成一对 Font Awesome class,主题本地提供免费版字体:
标签菜单
顶层入口指向 taxonomy 页面(/tags/、/categories/)时不需要手工配置子菜单:面板自动渲染「标签 + 数量」的 chip 网格,按数量降序排列。
分类怎么启用见分类体系。
顶栏控件
顶栏高 50px,由品牌(Logo 或字标)、居中的菜单和末端工具控件组成。启用后可用于各类 布局;窄屏下首页与 Landing 打开站点菜单抽屉,带侧栏的外壳页面则打开侧栏抽屉。
顶栏分为桌面完整形态与紧凑图标形态:
| 视口 | 状态 |
|---|---|
lg 及以上 |
品牌、居中的文字菜单,以及搜索、版本、语言、主题和 GitHub 控件;没有抽屉按钮 |
md 至小于 lg |
菜单以图标居中,工具控件留在末端;没有抽屉按钮 |
小于 md |
保留居中的菜单图标,末端只留搜索与相应的抽屉按钮;版本、语言、主题与快捷键帮助仍可在页脚最底层栏使用 |
各控件的开关不在这里:搜索图标要 params.offline_search(见全文检索),版本菜单要 params.versions(见多版本),语言菜单在配置了两种及以上语言时自动出现(见多语言),主题控件要 params.ui.dark_mode(见品牌外观)。
自动隐藏
开启后隐藏的顶栏仍保留 50px 高度。指针进入该区域上方 60% 的感应带,或键盘焦点进入 顶栏时,顶栏在原位淡入;布局不移动,也不遮挡静止的正文。左右各 64px 不属于唤醒区, 让折叠后的侧栏与大纲恢复按钮保持可用。
小于 768px、粗指针或纯触屏时自动停用,顶栏始终可见。首页不继承站点级开关,除非在自身
front matter 中显式开启。Hero 页面保留覆盖在主图上并随其滚动的顶栏。页面 front matter
顶层的 navbar_autohide 或分区 cascade 可以覆盖站点设置。
关闭顶栏
也可以只关闭某一页或某一段:
关闭后主题补回原本由顶栏承担的界面:移动端子导航、侧栏顶部的品牌与搜索行、大纲轨道上的工具按钮。这个开关适用于必须独占视口的页面,不作为常规排版偏好。本站文档栏目保留顶栏,通过 navbar_autohide: false 关闭自动隐藏,让栏目导航始终可见。
栏目切换器
侧栏顶部那一行是栏目切换器,决定当前显示哪棵树。入口集合按顺序去重构造:符合条件的
顶层栏目 → 符合条件的 sidebar_root_for: self 分区 → 当前解析出的根。候选必须有
permalink,且不能是分隔分组。
让一棵大子树自成一个根(带版本的 API 参考、独立手册),在它的 _index.md 里:
self 让这个分区索引与它的后代都用这棵新树;children 把索引留在父树里,只约束后代。在顶层分区或嵌套自根的 front matter 中设置 sidebar_root_menu: false,会将它从全站候选集合中排除。当前解析出的根不在集合中时仍会追加,因此浏览该分区时,它会保留为位置提示。
1.1 的实现让嵌套自根与顶层分区都遵守这项排除设置;1.0 中的嵌套自根即使设置了 false
也可能仍被列出。使用 build.render: never 或作为分隔分组的根不会成为切换器链接。
没有入口时不渲染切换器;只有一个入口时退化成一个无边框链接,两个及以上才是下拉菜单。切换器下面的树仍然把栏目首页本身作为第一个链接:切换器选一棵树,根链接选一篇文档。
面包屑与页面操作
普通内容页标题上方是面包屑行,这一行右端同时承载页面操作。顶层分区省略只有一级、与标题重复的面包屑,操作按钮的位置不变。
面包屑标签取本地化的 linkTitle,层级与侧栏一致。
页面操作菜单
页面操作是标题行末尾的拆分按钮:左半边一键复制本页 Markdown(成功后变成绿色对勾),右侧箭头展开完整菜单。菜单分两组,上半组是取走内容,下半组是改动与产出:
| 操作 | 出现条件 |
|---|---|
| 复制 Markdown 文本 | 站点开了 markdown 输出格式 |
| 在 ChatGPT 中打开 | page_context_menu.assistant_links: true |
| 在 Claude 中打开 | 同上 |
| 查看 Markdown 源码 | markdown 输出格式 |
| 查看编辑历史 | params.github_repo 能解析出源文件路径 |
| 编辑本页 | params.github_repo |
| 新建子页面 | params.github_repo |
| 提交文档 issue | params.github_repo |
| 提交项目 issue | params.github_project_repo |
| 打印整个分区 | 分区开了 print 输出格式 |
助手入口默认关闭:读者点击时,完整的当前 URL(含 query 与 fragment)会随本地化提示词发给第三方,页面正文不上传。开启前确认 URL 里没有敏感信息,并在隐私说明里披露这个边界。页面可用以下 front matter 收紧站点策略,不能反过来替站点开启:
在该页确认标题菜单与命令面板中均没有 ChatGPT、Claude 入口。跳转行为见 Agent 支持。
自定义外部操作排在菜单最后,url 支持三个已 URL 编码的占位符:
可用占位符:{url}(页面完整地址)、{title}(页面标题)、{markdown_url}(Markdown 版地址)。
在博客根分区及其一级子分区上,左半边变成 RSS 订阅链接,菜单里仍保留「复制 Markdown 文本」。没有 Markdown 输出的页面去掉左半边,箭头变成带文字的「操作」按钮。
这些操作同时是命令面板里的条目。
翻页器
正文末尾的上一页 / 下一页是两个文本链接,顺序与侧栏可见树一致:根页 → 第一篇 → 直到最后一篇。根页没有上一页,末页没有下一页。站点提供 data/docs_nav.json 时,这棵显式树同时决定翻页顺序,以及该文件声明过的 docs / book 栏目的栏目索引顺序——侧栏、翻页器与索引不会再把同一批子页排出三种顺序。文件没有声明的栏目,以及没有这个文件的站点,仍然沿内容树走。见布局与页面类型。
pager_types 只接受 docs、book、blog 三个值,其它取值告警并丢弃。单页退出用 front matter:
同一份顺序也写进 <head>:有上一页 / 下一页时输出 <link rel="prev"> 与 <link rel="next">,供浏览器与爬虫识别阅读序列。
翻页只在 HTML 输出中生效。打印、Markdown 与 RSS 既没有翻页链接,也没有这两个 rel 关系。
翻页器是页尾四件套的第三件(反馈 → 页面信息 → 翻页 → 评论),顺序固定,四者独立开关。
反向链接
右栏可以列出有哪些页面链接到这一页:一个带链接图标的「反链」组,排在目录下方、分类标签云上方,默认展开;低于 xl 断点时,它随目录一起进入侧栏抽屉。从搜索落到这一页的读者由此看到哪些页面认为它值得指向,也看到它在站点其余部分里的位置。默认关闭,由站点打开:
单页用 front matter 覆盖,分区用 cascade 覆盖它下面的所有页面:
索引在构建时从作者本来就在写的东西里派生:页面源码里的普通 Markdown 链接,以及 ref / relref shortcode。没有新语法要学,没有内容要迁移,也不需要 JavaScript——列表就在 HTML 里。扫描前先剥掉代码围栏与行内代码;指向同一个目标的多个链接合并成一条;自链接、外链、mailto: 与同页锚点都不计入。判断目标页面时去掉 fragment,每种语言各有一张互不相干的图,中文页面不会出现在英文页面下面。条目按稳定页面路径排序,同样的内容每次构建出同样的顺序;没有任何页面链进来时整个区块不渲染——没有标题,也没有空容器。
前八条直接可见,其余折进原生的「再显示 N 条」disclosure,避免被大量引用的页面把右栏撑满,其中不涉及 JavaScript。每一条都带来源页面的描述,悬停时显示。
读源码有一处已知遗漏:写在自定义 shortcode 参数里或原始 <a href> 里的 URL 不会成为一条边;解析不出来的目标被静默丢弃,不发告警。它是导航增强,不是链接检查器,查断链仍然要用链接检查器。
非布尔取值告警并回落到关闭。
页面的 Markdown 输出带同一份列表,前缀是「反链:」。RSS 省略它,print 输出格式连同整个右栏一起省略。
本站全站开启了它:看本页右栏的「反链」组就是实际效果;被引用最多的配置总览一页,列出了四十多个入链,其中大部分收在「再显示 N 条」里。
页脚
页脚形态由 params.ui.footer_style 决定(fat / slim / none,见品牌外观)。fat 的多列链接网格读 data/footer/<语言>.yaml。它不是菜单,主题没有 menus.footer:
brand.name与brand.logo不写时回落到站点自己的品牌名、Logo 与字标;tagline与slogan渲染 Markdown。- 站内
url相对当前语言根解析;external: true在新标签页打开并带rel="noopener noreferrer"。 - 网格列数等于数据里的列数。
- 单语言站可以使用
data/footer.yaml。 - 配了
fat但没有数据时自动降级成slim,可以先开启再补内容。
fat 页脚的版权行右端有一个折叠箭头,收起或恢复它上方的链接栅格。默认展开,读者的选择存在 localStorage 的 td-footer-collapsed 键里,跨页面保留;slim 与 none 没有这个按钮,它也与专注模式无关。
只要页脚有渲染,最底层栏右侧就固定保留同一组图标:版本、语言、主题、快捷键帮助。各菜单向上展开;版本按钮只显示分支图标,完整版本名仍保留在选项中。fat 页脚的折叠箭头排在这四项之后。侧栏不再重复这组控件,footer_style: none 则连同页脚一起移除底栏。
版权行与中间那句说明由参数控制,见配置总览。
验证
改完导航要检查这几处:
- 构建没有
Navbar menu … supports one interactive child level警告;出现它说明菜单嵌了三层; - 桌面端:父级菜单点击进入父级页面,悬停展开面板,Esc 关闭面板;
- 窗口缩到
lg以下:每个顶层入口仍有图标,没有图标的项在这个宽度下是空白; - 缩到
md以下:首页与 Landing 顶栏右侧只剩搜索和抽屉按钮;版本、语言、主题与快捷键帮助固定在 footer 最底层栏; - 侧栏顶部的切换器列出所有顶级栏目,当前项有选中标记;
- 任意文档页按 E / Q 翻页,顺序与侧栏一致,页面源码里有对应的
rel="prev"/rel="next"; - 打开反向链接后,
grep td-backlinks public/<某个被链接的页面>/index.html能找到这个区块,而没有页面链进来的页面里完全没有这段标记; - 打开页面操作菜单,确认该出现的项都在,不该出现的没有(例如未配置
github_project_repo时的「提交项目 issue」)。
相关
5.5 - 布局与页面类型
本页覆盖页面骨架:有没有侧栏、侧栏多宽、目录收几级、栏目首页是列表还是卡片。内容放在哪个目录见组织内容,这里只讲外壳。
规则是 外壳看 type,不看路径。文档可以放在 content/ 下的任何位置,只要给它 type: docs。
外壳类型
params.ui.shell_types 列出哪些 type 使用带侧栏的阅读外壳:
| type | 外壳 |
|---|---|
docs |
文档外壳:左侧栏(栏目切换器 + 目录树)+ 正文 + 右栏大纲 |
book |
文档外壳,另加编号目标、reading_width 阅读行宽与草稿横幅 |
blog |
文档外壳,侧栏默认展开,标题行左半边是 RSS |
swagger |
文档外壳,正文交给 Swagger UI 或 Redoc,见 API 文档 |
| 其它 type | 普通页面:顶栏 + 单栏正文 + 页脚,没有侧栏 |
OINK 1.3.0 新增 expo 类型:共享侧栏加图片相册,默认全宽且没有右侧大纲。
默认 shell_types 列表包含 expo;若使用上面的显式列表,请将其加入。
配置与示例见 Expo 相册。
分类页与标签页(taxonomy / term)不在这张表里,但也走同一套外壳。
给一棵子树指定 type 用 cascade,这是把文档放在任意路径的做法:
栏目根只是导航起点
这两个键 不决定外壳,只告诉主题文档树与博客树的根在哪,用于解析侧栏根、快捷入口与默认图标。上面 content/handbook/ 的例子照样有文档外壳,docs_section 保持 docs 不影响它。
需要让 docs 页的侧栏根变成站点首页,而不是文档栏目时:
取值只有这两个。其它值会告警并使用 section。
文档挂在站点根
以文档为主的站点可以把 docs 分区发布到 URL 根路径,源码仍然放在 content/docs/ 下。这需要三段配置一起给出。
第一段用 Hugo 原生的 permalinks 去掉 URL 里的 docs/ 段:
第二段让物理站点根索引仍可作为链接目标,但不再争抢同一个输出路径。每种语言的站点根索引(content/_index.md、content/_index.zh.md)都要写:
第三段把侧栏根声明为站点首页,让侧栏与翻页共用同一棵树:
docs_sidebar_root: home 之后,站点首页的所有顶层分区都会进入这棵树。博客、社区、下载这类不属于阅读序列的概览分区,在自己的 _index.md 里设 toc_root: true 退出,它们既不出现在树里,也不成为翻页目标:
文档此时与博客、社区等分区共享 URL 根路径。构建加 --printPathWarnings,发布前解决所有重复目标。
落地页
任意页面加 layout: landing 即使用落地页布局:顶栏 + 分区拼装的正文 + 页脚,没有侧栏。数据写法见首页与落地页。
landing_search: false 会把搜索入口从落地页外壳里去掉,其它页面不受影响。
侧栏
侧栏树来自 content/ 的目录结构,按 weight 排序,有 linkTitle 时用它作为标签。可调的是密度与尺寸:
sidebar_menu_compact只展开当前分支及邻近条目;设为false时整棵树全展开。sidebar_menu_foldable允许读者手动展开 / 折叠分区。博客栏目默认展开;某个分区要默认收起,在它的_index.md里写sidebar_expanded: false。sidebar_expand_levels是默认展开的层级数。sidebar_menu_truncate是单个分区最多渲染的条目数,避免上千页的目录把 HTML 撑到不可用。sidebar_width_min/sidebar_width_max是桌面端拖拽调宽的上下限(像素)。读者调整后的宽度存在浏览器本地,双击分隔条恢复默认。sidebar_item_overflow默认ellipsis(长标题省略),中文长标题多的站点可以改wrap换行。
达到 sidebar_cache_limit 后,有相同有效设置的页面可以共享一份中性渲染树。没有
JavaScript 时它仍然可见且可导航;外壳运行时只补当前路径并展开其祖先。页面或
cascade 覆盖会选择对应的缓存变体;启用 sidebar_headings 的 Book 页面仍使用自己
的页面专属树。
折叠状态、宽度与滚动位置保存在读者本地,按语言隔离。小于 md 时侧栏变成带遮罩的抽屉。
单页去掉侧栏用 front matter:
显式导航树 data/docs_nav.json
侧栏树默认从 content/ 推导。站点也可以给出一份显式导航清单,三个条件同时成立时主题改用它渲染:
- 站点存在
data/docs_nav.json且其中有sections键; - 页面的 type 是
docs或book; - 解析出的侧栏根不是站点首页。
文件是一棵嵌套的节点树。每个节点用 page 指向内容路径,children 定义导航子节点。
主题根据页面身份与这棵树解析链接和激活分支;url 与记录祖先链的
active_path_by_url 都是可选的旧提示字段:
这些路径不带语言前缀,也不带 baseURL 中的部署子路径。自 OINK 1.1 起,主题在比较前
去掉两种前缀,同一个 /docs/start/ 键可用于 /zh/docs/start/ 和
/handbook/zh/docs/start/;渲染出来的链接保留实际的语言与部署前缀。
清单可以把根级手册组织成导航树,无须把源文件移入 content/docs/。节点适用于某个
侧栏根,条件是它就是该根、位于该根的内容后代中,或是解析到同一个根的 Docs/Book
页面。例如设置 docs_section: docs 和 docs_sidebar_root: section 后,类型为
docs 的根级页面可以使用:
这里 content/config.md 与 content/api.md 仍是同级文件,导航里 API 则位于 Config
之下。选中 API 时,无须 URL 提示就会展开 Config。页面的自定义 URL 只改变链接,
不改变侧栏归属。独立的 Book 根或嵌套 sidebar_root_for 根各自选择自己的分支;
清单未包含的根使用其内容树。
这棵树同时决定翻页顺序与导航 JSON,侧栏与上一页 / 下一页保持相同排序。sections 为空数组
时告警并回退到内容树;page 指向不存在的页面时告警并跳过该项。带 manual_link 的占位节点与 sidebar_divider 分隔行留在侧栏里,但不会
成为翻页目标。1.1 的显式树也保留分隔分区的子页,使用与内容树相同的
只分组、不发布页面的 front matter 即可。
适用场景是导航顺序由外部工具生成的站点,例如从 Sphinx toctree 迁移过来、需要冻结既有章节顺序的手册。顺序由 content/ 的 weight 维护时不需要这个文件。
侧栏图标密度
页面 front matter 里的 icon 会出现在侧栏。叶子页全部带图标会降低可读性,用密度策略控制:
| 取值 | 效果 |
|---|---|
all |
每个有图标的条目都显示(未设置时的兼容默认值) |
groups |
只有根节点和有子页的节点显示图标 |
none |
侧栏不显示条目图标 |
非法取值只发警告并回落到 all,不让构建失败。本站使用 groups。
在侧栏里展开标题
Book 页可以在侧栏当前行下展开 h2–h4 分支,便于在长章节内跳转:
默认关闭。页面 front matter 或分区 cascade 也可设置同名键;显式
sidebar_headings: false 会关闭继承的启用值,右侧目录仍然保留。
整数指定展开到第几级(2–4),true 等于 2(只展开 h2),false 关闭。取值超出
范围时告警并关闭标题分支。只对 type: book
的页面生效,且只在侧栏当前行下展开。
目录 TOC
右栏大纲由 Hugo 从 Markdown 标题生成,收录层级是 Hugo 原生配置:
普通外壳运行时始终跟踪当前标题,无需额外开关。大纲绘制连续轨道、高亮当前区段
并标出位置。读者可以整体折叠右栏,状态存在本地。小于 xl 时右栏隐藏,大纲内容
移进侧栏抽屉。
自 OINK 1.2.0 起,标题跟踪会解码合法 URL 片段;非法百分号序列回退到字面的 标题 ID,跟随页尾标题链接时也遵循同一规则。
旧的站点键 params.ui.scroll_spy 与页面键 scroll_spy 在整个 1.x 期间仍作为静默
兼容 no-op 接受。两个布尔值生成相同的大纲,也不加载额外运行时;只有未来的破坏性
版本才会删除这两个键。
单页隐藏大纲用 front matter notoc: true。
只有进入 Hugo 目录的标题才出现在大纲里:Markdown 型 shortcode({{%/* … */%}})输出的标题会进,普通 shortcode({{</* … */>}})输出的通常不会。结构性标题应留在 Markdown 里。
栏目首页样式
带 _index.md 的分区会自动列出子页。两种样式:
list(默认):每个子页一个标题 + 描述段落;cards:网格卡片,读子页的title(或linkTitle)、description与icon。
可以按分区覆盖。非法取值会告警并回退:
相关的页面级开关:no_list: true 不列子页;simple_list: true 只输出一个无描述的项目符号列表;子页设 hide_summary: true 把自己从列表里去掉。不要手写子页清单:手写的清单会与侧栏失同步。
页宽
normal 是常规阅读宽度,wide 放宽正文栏,full 铺满视口。可以逐页或按分区覆盖;宽表格、大图与 API 参考页常用 wide:
Book 页另有一个 reading_width(slim / normal / wide),改的是正文本身的
阅读行宽,不动外壳。两个键取值非法都会告警并回退。
顶栏与页脚开关
顶栏与页脚属于逐页的布局决定,写在 front matter 顶层(不在 ui 下),可以用分区 cascade 一次设定:
验证
- 构建输出
Total in …且没有 ERROR; - 新建的
type: docs页面有左侧栏。没有则检查 cascade 是否覆盖到该页,以及shell_types是否包含这个 type; - 拖动侧栏分隔条,刷新后宽度保留,双击恢复默认;
- 窗口缩到
md以下时侧栏变成抽屉且可关闭,缩到xl以下时大纲移进抽屉; - 栏目首页的卡片数量与侧栏子页数量一致;
page_width: wide的页面比相邻页面宽;- 文档挂在站点根时,
hugo --printPathWarnings没有重复输出路径的告警。
相关
5.6 - 全文检索
OINK 的搜索是本地搜索:Hugo 在构建时给每种语言生成一份 JSON 索引,读者的浏览器下载它,在本地完成检索。不需要爬虫、账号、CDN,也不需要联网。主题默认不启用,一行配置即可开启。
搜索的入口是命令面板,打开方式与面板的其余内容见命令面板。
打开本地搜索
这一个键决定索引、Lunr 运行时与搜索对话框是否进入页面。三个条件同时成立时页面才带上它们:
params.offline_search为真;- 页面是首页,或者用了外壳布局(
docs/book/blog/swagger,见布局与页面类型),或者是开着params.ui.landing_search的落地页; - 当前输出不是打印。
任何一条不成立,构建就不往这个页面里放对话框、索引引用与 Lunr。这些资源不是被隐藏,而是不生成。
hugo server 下索引默认 也会生成,预览行为与线上一致。站点极大、每次改动都重建全站索引明显拖慢预览时,把它关掉:
控制索引体积
offline_search_index 决定每个页面往索引里写多少内容,因此同时决定两件事:读者能否搜到正文里的词,以及第一次搜索要下载多大的文件。
| 取值 | 索引进去的内容 | 什么时候用 |
|---|---|---|
title |
标题、标签、分类、search_keywords |
只靠标题定位的超大站 |
heading |
上面这些 + 页内各级标题 | 标题写得足够具体时 |
summary |
上面这些 + 描述与摘要 | 千页级站点;本站使用这一档 |
content |
上面这些 + 全文纯文本 | 默认值,几百页以内适用 |
其它取值会报 invalid params.offline_search_index 告警并使用 content。
offline_search_summary_length 是结果行里摘要的截断长度(默认 70),offline_search_max_results 是结果条数上限(默认 10)。这几个键的完整定义在配置总览。
读者搜第一个词之前要先下载整份索引。超过这个量级就把 offline_search_index 从 content 降到 summary。
调整排序
页面在 front matter 里影响自己的排名:
search_keywords 是额外的匹配词,可以写一个字符串,也可以写数组。它是这两个键里更有用的一个:读者搜 pg 或 GUC 即可命中标题只写着「PostgreSQL 参数」的页面。检索时关键词的权重仅次于标题,高于正文。
search_boost 是最终得分的正数乘子,默认 1.0,作用在文本匹配得分之上。1.5 不会把页面固定在第一位,在 OINK 1.4.0 中,只让它在相同关键词覆盖率与短语层级内前移。零、负数与非数字会告警并按 1.0 处理。
整节的默认值用 cascade 一次设定:
页面自己写的值覆盖继承来的值。本站 docs/ 下的页面按这种方式使用 search_keywords:每页列出中文说法、英文原词与配置键名。
把页面挡在索引外
search_exclude 是唯一写法。已移除的 exclude_search 与 excludeSearch 不再被
读取,因此不能保护页面;迁移检查器会报告它们。正文为空的页面不进索引。
不该公开的内容不要放进站点,也不要用 search_exclude 保护它。
中文、混合查询与标识符
以下行为已随 v1.4.0 发布。
搜索按空白和拉丁文字与 CJK 的边界拆词:PostgreSQL 备份 与 PostgreSQL备份
都会查询 postgresql 和 备份。连续的 CJK 字串仍按字面子串匹配,不使用词典;
主从复制 不等于 复制主从。请用空格分隔概念,例如 连接池 超时。
search_keywords 仍适合补充同义说法与缩写。
搜索保留下划线、连字符、点号、路径与命令选项。可以查询 shared_buffers、
pg_basebackup、--baseURL 或 /pg/data。这些内容按字面标识符处理,不作为
查询运算符或拼写纠错对象。英文词保留 Lunr 词干匹配;某个词没有精确或词干命中时,
5–7 个字符的词最多允许一次编辑,更长的词最多允许两次。短词和标识符使用字面子串匹配。
查询会统一大小写与 Unicode 全半角,最多使用 24 个不同的词。
关键词可以分布在不同字段中:标题含 PostgreSQL、描述含 备份,即可覆盖两个词。
排序规则如下:
- 有页面覆盖全部关键词时,只显示这些页面。否则显示标注命中词数与总词数的部分匹配,优先展示覆盖更多词的页面。
- 覆盖率相同时,连续完整短语优先。
- 在上述层级内部,把字段得分乘以
search_boost。每个词取最强字段,依次为页面标题、关键词、标签与分类、页内标题、描述、正文。同一字段中多次出现该词的页面,排在只出现一次的页面之前,但不会超过命中更强字段的页面。仍然同分时按标题和 URL 稳定排序。
输入过程中的字面子串也能命中。加权不能让部分匹配超过完整匹配。 结果仍按顶栏的栏目顺序分组;先应用全局结果数量上限,再在组内保留排序。 仅命中关键词时显示页面描述或摘要,不显示同义词列表。
同页多个小节命中时只占一行,显示最相关的小节标题与附近文字,并打开真实渲染的标题 ID。
显式 ID、自动生成的重复标题 ID、中文标题和部署子路径都保留。只有元数据命中、没有小节
命中时打开页面。heading 和 summary 范围可以定位标题命中;只出现在正文里的词需要
content。索引不会隐式扩大为全文。命中摘要展示附近最多 160 个字符;配置的摘要长度仍
控制无上下文命中时的后备摘录。
面板中的 搜索范围 并排显示全部栏目与当前顶层栏目(如文档、博客、书籍),点击即可切换。 每次加载页面默认全部栏目,在同页关闭再打开面板时保留选择。首页没有当前栏目。 命令和页面操作继续可用,命令模式隐藏范围控件。用 Tab 到达选中的范围,方向键切换, 再按 Tab 回到查询框;在查询框中按 Shift+Tab 则回到选中的范围。
输入法组字期间暂停结果更新与 Enter 激活,文字上屏后再检索。 空结果、加载中、索引不可用、结果数量与部分匹配状态都有可读文字,并向屏幕阅读器播报。
可选:在线搜索
本地搜索之外,主题保留了两个在线搜索集成,默认关闭。同一时间只启用一种:配置了多个入口时构建告警 You have more than one site-search option configured。
启用在线搜索意味着接受对应服务的抓取方式、可用性与隐私边界,这些应写进站点的隐私说明。
Algolia DocSearch
三个值必须都显式写出。缺任意一项都会告警并停用 Algolia。OINK 不会回退到其它项目的公共索引。DocSearch 的 JS 与 CSS 随主题内置,不从 CDN 加载,但每次检索请求都发到 Algolia。需要真实的密钥与索引才能工作,此处不渲染。
Google 可编程搜索
还需要给结果准备一个落地页:
搜索框把查询提交到 <baseURL>/search/?q=…,结果由 Google 的脚本在那个页面上渲染,需要访问 cse.google.com。同样需要外部服务,此处不渲染。
验证
-
构建,确认每种语言各生成了一份索引:
开发构建下文件名是
offline-search-index.zh.json,生产构建加指纹,形如offline-search-index.zh.7ab….json。一种语言一个文件,缺少某个文件说明那种语言的页面没进索引。 -
查看索引内容,这是排查「中文搜不到」的第一步:
条目数应接近中文页面数,
keywords与boost字段能看到写进 front matter 的值。 -
打开站点,按 /,分别用一个英文词与一个中文词各搜一次。结果按内容根分组,每组的名字是面包屑的第一段。
-
子路径部署(站点挂在
https://example.com/docs/这类路径下)时,打开浏览器开发者工具的网络面板,确认索引请求带上了子路径。索引请求打到域名根目录并返回 404、页面其余部分正常,是「搜索没结果」最常见的原因。
相关
5.7 - 命令面板
命令面板是站点唯一的模态入口:搜索页面、复制本页 Markdown、切换语言、切换版本、跳转到站点自定义链接,都在这一个对话框里完成。它随本地搜索一起装配:params.offline_search 关闭时,面板连同索引与 Lunr 都不进入页面,见全文检索。
打开面板
| 打开方式 | 打开成什么 |
|---|---|
| 点顶栏或侧栏的搜索框 | 完整搜索态 |
| ⌘ / Ctrl + K | 完整搜索态;再按一次关闭 |
| / | 完整搜索态 |
| 反斜杠键 | 纯命令态(等于预填了 >) |
| f / c | 同上两者,由键盘导航提供 |
在框里输入 > 开头的查询 |
纯命令态 |
/、反斜杠、f、c 都是裸单键,会给输入让行:焦点位于 input、textarea、select 或 contenteditable 中,以及正在用输入法组字时,按键作为普通字符输入。带修饰键的 ⌘/Ctrl + K 可以在输入框里打开面板,但同样会给输入法组字让行。
所有打开面板的快捷键都会让行于其他已打开的原生对话框或可见的 ARIA 对话框,
包括固定定位的对话框;隐藏的 ARIA 对话框不会阻止快捷键。
面板内:↑ ↓ 选择,Enter 执行,Esc 关闭并把焦点交还给打开它的控件。
面板内容
不输入任何内容时,面板按固定顺序列出四组:
| 分组 | 内容 | 谁决定 |
|---|---|---|
| 快速链接 | 顶栏一级菜单里选出的几个入口 | params.ui.quick_links |
| 页面操作 | 复制 Markdown、查看 Markdown 源码、编辑本页、查看修改历史、新建子页、提 issue、打印整节、打开双列视图(compare_translations) |
仓库配置、本页是否有 Markdown 输出;双列视图还取决于 params.ui.compare 与同源译文 |
| 偏好设置 | 切换版本 → 切换语言 → 切换主题 | 站点是否配了多版本、多语言、深浅色菜单 |
| 命令 | 打开 GitHub 仓库,之后是站点自定义命令 | params.github_project_repo(缺省回退到 github_repo)与 ui.command_palette.commands |
偏好设置三项的顺序与顶栏控件一致(版本、语言、主题),面板与顶栏是同一个次序。选中「切换语言」这类项后,面板不立即跳转,而是就地展开可选项,再选一次。
输入文字时,先是页面结果,按内容根分组(分组名是面包屑的第一段,组间顺序跟随顶栏一级菜单的顺序),命令与动作合并成一组排在最后。
以 > 开头时只列命令与动作,不查页面。不确定某个功能在哪个菜单里时用它定位。
不可用的项在能说明原因时仍然列出。站点没有配置仓库地址,「编辑本页」会带着「不可用」的说明留在列表里,而不是消失。
OINK 1.4.0 的本地搜索增加了 搜索范围 分段选项,可在并排的全部栏目与当前顶层栏目之间切换。 它在应用结果数量上限之前过滤页面,命令与页面操作继续可用。选择只在当前页面加载期间保留。 命中小节显示在页面标题旁,并打开真实标题锚点;同页多处命中合并为一行。 部分匹配显示关键词覆盖数量,详见查询语义。 Tab 到达选中的范围,方向键切换;范围控件在命令模式和偏好选择模式中隐藏。
快速链接
快速链接从 Hugo 主菜单里按 identifier 选取,不另写一份清单:
值是 menus.main 里条目的 identifier。不写这个键时默认取文档栏目与博客栏目(params.ui.docs_section 和 blog_section)。菜单本身怎么配见导航与菜单。
自定义命令
站点自己的命令写在 params.ui.command_palette.commands 下,排在内建命令之后,顺序即书写顺序:
上面是本站在用的那一条。字段共七个。写入其它键或无效记录时会告警并丢弃该命令:
id必填,小写字母开头,只能用小写字母、数字、下划线和短横线;不能与内建动作 ID 重名。title显示在面板里;description是它下面那行小字;icon是一对 Font Awesome class。keywords是数组,参与匹配但不显示,用于收纳读者可能输入的检索词。url与action有且只能有一个。url只接受http/https的完整地址、站内路径,或#开头的页内锚点;带主机名的地址在新标签打开。action引用一个内建动作 ID。
action: 给内建动作起别名内建动作已经在面板里,再包一层会让同一个功能以两个名字出现两次。
多语言站点把命令写在 languages.<lang>.params.ui.command_palette.commands 下,标题与关键词才能本地化。顺序由默认语言的那份清单决定:其它语言里同 id 的条目只覆盖字段,新增的 id 追加在末尾。各语言的命令顺序因此一致,读者换语言时命令不会换位置。
配置只能给出链接或引用内建动作,不能注入 JavaScript 回调:面板读取的是一份纯数据清单。
页面动作
面板里的「页面操作」与文档标题旁的拆分按钮是同一套实现:同一份动作描述、同一段 URL 生成逻辑、同一个执行器。按钮左半边复制本页 Markdown,右侧箭头展开全部动作。
要隐藏标题旁的按钮,设置 page_context_menu: false;命令面板中的对应操作仍然保留:
只隐藏某一页的按钮时,在该页 front matter 中设置 page_context_menu: false 即可。
assistant_links 默认关闭,原因是读者点击时 当前页面的完整 URL(含查询串与锚点)会被发送到第三方,页面正文不会上传。全站通过 params.ui.page_context_menu.assistant_links 启用,页面只能用以下
front matter 收紧策略:
确认该页的标题菜单和命令面板均没有助理入口。跳转行为见 Agent 支持。
links 是额外的外部动作,只出现在标题旁的菜单里,不进面板:
{url}、{title}、{markdown_url} 三个占位符会被替换成当前页面的值。
「编辑本页」「查看修改历史」「提 issue」这些动作是否可用,取决于仓库相关的配置,见仓库与页面信息;「复制 Markdown」「查看 Markdown 源码」需要页面开了 markdown 输出,见 Agent 支持。
与全文检索的关系
同一个对话框,两条独立的数据来源:
- 页面结果 来自本地搜索索引。索引未生成或下载失败时,面板照常打开、照常执行命令,页面那部分显示「页面索引暂不可用,操作仍可使用」。
- 命令与动作 来自页面里内嵌的一段 JSON 清单,不需要网络。
打印态不装配面板,打印输出里没有它;关闭 offline_search 后同样没有面板,此时 f 与 c 静默,不影响正常输入。
使用当前查询的站点操作
受信任站点 JavaScript 使用的运行时接口自 OINK 1.1 起提供。
使用前检查能力是否存在:v1.0.0 和未启用本地搜索的页面不提供它。集成代码应在主题脚本
之后加载,例如使用 layouts/_partials/hooks/body-end.html。以下示例假定站点实现了
openSiteAssistant,并自行管理服务商设置:
扩展行排在原生结果和操作之后,包括空结果与索引错误状态;空查询、命令、选择和加载状态
不出现扩展行。rows() 应保持纯净且同步,所有字符串都作为文本渲染。激活接收生成该行时
的查询,不会读取更新后的输入值。打开另一个受协调器管理的界面前调用 handoff(),此后
由站点负责新界面的焦点与失败提示。没有新界面的操作直接返回 Promise,不调用 handoff。
Shell 契约 定义了字段、取消、校验和生命周期。 YAML 仍不能包含回调,OINK 默认不添加远程服务或遥测。
验证
先在自己的站点根目录完成生产构建。下方命令中的
public/zh/docs/getting-started/index.html 是示例;请换成自己站点实际生成的文档页,
并按语言配置调整路径前缀。
-
构建后确认命令清单进了页面:
这一步确认操作数据已写入 HTML;其余步骤用于在启用本地搜索后检查命令面板界面。
-
打开站点按下 ⌘/Ctrl + K,什么都不输入:应该看到快速链接、页面操作、偏好设置、命令四组,顺序如上。
-
输入
>:只剩命令与动作。新加的命令应该排在「打开 GitHub 仓库」之后。 -
切到另一种语言重复第 3 步,确认命令的标题变了、顺序没变。
-
打印预览(⌘/Ctrl + P)里不应该出现任何面板痕迹。
相关
5.8 - 键盘导航
OINK 的交互式页面自带一套单键快捷键:WASD 在侧栏树中移动,J K 在标题间跳转,Q E 翻页,另有几个单键切换主题、语言与命令面板。默认开启,所有绑定都给输入让行,可以按站点或按页面关闭。
键盘导航不维护第二套状态:树的展开折叠复用侧栏原有的箭头按钮,逐节跳转读取右栏目录,切换语言与主题复用命令面板的同一批动作。键盘操作的顺序与鼠标操作的顺序因此一致。
侧栏
| 按键 | 行为 |
|---|---|
| W S ↑ ↓ | 焦点移到上一个 / 下一个可见项 |
| A D ← → | 折叠 / 展开分组;叶子节点上 A 跳到父级,D 无动作 |
| Enter Space G | 激活焦点所在的项:打开页面链接,或展开/折叠分组按钮 |
| Esc | 退出树,焦点回到正文 |
四个字母键不需要先进入树:焦点还在正文时按 S,以当前页在侧栏里的那一项为起点下移一格并落焦。焦点行整行加深底色,比「当前页」的底色深一档,用于区分当前页与焦点位置。
窄屏侧栏收进抽屉、或桌面侧栏被折叠时,第一次按这四个键先展开侧栏。页面没有侧栏树时静默。
方向键 只在焦点已经进入侧栏后 才作用于树,正文里保持浏览器原生滚动。RTL 语言下 ← → 随阅读方向对调,A D 恒等于「折叠 / 展开」。
自 OINK 1.1 起,不发布自身页面的分组通过展开按钮参与导航。从子页按 A 先回到父分组,再按一次才折叠;D 展开已折叠的分组,已展开时进入第一个 可见子项。Q / E 翻页会跳过分组按钮。
阅读
| 按键 | 行为 |
|---|---|
| J K | 沿页面目录跳到下一节 / 上一节 |
| N | 首页专用:跳到下一个顶层分区(首页 J 的助记别名) |
| Q E | 上一篇 / 下一篇 |
| H | 专注阅读模式:隐藏 / 恢复导航外壳 |
| B | 站点开启 params.ui.compare 且本页有译文时,打开或关闭双列视图 |
J K 的目标序列与右栏目录同源,落点与点击目录一致。跳转是固定 100 ms 的缓动滑行,与距离无关;连续按键不必等上一段动画结束。已经读到某一节内部一段距离后,K 先回到本节起点,再按一次才跳到上一节。页面没有标题时退化为一小段滑动。
Q E 按 侧栏树的可视顺序 翻页,不按日期。栏目入口页本身也是树里的一项,博客的栏目边界因此表现为「上一专栏最后一篇 → 下一专栏入口页 → 下一专栏第一篇」。折叠起来的分支不在这个顺序里:翻页顺序与焦点移动顺序是同一个。页面没有侧栏树时回退到页尾翻页器,没有翻页器时用 <head> 里的 rel=prev/next。
H 在首页只隐藏顶栏与页脚,在文档页同时隐藏左右栏与浮动按钮。状态记录在当前标签页的会话中,首帧之前恢复,用 Q E 连续翻页不丢状态、不闪烁。外壳隐藏时 WASD 不会把焦点送入不可见的侧栏。
外观、语言与路由
| 按键 | 行为 |
|---|---|
| L Y | 循环切换语言(两个键等价) |
| T | 亮 / 暗模式切换 |
| R | 在首页与顶栏的同源一级入口之间循环 |
这三个键在任何交互式页面上都有效,不限于文档外壳。单语言站点的 L、关闭深浅色菜单后的 T、只有一个一级入口时的 R 都静默。R 只在同源的一级菜单项之间循环,外链与顶栏上的工具控件不参与。
搜索与命令
| 按键 | 行为 |
|---|---|
| F 或 / | 打开命令面板的完整搜索态 |
| C 或反斜杠键 | 打开命令面板的纯命令态 |
| ⌘ 加 K 或 Ctrl 加 K | 打开面板;再按一次关闭 |
/ 和反斜杠属于搜索功能本身,关闭键盘导航后仍然可用;F C 是键盘导航提供的别名,指向同一个面板实例。部分非美式键盘布局上反斜杠不易按到,在面板里输入 > 前缀同样进入纯命令态。面板里有什么见命令面板。
Expo 图片查看器
Expo 查看器使用独立的快捷键。 打开图片时,页面单键导航暂停;关闭后恢复。
| 按键 | 查看器中的操作 |
|---|---|
| W / A / S / D | 将放大后的图片向上/左/下/右移动 |
| Q 或 H、E 或 L | 上一张、下一张 |
| J 或 −、K 或 + | 缩小、放大 |
| R 或 0 | 重置尺寸和位置 |
| F | 放大至适屏,完整容纳图片 |
| Escape | 关闭查看器 |
这些操作仅在查看器内部生效。keyboard_nav: false 关闭页面快捷键,
不会禁用查看器控件。Expo 随 OINK 1.3.0 提供。
保留不占用的键
? 保留不绑定。速查卡挂在页脚最底层栏的问号按钮上,鼠标悬停、键盘聚焦或触摸都能打开,列出当前页面实际可用的按键:单语言站点看不到切换语言那一行。
G G、Shift 加 G 和数字键同样保留,可能用作将来的跳转序列。
快捷键的让行规则
所有绑定都是裸单键,凡是可能和输入或弹层冲突的场合一律禁用:
- 焦点在 input、textarea、select 或
contenteditable区域里; - 正在用输入法组字(中文站的硬约束);
- 按住修饰键时:⌘ 加 C 仍是复制,Shift 加 ↓ 仍归浏览器;
- 命令面板、其他已打开的原生对话框或可见的 ARIA 对话框占用键盘。固定定位的 ARIA 对话框同样会阻止快捷键,隐藏的 ARIA 对话框则不会。
评论区在 iframe 中,键事件不冒泡到页面,无需额外隔离。
焦点顺序与无障碍
自 OINK 1.1 起,隐藏的侧栏与抽屉内容退出键盘焦点范围。鼠标点击正文、表格 滚动区或代码块后,再按其他键不会出现大范围边框。键盘焦点仍清晰可见:跳转链接在 文章标题附近显示焦点提示,可滚动的表格和代码保留自己的焦点提示与键盘滚动能力。
- 跳转链接:进入页面后第一次按 Tab 出现的就是「跳转到主要内容」,一步跳过顶栏和侧栏。
- 真实焦点:树内导航移动的是真正的 DOM 焦点,不是虚拟光标。屏幕阅读器因此读出链接名与「当前页」标记,Enter 是链接的原生行为,Tab 顺序没有被改写。
- 高对比度:焦点行的底色在
forced-colors模式下失效,退化为系统高亮色描边。 - 减弱动效:
prefers-reduced-motion打开时,逐节跳转与翻页滚动改为瞬时定位,不做滑行。 - 速查卡里的键帽与正文里的按键组件是同一套样式。
关闭
全站关闭:
单页关闭(交互密集的演示页常常需要),或者用 cascade 按整节关闭:
这个键只接受布尔值。写成 "false" 或其它非布尔值时会报 params.ui.keyboard_nav must be a boolean 告警并使用站点默认值。完整定义见
配置总览。
关闭后运行时不进入 JavaScript bundle,而不是加载后再判断。/、反斜杠和 ⌘ 加 K 属于搜索,仍然可用;页脚折叠链接栅格的箭头不受影响。
验证
先在自己的站点根目录完成生产构建。下方命令中的
public/zh/docs/getting-started/index.html 是示例;请换成自己站点实际生成的文档页,
并按语言配置调整路径前缀。
-
构建后确认速查卡按钮在页面里:
关闭键盘导航且没开本地搜索时,这个按钮整个不生成。
-
打开一篇文档,光标停在正文里连按 S:侧栏里应该从当前页那一项开始逐项下移,正文不动。
-
按 E 若干次,核对翻页顺序与侧栏从上到下的顺序一致;折叠一个分组再翻,被折叠的页面应该被跳过。
-
点进搜索框,按 J:页面 不应该 滚动,字符正常输入。使用中文输入法输入时同理。
-
系统里打开「减弱动态效果」,再按 J:应该瞬间定位,没有滑行。
-
验证 1.1 的焦点修复时,先通过跳转链接进入正文,检查标题附近的焦点提示;再折叠 侧栏并按 Tab,隐藏的链接应被跳过,恢复按钮仍然可以到达。
相关
5.9 - 多语言
OINK 使用 Hugo 的多语言模型,不额外引入目录约定:配置一个 languages 块,译文与原文并排放在同一个目录里,用文件名后缀区分。以下内容覆盖单语言站点扩展为双语站点需要改动的位置,以及双语站点的两处易错点:资源归属与标题锚点。
启用第二种语言
上面是本站在用的配置。四个字段的作用:
label是语言选择器里显示的名字,用该语言自己的文字书写:写简体中文,不是Chinese。locale是标准语言标签,会进<html lang>、hreflang备用链接和 Open Graph 元数据。weight同时决定语言排序和选择器的轮换顺序,小的在前。params是语言级覆盖:这里没写的键继承全局同名值。日期格式通常需要按语言各写一遍。
默认语言不带路径前缀(英文在 /docs/…),其它语言各占一个前缀(中文在 /zh/docs/…)。默认语言也需要前缀时加 defaultContentLanguageInSubdir: true。这会改变全站 URL,已上线的站点要同时配好重定向。
文件命名与资源
译文与原文并排放置,用后缀区分,Hugo 靠相同的基础文件名把它们认成同一页的两个语言版本:
- content/docs/
- install.md英文
- install.zh.md中文
- _index.md
- _index.zh.md
页面包同理:index.md 与 index.zh.md 放在同一个目录里。
页面包里的资源遵循一条规则:文件名不带语言后缀的资源由所有语言共享,带语言后缀的资源只属于那种语言。
- content/docs/install/
- index.md英文页
- index.zh.md中文页
- topology.webp两种语言都能用
- screenshot.zh.webp只有中文页能用
正文里引用带后缀的资源时 写不带后缀的名字:,Hugo 会按当前语言解析。
这条规则有一个推论:页面包里只有 index.zh.md、没有英文对等页时,不带后缀的资源不会分给中文页,它们归属默认语言,而默认语言在这个包里没有页面。此时所有资源都必须带 .zh. 后缀,本站 docs/ 下的中文页面包即是如此。
哪些内容需要翻译:
- 翻译:
title、description、摘要、菜单标签、标签名、图片 alt、提示块正文、shortcode 里面向读者的参数。 - 保持一致:日期、
weight、别名,以及任何影响路由的元数据。两边不一致会导致侧栏顺序在两种语言下不同。 - 不翻译:命令、配置键、文件名、URL、版本号、产品名、shortcode 名。
按语言分开的配置
三处内容不在 content/ 里,需要各语言各写一份。
菜单 写在各自语言下:
identifier 两种语言必须一致:命令面板的快速链接与搜索结果分组顺序都按它匹配。菜单的完整写法见导航与菜单。
首页数据 按语言取文件:data/home/en.yaml、data/home/zh.yaml。当前语言没有对应文件时回退到 en.yaml;单语言站点用一个 data/home.yaml 即可。见首页与落地页。
界面文案:主题自带 32 份完整界面语言包,即 Docsy 支持的 31 个 locale
文件名,再加通用 zh。每份语言包都以目标语言覆盖 OINK 的全部 235 条消息,
不再依赖生成的英文 fallback。zh 与 zh-cn 使用简体中文,zh-tw 使用繁体
中文;完整 locale 与占位符契约见架构。
要改某一条,在站点自己的 i18n/ 下建同名文件,只写要覆盖的键:
当前 Hugo 基线允许 locale: zh 与地区化中文语言包并存,上例中的具体
locale: zh-CN 也可使用,无需为旧 Hugo 保留 locale 兼容处理。
缺译回退与语言选择器
语言选择器的图标本身是一个链接:点击它按 weight 顺序切到下一种语言(在末尾回到第一种),悬停或键盘聚焦才展开列出全部语言的菜单,触摸屏上菜单不展开,点按即切换。双语站点因此一次点击即可来回切换。
菜单始终列出全部配置的语言,不论当前页有没有译文:
- 目标语言有译文 → 跳到那一页;
- 目标语言没有译文 → 跳到那种语言的 首页。
回退到首页优于把读者送进 404。代价是读者不一定察觉自己被送到了首页,双语站点应当把「每个页面都有对等译文」作为约束来检查,而不是依赖回退。
这个回退用于语言选择器。自 OINK 1.2.0 起,它与 SEO 分开:hreflang 只列当前页及
实际译文,博客每一分页使用自身的 canonical,后续分页不输出语言备用链接。
详见规范链接与语言备用链接。
中文页面不存在时,中文站里就没有这一页:侧栏、搜索索引、翻页顺序都不包含它。
搜索索引也按语言分开:读者在中文页面搜索只命中中文内容。支持分开的中文词、中英混合查询与技术标识符,细节见全文检索。
标题锚点要对齐
Hugo 从标题文本生成 ID,中文标题生成中文 ID:/docs/install/#prerequisites 与 /zh/docs/install/#前置条件 指向同一个位置,却是两个互不相通的锚点,跨语言的深链、目录与页内跳转都会失效。
做法是在译文标题里显式写出原文的 ID:
两条纪律:
- ID 从 英文页渲染出来的 HTML 里取,不要凭标题文本推断。标题里含行内代码、徽章或 shortcode 时,生成的 ID 与标题文本不一致。
- 中英对应页面的标题数量、顺序、ID 必须一致。确实需要在中文里加一节时,给它一个独立、稳定、不与英文冲突的 ID。
本文档站的翻译检查脚本
会检查源码结构与渲染后的标题 ID。下面命令在 oink.pgsty.com checkout 中执行;
普通消费站不自带这个脚本:
借用到自己的 CI 前,需要调整脚本固定的内容栏目和 EN/ZH 文件命名约定。
它按脚本所在位置查找源文件,--public 只选择渲染产物目录。手动检查时,先选一对
代表性译文,在各自生成的 HTML 中比对标题 ID。
新页面从建立时就写显式英文 {#id},成本低于事后回补。
双列视图
设置 params.ui.compare: true,读者就可以把当前页面与已有译文并排打开对照阅读:
只要 Docs、Book 或博客页面在同一站点下有已发布的 HTML 译文,页面动作栏旁就会出现
「打开双列视图」按钮;读者也可以从命令面板执行「双列视图」,或在开启键盘导航时直接按
B 打开(可以直接在本页试试),再按一次 B 即回到原页面。当前页面
固定在阅读起始侧,另一侧窗格加载所选译文;如果页面有多种语言译文,可以从工具栏菜单随时
切换目标语言。屏幕宽度小于 900px 时,视图会自动切换为带语言切换栏的单窗格布局。
工具栏中的「同步滚动」开关通过匹配上一节所说的共同标题 ID,让两侧窗格保持在
同一章节,因此对齐 {#id} 锚点能获得最佳效果。如果部分标题没有对应项,或两篇页面没有
共同标题,阅读器会在匹配标题之间或按全文阅读进度估算位置,开关旁的橙色「!」会说明属于
哪种情况,鼠标悬停、键盘聚焦或轻触即可查看说明;如果标题 ID 重复或在不同语言间顺序不
一致,「同步滚动」会自动关闭,两侧仍可各自独立滚动阅读。
开启双列视图不会在构建时生成额外页面,只有读者打开视图时,浏览器才会用同源 frame
加载现有的两篇 HTML 页面。没有译文的页面、草稿、跳回语言首页的回退链接以及部署在
其他域名的译文都会自动排除。如果想让某个语言版本不参与对照阅读(既不显示入口,也不作为
目标译文),可以在 languages.<lang>.params.ui 下设置 compare: false。
在生产环境启用前,请确认两点:
- 写在
hooks/head-end.html中的自定义统计脚本会在每个窗格内再次执行,请用window.OinkComparePane在窗格中跳过,详见统计与 SEO。 - 内容安全策略(CSP)需要允许
frame-src 'self',且响应头不能发送X-Frame-Options: DENY或frame-ancestors 'none',详见内容安全策略。
从右向左的语言
在语言下声明书写方向:
<html dir> 随之改变,主题额外加载 Bootstrap 的 RTL 样式表。主题自身的 CSS 全部使用逻辑属性(margin-inline-start 而不是 margin-left),镜像布局自动完成。站点自己写的 CSS 同样要用逻辑属性,否则 RTL 下会错位。
验证
-
在自己的站点根目录构建,确认两种语言的产物都在。下例假设英文在
/、中文在/zh/,请按语言配置调整;仅在启用本地搜索时检查索引: -
对照 SEO 行为检查
hreflang与 canonical:hreflang只列实际译文,博客每一分页生成自身的 canonical,并从第 2 页起省略语言备用链接。 -
在有译文的页面上展开语言选择器并选择另一种语言,确认停在同一篇文档;在没有译文的页面上重复一次,确认落到目标语言的首页而不是 404。
-
两种语言各搜一次同一个概念,确认都有结果。
-
比对一对译文的标题 ID。需要接入 CI 时,按上节说明适配本文档站脚本,不要直接用它检查另一套内容目录。
相关
5.10 - 多版本
产品有多个受支持版本时,文档通常也要分版本。主题提供两项功能:顶栏的版本切换菜单,与旧版本站点上的归档提示横幅。部署布局由站点决定:主题不做跨版本的单次构建,每个版本是一次独立的 Hugo 构建。
版本切换菜单
在 params.versions 里列出要出现在菜单里的版本。这个列表非空时,顶栏工具区出现一个分支图标的菜单,页脚最底层栏出现同样内容的纯图标向上菜单。
菜单项默认显示 version 的值,写了 name 就显示 name。当前项标成选中态,判定方式是条目的 version 等于 params.version,或者条目的 url 等于站点的 baseURL,两者满足其一即可。
没写 url 的条目显示为不可点击的灰项,可用作分节标题;name: '---' 是一条分隔线(分隔线上写 url 会告警)。name 支持行内 Markdown:
同一份列表也是命令面板里「切换版本」的数据来源,菜单与面板不会不一致。
逐页跳转的取舍
version_menu_pagelinks: true 会把当前页面的路径拼到目标版本的 URL 后面,读者切换版本时 停在同一篇文档。
代价是目标版本不一定有这个页面:文档结构在版本间会演进,旧版本没有新增的页面,读者切换过去就是 404。本站关闭这个选项。
单个条目可以覆盖全局设置:
结构稳定时开启,结构变动大时关闭。跳到版本首页多一步操作,仍优于 404。
归档横幅
不再维护的旧版本站点上,向读者说明这是一份快照:
archived_version: true 时,每个文档页与书籍页正文顶部出现一条横幅,写明当前版本已不再积极维护,并给出指向 url_latest_version 的链接。文案随站点语言本地化,无需自行编写;version 是横幅里显示的版本号。
横幅只出现在文档与书籍页面上,博客和落地页没有。
params.version 与 params.versions 的区别
两个键名字相近,职责不同:
params.versions是 一张跨站点的清单:菜单里能跳到哪些版本,各自的地址是什么。它描述的是其它站点。params.version是当前这次构建自己的版本标识。它决定菜单里哪一项被标成选中、归档横幅里显示什么版本号,data/download/*.yaml没写version时也以它兜底(见发布与下载页)。
它不一定是 Git 引用。需要一个能解析的发布 tag(例如安装命令里引用的那个)时,另设一个自己的参数,不要复用 params.version。这两个键的完整定义在配置总览。
多版本部署布局
| 布局 | baseURL |
特点 |
|---|---|---|
| 子域名 | https://v1-9.docs.example.com/ |
各版本相互独立,互不影响;需要给每个版本配 DNS 与证书 |
| 子路径 | https://docs.example.com/v1.9/ |
单域名,SEO 权重集中;需要托管方支持按路径路由到不同产物 |
每个版本是一次独立构建:从对应的 Git 分支或 tag 检出内容,用那一版自己的 hugo.yml 构建,产物发布到对应地址。当前版本的站点把 versions 列全,旧版本的站点在列全之外再加上归档横幅。
baseURL 必须包含那段路径否则搜索索引、页面动作与资源链接都指向域名根目录:页面看上去正常,搜索却没有结果。这是子路径部署最常见的故障,部署细节见发布上线。
验证
先在自己的站点根目录完成生产构建。下方命令中的
public/zh/docs/getting-started/index.html 是示例;请换成自己站点实际生成的文档页,
并按语言配置调整路径前缀。
-
构建后确认版本菜单进了页面:
params.versions为空或未配置时,菜单整个不生成。 -
看当前版本有没有被标成选中:
一条都没有,说明
params.version与versions里的version字段对不上,或者baseURL与该条目的url不一致(注意结尾斜杠)。 -
逐个访问菜单里的链接。开启
version_menu_pagelinks时,在一篇旧版本不存在的文档上试一次,确认落点可以接受。 -
归档站点上打开任意文档页,横幅应该在正文最上方,语言与站点一致,链接指向最新版本。
-
按 ⌘/Ctrl + K 打开命令面板,「切换版本」列出的应该是同一份清单。
相关
5.11 - 分类体系
目录树只有一条路径,分类体系(taxonomy)给页面加第二条:同一篇 PostgreSQL 备份文档既在「运维」目录下,又能从「备份」标签页找到。启用它只需要 Hugo 的 taxonomies: 配置,术语页、术语卡片、右栏分类云与顶栏分类面板都由主题自动生成,无需编写模板。
本页带着一个分类:标题下面的「分类: 定制站点」一行,以及右栏目录下面那组带计数的芯片,都不需要在页面上写配置。
启用分类法
分类法由 Hugo 决定,主题不额外提供开关。在 hugo.yml 顶层 写 taxonomies:,键是单数名、值是复数名:
这是本站的配置。三点需要注意:
- 写了
taxonomies:之后它就是 完整列表,不是追加。想在自定义分类法之外保留tags/categories,必须把它们一起列出来。 - 复数名同时是 URL 段:
/zh/tags/、/zh/categories/。 - 全部关闭:
disableKinds: [taxonomy, term]。
加一个自己的分类法,例如按产品模块归类:
分类法的显示名:tag tags category categories module modules 这六个键在主题的每个语言文件里都有本地化标题(中文分别是「标签」「分类」「模块」)。其它分类法用复数名的 humanize 结果(products → Products)。要自己定名字,在 content/<复数名>/_index.md 与 _index.zh.md 里写 title / linkTitle,主题会优先用它:
为页面添加标签
front matter 里的键名用 复数名(taxonomies 的值那一列),值始终是列表,只有一项也要写成列表:
整个栏目共用一个分类时,写在栏目首页的 cascade 里,无需每页重复:
本站 docs 的六个栏目都是这样配置的。页面自己写 categories: 会覆盖 cascade,不合并:要在栏目分类之外再加一个,两个都要写出来。
页面上的术语行
文档页与博客页在标题、摘要下面渲染一行已分配的术语,链接指向对应的术语页,本页顶部的「分类: 定制站点」即是。这一行的容器是 .taxonomy-terms-article,按分类法另带一个 .taxo-<复数名> 类,单独调样式时用这两个选择器。
默认列出该页的 全部 分类法,只有 authors 与 series 这两个保留复数除外——它们各自有专门的呈现面(署名行与系列横幅),再列一遍标签等于把同一件事说两遍。在 page_header 里点名,就能把它放回去。
只想显示其中几种、并固定顺序:
主题认识名字的两个分类法
authors 与 series 就是普通的 Hugo taxonomy,按普通方式声明——主题不为它们增加任何参数。主题增加的是各自的一套呈现,所以「声明」本身就是全部开关:
| 复数名 | 声明之后打开了什么 | term 页变成什么 |
|---|---|---|
authors |
文章头部的头像与带链接的名字、列表行上的名字、feed 里每位作者一条 <dc:creator> |
作者主页:显示名取 term 页的链接标题(有 linkTitle 用它,否则用 title),description 是一句话介绍,正文是长介绍,头像取题图解析器为这一页选中的那张 |
series |
正文上方一条横幅,写明系列名、本篇位置、下一篇,以及折在 <details> 里的完整列表 |
系列引言,成员按阅读顺序排列,而不是最新在前 |
两者的完整说明与各自需要的 front matter 在写博客。这里只提两件事:
- 主题刻意不设
data/authors文件。作者主页就是 term 页本身,因此不存在第二份权威跟它打架。 - 系列 term 页是唯一不按时间倒序排列的 term 页。写了
series_weight的成员按升序排在前,其余按日期升序跟在后。term 页没法把顺序交给 Hugo,所以主题自己算一次,两处呈现读同一份结果。
标签页与分类页
每种分类法生成两级页面:
| 页面 | URL | 内容 |
|---|---|---|
| 分类法列表页 | /zh/categories/ |
页头是分类法图标、本地化名(「分类」)与术语数,下面每个术语一张卡片,使用次数多者在前:术语图标(作者则是头像)、术语名与页面数 |
| 术语页 | /zh/categories/定制站点/ |
页头是术语标题与页面数(关闭面包屑时另有一行「分类」kicker 链回列表页),下面按日期倒序列出该术语的全部页面,样式与博客列表一致 |
中文术语的 URL 使用中文字符(浏览器地址栏显示 定制站点,HTML 里是百分号编码),Hugo 不做拼音转写。需要 ASCII URL 时改用英文术语,再在 content/categories/<术语>/_index.zh.md 里用 title 给它一个中文显示名,这是 Hugo 的术语页内容文件机制。
术语页在内容树里没有固定位置,它借用一个:某术语的成员全部位于同一个顶层栏目下时,术语页用那个栏目渲染侧栏树与根链接,读者从文档里点进标签仍留在文档导航中;成员跨栏目时回退到站点级的树。
术语卡片只出现在分类法列表页;术语页上换成右栏的分类云。
右栏的分类云
文档页、博客页与术语页的右栏(目录下面)每种分类法一组,标签带计数,可折叠。
定义了分类法且当前范围内有术语时,默认自动出现。在页面 front matter 中设置
toc_taxonomies: false 可隐藏该页的分类云与分类法切换器;在 hugo.yml 中设置
params.ui.toc_taxonomies: false 则全站隐藏。这只改变右栏显示,不改变页面的标签、
署名、系列或分类归属。
分类法列表页与术语页的右栏最上面是分类法切换器:声明的每种分类法一行,带图标、名称与术语数,链向各自的列表页,当前那一行高亮。列表页的分类云按全站统计,并略去自己这一种——它的术语就是旁边的卡片。只有一种分类法的站点不显示切换器。
计数 不是全站计数,而是按顶层栏目统计:先看页面的 type 有没有同名栏目(type: docs 的页面用 /docs/ 这棵树),没有就用页面所在的顶层栏目。博客页上的「标签: release 4」说的是博客里有 4 篇,不是全站有 4 篇。
图标按复数名配置:
categories 与 tags 的默认值就是上面那两个,其它分类法默认 fa-solid fa-shapes。图标是一对 Font Awesome class,与站点其它地方的图标写法一致。
顶栏菜单里的分类面板
主菜单里指向分类法列表页的条目,会自动变成一块术语芯片面板(按用量降序,带计数),无需手写下拉项:
pageRef: /tags 与旧式的 url: /zh/tags/ 都能识别:URL 形式的菜单先解析成本站页面再判断类型,从旧配置迁移时不必改写法。菜单本身的其它写法见导航与菜单。
双语标签
Hugo 的分类按语言分开统计、分开链接:/categories/ 与 /zh/categories/ 是两棵互不相干的树,中文页只进中文那棵。术语要在各自语言的 front matter 里各写一遍:
两条要注意:
- 同一个词在两种语言里写成同样的字符串(例如
release),得到的仍然是/categories/release/与/zh/categories/release/两个术语页,各自只统计本语言的页面。不要为了统一而在中文页里写英文词:右栏芯片会显示英文。 - 分类法的显示名会跟着语言走(上面那六个内置键),但 术语名不会:术语就是你在 front matter 里写的那个字符串,主题不翻译它。英文页里写
高可用,英文站的芯片上显示的就是高可用。
多语言站点的其余部分见多语言。
按内容类型开关
要在文档中保留右栏分类云、在博客分区隐藏它,为博客索引与子页设置显示开关:
分类归属是另一回事,由页面上设置的术语决定。例如,本站的内容分类如下:
| 内容 | categories | tags | 效果 |
|---|---|---|---|
content/docs/** |
栏目级 cascade(「定制站点」等六个) | 不打 | 术语行只有一行「分类」 |
content/blog/** |
每篇写(release、oink) |
每篇写(Oink、Release) |
术语行两行,右栏两组芯片 |
让整个栏目从分类里消失:删掉栏目首页 cascade 里的 categories,不需要别的配置。让某一页不进分类:在它自己的 front matter 里写 categories: [],空列表覆盖 cascade。
验证
在自己的站点选择一篇已设置分类术语的页面,检查:
- 标题区显示了
params.taxonomy.page_header选定的术语; - 启用
toc_taxonomies时,右栏有分类分组与计数;关闭后分类云消失,页面的术语仍保留; - 自己的分类法索引(例如
/zh/categories/)列出术语,点击后能看到所属页面。
在站点根目录执行,将两个示例路径换成自己的页面与分类法产物路径,并按需包含语言前缀:
限制
page_header: []不会 隐藏术语行:空列表被当作未设置,回落到「列出全部分类法」。要去掉这行,就不要给这些页面打标签,或在assets/scss/_styles_project.scss里隐藏.taxonomy-terms-article。toc_taxonomies: false可以隐藏右栏分类云,但配置不支持限制可见分类云的术语条数。- 术语页没有跨语言对等关系:语言切换在术语页上不保证落到「同一个术语的另一种语言」。
相关
5.12 - 仓库与页面信息
面包屑行右侧的 操作菜单 里与仓库有关的条目,由几个 github_* 参数推导;页尾的「最后修改」信息行来自 git 历史。前提是内容存放在一个 GitHub 风格的仓库里。
四个键接通全部链接
操作菜单里所有跟仓库有关的条目,都由这几个键推导出来:
上面是本站的真实配置。填好之后,本页的操作菜单里这几条指向:
| 菜单条目 | 目标 |
|---|---|
| 编辑当前页面 | …/edit/main/content/docs/customize/repository.zh.md |
| 查阅编辑历史 | …/commits/main/content/docs/customize/repository.zh.md |
| 添加子页面 | …/new/main/content/docs/customize?filename=change-me.md&value=<模板> |
| 提交文档议题 | …/issues/new?title=仓库与页面信息 |
| 提交项目议题 | https://github.com/pgsty/oink/issues/new |
几点约定:
github_repo指向内容所在的仓库,不是主题仓库。写主题仓库会把读者的改动引到错误的位置。省略它时,上表四项文档操作不可用;项目 issue 仍只取决于github_project_repo。github_project_repo是第二个仓库,接收产品缺陷而非文档错误的议题。读者难以区分两者时不要配置它。github_branch默认main,填的是内容分支,不是部署分支,也不是 Pages 自动生成的分支。github_subdir是仓库内路径。站点源码在仓库根目录时留空;放在子目录(例如仓库里同时有代码和website/)时填website。
这几个键都可以在站点、单语言、栏目 cascade 或页面 front matter 上设置,内容来自多个仓库时用得到。键的完整定义在配置总览。
内容来自另一个仓库
内容来自上游仓库时,用栏目 cascade 覆盖仓库参数,再用
path_base_for_github_subdir 移除或替换物理源码路径的前缀,将结果接到
github_subdir 后面。复制到站点内部的源文件使用相对站点根的路径:
content/reference/api/client.md 因此映射到上游的 docs/api/client.md。
自 OINK 1.2.0 起,主题会在匹配前将 Windows 与 Unix 源码路径统一为 /,并保留文件名
大小写。物理挂载位于站点外部时,需要匹配绝对源码路径,不能使用 Hugo 的虚拟
挂载目标:
配合 github_subdir: docs,/srv/upstream/docs/api/client.md 映射为
docs/api/client.md。映射并整理后,源码必须得到非空的仓库相对路径。外部文件
未映射、结果仍为绝对路径或带盘符、结果为 . 或向父目录逃逸时,不生成编辑、
历史与新建子页操作;文档和项目 issue 链接仍遵循各自仓库配置。Windows 映射
表达式也使用 / 分隔符。
path_base_for_github_subdir 的值是正则;源文件名与本地不同名时改用 from / to 映射,例如把每个栏目的 _index.md 对到上游的 README.md:
OINK 把 .md 与 .zh.md 并排放在同一个目录里,两种语言共用同一个路径前缀,正则里不需要语言目录。改完从叶子页、栏目首页、两种语言各点一次「编辑当前页面」:正则去掉的部分过多时,生成的 URL 看上去合理,实际是 404。
关闭其中几条
菜单里每个条目都带一个稳定的操作 ID:
| 菜单条目 | 操作 ID |
|---|---|
| 复制 Markdown 文本 | copy_markdown |
| 查阅 Markdown 源码 | view_markdown |
| 在 ChatGPT / Claude 中打开 | open_chatgpt / open_claude |
| 查阅编辑历史 | view_history |
| 编辑当前页面 | edit_page |
| 添加子页面 | create_child_page |
| 提交文档议题 | create_issue |
| 提交项目议题 | create_project_issue |
| 打印完整章节 | print_section |
托管服务不支持某条时,用 CSS 隐藏:
命令面板用的是同一批 ID,隐藏菜单条目不会让它从面板里消失。全站用不上的目标应当从配置里省略对应的键,而不是用 CSS 遮盖:CSS 只能隐藏链接,不能把错误的链接改对。
整个菜单也可以按页面关闭,front matter 写 page_context_menu: false,见页面参数。
「添加子页面」预填的新页面模板来自主题的 assets/stubs/new-page-template.md;站点在自己的 assets/stubs/new-page-template.md 放一份同名文件即可替换成自己的骨架。
最后修改时间
这一行的数据来自 git,不是文件的 mtime。打开 Hugo 的 git 支持:
页尾出现「最后修改 2026年8月17日 · <commit 主题> (a1b2c3d)」,commit 部分链到 …/commit/<hash>。lastmod_commit 三个取值:
| 取值 | 显示 |
|---|---|
subject(默认) |
commit 主题 + 缩写 hash |
hash |
commit a1b2c3d |
none |
只有日期,不链 commit |
写别的值时会报 invalid params.ui.lastmod_commit 告警并使用 subject。
两点注意:
- CI 必须有足够的 git 历史。浅克隆(
fetch-depth: 1)取不到文件的最后一次提交,日期会缺失或错误。GitHub Actions 里设fetch-depth: 0。 - 未提交的文件没有 git 时间。本地预览新写的页面时这一行不出现。
git 历史不可用时不要用构建时间代替「最后修改」,构建时间不是内容的修改时间。
这一行属于 页面信息(Annotation) 组件,默认开启,位置在反馈之后、翻页器之前。整页关闭写 annotation: false。
这一行不是页面信息区块的全部。同一个区块还会渲染两种来源说明,都由页面 front matter 驱动,不需要覆盖模板:
- 上游署名:页面改写自别处时写
upstream_link,配上upstream_name、upstream_copyright、upstream_license、upstream_notice四个必填键,页尾出现一条带作品、版权人、许可证与完整声明链接的署名行;再写upstream_modified: true追加一条「本地已修改」。 - 译文说明:
params.ui.translation_notice写权威版本的语言代码,译文页就显示一条指回原文的说明;以本语言原创的页面写translation_notice: false退出。
这两族键的完整定义见页面参数。
确实需要自定义时,三个覆盖点各管一层:
| 覆盖哪个 partial | 改什么 |
|---|---|
layouts/_partials/annotation-items.html |
增删或重排这些行,保留主题的标记、图标、打印规则与无障碍标签 |
layouts/_partials/page-meta-lastmod.html |
换掉这些行的渲染标记 |
layouts/_partials/page-annotation.html |
换掉整个区块的外层容器 |
页尾的组成
五个组件的顺序是固定的,所有阅读型布局共用一份实现:
| 顺序 | 组件 | 主题默认 | 页面开关 |
|---|---|---|---|
| 1 | 分享 Share | 关(params.ui.share 为空) |
share: false,或页面自己的列表 |
| 2 | 反馈 Feedback | 关 | feedback: true / false |
| 3 | 页面信息 Annotation | 开 | annotation: false |
| 4 | 翻页器 Pager | docs / book / blog 开 | pager: false |
| 5 | 评论 Comments | 配置完整时开 | comments: false |
顺序对应读者读完最后一段之后依次会做的事:把这页递出去、说一句有没有帮上忙、看看它从哪来、翻到下一页、加入讨论。分享排在最前,因为它是唯一朝外的一块,而且一个决定要把文章转给别人的读者,在被问「这页怎么样」之前就已经决定了。分享栏的配置见写博客。
评论的配置在启用评论。
反馈组件
一行问题、两个按钮:「这篇文档解决了你的问题吗?」→ 是 / 否。选「否」再展开四个可选原因。默认关闭:
只给文档栏目开,用 cascade(博客通常只留评论):
行为边界:
- 点击即完成,没有输入框、没有提交按钮、没有登录。
- 选择按「页面 + 语言」写进浏览器
localStorage,读者回访时还能看到并修改自己的选择。 - 站点已有 Google Analytics(
gtag)时,发送docs_feedback事件,字段result(solved/not_solved)、page_path、language;选原因时再发一次,多带reason与refinement: true,便于和首次计数区分。没有 analytics 时组件照常工作,只是不上报,它不需要任何后端。 - 本页启用了评论时,反馈结果下面会多一条「在评论区补充详情」的锚点链接。反馈与 giscus 是两条独立的数据流,主题不会代替读者写评论。
本页在 front matter 里写了 feedback: true(docs 栏目默认关闭),页尾可以看到真实的组件。
贡献者墙
contributors shortcode 渲染一面 GitHub 头像墙,数据来自站点 data/ 目录下的一个文件,不在构建期访问 GitHub:
字段:github 必填并校验为合法 GitHub 用户名;重复时告警并跳过后项。name 缺省等于 github;role 可选;url 缺省是
https://github.com/<github>;avatar 可选,不填时渲染成首字母占位块,不发任何
网络请求,填写时必须是 http(s):// 或站内根相对路径。
多套名单写多个数据文件,用 data= 指定:
在 Markdown 与 RSS 输出里,头像墙降级成一串 - [@handle](url) — role 的列表。
data/contributors.yaml上面的例子因此不在本页渲染。放一个数据文件进 data/ 就能看到效果。
验证
在自己的站点根目录执行。将示例 PAGE 换成实际生成的页面,其源文件应属于配置的仓库:
- 打开该页标题旁的操作菜单。「编辑当前页面」应指向
github.com/<你的仓库>/edit/<分支>/<源文件路径>,与实际源文件路径逐段对应。在命令输出中查看带data-td-action="edit_page"的链接。 - 从分区首页(
_index.md)再检查一次,确认其源文件路径也正确。 - 检查页尾的「最后修改」行;本地新建、尚未提交的页面没有这一行是正常的。
相关
5.13 - 打印支持
单页打印不需要配置:外壳(侧栏、目录、顶栏、按钮)都带 d-print-none,浏览器的 Cmd/Ctrl+P 得到的是一份干净的正文。主题因此没有页面级的「打印本页」按钮。
需要配置的是另一件事:把一整个栏目(或一整本书)连同全部子页面合成一份带目录的连续文档。以下内容覆盖启用方式、打印视图的结构,以及排除页面的做法。
启用整章打印
print 是主题声明的自定义输出格式,主题不替站点打开它。在站点自己的 hugo.yml 里给 section 加上:
这是本站的配置。outputs 的每个键是 整体替换 而不是合并:加 print 时要把该类型原本有的格式(HTML、RSS、markdown)一起写全,漏一个就丢一种输出。
开启后,每个栏目多出一个 URL。路径段 _print 在最前面,语言前缀之后:
| 页面 | 打印视图 |
|---|---|
/zh/docs/customize/ |
/zh/_print/docs/customize/ |
/zh/docs/ |
/zh/_print/docs/ |
/zh/blog/release/ |
/zh/_print/blog/release/ |
页面操作菜单里同时出现「打印完整章节」,命令面板里也能搜到同一条(操作 ID print_section)。它打印的是 当前栏目:在 /zh/docs/customize/print/ 这页点它,得到的是整个「定制站点」栏目,不是这一页。
打印视图的结构
打开上面任意一个链接,从上到下是:
- 一条提示条:「这是本节的多页打印视图。点击此处打印。返回本页常规视图。」它带
d-print-none,只在屏幕上出现,不进纸。 - 栏目标题与摘要。
- 全栏目目录,条目编号是
1:、2:、2.1:这样的层级号,链接指向文档内的锚点。 - 每个页面依次排列,标题变成
1 - 配置总览这种「编号 - 标题」,描述作为导语,正文原样渲染。
页面顺序是侧栏顺序(weight),子栏目递归展开。第二页起每页都另起一页;第一页是否另起一页,取决于栏目首页自己的正文是否超过 50 个词:首页只有一句话时不单独占一张纸。阈值可以调整:
不需要那份目录:
也可以只对某个栏目关闭,写在栏目首页 front matter 里:
把某些页面排除在外
纯链接页、只有一段跳转说明的页、体积巨大的截图页进纸意义不大。给它们写 no_print:
它只影响整章打印视图,页面自己的 HTML 与浏览器 Cmd/Ctrl+P 不受影响。侧栏分隔项(sidebar_divider)也自动排除;分隔项为分区时,其子文档仍保留在打印顺序中。
组件在打印态的形态
打印是四态输出之一,每个组件都有确定的打印形态。整章打印视图与浏览器打印单个页面,规则一致:能交互的降级成静态,可折叠的一律展开。
| 组件 | 打印形态 |
|---|---|
| 提示块 | 静态块,折叠型(- / + / DETAILS)全部展开;边框转灰、去底色 |
| 标签页 | 标签条消失,所有面板依次展开,每个面板带自己的标题 |
| 代码块 | 去掉复制与展开按钮,取消最大高度与滚动,长行改为自动折行 |
| 表格 | 完整静态表格;浏览器预览支持键盘滚动,打印媒体样式在纸张输出中展开区域,表头跨页重复 |
| 图片 | 图与图注保留,缩放相关的属性被剥掉,宽度收进版心 |
| 画廊 | 网格改为竖排堆叠 |
| 文件树 | 静态面板,目录全部展开,分栏停在构建期宽度 |
| 参数表 | 完整定义列表,两种形态一致 |
| 公式 | 静态渲染的 KaTeX / MathML |
| Mermaid | 独立 Print 保留源码且不加载引擎;打印普通 HTML 时显示已渲染图,未渲染时显示源码 |
| Markmap | 打印视图是 HTML,运行时照常渲染 |
| PlantUML | 独立 Print 保留源码,不加载运行时;打印普通 HTML 页面时保留已渲染图片,未渲染时显示源码 |
| ECharts · Infographic | 降级成围栏源码块,不渲染图表 |
| Asciinema · OpenAPI | 一行带标题的静态链接,录像或规范地址可见;三套运行时都不加载 |
| 卡片 / 步骤 / 徽章 / 按键 | 静态呈现,内容不变 |
页面外壳不进纸:侧栏、目录、顶栏、页面操作菜单、反馈组件、标题旁的锚点链接、行内复制按钮。
打印含浏览器渲染图表的普通 HTML 页面时,应先确认图表已经绘制完成。
浏览器打印样式
主题自带一层 @media print 规则,单页打印与整章打印共用:
- 纸张
A4,页边距18mm 16mm 20mm;正文10.5pt,强制浅色配色。 - 字体切到
--td-print-font-family这个排印令牌,见品牌外观。 - 标题不与正文分家(
break-after: avoid-page),段落与列表项保留 3 行孤行 / 寡行控制。 - 表格、图片、块引用、提示块、卡片、标签页尽量不跨页断开;代码块允许跨页,但会自动折行而不是截断。
- 链接加下划线、转深蓝色,不会在链接后面打印出 URL 文本。需要这个行为的站点自己加:
- 收起的
<details>一律展开:折叠的提示块与文件树目录在纸上是完整的。
自定义排版写在 assets/scss/_styles_project.scss 的 @media print 块里,不需要改模板。
替换打印模板
需要改结构(例如给每页加页眉、换编号格式)时,覆盖最窄的那个 partial,都在 layouts/_partials/print/ 下:
| Partial | 负责 |
|---|---|
print/render.html |
整章视图的骨架:提示条、目录、递归内容 |
print/page-heading.html |
文档开头的标题与导语 |
print/content.html |
单个页面在整章视图里的呈现 |
print/toc-li.html |
目录里的一行 |
后三个支持 按内容类型 分化:建 print/page-heading-blog.html、print/content-book.html,主题会优先用带类型后缀的那个。
整本书的打印(type: book)走另一条路径:章节编号、图表编号与交叉引用都保持全书连续,见书籍出版。
验证
在自己的站点根目录构建,检查已启用 print 的分区产物。将示例路径换成该分区实际
生成的文件;有语言前缀时也应包含在内:
随后在运行中的站点上,打开该分区的 打印整个分区 操作:
- 确认打印视图包含该分区的页面,并排除了
no_print: true的页。 - 按
Cmd/Ctrl+P,打印预览中应没有提示条、顶栏和按钮。 - 选择含标签页与折叠提示块的页面,确认预览里所有面板都展开;写法可参考标签页。
- 打印一份 PDF,通读分页情况,必要时调整
section_break_wordcount。
相关
5.14 - Agent 支持
HTML 页面里有侧栏、脚本与样式,模型读它要先剥掉这层外壳。OINK 让同一份内容再产出一份纯 Markdown:每页一个 .md,站点根目录一份 llms.txt 索引,页面上一个「复制 Markdown 文本」按钮。三者都是构建期产物,没有运行时服务,也不需要内容协商。
这三件事都要站点自己在 outputs 里声明,主题不替站点打开。另有两样同样需要显式打开的产物,服务于一次要读不止一页的 agent:每个栏目一份全文包,每种语言一棵导航树。
每页一份 .md
markdown 是 Hugo 的内置输出格式。把它加进需要的页面类型:
这是本站的配置。outputs 的每个键是 整体替换 而不是合并:加 markdown 时要把该类型原本有的格式(RSS、print)一起写全,漏一个就丢一种输出。
URL 规律是在页面 URL 后面接 index.md:
| 页面 | Markdown |
|---|---|
/zh/docs/customize/agents/ |
/zh/docs/customize/agents/index.md |
/zh/docs/customize/(栏目首页) |
/zh/docs/customize/index.md |
/zh/(站点首页) |
/zh/index.md |
每个 HTML 页的 <head> 里同时有一条发现用的链接,抓取工具不必推断 URL:
.md 的内容
不是把渲染好的 HTML 转回 Markdown,而是 你写的源码:front matter 换成一个 H1 标题加一段引用式摘要,其后是正文原文,shortcode 就地展开成各自的 Markdown 形态。
原生 Markdown 形态的组件(提示块、表格、参数表、图片属性行、代码围栏、数据围栏)在 .md 里原样保留源码,模型读到的与你写下的是同一份内容。栏目首页在正文之后还会附一份 Section pages: 子页链接清单。
shortcode 形态各有确定的降级:徽章变成强调文本或链接,按键变成 Ctrl + K,标签页变成一段段 **标签名** 小节,参数表变成条目列表。每个组件页的「输出形态」小节写了它自己那一行。
站点没有开 LLMS 输出时,上面那条 LLMS index: 不会出现:主题不指向不存在的文件。
llms.txt
llms.txt 是站点根目录的一份纯文本清单,告诉模型「这个站有什么、机器可读版本在哪」。给 首页 加上 LLMS 输出格式即可生成:
多语言站点每种语言各一份:/llms.txt 与 /zh/llms.txt。内容是自动生成的站点索引:
三段的来源:Site index 是本语言首页加站点主菜单(menus.main,条目有 Markdown 版就链 Markdown 版,带 description 的顺带写上);Documentation index 是 docs 栏目的子栏目及其下一层页面,缩进表示层级,每行附上该页的 description;Site locales 是站点配置里的全部语言。指向站外的菜单条目(GitHub、issue 跟踪器)会被剔除:它们属于导航外壳,不是本站内容。
改进 llms.txt 的入手处是主菜单与各栏目首页的 description,不是这个模板。
全文包
每页一份 .md 适合已经知道自己要读哪一页的 agent;想通读整本手册的 agent 只能一页页爬。LLMSFULL 输出把这件事压成一个文件:每个顶层栏目一份 llms-full.txt,按阅读顺序装下该栏目的每一页。它是 OINK 0.8.0 的新增能力,栏目不主动要就不生成。
开关在栏目首页自己的 front matter 里,不在站点配置:
front matter 里的 outputs 会整体替换站点级列表,所以要把该栏目原本有的格式写回去:这里漏掉 markdown 或 print,栏目首页就少一种输出。front matter 按语言分开,双语站点要在 _index.zh.md 里同样写一遍,才有中文的全文包。
产物是每种语言一份,落在栏目根下——/docs/llms-full.txt 与 /zh/docs/llms-full.txt。顺序就是侧栏与翻页器呈现的阅读顺序:docs、book 栏目声明了 data/docs_nav.json 显式树时以显式树为准,否则按内容树的 weight。侧栏里藏起来的页面(toc_hide)同样不进包。
每一页前面有一条带来源 URL 的分隔,其后的正文与该页自己的 .md 逐字节相同:
Source: 指向该页的 Markdown 输出;页面没有 .md 输出时回退到它的 HTML 地址。
只有顶层栏目能带全文包。写在更深一层的栏目上会报 LLMSFULL output requires a top-level section 告警,并且不生成该文件。
只要有栏目开了全文包,llms.txt 就会多出一段 ## Full-text bundles,列出本语言的全部全文包:发现入口仍在 agent 本来就会抓的那个文件里。
本站的文档栏目已经开启:https://oink.pgsty.com/zh/docs/llms-full.txt 是全部中文文档,一次抓取。
导航 JSON
侧栏是站点的目录,读得懂它的 agent 可以先规划路线再抓正文。NAVJSON 输出把它变成数据:每种语言一份 navigation.json,放在语言根目录下。和全文包一样,它是 OINK 0.8.0 新增、默认关闭,由站点在首页打开:
这会产出 /navigation.json 与 /zh/navigation.json。这棵树就是侧栏与翻页器读的那一棵——docs、book 栏目声明了 data/docs_nav.json 显式树时以显式树为准,其余按内容树的 weight:
| 键 | 含义 |
|---|---|
id |
去掉语言前缀的页面路径,同一页在每种语言里 id 相同 |
url |
该语言下 HTML 页面的绝对地址 |
markdown |
该页 .md 的绝对地址,只有页面确实产出 .md 时才有 |
title |
导航标题(linkTitle,回退到 title) |
description |
页面的 description,有才写 |
kind |
真实页面是 home、section、page;占位条目是 external 或 link |
children |
有序子节点,有子节点才写 |
数组顺序就是契约,weight 不会被序列化:顺序已经算好了,消费方再排一次只会与它来源的侧栏对不上。
占位条目保持侧栏里的样子:manual_link 是 external 节点,URL 照作者写的原样带出;manual_link_relref 是 link 节点,引用已经解析好。两者都没有页面身份,因此既没有 id 也没有 markdown。侧栏分隔线与 Hugo 从不渲染的页面会被略去,它们的子节点留在原位。
契约带版本:schemaVersion 是 1,JSON Schema 随主题仓库发布,见 schema/nav.v1.schema.json——要消费这个文件就拿它做校验。站点发布了它时,llms.txt 的站点索引里会列出本语言的 navigation.json。
本站已开启:https://oink.pgsty.com/zh/navigation.json 就是这棵树的实例。
页面上的 Agent 动作
面包屑行右侧的操作菜单里,跟 Agent 有关的是四条:
| 条目 | 做什么 | 出现条件 |
|---|---|---|
| 复制 Markdown 文本 | 抓取本页 .md 写进剪贴板(悬停时预取,点击后无明显等待) |
本页有 markdown 输出 |
| 查阅 Markdown 源码 | 新标签页打开 .md |
本页有 markdown 输出 |
| 在 ChatGPT 中打开 | 带一句提示词跳转到 ChatGPT | assistant_links: true |
| 在 Claude 中打开 | 同上,跳转到 Claude | assistant_links: true |
前两条只要开了 markdown 输出就存在。「复制」是拆分按钮的左半边(剪贴板图标),复制成功后短暂显示一个对勾。
后两条默认关闭,要显式打开:
打开之后的边界:读者点击时,运行时用浏览器地址栏里的完整 URL(含真实域名、查询串与锚点)拼一句提示词,中文站是「请阅读
页面可以收紧站点策略,不能反向打开:front matter 里 page_context_menu: { assistant_links: false } 关掉本页的助手链接;站点没开时页面写 true 不会生效。整个菜单按页关闭用 page_context_menu: false,见页面参数。
命令面板里也能搜到这两条助手动作(用的是同一份动作清单),见命令面板。
按页面退出 .md 输出
在页面 front matter 里重写 outputs。它同样是整体替换,只写要保留的格式:
要保留 RSS、只去掉 Markdown,就把其它格式列全:
自定义输出
主题用 layouts/all.md 渲染 Markdown 输出,用 layouts/index.llms.txt 生成 llms.txt,两种可选输出则由 layouts/list.llmsfull.txt 与 layouts/index.navjson.json 负责。站点在自己的 layouts/ 下放同名文件即可整体替换,但 先考虑更窄的做法:
- 按内容类型:
layouts/blog/single.md、layouts/docs/list.md这样带类型的路径只影响那一类内容,主题的打印模板即按此分化(layouts/blog/single.print.html)。查模板查找顺序确认你的组合。 - 按 shortcode:站点自己的 shortcode 可以加输出格式专属模板,让它在 Markdown 输出里给出更适合机器读的形式。
- 按页面:少数高价值页面手写内容,成本低于改模板。
llms.txt 的内容由站点结构决定,改模板之前先确认问题不在主菜单或 description。替换 index.navjson.json 还意味着接手 nav.v1 契约:你自己产出的内容仍要能通过 schema/nav.v1.schema.json 的校验。
验证
在自己的站点根目录执行,先启用 Markdown 与 LLMS 输出。将 PAGE_MD 换成实际页面
生成的 Markdown 文件。下例假设文档分区为 docs,请按站点结构调整分区名与语言前缀。
对运行中的本地预览或生产站点,用自己的地址与页面路径检查。BASE_URL 要包含部署
子路径;检查译文站点时也包含语言前缀:
再检查四处:
- 所选页面 HTML 的
<head>中有rel="alternate" type="text/markdown"; - 点击标题旁的复制按钮后粘贴,得到的是 Markdown 而不是 HTML;
llms.txt里没有指向站外的链接;- 开启对应输出时,
llms-full.txt里每一页都以Source:行开头,同一页在各语言navigation.json里的id相同。
限制
- 主题生成的机器可读输出共有四种构建期文件:每页
.md、llms.txt,以及需要显式打开的、每个顶层栏目一份的llms-full.txt与每种语言一份的navigation.json。站点地图仍是 Hugo 自己的sitemap.xml。 - 全文包属于顶层栏目,没有整站一份的
llms-full.txt:想读全站的 agent 按栏目逐个读,清单在llms.txt里。 LLMS、LLMSFULL、NAVJSON都声明为非替代格式,所以它们都不会出现在<head>的alternate链接里,也没有对应的页面操作;它们靠约定俗成的路径与llms.txt里的条目被发现。- 服务端内容协商(同一个 URL 按
Accept: text/markdown返回 Markdown)不属于主题范围,要做在托管层。 - Markdown 输出走 源码 路径:只在浏览器端由 JavaScript 生成的内容(运行时绘制的图表)在
.md里是围栏源码,不是图。
相关
6 - 维护管理
本栏目覆盖内容写完之后的运维事项:在本机预览、构建并部署产物、接入评论与分析、跟随主题版本升级、故障定位。前面五个栏目决定站点的外观与内容,这一栏决定站点能否构建、部署在哪、出问题如何排查。
按任务导航
6.1 - 本地预览
两条命令覆盖日常工作:hugo server 在本机预览改动,hugo 产出可以部署到任何静态托管的 public/。前提是本机安装了 Hugo Extended(不低于 0.168.0);用 Hugo Module 引入主题时还需要 Go。构建不依赖 Node.js、npm 与 PostCSS,它们只服务于本仓库自身的回归检查。
预览服务器
在站点根目录(hugo.yml 所在的目录)执行:
打开 http://localhost:1313/。保存文件后 Hugo 重新构建并刷新浏览器,切换 Git 分支同样触发重建。首次启动较慢:用 Hugo Module 引入主题时,Hugo 要先通过 Go 把模块下载到缓存,之后的启动都走缓存。
常用开关
-D/--buildDrafts,- 把
draft: true的页面也构建出来 -F/--buildFuture,- 把
date/publishDate在未来的页面也构建出来 -E/--buildExpired,- 把
expiryDate已过的页面也构建出来 --disableFastRender,- 每次改动都整站重渲染,不用增量
-M/--renderToMemory,- 只在内存里渲染,不落
public/ -N/--navigateToChanged,- 保存哪个页面,浏览器就跳到哪个页面
--bind,- 监听地址;要让局域网或容器外访问就设
0.0.0.0 -p/--port,- 监听端口
--minify,- 预览也压缩输出,用来复现生产环境下的渲染
--printPathWarnings,- 有两个页面写到同一个目标路径时告警
本站开发时用的组合是:
-DFE 是 -D -F -E 的合写,草稿、未来与过期页面一并构建,写作时新建的页面才可见。
改动没有生效
Hugo 默认开启快速渲染(fast render),只重建它判定受影响的部分。修改布局、配置、data/ 或被 include 引用的文件时,增量判定可能不准,页面看起来没有变化。三步排查:
- 加
--disableFastRender重启,看是否恢复。 - 硬刷新浏览器(
Cmd/Ctrl+Shift+R),排除浏览器缓存。 - 仍未恢复则清缓存后重启。
从其它设备访问
hugo server 默认只监听 127.0.0.1,其它设备访问不到。要在手机或另一台机器上预览:
--baseURL 必须写成对方可访问的地址,否则页面能打开,但 CSS 与搜索索引这类走绝对路径的资源会指向 localhost。
生产构建
部署产物用 hugo 构建,不用 hugo server:
产物写入 public/,该目录可以脱离源码树独立部署。三个开关各管一件事:
--gc- 构建后清掉
resources/_gen里不再被引用的缓存资源 --minify- 压缩 HTML、CSS、JS 与 XML 输出
--printPathWarnings- 两个页面撞到同一个输出路径时告警,多语言站点最常见的静默错误
统一使用 Hugo Extended 0.168.0,告警保持可见。禁止启用 --panicOnWarning
或 HUGO_PANICONWARNING,上游 LibSass 弃用告警允许出现。giscus 配置缺失或主题
取值无效时也会告警并降级;发布前应核对这些诊断与实际产物。
本站 CI 使用上面的生产构建命令。验证公开主题版本时,关闭本地模块替换与两种工作区。
baseURL 与构建环境
baseURL 写在 hugo.yml 里,也可以在命令行覆盖:
部署到子路径时 --baseURL 必须带上那段路径,细节见发布上线。
构建环境用 -e / --environment 选择,hugo 默认 production,hugo server 默认 development。这个选择在 OINK 里有三处可见后果:
production下才输出<meta name="robots" content="index, follow">,其它环境输出noindex, nofollow。production下robots.txt是Allow: /,其它环境是Disallow: /。production下才渲染 Hugo 的 Google Analytics 模板,静态资源也才做指纹与 SRI。
预览部署(PR preview、staging)用非 production 环境构建,产物自带不被搜索引擎收录、不上报分析的行为:
容器内预览
容器不是必需的。两种情况适合用容器:团队需要固定工具链版本,或不希望在每台开发机上安装 Hugo。
镜像里装 Go 的原因:用 Hugo Module 引入主题时,Hugo 需要 Go 解析并下载模块。用 submodule、离线归档或直接克隆的站点可以去掉 Go,镜像会小很多。
public/容器里的进程默认是 root,生成的 public/ 属于 root,宿主机上删不掉。共享环境里用 --user "$(id -u):$(id -g)" 映射用户 ID(上面的生产构建命令已经带了)。
镜像不需要 Node.js、npm 与 PostCSS,也不应出现拉取远程浏览器资源的步骤。网络隔离环境需要预先镜像基础镜像与这两个软件包。
清缓存
Hugo 的中间产物分三处,从轻到重依次清:
public/,- 删了页面但线上还在;或用
hugo --cleanDestinationDir让构建自己清 resources/_gen/,- 换了图片处理参数、换了字体或主色,页面还是旧样子
hugo mod clean,- 换了主题版本但解析出来还是旧的;加
--all清整个模块缓存
public/ 与 resources/ 都应该写进 .gitignore,不要提交生成产物。
与主题一起改
同时修改主题与站点时才需要这一节。用 HUGO_MODULE_REPLACEMENTS 把模块临时指向本地 checkout,go.mod 保持不变:
本站的 Makefile 封装了这几条命令,要求主题 checkout 在同级目录 ../oink:
CI 与生产构建仍可能继承替换或 workspace。不要提交包含开发机路径的 workspace。
验证公开标签时,移除 HUGO_MODULE_REPLACEMENTS,同时设置 GOWORK=off 和
HUGO_MODULE_WORKSPACE=off。还要检查 go.mod、Hugo 配置中的持久替换,以及
_vendor/ 副本;先在同一环境下用 hugo mod graph 确认精确版本,再执行构建。
断网构建验证
网络隔离环境的验收要同时覆盖构建阶段与浏览器阶段。六步:
- 从一份已校验的主题归档与空的模块缓存开始(
hugo mod clean --all)。 - 阻断出站 HTTP、HTTPS 与 Go module proxy。
- 运行生产构建
hugo --gc --minify --printPathWarnings。 - 浏览产物里两种语言的页面:文档页、博客页、首页、404。
- 操作搜索、深浅色切换、图表与内容组件。
- 检查子资源来源,确认没有意外的远程主机。
最后一步使用与固定发布版本对应的主题 checkout 中的输出检查器。它需要 Python 3, 不依赖本文档站的测试框架。将下面两个绝对路径换成自己的目录,base URL 与构建时保持一致:
脚本扫描四种输出里的每个 href / src / srcset / poster 与表单 action,要求它们是站内相对路径或 http / https / mailto / tel,并拒绝行内 on* 事件处理器与 javascript: URL。指向别的主机的 <iframe> <script> <link> <img> <video> <audio> <embed> <object> <source> 一律报错,站点确实要嵌入第三方内容时加 --third-party 放行,多域名语言配置用 --allow-host 追加首方主机。
一次通过只证明当次提交与当次环境。每个主题候选版本、每次随附依赖更新之后都要重跑一遍。
验证
一次干净的生产构建应该是这样:
看到 Total in … 且无 ERROR 即表示构建通过。随后确认:
- 日志里没有 npm、PostCSS、Autoprefixer 或下载浏览器资源的步骤。出现了说明配置里混进了上游 Docsy 的流程。
public/下有sitemap.xml、robots.txt,robots.txt是Allow: /。- 启用本地搜索后,
public/下每种语言各有一份索引:生产文件名为offline-search-index.<语言>.<hash>.json,开发环境不带 hash。打开搜索,确认data-td-index-src指定的实际 URL 返回 200。 - 用
hugo server打开代表性页面:一个文档页、一个博客页、首页、404,两种语言、两种配色都看一遍。
构建失败或结果不对,去排错与检查。
相关
- 发布上线 — 把
public/发到 GitHub Pages、Cloudflare 或别处 - 排错与检查 — 构建、语言、搜索、平台四类常见故障
- 从零建站与其它安装方式 — Hugo Module / submodule / 离线归档的取舍
- 配置总览 —
hugo.yml里每个键的定义
6.2 - 发布上线
OINK 站点的产物是一个纯静态目录,任何能托管静态文件的地方都能部署,不需要 Node 运行时、服务端渲染或构建插件。托管商一侧只有三件事:用正确的 Hugo 版本执行一条命令、发布 public/、让 baseURL 与最终访问地址一致。
先使用 Hugo Extended 0.168.0(内置 LibSass,无需安装外部 Sass)在本机完成生产构建。请勿启用 --panicOnWarning 或 HUGO_PANICONWARNING,保持告警可见并审阅(上游 LibSass 弃用告警属预期提示)。
确定 baseURL
baseURL 是最常见的故障源,失败方式也隐蔽:页面能打开,但搜索索引 404、页面操作链接指向错误位置、部分资源加载失败。
部署到域名根目录:
部署到子路径(https://example.com/docs/)时,路径必须写进 baseURL:
也可以在构建时覆盖,让同一份源码部署到不同位置:
canonifyURLs 修子路径Hugo 的 canonifyURLs 默认 false,保持这个默认值。OINK 的模板与内容链接都基于 baseURL 解析:路径不对是 baseURL 不对,打开 canonifyURLs 会把本来正确的相对链接一起改写,让问题更难定位。
启用本地搜索后,打开搜索,在浏览器 Network 面板检查实际索引请求:语言与部署子路径
应正确,响应应为 200。页面的 data-td-index-src 属性给出完整地址;生产文件名是
offline-search-index.<语言>.<hash>.json,开发环境不带 hash。不要通过猜文件名来验证。
选一个托管商
源码托管在 GitHub 时,一份 Actions 工作流就够:构建在 Actions 里执行,产物通过
Pages 部署 API 发布,不需要维护 gh-pages 分支。OINK Starter 已经包含下面的文件;
只有手工组装站点时才需要复制。
这是 OINK Starter 内置的工作流。几处不能删:
fetch-depth: 0— 站点开了enableGitInfo时,「最后修改时间」和贡献者信息要读完整 Git 历史,浅克隆会让它们为空。setup-go+go mod download— Hugo Module 方式引入主题时,Hugo 需要 Go 才能解析模块。用 submodule 安装主题的站点改成submodules: recursive,用离线归档的站点把themes/oink/提交进仓库,这两步都可以去掉。GOWORK: off与HUGO_MODULE_WORKSPACE: off— 防止本地开发用的go.work意外参与 CI 构建,保证 CI 验证的是go.mod里固定的那个公开标签。--baseURL "${{ steps.pages.outputs.base_url }}/"— 项目站点的 URL 形如https://<OWNER>.github.io/<REPO>/,configure-pages会把它算出来,不用手写。 告警保持可见并接受审阅。禁止启用--panicOnWarning或HUGO_PANICONWARNING; 上游 LibSass 弃用告警允许出现。
在仓库 Settings → Pages → Build and deployment 里把 Source 设为 GitHub Actions,推一次 main,在 Actions 标签页查看第一次运行。
自定义域名在同一设置页的 Custom domain 里填写,并按提示配置 DNS,随后把
hugo.yaml 里的 baseURL 换成这个域名。发布流程需要产物里带 CNAME 文件时,
把它放进 static/CNAME,Hugo 会原样复制到 public/。
OINK Starter 内置 .github/workflows/cloudflare-pages.yaml,使用 Direct Upload。
生产构建留在 GitHub Actions,Wrangler 把同一份 public/ 产物上传到 Cloudflare
Pages 项目。
- 创建一个 Direct Upload Pages 项目。项目名默认与仓库相同,也可用仓库变量
CLOUDFLARE_PROJECT_NAME覆盖。 - 添加仓库 secrets:
CLOUDFLARE_ACCOUNT_ID与CLOUDFLARE_API_TOKEN。token 需要 Account → Cloudflare Pages → Edit 权限。 - 手动运行一次 Deploy to Cloudflare Pages。设置仓库变量
CLOUDFLARE_PAGES_ENABLED=true后,每次推送main才自动部署。 - 规范 URL 默认是
https://<project>.pages.dev/;自定义域名成为生产地址时设置CLOUDFLARE_SITE_URL。
workflow 固定 Hugo Extended 0.168.0,从 go.mod 读取 Go 版本,关闭本地模块
workspace,并在上传前使用 Hugo Extended 0.168.0 构建,保留可见告警。这是 Starter 用户最可复现的推荐路径。
Cloudflare Git integration 仍然是另一种可选模式:构建命令设为
hugo --gc --minify --printPathWarnings,输出目录 public,Hugo
固定 0.168.0,Go 固定 1.27。同一个项目只选 Git integration 或 Direct Upload
workflow 其中一种;预览部署应按预览 URL 和非生产环境构建,避免被搜索引擎收录。
Netlify — 构建命令 hugo --gc --minify,发布目录 public,环境变量 HUGO_VERSION。同样的设置可以写进仓库:
用 submodule 安装主题就打开递归 submodule 检出;用 Hugo Module 就要求构建环境有 Git 和 Go。生产与预览应使用同一个 Hugo 版本,除非预览环境本来就是用来测升级的。
Vercel — 同样的三件事:构建命令 hugo --gc --minify、输出目录 public、环境变量 HUGO_VERSION。它同样不需要安装 npm 依赖。
任意静态服务器(Nginx / Caddy) — 原样提供 public/ 的内容。下例通过
current 符号链接指向一份发布目录,创建方法见下方离线打包步骤。配置 Nginx 或
Caddy 时,将宿主的站点根目录设为这个链接:
站点是纯静态的,没有需要转发给应用服务器的路径。
对象存储 — Hugo 自带 deploy 命令,把目标写进配置即可:
构建之后执行 hugo deploy:它比对远端与 public/ 的差异,只上传变化的文件,并在给了 cloudFrontDistributionID 时使 CDN 缓存失效。不带 --target 时用第一个目标,--dryRun 先看要改什么。两个前提:Hugo 二进制带 withdeploy(hugo version 的输出里能看到),云厂商凭据由标准环境变量或配置文件提供(AWS 上先用 aws s3 ls 确认)。
离线打包 — 网络隔离环境里,在能联网的机器上构建,把产物打成一个包带过去。
先将归档路径换成自己的值,每份产物使用新的发布名称。Linux 宿主上的命令需要
/var/www/oink 的写权限及 GNU mv;current 应不存在或为符号链接,
current.next 应不存在。按上例配置 Nginx 或 Caddy,从 current 提供文件;
这是宿主配置,不是 Hugo 选项。
仅在构建与打包成功后,把这份归档传到 Linux 宿主,再在那里执行以下命令。 每份产物使用新的发布名称:
这组命令只有在新目录创建成功、解压完成且 index.html 存在时才切换 current。
随后检查线上页面,并保留上一份发布目录用于回滚。不要把新包解压覆盖到已有发布目录。
构建时就要用目标环境的 baseURL;需要改变它时重新构建。
托管商没有 Go — 用 Hugo Module 引入主题需要构建环境有 Go。平台不提供时,改用 Git submodule(构建前执行 git submodule update --init)或离线归档(把 themes/oink/ 提交进仓库),见从零建站与其它安装方式。
预览部署不要被收录
Hugo 的 -e / --environment 只选择构建期行为,不改变站点内容,但 OINK 有三处会跟着它变:production 环境才输出 <meta name="robots" content="index, follow">、才让 robots.txt 变成 Allow: /、才渲染 Google Analytics 模板。PR preview、staging 这类构建不要用 --environment production:
出来的产物自带 noindex, nofollow 与 Disallow: /,也不会向分析服务上报数据。
内容安全策略
主题自带的运行时、字体与图标都是同源资源,但仅有
script-src 'self'; style-src 'self' 并不能覆盖普通 OINK 页面。主题会输出行内的
主题初始化与外壳预绘制脚本、初始画布及主题色和字体角色样式,以及部分组件的
style 属性;Markmap 还会增加行内配置与样式。主题不提供通用策略,也不自动生成
CSP 哈希或注入 nonce;部署方需根据实际构建产物制定策略。
额外功能也会改变所需指令:
- 作者写的行内 HTML 与行内脚本,
renderer.unsafe: true之下由作者负责。 - ECharts 的
$fn:回调:回调函数由站点注册到window.OinkEchartsFunctions,注册脚本的来源要进script-src。 - 分析脚本:站点自己插入的那段脚本与它上报的目标。
- OINK 1.4.0 提供 PlantUML 本地渲染,需要允许同源脚本、在
script-src中允许wasm-unsafe-eval以编译 WebAssembly,并在img-src中允许blob:,无需放行渲染服务来源。 - 远程 API 规范与自建图表服务:落在
connect-src与img-src。 - giscus:
script-src与frame-src要一起放行。 - 双列视图:窗格是同源 frame,
frame-src(或child-src)需要允许'self',响应也要允许同源嵌入(不能有X-Frame-Options: DENY或frame-ancestors 'none')。开启后每个 HTML 页面多一段内联启动脚本,脚本 hash 随之变化。
对允许执行的行内脚本和样式块,按最终部署字节计算哈希,或由托管层在每次响应时向策略头和对应标签注入新的 nonce。<style> 标签上的 nonce 不会授权行内 style="..." 属性,后者需在 style-src-attr 下单独配置。正文、配置、压缩方式或主题更新后,请重新核对哈希。建议先以 Content-Security-Policy-Report-Only 模式上线观察,在浏览器中验证深浅色启动、外壳状态、菜单及全部已启用组件无误后,再切换为强制执行。
从只覆盖已审查功能的最小策略起步,逐项放行:不需要回调时让 ECharts 选项保持纯数据,审查作者写的行内脚本,只为站点主动启用的集成添加远程来源。产物里的子资源来源可以先用断网构建验证里的脚本扫一遍。
验收清单
部署完成后按这张表走一遍。前四项是构建期的,后面几项要在真实 URL 上查。
生产构建- 构建命令带
--printPathWarnings,日志里有Total in … baseURL正确- 页面源码里
<link rel="canonical">指向真实生产地址(含子路径) 站点地图<baseURL>/sitemap.xml可访问;多语言站点是一个索引,指向/en/sitemap.xml、/zh/sitemap.xmlrobots<baseURL>/robots.txt是Allow: /并带Sitemap:行;预览部署应该是Disallow: /搜索索引- 启用本地搜索后,
data-td-index-src指定的实际 URL 返回 200;生产文件名带 hash,站内搜索有结果 Markdown 输出- 任一页面 URL 后面加
index.md能取到纯文本(站点在outputs.page里开了markdown时) llms.txt- 站点在
outputs.home里开了LLMS时,首要语言与每种已启用语言根都能访问llms.txt 已启用语言- 每种语言的文档页、博客页、首页都能打开,语言切换落到对应页面而不是首页
外观与交互- 深浅色切换、打印视图、代表性组件(提示块、标签页、代码块复制)正常
404- 访问一个不存在的路径,看到站点自己的 404 页
sitemap.xml、robots.txt、.md 与 llms.txt 这几项的开关在配置总览,Agent 输出的细节见 Agent 支持。
回滚
静态站点的回滚就是重新发布上一个已知可用的 commit,不要在生产上手工改文件。
- GitHub Pages:在 Actions 里找到上一次成功的
Deploy to GitHub Pages运行,点 Re-run all jobs;或者git revert出问题的提交再推一次。 - Cloudflare Pages / Netlify / Vercel:在部署列表里选上一个成功的部署,用平台的 Rollback / Publish deploy 把它重新设为生产版本。
- 自建静态服务器:把
current切回离线打包时保留的上一份发布目录。不要把旧包覆盖到新文件上,否则新版本独有的路径仍会在线上保留。
在 Linux 宿主上,把示例路径换成保留的已知可用版本。符号链接与 GNU mv 的前提同上:
通过线上 URL 检查一个旧版代表页,并确认仅在被撤回版本中新增的路径已不再提供。
问题出在主题升级而不是内容时,回滚的是 go.mod 里固定的版本,见版本升级。
相关
6.3 - 启用评论
OINK 的评论走 giscus:每个页面对应一条 GitHub Discussion,读者用 GitHub 账号登录后发言,维护者在 GitHub Discussions 里审核与管理。主题不提供自建评论后端,也不内置 giscus 以外的服务商。
前提是一个公开的 GitHub 仓库,访客读不到私有仓库的 Discussions。
启用评论的页面会从 https://giscus.app 加载脚本和 iframe,网络隔离环境里用不了。它默认关闭,只在显式打开时才加载。站点有隐私政策时,这条外部数据边界应当写进去。
准备 GitHub 仓库
-
选一个公开仓库存放评论线程,可以就是站点源码仓库。
-
在仓库 Settings → General → Features 里勾选 Discussions。
-
为该仓库安装 giscus GitHub App。未安装 App 时访客无法评论或表态。
-
选一个 Discussion 分类。giscus 推荐 Announcements 类型:只有维护者与 giscus bot 能在该类型下新建 Discussion,读者不会误开话题。
仓库 ID 与分类 ID 是公开标识符,不是凭据。不要往 Hugo 配置里放 personal access token、OAuth secret 或密码。
生成配置
打开 giscus.app,按表单填仓库、映射方式和分类,页面下方会生成一段 <script>。把里面四个属性抄进 OINK 配置:
data-reporepodata-repo-idrepoIddata-categorycategorydata-category-idcategoryId
映射方式(mapping)决定哪个页面对应哪条 Discussion。OINK 默认 pathname,适合发布路径稳定、同一个仓库要服务多个域名或预览环境的站点。开始收集评论之后再改 mapping 或移动页面,giscus 会去找另一条 Discussion:已有评论不会被删除,但页面上再也找不到它们。映射方式要在上线前定好;确实要改 URL 时,同时保留重定向或重命名 Discussion。
全站启用
从你自己仓库生成的配置中复制四个仓库与分类字段;启用评论前,替换下面全部占位值:
repo、repoId、category、categoryId 必须来自同一个仓库及其选定的 Discussion 分类,四个键缺一不可:任何一个缺失或只有空白字符,Hugo 打一条 WARNING 并跳过 giscus,构建成功并保留告警;发布前应审阅诊断并补齐缺失值。type 目前只接受 giscus,写别的值同样是告警加跳过。params.comments 的键名与 Hextra 同形,从 Hextra 迁来的配置可以照搬。
其余的键(strict、reactionsEnabled、emitMetadata、term、lang、lightTheme、darkTheme、ariaLabel、errorMessage)都有默认值,完整定义见配置总览。功能开关既可以写 YAML 布尔值,也可以写 giscus 风格的 0 / 1。
按页开关
front matter 里的 comments 可以从任一方向覆盖全站开关,离页面最近的值优先。
只给某些页面开评论。全站关掉但保留完整仓库配置,再让选中的页面显式打开:
只关掉某些页面。全站开着,让不适合讨论的页面退出:
整个栏目统一设置用 cascade。本站在 content/docs/_index.zh.md 的 cascade 里写了 comments: true,本页底部因此有一个真实的 giscus 评论区。
站点同时配了 services.disqus.shortname 时,giscus 优先:giscus 生效即抑制 Disqus,comments: false 同时关掉两者,giscus 必填键不全则告警跳过、由 Disqus 兜底。
多语言文案
giscus 的界面语言自动跟随当前 Hugo 语言:简体、繁体、香港繁体分别映射到对应的 giscus locale,不支持的语言回退英文。只有自动选择不合适时才显式设 lang。
需要翻译的是 OINK 一侧的两句文案:评论区的无障碍标签与加载失败提示。它们按语言配置,与全局仓库配置合并:
语言层只需要写差异部分,repo / repoId / category / categoryId 留在 params.comments 里就够了。
跟随深浅色
theme: auto 时,giscus iframe 跟随 OINK 的深浅色切换按钮和浏览器的 prefers-color-scheme,读者切换主题时评论区一起变。
需要更贴合站点配色时,用 lightTheme / darkTheme 分别指定两套 giscus 主题,取值是 giscus 内置主题名或站点自己托管的 CSS。本站用的是后者:
theme 写成固定主题名时不再跟随切换。
giscus 的 iframe 从 giscus.app 加载,要读站点上的这个 CSS 文件需要 CORS 允许。本站在 hugo.yml 的 server.headers 里给本地预览加了 Access-Control-Allow-Origin: '*';线上由托管商的响应头配置决定。
隐私与 CSP
- OINK 不会索取或保存读者的 GitHub 密码与访问令牌,登录与发帖全程在 giscus / GitHub 一侧完成。
- 评论初始化脚本是主题自带的同源资源,只加入启用了评论的页面,未开评论的页面没有这段脚本。
loading: lazy时,读者滚动到评论区附近才加载 iframe。- 站点有严格的内容安全策略时,
script-src和frame-src都要放行 giscus,合并进现有策略而不是替换其它指令(总则见内容安全策略):
外部脚本加载失败或没能创建 iframe 时,OINK 结束加载状态并在实时状态区域显示 errorMessage,不会让页面停在「加载中」。
验证
然后逐项确认:
- 打开一个应该有评论的页面,页面底部出现 giscus,显示「使用 GitHub 登录」,界面语言是当前页面的语言。
- 切换 OINK 的深浅色,评论区跟着变(
theme: auto时)。 - 打开设置了
comments: false的页面,确认那里既没有 giscus 也没有其它评论组件。 - 发一条测试评论,回到 GitHub 看指定分类下是否出现了对应的 Discussion,并且能在 GitHub 上管理。
首次评论或表态创建 Discussion 之前,浏览器控制台提示「找不到 Discussion」是正常现象。
出问题时按这个顺序查:构建日志里的 WARNING(四个必填键)→ params.comments.enable 与 type → 页面 front matter 的 comments → 仓库是否公开、Discussions 是否开启、giscus App 是否安装 → 浏览器控制台与响应头(CSP 是否拦了 giscus.app)。找不到已有评论线程,先恢复原来的 mapping 和页面路径。
相关
6.4 - 分析与 SEO
主题默认不加载任何分析、表单或广告脚本,不配置就没有对外请求。接入需要显式配置,并把这条外部数据边界写进站点的隐私说明。SEO 一侧相反:canonical、hreflang、robots meta、Open Graph 与 Twitter 卡片由主题逐页生成,需要你做的是把 baseURL 与每页的 description 写对。
接 Google Analytics
用 Hugo 内置的服务配置。启用前,将 G-YOUR_MEASUREMENT_ID 换成你自己的
GA4 measurement ID:
主题只在 production 环境渲染这段脚本。普通 hugo server 默认使用 development,
不会上报;但 hugo 构建默认 production,即使运行在预览宿主上也一样。PR 与 staging
部署需要明确选择非 production 环境,并将 PREVIEW_URL 设为预览的实际地址:
详见预览部署配置。
不要同时设置已经弃用的顶层 googleAnalytics 键。不需要分析时删掉整段配置,不要填一个假 ID。
配上之后,页面浏览量与事件会发给 Google。严格的同源内容安全策略也需要为它放行,见内容安全策略。这是站点决策,不是主题默认。
接其它分析服务
Plausible、Umami、Matomo 这类服务只要求插入一段脚本。主题提供两个注入点,在站点仓库里建同名文件即可,不用改主题:
layouts/_partials/hooks/head-end.html,- 分析脚本、cookie 同意脚本、主题没提供的 meta 标签
layouts/_partials/hooks/body-end.html,- 只影响交互、不影响首屏的第三方代码
使用 Plausible 时,先把 your-site.example 换成你自己账户中登记的域名,再加入这个钩子:
hugo.IsProduction 这一层不要省:没有它,每个人的本地预览都会向你的统计上报数据。
这是有意的:cookie 同意脚本必须先于分析脚本运行,才能真正拦住它。
「这篇文档解决了你的问题吗」反馈组件是另一件事:默认关闭,不发网络请求,配置见仓库与页面信息。
双列视图窗格
开启 params.ui.compare 后,每次打开
双列视图都会在同源 frame 中加载两侧页面。主题内置统计(services.googleAnalytics.id
与 params.analytics.*)会自动跳过对照窗格,避免打开双列视图时重复计算浏览量。启用
params.ui.compare: true 后,OINK 会改用自带的窗格感知模板输出 Google Analytics
代码,不再调用 Hugo 内置模板,因此站点覆盖的 layouts/_partials/google_analytics.html
不会生效。
写在 head-end.html 中的自定义脚本仍会在每次窗格加载时执行,上报浏览前请通过
window.OinkComparePane 跳过窗格:
服务器和 CDN 统计同样会看到窗格请求。这些请求带有查询参数 oink_compare_pane=1,
可以据此过滤。
页面描述
<meta name="description"> 按这个顺序取值,取到第一个非空的就停:
- 页面 front matter 的
description - Hugo 计算出的页面摘要(
.Summary) - 站点配置里的
params.description
每页写一句 description 是唯一需要作者做的 SEO 动作。它同时用于三处:搜索引擎的摘要、栏目首页的卡片副标题、站内搜索的结果预览。
多语言站点要给每种语言各写一句,不要把英文描述抄到中文页上。站点级默认值也是分语言的:
canonical 与 hreflang
主题为每个页面输出一条 canonical,并为实际译文输出 hreflang 备用链接,不需要配置:
hreflang 的语言代码来自各语言的 locale(本站是 en-US / zh-CN),链接依据 Hugo 的译文配对关系生成。自 OINK 1.2.0 起,未翻译的页面不再输出缺失语言的 hreflang 与 og:locale:alternate 标签(尽管页面顶部的语言切换器仍可回退至目标语言首页)。
博客索引的各个分页均使用其自身的 canonical URL,且从第 2 页起不再输出多语言备用链接,以避免不同语言文章数量不同造成分页错位(1.1.0 仍保留旧行为)。
canonical 由 baseURL 拼出。baseURL 配错时 canonical 会把搜索引擎指向不存在的地址,比构建失败更难发现。上线前照发布上线的验收清单查一遍。
多语言的完整配置在多语言。
社交卡片
主题调用 Hugo 内置的 Open Graph 与 Twitter 卡片模板,标题、描述、URL、语言、站名都是自动的:
要让分享出去的链接带图,在 front matter 里给 images:
给全站一张兜底图就把同样的键写进 params:
有图时 twitter:card 从 summary 变成 summary_large_image,并多出 og:image 与 twitter:image 两条。本站两处都没有设置,上面的渲染结果里因此看不到图片相关的标签。
站点地图
Hugo 自动生成,多语言站点生成的是一个索引:
站点级默认值和页面级覆盖都是 Hugo 原生的:
changefreq 与 priority 是提示不是承诺,搜索引擎可以忽略。值得做的是发布前确认草稿、私有内容与非规范副本没有进入站点地图,并且每种语言的那份都生成了。
robots.txt 与不收录
Hugo 只在站点配置里打开开关时才生成 robots.txt:
主题提供的模板按构建环境给出两种结果,不需要你写内容:
页面里的 robots meta 跟着同一个开关走:production 且不是打印输出时是 index, follow,否则是 noindex, nofollow。预览部署不要用 --environment production 构建,非 production 自带不收录的行为。
主题没有按页 noindex 的开关。某一页不该被收录时,可靠的做法是不发布它(draft: true,或用 Hugo 的 _build 选项)。既要发布又不想被收录,就用 head-end.html 钩子自己输出;主题已经输出了一条 robots meta,两条同时存在时如何合并由搜索引擎决定。
收录检查
上线一两周后,按这个顺序确认搜索引擎看到的东西和你以为的一致:
- 抓取权限:访问
<baseURL>/robots.txt,确认是Allow: /而不是Disallow: /。 - 页面清单:访问
<baseURL>/sitemap.xml,点进语言子地图,看页面数量对不对。 - 收录数量:在搜索引擎里查
site:你的域名,数量级对得上就行,不必逐页核对。 - 规范地址:搜索结果应当落在 canonical 指向的 URL 上,而不是带
?参数或旧域名的版本。 - 主动提交:在 Google Search Console / Bing Webmaster Tools 里加上站点并提交
sitemap.xml的地址,比等着被爬快。
验证
在自己的站点根目录执行。将 PAGE 换成自己站点实际生成的页面,并按需包含语言前缀:
在浏览器 Network 面板确认已配置的统计请求使用自己的 measurement ID 或登记域名。
再检查用 --environment staging 构建的预览部署,应没有统计请求。未配置分析时,
两种环境均不应产生统计请求;其他显式启用的集成仍可能访问各自的远程服务。
相关
6.5 - 版本升级
升级 OINK 是换一个固定的模块版本,再确认站点仍能生产构建。内容多数不用改; 0.4 shortcode 改成当前 Markdown 原生形态时,有一套默认干跑的迁移工具,不必手改 几百个文件。
升级会改变渲染结果。先建一个升级分支再动手,回退的代价就是丢弃一个分支。
维护中的站点统一使用 Hugo Extended 0.168.0 及其内置 LibSass,无需安装外部 Sass。
不要启用 --panicOnWarning 或 HUGO_PANICONWARNING;Hugo 内置 LibSass 的弃用告警属于预期输出。
升级时请同步更新 Hugo workflow 固定版本;下方旧版本章节中的要求仅说明当时的发布状态。
升级到 1.5.1
OINK v1.5.1 稳定了 1.5.0 引入的按需开启双列视图,
并修正 RSS、Print、导航、校验、搜索与无障碍行为。未设置 params.ui.compare 的站点生成的页面
与输出保持不变,详情参见1.5.1 发布说明。
在关闭替换与工作区的环境中验证公开模块解析,固定 github.com/pgsty/[email protected],
并用 --printPathWarnings 构建。开启 params.ui.compare: true 时请注意:
- 在译文中保持与原文一致的
{#id}标题锚点,以便「同步滚动」准确对齐章节。 - 如需让某个语言版本不参与双语对照(既不显示入口,也不作为目标译文),可设置
languages.<lang>.params.ui.compare: false。 - 用
window.OinkComparePane让head-end.html中的自定义统计脚本在窗格里跳过上报。开启对照后,站点自定义的 Hugogoogle_analytics.htmlpartial 覆盖文件不再生效,详见统计与 SEO。 - 如有内容安全策略(CSP),需允许同源 frame(
frame-src 'self'),并确保服务器响应头允许同源页面嵌入。 - 打开一篇已有译文的 Docs、Book 或博客页面,确认「打开双语视图」能正常加载目标译文、按共同标题同步滚动,并在窄屏下切换单窗格。
升级到 1.4.0
OINK v1.4.0 保持仅依赖 Hugo 的构建方式,新增本地 PlantUML、统一图表阅读、
更完善的本地搜索、按需开启的表格阅读器与博客画廊。详情参见
1.4.0 发布说明。
在关闭替换与工作区的环境中验证公开模块解析,固定 github.com/pgsty/[email protected],
用 --printPathWarnings 构建,并检查导航、图片查看器与阅读器设置。源码副本方式的
消费站还需要刷新固定主题文件;修改 go.mod 不会升级已有的 vendor 副本。
OINK v1.3.1 修复 Markmap 文字对比度和固定 TOC 的页脚避让,并为技术表格与
文件树长注释提供显式开启的布局。已有内容保留默认外观,仅在需要完整技术信息时
开启技术宽表布局或文件树的 wrap=true。
提供四套风格的站点继续使用 preset: paper 和显式 [paper, slate, ink, terminal]
菜单。1.3.1 发布说明列出修复细节与贡献者致谢。
先看发布注记
保留 params.plantuml: true 或 plantuml.enable: true,删除 svg_image_url,即可使用
随主题提供的两份浏览器脚本,无需额外软件或渲染服务。保留非空端点则继续使用
服务端模式。完全关闭渲染请设置 params.plantuml: false 或 plantuml.enable: false;
空端点现在会选择本地模式。切换已有图表前,请核对不支持的 include、库与 CSP 要求。
每个版本的变更、破坏性改动与升级要点都写在发布注记里,升级前先读一遍目标版本那篇:
- 本站的 项目博客 里的 release 系列
- GitHub 上的 Releases 页面
注记说明这次要不要改内容、有没有配置键被移除、默认行为有没有变化。跳过这一步的代价是升级后对着一个变了样的页面猜原因。
升级 Hugo Module
生产站点固定已发布标签或主动选定的不可变 commit,不跟随分支,也不用 @latest。
下面升级到已发布的 v1.5.1 标签;选择后续版本时,先确认已经发布且模块可以解析,
再替换示例中的版本。
选择标签时,确认模块图显示的就是该版本。主动选定的不可变 commit 在 go.mod
中通常记录为 Go 伪版本。提交生成的 go.mod 与 go.sum。使用上面的公开标签时,
go.mod 包含:
make dev 和 make check 只为当次命令设置 HUGO_MODULE_REPLACEMENTS,
使用同级主题 checkout。验证发布标签时,移除该环境变量替换,禁用 GOWORK
和 HUGO_MODULE_WORKSPACE,并检查是否存在持久 replace 指令或 _vendor/。参见
本地预览指南。
使用 Git submodule 时,先确认没有本地修改,再拉取标签并检出精确的公开版本, 不要跟随远端分支:
验证后提交更新的 submodule 指针。离线归档与克隆则用选定版本的完整内容替换
themes/oink/,确认 theme: 的值仍与目录名一致。安装方式的取舍见
从零建站与其它安装方式。
升级后必做
三件事一起做了:清掉可能过期的缓存、用新版本重新构建、保留并审阅构建诊断。
--logLevel info 包含信息级诊断,告警保持可见并接受审阅。升级 Hugo
之前,先处理当前固定版本发出的弃用提示;诊断级别与移除时间取决于具体的弃用功能。
构建通过之后,人眼再过一遍:首页、一个文档页、一个博客页、404、两种语言、两种配色、打印视图,以及站点自己定制过的地方。
从 1.0 升到 1.1
从 1.0.0 升级到 1.1.0 不需要迁移源码,模块的 Go 1.27.0 声明与 1.0.0 相同。 当前升级统一使用 Hugo Extended 0.168.0 验证;历史工具链信息保留在 1.1 发布说明中。
选择新固定版本前,检查这些受影响的页面与行为:
语言- 32 份界面目录均具有相同的原生消息结构。检查站点语言标签、复数计数与 RTL 方向;正文译文仍由站点负责。
分类法- 根页变成术语卡片目录,并提供分类法切换器。检查分类法模板或 CSS 覆盖、作者头像与本地化面包屑。
侧栏- 缓存树保留页面有效设置,没有 JavaScript 时也可使用。检查折叠、悬停恢复、移动抽屉与键盘焦点,隐藏内容必须退出焦点顺序。
分组sidebar_divider: true保留分区子文档。仅在明确不发布分组自身输出时添加build.render: never;检查子导航、面包屑、翻页、Print 与 Book 目录。根菜单- 显式
sidebar_root_menu: false对自根分区也生效;当前可链接的根仍作为位置标记显示。 自定义脚本- 若还需支持 1.0.0,先检测
OinkSidebar与OinkCommandPalette.registerSearchTail。通过 API 恢复分支状态,不要直接修改 class 或 ARIA 属性。 文章复制- 启用图片缩放时,以纯文本与富文本 HTML 复制图片和图注。预览提示不得进入文章复制内容,缩放与键盘操作仍需正常工作。
Print 与 Redoc- 检查单页和 Book 聚合 Print、标题与标签页链接,以及真实部署前缀下的本地 Redoc 规范。本地规范路径相对于
static/。
params.ui.image_zoom 与 params.offline_search 仍默认关闭。新搜索钩子不会启用远程
服务,也不会添加查询遥测。params.ui.scroll_spy 与页面级 scroll_spy 在 1.x 中仍
作为 no-op 接受;移除无效补丁不影响普通大纲跟踪。
将受影响的站点级主题副本与新实现比较后再更新或移除。保留旧图片缩放脚本或侧栏 partial,会让站点继续使用旧实现,无法获得上游修复。
在文档站验证本地主题修改时,使用同级主题 checkout,不要提交文件系统模块替换:
这些命令验证的是本地 checkout。验收正式版本时,固定已发布标签,在没有模块替换的情况下 构建,并验证部署后的页面。创作与 API 细节见内容分组、 侧栏契约、 搜索动作 与图片缩放。
从 1.1 升级到 1.2
OINK 1.2.0 默认改为 Paper。需要保留原有外观的站点,在采用此改动前设置
params.ui.preset: slate。preset_menu: true 开启读者切换,默认仍为 false。
Ink 与 Terminal 需要显式设置预设或菜单列表;按钮不显示实验标记,配置启用边界不变。
自定义深色品牌选择器的兼容处理见品牌外观。
OINK 1.2.0 已发布。除上面的默认外观变化外,无需迁移内容源码。 当前升级统一使用 Hugo Extended 0.168.0 验证,并按以下清单验收站点:
- 检查所选预设、明暗图标、键盘与手机菜单、保存的偏好,以及自定义字体和强调色覆盖。 太阳表示亮色,月亮表示暗色;切换风格不得改变保存的明暗偏好。
- 复查显式导航、隐藏子树、页面链接、博客分页 canonical,以及缺少译文页面的 SEO 备用链接。
- 检查仅关键词命中的 CJK 搜索摘要,以及带字面百分号的大纲链接。复查 Windows 或挂载内容的编辑、历史与新建子页链接;映射结果必须是仓库相对路径。
- 检查 JavaScript 被禁用或阻断时的 Landing 内容、指标格式、弹窗与快捷键、 复制回退、Draw.io 操作,以及窄屏上的编号公式。
- 对图表端点与资源 alt 元数据执行保留告警的生产构建;非法值现在会警告并采用
安全回退。要有意关闭渲染,设置
params.plantuml: false或params.drawio.enable: false。 - 测试出版或内容转换时,使用修订后的 PDF 与迁移工具。审查 PDF 远程资源开关 和迁移 diff,包括嵌套在列表中的代码示例。消费站升级工具也随 1.2.0 一同发布,可用于批量清点、更新与验证模块版本。
内容迁移工具
0.4 的一批 shortcode 已换成当前 Markdown 原生形态。主题仓库带了一个只依赖 Python 标准库的工具做这件事:
用它的时候记住四条:
- 干跑是默认行为,只有
--write才落盘。先干跑,读 diff,再写。 - 重跑一次应该零改动。第二次
--write还报改动,说明有转换不收敛,停下来看那几个文件。 - 围栏里的文字不动,文档站里示范旧写法的代码块不会被误伤。
- 表达不了的构造原样保留,并附
file:line与原因列出,作为手工处理清单,不是失败。
只想先转某一类时用 --only,键名见下表最后一列:
改完重新构建一次并审阅告警,并逐页看渲染结果:工具保证语法正确,不保证语义符合预期。
0.4 → 当前语法映射
{{%/* alert color= title= */%}}、{{%/* details */%}}、{{%/* pageinfo */%}}、手写<details><summary>,callout{{</* tabpane */>}}+{{%/* tab header= */%}}、{{</* code-group */>}}+{{</* code-tab */>}},tabs{{</* filetree */>}}与filetree/folder、filetree/file,filetree{{</* gallery */>}}与gallery/image,gallery{{</* echarts */>}}、{{</* infographic */>}},datafencedoc-cards/doc-card、nav-cards/nav-card、card/cardpane、doc-carousel,cards{{</* imgproc */>}}、{{</* image */>}},image{{</* readfile file= */>}},include围栏属性,{filename="x"}fencetitle{{</* badge outline= */>}},badge{{</* example */>}}+ 围栏、{{</* book-figures kind="tbl" */>}},eg{{%/* _param x */%}}、iframe、conditional-text、blocks/*、netlify、不带 kind 的xref,reportonly
每个新写法长什么样、有哪些参数,去组件里对应的那一页。
从 Docsy 迁移
OINK 是 Docsy 的硬分支:内容模型、td- 命名、Sass 变量、大部分 front matter 都还在。迁移的核心动作是删掉站点里复制的公共外壳,让主题的实现接管,而不是重写正文。
-
固定目标版本。在
go.mod里换成 OINK 的发布标签,或者用完整的版本化归档。评估期可以用不提交的go.work指向本地 checkout。 -
清点覆盖项。把
layouts/、assets/、static/下每个站点级文件归成四类:公共外壳的副本(验证后删)、OINK 已提供的组件(删或机械重命名)、品牌定制(保留,缩到最小 hook)、业务专属数据与交互(留在站点)。按引用关系删,不要清空layouts/:首页、下载页这些地方可能还在调用你要删的 partial。 -
搬配置。
title、languages.*、github_repo、github_branch、page_width、params.ui.*全部留在原来的语义位置,OINK 没有另起一套命名空间。搜索与 Logo 这类只要打开对应的键:hugo.ymlDocsy 的驼峰式检索键在 OINK 中已改名:
offlineSearch、offlineSearchIndex、offlineSearchMaxResults、offlineSearchOnServe、offlineSearchSummaryLength一律改为下划线形式。这一步要自己盯着改——那份「中断构建并报出新键名」的迁移登记表已经删除,旧键现在只是一个没人读的键,检索会一声不响地保持关闭。 -
字体与样式的兼容点。站点的
assets/scss/_variables_project.scss里那些 Docsy Sass 变量仍然生效,会作为字体角色的种子值,不用为了升级把它们删掉:$td-fonts-serif、$font-family-sans-serif、$headings-font-family、$font-family-code各自喂给对应的字体角色。Docsy 的 Google Fonts 开关$td-enable-google-fonts、$td-google-font-name与$td-web-font-path主题已不再读取,留在文件里不影响构建,也不产生任何效果:OINK 自带 Inter、Chakra Petch 与 IBM Plex Mono,任何预设都不向 Google Fonts 发请求。想换字体走 token 层,见品牌外观。 -
换 shortcode。Docsy 的
alert、pageinfo、tabpane、card系列都有当前对应 形态,用上面的迁移工具批量转,--only一类一类来。 -
一次删一组,每组构建一次。在临时副本里演练,记下主题 commit、Hugo 版本、删了哪些文件、产出多少个 HTML;确认等价之后再在生产分支上重做一遍。
第二步里「验证后删」的那一类,通常是这些文件:
layouts/baseof.html与公共的 docs / blogbaseof*.html;- navbar、footer、sidebar、TOC、search、head CSS 的 partial 及其对应 hook;
- 旧的品牌文档外壳 partial;
asciinema、echarts、infographic、doc-carousel、details、tab/tabpane、card 与param的 shortcode 副本;- 只服务于上述实现的 JavaScript、Lunr 副本、轮播代码与 SCSS;
- 不再被任何站点资源需要的 PostCSS 与 Autoprefixer 步骤。
删完之后有两类问题会浮出来。
站点自己的脚本报 $ is not defined:主题不带 jQuery,它以前由 Docsy 在每个页面的 <head> 里加载。主题的功能都不需要它,仍然需要的站点自己引入:
用 Docsy blocks/* 搭的首页在 OINK 构建中报
template for shortcode "blocks/cover" not found:主题没有这一组 shortcode。改用
data/home/<语言>.yaml 的首页分区,或给页面写 layout: landing,见
首页与落地页。
从 0.4 升级的要点
0.4 改了几个默认行为。升级后发现页面多了或少了东西,先看这几条:
-
顺序翻页默认开启。
docs、book、blog页尾都有上一页 / 下一页;文档沿侧栏树走,博客沿时间走。刻意不属于任何序列的页面用pager: false退出。 -
顶栏在所有布局上都显示。紧凑状态只有一行图标导航,没有第二套移动端手风琴菜单,依赖旧移动菜单的本地脚本与测试要删掉。整个分区不要顶栏时用 cascade 里的
navbar_enabled: false。 -
页脚默认
fat且全站生效。只接受fat/slim/none;页脚数据必须放在data/footer/<语言>.yaml(单语言站点用data/footer.yaml),data/home里残留的footer键会告警并提示新位置。 -
单键导航默认开启:
/打开完整搜索,\只进命令模式。培训材料里描述旧行为的地方要改。页面操作也挪到了面包屑旁边的拆分按钮上。 -
代码块的 DOM 变了。
.td-code外壳套在原来的.highlight外面(.highlight与.chroma都保留),站点 CSS 里.td-content > .highlight这类直接子选择器要改成后代选择器.td-content .highlight。 -
两个 ICP 页脚参数被移除:
footer_icp与footer_icp_url换成一个支持行内 Markdown 的字符串。hugo.yml -
数学公式要站点自己开 passthrough。Hugo 不会合并主题的
markup配置,用\(…\)、\[…\]、$$…$$的站点必须在自己的hugo.yml里启用 goldmark passthrough 扩展,见公式。
验证
升级不是「构建通过」就算完,按表面分别看:
文档 / Book- 侧栏顺序、翻页、标题、页面操作、编号与交叉引用
博客- 时间顺序翻页、RSS 归属、顶栏与页脚
首页 / Landing- 无 JS 时的内容、紧凑菜单、打印
发布页- 推导出的下载 URL、校验和、发布状态
组件- 站点用得最多的那几个组件各找一页看渲染结果
无障碍- 纯键盘走一遍、焦点顺序、两种配色、强制颜色模式
部署- 站内链接与资源都保留了 base path 前缀
本站的完整门禁是:
其它站点跑等价的构建、链接、输出与浏览器检查即可,细节见排错与检查。
最后一步在真实环境上做:先部署一份预览,在真实 URL 上验证页面与浏览器的网络请求,评审通过再合并,合并后在生产上做一次冒烟测试。
回滚
回滚的是版本固定,不是工作树:
三条原则:
- 保留升级前的模块固定、站点 commit 与已知可用的部署产物,回滚时三者一起恢复。
- 不要只回滚一部分。给新主题塞回几个旧布局副本,会得到一个比任何完整版本都更难诊断的混合状态。
- 升级分支与验收证据都留着。回滚是为了先恢复线上,不是丢掉已经做完的工作。
线上产物本身的回滚(重新发布上一个部署)见发布上线。
相关
- 发布上线 — 部署产物的回滚
- 排错与检查 — 升级后构建报错怎么读
- 本地预览 — 清缓存与
go.work工作区 - 从零建站与其它安装方式 — 四种安装方式的取舍
- 组件总览 — 每个组件的当前写法
6.6 - 排错与检查
出问题时先做一次干净的生产构建,从第一条错误开始看,后面的多半是级联结果:
日志里出现 npm、PostCSS、Autoprefixer 或下载浏览器资源的步骤,说明配置里混进了上游 Docsy 的流程。OINK 消费端的构建只有一条 Hugo 命令。
下面四张表按「症状 → 原因 → 修法」组织,找到症状那一行即可,不必从头读。
构建
| 症状 | 原因 | 修法 |
|---|---|---|
| 构建报要求更高的 Hugo 版本 | 装的是标准版而不是 Extended,或版本低于 0.168.0 | hugo version 输出里必须有 extended。多个 Hugo 共存时先查 PATH 与版本固定配置,而不是再装一份 |
module "github.com/pgsty/oink" not found |
主题没解析出来 | Hugo Module:看 hugo mod graph、go.mod、go.sum,以及有没有多余的 workspace / replace。submodule:CI 有没有在 Hugo 之前跑 git submodule update --init。归档 / 克隆:theme: 的值要与 themes/ 下的目录名一致 |
| 模块下载卡住或超时 | Go 的模块代理不通 | Hugo 通过 Go 拉模块,所以走 GOPROXY。国内网络可以 export GOPROXY=https://goproxy.cn,direct;隔离环境改用离线归档或提交 themes/oink/ |
页面上出现 {.cards}、{.steps}、{caption=…} 这类原样文字 |
站点没开 goldmark 的块级属性 | 站点的 hugo.yml 里必须有下面那三项,主题的 markup 配置不会被 Hugo 合并进来 |
图片带属性行时被包进了 <p>,图注没生效 |
缺 wrapStandAloneImageWithinParagraph: false |
同上,三项一起加 |
| 行内 HTML 被转义成文字 | 缺 renderer.unsafe: true |
同上 |
\(…\) $$…$$ 原样显示 |
站点没启用 goldmark passthrough | 见公式;math: true 不是启用开关 |
shortcode "tabs" must be closed or self-closed |
有 {{< tabs >}} 没写对应的 {{< /tabs >}} |
报错里带 文件:行:列,去那一行补上闭合标记 |
template for shortcode "tabs" not found |
正文里写了一个不存在的 shortcode,或引用 shortcode 语法时没有转义 | 文档里讲解 shortcode 语法时必须转义:在开标记与闭标记的内侧各加一对 /* 与 */,Hugo 才会把它当文字而不是调用。名字打错就改回正确的名字 |
... attributes: unknown attribute "witdh" at ... |
属性行里的键拼错或不被允许 | 警告列出允许键并忽略坏属性;style 与 on* 同样丢弃。按提示修正属性名 |
shortcode "field": unsupported parameter "colour" at ... |
shortcode 参数名不对 | 警告指出 shortcode、参数、文件与行号,并忽略不支持的参数以保证本地预览可用;按提示修正参数名 |
invalid params.ui.page_width "widee" (allowed: normal | wide | full) -- using "normal" |
配置或 front matter 的取值,不在允许集合里 | 配置类错误降级而不中断,避免一个笔误导致 hugo server 下每个 URL 都返回 500。消息列出键名、收到的值和回退值 |
| 某个页面设置不生效,也没有任何提示 | 键写在了 front matter 的 ui: 段里 |
页面键写在 front matter 顶层,键名是站点键去掉 ui.。写进 ui: 段的键没有人读,也没有人报错,见页面参数 |
| 构建通过但线上少东西 | 有 WARNING 没人看 | 审阅构建告警,修正非法配置取值、缺失的 giscus 必填键或不支持的 comments.type。上游 LibSass 弃用告警允许出现 |
那三项 goldmark 配置:
两个最常见的 shortcode 报错长这样,注意结尾的 文件:行:列:
语言
| 症状 | 原因 | 修法 |
|---|---|---|
| 译文页面不出现 | 四种可能,按顺序查 | ① hugo.yml 里有 languages.zh 且设了 weight;② 文件名是 page.zh.md,zh 必须小写;③ 译文 front matter 没有 draft: true,date 不在未来;④ 影响路由的元数据与源文件一致 |
| 语言切换跳到了首页 | Hugo 没找到对应译文 | 这是设计行为:找不到译文就回退到目标语言首页。要跳到对应页面,需要那个译文文件确实存在 |
| 锚点链接打开了页面却不定位 | 译文标题文字不同,自动生成的 ID 也不同 | 在译文标题上显式写英文 ID:## 安装 {#installation}。标题里含 shortcode 或行内 HTML 时不要凭文本猜 ID,去看英文页渲染出来的 HTML |
| 菜单 / 首页分区没翻译 | 这些不在页面里,在配置和数据文件里 | 菜单在 languages.<lang>.menus,首页分区在 data/home/<lang>.yaml,界面字符串在 i18n/<lang>.yaml,见多语言 |
页面的 hreflang 指向另一语言首页 |
已发布的 1.1.0 行为或复制的旧 SEO partial 可能沿用语言切换器的回退 | 1.2.0 开发实现会从 SEO 备用链接中省略缺失译文。检查实际解析的主题版本与模板覆盖;需要译文时补上对应页面。可见语言切换器的首页回退仍然有效 |
搜索
| 症状 | 原因 | 修法 |
|---|---|---|
| 搜索框有但一直没结果 | 索引没生成 | 检查 params.offline_search 并打开搜索,在 Network 中查看页面 data-td-index-src 指定的请求。生产文件名为 offline-search-index.<语言>.<hash>.json,开发环境不带 hash;hugo server 未生成索引时还要检查 offline_search_on_serve |
| 索引文件请求 404 | baseURL 不对 |
子路径部署下 baseURL 配错是索引 404 最常见的原因。先在浏览器网络面板看它去哪里取索引,见发布上线 |
hugo server 下搜不了,构建出来就正常 |
站点把预览期的索引关掉了 | params.offline_search_on_serve 默认为 true,预览与线上行为一致;配置里显式写成 false 时预览不生成索引,删掉或改回 true |
| 中文搜不到 | 多数不是分词问题 | 中文查询走主题的 CJK 子串回退。先确认那个中文页面的内容进了中文索引(打开中文页面 data-td-index-src 指定的实际 URL),再看分词 |
| 新页面搜不到,旧页面正常 | 索引是构建产物 | 重新构建。hugo server 下改了页面要等它重建完 |
params.search.algolia requires explicit appId, apiKey, and indexName values |
Algolia 三个键没配全 | 三个键必须显式给全,主题不会替你用别的项目的 DocSearch 凭据。不用 Algolia 就把这段配置删掉 |
| 命令面板搜不到内容 | 它与全文检索是两件事 | 索引不可用时命令面板仍然能打开,只是提示索引不可用,页面操作与命令照常,见命令面板 |
平台
| 症状 | 原因 | 修法 |
|---|---|---|
| GitHub Pages 上页面 404 或样式全丢 | 项目站点的 URL 带仓库路径,baseURL 没带 |
用工作流里的 --baseURL "${{ steps.pages.outputs.base_url }}/",别手写。完整工作流见发布上线 |
| GitHub Pages 上「最后修改时间」「贡献者」全空 | checkout 是浅克隆 | actions/checkout 加 fetch-depth: 0:enableGitInfo 要读完整历史 |
| Cloudflare Pages 构建报 Hugo 版本太低 | 构建镜像的默认 Hugo 低于主题要求 | 在 Production 和 Preview 两个环境都设 HUGO_VERSION,并设 SKIP_DEPENDENCY_INSTALL=1 |
| 托管商构建时拉不到主题 | 构建环境没有 Go | Hugo Module 需要 Go。平台不提供就改用 submodule 或把 themes/oink/ 提交进仓库 |
| CI 上构建结果和本地不一样 | go.work 参与了 CI 构建 |
CI 里设 GOWORK: off 与 HUGO_MODULE_WORKSPACE: off,让它只认 go.mod 里固定的版本 |
| 预览部署被搜索引擎收录了 | 预览也用了 production 环境构建 | 预览构建不要带 --environment production,非 production 自带 noindex 与 Disallow: /,见分析与 SEO |
| macOS 报打开文件过多 | 实时预览监视的文件超过了 shell 限制 | 先把生成目录与无关目录排除出监视范围,这通常才是根因;再考虑 ulimit -n |
| WSL 下很慢或漏掉改动 | 跨 Windows 挂载点工作 | 让 Hugo 处理 Linux 文件系统里的路径,跨文件系统的变更通知和权限行为会让实时重载失效 |
| 缺 Bootstrap / Font Awesome / Lunr / Mermaid 之类资源 | 发行物不完整 | 不要用 CDN URL 掩盖。确认 assets/third_party/、assets/js/third_party/、static/webfonts/、VENDOR.json 都在;确实缺就重新获取同一个固定版本 |
站点自带检查
构建命令在自己的站点根目录执行。输出检查器是独立脚本,来自与固定发布版本对应的
主题 checkout;将 /path/to/oink、/path/to/my-site/public 和 base URL 换成自己的值。
其余命令是本文档仓库的测试实例,并非每个 OINK 消费站都自带的命令。
生产构建,- 重复输出路径、参数非法、外部集成配置不全
输出信任检查,- 四种输出里的每个
href/src都是站内相对或http(s)/mailto/tel;没有javascript:URL、没有行内on*事件处理器;跨站的<iframe><script><img>等要显式加--third-party才放行 翻译对等,- 每个英文页有没有中文对等页,以及渲染后的标题 ID 是否逐一对齐;锚点链接错位在这里暴露
完整门禁,- 下面六项串起来跑
npm test 里的六项各管一段:
test:base— 先构建一次,再跑 Markdown 风格、翻译对等、渲染后的 Markdown 与链接检查。test:hugo-build— 构建断言:博客元数据、RSS、内容组件、构建过程零弃用提示。test:md-output— Markdown 与llms.txt输出的 golden 比对,字节级。改了组件的 Markdown 形态就会在这里挂。test:alt-site— 用tests/fixtures/*.yml里的替代配置各构建一次,确认不同配置组合都能起来。test:favicons— head 输出的 golden 比对。test:release-pin-contract— 站点公告的版本与go.mod固定的版本是否一致。
浏览器行为另开一套:npm run test:browser 依次跑 Playwright 的无障碍(axe WCAG AA)、响应式外壳、键盘导航、内容组件、代码块与场景组件六个套件。
check-output-security.py 在主题仓库里它在主题仓库的 bin/ 下,不依赖站点测试框架。主题工具路径与待检查的站点产物路径应分别指定,完整例子见断网构建验证。
诊断习惯
上面的表覆盖不到的问题,按这几条挖:
- 用固定的 Hugo Extended 版本复现,不在版本浮动的环境里判断。
- 清掉
public/与resources/_gen再重建,排除陈旧缓存。 - 对比开发与生产两套配置层,很多只在线上出现的问题是环境差异。
- 看第一条错误,不是最后那条。
- 用一个最小页面区分「主题行为」和「站点覆盖」:把可疑内容单独放一页,站点覆盖分批重新启用,定位到具体那一项。
- 看故障页面的浏览器控制台与网络面板,尤其是 404 的资源路径。
求助渠道
开 issue 时带上这几样,能省掉一轮来回:Hugo 版本(hugo version 完整输出)、主题版本(hugo mod graph | grep oink)、第一条完整错误、能复现的最小页面或最小站点。
- 主题与文档的问题:https://github.com/pgsty/oink/issues
- 本站内容的问题:https://github.com/pgsty/oink.pgsty.com/issues
- 上游 Docsy 的兼容性讨论:https://github.com/google/docsy/discussions
相关
7 - 设计与开发
本专栏各契约描述 OINK v1.5.1。主题开发使用同级 checkout;发布验收在关闭替换与工作区后验证公开模块。
本专栏是 OINK 可长期维护的设计记录。站内其它专栏按任务讲解如何搭建站点; 这里集中说明现行不变量、这些选择背后的理由、用于比较方案的证据,以及仍处于 候选阶段的工作。
如何阅读本专栏
| 层次 | 含义 |
|---|---|
| 契约 | 兼容实现必须保留的规范性行为 |
| 决策 | 用于解释现行行为的已接受理由与边界 |
| 研究 | 带日期且不具规范性的证据,必要时应重新验证 |
| 提案 | PRD 与 RFC 草案;公开在这里不代表已经实现 |
契约目录
| 契约 | 权威范围 |
|---|---|
| 架构契约 | 构建、配置、诊断、本地化、特色图片、输出、安全、无障碍与性能 |
| 组件契约 | 组件 API、Book 与发布原语、校验和输出降级 |
| 外壳与导航契约 | 导航、搜索、博客展示、操作、分类法与页尾组合 |
| 落地页契约 | 落地页数据、22 种区块注册表、运行时、无障碍与输出 |
| 迁移边界 | 从 0.4 到当前版本所支持的内容与配置迁移 |
设计记录
| 集合 | 内容 |
|---|---|
| 设计决策 | 已接受的诊断、配置、创作模型、Expo 与 CLI 设计理由 |
| 设计研究 | Goldmark 探针、消费站证据、CLI 验收与 10 月主题评审 |
| 候选提案 | 知识图谱、媒体收敛、视觉预设与 CLI 路线图等活跃 PRD |
以后所有 OINK PRD 或 RFC 都必须以中英文页面对的形式放入
content/docs/design/proposals/,不得再在仓库中创建 plan/、plans/ 或
proposal/ 目录。提案被接受后,应同步更新实现、对应检查器与相关契约,把稳定
理由沉淀到“设计决策”,并通过 Git 历史与变更日志退出草案。
Expo 契约
Expo 契约 描述 OINK 1.3.0 新增的图片相册布局, 已接受决策 记录内容模型和对早期 Muse 实验的修订。 创作指南说明相册创建与查看器操作。 契约、已接受决策与创作指南说明同一套已发布的内容模型与控件。
权威来源与维护
本目录同时管理英文与中文维护者设计文档。主题仓库管理可执行事实:hugo.yaml
管理公开默认值;对应的解析器与检查器定义可选结构;layouts/ 与 assets/
管理渲染行为;检查脚本与 tests/goldens/ 管理验收;VENDOR.json 管理内置
依赖的版本、许可证、文件与校验和。
公共行为发生变化时,必须在同一次交付中更新实现、对应检查器以及本目录下相关 契约的中英文版本。测试应验证行为和输出,不应只固定某段文字。
7.1 - 架构契约
本契约描述 OINK v1.5.1。
Hugo 工具链
维护中的站点、开发、CI 与发布验收统一使用 OINK v1.5.1 和 Hugo Extended 0.168.0,固定这套工具链。
禁止启用 --panicOnWarning 或 HUGO_PANICONWARNING。告警保持可见并接受审阅;
上游 LibSass 弃用告警允许出现,不会让成功的构建失败。无效输入测试直接检查对应
告警和安全降级产物。
主样式与 ReDoc 样式使用 Hugo 内置 LibSass,普通用户无需安装 Dart Sass 或其他 Sass 程序。项目变量与项目样式的 SCSS 钩子保持可用。后续升级必须保持这条依赖 边界,并同步修改要求、检查器、CI 固定版本与双语契约;历史发布和研究记录保留 当时的工具版本。
OINK v1.5.1 模块要求 Go 1.27.3,开发与 CI 也固定使用此版本。 Go 用于解析 Hugo Modules;已准备好传统主题副本的站点无需 Go。
仓库与装配
仓库根目录是一个完整的 Hugo 模块与主题,不是站点,也不是 npm workspace。
Hugo Extended 使用内置 LibSass 编译 SCSS,并渲染模板。浏览器运行时与第三方资源都已提交到仓库,
因此普通构建不会访问网络。公开的双语文档、示例与浏览器测试位于同级的
oink.pgsty.com 仓库;主题仓库只在 tests/site/ 中保留范围明确的内部回归
夹具,不再维护独立的公开示例面。
生成的 public/ 与 resources/ 目录绝不是源文件。随主题内置的运行时、字体
家族与 Font Awesome 字形定义属于受支持的发行内容,并非待清理的死代码;
VENDOR.json 与 bin/check-vendor.py 固定其完整性。OINK 发布完整的受支持
Font Awesome 发行包,因为用户编写的内容可能使用主题模板本身没有引用的图标。
Font Awesome 官方编译 CSS 作为一份稳定、带指纹的 vendor 样式表发布,并排在由
主题与消费站 SCSS 编译出的指纹 main.css 之前。站点样式的普通修改不会再让图标
发行包失效,同时常规层叠顺序仍允许站点覆盖它。KaTeX、DocSearch、Swagger 与
Asciinema 等能力样式继续保持独立,只在实际使用时加载。内容指纹使不可变 URL 成为
可能;HTTP 缓存响应头属于部署宿主,而不是 Hugo 主题的职责。
Hugo 类型 docs、book、blog 与 swagger 选择阅读外壳;
params.ui.shell_types 可以增加类型。落地页使用 layout: landing。OINK 没有
article 类型或第二套博客外壳;沉浸式页面只是外壳契约
定义的一种博客展示方式。
layouts/_partials/shell/config.html 解析共享外壳事实。布局必须先通过
content/render.html 渲染,再执行 scripts.html,因为渲染钩子与 shortcode
会在 Page Store 中登记能力标志。覆盖时应选择范围最窄的 partial;若合并会改变
Hugo 的查找优先级,即使几个基础模板看起来相似,也应保持分离。
配置与诊断
主题策略位于 params.ui.*;comments.giscus、plantuml、drawio 等包含多项
设置的集成保留在顶层。布尔功能直接使用布尔值,除非它还包含多项设置。页面级
覆盖会去掉 ui. 前缀:params.ui.image_zoom 对应 image_zoom,front matter
中绝不嵌套 ui map。hugo.yaml 声明公开默认值;对应的解析器与检查器定义
任何可选配置的结构或范围。
无效输入遵循同一条规则:警告中写明输入值、允许的结构与安全回退,然后使用该
回退,或省略不安全的功能。普通构建与预览保持可用;告警和降级产物由对应检查器直接验证。主题绝不调用 errorf,check-params.py 会强制守住
这条边界。不要为无法到达的状态增加臆测式校验。
OINK 没有通用的键名重命名注册表。仍需给出迁移诊断的过渡,应在所属解析器中 添加针对性警告,并配严格的反向测试;已经移除的键绝不能作为兼容路径继续读取。
可能联网的功能必须显式启用,并以关闭方式降级。OINK 1.4.0 启用 PlantUML 且未配置端点时
使用本地渲染,显式 plantuml.svg_image_url 才选择服务端模式。Draw.io 需要
drawio.drawio_server,Algolia 需要
appId、apiKey 与 indexName;配置不完整时发出警告,而且不产生网络请求。
Draw.io 只在渲染内容含 PNG 或 SVG 候选图片时加载,并且每个不同的图片 URL
只检查一次。
图表端点必须是字符串,内容为带主机的 HTTP(S) URL 或同站路径。不支持的协议、
省略协议的 URL、反斜杠、空白字符及缺少路径的同站引用都会告警,并在选择运行时前关闭该集成。
plantuml: true 或未配置端点的 plantuml.enable: true 选择浏览器渲染。固定的两份本地
资源(@plantuml/core 1.2026.9 与 Viz.js 3.24.0)仅在包含 PlantUML 围栏的 HTML 页面
加载。普通构建和读者无需安装 Java、Graphviz 可执行程序或 npm 包,也不依赖 CDN 或
渲染服务。非空端点继续选择服务端模式,保留地址校验与 svg 行为;非法端点不回退到
本地模式。本地渲染不会下载可选库、主题、include 文件或外部资源。生成的 SVG 以 Blob
图片显示,不插入页面 DOM。输入错误或资源加载失败时保留可读源码;独立 Print、Markdown
与 RSS 保留源码,不加载运行时。
添加嵌入参数时保留编辑器 URL 原有的查询参数与片段。消息必须同时来自当前 编辑器 iframe 及其配置的源;无关或格式错误的消息会被忽略。图表数据只发送到 该源。导出结果必须是 PNG 或 SVG 的 data URL。
Mermaid 与本地 PlantUML 的小型调度器只出现在使用它们的 HTML 页面;大型引擎 在可见图表接近视口时加载。同页共享引擎,主线程串行执行,未引入 Worker、新依赖 或构建步骤。缺少 IntersectionObserver 时使用滚动与尺寸检查。普通浏览器打印不等待 异步渲染:已有图显示图片,其余保留源码。独立输出不变。
界面本地化
OINK 1.1.0 将原生界面语言包扩展到下方列出的完整语言集合;消费站点编写的正文 仍需自行翻译。
OINK 为
google/docsy@64f51c5
中现有的 31 个 locale 文件名提供原生界面文本,并额外保留通用 zh 作为简体中文
默认值:
这些语言包覆盖主题界面文案,站点正文仍由站点自行翻译。 Docsy 以后增加的 locale 不会自动成为 OINK 支持项;它必须先补齐完整的 OINK 词条,并接受与现有语言相同的审校。
i18n/en.yaml 管理消息 schema。OINK 的 32 份语言包都必须拥有完全相同
的消息集与原生界面文本;只有经过审查的产品名、标点、通行缩写或目标语言真实
同形词可以与英文保持相同,不再生成整段英文 fallback。zh 与 zh-cn 使用简体中文,
zh-tw 使用繁体中文。
当前 Hugo 基线允许通用 zh 语言与地区化中文语言包并存时使用 locale: zh,也可
选择 locale: zh-CN。两种配置都不改变语言包文件名 i18n/zh.yaml。
%s、{count}、{{ .Count }} 等运行时占位符可以移到符合目标语言语法的位置,
但字节内容必须保持不变。取值可以是字符串或 Hugo 复数消息映射。复数映射使用该
语言支持的类别(zero、one、two、few、many、other),必须包含 other,
每种形式都是包含相同占位符的字符串。语言包不得包含隐藏的双向文本控制符;
阿拉伯语、波斯语和希伯来语的方向仍由消费站点的语言设置(direction: rtl)
决定,不得把方向字符塞进译文。
bin/check-i18n.py 会检查 locale 集合、schema、取值类型、占位符、方向控制符,
以及少量已审查的英文本地同形词。因此增加可见字符串时,必须在同一变更中为每份
语言包提供译文,不能再运行 fallback 生成器。
特色图片
Hugo 的 images 是唯一的创作 API;params.images 只作为全站社交卡片回退。
| 来源 | 阅读列表缩略图 | 社交卡片 |
|---|---|---|
页面 images,或页面包中的 **featured*、*feature*、{*cover*,*thumbnail*} |
是 | 是 |
分区 cascade.images |
是 | 是 |
站点 params.images |
否 | 是 |
images: [] 会清除显式值或 cascade 继承值,但不会禁止发现页面包资源。只把解析
到的第一张图片作为代表图。Hugo 可以裁剪本地可处理的位图;SVG、static 与远程
资源仍然有效,只是不能执行 Hugo 图片操作。
featured-image-resolve.html 统一决定来源优先级与相对、绝对 URL。页面自己的
显式 images 优先于页面包资源,页面包资源优先于继承的 cascade 图片;显式值与
继承值恰好相同时也遵守此顺序。有源文件的页面通过原始 front matter 判断是否显式
声明,由 Hugo 解析 YAML、TOML 或 JSON;没有源文件的生成页面将解析后的 images
视为显式值。列表缩略图、Open Graph/Twitter/schema
帮助模板、作者头像、Pinterest 图片与博客展示都消费同一个决定。
params.ui.featured_image 只用于博客,默认值为 none;页面或 cascade 可用
front matter 覆盖。banner 在单页标题上方渲染图片,wash 用图片给页头着色,
hero 在单页与分区索引上把图片绘制为外壳背景。缺少图片或使用非 HTML 输出时
不渲染图片。
输出与运行时
每个基础模板都会设置 Page.Store.tdOutputFormat:
| 输出 | 契约 |
|---|---|
| HTML | 完整的语义内容;只为实际用到的能力加载本地运行时 |
| 展开的内容;不含外壳导航、搜索或图片缩放运行时;共享操作层仍支持明确的打印控制 | |
| Markdown / LLMS | 保持源 Markdown 形态,不含 td- 组件标记 |
| LLMSFULL | 按顶层 section 选择启用:每个启用 section、每种语言一份 llms-full.txt,按阅读顺序拼接同一份 Markdown |
| RSS | 安全的静态摘要,或明确省略 |
| NAVJSON | 按站点选择启用:每种语言一份 navigation.json,序列化侧栏与 pager 已经在读的导航权威 |
| BookManifest | 选择启用、供 EPUB/PDF 打包工具消费的有序 JSON 交接 |
各输出格式按既定顺序执行。一次渲染会在整个输出期间保持自己的可变格式状态;摘要
辅助逻辑不得把较早的格式值恢复到 RSS 渲染中。但在 Print 内,Hugo
可能并行渲染同一 Book 页面与相互重叠的聚合。因此每页由一个缓存 coordinator 按
固定顺序生成普通与整书两种变体,各调用方只选择自己需要的形态。普通 Print 保留
页面局部标题与带路由的 xref URL;Book 聚合保留带命名空间的标题与文档内 xref。
正文移入 Print 之前,相对链接按各自来源页面的 HTML URL 解析。单页和聚合输出
都保留语言与部署路径前缀、查询参数和编码路径。纯片段链接仍留在当前文档,交由
标题和脚注命名空间处理;规范路径与外部链接保持原目标。
只改写实际的 href 属性;title 等非链接属性内即使出现相同文字,也必须原样保留。
标题片段在命名空间匹配前只解码一次百分号编码,涵盖 Unicode 与编码后的 ASCII。
字面加号仍为加号,格式错误的转义保持原样;ID 与属性中的 HTML 实体单独解码。
片段规范化不会改动路径、查询参数或外部来源。
Tabs 及其他 Markdown shortcode 内渲染的标题使用相同的命名空间,
即使 Hugo 没有将它们纳入 Page.Fragments。
以实际渲染的标题 ID 为准,保留中文、字面加号和百分号序列,不从转义后的标题自链接反推 ID。
Landing 页面配置 RSS 输出后,通过共享 RSS 渲染器生成静态 feed。 声明的 alternate 输出必须存在实际文件;省略交互式 Landing 组件不等于省略 feed。
Print 页面在送往纸张之前也是可供浏览器阅读的文档。屏幕预览中的宽表格保留带名称、 可键盘聚焦的滚动区,不得撑宽整个页面;打印媒体样式在实际打印时展开这些区域。 表格无需加载运行时;RSS 表格仍是不可聚焦的静态内容。
站点自行选择是否启用自定义输出;OINK 不会强制生成昂贵的整书聚合。HTML 加载 共享操作层、核心层,以及由页面 flag 选择的稳定第一方能力分片。需要模板化的能力 每种语言至多发布一份;flag 只决定引用哪些 script tag,绝不再生成新的组合 bundle。 Print 保留操作层,并且只加载渲染打印功能所需的运行时。大型第三方 UMD 文件保持 独立;未使用的功能运行时不会出现。
顶层 section 在自己 _index front matter 的 outputs 中列出 LLMSFULL 才会启用它,
主题绝不替站点把它加进输出集合。逐页 Markdown 与全文包由同一个渲染器产出,因此全文包
就是那份语义 Markdown(同样不含 td- 组件标记)按侧栏与 pager 的阅读顺序拼接。在顶层
之下启用会告警且不产出任何文件,普通构建仍然可用,检查器直接验证告警与已禁用的输出。
站点在 outputs.home 中启用 NAVJSON,为每种语言在语言根下发布一份 navigation.json。
它序列化侧栏与 pager 所读的同一条权威链:存在显式 data/docs_nav.json 树时用它,否则用
带 weight 的内容树。数组顺序就是契约,weight 绝不序列化,该输出标记为 notAlternative。
schema/nav.v1.schema.json 为该格式提供版本,它是手写的契约产物,随模板与检查器一同修改,
不受生成式配置 Schema 漂移门禁管辖。两种输出默认关闭,都不启用的站点构建结果逐字节不变;
bin/check-agent-indexes.py 是它们的归属检查器。
只有 Book 根在 outputs 中明确列出 BookManifest 时才会生成它。它引用该 Book
既有的逐页 Markdown,并记录派生出的页面顺序、标题、编号目标与 xref;主题不会在
其中猜测出版元数据。
主题仓库提供 bin/book-epub.py 与 bin/book-pdf.py 作为显式出版步骤,并用
bin/check-book-epub.py 与 bin/check-book-pdf.py 承担产物门禁。EPUB 打包器组合
BookManifest 与同一份整书 Print HTML,消费站另行传入出版 metadata;PDF runner
只在临时回环地址提供该 Print 产物,通过 script-src 'none' 内容安全策略调用显式指定的
Chrome/Chromium 二进制,输出带 CSS 页码的 A4 页面。两种工具都会拒绝缺失资源或越出构建树的资源;网络资源
与覆盖已有输出分别需要独立的显式开关。网络 opt-in 只允许被动 HTTP(S) 媒体,远程脚本与
本地文件协议仍属非法。EPUB metadata 文件中的相对资源以该文件所在目录为基准,不依赖
调用者的工作目录。普通 Hugo 构建不会执行出版工作;PDF 仍从 Print 派生,而不是另一种
模板输出。
PDF 服务还会应用 CSP sandbox、拒绝 meta refresh 导航,并拒绝指向构建树以外的 符号链接。没有网络 opt-in 时,图片与媒体请求仅限回环同源地址和 data URL, CSS 或 SVG 发起的请求也受此限制。
性能规则如下:
- 若站点级资源或
partialCached结果可以承担工作,不要为每一页遍历.Site.Pages; .Content只渲染一次,完成后再读取 Page Store 标志;- 直接输出正确标记,不要扫描 DOM 后再修复;
- 浏览器工作按资源 URL 分组,而不是按 DOM 实例重复;
- 成本显著的普通输出应保持选择启用;
- 默认不输出 Speculation Rules:必须先由一个明确的生产消费站用可回滚的
moderate实验测量Sec-Purpose: prefetch请求、实际命中导航、传输字节与 CSP 影响; - 校验确实可达的作者输入,不校验假想的内部状态。
bin/measure-baseline.py 测量构建时间、输出体积、bundle 数量与 shortcode
密度。
单页 Print 只输出一次页面摘要。分区 Print 将所有纳入后代页面的数学能力标志 传递到聚合根,因此目录页本身没有公式时,仍会按子页需要加载本地 KaTeX 样式。 完全没有公式的打印文档不加载这些样式。
信任边界、CSS 与无障碍
作者可以启用 Goldmark unsafe,但配置与组件参数不能视作原始 HTML。共享属性
策略使用允许清单、校验 class token、放行 data-* 与 aria-*,并在丢弃
style、srcdoc、on*、保留属性与未知属性时发出警告。需要本地 URL 或明确
绝对 URL 时,URL 帮助模板会拒绝危险协议与协议相对 URL。公开 API 承诺支持的
远程 URL 仍然可用,但构建时绝不抓取它们。
普通图片、Gallery 及缩放原图的 URL 在校验后仍须按 HTML 属性转义。
引号与 & 保持为 URL 的内容,不得生成新属性或被当作 HTML 实体解码。
主题输出使用 td- class、data-td-* 属性与 --td-* 自定义属性;.steps、
.cards、.full-width、.wide、.expand 等作者标记保持无前缀。CSS 支持 RTL、打印、强制颜色、
减少动画、超长 token 与窄视口。主题拥有的装饰图标带 aria-hidden;只有包含
任务列表或原始 Font Awesome 元素的页面才加载作者内容无障碍修复。
阅读容器区分指针聚焦与键盘导航。由指针聚焦的 main 区域、表格滚动区或代码 pre
不会仅因读者随后按键而出现边框;Tab、失焦或新的非指针聚焦会清除这项豁免。普通控件
保留自己的焦点样式,滚动容器保留 tabindex,跳过导航的目标在标题附近显示局部边框,
不再包围整篇文章。强制颜色模式保留键盘提示,不使用全局焦点边框重置。
字体角色为 ui、body、heading、code、display、meta、brand 与 print,
通过 --td-*-font-family 暴露。ui 是主字体:body 经它解析,heading 又经
body 解析,因此赋一次值即同时移动界面、正文与标题。params.ui.typography
可取 technical 或 system;两者编译到同一份样式表,不加载运行时。旧
Bootstrap/Docsy Sass 变量继续为这些角色提供初值。
params.ui.fonts 让配置层触达同一组角色,供不愿挂载 SCSS 或新增样式表的站点
使用。它只写字体族名,绝不加载字体文件:所写字体族必须是读者已有的,或站点
自己用 @font-face 声明过的,这也让该键留在网络契约之外。取值只放行纯粹的
字体族语法,输出的 :root 块由匹配到的片段重新拼装;未知角色或不安全取值只
告警并单独丢弃。该块在样式表之后渲染,正是这一点让作者字体在同等优先级下压
过预设。外壳读站点的字体,不自带字体:Book 的编号与题注用正文字体,而非某种
技术字体。
强调色按角色拆开。强调文字(链接、外链、行内代码)跟随 Bootstrap 链接
族与 --bs-code-color,主题色永不重声明它们;行内代码随视觉预设变化:Slate 保留胭脂红明暗对,Paper 使用墨色文字与淡底,
使一页密集的标识符读成「代码与正文」而非「代码与链接」。强调底(选中行、
指针划过导航行时那层更灰的底、hover 淡铺、目录药丸与轨道光点、徽章 hover、
卡片 hover 时的外边、分享按钮 hover 时的实心底、文本选中、焦点环)跟随
--td-accent、--td-accent-rgb 与 --td-accent-hover,它们默认取链接族,也是
params.ui.theme_color 唯一注入的属性。属于外壳而非正文的文字同样跟随它们:
视口正停在其上的目录锚点、以及指针或键盘焦点落在其上的 Book 章节小标题,
按分区颜色点亮,而不是链接蓝。theme_color 与 theme_color_dark
取 #rgb/#rrggbb;front matter 与分区 cascade 覆盖站点值。未配置的站点不注入
任何内容。解析失败的值告警并保留默认配色。解析成功但在主题自身画布上低于 4.5:1
的颜色,带可抑制 id 告警并照常生效:该检查是建议性的,只有解析失败才丢弃颜色。
亮色是主键:没有有效 theme_color 的 theme_color_dark 告警并被忽略,一页要么
两种模式都着色,要么都不着色。省略暗色一半时,向白按 4% 步进提亮,直到在暗色画布
上达到 4.5:1。注入的每个字节
都由解析出的整数通道格式化,绝不来自作者文本。同一个解析器同时回答 head 注入块
与侧栏根切换器的「这一页是什么颜色」。
视觉预设
OINK 1.2.0 默认使用 Paper。params.ui.preset 接受 paper、slate,以及显式
选择的实验预设 ink、terminal;非法值与保留名称(folio、canvas)告警并回退到
paper。params.ui.preset_menu 默认 false;true 提供 Paper、Slate 与站点默认值,
列表指定可选项并可显式开启实验。列表必须包含站点默认值,缺失时告警并补入。不支持页面级预设。
Hugo 为所有文档根元素输出 data-td-preset 与 data-td-site-preset,包括 404
和打印输出。开启读者选择时,head 内联脚本在 CSS 加载前校验 td-preset。选择
带默认标记的预设会删除该存储键。非法存储值被清除;存储被禁用时,控件仍可在当前
页面使用,并显示无法持久化的提示。storage 事件同步标签页,td-preset-change
携带 {preset, previous, stored}。明暗状态独立使用 data-bs-theme、
td-color-theme 与 td-theme-change。浏览器栏颜色跟随实际明暗和预设。禁用
JavaScript 时显示站点默认的浅色预设;外观控件需要 JavaScript。
四套预设编入同一个样式表。Slate 保留 v1.1.0 的基础色板选择器与值。Paper 调整
配色和少量组件规则,不改变外壳列宽、断点或全局间距。Paper 深色块重声明浅色块的
每个色板 token,并覆盖嵌套深色区域。字体角色保持同等选择器优先级,顺序为预设、
typography: system、head 输出的 params.ui.fonts;站点的
_styles_project.scss 仍在最后。brand 单独控制字标。Paper 使用本地 IBM Plex Sans,
Slate 保留 Inter;两者的字标保留 Chakra Petch,代码保留 IBM Plex Mono。
系统排版模式不请求内置文字字体,除非站点显式覆盖角色。没有新增外部字体请求。
Ink 统一使用 Inter,配合红色标记、正文链接下划线、直角与无阴影。Terminal 使用
等宽界面和标题、Plex Sans 正文、青色链接、琥珀强调、2 px 圆角与无阴影;仅压紧
桌面导航,保留正文行长与手机触控目标。CSS 标题标记使用空的无障碍替代文字,
不支持的浏览器省略标记。显式 fonts.ui 仍控制主字体,除非设置了有效的
fonts.body。参见实验记录。
Paper 在浅色与深色模式下均不再绘制正文 h2 标题右侧的装饰横线。
标题的字体、间距、锚点与正常换行保持原样,仍支持站点自定义的标题边框。
Giscus 自动色板同时跟随风格与明暗;显式主题或浅深色样式表配置仍优先。打印使用 当前预设的浅色配色与白纸背景,即使屏幕为深色。Mermaid 与 ECharts 继续只跟随 明暗,API 组件保留供应商色板。参见已接受决策 与本地验收记录。
译文阅读运行时
唯一设置是布尔参数 params.ui.compare,默认 false,可按语言覆盖。无效值会警告并
保持关闭。Hugo 只从同样开启对照的语言中的真实译文页面推导候选,并输出少量描述数据。
入口脚本在点击后加载阅读器,阅读器使用两个同源 HTML frame,不生成语言组合或新的
内容输出。保留的宿主页使总数为三个活动文档。读者偏好(最后的目标语言与同步设置)存放在
localStorage 的 td-compare-reader 键中,读写均包在 try/catch 里;可恢复的会话(目标、
同步、可见窗格和语义书签)只存放在阅读器自身历史记录的 history.state.oinkCompareSession 中。
oink_compare_pane=1 仅在已打开阅读器拥有的 frame 中启用内部窗格状态。顶层访问
该 URL 仍显示普通页面。自定义脚本可以在 head hook 中读取 window.OinkComparePane。
窗格状态停用外壳运行时与快捷键、搜索初始化、评论和主题管理的统计,保留正文运行时与
样式。bin/check-compare.py 把每个浏览器运行时归类为窗格中停用、窗格中运行或仅限
宿主页,新增运行时必须声明它在窗格中的行为。开启对照后,生产构建通过主题自带的、
能识别窗格的模板渲染 Google Analytics,而不是 Hugo 内置 partial;因此站点覆盖的
google_analytics.html 不再生效。Frame 隔离文档 ID,同源内容仍属于受信站点内容。
窗格需要 frame-src 'self' 和同源嵌入。普通 canonical 链接继续标识原页面。Print
和机器输出不包含对照控件或对照运行时。
窗格以其启动脚本报告 DOMContentLoaded 为就绪,而不是等待所有图片和嵌入内容加载完成;
缓慢的资源不会推迟或销毁已经可读的窗格。无效文档在其 load 事件或 15 秒文档超时后
判定为失败。iframe 初始的 about:blank load 事件会被忽略,但超时时仍为空白的文档
会判定为失败。浏览器在就绪时测量几何位置,并在窗口尺寸、frame 的 load 事件、
字体、图片以及折叠项或组件布局变化(包括 td:tab-shown)后重新计算。用户输入决定当前活动窗格,程序驱动的跟随滚动不会夺取控制权。滚动更新
按动画帧合并。主动窗格连续滚动时,跟随窗格逐帧即时跟随,因为每帧重新开始平滑滚动会
让跟随窗格停滞;单次跳转在允许动画时平滑过渡,偏好减少动态效果或恢复阅读位置时直接
定位。关闭或替换窗格时会终止对应的事件监听器、观察器和加载超时计时器。原生
dialog 的 inert 机制将焦点限制在阅读器内,子文档键盘事件处理则保留窗格内组件弹窗
的交互。阅读器的历史记录在页面刷新或前进/后退恢复后仍然保留:入口脚本将恢复状态
绑定到该历史记录的 token,并在阅读器脚本加载完成后再次校验;若加载期间已离开该
记录,则取消恢复。在原记录上重新打开时保留既有 token 且不重复写入历史。历史记录
跳转会先处理显式关闭或用户排队触发的重开,再执行自动恢复。跨越窄屏断点迁移语言
菜单时,会先更新窗格可见性与不可交互状态,再恢复菜单的键盘焦点与展开状态。
发布状态
发布流程中应分别跟踪本地验证、模块打标、消费站版本更新与站点部署。
v1.5.1 审查修正
v1.5.1 继续使用 Hugo Extended 0.168.0 和 Go 1.27.3。 验证报告必须注明实际工具链和本地模块来源。
Feed 在应用 services.rss.limit 前按日期倒序排列,无日期条目省略 pubDate。
RSS 摘要和页面订阅源可能在 RSS 阶段共用页面 Store。摘要渲染保持该 Store 的 RSS
格式状态,不恢复陈旧的 HTML 状态,同时保留手工摘要分隔符的语义。
焦点指示器使用当前预设的不透明链接颜色。导航栏控件及会裁剪内容的代码块、 FAQ 容器保留可见的键盘焦点。强制颜色模式明确显示当前导航项和页脚字标。 标题及字段自链接保持键盘可达,打印链接保留下划线。本地 Bootstrap RTL 制品提供方向规则,但不替换主题的字体角色。正文列表间距不应用到组件列表。
配置 schema 接受 translation_notice 为 false 或非空语言代码字符串,
语言是否存在由所属解析器验证。分页和面包屑标签覆盖全部 32 种语言。
缩略图替代文本直接使用页面标题,不添加英文前缀。
7.2 - 组件契约
本契约描述 OINK v1.5.1。
教程与完整示例位于面向读者的组件专栏。本页定义这些 指南所依赖的 API 与行为。
创作模型
一个区块加属性便能表达组件时,使用普通 Markdown;需要复合正文或 Markdown 无法携带的事实时,使用 shortcode。OINK 没有并行的组件注册表。原生形态要求:
只有 {{%/* steps */%}} 使用百分号分隔符,因为它的正文属于页面大纲;其它
shortcode 一律使用尖括号分隔符。复合正文通过 content/render-block.html
处理,并使用唯一的 ID 作用域。Shortcode 与组件参数中的 caption、label、title
和 name 是纯文本,Markdown 应放在正文里。落地页叙述字段遵循自己的契约。图标
由一对 Font Awesome class 表示。组件暴露安全的 class 与属性,不接受任意颜色
或内联样式。
公共 API
OINK 有 29 个 shortcode:
- 核心:
tabs、tab、steps、cards、card、fields、field、include、kbd、badge、param、comment、contributors、asciinema; - Book:
fig、tbl、eq、eg、xref、book-toc、book-figures、book-tables、book-equations、book-examples; - 发布:
release-card、release-assets、download; - OpenAPI:
swagger、redoc。
| 组件 | 原生形态 | Shortcode 形态 | HTML 运行时 |
|---|---|---|---|
| 提示块 | > [!TYPE]、折叠、{icon=} |
无 | 无 |
| 标签页 | 相邻围栏或表格加 {tab= group= value=} |
tabs / tab |
只在使用页加载 tabs |
| 步骤 | 有序列表加 {.steps} |
steps |
无 |
| 卡片 | 链接列表加 {.cards} |
cards / card |
无 |
| 参数表 | 表格加 {.fields} |
fields / field |
无 |
| FileTree | filetree 数据围栏 |
无 | 只有注释存在时加载分隔条运行时 |
| 画廊 | gallery 数据围栏 |
无 | 符合条件时共享图片缩放 |
| 图片 | Markdown 图片加块属性 | 无 | 符合条件时加载图片缩放 |
| 表格 | 属性、caption、编号或标签页 | 复合 Book 表格使用 tbl |
标签页表格加载 tabs;.expand 启用阅读器 |
| Book 目标 | 图片、表格、passthrough、围栏加 {num=} |
fig、tbl、eq、eg |
无 |
| 发布资产 | checksums 数据围栏 |
release-assets |
HTML 中加载复制功能 |
| 数学与化学公式 | passthrough、math、chem 围栏 |
eq |
无;构建期渲染并加载本地样式 |
| 图表与数据 | mermaid、plantuml、markmap、echarts、infographic 围栏 |
无 | 只加载选中的本地运行时 |
Mermaid 与本地 PlantUML 共用查看器,支持平移、缩放、适应窗口、源码查看与 复制、独立 SVG 下载。只有距离视口不超过 320px 且尺寸可用的图表才加载引擎 并渲染。引擎各加载一次,在主线程串行执行;隐藏内容恢复后再处理,主题变化 丢弃过期结果。查看器复用渲染结果,保留阅读位置,并支持触控、键盘、焦点恢复 与减少动态效果偏好。PlantUML 保持 Blob 图片隔离,SVG 标记不进入页面 DOM。 源码只经过一次 HTML 转义;箭头、引号、和号及中文保持准确。错误保留源码, 不阻塞后续图表。复制或导出失败有明确提示;下载仅报告请求已发起。 导出内含样式,不允许外部资源依赖。普通浏览器打印保留浅色 Mermaid 与 PlantUML 图形;暗色 Mermaid 和未完成渲染的图表使用黑字白底、可换行的源码,无须在打印 时异步重绘。打印后恢复源码折叠状态,保留已有图形与阅读偏好。 独立 Print、Markdown、RSS 保留源码回退。详见 Mermaid 与 PlantUML。显式服务端模式保持既有行为。
可选组件呈现
OINK 1.4.0 支持以下可选呈现变体。示例见组件呈现。
| 组件 | 原生修饰类 | 完整形式 |
|---|---|---|
| 分隔卡片列表 | {.cards .td-cards-list} |
cards variant="list" |
| 紧凑步骤 | {.steps .td-steps-compact} |
百分号分隔的 steps variant="compact" |
| 命令条 | 带 {class="td-code-command"} 的单行围栏 |
沿用代码渲染器,没有新增短代码 |
未知 variant 值告警并回退到默认布局。命令条遇到多行、行号(包括站点默认行号)或折叠设置时,告警并使用普通代码块,保留所有源码。现有 copy 与 wrap 政策继续适用。不引入新运行时或组件注册表。
原生与完整卡片共用表面和标题规则。只有带链接的卡片显示悬停强调;正文中的独立链接保持可用,不被标题的整卡链接覆盖。标签页的单个标签平时背景透明,支持悬停的设备上才显示淡底色,选中项以细下划线和链接色标识。文字在不同状态间保持相同字重和位置,键盘焦点另有清晰轮廓。原生折叠块保留独立操作的摘要行。复制控件保持点击区域尺寸和既有成功/失败语义。粗指针设备上的复制和标签页控件高度至少为 44px。
预设颜色与字体角色继续生效。Ink/Terminal 保持平面表面以及直角/2px 圆角。打印、RSS、Markdown、LLMS 保留内容和既有输出语义,呈现变体不隐藏内容,也不向静态输出添加操作控件。
参数名在四种预设中均使用无边框的加重等宽文字,Terminal 也遵循此规则;原生 Markdown 表格与完整短代码保持一致。
校验
无效的作者输入遵循架构契约:发出警告,使用文档
规定的安全回退或省略组件。命名参数与位置参数不能混用。Book 目标 ID 匹配
[A-Za-z][A-Za-z0-9_.:-]*,Book 编号匹配 [0-9A-Za-z.-]+,class 必须通过
token 校验。渲染钩子与 shortcode 目标共享同一个页面注册表,因此冲突不会生成
重复的输出 ID。
URL 使用 content/url.html;不允许原始反斜杠,因为浏览器可能将其解释为 URL
分隔符。图片依次从页面资源、分区资源、全局 assets、static
或显式远程 URL 中解析。本地位图带固有尺寸;SVG、static 与远程来源仍然有效,
但不能执行 Hugo 图片操作。
资源元数据 alt 必须是字符串;无效值会告警并被忽略,保留正文填写的替代文本。
空的 tabs 会告警,在 HTML、Print、Markdown 与 RSS 中都不输出容器。
xref 没有可用目标或找不到目标页面时,保留文字但不生成链接。
fig 同时填写 caption 与 title 时会告警,所有输出都优先使用 caption。
块公式上的通用属性不会启用 Book 编号;没有 num 时,id 与 caption
会告警并被忽略,公式仍按无编号形态渲染。
组件行为
提示块与标签页
提示块类型包括 note、tip、important、warning、caution、success、
danger、question、example、quote 与 details;- 表示初始折叠,+
表示初始展开。未知类型渲染为保留原标记的普通块引用,不依赖 JavaScript。
只有连续且区块类型相同的相邻标签页才会分组。group 启用
#<group>-<value> hash 与 td-tabs:v1:<group> 存储键;未分组标签页两者都不用。
OINK 1.4.0 中,初始片段或 hashchange 指向面板或面板内标题时,会先由外
至内展开所有祖先面板,再滚动到目标。片段选择优先于已保存的偏好,并同步同名组,
但不改写存储;只有读者主动选择才持久化。标签页外的普通标题仍使用原生跳转。
HTML 在 JavaScript 运行前暴露所有面板,打印输出展开面板,Markdown 保留作者
源文,RSS 接收渲染后的文本摘要。完整形态支持任意 Markdown;tab.label 必填,
父级存在 group 时 value 才严格必填,孤立的 tab 会警告且不渲染。
HTML 与单页 Print 保持分组标签页的 ID 和本地链接不变;同页重复分组与嵌套标签页 容器的 ID 保持唯一。多页分区 Print 与整书 Print 给主题生成的标签页容器、面板 ID 加上来源页面的命名空间,并同步改写本地 链接。不同章节复用同一 group 时,链接仍须指向本章面板;指向普通 HTML 的跨页 链接保留原目标。 片段链接改写支持正常的 HTML 属性语法,并保留其他属性内的原始文本。
步骤、卡片、参数表与表格
原生步骤接受普通区块内容。只有某一步必须包含百分号容器时才使用 shortcode。
原生卡片是链接列表;完整形态增加正文、徽章、图标与图片。原生参数表把第一列
映射为名称、最后一列映射为描述,中间列由 meta= 或表头映射;完整形态允许
区块描述。card 与 field 只能放在各自的父容器中。
参数锚点为 field-<name>,名称转小写,连续标点折叠为连字符,因此
params.ui.typography 变成 field-params-ui-typography。重复锚点追加位置后缀。
表格渲染钩子负责响应式包装与 caption。标题在框内保留均衡留白,与单元格文字对齐;
正文中存在角落入口时,标题末尾为按钮预留空间。.matrix 把第一列变为行表头;
.full-width 加宽外层视口以占满文章可用宽度。在屏幕显示中,.wide
让技术表格按内容尺寸排版,在容器内横向滚动;单元格使用 overflow-wrap: break-word
与 word-break: normal 保留标识符,正文单元格以 max-width: 40ch 作为软宽度约束,
让长描述适当换行。普通表格保持现有布局。该标记不影响定义列表形式的 .fields,
也不影响静态与打印输出。.fields 不能与 matrix、full-width、编号或标签页组合;
编号与标签页也互斥。
.wide这个简短的作者标记随 v1.4.0 发布。
它沿用 1.3.1 引入的技术宽表布局,继续兼容已有写法,可以与 .matrix、
.full-width 组合;Markdown 保留作者原本使用的标记。
表格阅读
.expand为主题渲染的 Markdown 表格显式启用,独立于 .wide。
只有 .expand 表格才生成 HTML 阅读容器并加载 table-reader.js。
运行时在右上角添加直接展开入口,悬停或键盘聚焦时显示,触屏和强制颜色模式常显。
不增加表格上方工具栏、全站开关、自动大小检测或整表点击事件。
.fields .expand 会告警并忽略展开;其他布局标记、标题、编号和标签页可正常组合。
开启 Goldmark 块属性后,{.wide .expand} 与 {class="wide expand"} 均可使用,
属性行与表格之间不能留空行。
一个原生模态对话框移动原表格与滚动容器,保留 ID、链接、选择、字号、行顺序和原生语义。
初始焦点位于标题,Tab 保持在对话框内;Escape 和关闭按钮还原原节点、表格与页面滚动位置,
并将焦点返回入口。协调器关闭时不抢回焦点。普通 caption 随表格移动,编号标题用于命名
对话框,并在打开期间为原本无名称的表格提供名称。单行表头在视口内吸附;
.matrix 保留原有行表头和固定首列行为。不增加排序、筛选、密度、导出或虚拟滚动控制。
缺少 JavaScript、资源被阻止或对话框不受支持时,保留可读正文且不出现无效入口。 引用移动子树外节点的表格保持正文阅读;指向表格外目标的同页链接先还原表格再导航。 浏览器打印前同步还原。专用 Print、RSS、Markdown 与 LLMS 保留原有内容边界, 不增加阅读器控件或运行时。接受的决策记录设计理由。
图片、画廊、FileTree 与围栏
Markdown 图片钩子是普通图片 API。行内图片保持行内;块图片带 caption 或 num
时变为 figure。图片处理只属于这一原生形态:完整 fig 源形态是编号容器,其参数表
刻意不含 command/options,需要处理的编号图片写成带 num 的原生块图片。
允许的图片属性包括 id、num、caption、width、height、
link、command 与 options,以及共享安全属性。command 与 options 必须同时
出现,并对可处理的本地资源调用 Hugo Fit、Resize、Fill 或 Crop。普通
链接图片使用 Markdown 语法,因此 link 属性要求同时有 caption 或编号。链接
图片与装饰图片不加载缩放。
缩放按钮通过 ARIA 无障碍名称保留图片的 alt 与本地化预览操作,不向正文插入辅助
文字。复制纯文本或富文本 HTML 时,即使编辑器移除主题样式,也不得额外带入
预览提示;作者原有的图片与图注保持不变。资源 metadata 中的 alt 必须是字符串;
无效值会告警并被忽略,保留正文中编写的图片 alt。
Draw.io 与图片缩放共用一张图片时,编辑与缩放是同级的独立按钮。编辑入口支持
键盘访问,并在触摸设备和强制颜色模式下保持可见。
Draw.io 在模态对话框中打开,外层始终提供本地化的关闭按钮。关闭按钮、遮罩、编辑器
退出消息,以及焦点位于外层对话框时的 Escape 均可关闭编辑器、清理消息监听器并把
焦点归还编辑按钮。跨域 iframe 内的按键归编辑器所有,不能冒泡至外层;用户可沿
编辑器的 Tab 顺序移出 iframe,访问外层关闭按钮。重新打开时创建新的编辑会话。
画廊每行接受一张 Markdown 图片,可带描述、链接与 class。FileTree 接受缩进、
- name、可选 /、注释,以及经过校验的 icon、tone、open、type 属性。
FileTree 围栏参数 wrap 接受 true 或 false,默认 false;设置为 true 时,
屏幕上的长注释和无空格标识符在列内换行,行高从现有最小高度随内容增长,文件名
仍使用省略号。省略参数或设为 false 时保留桌面与平板的紧凑行;移动端堆叠、
分隔条操作、目录折叠和打印展开保持现有行为。无效值告警并回退到 false。Markdown 保留显式 wrap 参数和文件树源文;打印输出渲染
展开的静态图片与文件树。
所有代码高亮都使用 Chroma。通用围栏属性包括 title、copy、wrap、
collapse、label、id、行选项、标签页,以及 Book 的 num/caption。复制
操作返回作者源文。Mermaid 色板按明暗切换,与视觉预设独立;默认深色连线标签
背景使用 #404040,使标签文字达到 AA 对比度。显式
params.mermaid.themeVariables 配置仍然优先。
ECharts 输入是声明式 JSON/YAML;回调使用
window.OinkEchartsFunctions 中的 $fn:<name>,绝不执行嵌入脚本。
OINK 1.4.0 中,Infographic SVG 跟随容器宽度缩放,覆盖横竖屏及外壳布局
变化,无须重复渲染 DSL。初始隐藏的容器等到可测量时再渲染;围栏 height 与
full 的含义保持不变。
透明的 Infographic 产物默认使用白色底面;显式 DSL 背景优先设置该底面。
站点明暗切换保留作者的 light、dark、hand-drawn、色板和 SVG 样式,
使默认深色文字保持可读,无须重新着色。作者明确选择透明背景或自定义文字颜色时,
仍须自行检查对比度。
Markmap 节点文字在屏幕显示下通过 --markmap-text-color 继承页面正文颜色
(--bs-body-color),在明暗模式切换、预设风格变更或恢复用户偏好时均可实时同步,
确保足够的对比度;打印时则保留默认深色文字,以适配白底纸张。该映射作用于正文
容器及生成的 SVG,站点仍可通过同优先级的自定义 CSS 或 SVG 内联属性进行覆盖。
(OINK 1.3.1 新增。)
OINK 1.4.0 中,Markmap 在首次可测量显示和容器尺寸变化时适配,也覆盖 隐藏面板展开的情况。读者平移或缩放后暂停自动适配,布局变化保留当前视图。 可用键盘操作的 适应窗口 按钮恢复完整导图并重新启用自动适配。布局适配不加 动画,减少动态效果偏好也会关闭节点过渡。打印时隐藏该按钮。解析前会移除首尾 空行和共同缩进,保留 YAML 配置与大纲层级。
数学公式使用 Hugo 构建时生成的 KaTeX 产物和本地 CSS,不加载浏览器数学运行时。 HTML 和 Print 使用本地样式表,并保留 MathML 与作者的 TeX 源文。 数学公式按架构契约中的当前 Hugo 基线验证。Markmap 使用与样式配套的本地 KaTeX 运行时。 OINK 1.4.0 按阅读列的实际可用宽度为编号公式保留阅读空间,也覆盖侧栏 或目录挤窄的正文列。标题无法与公式并排时移到下一行,连续长词允许换行。 仅含编号的标题、MathML 与 TeX 仍然可用。打印时公式独占阅读列全宽,标题排列在 公式下方。长标题既不得把公式挤到零宽,也不得撑宽整页。
Swagger 与 Redoc 接受 HTTP(S) 规范 URL 或以 static/ 为根的路径,都不解析页面
资源。Redoc 将开头有无斜杠视为等价,并把本地路径与 baseURL 拼接。只有 HTML
输出可交互;Print、Markdown 与 RSS 输出静态规范链接。
Book
book 类型扩展 docs 外壳,并遵循内容树或 data/docs_nav.json。book_number、
book_part、book_kind 与 book_status 是展示元数据,不改变 Hugo 发布状态。
带编号的类型为 fig、tbl、eq 与 eg,默认 ID 是 <kind>-<num>。eg
需要 caption;不带 num 的 eq 是无编号展示公式。xref 要么准确指定一种类型
并可附带 page/anchor,要么指定一个 anchor 和显式文字。带编号的示例是一个
完整的边框正文与 caption。
脚注属于页面文档。原生编号表格与围栏会让脚注留在页面里。Shortcode 正文是独立
的 Goldmark 文档,因此 tbl、eg、fig、card、tab、field 或 include
中的脚注引用会警告并保持字面形式;该检查忽略代码形态的文本。
book-toc 按 1–3 层导航顺序生成目录;四个 book-* 索引各自收集一种目标。
单页 Print 与普通 HTML 保持完全相同的普通标题、脚注与标签页 ID。只有多页分区
Print 与整书 Print 会改写跨页链接,并给页面局部标题、脚注和主题生成的标签页 ID 增加命名
空间,避免聚合后冲突。本地标题链接在匹配前按 URL 规则解码一次片段,因此指向
Unicode 标题的普通 Markdown 链接在聚合后仍能到达原目标。显式目标 ID 保持不变。
消费站点自行选择是否启用这些潜在成本较高的聚合输出。消费站手写的 HTML 锚点和
自定义短代码 ID 默认仍是全局 ID。给带 id 的元素增加布尔属性
data-td-print-local,可将其声明为页内锚点。聚合输出会给该 ID 和同页链接增加
命名空间,普通 HTML 与单页 Print 保留原 ID。旧别名和自定义短代码标题因此可保留
原有深链接,同时避免跨章冲突;未标记的作者 ID 必须在聚合文档中保持唯一。
发布与下载
发布 front matter 使用一个
https://github.com/<owner>/<repo>/releases/tag/<tag> 形态的 release_url;owner、
项目与 tag 来自 URL,日期来自页面。构建不会抓取远程发布状态。已经移除的
release map、release_products 与 release_group_by_product 会警告并给出
替代项,它们不是兼容路径。分区索引列出所有页面;能解析时使用 project tag,
否则使用页面标题。
使用 layout: releases 时,HTML 保持发布索引;分区 Print 聚合完整发布正文,并使用
章节局部 ID。开发与生产构建遵循相同行为。
校验和可以接受规范行,也可以接受一个源资源,两者不能同时提供;文件名不能是 路径。HTML 增加本地复制功能,静态输出暴露完整 hash。
下载使用 data/download/<key>.yaml。channel 可取 rolling 或 pinned;只有
pinned URL 与命令会插值 ${version} 和 ${tag}。发布前,rolling channel 保持
可用,pinned channel 显示 pending。Markdown 渲染完整 channel 列表;RSS 省略
该组件。
切换标签页后,重新测量新显示面板中的可折叠代码块;字体加载完成后或没有
ResizeObserver 时,展开和收起控件仍须可用。
验证
共享输出规则见架构契约,例外随各组件定义。 Markdown 与 RSS 不设置浏览器运行时标志;Print 只保留渲染打印功能需要的标志。 源码检查覆盖参数、渲染钩子策略、运行时隔离与迁移;输出检查比较 HTML、Print、 Markdown、RSS 与 LLMS golden;浏览器测试覆盖交互界面。迁移行为见 迁移边界。
v1.5.1 审查修正
以下修正已随 v1.5.1 发布,并属于当前消费站契约。
- 只接受命名参数的短代码遇到位置参数时警告并继续普通构建。无效输入检查必须 验证构建成功及安全输出,不再同时接受成功与失败退出码。
- Book 编号无效或缺失时保留可读的无编号内容,不生成空的
fig-、tbl-、eg-、eq-目标,也不注册到图表索引中。 - 自动 ID 包含完整短代码祖先序号。独立渲染的 Markdown 正文为代码块、图表、 FileTree 和字段的自动 ID 及引用加入作用域;作者指定的代码块 ID 保持原样。
- RSS 为 Book 图片、表格、公式、示例、链接、贡献者、接口规范链接、录屏链接和 校验和表格输出静态 HTML,不泄漏对应的 Markdown 源码或浏览器控件。 Release assets 仍按既有约定在 RSS 中省略。RSS 检查使用普通分区 feed。
- 复制反馈使用
aria-disabled配合操作守卫,在阻止重复写入剪贴板时保留键盘焦点。
7.3 - 外壳与导航契约
本契约描述 OINK v1.5.1。
权威来源与导航
| 关注点 | 权威来源 |
|---|---|
| 全局导航 | Hugo menus.main |
| Docs / Book 侧栏与翻页 | 内容树或 data/docs_nav.json |
| 根栏目切换器 | 解析后的顶层内容根 |
| 内容发现 | 各语言的本地搜索索引 |
| 页面与命令面板操作 | 共享操作注册表 |
任何功能都不能引入另一套菜单或页面树。菜单只允许一层子项交互;更深层级会警告,
并平铺到带链接的分组标题下。外部链接使用
target="_blank" rel="noopener noreferrer";内部链接保持语言与子路径感知。
顶部导航栏的桌面视图与抽屉视图投影同一棵树,每个下拉面板都是一列宽度适中的
“图标 + 标题"行——mega 面板与其 columns 菜单参数已退役,配置 columns
会发出警告并保持单列。菜单描述只是配置数据,不再渲染。链接树在空间足够时保持居中;
手机宽度先为品牌与工具按钮保留实际宽度,避免较长菜单与搜索按钮重叠。
lg 以上是文字链接,之下收缩为图标链接。lg 与 md 之间,右端保留搜索、版本、
语言、主题与 GitHub,没有菜单按钮;md 以下外观入口保留在搜索与抽屉入口旁,版本、语言与 GitHub 移入底栏工具组,此时首页
与显式 Landing 页在搜索旁增加一枚抽屉入口,展开完整的带标签菜单树。带侧栏的外壳页面
在这个位置打开自己的侧栏抽屉。显式 Landing 布局只使用 Landing 抽屉,即使内容类型
为 Book 或 Docs;同一页不会同时输出两个抽屉按钮。导航栏负责加载抽屉脚本,即使
站点自定义首页或 Landing 正文、没有调用主题的区块渲染器,菜单仍可操作。
从 md 起均不显示抽屉按钮。
主题自带的 404 页面没有阅读侧栏;即使根级 cascade 将其类型设为 Docs 或 Book,
也不会显示侧栏抽屉入口。把自定义类型加入 shell_types 只声明其外壳能力,
不会在自定义模板中创建侧栏。没有侧栏的全宽定制页应设置 sidebar_enabled: false。
语言链接指向页面译文,缺少译文时
指向对应语言首页;多个语言共享主机与 base path 时保持相对链接,只有语言拥有
独立 baseURL 时才变成绝对链接;hreflang 始终使用绝对链接,而且只列出真实译文,
不把切换器提供的语言首页回退当成译文。博客分页使用每一页自己的 canonical URL;
后续分页不输出跨语言 alternate,因为不同语言归档的分页边界未必相同。
navbar_autohide 从 768px 起只对精细指针生效,绝不作用于触控或抽屉宽度;
隐藏的导航栏不交还占位:两种状态下布局都保留导航栏横带,固定顶栏正好占满这条
横带、下边框画在带内,显现时原地淡入、不遮挡静止内容,hero 页面忽略该策略、
保留自己的叠加导航栏。首页与 hero 页面共用同一套柔和边界:导航栏不画下边框、
滚动时不投阴影,改由栏下一小段渐隐过渡收束边缘。
侧栏与翻页共享同一个根和顺序。manual_link、build.render: link、分隔行、
隐藏节点与占位节点保留各自已定义的语义。sidebar_icon_policy 可取默认的 all、
groups 或 none;图标是一对 Font Awesome class。无效策略遵循共享的警告与
回退契约。达到 sidebar_cache_limit 后,两种 walker 只有在语言、导航根与实际
影响输出的有效设置均相同时才复用中性标记。没有 JavaScript 时这份标记仍然可见;
普通外壳运行时只补上 active 路径。会输出 sidebar_headings 的 Book 页面保持页面
专属,并绕过共享树缓存。
根候选先收集可链接、非分隔项的顶层分区,再收集 sidebar_root_for: self 分区,并按 URL 去重。
两种来源都遵守显式的 sidebar_root_menu: false;未设置或 true 时保留。
当前解析出的根即使被排除在全站候选之外,仍会追加。零入口不输出控件,单入口
输出静态链接;所有 URL 保留语言和部署路径前缀。分隔项和使用 build.render: never
的分区不会成为切换器链接。
sidebar_divider 叶子保持静态标题;分区节点在两套侧栏遍历器中保留子项,标题不跳转,
启用折叠时提供真正的展开按钮。分组自身不进入翻页序列,子页保持原有顺序。配合
build.render: never 可省略分区自己的输出而不隐藏后代。面包屑显示不带链接的标题,
搜索跳过分组,导航 JSON 提升其子节点,Print 保留子文档。Book 目录保留分组标题和子项链接,
但跳过未发布的分组正文标题。toc_hide 仍隐藏整棵子树,
不能代替分组。显式导航键使用不含语言和部署前缀的路径,实际链接则保留这两个前缀。
只有以分区为根的 Docs/Book 导航才求值显式树。以首页为根的导航和其他外壳忽略
该数据及其警告。sections 数组为空时,仅在显式树适用的范围内警告并回退到内容树。
未缓存的显式侧栏根据解析后的页面身份标记当前页,并展开清单中定义的导航祖先;
节点省略 url 或页面使用自定义 URL 时仍然有效。当前页未列入清单时,树中的物理
祖先仍会展开。旧 URL 与激活路径提示仍受支持。
侧栏、翻页与导航 JSON 根据 page 解析出的页面身份选择显式分区。节点属于当前根,
条件是它就是该根、位于该根的内容后代中,或是解析到同一个侧栏根的 Docs/Book 页面。
因此手册文件可以保留在内容根目录,由 Docs 分区负责导航。选中节点的 children
定义导航层级,无须对应物理内容层级。自定义 url 不改变归属。Docs 的树不接管
独立的 Book 根,反之亦然;嵌套侧栏根只选择自己的分支。显式树未包含的根继续使用内容树。
两种权威来源都剪掉 toc_hide 子树;导航 JSON 将 manual_link_relref 保留为指向
解析目标的内部链接,不把占位页自身当成页面身份。
Book 标题与响应式目录
params.ui.sidebar_headings 默认为 false。Book 左侧显示页面树,右侧显示当前页
目录。设置 sidebar_headings: true(仅 h2)或 2、3、4(最深标题层级),才会
在当前 Book 导航项下加入页内标题。页面 front matter 可覆盖分区 cascade 或站点
设置;显式 false 可关闭继承的启用值。这个开关不影响右侧目录,也不影响它在
xl 以下移入侧栏。
文档站的 Book 不再默认启用左侧页内标题。启用时,标题分支继承父导航项的深度, 为图标与章节编号预留位置,再增加一级缩进;更深的标题继续缩进。 从右向左的布局使用相同的逻辑间距。
从 1200px 起,固定右栏按视口宽度的 20% 伸展,限制在 286–420px;flow 右栏按
22% 伸展,限制在 340–440px,仍接受 --td-shell-toc-flow-w 覆盖。
栏位占宽与可见面板共用同一宽度,直接跟随视口,不对宽度做过渡动画;
连续缩小窗口时,正文不能在拖动间隙反向变宽。长标题在栏内换行,变宽后可自然收为一行。
低于 1200px 时,目录分组移入已启用的左侧栏;低于 768px 时,位于手机抽屉内。
设置 sidebar_enabled: false 的页面在 1200px 以下隐藏目录栏。浏览器缩放遵循
这些 CSS 视口断点。桌面上折叠右栏会立即释放其全部占宽,面板保留滑动与淡出动画。
侧栏运行时
设置 sidebar_menu_foldable: true 时,各分支控制其子项的显示。
sidebar_menu_compact 不得永久隐藏已展开分支中的叶子条目。
这适用于 Docs 和 Book 的缓存树及逐页渲染树;不可折叠的树保留 compact 筛选。
仍在使用 OINK 1.2.0 的站点可能在展开后隐藏深层叶子。升级采用本修复前,
站点可设置 sidebar_menu_compact: false,同时保留折叠功能。
OINK 1.1.0 提供本节的展开状态 API 与隐藏内容隔离;1.0.0 不提供该 API。
window.OinkSidebar 管理已注册的树分支及可搬迁的 TOC、反向链接和分类法分组,
不依赖它们当前的 DOM 父节点。setExpanded(id, boolean, {source}) 对有效目标返回
true,对未知 ID 或非布尔值返回 false。getState(id) 返回新的 {id, expanded} 快照
或 null。ID 使用现有 aria-controls 指定的受控区域 ID,不能修改注册范围之外的元素。
来源为 user、active-path、responsive 和默认的 api。所有写入先提交
aria-expanded、td-is-open、本地化标签和区域的 inert 状态,再向 document 发送一次
oink:sidebar-disclosure 事件,详情为 {id, expanded, source}。重复写入相同状态不发
事件。API 恢复时保留当前路径祖先展开,用户仍可主动折叠这些分支。关闭含焦点的区域时,
先把焦点归还给展开按钮,再执行隔离。
ready 是初始活动路径补全和响应式搬迁完成后解析为 API 的 Promise;isReady 和
oink:sidebar-ready 也提供可供晚加载消费者检查的完成状态。可选的持久化由站点负责:
等待 ready,在 try/catch 中读取存储,再通过 setter 恢复有效 ID。OINK 自身保存整栏
折叠、宽度和滚动位置,不定义读者分支选择的版本/语言存储格式。
桌面折叠和移动抽屉关闭时,面板内容设为 inert,面板标记 aria-hidden。先移出焦点再
隔离,打开时先解除隔离再聚焦。面板本身继续充当 16px 指针感应区,外部恢复按钮保持
可用。悬浮、Escape、遮罩关闭、断点清理和滚动解锁保留原有行为。这些运行时属性不进入
无 JavaScript 的服务端回退标记。
TOC 整栏折叠时也隔离其隐藏面板。如果焦点原本位于折叠按钮,先移至可见的浮动恢复按钮; 恢复整栏后,焦点回到栏内可见的控制按钮。右侧内容搬入移动侧栏时,先清除原先的整栏 隔离,再由抽屉管理交互。抽屉的 Tab 循环只包括可见、非 inert 的控件,排除隐藏或 折叠区域中的后代。
沉浸式博客展示
OINK 没有 article 类型或第二套外壳。沉浸式阅读由普通博客外壳上的四个独立键 组成,可设在页面或分区 cascade 上;分区索引会重复它自己也需要的值:
博客外壳默认不渲染面包屑导航——文章应作为独立作品阅读——所以这份配置不需要
相应的键。breadcrumb 仍是普通键,页面或 cascade 可以在任何外壳上明确打开
或关闭它。
hero 在单页与分区索引上把共享特色图片用作装饰性的全出血背景。没有图片时
渲染普通开场;banner 与 wash 仍只用于单页。顶部导航栏以对比遮罩叠在 hero
上,并随页面一起滚动。
toc_style 可取 fixed 或 flow;flow 在文章旁放置更宽的导轨,并且只在滚动
之后固定。它的静止位置与文章信息行对齐;页面没有信息行时,与描述对齐。标题
换行数无法预知,因此由 docs-shell.js 测量偏移;没有 JavaScript 时,导轨从
文章起点开始。toc_taxonomies: false 移除术语云;导轨既无 TOC 又无术语云时
完全不渲染。notoc 仍是页面级 TOC 退出键。这些开关不改变署名、标签、系列、
翻页顺序、feed 或页尾组合;导轨在 xl 断点以下消失。
在页面向下滚动且页脚进入视口时,固定 TOC 导轨(fixed)会共享侧边栏的页脚可见
偏移量(--td-shell-footer-offset),使其底部可滚动区域止于页脚上方,确保
导轨底部的分类标签或末尾标题始终可通过滚轮与键盘访问;页脚尺寸或正文高度变化时(包括标签页、
博客视图切换和媒体加载),无需滚动即可更新避让边界。即使在禁用侧边栏(sidebar_enabled: false)的单栏沉浸
阅读中,该避让机制依然生效。页面无页脚时偏移量为 0;而 toc_style: flow 则依托
自身的 sticky 边界停止于正文末尾。移动端抽屉与打印输出均不受影响。(OINK 1.3.1 新增。)
搜索、操作与运行时
params.offline_search 选择启用各语言的本地索引。启用后默认也在 hugo server
期间构建;大型编辑循环可以设置 offline_search_on_serve: false。HTML 搜索出现
在首页、外壳页面,以及启用 landing_search 的落地页上。其它非外壳页面与 Print
不包含对话框、Lunr 或命令面板。
搜索元数据包括 search_keywords、默认值为 1 的 search_boost,以及
search_exclude。索引携带 URL、标题、分类法、摘录、小标题、description、
正文或摘要、根、分区、类型、关键词、boost、面包屑导航与图标。夹具预算为原始
2 MiB、gzip 512 KiB。站点可以通过 hooks/search-keywords-extra.html 返回额外
字符串。关键词用于匹配与排序;CJK 查询仅命中关键词时,结果显示页面描述或
摘录,不展示关键词列表。正文命中仍使用匹配位置附近的文字作为摘要。
以下行为已随 v1.4.0 发布。
查询统一大小写与 Unicode 全半角,按空白和 CJK/拉丁边界拆分,最多保留 24 个不同词, 并保留标识符标点。连续 CJK 字串按字面子串处理,不增加词典或远程依赖。 字面字段匹配与英文 Lunr 词干匹配支持跨字段覆盖;拼写回退仅适用于至少五个字符的 ASCII 词,允许一次编辑,八个字符起允许两次。有完整覆盖时不显示部分匹配; 否则显示覆盖数量并优先按覆盖率排序。同覆盖率中完整连续短语优先,正数页面加权 作用于各词最强字段得分之和。同一个词在其最强字段中重复出现时,最多获得不到 20% 的加分, 只在同一字段层级内调整次序,不会越过更强的字段;只出现一次时按该字段原本的权重计分。 仍然同分时按标题与 URL 排序。 先应用全局结果上限,再沿用栏目分组与菜单组序。
可选的 sections 条目包含真实渲染的 id、title;content 范围还包含指向唯一
正文的 UTF-8 字节 start 偏移。直接读取渲染内容,包括 h1/h6 与短代码标题,不从
标题或配置后的目录推导 ID。索引读取时临时选择 HTML,再恢复此前的输出上下文,
避免 RSS/Print 遍历污染 HTML 内容。精简范围不包含正文偏移。没有小节字段的旧索引仍可使用。
同页只返回一条结果,附带最相关小节、编码后的片段与附近文字;仅元数据命中时可指向
页面。保留语言和子路径 URL、排除规则及各范围的内容边界。
规范化匹配后的摘要与高亮保留原始文字。标题提取区分 id 与 data-id,忽略脚本和样式内容。
带标签的原生单选组将全部栏目与页面的 FirstSection 根并排呈现,沿用索引的根栏目权威。
每个选项保留 44 px 点击区域与可见焦点环;Tab 到达选中项,方向键切换范围,
Tab/Shift+Tab 的焦点始终留在对话框内。
过滤在排序和数量限制之前执行;其他栏目命中不会阻止当前范围内的拼写回退。
默认全部栏目,选择只在当前页面加载期间保留。
范围仅影响页面,在命令与偏好选择模式隐藏。结果数量和部分覆盖在统一底栏中显示并实时播报,
桌面端的键盘提示也放在同一底栏;手机端仅隐藏键盘提示。
输入法组字暂停延迟查询、索引加载引起的刷新及 Enter 激活。不引入新配置权威或构建步骤。
主题搜索归属检查器与站点真实语料、浏览器测试验证这些增量;维护中的 summary 索引
继续遵守 2 MiB / 512 KiB 预算。
内置操作 ID 包括 compare_translations、copy_markdown、copy_link、open_chatgpt、open_claude、
view_markdown、view_history、edit_page、create_child_page、create_issue、
create_project_issue、print_section、print、switch_preset、switch_theme、
switch_language、switch_version 与 open_github。分享栏之外的 copy_link
只出现在命令面板中。站点通过
languages.<lang>.params.ui.command_palette.commands 配置的命令可以打开安全
URL,或调用内置 ID,绝不能注入 JavaScript。剪贴板的旧式回退恢复此前的焦点、选区及其方向,
但复制期间其他控件已获得焦点时不再抢回。
OINK 1.4.0 中,阅读列变窄或文字放大时,面包屑与页面操作会分行排列。 连续长词允许换行,不裁剪路径文字,所有面包屑链接保持可用。操作菜单限制在视口 以内,文字放大至 200% 时仍保留键盘焦点与关闭行为,包括 320px 窄屏。
页面操作菜单在键盘焦点离开整组控件时关闭,包括 Tab 与 Shift+Tab 导航。 焦点在组内移动时保持展开;关闭菜单不会抢回读者已经移到别处的焦点。 在关闭的菜单按钮上按下向下方向键,会展开菜单并立即聚焦第一项操作。
编辑、历史与新建子页操作要求源文件具有仓库相对路径。物理文件名和站点工作目录
先统一为 / 分隔符,再判断包含关系。path_base_for_github_subdir 匹配归一化后的
路径:站点内内容使用相对工作目录的路径,外部挂载使用绝对路径。字符串正则可以
移除匹配的内容,{from, to} 映射可以替换它;外部来源必须显式匹配规则。
映射并整理路径后,空路径、.、绝对路径、带盘符的路径以及以 .. 路径段开头的
结果都会隐藏这三项操作。文档 issue 与项目 issue 操作仍按各自的仓库配置提供。
Windows 映射应匹配 / 而不是 \;归一化不会改变文件名大小写。
命令面板有空状态、文本搜索状态与 > 命令状态;快捷链接来自导航。它没有历史、
语义搜索、个性化或远程回退。搜索查询留在浏览器内,默认不发送遥测。
OinkSurfaceCoordinator 协调命令面板、外观面板、Draw.io、Expo 查看器、抽屉、根栏目、语言与版本菜单。各界面自行
管理焦点恢复与 Escape。已关闭的外观面板收到协调器的关闭请求时,不恢复焦点。
键盘导航会忽略可编辑控件与模态框。桌面端非模态外观面板允许搜索快捷键;移动端模态外观面板及其他已打开的 dialog(包括固定定位的 ARIA
对话框)仍独占按键。移动端抽屉内,Escape 先关闭根栏目切换器并归还触发器焦点,
再次按 Escape 才关闭抽屉;焦点移出切换器时收起弹层。/、\、f、c
打开搜索或命令;j/k 移动标题;q/e 翻页;h 改变展示方式;l/y、
t、r 分别打开语言、主题与根栏目选项。侧栏 WASD/方向键导航使用真实焦点,
不会改写 Tab 顺序。
不带链接的分隔分组按钮也参与树导航。从子页按 Left/a 先聚焦父分组,再按一次才折叠;
Right/d 展开已折叠的分组,已展开时进入第一个可见子项。上一页/下一页仍只遍历链接,
不会把分组按钮当成页面。
页面大纲从同一套标题模型与滚动容器计算后的 scroll-padding-top 推导光标和可见
标题范围;SVG 线条与圆点共享同一组动画值,不会漂移。合法 URL 片段会被解码;
非法百分号序列则回退到字面的标题 ID,建立链接索引和选中页尾请求的标题时遵循
同一规则。禁止增加臆测性的 DOM
修复遍历。这项跟踪始终由普通外壳运行时负责。params.ui.scroll_spy 与页面键
scroll_spy 在整个 1.x 期间都是静默兼容 no-op,不加载独立运行时;只有未来的
破坏性版本才会删除它们。
搜索尾部扩展
OINK 1.1.0 包含该 API,1.0.0 中不存在。
受信任的站点 JavaScript 可调用
OinkCommandPalette.registerSearchTail({id, rows, activate});YAML 和操作清单仍然只接受
数据。资源包继续按本地搜索开关装配。注册要求唯一且符合 [A-Za-z0-9][A-Za-z0-9_-]* 的
ID,以及两个函数;非法或重复注册抛出异常。返回的注销函数可重复调用,旧句柄不能删除
复用该 ID 的新注册。面板打开时的注册变化会合并调度一次渲染,注销会取消该扩展的待完成操作。
rows(context) 同步返回描述符。context 是冻结的 {query, locale, phase, pageResultCount}
快照:query 去除首尾空白,locale 使用 HTML 的语言标签,phase 为 results、empty 或
error,数量只统计上限截取后的本地页面结果。仅在非空文本搜索完成后调用扩展,不在空查询、
命令、选择或加载状态调用。扩展行按注册顺序放在本地化的 Actions 组中,排在全部原生页面及
操作之后。原生空结果、错误提示与输入触发的索引重试保留。
描述符必须提供扩展内唯一、符合相同语法的 id,以及非空字符串 title。
可选的 description、icon、disabledReason 为字符串,available 为布尔值,默认 true。
OINK 复制并冻结这些字段,把显示字符串当作文本渲染。描述符非法、ID 重复、返回异步结果或
回调抛出异常时,本轮跳过整个扩展,其他扩展不受影响。不承诺回调的精确调用次数。
activate(row, context) 只通过普通结果行的激活路径调用,收到复制后的描述符和生成该行时
的上下文,另带 AbortSignal 与 handoff()。操作待完成时阻止其他结果行激活,包括进入原生
选择菜单。同步异常和 Promise
拒绝会释放待完成状态、保持面板打开,并播报本地化的操作失败信息。成功值被忽略;成功时关闭
面板,不从其他界面抢回焦点。关闭、重新打开、渲染的查询改变或注销会取消待完成操作,旧操作
的迟到结果不能修改新会话。
打开另一个受协调器管理的界面前,调用 context.handoff()。它关闭面板,但不归还焦点,也不
取消本次激活。随后由站点管理新界面的焦点与错误提示。之后的新 Palette 会话或注销仍可取消
尚未完成的操作;成功完成不会取消已经移交的操作。OINK 不强制超时。
rows() 必须保持纯净。这是受信任代码的契约,不是安全沙箱。默认查询仍留在本地,不内置
远程服务商或遥测。扩展的网络行为和服务商所需授权由站点负责。
桌面端非模态外观面板允许搜索快捷键;移动端模态外观面板与其他已打开的对话框 仍独占按键。在移动端抽屉内,Escape 先关闭焦点所在的根栏目切换器并将焦点还给 触发器;再次按 Escape 才关闭抽屉,焦点离开切换器时也会收起弹层。 从落地页抽屉打开搜索后,关闭搜索会将焦点还给可见的菜单按钮。 在命令面板中选择视觉预设后,面板保持打开,供读者继续操作。
分享
params.ui.share 默认为空,可接受 16 个目标的任意有序子集:x、bluesky、
mastodon、facebook、linkedin、reddit、hackernews、telegram、
whatsapp、line、pinterest、weibo、chatgpt、claude、email、copy。
页面列表会替换继承列表;share: false 退出。未知项会警告并丢弃。只有普通页面
渲染分享栏;Print、Markdown 与 RSS 省略它。
目标是携带页面永久链接与标题的普通 intent 链接,外加本地 copy_link 按钮。
Pinterest 图片来自共享特色图片解析器。ChatGPT 与 Claude 接收构建期生成的永久
链接提示,与页面菜单里的助理操作相互独立。Discord 没有公共 intent 目标,因此
有意不提供。
分享栏不加载平台 SDK、iframe、脚本、样式表、计数器或 campaign 参数;只有读者
主动点击链接时才产生请求。它是一行带无障碍标签的字形。
share/items.html 解析目标,share/bar.html 负责渲染。
注记
页面注记在 annotation-items.html 中解析描述项,再通过
page-meta-lastmod.html 渲染。注记中的行内链接始终带下划线,包括弱化显示的修改时间
与上游归属信息,使读者无需仅凭颜色辨认链接。两者都可以做窄范围覆盖。各行顺序如下:
| 行 | 条件 |
|---|---|
| 最后修改 | 已设置 Lastmod |
| 上游 | front matter 中的 upstream_link 非空 |
| 翻译 | 配置的权威语言存在译文,而且本页包含作者正文 |
upstream_link 是页面级事实;cascade 有效,upstream_link: "" 表示退出。
其它上游事实按站点参数 → data/upstreams[upstream_source] → front matter 解析:
upstream_name、upstream_copyright、upstream_license、upstream_notice,
以及可选的 upstream_ref、upstream_modified。存在链接时,前四项必填。无效或
残缺的署名会警告,而且不渲染法律声明;不支持的 URL 会被拒绝。
upstream_modified 改变署名动词并链接提交历史,不增加新行。notice 页面承载
完整的许可证与免责声明。翻译说明通过 params.ui.translation_notice 选择启用,
以页面键 translation_notice 参与 cascade,跳过生成页面或无正文页面;以本语言
原创的页面可以用 translation_notice: false 关闭。
作者与系列
博客文章页头依次为标题、信息行、术语徽章、作者署名、系列条;description 在其后
引出正文。信息行 article-info.html 始终包含日期;启用 reading_time 后再增加
字数与分钟数。Front matter 的 upstream_link 与注记使用同一个页面级事实,
并在共享 URL 策略保护下增加本地化的原文链接。术语行只是裸徽章组,分类法名称
位于分组标签中,不显示前缀。术语徽章静止时是浅中性底与弱化文字,前置该分类法的
term 图标;可点击徽章在 hover 或 focus 时才取得当前分区的强调色淡铺、边框与文字。
图标词汇表由 taxonomy-icon.html 独家拥有——每个
分类法配一对图标:整体分类法一枚、单个术语一枚(folder-open/folder、
tags/tag、cubes/cube、users/user-pen、series 用
book-bookmark/book,其余用 shapes);params.ui.taxonomy_icons 可覆盖:
字符串同时作用于两个表面,taxonomy/term map 分别设置;无效输入警告并保留
内置。右栏词云只在云头戴整体图标:云 chip 保持"文本 + 计数”——分类法已经亮明
身份,再在每个 chip 上重复图标只是噪声。独立的分类法目录卡片会带一枚术语图标;
作者署名只放人物——头像、姓名与个人资料的一行简介——
不带标签或日期。列表行、卡片与术语归档共享同一形态的元数据行:日期、一条本地化
的作者与分区短语,以及由同一个 reading_time 开关控制的字数和分钟数。句子下方
是独立成行、自动换行的徽章行,按分类法字母序列出页面在全部分类法下的词条,每枚
徽章佩戴各自的 term 图标;卡片排除 authors——其句中已具名。
只有声明 taxonomies: {author: authors} 才启用作者。作者 term 页面拥有显示名称、
摘要、正文与特色图片头像;没有 profile 时,回退到链接标题、首字母与归档。
authors-resolve.html 在文章页头、列表行中保留 front matter 顺序,并为每位作者
生成一个 RSS dc:creator。没有 authors 时,旧 author 保持原样;两者同时
存在时,authors 无警告胜出。自定义作者分类法复数名按普通分类法处理。
只有声明 taxonomies: {series: series} 才启用系列。Term 页面拥有引言;不新增
参数、数据文件、封面模型或运行时。页面使用 series: [name] 与可选的
series_weight。series-pages.html 先按 weight 排有权重成员,再按日期升序排
无权重成员,并用 Path 打破平局;系列条与 term 页面共享该顺序。第一个命名系列
得到一条 HTML/Print 系列条。面板是半透明加模糊,而不是一张不透明卡片:hero
文章会把题图铺在这一段背后,不透明底色等于在画面上挖个洞;普通文章上这层色调
就落回页面自身的底色,所以一种处理同时服务两种场景。summary 拥有整行与末端
箭头;系列名连同它的分类法图标,仍是 summary 的兄弟链接,覆盖在一份隐藏的等宽
占位文字上,避免 summary 内出现嵌套交互控件。展开后先划一条细线,再在同一层
表面上把成员阅读顺序放进一个保持 DOM 顺序的自适应网格。每个链接都把序号纳入
点击目标,序号贴在固定方格轨道的末端,因此无论多少篇,标题都对齐在同一条边上;
窄屏保持一栏,只有当每个标题仍有可读宽度时才增加等宽栏,因此桌面面板能用满自身
宽度,也不会把一条选中背景拖过整篇正文。悬停与读者所在位置直接借用侧栏导航
处理这两种状态的同两种底色,当前篇再加上填充序号与加粗标题,不靠颜色单独表意。打印时显示同一份展开
列表,收为单栏。单篇系列与非 HTML 输出省略它。编号、交叉引用与聚合输出仍属于 Book。
默认文章分类法徽章会排除保留的 authors 与 series,因为专属界面已经展示
它们。显式设置 params.taxonomy.page_header 可以恢复任意一项。
博客索引与页面组合
博客分区索引使用 params.ui.blog_index:默认的 list、cards 与 gallery 都按最新
优先排列,共享 blog_index_size 分页,不按年分组。
table 显示日期、标题、标签行;关闭布局切换时列出整个分区,不分页;开启时
使用与其他布局相同的分页。卡片使用共享首图、本地化日期/作者/分区元数据、
标签与三行摘要。
分类法页(/tags/、/authors/)与其术语页共用一个页头
shell/taxonomy-head.html。分类法页以整体分类法图标的着色方块、本地化名称与
术语数开头;术语页以术语标题与取自 ui_taxonomy_pages、按当前 locale 的 CLDR
复数类别选择的页面数开头,没有渲染面包屑时标题上方再加一行
kicker,写明分类法并链回列表页——面包屑开启时它在上一行已经做了这两件事:代表
生成的分类法页的那一级面包屑借用页头同一个本地化标签,而不是 Hugo 的复数名标题。
页头之下,分类法页把术语排成单行卡片网格 shell/taxonomy-cards.html:使用次数
多者在前、同数按字母序(与右栏词云同序),以 auto-fill 填满等宽列,因此术语
很少时两张卡片也不会被拉宽到整页。一张卡片就是术语图标、术语名与页面数,整张
卡片即链接;只有作者以署名同款小头像开头,走同一个头像 partial。卡片不带描述、
不带最新一页:术语没有标题与计数之外值得一说的内容,多出的那一行只会让网格发糊。
不再有筛选芯片行与「全部」芯片:分区根已经在侧栏与顶栏里。术语页保持行列表,
作者资料页保留自己的页头。
分类法页与术语页的右栏以 shell/taxonomy-switcher.html 开头:声明的每种分类法
一行——整体图标、本地化名称、术语数——链向其列表页,当前分类法置于选中底色上。
这是从一种分类法的页面去另一种的路:词云芯片跳到术语页,词云头只负责折叠;只有
一种分类法的站点不渲染切换器。这一组与词云共用 toc_taxonomies 开关。分类法页
的词云按全站统计(taxonomy-root.html 对该 kind 不返回根),并省略自己那一组,
它的术语就是旁边的卡片;术语页保持分区作用域与完整的一组。
params.ui.blog_index_toggle 为当前分页切片渲染四种形态,读者依次循环切换
list、cards、table、gallery。OINK 1.4.0 已支持这四种形态。
配置值控制首次绘制,隐藏形态不加载图片。读者存储的选择只作用于发布了全部四种
形态的索引:切换器关闭的分区只发布一种形态,并始终显示它。Front matter 或
cascade 可为每个分区覆盖站点模式。没有切换器的 table 仍是完整且不分页的归档。
博客画廊
blog_index: gallery 将每篇文章呈现为一张可点击的封面,沿用共享首图解析器、
列表与卡片的日期顺序及 blog_index_size 分页,并保留 manual_link。
文章内容类型仍是博客;点击封面进入文章,不打开图片查看器。
params.ui.blog_gallery 接受 mode(wall、cards、seamless,默认
wall)与 size(160–360 的整数 CSS 像素,默认 240)。页面或 cascade 的
blog_gallery 块整体替换站点块。缺失或无效字段使用内置默认值,无效值产生
警告。画廊工具栏独立于 Expo 相册保存模式和大小,存储键为
td-blog-gallery-mode、td-blog-gallery-size;外层视图仍使用 td-blog-index。
展墙复用 Expo 的等高拼接算法,间隙为 6px;无缝模式去掉间隙。卡片模式采用 最短列优先的瀑布流。封面保持比例,在限定宽高比的格子中完整显示,不裁切。 画廊宽度低于 500px 时,展墙与无缝模式会进一步限制格子的宽高比,避免超长 竖图将文字列挤得过窄。 可处理且超过 960px 的封面生成 960 × 960 范围内的预览,GIF 使用静帧预览。 构建过程不新增远程图片抓取。
展墙与无缝模式在悬停或键盘聚焦时显示标题和描述。没有悬停能力时,文字始终 显示在图片下方,行间距计入文字高度;卡片模式始终显示文字。没有封面的文章 保留有标题、可点击的占位;加载失败的图片在增强后使用相同回退。链接以标题 命名,封面视为装饰图片。隐藏视图使用延迟加载。视图重新显示、侧栏改变宽度、 字体或图片尺寸加载后都会重新排版,避免项目重叠。禁用 JavaScript 时,响应式 网格仍显示链接与文字,并隐藏不可用的控件。
params.logo 始终是品牌标志;params.wordmark 或站点标题是紧凑宽度下隐藏的
文字部分。Docs、Book、Blog 与 Swagger 共享一个外壳模型。页尾顺序为分享、反馈、
注记、翻页、评论。Docs/Book 翻页遵循侧栏前序遍历;Blog 按 weight 后接日期倒序;
pager: false 退出。静态输出省略翻页 UI。
每一种实际渲染的页脚形态,都会在最底层栏右侧保留纯图标工具组,顺序为版本、
语言、主题、快捷键帮助。各菜单向上展开;版本触发器不直接显示当前分支或版本名。
胖页脚的折叠箭头排在工具组之后。低于 lg 时,底层栏放弃版权/居中/工具组的
三列布局,改为三行全宽居中堆叠,工具组在最后一行。这些全局控件不再出现在
侧栏底部;footer_style: none 会移除整条底栏。
OINK 没有归档外壳、任意深度飞出菜单、第二个导航权威、查询上传,也没有针对已
移除配置的浏览器兼容 shim。反馈只通过既有 gtag 发出 docs_feedback,在本地
保存选择,而且不替代 Giscus。
显式设置 params.ui.quick_links: [] 会关闭快速链接,不恢复默认值。
无效的 sidebar_root_link_self 会告警并采用文档规定的 true 回退。
分类法总览页与 term 页使用共享的 page_width 校验器。
译文对照
params.ui.compare: true 为存在同源 HTML 译文的 Docs、Book 和博客页面,在页面动作旁
增加纯图标的「打开双列视图」入口(图标与外壳中任何控件都不相同),并在命令面板中增加本地化的「双列视图」动作
(compare_translations)。阅读器标题、同步滚动开关、关闭图标和同步说明使用同一套
语言目录。图标入口通过悬停提示和无障碍名称提供标签。启用键盘导航时,B 在两种状态间
切换:在页面上按下打开阅读器,在工具栏或任一窗格中再按一次关闭,并沿用页面快捷键的
限制(不带修饰键、不在输入法组字中、焦点不在可编辑区域)。入口和关闭按钮声明
aria-keyshortcuts,帮助面板只在当前页面确实有目标译文时,以视图名称列出这个快捷键。
默认关闭。这是唯一站点参数,无需页面配对数据、新布局或新输出格式。把它设为
false 的语言既不提供入口,也不会成为目标。语言导航使用的首页回退不会成为对照目标;构建策略不输出 HTML 的译文(build.render: link 或
never,包括经 cascade 继承的设置)也不会。博客归档第二页及之后的分页没有对应页,因此不提供
入口、命令面板动作或快捷键。
阅读面占满窗口,两栏显示原有页面。当前页面固定在阅读起始一侧,因此从右到左的宿主页 会镜像窗格顺序。目标语言沿用既有语言顺序;选择器复用顶栏语言栏的图标、圆角触发器、 浮层菜单样式以及选项的选中与悬停状态,并支持鼠标与键盘选择。只加载选中的译文,更换目标保留同步开关状态。 每栏保留自己的语言、方向、脚注和正文组件。
小于 900 CSS 像素时,一行工具栏加一行切换栏,每次显示一个保留的窗格。标题可截断, 同步滚动和关闭按钮固定在行尾。切换栏放置语言按钮;有多个目标语言时再放一个纯图标的 目标语言菜单,不再重复窗格标题。只有一个目标时,窗格标题和切换栏都只显示语言名称, 不会出现只有一个选项的菜单。矮视口会进一步压缩两行:在 320 × 256 CSS 像素(400% 缩放)下仍保留至少 160 像素正文。强制颜色模式用边框标出当前窗格。跨越宽度断点时,语言菜单随布局 搬移,同时保留键盘焦点、菜单开闭状态和当前选中的窗格。
同步滚动按共同的可见标题 ID 匹配位置,并在相邻标题之间插值。未匹配的标题会跳过。
「同步滚动」是带标签的开关(role="switch"):窗格加载期间保留读者的选择并暂时禁用,
无法同步或窗格加载失败时显示为关闭。部分标题匹配时,以匹配标题为锚点估算中间位置;
没有共同标题时,改按全文阅读进度估算位置。两种情况下,开关旁都会出现一个不带底色的
橙色感叹号,鼠标悬停、键盘聚焦或轻触时弹出说明,指出属于哪种情况;说明可见时,
Escape 先关闭说明,再按一次才关闭阅读器。是否匹配与能否用作锚点是两回事:已匹配但
滚动不到阅读线的标题不提供锚点,也不算不匹配,因此不会触发「部分匹配」提示;如果没有
任何共同标题可以作为锚点,提示会说明当前按全文阅读进度同步。若标题 ID 重复或顺序
交叉,由于对应关系存在歧义,开关会自动禁用并在说明中给出原因,两栏仍可独立滚动阅读。主动窗格连续滚动时(触控板、
惯性滚动、按住按键),跟随窗格逐帧即时跟随;单次跳转在允许动画时平滑过渡。偏好减少
动态效果或恢复阅读位置时始终即时定位。
关闭同步开关后两栏恢复独立滚动;同步滚动按章节锚点对齐,不强求段落逐行齐平。
打开阅读器时会在分栏重排后延续当前阅读位置。焦点先落在对话框标题上,以便读屏软件播报视图;
在读者用 Tab 进入窗格之前,在这里按 PageUp、PageDown、空格、方向键、Home 和 End 会滚动阅读
窗格(小于 900 像素时为当前可见的窗格),同步随之跟随。点击关闭、按 Escape、按 B 或浏览器后退都会
恢复宿主页滚动位置,并把焦点还给打开阅读器时持有焦点的元素(例如按下 B 时所在的正文
链接);该元素已不存在或不可见时,退回到入口按钮。只有展开的语言菜单或可见的同步说明会
先于阅读器处理 Escape。刷新或前进/后退恢复到阅读器自己的历史记录时,阅读器会
在原记录上重新打开而不新增记录,因此一次后退即可关闭,前进可再次打开。这种恢复会还原所选目标语言、同步滚动、可见窗格和两侧
阅读位置,阅读器在滚动停止后把它们记录在自己的历史记录上。读者最后一次明确选择的目标语言和
同步设置也会记在本浏览器中,供以后打开时使用;无法使用存储时,回到第一个目标语言并开启同步。若刷新后
阅读器脚本仍在加载,此时后退也会取消这次恢复。窗格内有对话框时,Escape 先关闭内层
对话框。页内锚点留在当前窗格内且不增加历史记录,并像原生锚点跳转一样展开包含目标的隐藏标签页;站内其他文档链接会退出对照并跳转;
外部链接在窗格外打开。正文可读之前,窗格显示本地化的「正在加载」提示。目标请求等待期间,
iframe 初始的 about:blank load 事件会被忽略;如果 15 秒超时到达时文档仍为空白,就将其
视为加载失败。页面无法加载时,显示本地化的「重试」按钮和原页面直达链接;再次选择同一语言也会重新加载。
已接受决策记录理由和边界。
bin/check-compare.py、主题滚动几何测试和文档站 test:compare 浏览器测试负责
验证。专用八语言 fixture 是合成验证,不代表下游站点已采用。
验证
bin/check-navigation-contract.py、bin/check-shell.py、JavaScript 测试、输出
golden 与消费站点浏览器套件覆盖导航、语言与子路径链接、博客变体、页尾顺序、
键盘行为、无障碍与响应式布局。
外观控件
顶栏与底栏共用点击或键盘展开的外观控件,Landing 手机抽屉另有带标签的入口。
preset_menu 允许选择时显示原生风格单选组;dark_mode.show_menu 开启时显示
浅色、深色、跟随系统单选组。选择立即生效,面板保持打开。Enter、Space 或向下
方向键展开;方向键在组内选择,Tab 在组间移动,Escape 关闭并归还焦点。触发按钮
图标表示实际明暗状态:浅色显示太阳,深色显示月亮,跟随系统变化时也同步更新。
英文分组标题为 Style 和 Light,中文为「风格」与「明暗」。风格选项以两列独立 按钮排列,使用带主题色的纸页、叠层、笔尖或终端图标与预设名称,不显示 Aa 预览 或实验标记。站点默认项在悬停提示与无障碍名称中注明,选择后清除保存的风格。 选中项使用淡色背景与边框,键盘焦点另有轮廓线。
桌面使用锚定触发器的非模态 dialog,外部点击或焦点离开时关闭。低于 768 px 或
从 Landing 抽屉进入时,通过 showModal() 在浏览器顶层打开底部表单,提供关闭
按钮和 44 px 选项目标。关闭表单保留下方抽屉。表面协调器在展开前关闭无关弹层。
t 快捷键继续通过 switch_theme 切换明暗;switch_preset 是独立的命令面板选项。
快速关闭并重新打开外观面板时,先前排队的关闭事件不得清除新面板的展开状态。
Ink 与 Terminal 实验预设需显式配置;preset_menu: true
继续提供 Paper/Slate 与站点默认值。两者复用同一套状态、键盘、命令面板与底部表单
机制。Terminal 压紧桌面导航行,保留正文与手机触控目标尺寸。
Expo 相册
新增 type: expo 复用本外壳并提供图片相册展示。
其内容模型、控件和输出由 Expo 契约 维护。
本地组件表面
OINK 1.4.0 统一搜索/命令行、侧栏圆角与外观控件的尺寸规则。选中与悬停保持可区分,键盘焦点即时可见,偏好存储、导航和预设选择不变。不增加滑动高亮运行时。
v1.5.1 审查修正
这些修正已随 v1.5.1 发布,并属于当前消费站契约。
manual_link_relref无效时警告并回退到页面永久链接。侧栏、分区索引及导航 JSON 共用解析,包括部署子路径处理。引用缺失不得中断普通构建。- 导航 JSON 的 ID 与分区索引的查找键先移除部署前缀,再移除语言前缀。 无论默认语言是否使用独立目录,显式分区顺序和跨语言标识都保持一致。 对外链接仍保留两个前缀。
- 版本菜单与命令共用 URL 解析。页面链接先移除当前部署前缀,再拼接目标版本 URL,保留语言路径,避免重复加入旧版本目录。
- 博客列表使用自己的分页:本地化导航采用带
aria-current的普通链接,head 的prev/next指向列表页。博客列表不再显示文章阅读顺序的翻页器。归档分页 及其 head 链接仅用于 HTML;Print 只生成一份完整的分区聚合文档,不按 HTML 归档页数重复生成。 - 所有仅有导航栏的页面,包括沉浸式博客文章,都提供移动端站点抽屉。 语言、版本和仓库入口在抽屉中可达。关闭的侧栏抽屉不向视口投射阴影。
- 两种树遍历器的
groups图标策略都包括导航根、顶层条目和有子项的条目, 更深层的叶子不显示图标。 - 没有目录、分类法输出或反链时,不渲染空面板和控件。仅有分类信息的右栏 保留收起/展开入口,标签描述侧栏操作。Book 目录在作者编号旁禁用浏览器 自带的列表编号。
- Escape 关闭语言、版本和工具悬停菜单并将焦点返回触发器,不会重新打开菜单。 复制、反馈、双列视图加载和 Expo 边界控件保留当前焦点。
7.4 - 落地页契约
本契约描述 OINK v1.5.1。
外壳与数据
任何普通页面都可以声明 layout: landing。它渲染顶部导航栏、全宽画布与页脚,
不显示 docs 侧栏或 TOC 导轨。首页继续把 data/home/<lang>.yaml 作为兼容的创作
路径,并通过同一个渲染器处理。
非首页依次从内联 front matter、data/landing/<key>/<lang>.yaml、单个
data/landing/<key>.yaml 中精确匹配语言的条目,以及英文或无后缀本地数据中
解析 sections。落地页绝不抓取可变事实;星标数、价格、截图与头像必须在 Hugo
运行前提交或生成。
params.ui.landing_search 默认为 true,而且只有启用 offline_search 时才打开
既有本地命令面板。params.ui.github_stars 与 params.ui.alt_site 是可选的本地
界面事实。
区块注册表
注册表恰好有 22 种内置区块:
hero、metrics、capabilities、principles、cards、logo-wall、gallery、testimonials、contributors、faq、markdown、cta;pricing、pricing-compare、command-box、steps、timeline、code-plate、preview、case-study、download、bar-chart。
条目可以是类型字符串,也可以是包含 type、key、id、enabled、内联
data 或有意指定的本地 partial 的 map。作者提供唯一 ID,OINK 把它规范为
锚点安全值。未知类型遵循共享的警告与安全回退策略,绝不静默消失。内置区块由 landing/ partial 负责;已经移除的
home/ partial 名称不是 API。
preview 通过站点渲染钩子,把 Markdown source 放在 RenderString 输出旁,
因此其内容会登记与 docs 内容相同的运行时。源码面板使用 Chroma,并带默认值为
page.md 的 file 名称。Markdown 输出使用四个反引号包围的 markdown 围栏;
RSS 省略它。面板标签来自主题 i18n。
hero.align 可取 start 或 center。Center 只适用于文本;与图片组合时会警告,
并回退到 start,同时保留图片。download 消费与 shortcode 相同的
data/download/<key>.yaml 结构,不引入第二套 channel、版本、发布或插值模型。
语言、运行时与无障碍
叙述文件可以按语言拆分。共享事实字段依次解析 <field>_<exact language>——其中
- 规范为 _——再解析 <field>_<primary language>,最后解析无后缀字段。
不接受 camelCase 别名。叙述字段通过站点渲染钩子渲染行内或区块 Markdown;复用
为无障碍名称的值会转为纯文本。区块文案属于站点数据;只有主题控件使用 OINK
i18n。
每次内置 Markdown 渲染都有稳定的区块、条目和字段作用域。重复正文和跑马灯
副本使用不同的自动代码 ID,复制按钮、标题、视口和行链接也随之对应。作用域
不依赖渲染次序或输出次序。显式代码 ID 保持不变;整页出现重复显式 ID 时会
警告,构建继续并保留告警。自定义 partial 重复调用 Markdown 辅助模板时,应为
每个渲染位置传入不同且稳定的 scope。
区块 Markdown 正文使用共享 .td-content 组件作用域,并登记实际内容所需的
运行时。代码折叠、表格、图片及其他内容组件在落地页内保留正常样式。该作用域
仅包围正文,不改变外层营销标题或网格。
交互式 HTML 设置 hasLanding,从而只按需添加 landing.js。运行时复用
OinkSurfaceCoordinator,负责出现动画、数字递增、复制、紧凑菜单与主题图片
增强。没有 JavaScript 或 Landing 脚本加载失败时,服务端输出仍然完整可见。
出现动画的候选元素默认可见,只有成功安装观察器后才标记为等待动画。数字指标
由服务端按配置输出完整的数字格式、前缀和后缀,递增动画的最后一帧使用同一显示文本。
交互式 HTML 中的代码面板可通过键盘聚焦,包括 capabilities 的代码视图与
code-plate 的逐行代码。读者可以用 Tab 进入长行区域,再用方向键滚动,不依赖
浏览器自动把滚动容器加入焦点顺序。这两种逐行渲染器不为静态 Print 输出添加
焦点入口。
跑马灯只用 CSS 复制;副本带 aria-hidden 与 inert,本地化复选框无需
JavaScript 也能持久保存暂停状态。减少动画会停用动画,强制颜色保留控件,主题
图片响应共享主题事件。顶部导航栏的 mega 面板与其 columns 参数已退役:仍然配置 columns 的菜单会告警并保持单列。紧凑菜单使用真实链接
与按钮,不捕获焦点,也不复制桌面导航树。
输出与兼容性
| 输出 | 契约 |
|---|---|
| HTML | 完整静态区块加渐进增强 |
| 静态网格与内容,移除控件 | |
| Markdown | 不带主题 class 的标题、正文、列表、表格与代码 |
| RSS | 配置后输出有效 RSS 文档;省略落地页区块 |
非 HTML 输出不设置 Landing 标志或运行时。根相对链接与资源遵循部署子路径;普通 构建不下载图片。
启用 RSS 时使用共享 feed 渲染器,实际输出与 HTML head 公布的 feed 地址一致。 章节与首页 feed 保留普通子页面摘要;显式启用 RSS 的普通页面可以输出空 channel。 省略落地页区块不等于生成空文件,也不能公布不存在的 feed。
已经移除的 0.4 组件形态属于迁移工具,不是并行的落地页实现。OINK 不增加价格 周期切换、远程事实 API、热点编辑器、可视化构建器或第二套注册表。既有首页数据 与显式自定义区块 partial 继续有效。
视觉预设
Paper 去掉首屏网格与光晕,使用暖色阴影、Plex Sans 展示标题和链接色主按钮。 Slate 保留技术网格、光晕、Chakra Petch 标题与原有主按钮颜色。两者共用分区结构, 不改变密度。手机抽屉包含共用外观表单,参见外壳契约。
显式开启的 Ink/Terminal 实验也去掉网格、光晕与阴影。Ink 使用高字重 Inter 标题、 直角卡片与红色主按钮;Terminal 使用等宽标题、2 px 圆角、琥珀主按钮与静态光标形 装饰。两者都不新增动画,不改变分区列结构。
本地操作控件精修
OINK 1.4.0 中,按钮按压仅移动背景,文字和点击区域保持固定。命令框为默认标签和本地化复制成功文字预留空间,复制结果仍由原有剪贴板 Promise 决定。复制动作、实时状态、数据模型和静态输出边界保持不变。
v1.5.1 审查修正
以下修正已随 v1.5.1 发布。
HTML 与文本输出共用 items、actions、定价 tiers 的记录数组校验,格式错误的 记录警告后跳过。可选的定价 features 缺失、对比 cells 无效,不再中断 Markdown 构建。
Landing 链接在添加语言及部署前缀前使用共享内容 URL 策略。仅含片段或查询的 链接保留当前页面语义。不支持的协议警告后丢弃链接目标,HTML 和 Markdown 遵循相同的链接语义。
Marquee 仅向辅助技术暴露第一行,重复行及副本组使用 inert 并从无障碍树隐藏。 Landing section 保留流式内边距,不再被旧的默认页 section 间距覆盖。 FAQ 焦点指示器位于会裁剪内容的 summary 内侧。
7.5 - Expo 布局契约
本契约描述 OINK v1.5.1。
本页规定实现必须遵循的行为。创建内容请阅读 Expo 相册, 设计理由见设计决策。
内容与导航
type: expo 选择相册布局,分区可通过 cascade 将其应用到下级页面。
一个相册对应一个 Hugo 叶子页面包,包含 index.md 和 images/ 目录。
图片元数据放在相册 front matter 的 resources 中,无需为每张图片单独创建内容文件。
分区页面列出下级主题和相册。共享导航树支持嵌套分区、当前语言和移动端抽屉。
Expo 使用全部可用页面宽度,没有右侧大纲。页面显式设置的 page_width 可以覆盖宽度。
顶部导航栏遵循共享站点与页面设置,Expo 默认不会将其关闭。
与 Book 相同,没有设置 sidebar_root_for 祖先的相册以其顶层分区而非首页树作为侧栏与根切换器的根。
配置
expo/config.html 按以下顺序解析配置:
- 使用页面的
expo块,包括 Hugo cascade 提供的配置块。 - 页面没有该块时,使用站点的
params.ui.expo。 - 逐字段验证;字段缺失或无效时,使用对应的内置默认值。
页面块整体替换站点块,解析器不会合并这两个块中的字段。
| 字段 | 可选值 | 内置默认值 |
|---|---|---|
images |
字符串,匹配页面包内的图片资源 | images/* |
mode |
wall、cards 或 seamless |
wall |
size |
160 至 360 的整数,单位为 CSS 像素 | 240 |
无效配置产生警告并使用回退值。 尺寸是布局计算的目标值,不会同时指定每个图片块的宽和高。
图片资源
expo/items.html 为所有输出选择图片、解析元数据。它使用 Hugo 资源元数据与共享
媒体解析器,并排除非图片资源。可选的 params.original 必须指向同一页面包中的图片。
构建过程不会抓取远程相册图片。
| 值 | 规则 |
|---|---|
| 标题 | 使用资源标题;未指定时由文件名生成 |
| 替代文字 | 使用 params.alt;为空或缺失时回退到标题 |
| 说明与署名 | 将 params.caption 和 params.byline 作为纯文本处理 |
| 图片顺序 | 按数字 params.weight 升序排列,默认为零 |
| 同权重图片 | 保持资源集合的名称顺序 |
| 显式 ID | 以 ASCII 字母或数字开头,后续字符还可使用 _ 或 - |
| 图片锚点 | 在 ID 前添加 img-;未指定 ID 时使用资源名称的哈希 |
ID 必须在相册内唯一,可使用字符串或 YAML 整数。无效或重复 ID 产生警告,并回退到
文件名哈希;哈希也被占用时,追加第一个未使用的数字后缀。重命名文件会改变默认锚点。
需要长期引用时,应保持显式 ID 与相册 URL 不变。expo.images 必须是非空的资源
通配式;params.original 即使为 false 或空值,也必须指向页面包内的图片。
可处理图片的宽或高超过 960 像素时,生成不超过 960 × 960 像素的预览。 GIF 也会生成静态预览,用于暂停和减少动态效果。动态源图仍然可用,原始文件保持不变。
相册卡片使用排序后的第一张图片作为封面。分区没有自身图片时,从下级相册中选取 首张可用图片。封面使用 4:3 画框,可能裁切预览。大型收藏由作者拆成相册; Expo 不会自动对图片资源分页。
布局与控件
| 模式 | 排列与说明 |
|---|---|
| 展墙 | 按行排列图片块,间距为 6 CSS 像素;支持悬浮的设备在悬浮或聚焦时显示说明 |
| 卡片 | 每张图依次填入当前最短的列;说明始终可见 |
| 无缝 | 沿用展墙计算,图片块间距为零;说明行为相同 |
对于极宽或极高图片,图片块的宽高比有上限和下限。块内完整显示图片,因此可能留白。 不支持悬浮的设备在图片下方持续显示说明。
工具栏包含一个共用背景的三格单选组,后面依次是播放/暂停按钮和横向尺寸滑杆。 滑杆两端分别用小图与大图图标标示。所有控件垂直居中。没有 GIF 的相册保留禁用的 播放按钮。图标控件具有本地化名称、适用的提示文字,以及可见的键盘焦点。 卡片与相册封面使用预设的圆角设置;展墙与无缝图片块保持直角。获得焦点的图片块在说明之上 显示双色内嵌焦点环。
外观控件位于共享导航中。相册主体不显示外观选择器或文章操作菜单。 没有 JavaScript 时,图片链接与说明仍然可见。
读者偏好与外观
浏览器将所选模式与尺寸保存在 td-expo-mode 和 td-expo-size 中,只有合法值会生效。
有效的存储值优先于作者默认值,并在同一来源下跨相册、刷新保留。
存储值无效或存储不可用时,使用作者默认值。
显示模式和尺寸独立于外观。Paper、Slate、Ink、Terminal 在亮暗模式下使用共享的 颜色、字体与圆角设置。切换预设后,网格会根据字体变化重新计算,同时保留读者选择 的显示模式和尺寸。
GIF 在网格与查看器中默认播放。播放/暂停控件同时控制当前相册及其查看器的动态效果,
该设置不会跨页面加载保存。prefers-reduced-motion: reduce 默认使用静态预览,
读者仍可主动播放。系统偏好变化时会更新播放状态。前四个图片块立即加载,其余网格图片采用懒加载。
布局稳定性
当滚动条占用布局宽度时,Expo 保留文档滚动条空间。相册高度接近视口高度时, 布局必须保持稳定。网格重算高度时,滚动条不能反复出现、消失。 打开或关闭查看器时,底层网格的可用宽度必须保持不变。
查看器行为
查看器使用原生模态对话框。图片显示区域不包含工具栏和说明区域。
W、H 表示原图宽、高;A、B 表示图片区域可用宽、高,单位为 CSS 像素。
| 操作 | 缩放比例 |
|---|---|
| 打开或重置 | min(1, A/W, B/H) |
| 放大至适屏 | min(A/W, B/H) |
| 放大 | 当前比例乘以 1.25,不超过缩放上限 |
| 缩小 | 当前比例除以 1.25,不低于缩放下限 |
较小的比例决定受限边。两种自动缩放操作都完整容纳图片,图片区域不出现滚动条。 重置让小图保持 100%,放大至适屏则可以放大小图。放大至适屏不调用浏览器 Fullscreen API。 只有手动缩放可以超出图片区域,并启用滚动。
百分比将原图像素按 CSS 像素计算。缩放保留观看中心,重置使图片居中。 切换图片会恢复初始尺寸与位置。上一张/下一张在相册首尾停止;到达边界后的按键 不会改变当前缩放比例与位置。
指针与键盘输入
按住鼠标主键拖动,可移动放大后的图片。图片随指针移动,并在边界停止。 只有超出显示区域的方向可以移动。放大后的图片在触摸输入下保留原生滚动。 松开或取消指针、切换图片、重置、窗口失焦都会结束拖动。图片完整显示在图片区域内时, 至少 48 CSS 像素的横向滑动会切换图片;双指缩放和页面已缩放时不切换。
双击或双触在初始尺寸与近看之间切换。缩小显示的原图以 100% 显示,并保持指针下的位置不动; 已经是 100% 的原图放大至适屏。处于其他尺寸时恢复初始尺寸。
查看器快捷键在打开的对话框内生效,不接管文字输入、输入法组合输入或
Ctrl/Command/Alt 组合键。对话框打开时,页面级单键导航暂停。
keyboard_nav 控制页面导航脚本,不会禁用 Expo 查看器脚本。
| 按键 | 操作 |
|---|---|
| W / A / S / D | 将放大后的图片向上/左/下/右移动,每次 80 CSS 像素 |
| Q 或 H | 上一张 |
| E 或 L | 下一张 |
| J 或 − | 缩小 |
| K 或 + | 放大 |
| R 或 0 | 重置尺寸和位置 |
| F | 放大至适屏并居中 |
| Escape | 关闭查看器 |
无需横向滚动时,左右方向键用于切换图片;否则保留原生滚动。 按钮提示显示适用的快捷键。从右到左书写的语言中相册顺序镜像:指向起始方向的方向键打开上一张, 滑动方向与之一致,上一张/下一张图标同样镜像。
链接、焦点与加载
打开原图在新标签页中显示原图文件。在相册中定位会关闭查看器、更新 URL 锚点、
滚动到当前图片,并聚焦其触发链接。普通关闭操作将焦点返回最初打开查看器的链接。
原生对话框约束焦点,并阻止与底层页面交互。查看器打开时,底层页面不能滚动。
查看器注册到 OinkSurfaceCoordinator:打开时关闭其他浮层,closeOthers() 或
closeAll() 也能关闭查看器。移交到新界面后,焦点保持在新界面中。
原图加载期间,查看器按最终尺寸显示网格预览;原图加载失败时保留该预览。 每张图片加载完成后,查看器预取前后各一张相邻图片。
Expo 只在有图片的 HTML 相册中加载查看器和布局脚本。普通 Image Zoom 保持单图行为, 不会再次包裹 Expo 图片链接。没有 JavaScript 或不支持对话框时,这些链接直接打开原图。
切换图片时,先清除上一张已解码的图片;下一张原图加载期间,按最终尺寸显示 新的网格预览,原图加载失败时保留预览。相册正文与图片标题在 Markdown 中 保持为独立区块。
其他输出
所有输出使用同一套图片解析器。各格式通过常规 OINK 输出配置启用。
| 输出 | 图片内容 |
|---|---|
| 静态预览、标题、说明、署名与原图链接;不加载 Expo 脚本 | |
| Markdown 与 LLMSFULL | 原图链接与元数据 |
| LLMS | 在现有索引中列出相册页面 |
| RSS | 静态图片说明 |
| 浏览器打印 | 展开相册内容,隐藏操作控件 |
作者负责翻译相册内容和资源元数据,图片控件使用共享界面语言目录。
已有的 gallery 围栏仍是正文组件,遵循自身契约。
验收与发布边界
在主题 checkout 中运行专项检查:
在同级文档站 checkout 中运行站点验收:
双语示例是集成测试与视觉检查页面。
tests/browser/expo.spec.mjs 检查交互与无障碍。
expo-stability.spec.mjs 在视口高度临界点采样连续帧,并强制启用占宽度的滚动条,
不依赖宿主设置。
要补充 Chromium、Firefox 和 WebKit 检查,先安装匹配的 Playwright 浏览器, 再从文档站 checkout 运行:
独立的 Graph 副本提供大型收藏的补充证据。
7.6 - OINK 迁移边界
本契约描述 OINK v1.5.1。
本页定义维护者迁移工具与配置边界;面向读者的升级步骤见 版本升级。
工具范围
bin/migrations/oink06.py 只扫描和自动改写站点内容目录下的 Markdown 文件,
包括受支持的 YAML front matter。它不改写 Hugo 配置、数据文件、布局、资源、
模块或生成输出。TOML/JSON front matter 与有歧义的 Markdown 会连同位置一起报告,
留给人工检查。
默认执行 dry-run;完成后的迁移具有幂等性:
代码围栏的内容不会改写,包括与有序或无序列表标记处于同一行、可带引用前缀的围栏。
代码示例里额外的字面引用前缀不会结束该围栏。
book_figures.py 保留范围明确的 TPME、DDIA v1/v2 与
pg-internal profile;它不是通用解析器。
隔离验证工具 bin/measure-baseline.py 和 bin/sites/build-all.py 会在清理已有
输出前,拒绝与任一输入站点、运行工具的 checkout、选中的主题 checkout 或其他
快照交叠的快照目录,包括通过符号链接别名指向这些位置的 --keep 目标,以及
通过 --theme 选择其他主题 checkout 的情况。
更新消费站点仓库
主题发布后,应清点维护中的消费站点 checkout,并升级它们固定的版本。
主题的 bin/update-consumers.py 扫描指定根目录下的直属项目及其 site/ 目录,
不递归进入归档、生成站点、缓存或主题测试夹具。
此工具随 OINK 1.2.0 发布。在主题 checkout 中执行,先清点,再升级到正式标签。
第一条命令只报告版本采用情况。第二条更新 go.mod 和 go.sum 中的 OINK
条目,核对精确的模块解析图,并对每个选中站点运行保留告警的生产构建。
执行时禁用 GOWORK、Hugo 模块 workspace 和环境变量中的模块替换。日志与
原始模块文件保存在临时报告目录,也可通过 --report-dir 指定目录。
更新失败会恢复模块文件;构建失败则保留新版本以便排查,并返回失败状态。
扫描根目录无法读取或消费站模块格式错误时,会记录失败条目,继续清点其余站点,
并以非零状态退出。显式选择的目录不存在或不是 OINK 消费站时,也会明确报告失败。
工具跳过链接 worktree、隐藏副本和非默认分支。应检查所有跳过与阻塞条目:
显式项目根本身没有 Hugo 配置且不是 OINK 消费站时,会解析到包含 Hugo 站点的
site/ 子目录。普通 Go 应用在根目录的 go.mod 不阻止此解析;真正的根目录消费站优先。
通过 --sites <path>... 显式选择已核对的站点,包括已有模块改动的 checkout 和没有
Git 的源目录。自动扫描会跳过无 Git 目录,显式选中后执行同样的文件备份与构建检查。
工具也会列出传统 themes/oink 消费站供独立维护:将干净的 Git 子模块切到已发布 tag,
或备份后替换已核对的上游源码副本;保留离线构建与站点层覆盖,再执行站点自身检查。
模块更新工具不会改写传统主题目录。
go.mod 中的 OINK 替换需要手工处理。vendor 主题需先独立核对,再使用
--refresh-vendor 备份并重新生成 _vendor/;只改模块版本不会更新 vendor
中的主题。是否实际使用 vendor 输出以 Hugo 解析的模块图为准;通过 ignoreVendorPaths
绕过的历史快照会保留,不会阻塞已发布模块的升级。
要开启四种风格,并以 Paper 为站点默认值,在站点配置中显式设置:
preset_menu: true 只提供稳定风格与站点默认值;显式列表还会开启 Ink 与 Terminal。
这些设置会在后续版本升级时保留。自定义外壳的站点需单独检查:绕过 OINK 外壳的模板
不会仅因配置更新就获得外观控件。
维护者 checkout 还支持将 --check-appearance 与 --check 一起使用,检查新构建的
首页及各语言入口是否以 Paper 为默认值并渲染四种风格控件。禁用首页的站点改查文档
入口,跳过重定向别名。浏览器交互和各站点自身的检查仍需独立执行。
保留无关改动,同步站点 README 和配置中的当前主题版本说明,并运行站点自身的 检查与视觉验收。该工具只更新模块固定版本并执行构建检查,不修改正文或自动提交。
从 0.4 内容迁移到当前形态
| 已移除形态 | 当前形态 | 工具键 |
|---|---|---|
alert、details、pageinfo、原始 disclosure |
> [!TYPE] 提示块 |
callout |
tabpane、旧 tab、code-group、code-tab |
相邻 {tab=} 区块,或 tabs / tab |
tabs |
FileTree shortcode 或 {.filetree} 列表 |
filetree 围栏 |
filetree |
Gallery shortcode 或 {.gallery} 列表 |
gallery 围栏 |
gallery |
| ECharts / infographic shortcode | 同名数据围栏 | datafence |
| Docsy 卡片家族 | .cards 列表或 cards / card |
cards |
imgproc、image |
Markdown 图片加属性 | image |
readfile |
include |
include |
围栏 filename= |
title= |
fencetitle |
badge outline= |
移除 outline |
badge |
叶子 example、book-figures kind= |
eg、显式 book-* 索引 |
eg |
| 百分号分隔的 fields | 尖括号分隔的 fields / field |
fieldsdelim |
Docsy _param 占位符与 card header= 高亮 |
Font Awesome / badge / param 或提示块 |
param_placeholders |
| 不支持的旧 shortcode | 报告源码位置,人工检查 | reportonly |
配置与 front matter
以下配置改动需要手工处理;工具可以报告匹配的 front matter 键,但绝不编辑站点 配置。
| 旧配置 | 当前配置 |
|---|---|
offlineSearch* |
offline_search* |
disable_click2copy_chroma |
ui.code_copy,取反 |
content_width |
`reading_width: slim |
github_url |
github_repo |
ui.no_left_sidebar |
ui.sidebar_enabled,取反 |
| breadcrumb 别名 | ui.breadcrumb |
ui.scrollSpy |
无行为替代;ui.scroll_spy 仅作为 1.x 静默兼容 no-op 保留 |
ui.showLightDarkModeMenu |
ui.dark_mode.show_menu |
ui.readingtime |
ui.reading_time |
ui.ul_show |
ui.sidebar_expand_levels |
ui.docs_root |
ui.docs_sidebar_root |
ui.pager |
ui.pager_types |
annotation/zoom/keyboard/reading 的 { enable: bool } map |
裸布尔值 |
ui.typography.preset |
ui.typography |
print.disable_toc |
print.toc,取反 |
Prism、rss_sections 与 algolia_docsearch 已移除。Chroma 是唯一高亮器;Algolia
配置为 search.algolia。页面级覆盖会去掉 ui. 前缀。旧 hide_feedback、
hide_readingtime、exclude_search、content_width、camelCase 手工链接与嵌套
front matter ui map 会连同替代项一起报告。
从 0.5 到 0.6
- 用
upstream_link加upstream_name、upstream_copyright、upstream_license、upstream_notice替代upstream_attribution;把downstream_modified改名为upstream_modified。 - 用一个 GitHub
release_url替代releasemap;从发布索引移除release_products与release_group_by_product。 - 博客与默认日期现在采用 ISO
2006-01-02;面向读者的日期继续显式保留time_format_blog或time_format_default。
已移除名称会警告,并采用文档规定的安全回退或不渲染;构建和预览可以继续。blog_index_toggle、
featured_image: hero、toc_style 与 toc_taxonomies 是增量选择启用项,不会
引入内容类型;沉浸式阅读仍使用普通博客外壳。
前置条件与验证
按照组件契约启用 Goldmark unsafe 渲染、块属性与
独立块图片。要使用 \(...\)、\[...\] 或 $$...$$,需要显式启用 passthrough;
Hugo 不会合并主题的 markup 配置。
针对改动的契约,使用固定的 Hugo Extended 0.168.0 工具链运行范围最小的源码与输出 检查;运行时变化时执行 JavaScript 测试,并生产构建根路径与子路径。对于维护范围 内的站点,在桌面与窄视口检查有代表性的 EN/ZH Docs 与 Blog 路由。
7.7 - 设计决策
决策记录解释 OINK 为什么在多个兼容方案中选择了当前设计。上方五份契约仍是 现行行为的规范描述;实现与归属检查器仍是可执行事实。
OINK 过去把评审、PRD 与执行记录放在本地 plan/ 目录中。这样既不便发现有价值的
推理,也容易让已经放弃的设计看起来仍有权威。已经接受的理由现在统一进入这座双语、
版本化的文档站,与它所支撑的契约放在一起。
决策地图
| 决策 | 解决的问题 |
|---|---|
| 警告与安全回退 | 为什么普通预览能容忍错误输入,而发布仍保持严格 |
| 配置模型 | 配置放在哪里、页面如何覆盖,以及 OINK 为什么不另造配置命名空间 |
| Markdown 优先创作 | 为什么优先使用原生 Markdown,以及 Docs、Blog、Book、Landing 如何延长共享系统 |
| 生成式配置 Schema | 为什么编辑器 Schema 是生成的投影,以及漂移门禁如何阻止第三个配置权威出现 |
| 可选 CLI 与结果契约 | 本地 CLI 候选的独立 Go 可执行文件、版本化诊断、覆盖范围与显式写入边界 |
| 视觉预设 | Paper 默认、Slate 兼容、可选外观菜单、独立明暗状态与字体边界 |
| Expo 相册 | 资源页面包、可选元数据、共享外观与相册查看 |
| 博客画廊 | 文章封面、共享 Expo 排布、独立偏好与可读的文字说明 |
| 表格阅读 | 显式 .expand、独立 .wide,以及可还原原表格的角落直接展开入口 |
| 译文对照 | 一个开关、真实同源译文、按需加载的双栏阅读器和基于标题的同步 |
| 组件精修 | 共用样式、分隔式卡片、紧凑步骤与命令条 | | 统一图表阅读 | Mermaid 与本地 PlantUML 共用查看器、源码导出与按需加载 | | 本地搜索改进 | 混合查询、真实小节锚点、部分匹配与栏目范围 |
记录格式
一份已接受决策应记录背景、选择、后果,以及证明该选择仍然成立的证据。它不重复参数 参考或教程。每份决策都要链接到归属契约与验证面,中英文页面必须同步修改。
决策发生变化时,应在同一次交付中更新实现、检查器、受影响契约与决策记录。旧答案留在 Git 历史和版本变更记录中,不在导航树里并列保留两套“现行”答案。
相关
7.7.1 - 警告与安全回退
OINK 不调用 Hugo 的 errorf。作者或站点输入无效时,主题发出警告,并使用文档中
明确的安全回退,或者省略无效片段。开发、CI、发布与部署统一使用 Hugo Extended
0.168.0,禁止启用 --panicOnWarning 或 HUGO_PANICONWARNING。告警保持可见,
检查器验证具体诊断与回退后的产物。
背景
Hugo 把整座站点作为一次事务构建。编辑一页时触发的 errorf 会让该次重建中的所有 URL
都返回错误,包括无关页面和首页。服务器进程仍然存在,修正输入后也会自动恢复,但多人共享
的预览在此期间完全不可用。
警告的开发成本不同。出错的值可以回退,站点其余部分仍可检查,作者也能看到准确消息。 发布构建保留这些告警供审阅。上游 LibSass 弃用告警允许出现;非预期的主题或内容告警需要检查。
决策
校验遵循四条规则:
- 点明无效键和值、允许的形状以及实际采用的回退值。
- 值来自页面 front matter 时带上页面位置;站点级错误不要在每一页重复刷屏。
- 不允许无效值继续参与后续运算。先校验,再用规范化后的值渲染。
- 没有诚实回退时,警告并且不渲染。不能为了继续构建而编造内容、发起网络请求或输出 不安全 URL。
枚举、布尔、CSS 长度与数字的共享校验形状位于
layouts/_partials/validate.html。领域 resolver 可以增加更窄的规则,但必须保留同一套
警告与回退契约。
安全边界
继续构建不等于继续输出危险内容。被拒绝的 CSS 长度要在进入 style 属性之前回退;远程服务
配置不完整时,要在浏览器可能发起请求之前省略组件;不安全的操作 URL 直接丢弃。真正的保护是
坏输出没有出现,而不是 Hugo 被终止。
编辑与发布采用相同的告警与回退行为:
| 阶段 | 无效输入的处理 |
|---|---|
hugo server 或普通本地构建 |
警告、回退或省略,其它页面继续可用 |
| CI、版本验收、部署 | 告警并安全回退,审阅诊断并验证产物 |
后果
- 每个回退值都是公开契约的一部分,必须与主题声明的默认值一致。
- 从“失败”改成“回退”时,测试也必须改变。负向测试要证明构建成功、警告文案与渲染后的 回退或省略结果。警告本身不得导致 Hugo 构建失败。
- 检查器必须直接验证被拒绝的输出。例如 URL 安全测试应同时断言告警文本与危险 URL 未进入产物。
- 渲染产物负责 DOM、属性、顺序与已注入 token 的断言;浏览器套件负责计算后的颜色、尺寸、 间距、断点与交互结果。只要公开结果可以直接观察,检查器就不应冻结某一种 Sass 写法。
- 源码级检查仍用于
errorf等禁止构造,以及产物无法证明的少量拓扑不变量,例如唯一 authority、 唯一 resolver,或有意收窄的 caller set。
验证
本决策的归属参考包括
架构契约、
bin/check-params.py,以及主题夹具与本站使用 Hugo Extended 0.168.0 的生产构建。
本告警策略于 2026-10-18 更新。不得恢复遇到告警就中断的发布门禁。
7.7.2 - 配置模型
OINK 保留 Hugo 原生键与仍有价值的 Docsy 兼容键,把主题呈现和行为放在
params.ui.* 下,并用同名的顶层 front matter 键提供页面覆盖。它不增加
params.oink.* 配置树,也不建立一套遮蔽 Hugo 配置模型的注册表。
背景
OINK 继承了成熟的配置面,又增加了阅读外壳、内容输出和本地交互。早期设计曾尝试把所有 主题自有键迁入一个新命名空间,并在每页一次性解析完整配置字典。这样会在 Hugo 原生键旁边 再造一种语言,使 section cascade 更复杂,迁移规模甚至超过它要控制的行为本身。
现行模型直接体现每一层的归属:
| 层次 | 职责 | 示例 |
|---|---|---|
| Hugo | 站点身份、语言、菜单、输出、分类法、markup、模块 | baseURL、languages、outputs |
| 站点事实与集成 | 仓库、版本、作者、本地搜索、评论、外部服务 | params.github_repo、params.version、params.comments |
| OINK 界面 | 外壳、导航、呈现与本地交互 | params.ui.sidebar_*、params.ui.typography、params.ui.share |
| 页面或栏目 | 对可覆盖站点默认值的局部调整 | sidebar_enabled、featured_image、share |
| 数据文件 | 不是开关的结构化事实与有序内容 | data/landing、data/download、data/docs_nav.json |
决策
配置 API 遵循以下规则:
- 站点事实保留在既有顶层;界面选择归入
params.ui.*。 - 页面覆盖去掉
ui.前缀,其余名称保持一致。section 的cascade可以把这个顶层键应用到后代。 - 一个布尔值足以表达完整政策时使用标量;只有真正存在下级设置时才使用 map。既有 map 可以接受 布尔速记。
- 名称采用正向、snake_case,并按功能分组。密切相关的设置共用前缀,不为此再建一层 resolver。
- 主题默认值声明在主题的
hugo.yaml中。只有静态值会抹掉刻意存在的外壳差异时,模板才可以 推导默认值。 - 每个功能族负责自己的规范化与校验。共享 helper 提供常见形状,但不存在一套悄悄重写任意旧键的 全局兼容注册表。
完整的现行键、类型与默认值统一放在配置参考中。本决策只记录 归属规则,不再维护第二张参数表。
兼容策略
公开键改名时,由归属 resolver 给出定向警告,同时提供迁移说明和负向测试。已移除或拼错的键 不构成永久别名层的理由。Hugo 与第三方原生 camelCase 键继续保留原样;OINK 自有新增使用 snake_case。
页面值通过 Hugo 普通的 front matter 与 cascade 模型解析。OINK 不要求作者在 front matter
里写嵌套 ui: 树,也不承诺合并任意嵌套页面 map。
后果
- 新增公开设置时,必须有声明或明确推导的默认值、归属 resolver、文档,以及正向和负向测试。
- 配置指南链接到唯一参考表,不在各处重复类型与默认值。
- 只有有序或重复事实才值得新增数据结构,不能只因为不想增加参数就造一个 data 文件。
- 无效标量值遵循警告与回退决策。
验证
bin/check-params.py 审计声明默认值、页面别名、警告行为与禁止 errorf 的不变量。公开参考及其
中文对页由集成站的双语和渲染链接检查覆盖。
7.7.3 - Markdown 优先创作
Goldmark 能保留目标语义时,优先提供原生 Markdown 形态。只有原生形态无法表达真实能力时, 才保留 shortcode。新增内容场景时延长既有外壳和数据模型,不另建一套并行渲染系统。
背景
OINK 同时服务短手册、大型参考文档、发布归档、落地页和书籍。对十一个消费站点、五千多篇 Markdown 的盘点呈现了两个极端:有些页面几乎不用主题语法,有些页面则由大量嵌套 shortcode 与站点自有 layout 拼成。
只为后一类优化的组件 API 会变成私有 DSL;只支持纯 Markdown 又会迫使书籍、富图、标签页和 结构化发布退回站点自有 HTML。真正有用的边界是能力,而不是语法看起来是否新颖。
决策
OINK 按以下顺序设计:
- 原生 Markdown 优先。 列表可以成为 Steps、Cards 或 FileTree 标记;表格可以成为 Fields 或矩阵;blockquote 可以成为 callout;代码围栏、图片与 passthrough 块通过渲染钩子携带属性。
- shortcode 只补能力。 CommonMark 缩进、嵌套容器、处理选项或跨页登记无法安全表达同一结果时, 才保留全量 shortcode 形态。
- 语义实现只有一套。 原生形态与全量形态进入同一组规范化 partial 和输出契约,不能只是两种 外观相似的组件。
- 沿一条系统延长。 新 Landing 区块进入 section 注册表;新 Blog 呈现仍是 Blog 变体;Book 编号接入内容原语与导航系统。OINK 不为一个功能再造第二套卡片、落地页、导航或 Article 外壳。
- 事实不藏在呈现字符串里。 版本、仓库、日期与有序记录来自 front matter、站点参数或数据文件。 shortcode 参数不能成为第二个事实来源。
输出契约
只有在每种已启用输出中都得到明确语义结果,一种创作形态才算完整:
| 输出 | 要求 |
|---|---|
| HTML | 服务器端先输出完整语义内容,JavaScript 只做增强 |
| 静态、展开,不包含依赖交互的控件 | |
| Markdown / LLMS | 保持源码形态的正文、链接、列表、表格与围栏,不泄漏组件 HTML |
| RSS | 安全的静态内容,或者明确省略 |
这一要求避免一个漂亮的 HTML-only 组件悄悄破坏 Agent 输出、订阅源或整书打印。
信任与呈现
渲染钩子与 shortcode 使用明确的属性白名单。不安全 URL scheme、内联事件处理器和任意 style 输入会被丢弃。只有在文档明确规定、下游站点 CSS 已属于既有创作契约的表面,才接受作者 class。 图标使用一对 Font Awesome class;OINK 不再发明第二种图标 ID 语言。
后果
- 提议新组件时,必须先说明 Markdown 加既有渲染钩子为什么不够。
- 保留全量 shortcode 时,必须点明它独有的能力,并测试两种形态进入相同的规范化输出。
- 外壳变体使用相互独立的呈现键,因此启用 Hero 或流式大纲不会改变分类法、订阅源、翻页顺序或 内容类型。
- 消费站证据是带日期的研究,不是永久冻结偶然语法的理由。当前公开面仍由 组件契约与外壳契约定义。
验证
主题的组件、Book、输出与 golden 检查器先验证创作契约,本站的双语示例与浏览器套件再完成集成 验收。原生形态背后的 Goldmark 事实记录在 块属性研究中。
7.7.4 - 生成式配置 Schema
schema/ 下的两份 JSON Schema 由 bin/generate-config-schema.py 从主题的
hugo.yaml 与模板读取点投影生成,手工编辑无法通过 CI。Schema 是既有权威的
只读投影,不是第三个配置权威。
背景
主题已有两个配置权威:hugo.yaml 在注释旁声明每个默认值;check-params.py
的读取点扫描知道模板实际消费的每一个键。编辑器对两者一无所知,作者只能凭记忆
敲 params.ui.* 和 front matter。
JSON Schema 能给编辑器补全与悬浮文档,风险在于 Schema 悄悄变成会漂移的第三个 权威。任何手工维护的 Schema 都终将与实现脱节,而脱节的补全比没有补全更危险。
决策
bin/generate-config-schema.py 在 schema/ 下生成两个文件:
site-params.schema.json 校验站点的 hugo.yaml(类型与默认值取自主题自己的
hugo.yaml,描述取自其注释块);front-matter.schema.json 校验页面 front
matter(模板作为创作面读取的全部键,描述继承自对应站点键)。仅为提示「已重命名
或已移除」而读取的键按名排除。
两个刻意的克制成为决策的一部分:
- front-matter Schema 不带类型约束。多个键在站点类型之外还接受裸布尔退出
(
share: false、theme_color: false);对合法输入画红线比没有提示更糟。 hugo.yaml读取器只解析该文件实际使用的形态——嵌套映射、标量、行内列表。 读不懂的构造是硬错误,超出能力时漂移门禁会大声失败而不是错误生成。
参数检查器标明有文档化布尔简写的映射,站点 Schema 为这些映射保留布尔值与
对象两种形态,包括 ui.dark_mode;print、ui.expo 等纯配置映射仍是对象。
feedback.enable 等 front matter 读取点生成 feedback 下的嵌套属性,
不生成带点的字面键,也不排除受支持的布尔页面覆盖。
后果
改变 Schema 的唯一途径是修改 hugo.yaml 或扫描所读的模板:公开配置面变化时,
Schema 在同一次提交中随之再生,不存在需要单独记得维护的第二份清单。代价是
生成器与读取点扫描成为公开配置面的隐含门禁——新增参数键必须能被它们理解,
否则 CI 直接失败。
验证
python3 bin/generate-config-schema.py --check 在内存中重新生成,schema/
过期或缺失即失败;主题 CI 把它放在参数契约检查旁边运行。编辑器接入方法与
行为描述的规范位置是配置总览。
视觉预设枚举值另从 preset-config.html 提取。preset_menu 联合类型接受布尔值或
由解析器管理的预设名称列表,schema 不另行维护名称清单。
7.7.5 - 可选 CLI 与结果契约
本契约描述 2026-10-04 收缩后的本地 0.1.0-dev 命令界面。保留站点诊断、
真实 Hugo 检查、初始化、构建、升级及有保护的维护计划;使用 Cobra、默认彩色
英文文本与 JSON/YAML 结果。Studio、通用编辑、context、snippets、editor 与
CI 生成已撤下。历史 R1–R8 验收只对记录中的源码和二进制成立,不能替代当前验证。
CLI 目前为本地候选版本,尚未作为独立版本或 Homebrew 软件包发布。
背景与归属
主题是 Hugo 模块;消费站工具是可选的可执行文件,具有不同的安装和版本发布周期。
pgsty/oink-cli 负责名为 oink 的可执行文件及其 Go 测试。它调用外部 Hugo
二进制,不引入 Hugo 私有运行时,也不在运行时依赖同级 checkout、Python、Node.js
或未发布的主题脚本。
配置解析、渲染、路由与锚点由 Hugo 负责。CLI 检查 Hugo 的生效配置、模块图、挂载 与渲染文件,不另建路由解析器、导航权威或配置命名空间。仅针对主题的回归脚本继续 作为维护者工具。架构契约仍负责主题行为;本页 负责首期 CLI 边界与结果封套。
配置预处理仅在临时副本中重定位 workspace、replacement 与缓存路径。默认值、 配置合并、语言选择、验证和渲染语义仍由 Hugo 负责。
CLI 简介提供概览与本地构建步骤。 带日期的验收记录将已执行 检查、未解决限制和发布状态分开说明。 维护验收记录 保留历史 R1–R8 计划与绑定源码的验收证据。 路线图继续保留后续提案和采用假设, 不再重复当前命令参考。
命令与修改边界
命令帮助按日常、维护与发布分组,使用 oink COMMAND --help 查看准确选项。
| 命令 | 行为与写入边界 |
|---|---|
doctor |
只读工具链、生效配置、模块来源、workspace/replacement/vendor 诊断 |
check [links|translations|style] |
在隔离副本中检查真实 Hugo 输出及声明的源码/翻译政策 |
init DIRECTORY |
先验证固定 Starter,再创建新的或空站点 |
dev、build |
普通 Hugo 进程;正常输出与缓存写入由 Hugo 管理 |
upgrade --to TAG |
默认预览,只有 --write 才应用验证后的模块修改 |
translations status、translations diff PAGE |
只读关系、哈希审阅状态和差异 |
translations review SOURCE TARGET、baseline capture |
必须提供审阅者与理由,默认预览,可保存新 --plan |
new BUNDLE --title TEXT、move SOURCE TARGET |
验证候选并预览完整 diff,可保存新 --plan |
plans apply FILE |
重新验证受支持的已保存计划,仅写选定站点的指定文件 |
inspect PAGE、impact --since REF |
只读真实页面与历史/当前影响事实 |
workspace list、workspace check [GROUP] |
仅选择显式登记的站点 |
build --check |
检查、封存并导出同一次隔离生产渲染 |
artifacts verify |
离线比较本地产物与清单 |
verify |
显式 --network 后比较部署 HTTP 响应与清单 |
联网默认关闭,所有命令均不交互。Hugo 参数仅在 dev/build 的 -- 后透传。
已撤下命令与其旧计划不能应用;受支持计划类型仅为 authoring.new、
translations.review、baseline.capture、content.move。
版本化结果封套
| 选项 | 输出 |
|---|---|
| 默认 | 简洁彩色英文文本 |
--json、-J |
一个 JSON oink.result/v1 对象 |
--yaml、-Y |
一个具有相同结果字段与类型的 YAML 文档 |
--verbose、-v |
全部发现、覆盖明细与工具日志 |
--no-color |
无颜色英文文本 |
只能选择一种结构化格式。非空 NO_COLOR 或 TERM=dumb 也会关闭文本颜色。
结构化输出不添加终端颜色,工具日志写入 stderr。--format json|yaml 与
--non-interactive 保留为隐藏兼容选项;所有命令均不交互。
默认文本展示状态、计数、最多八条活动发现及明确的未检查覆盖。详细事实与已审阅 发现保留在结构化结果中。计划与升级预览展示完整 diff。Cobra 管理命令分发与各级 帮助。CLI 提示采用 ASD-STE100 风格的简短主动英文句;用户内容与 外部工具证据保留原语言。
| 字段 | 类型与含义 |
|---|---|
schema_version |
字符串;本契约使用 oink.result/v1 |
version |
字符串;CLI 构建版本,开发版本保留相应后缀 |
command |
字符串;请求的命令,或 help / version |
site |
可选字符串;可取得时的选定源目录或生成目标目录 |
exit_code |
整数;下文定义的 CLI 结果码 |
diagnostics |
发现项数组;空数组表示没有记录发现项 |
coverage |
带范围的覆盖声明数组;调用者必须将它与发现项一并检查 |
evidence |
子进程记录数组;未运行子进程时为空 |
data |
可选的命令专有 JSON 值;当前命令返回诊断事实、初始化来源或升级计划等对象 |
首版允许添加字段与新规则 ID。消费者应忽略未知字段,将 ID 作为不透明字符串, 不解析其拼写。改变已有封套字段的含义或类型,需要新的 Schema 版本。命令专有事实 与原始工具输出属于证据,不是供用户导入内部 Go 包的 SDK。
机器可读 Schema 随 CLI 仓库提供,路径为 schema/result.v1.schema.json。
每份证据记录包含 command(参数数组)、可选的 directory、stdout、
stderr 和子进程自己的 exit_code。捕获的诊断与构建输出保留在结果中。
dev / build 直接流式输出的内容进入日志流,不再重复缓冲进证据。子进程状态与
CLI 的 0 / 1 / 2 结果码不同;负的子进程状态可能表示未取得正常退出码。
JSON Schema 定义了该结果封装的结构。
发现项、严重度与位置
每个诊断包含 rule_id、severity、message、action,以及可选的 location。
稳定规则 ID 标识问题条件。原始 Hugo 文案、翻译后的消息、路径和特定构建细节不是
稳定 ID。已有 ID 不得重新分配给不同条件。
可选的 incomplete: true 标识必需工作失败,政策不能降级这种失败。
经过审阅的排除项和基线确认仍保留在 diagnostics 中,附带
disposition: "excluded" 或 "baseline",
以及含 reason、reviewed_by 和 RFC 3339 reviewed_at 的 review。
被排除的问题保留已记录严重度并保持可见,但不阻断已完成的政策检查。
任何未完成的必需覆盖(包括 not_checked)都决定退出码 2;
只有 complete 或 not_applicable 满足必需覆盖。
严重度取值为 info、warning 和 error。error 是阻断项,info 与 warning
是信息或建议项。但如果所需 Hugo 构建因
构建或归属产物检查失败,则必要工作未完成。信息性的完成说明与范围解释放在覆盖
详情中。自动化必须读取结果退出码与覆盖状态,不能只统计严重度。
提供 location 时,其中包含 file,以及可选的 kind、line 和 pointer。
kind 区分 source 与 output。渲染产物中的问题指向实际产物,可以在 pointer
中给出元素、属性或 JSON 位置提示;这个字段并不统一承诺采用 RFC 6901 语法。
只有明确知道行号时才提供 line。渲染链接失败不能成为编造 Markdown 源码行号的理由。
CLI 不会仅因生成的编辑器 Schema 未列出某个字段,就拒绝合法的自定义 front matter。 配置有效性继续服从 Hugo 与所属主题解析器、检查器;参见 生成式 Schema 决策。
覆盖范围与退出语义
每个覆盖条目包含 id、status、required(布尔值)和 detail,描述实际运行的
范围。一份报告可能对同一大类提供多条声明,应全部检查。
| 状态 | 含义 |
|---|---|
complete |
所述操作、检查或产物检查范围已完成 |
not_checked |
本次没有检查所述范围 |
not_applicable |
对于当前输入,无需执行所述检查 |
unsupported |
不支持所述契约或必需输入形态 |
incomplete |
所述工作属于必要项,但未能完成 |
| CLI 退出码 | 含义 |
|---|---|
0 |
请求中的必要工作已完成,没有阻断项 |
1 |
已完成的检查发现政策问题,例如损坏的本地链接或不安全的写入请求 |
2 |
必要工作未完成,包括参数、工具、构建、I/O、取消或必需契约不受支持等失败 |
未完成状态的优先级高于政策问题。未完成的必需覆盖不能返回成功;
只有 complete 或 not_applicable 能满足必需覆盖。
Hugo 构建失败时保留原始证据并停止产物验收,不会把旧产物或部分产物报告为检查通过。
未启用的可选机器输出不构成缺失输出错误。
渲染引用范围包括受支持的 HTML URL、锚点和已输出的机器契约。覆盖声明明确排除 浏览器交互、无障碍、视觉呈现、外部 URL 可访问性、托管重定向及生产部署,也列出 未检查的动态资源与内容语义。静态产物检查覆盖已声明的翻译配对与渲染文件;浏览器 执行与译文语义审校仍需独立完成。
Hugo 公共 Page.OutputFormats 按页面和启用语言给出预期产物名称与 URL。隔离
副本添加带有本次运行唯一标识、不会进入普通列表的探针;每个启用语言都必须输出
自己的有效清单。产物检查前会移除已识别的探针文件,不改动已有页面选择的输出。
未进入普通列表的静态内容通过 Hugo GetPage 解析,不从源码语法推导路由或输出
文件名。枚举还提供每种语言的生效 base URL 与本地搜索设置。已启用且受支持的机器
产物按这些确切预期检查;未启用的可选输出仍然可选。
同一 Hugo 探针通过公共 Page.Path、Page.File、Page.Translations、
Page.Aliases 和 Page.OutputFormats 提供 data.pages,保留语言、实际 URL、
发布设置、翻译关系及声明输出。没有可靠来源时,sourceKnown: false 和
sourceScope: "unknown" 明确说明未知;生成分区不会得到虚构的源文件。
站点所属的已知路径相对于选定站点,已复制的已知依赖输入明确标记依赖范围。
这些事实描述生产视图;独立的内部分析视图可以包含草稿、未来和过期页面,
但不改变生产产物,也不把它们声称为已发布。
data.references 记录实际观察到的 HTML 和机器输出引用、解析后的实际 URL、
产物文件/位置、存在时的本地目标和已检查时的锚点状态,不推断 Markdown 源码行号。
页面与引用数据仍是可增补的命令证据,不是公开 Go SDK。
项目检查政策
选定站点根目录可以提供普通文件 oink.yaml,使用
schema_version: oink.policy/v1,管理检查选择、严重度覆盖、经审阅的问题排除、
外部 URL 范围、翻译范围、受保护正文声明和可选基线文件路径。
语言、标题、URL、菜单及配置继续归 Hugo 输入所有;依赖版本归模块文件。
符号链接、未知字段/分组、不支持版本、无效审阅元数据或多个 YAML 文档属于必需输入失败
(退出码 2)。诊断与检查只读取这项政策。
没有政策时,链接、翻译和风格均启用且必需。check links、
check translations 或 check style 显式选择一个必需分组,不受政策选择影响。
未选中或禁用的可选分组报告 not_checked。每次检查仍保留必需的 Hugo 构建与诊断
构建和输出枚举前提。独立检查的翻译与源码引擎另用显式且不可发布的草稿/未来/过期
分析视图,从不替代生产产物。受管理的 build --check 只渲染生产视图,遇到未知且
必需的范围身份时返回 2。
rules 映射向确切且不透明的规则 ID 指定 error、warning 或 info。
经审阅的 exclusions 项须有 rule_id、规范相对 file glob、reason、
reviewed_by 和 RFC 3339 reviewed_at;不支持 ** 和路径逃逸形式。
站点内部源码位置使用相对站点路径匹配;选定站点之外的源码路径不能匹配排除项。
问题及审阅元数据保持可见。必需构建、输入、工具或覆盖失败不能经严重度更改或排除变成成功。
同 origin 但位于配置 base path 之外的 HTML 引用属于政策问题,除非经审阅的
external_scopes URL 声明其为单独部署的路径范围。每项范围要求同样的审阅元数据,
以及不含凭据、query 或 fragment 的绝对 HTTP(S) URL;匹配按完整路径段进行。
范围不能豁免项目内部缺失目标或机器输出的必需本地目标。
不同 origin 的引用在离线静态检查中仍明确标记为未验证。
翻译政策与审阅证据
translations.scopes 按规范绝对 Hugo Page.Path 前缀选择源页面,再通过 Hugo
翻译身份寻找目标,不从文件名推断公开路由或语言。每项范围包含 path、
source_language、required_languages、mode 和 drafts。
mode 默认 localized,也支持 strict。drafts 默认 include;
ignore 排除草稿源页面/目标,require-published 要求选定源页面和必需目标实际
存在于生产发布视图。Hugo 已知但禁用的语言为 not_applicable;未知语言属于
无效政策。最具体的匹配路径决定源页面所属范围。
没有范围时,检查以 Hugo 已启用默认语言为源的已有配对及重复关系,不要求全站普遍
本地化;translations.coverage 将未配置语言覆盖记为可选 not_checked。
缺失必需目标和选定关系重复属于政策问题。草稿/发布状态与审阅状态分别记录。
约束均须显式选择:严格模式的 explicit_ids 比较完整的已识别显式 ID 映射,
本地化模式要求选定 ids 列表。ids 要求两份文件均有指定 ID,placeholders
比较指定正文字符串的确切数量,code_labels 保护指定语言/info token 的围栏代码。
required_fields 要求双方指定的点分 front matter 字段非空;equal_fields
比较其实际值。默认没有规则要求标题数量、翻译正文或所有代码块一致。
.oink/translations.json 使用 oink.translations/v1。显式审阅记录绑定 Hugo
源/目标 ID、源语言、完整源文件/译文的字节 SHA-256、审阅人、理由和 RFC 3339
时间。无记录为 unknown;哈希相等为 current;仅源、仅译文或双方改变分别为
source_changed、translation_changed、both_changed。这些状态按字节比对审阅后的
变化,不依赖文件修改时间。来源未证实则保持未知;
已有审阅或受保护约束无法验证时,返回必需工作未完成。
原生内容规则与覆盖
源码规则从 Markdown 结构和独立的已启用 Hugo 属性提取证据,保留原始 UTF-8 字节、CRLF/BOM、源码偏移及含未知字段的 YAML/TOML/JSON front matter。 实际生效的 Hugo 属性开关和数学透传分隔符控制识别。围栏/行内代码、短代码主体、 原始 HTML 和透传内容不会成为正文或虚构标题。不支持的正文语法保持可见; 必需源码覆盖不能静默通过。
通用规则检测重复的已识别显式 ID,并检查 style.protected 声明中的 file、
确切正文 literal 和预期 count。有界 OINK v1.1.0 与 v1.2.0 目录另提供代码/表格属性、
弃用 front matter 和被丢弃的不安全属性建议。每项规则在
data.native_rule_provenance 中记录模块、版本、不可变 revision、模块 sum、
许可证及确切来源文件的 SHA-256。
只有实际挂载的公开 v1.1.0 或 v1.2.0 模块缓存输入匹配相应版本记录的哈希时,
才运行该目录。后续版本必须经过明确的源码审计,才能启用原生主题规则。
replacement、vendor 副本、其他版本或未知来源不选择最新主题回退:
native-theme-rules 为可选 not_checked,通用语法检查仍运行。
基线与经审阅文件计划
baseline 选择规范相对文件,默认 .oink/baseline.json,使用
oink.baseline/v1。捕获要求工作已完成并有显式审阅元数据。指纹绑定确切规则 ID、
规范化位置/指针和条件消息,不包含严重度。已确认问题保留原严重度,附带
disposition: "baseline" 并保持可见;新条件仍按政策阻断。
必需但未完成的发现项或覆盖不能被确认豁免。
审阅和捕获预览 oink.plan/v1:选定编辑、可读 diff、基础存在状态/字节/模式、
修改后字节及只读保护条件。计划 ID 不包含可变的验证/应用/恢复状态。
--plan FILE 排他保存计划;这些命令不接受 --write。
plans apply FILE --site DIR 要求确切选定站点、通过相同检查重新验证隔离候选,
并在任何写入前重新检查源码保护条件。逃逸、.git、符号链接和非普通文件受保护,
候选与源码目录重叠会被拒绝。过期计划安全失败。可选 external_inputs_hash 将
捕获的非站点输入字节、完整模式和清单绑定计划 ID。这个不透明 SHA-256 不授予
外部路径或读取权限;归属验证器比较新证明的输入,选定写入前后重新核对可信原始
外部保护条件。
排他安装保留提交期间新创建的文件。部分失败只还原本次拥有且未变化的写入,保留 后续编辑器字节、模式或删除状态。报告的恢复目录保存原字节/模式及实际捕获的并发 证据。无关文件和编辑器新建子文件均保留。文件内容不能授权 shell 执行或发布。
捕获页面查询与影响
inspect PAGE 按精确的 language:path ID、Hugo Path、permalink 或已证明的
站点源文件选择实际 Hugo 页面。已知默认语言可消解同一 Path 的多语言匹配;仍有
歧义或未知选择器时返回必需未完成 2。data.inspection 展示源码字节哈希、完整
模式、实际输出身份、观察到的入站/出站引用、翻译与物理 bundle 附件。物理附件与
观察到的发布资源分别记录。
impact --since REF 比较捕获的当前输入与隔离的 Git 已提交树,二者由同一 Hugo
引擎渲染。保留已删除的旧页面与其入站边,纳入未修改的引用页面、翻译同伴、附件及
实际派生产物。全局或不确定输入扩大因果范围;alias 等无法证明页面归属的实际
HTML 输出也会保守扩大为全范围。不会按 alias 声明猜测路由归属。只有证明归属 Git
模式范围的历史输入比较可执行位;其他模块/外部输入与当前事实保留完整模式。
历史 materialization 读取有界 Git 对象,不运行 checkout hook、filter、smudge 或文档内容。上限为 10,000 个文件、单文件 16 MiB、树总计 128 MiB;必需的私有 历史上限为 256 MiB。符号链接、submodule、超限/缺失对象、必需历史不完整及不支持 的 monorepo GitInfo 均明确为未完成。已提交的站点内部依赖可被证明;当前外部本地 replacement/workspace 字节不能替代历史证据。
data.impact.baseline_state 为 complete、incomplete 或 unavailable。
必需基线不可用时返回 2,保留全部已知当前页面、附件、引用与输出,并扩大范围。
不虚构旧页面或变更;只记录实际解析出的 commit。check [GROUP] --since REF
有意执行完整当前检查,并声明 data.check_scope: full,保持完整的验证覆盖。
data.impact.full_scope 单独描述因果不确定性,与验证范围分别表达。
完成的 inspect、impact 事实查询返回 0,即使单独报告的
data.current_check 含已完成质量发现 1。必需捕获失败仍为顶层 2。
check --since 保留当前政策的质量退出码和必需完成状态优先级。
context 已移除,页面事实可通过 inspect 的 JSON/YAML 报告读取。
内容移动计划
move SOURCE TARGET [--plan FILE] 预览物理站点相对文件或 bundle 的迁移。
由实际 Hugo 身份确定翻译同伴与新旧输出。计划包含保留字节/完整模式的文件及二进制
附件、可读 diff、已证明的 Markdown 目标重写、观察到的路由变化及 alias 建议。
不通过重写 front matter 自动安装 alias。原始 HTML、shortcode 输出、经过变换的
目标及源码/输出归属歧义保持为可见人工动作;不修改不透明源码片段。重复的普通
Markdown 目标也可能缺少唯一源码/输出出现位置证明,包括聚合/打印视图。仅 URL
匹配不足以授权重写这些出现位置。物理附件迁移不证明新的发布 URL。资源 URL
变化需要配对实际渲染边及相同产物字节;已证明的处理后图片 URL 不证明绝对原始
资源 URL。未证明的原始 URL 保持人工处理,不按目录迁移构造。
原始 before 检查与临时 route_probe 独立于最终候选检查。临时迁移可能因旧入站
链接产生发现 1。只有最终隔离候选及引用证明通过,计划才标记验证或保存。不支持
的身份或必需捕获失败返回 2;实际最终质量失败保持 1,不能保存可应用计划。
内容移动计划必须保存在选定站点之外。oink.plan/v1 的新增 move 选择器与
source_inputs_hash 绑定完整原始源码清单、字节及完整模式,同时应用外部输入与新
目录保护。应用已保存计划时重新生成原始/迁移 Hugo 证明,并在最终引用验证前要求
期望 plan ID 与文件完全一致。写前重新核对当前保护条件,恢复原始模式而非隔离
副本模式,拒绝或受保护恢复时保留后续编辑者的字节/模式。过期输入或已有新目标
无法提供必需证明,返回 2。只有显式 plans apply 写选定文件;预览不 stage
也不提交 Git 变更。
支持的输入边界
首期完整验证支持普通 checkout 或无 Git 元数据的实际文件,包括复制到隔离目录中
的受支持本地模块 replacement。它不会沿已挂载符号链接或外部挂载项返回用户工作区。
有效挂载范围之外的辅助符号链接不复制到快照,也不视为已验证。使用 .git 文件的
关联 Git worktree 需要实际文件审查副本;依赖 Git 的行为需要副本具有自己的 Git
元数据。
快照排除顶层 public、resources、node_modules、tmp 和 Hugo 构建锁。
挂载项需要这些被排除的输入时,不能静默通过。支持根配置与标准 config 树;显式
配置文件必须在选定站点内,自定义 HUGO_CONFIGDIR 位置会被拒绝。受支持的配置
路径重定位不构成第二套 Hugo 验证实现。
内容适配器(_content.gotmpl)可能生成无法通过受支持公共 Hugo API 完整枚举的
隐藏页面,因此完整产物验证对这类输入返回必要工作未完成。禁用页面类型或选择
render segment 导致某个启用语言缺少探针,也属于未完成。多主机语言配置不在首期
完整检查范围内,返回必要工作未完成;单主机的多语言路径仍受支持。上述情况不能
被报告为成功的部分检查。
普通内容计划
new BUNDLE --title TEXT [--language LANG] [--translations LANGS]
[--kind page|docs|blog|book] [--plan FILE] 根据捕获的站点自有内容挂载预览普通
Hugo 叶子包。主语言默认采用生效默认语言,选定译文必须是不同的已启用语言。
共享文件名与语言目录布局跟随真实 Hugo 挂载,包括实际 sites.matrix.languages
选择,不假定旧 lang 字段。含糊、过滤或不支持映射需要人工创作。已有包或占用同一页面的同级内容文件会被拒绝。
主索引 draft: false,选定译文索引 draft: true。标题文本来自显式输入,不会
自动翻译,也不创建审阅记录。完整质量分析和隔离候选验证先于共享受保护计划。每份新文件必须对应恰好一个
实际站点自有 Hugo 源页面且有实际渲染输出,译文草稿使用显式分析视图。仅链接/无
输出、忽略、隐藏或 build-never 内容不能仅凭既有站点构建正常而通过,即使普通源码检查组关闭也须验证
必需来源身份。
--plan 只保存新计划文件,显式 plans apply FILE --site DIR 重新验证源码字节/
模式、存在状态、新目录和后续附件冲突,再应用修改。新目录状态绑定计划身份,
在验证前后、写入之间和完成时核对。回滚保留后续编辑器附件并报告恢复,
不删除无关目录条目。
编辑器设置与 Markdown 片段由站点编辑器管理,editor 与 snippets 已移除。
初始化配置
init DIR [--profile project|docs|blog|book] [--languages en|en,zh|all]
组合同一份内嵌 MIT 许可证 Starter 归档。默认 project 按字节保留此前完整语言投影。
语言选择独立于内容配置,all 表示英语、中文、法语。
显式 docs、blog、book 保留对应归档内容分区及共享首页、资源、示例、工作流与
许可证。原生分区 front matter 定义文档、博客或连续书籍模型与导航。各语言站名/
描述来自其归档分区,已有本地化首页卡片/动作/CTA 投影到该分区。只序列化这些配置
生成的 hugo.yaml 和 data/home YAML,保留内容与许可证字节不变。
没有四份复制 Starter 树,也不在运行时下载模板。
未知配置在候选验证或写入前拒绝,政策退出 1;必需 Hugo 缺失/验证失败为未完成
2。新建/空目标、先候选验证再发布、排他创建与并发编辑恢复保护适用于全部配置。
预备依赖后,普通 Hugo 可以构建生成站点。归档工作流仍为来源示例,init 不生成或
执行 R3 的校验和绑定 CI 模板。
有界升级比较
upgrade --to TAG 现根据同一原始站点输入捕获基线与候选视图,返回可读模块 diff
及完整模式变化。比较记录实际 Hugo 页面/输出/语言设置、生成文件哈希/大小/模式,
以及原始 alias 声明和单独观察的重定向文件。报告删除/新增 URL、已证明重定向、
alias 目标/字节变化及启用语言/输出/搜索变化。
只有在确切旧输出文件处观察到指向对应实际候选页面的重定向,才能证明旧 URL 被保留。
未知/相对定制 alias 身份仍为必需未完成 2;删除此前生成路由或输出为阻断发现 1。
两个实际解析主题版本必须匹配明确选择的 pin;未知/替换 pin、未知/不同的规范 Hugo
版本或环境、意外其他输入变化均保持未完成。比较支持单个 HTTP(S) base origin/path,
多主机输入保持未完成。人工审阅保持
明确的可选未检查覆盖。观察到 alias 改指向另一个唯一 Hugo 页面时,独立于同页 URL
移动而阻断。
升级 v2 计划 ID 绑定选定模块计划、复制源码字节/完整模式/文件清单和规范实际比较。
--expect-plan ID 核对新的捕获/比较,不复用此前成功构建。生成文件哈希也绑定计划,
因此非确定性模板即使源码看似不变,也可能需要重新预览。预览返回前、每次写入前及
写入后重新核对保护条件,包括只读核对已证明的本地依赖/workspace 输入。只写选定
模块文件,回滚只恢复该操作拥有且未变化的文件,保留后续编辑器字节。已有脏目标、
replacement 与 vendor 保护继续生效。
文件保护与发布检查
初始化内嵌完整 Starter 提交并保留许可证。来源清单记录其哈希和每项投影:选择已有 语言配置、确切的公开主题 pin 与校验和,以及为新目录关闭 Git 元数据。候选验证先于 目标写入。排他创建拒绝既有文件;回滚只移除本次操作创建且未发生变化的文件,并保留 并发用户编辑、给出恢复证据。
升级只处理单站点选定的 go.mod 与 go.sum 变更。它保留无关依赖与指令,
--write 拒绝有未提交修改的目标文件;传入审阅后的计划 ID 时核对该 ID,写入前
再次检查目标,并记录备份与恢复信息。无关的脏源文件不应阻止只读诊断,更不能成为
覆盖这些文件的理由。
check --release 关闭 GOWORK 与 HUGO_MODULE_WORKSPACE,并移除子进程的
环境 replacement。它保留 go.mod replacement,报告冲突的本地 OINK 替换政策,
并将 vendor 证据与公开 requirement 分开。升级拒绝 OINK replacement,也拒绝
_vendor;vendor 刷新保留为单独的显式流程。修改模块 pin 不会被描述为已更新
vendor 字节。实际选中 vendor 主题时,--release 将公开来源验证报告为必需但未完成(退出码 2);仅版本元数据相同不足以证明 vendor 字节来自该公开标签。普通 check 仍可验证 vendor 的实际产物。
Hugo 配置中的模块 replacement 也仅在发布快照中禁用。如果 Hugo 在解析或构建时 修改了该快照的模块文件,CLI 会报告依赖输入需要显式预备和审查,保留原始字节, 而不会静默接受依赖未审查模块文件变更的构建。
已检查构建与产物身份
build --check --destination DIR --manifest FILE 在隔离环境中构建生产视图并保留可见告警。
Hugo 只渲染一次;检查引擎检查该产物,再封装并导出同一份字节。CLI 不调用第二个
渲染器生成发布目录,源码 checkout 保持不变。只有结果为 0,且必需覆盖已完成
或不适用时,才能封装产物。阻断项或未完成检查不会产生已验证导出。
这个生产视图不包含单独的不可发布维护渲染。显式翻译范围政策所需的 Hugo 身份
因源码被排除发布而未知时,返回 2。CLI 不根据文件名推断缺失翻译,也不静默
跳过范围。独立 check 与 translations 命令保留完整维护视图。
目标必须为新目录或空目录,且父目录已存在;本地清单必须是产物树之外的新文件。 导出通过独占创建保留准确字节和普通文件的完整模式,不受 umask 影响,并按清单 重新检查源树与目标树。既有条目、符号链接和重叠目录树会被拒绝。部分导出失败后, 目标仍是未验证证据并予以保留。空目录和目录模式不属于发布文件清单。
--marker 可选,默认关闭。它添加 .well-known/oink-build.json,只包含
oink.build-marker/v1 和产物 ID。计算产物 ID 时排除该文件条目以避免循环哈希,
再将其准确摘要纳入最终清单。既有标记路径会被拒绝。本地清单不会自动复制到公开
产物树。
单独保存的 oink.artifact/v1 清单记录源码输入哈希、已知源码 Git revision 与
dirty 状态、实际解析的主题身份、CLI/Hugo 版本、生效环境/base URL/发布设置、
必需覆盖、实际 Hugo 路由上下文,以及每个文件的相对路径、大小、完整模式和
SHA-256。规范 URL 和 HTML 语言来自实际 HTML;Hugo 语言键单独保留。
未知 Git 状态仍是未知。原始输入字节和模式在添加临时探针、重定位 workspace 或
replacement 路径之前捕获。公开清单不包含本机绝对路径、任意参数、进程日志或
覆盖条目的自由文本。哈希用于记录文件字节身份。
受管理构建仅接受 -- 后的布尔 Hugo 参数 --minify、--gc、--ignoreCache
和 --noTimes,包括 =true/=false 形式。其他透传参数属于不支持的输入。
普通 build 保持既有透明透传行为。生效的示例/本地地址在普通诊断中是警告,
在已检查发布构建中是错误。--release 仍需独立的实际公开主题解析证据。
本地产物与部署验证
artifacts verify --artifact DIR --manifest FILE 只读且离线,比对准确文件集合、
字节、大小和完整模式。文件修改、缺失、新增、不安全或模式变化会使身份失效
(1)。无效清单、不可读输入以及不支持或中断的检查返回 2。上传器消费产物
之前应立即重新验证;后续编辑不能沿用先前的成功结果。
verify --site URL --manifest FILE --network 显式授权 HTTP 读取。它按清单限制
响应大小并比对解码后的字节摘要,检查每个声明文件和不同的实际 Hugo 路由 URL,包括全部语言与
子路径上下文。已记录的 HTML 规范 URL/语言值和启用的标记也会接受检查。
共享 URL/文件的请求可以合并,但保留其上下文。HTTP 无法验证本地文件模式位。
错误正文、soft-404、错误路由、已捕获规范 URL/语言值变化或错误标记是确定的
发现项(1)。超时、认证失败、限流、服务不可用或缺少必需标记属于未完成工作
(2)。跳转离开选定 origin/base path 时会被阻止;命令不发现或发送凭据。
静态构建检查不执行部署验证。构建联网权限不授权后续验证请求或上传。
已撤下 CI 生成
移除 ci init。CI 配置保留在站点或 Starter 中。plans apply 拒绝旧 CI 计划。
显式工作区登记
登记与可选工具边界通过冻结归属/运行时、实际协议、四消费者一致性/保护及 规范源码/渲染门禁。A07 适配器与 A15 工作区受支持范围已在 维护记录中本地接受。
工作区是一份显式指定的 YAML 登记文件,独立版本为 oink.workspace/v1。
它只包含站点名称与目录:
登记文件必须是非符号链接的普通文件,只包含一份 YAML 文档和已知字段,登记
1–64 个站点,最多 256 KiB。名称符合 [A-Za-z][A-Za-z0-9_-]{0,63},区分大小写。
目录是相对登记文件实际父目录的字面路径,或绝对路径;不展开变量、glob 或扫描同级
目录。显式目录符号链接与操作系统路径别名解析到规范身份。拒绝重名、实际根目录
重复或重叠、文件系统根目录、悬空符号链接,以及非目录祖先。若缺失目录有已证明的
现存祖先,仍可列出;检查该站点返回 2,不会阻止后续选定站点继续检查。
workspace list|check [GROUP] --workspace FILE [--sites NAME,NAME] 在省略
--sites 时选择全部登记站点。显式选择必须使用准确、非空、不重复的登记名称;即使
参数顺序不同,仍保留登记顺序。list 不需要 Hugo 渲染器。check 复用单站引擎、
各站自己的 Hugo 输入与 oink.policy/v1 政策,不在登记文件中复制 Hugo 配置。
现有 oink.result/v1 封套包含 data.registry、selected_sites、
sites: [{name, path, result}]、completed_sites、finding_sites 和
incomplete_sites。每个子项是完整单站结果。已完成站点包括退出 0 与 1;发现
问题的站点是退出 1 的子集。只要有选定站点未完成,汇总退出为 2;否则有阻断项
时为 1,其余为 0。人类可读输出包含逐站结果与发现项,不推断未选站点已完成。
受支持的单站命令接受 --workspace FILE --site NAME,必须显式选一个登记名称,
没有默认站点。init、artifacts 与 verify 不接受这种选择。
保存的 plans apply FILE 必须绑定选定规范目录;改选其他登记站点时,在写入前
返回 2。不自动批量应用计划或升级。既有候选验证及源码/依赖字节与模式保护条件
继续生效。列出或检查登记不会创建缺失站点、安装工具、提交或写入消费者配置。
可选检查适配器
各站 oink.yaml 中的显式 tools 项选择已预备的可执行程序。这些项扩展
oink.policy/v1,不是另一份 Hugo 配置,也不是安装器。每种工具包含 enabled
(默认 true)、required(默认 false)、command(默认与工具种类同名)、
config(提供时为站点内干净相对路径的普通文件)与 timeout_seconds
(默认 60 秒;非默认值限 1–300)。命令是单个可执行文件名称或绝对路径,不能是
shell 表达式。
| 种类 | 归属检查组 | 当前支持协议 | 配置边界 |
|---|---|---|---|
markdownlint |
style |
markdownlint-cli 0.49.1 |
可选声明式 JSON/YAML/TOML;不支持 JS、JSONC、自定义规则或 extends |
vale |
style |
Vale 3.24.0 |
显式 INI 与已捕获的受支持声明式风格子集 |
lychee |
links |
lychee 0.24.2 |
可选有界请求设置;显式联网授权 |
未配置的工具不会自动发现。工具不属于选定检查组时,明确显示 not_checked。
已配置的可选工具若缺失、不受支持或无法完成,会保留遗漏;必需工作未完成返回
2,不能通过规则严重度、排除项或问题基线降级。不能同时设为必需和禁用。
已完成的类型化发现项仍按政策处理:阻断项返回 1。未知工具版本或无效协议输出
不能算作检查完成。
data.adapters 记录每种工具的必需属性、状态、类型化诊断、adapter.KIND
覆盖、原始进程证据、遗漏与来源。来源包含已观察的受支持版本、可执行文件 SHA-256、
捕获配置/风格路径及其 SHA-256 和完整模式,以及固定的公开协议源码。工具日志写入
stderr 和证据;JSON stdout 仍只有一份结果。每个进程的时间与输出受限;可执行文件、
捕获配置发生变化,或工具修改私有输入时,其证据失效。
文字工具接收已证明站点自有 Markdown 的私有遮蔽副本。Front matter、BOM/CRLF 与 UTF-8 偏移、代码、短代码、原始 HTML、已配置数学公式与属性保留其源码边界。 代码正文不参与源码归因;markdownlint 仍能读取 Markdown 结构和围栏/行内代码 边界,Vale 使用纯正文遮蔽。触及排除区域或遮蔽生成文本的发现项不会归因到原文。 只有已证明的原始行/范围才输出源码位置;不支持语法与被抑制发现项保留可见遗漏。 适配器不格式化或改写原文。
Markdownlint 通过不可预测的生成 JSON pointer,在上游 rc 合并之后隔离捕获的规则
对象。拒绝可执行配置、自定义规则加载器与递归 extends。Vale 使用显式捕获 INI、
--no-global 和复制的声明式风格;不支持 sync、packages、actions、scripts、转换
或风格流水线。Lychee 接受有界 timeout、max_retries 与 max_concurrency
设置,以及字面 cache = false;缓存保持关闭,拒绝 cache = true。拒绝预处理器
与任意命令选项。
默认离线。没有显式 --network 时,不调用 lychee,连版本探测也不运行:可选覆盖
为 not_checked,必需覆盖为未完成 2。它只接收实际 Hugo 输出观察到的外部
HTTP(S) 引用;本地链接仍归原生检查。确定失败的 4xx 响应属于政策发现项,但
401、403、408、425 与 429 除外;这些状态、5xx、DNS/TLS 失败和
超时属于不确定结果,必需时返回 2,可选时保留遗漏。不验证外部片段、浏览器行为
或远端内容身份。发现项保留渲染输出文件与 DOM pointer,不从外部 URL 臆造
Markdown 行号。
子进程不接收调用者的代理 URL/凭据设置或 Node 预加载变量。已验证运行时可以传入
字面的 NO_PROXY/no_proxy 主机列表数据。工具预备与任何联网操作仍是独立显式动作;这些
命令不安装工具。
离线与兼容性边界
受管理子进程默认离线。依赖缺失属于未完成工作。--network 为当前操作显式启用
联网,不能与 --offline 同时使用。隔离检查可以从已准备的本地模块为临时缓存提供
依赖。在临时缓存下载,并不承诺下一次调用拥有持久缓存。
只有模块下载制品会用于预备缓存,隔离资源缓存从空目录开始。CLI 不复用全局
GetRemote 缓存来承诺远程资源构建可离线运行。必需资源应作为本地输入提供,或为
该次操作显式启用联网。
CLI 不下载 Go 工具链,不安装软件包,不修改全局配置,也不启用遥测。
当前开发与验收基线是 Hugo Extended 0.168.0;版本门禁要求 0.168.0 或更新版本。禁止启用 --panicOnWarning 或 HUGO_PANICONWARNING;告警保持可见,上游 LibSass 弃用告警允许出现。新建站点使用 CLI 独立维护的 Starter 快照,主题升级遵循升级指南。初始化站点保留普通 Hugo 输入,移除 CLI 后只需要站点文档要求的依赖。
已撤下本地 Studio
CLI 移除 studio。使用普通编辑器与 oink dev 预览站点;通过 inspect
及结构化报告读取维护事实。带日期 R7 验收保留为对应输入的历史证据。
已撤下管理 API
CLI 不再提供管理 API,旧 Studio API 验收不代表当前可执行程序。
历史捕获限制
历史 R7 限制归属带日期验收记录;当前命令覆盖与输入范围以本契约为准。
已撤下通用编辑
移除 edit 与 Studio 编辑。使用普通编辑器修改源码,再运行 check。
new、move、审阅记录与基线计划继续保留候选验证和字节/模式保护。
旧编辑计划会被拒绝,带日期 R8 记录保留为历史证据。
已撤下文本和字段编辑
CLI 不再承担通用文本或 front matter 编辑表单。
已撤下片段和附件编辑
使用站点编辑器编写 Markdown、添加附件。CLI 不再提供片段目录或通用附件编辑命令。
已撤下 Studio 编辑
CLI 不提供编辑器,也不接受浏览器 Apply 请求。
验证与剩余范围
使用 make test 验证离线 Go 测试与 vet;使用 make test-hugo 验证真实 Hugo,
并为已配置可选工具运行 make test-tools。
在 macOS arm64、Go 1.27.1 与 Hugo Extended 0.166.0 上,make test
(离线 Go 测试与 vet)和 make test-hugo 均通过。冷缓存配置读取失败已修复:
两处 JSON 读取器接受前导 hugo: 模块收集提示,保留原始证据,并拒绝未知前缀
或畸形 JSON。确定性回归测试在每次配置调用前强制加入该提示。
原生规则也通过公开 v1.2.0 来源的审计检查。可选工具适配器仍由独立的
make test-tools 门禁验收。
带日期维护验收记录 保留旧 R1–R8 与 A18 证据。声明目标为 macOS arm64、Linux arm64/amd64, Darwin amd64 是未取得资格的实验目标,Windows 不受支持。
7.7.6 - Paper 与 Slate 视觉预设
Paper 与 Slate 已随 1.2.0 发布。Ink 与 Terminal 作为显式启用的风格一同提供, 后续设计工作见下文记录。
决策
Paper 成为默认,使用暖纸色与墨色、蓝色链接、IBM Plex Sans、标题细线和外框
表格。Slate 保留 v1.1.0 色板、Inter/Chakra/Plex Mono 字体角色与 Landing 网格、
光晕。这样为阅读站点提供更安静的默认外观,同时保留明确的兼容选项。代价是默认
外观发生可见变化:原站点可设置 params.ui.preset: slate。1.2.0 发布注记与升级指南已醒目说明此变化。
读者菜单默认关闭(preset_menu: false),文档站开启。一个外观入口包含原生
风格与明暗单选组,手机通过浏览器顶层模态 dialog 显示底部表单。触屏和键盘无需
悬停即可使用。代价是原来单击即切明暗变成选择面板;t 快捷键仍可直接切换明暗。
风格与明暗使用不同的属性和存储键。选择站点默认预设清除风格键。Hugo 在无 JavaScript 时输出默认值,白名单内联脚本在 CSS 前恢复读者选择,避免初始预设 不一致;禁用存储时仍可操作。预设共用一个样式表,代价是增加少量 CSS。
brand 将字标与展示标题分开。Paper 新增本地 OFL IBM Plex Sans 可变字体,包含
正体、斜体和六个小型文字系统子集,按实际使用下载。系统排版与显式字体角色覆盖
仍优先,中文使用系统栈。第一阶段不加入衬线字体,不新增外部字体请求。
密度由页面任务决定:首页保留展示尺度,长文保留阅读行宽,导航与参数表保持紧凑。 不统一扩大间距,不引入第二套外壳或几何抽象。Giscus 和打印跟随预设,API 供应商 组件与图表保持现有的明暗行为,以此约束第一阶段范围。
后续工作
2026-10-24,维护者决定移除 Paper 标题右侧的尾随细线,减少视觉干扰。OINK 1.4.0 的实现 在浅色与深色模式下均移除这条线,保留标题字体与锚点。此修订随 OINK 1.4.0 发布;上文的 1.2.0 决策保留当时的外观记录。
10 月 5 日随后开展的实验在显式配置后提供 Ink 与 Terminal,见
实验记录。两者尚未成为
稳定默认选项。preset_menu: true 提供 Paper/Slate 与站点默认值;显式列表可以
展示实验。四种风格共用简洁的图标与名称按钮,不另加实验标记。这样可用真实主题
输出评审,同时保留普通菜单的
选项范围,代价是额外的局部 CSS,以及开启实验后更大的菜单。
实验以主题自有组件规则实现直角/2 px 圆角与紧凑桌面导航,不引入全局密度框架。 复用现有本地字体、状态管理与无障碍控件。图表和 API 供应商组件仍只随明暗变化, 评论色板与打印跟随实验预设。剩余工作是视觉定稿、更广设备评审,以及是否晋升为 稳定选项的决定。
依据
2026-10-25,维护者授权在既有四种预设中精修组件。共用表面、分组行、稳定文字和 仅背景移动的按压反馈,均复用现有变量与 CSS。Markdown 仍是主要写作方式,三个 本地呈现变体均需显式启用。Paper/Slate 保留圆润表面,Ink/Terminal 保留扁平与 直角/2px 几何。滑动选中背景试验暂缓:静态状态已经提供所需反馈,无需额外位置 测量或运行时层。这些修改随 OINK 1.4.0 发布;支持的写法与回退行为归属 组件契约。
架构契约与
外壳契约管理当前行为。
check-presets.py 管理 token 对称、AA 色板、冻结的 Slate v1.1.0 色板及严格配置
输出。字体、参数、vendor、命名空间、动作与运行时检查继续沿用原归属。文档站的
appearance.spec.mjs 检查真实输出;带日期验收记录
区分已执行检查与后续实验。
7.7.7 - 四种视觉预设的组件精修
背景
Cards、折叠区、Tabs 和代码控件逐渐积累了不同的间距、边框与反馈方式。 这次精修保留熟悉的创作语法,让相关组件的呈现更加一致。 带日期的评审记录了 实现与验收证据,包括最初的分阶段计划。
已接受的样式规则
沿用现有的页面、内容与浮动表面 token。独立卡片拥有自己的边界,分组列表共用 外框和内部分隔线。嵌套代码块与表格避免叠加多余边框。
按下反馈保持文字与点击区域不动。为本地化复制反馈预留空间,只有复制成功才显示 成功状态,失败时保留明确提示。键盘焦点立即可见,触屏控件具有足够的点击区域。 减少动态效果时仍保持清晰的状态变化。
Paper 与 Slate 保留圆角表面,Ink 保持方正扁平,Terminal 保持紧凑扁平。 Paper 去掉标题右侧的装饰尾线。Ink 与 Terminal 仍是显式启用的实验性预设; 组件精修没有把它们升级为稳定预设。
可选创作变体
默认网格、步骤与代码块继续可用。作者根据内容选择需要的变体。
分隔式卡片列表
呈现为带内部分隔线的单列分组。完整短代码支持 cards variant="list",
保留条目顺序、链接与描述。
紧凑命令条
显式标记的单行命令显示为紧凑条,复制控件独占空间。多行、带行号或可折叠的 代码块会发出警告并保留普通呈现。源码与复制文本保持完整。
紧凑步骤
完整短代码支持 steps variant="compact"。较小的标记与间距适合短流程,
保留编号、嵌套与锚点。不支持的短代码变体会发出警告并回退到默认呈现。
范围与兼容性
同一套规则也用于表格、Fields、徽章、Landing 操作与外壳列表行。 继续由现有渲染器、剪贴板逻辑与 token 负责,不增加 JavaScript 依赖、字体下载、 全局密度选项或外部 Sass 可执行文件。原生 Markdown、无 JavaScript 阅读、 打印与机器可读输出保留完整内容。
移动选择背景经过评估后暂缓,OINK 1.4.0 保持静态选中反馈。后续动效试验 需要单独验收。
验证与交付
主题专项检查覆盖组件、内容原语、代码块、阅读、外壳、预设与命名边界。 真实站点的组件对照与外观测试覆盖中英文、深浅色、桌面与移动端、键盘、触控、 减少动态效果与打印。三个变体和共用样式均随 1.4.0 发布。
7.7.8 - Expo 图片收藏布局
Expo 随 OINK v1.3.0 提供。
背景
Docs 组织说明,Blog 组织文章,Book 组织章节。Expo 按主题和相册组织图片收藏。 读者需要浏览相册、选择图片,并查看细节。
Graph 实验使用已有收藏的独立副本,包含 149 个相册、7,658 张原图。 其中有不同尺寸的图片、长图、GIF、说明和已有引用锚点。 这份收藏提供了最初的内容与规模需求。
选择
每个相册对应一个页面包
一个相册对应一个 Hugo 叶子页面包,包含 index.md 和图片文件。
文件名提供默认标题。可选的 resources 元数据补充说明、署名、顺序、显式 ID,
以及独立原图路径。
| 考虑过的模型 | 对创作的影响 |
|---|---|
| 每张图片一个目录和元数据文件 | 大型收藏需要管理大量小文件 |
| 每张图片一个 Markdown 页面 | 适合逐图撰写文章,但每张图片都需要页面模型 |
| 每个相册一个页面包,资源元数据可选 | 一个页面加图片文件即可开始,之后按需补充元数据 |
Expo 采用第三种模型。相册拥有页面 URL 和浏览序列,每张图片拥有锚点。 需要长期引用时,作者提供显式 ID。大型收藏由作者自行分册。 当前版本没有资源分页或独立图片数据库。
共享外观与紧凑控件
Expo 是一种布局,沿用 Paper、Slate、Ink、Terminal 四种预设及其亮暗模式。 共享的颜色、字体、导航与圆角设置,让相册与站点其余页面保持一致。
三个相邻的单选格同时呈现展墙、卡片和无缝选项,后接播放/暂停按钮和尺寸滑杆。 图标使工具栏保持紧凑,本地化名称、提示与焦点标记支持键盘及辅助技术操作。
读者的显示选择在切换相册和外观后保留。外观控件位于导航中,图片工具栏负责相册操作,
因此相册主体省去文章操作菜单。文字页面中的图片组仍使用 gallery 围栏组件。
动图与图片查看
GIF 默认播放。暂停控件和系统减少动态效果设置提供静态预览,读者也可主动播放。 懒加载减少初始请求,但大型 GIF 仍需传输原始文件。作者组织动图时需要考虑相册体积。
查看器从原尺寸开始,大图缩小到适屏。放大至适屏取宽、高缩放比例中的较小值, 保证整张图片可见。手动缩放、鼠标拖动和 WASD 平移用于查看细节。 Q/E 和 H/L 切换图片,J/K 缩放,R 重置,F 放大至适屏。 这些快捷键只在查看器内处理,避免同时触发页面导航。
原图链接和相册定位链接保留访问与引用能力。原生模态对话框负责约束焦点。 Expo 只在需要的页面加载相册脚本,普通 Image Zoom 保持原有单图行为。 没有 JavaScript 时,读者仍可使用图片链接并阅读说明。
稳定布局与共享输出
占宽度的滚动条会改变网格宽度,新宽度又可能改变行数与总高度。 当相册高度接近视口边界时,这些变化可能导致滚动条反复出现、消失。 这条反馈链造成了报告中的闪烁,即使查看器没有打开也会发生。
Expo 保留滚动条空间,并在模态框锁定页面滚动时保持网格宽度。 回归测试强制启用占宽度的滚动条,采样连续帧。单张截图无法发现这类故障。
HTML、Print、Markdown、LLMSFULL、RSS 和相册封面共用图片解析器, 使各输出的图片顺序与元数据保持一致。静态格式无需查看器脚本即可使用。 修改解析器时,需要检查每一种受影响的输出。
验证与归属
主题仓库拥有解析器、布局、脚本、schema 与专项检查。 文档站拥有双语示例、浏览器测试,以及桌面和手机视觉检查。 验收覆盖四种预设、亮暗模式、键盘输入、焦点、动态效果与静态输出。
Graph 副本提供更大规模的补充证据。最初验收核对了 7,658 个文件哈希、图片锚点, 以及未改动的源收藏。个人图片数据、构建产物与截图位于主题和文档源码仓库之外。
当前验收命令见 Expo 契约。
7.7.9 - 将博客文章呈现为画廊
本决策记录正式发布的博客画廊视图及其兼容边界。
外壳契约定义行为; 设计博客介绍阅读体验; 创作指南提供配置方法。
背景
有些博客栏目更适合通过封面浏览。现有的水平条目、卡片和表格都优先呈现文章 信息。Expo 已有展墙几何算法,但其相册资源模型与图片查看器面向另一种内容 和点击目的地。
决策
画廊作为第四种 blog_index 形态,沿用其他博客视图的分页结果和特色图片
解析器。文章保留 URL 与元数据,作者无需在相册清单里重复登记。
共享 Expo 的行布局算法与控制样式,博客运行时和读者偏好则独立维护,因为
博客封面点击后进入文章。wall、cards、seamless 是画廊内部的排布选择,
外层视图切换器负责选择四种博客形态。
封面在限定比例的格子中完整显示,接受极端宽高比产生的留白。窄屏进一步限制 格子比例,给描述保留可读宽度。触屏文字显示在图片下方,并计入行高;桌面 键盘聚焦显示说明。没有封面时仍保留文章入口;禁用 JavaScript 时,网格仍 显示标题和描述。
影响
开启视图切换会输出四种形态的标记。隐藏视图使用延迟加载,但生成缩略图仍 需要构建时间。作者可以关闭切换,只输出选中的形态。这项改动不引入远程图片 服务或新的浏览器库。
主题的博客栏目索引负责自动集成。自定义首页和术语页模板需要显式渲染画廊 及其控件;站点配置不会替换自定义模板。
验证与归属
主题的 bin/check-shell.py 检查文章顺序、分页、外部链接、缺失封面与无效
配置回退。配置 schema 跟随主题默认值和解析器;运行时测试检查四种视图的
循环切换与偏好恢复。
文档站的 tests/browser/blog-gallery.spec.mjs 使用中英文合成内容承担浏览器
验收,覆盖排布切换、调整宽度、独立偏好、键盘导航、触屏文字、图片加载失败、
禁用 JavaScript 与画廊无障碍。
7.7.10 - 显式开启表格阅读
本决策记录正式发布的 .expand 表格阅读行为。
组件契约定义行为,表格指南说明作者语法,日期研究保留此前的方案比较。
背景
大多数文档表格适合直接阅读,只有少数扩展包、参数或兼容性表格需要更大的空间。在每个表格上方添加工具栏,会为偶尔使用的功能增加视觉负担。此前原型比较了角落直接展开按钮和悬停显示的省略号菜单。
决策
作者通过 .expand 为单个 Markdown 表格启用原生阅读对话框。.wide 独立控制表格的自然宽度,{.wide .expand} 同时使用两者。不根据表格大小、列数或 .wide 自动启用,也不增加全站开关。
只有一个展开动作时,直接点击即可。在表格右上角放置小图标,桌面端悬停或键盘聚焦时显示,触屏设备常显。图标保持小巧,触屏点击区域至少为 44px。不增加表格上方工具栏或只有一个选项的省略号菜单。
将原表格及滚动容器移入一个模态对话框,保留字号、原生语义、链接、文字选择和作者顺序。关闭时还原同一个节点、表格内部滚动位置、页面位置和入口焦点。编号表格的标题用于命名对话框,不复制 ID。浏览器打印前同步还原表格;没有 JavaScript 或对话框支持时,保留原来的正文表格。
边界
本次不包含排序、筛选、TSV 导出、密度与换行开关、新的固定列控制、虚拟滚动、独立阅读路由或 Fullscreen API。保留现有 .matrix 语义。仅主题渲染的 Markdown 表格可显式启用;.fields 组合会告警并忽略 .expand。headers 或 ARIA 属性引用表格外节点时保持正文阅读,因为模态状态会使外部目标不可交互。依赖原有祖先元素的项目样式,需要检查表格移入对话框后的效果。
对话框继承现有预设和明暗模式 token。Paper 是入口设计的主要审阅对象,其他预设进行兼容性检查,不增加新的视觉变体。移动端数据超过视口宽度时仍需横向滚动。
归属与验证
主题负责表格渲染、按需加载的运行时、本地化和限定范围的 SCSS。bin/check-content-primitives.py 覆盖显式启用、无效组合、本地化和静态输出。文档站的 tests/browser/table-reader.spec.mjs 负责键盘、触屏、链接、选中文本、焦点与滚动恢复、打印、失败降级和无障碍检查。运行时检查与文档站的两个 Make 测试套件仍是交付门槛。
原草案退出活动提案,保留在 Git 历史中。研究记录描述当时的实验,包括本次明确排除的功能。
7.7.11 - 译文对照
本决策记录正式发布的 params.ui.compare 译文对照阅读器。
外壳契约定义阅读器及其控件,架构契约定义浏览器边界。多语言指南说明站点设置,日期研究保留此前的源码调研。
背景
翻译文档常常需要并排阅读和审校。缺少原生支持时,读者只能同时打开两个浏览器标签页并分别手动滚动。如果为此设计专门的布局或在构建期预生成语言配对页面,会导致页面数量成倍膨胀、内容重复。由于 OINK 站点原本就将每篇译文发布为独立的标准 HTML 页面并共享标题 ID,浏览器端可以直接复用这些已发布页面完成双栏并排对照。
决策
只用一个布尔站点参数 params.ui.compare(默认 false)开启对照。Hugo 仅列出 Docs、Book 和博客页面真实存在的同源 HTML 译文;草稿、非 HTML 输出格式、外部域名以及语言导航回退到首页的链接都不计入;构建策略不输出 HTML 的译文(直接或经 cascade 设置 build.render: link 或 never),以及没有对应页的博客归档第二页及之后的分页,也都不计入。在 languages.<lang>.params.ui 下将该参数设为 false 的语言既不显示入口,也不作为目标译文。作者无需添加任何 front matter、新布局或额外输出格式。
阅读器按需打开:可通过页面动作旁的图标按钮、开启键盘导航时的 B 快捷键(再按一次即关闭),或命令面板动作 compare_translations 触发。它在模态对话框中用两个同源 frame 加载现有页面。当前页面固定在阅读起始侧,因此从右向左(RTL)书写的宿主页会自动镜像左右顺序。界面统一称为「双列视图」,入口按钮标签为「打开双列视图」(1.5.0 发布时名为「双语视图」,见修订)。
「同步滚动」开关按共同的可见标题 ID 建立映射,并在相邻标题之间平滑插值。部分匹配时使用可用锚点;没有共同标题时,改按全文阅读进度估算位置;这两种情况由开关旁的橙色感叹号给出说明。已匹配但滚动不到阅读线的标题不算不匹配。若标题 ID 重复或顺序交叉,则自动禁用同步,两栏仍可独立滚动。点击关闭、按 Escape、再按 B 或使用浏览器后退都会恢复宿主页的滚动位置和键盘焦点。在阅读器打开时刷新页面或通过历史记录返回,会在当前历史记录上重新打开阅读器,因此一次后退即可关闭。
屏幕宽度小于 900 CSS 像素时,阅读器改为单窗格切换布局,顶部保留一行紧凑工具栏和一行语言切换栏(同时承载目标语言菜单)。在 320 × 256 CSS 像素(约 400% 缩放)下,正文仍至少保留 160 CSS 像素的可视高度。
窗格文档会停用外壳导航运行时、搜索、评论和主题内置统计。开启对照后,主题会使用自带的窗格感知模板输出 Google Analytics,避免把窗格重复计入访问量;站点覆盖的 Hugo google_analytics.html partial 不再生效。head-end.html 中的自定义脚本可读取 window.OinkComparePane 判断当前是否处于对照窗格。
边界
本次发布不包含跨版本差异对比、手工页面映射、逐段锁定行高、文本 diff 视图或跨源目标。窗格要求 frame-src 'self' 并允许同源嵌入:若响应头带有 X-Frame-Options: DENY 或 frame-ancestors 'none',窗格将无法嵌入并回退为直接打开原页面链接。阅读器打开期间,宿主页加上两个窗格共有三个活动文档;未打开阅读器时不会发起任何额外请求。
归属与验证
主题负责发现译文、按需加载的入口与阅读器运行时、32 个界面语言目录和限定作用域的 SCSS。bin/check-compare.py 覆盖发现规则、Docs/Book/博客页面、按语言关闭、键盘提示、统计、输出隔离,以及每个运行时在窗格中的行为。tests/js/compare-scroll.test.js 覆盖滚动几何。文档站的 test:compare 测试负责同步、生命周期、刷新、窄屏与缩放布局、从右到左顺序、八种语言、失败处理和无障碍。
修订
- v1.5.1(2026-11-06):界面名称由「双语视图」改为「双列视图」(英文 Duo View):阅读器把任意两篇页面并排成两列,也包括简繁等同一语言的不同书写形式,以简短的版式名称命名比强调「双语」更贴切,其他语言各用本地化的简洁说法;入口、命令面板动作、标题和帮助面板随之改名,参数与动作 ID 不变。
B改为在页面与阅读器之间切换,打开后滚动键立即可用。浏览器会记住最后选择的目标语言和同步设置,刷新或前进/后退恢复时还会还原可见窗格和两侧阅读位置。「同步滚动」改为开关控件;提示改为不带底色的橙色感叹号,悬停、聚焦或轻触时弹出说明,并按部分匹配与按全文进度同步两种情况分别说明。已匹配但滚动不到阅读线的标题不再被报告为不匹配;如果没有任何共同标题可以作为锚点,提示会说明当前按全文阅读进度同步。
提案草稿已从活动提案中移除,保留在 Git 历史中。
7.7.12 - 统一图表阅读体验
统一图表查看、源码导出与按需加载随 1.4.0 发布,行为归属组件、架构与浏览器打印契约。
背景
此前 Mermaid 已有平移和缩放弹窗,但页面打开时就加载引擎,并为弹窗再次渲染 SVG。 此前本地 PlantUML 在页面打开时加载两份引擎,仅支持横向滚动。两者都保留作者源码。
范围
共用现有弹窗与样式,提供查看、复制源码和下载独立 SVG。图表距离视口不超过 320px 时才加载大型引擎并渲染。不改变库版本、PlantUML 服务端模式、其他图表 家族、CLI,也不改变普通使用者仅依赖 Hugo 的构建要求。
行为与边界
一个轻量调度器在主线程串行调度两个引擎,每个引擎只加载一次。隐藏或宽度为零 的容器等待显示。主题切换使旧结果失效;已打开的查看器保留阅读位置。查看器复用 已渲染快照,支持触控平移与双指缩放、滚轮、键盘、适应窗口、Escape、焦点恢复 及减少动态效果偏好。错误后仍可查看源码。复制和导出报告失败;下载仅报告已发起。
PlantUML 始终通过 Blob 图片展示,SVG 不进入页面 DOM。导出的 SVG 内含样式,
移除主动内容,并拒绝依赖外部资源。不会下载 include、主题或可选库。浏览器
打印同步显示已渲染图片或可读源码,因为 beforeprint 无法等待异步引擎。
独立 Print、Markdown 和 RSS 保留既有源码回退。
兼容性
不增加配置项或构建步骤。本地 PlantUML 仍需显式启用,显式端点沿用旧渲染器。 没有 IntersectionObserver 时,限频的滚动与尺寸检查执行相同可见性判断。 禁用 JavaScript 或资源受阻时保留源码。
实现与验收
从 assets/js/mermaid.js 抽取共用调度与查看逻辑,保留引擎适配器和弹窗 partial。
更新归属输出检查器、运行时测试、真实中英文文档和浏览器测试。验证同页多图、
宽图、隐藏标签页与折叠区、快速主题切换、错误、资源受阻、源码准确性、独立导出、
触控、键盘、无障碍及打印。比较改造前后的请求。
决策记录
- 2026-10-25:初始有限设计。保留主线程串行执行和 Blob 隔离;立即打印时, 未渲染图表显示源码。本地实现与图表浏览器验收已完成,完整站点验证随交付证据 记录,相关行为随 OINK 1.4.0 发布。
7.7.13 - 本地搜索改进
负责人:OINK 维护者。受影响契约:Shell。 本文描述的搜索行为随 OINK 1.4.0 发布。
证据与范围
此前 CJK 路径要求整条查询连续出现在同一字段中。英文使用 Lunr,两者都只返回页面链接, 没有小节上下文。保留每语言一个本地索引、现有命令、输入法组字、排除规则、关键词权重和 正数页面加权。不引入词典、服务、账号、外部可执行程序、配置键或额外构建步骤。
已接受的行为
按空白及拉丁文字与 CJK 边界拆分查询,保留 shared_buffers、pg_basebackup、
--baseURL 和 /pg/data 等技术标识符。关键词可以分布在不同索引字段中。优先返回
包含全部关键词的页面,其中连续短语优先;仅当没有页面覆盖全部词时,显示明确标注
覆盖数量的部分匹配。在这些层级内部应用页面加权。通过已内置的 Lunr 保留英文词干
匹配和有界的拼写回退。
每页只显示一条结果,选取最相关小节及摘要。直接读取渲染后的标题 ID,不重新推导 slug。 保留语言和部署子路径。正文只存储一次,小节边界使用 UTF-8 字节偏移。 heading 和 summary 范围保留标题锚点,不隐式收录全文。
在现有面板中增加带标签的原生单选组,可选择全站或当前顶层内容栏目。 默认全站,命令与页面操作继续可用。播报结果数量和部分匹配覆盖率,保留键盘选中、 焦点约束及输入法组字期间延迟查询的行为。
兼容性与检查
现有页面元数据与范围配置继续有效。小节是新增索引元数据;引擎仍接受没有小节的旧索引。
主题归属检查包括 check-search.py、搜索引擎与运行时测试以及面板检查。
站点归属检查包括双语真实内容查询预期、根路径及子路径锚点验证、浏览器键盘、组字、
栏目限定、响应式、深浅色和 axe 检查。
在相同真实语料上比较改造前后的结果与延迟,记录页面和小节数量、工具版本、预热和重复次数、
原始及 gzip 字节数。每语言保持在 2 MiB 原始、512 KiB gzip 预算内。
合成案例覆盖易混淆标识符,以及重复、Unicode、显式和含特殊字符的标题 ID。
决策与验收
2026-10-25:记录最小设计后完成本地实现。行为现由 Shell 契约、搜索与命令面板指南负责。
主题检查覆盖四种索引范围、真实 UTF-8 小节偏移、多语言及子路径 ID、旧索引兼容、加权和
按根栏目缓存的对话框。站点维护 tests/search/queries.json 和
scripts/check-search-corpus.mjs,根路径与子路径集成构建均执行 summary 查询门禁。
固定文档源码 ab2747a 每语言包含 149 页、1,321 个标题。43 条查询包含两条摘要范围的
负向案例。预期页面进入前五的数量分别为:英文摘要 7/7 → 7/7、中文摘要 2/7 → 7/7、
英文全文 10/10 → 10/10、中文全文 6/17 → 17/17。这是人工选定的回归预期,不是通用相关性评分。真实语料没有同时包含 连接池 超时 的页面,该项特意检查有标记的
部分匹配;主题合成案例检查完整匹配。
固定摘要索引的原始体积为 185,183/184,281 字节,gzip 为 39,529/43,208 字节 (英文/中文),均在预算内。全文诊断索引原始体积为 1,640,294/1,516,283 字节, gzip 为 536,971/578,674 字节,超过 gzip 预算。维护中的站点保留 summary 范围; 包含全部历史研究正文的配置不能通过预算要求,实现也不会静默截断这些内容。
浏览器验收覆盖中英文、390/1440 px、深浅色、真实锚点跳转、可见且可播报的部分匹配、
原生范围选择、博客与书籍根栏目、组字和命令保留。本地验收制品目录为
/Users/vonng/tmp/oink-search-20261025/,保存准确命令、JSON 计时、日志与截图。
这些带日期的测量仅针对当时语料;已接受的行为随 1.4.0 发布。
2026-10-26:修复索引读取时的跨输出上下文污染,增加多输出归属检查, 完成此前失败路由的无障碍与文件树复验。
2026-10-26:根据视觉反馈优化面板。两个范围选项改为紧凑的分段样式,保留原生单选组的 键盘行为与 44 px 点击区域;收紧桌面面板和列表间距,减淡选中背景,将实时状态与 键盘提示合并为统一底栏。
2026-10-26:对抗性审查修正了规范化匹配后的摘要与高亮偏移,区分标题 ID、数据属性与 脚本字符串,并将英文拼写回退限制在当前栏目。回归覆盖兼容字符、组合字符、短代码标题 以及结果文本中的 HTML 字面内容。
7.8 - 设计研究
只有其他维护者能够检查方法、理解范围并复现相关检查时,研究才适合进入公开 Design 内容树。 请勿将原始 Agent 对话、临时构建日志和本机绝对路径写入研究记录。
研究地图
| 记录 | 证据 |
|---|---|
| Goldmark 块属性 | 该研究记录的历史 Hugo 版本上,渲染钩子能看到什么,以及 CommonMark 容器的边界 |
| 消费站与迁移证据 | 带日期的语料盘点与确定性 Book 迁移结果 |
| 2026-08-26 全面审查 | 实现、配置、输出、安全、测试、性能与文档审查 |
| 2026-09-19 社区 Issue 与 PR 调研 | 侧栏、焦点与搜索反馈的复现、PR 接收建议和解决方案 |
| 2026-09-20 OINK 1.1 发布审查 | 五项运行时修复、文档准备、验证证据与发布边界 |
| 2026-09-29 CLI 验收快照 | 本地 CLI 候选已执行的 Starter、真实站点、离线、升级及可复现归档检查 |
| 视觉预设验收,2026-10-05 | Paper/Slate 本地实现、真实输出与有范围说明的浏览器证据 |
| Ink 与 Terminal 实验,2026-10-05 | 显式实验预设、设计取舍与真实站点验证 |
| OINK 1.2 发布前审查,2026-10-05 | 本地候选版本最终检查、清理、本地资源、兼容性与发布边界 |
| CLI 维护验收,2026-10-03 | 历史 R1–R8 与 A18 的源码绑定运行时及归档证据 |
| 主题评审,2026-10-13 | 输出与交互缺陷复现、修复及本地验证范围 |
| 组件精修验证,2026-10-25 | 最终组件验收、409 项 Chromium 测试、针对三个引擎的视觉复核、CSS/JS 体积对照,以及单独记录的画廊基线失败 |
| 文档对照研究,2026-10-29 | 支撑译文对照决策的带日期源码研究;属于历史记录,不是已发布契约 |
| 审查修复,2026-11-06 | v1.5.0 缺陷复现、本轮开发修复、验证及剩余工作 |
| OINK 1.5.1 发布就绪审查,2026-11-06 | 三个已修复的候选版本回归、归属检查、浏览器验收、出版证据与发布关卡 |
发布规则
研究记录需说明日期、输入、相关版本、方法、结果与已知范围,容易变化的数字明确标为快照。 涉及外部框架的比较,公开前应依据一手资料核验,并提炼出直接影响 OINK 的结论。
7.8.1 - Goldmark 块属性实测
这些探针在 Hugo Extended 0.160.1 与 0.164.0 上得到字节一致的相关输出。它们解释 OINK 的原生组件形态;当前 组件契约 定义现行规范。
方法
探针使用一个不带 OINK 模板的最小 Hugo 站点。渲染钩子把上下文字段与 .Attributes 输出为
可见标记。站点开启 Goldmark 块属性、行内与块级数学 passthrough 分隔符,以及为检查原始 HTML
启用的 unsafe 渲染,并设置 wrapStandAloneImageWithinParagraph: false。
每种源码形态分别用兼容下限版本和当时的当前 Hugo 版本渲染,再逐字节比较相关产物,记录 Goldmark 的结构行为。
结论
| 源码形态 | 钩子结果 | 设计意义 |
|---|---|---|
含段落、围栏、callout、嵌套列表并以 {.steps} 结尾的有序列表 |
class 落在最外层 <ol>,列表项中的富块内容完整保留 |
Markdown 列表可以成为 Steps 原生形态 |
| 列表项内标题 | 标题保留在 <li> 内,并进入 .TableOfContents |
原生 Steps 可以携带可导航标题 |
以 {.filetree} 结尾的嵌套列表 |
class 落在最外层 <ul> |
FileTree 无需外层 wrapper 即可保持层级 |
独占图片加 {#id num= caption= .class} |
render-image 收到 IsBlock=true 和全部属性 |
Book 图可以有原生图片形态 |
| 段落中的行内图片 | IsBlock=false,图片收不到块属性 |
行内图片不能使用块级 figure 契约 |
块级公式加 {#id num=} |
render-passthrough 收到 block 类型与属性 |
编号公式可以使用原生 passthrough 形态 |
表格加 {.fields #id num= caption=} |
render-table 收到 class 与命名属性 |
Fields、矩阵、题注和 Book 编号可以共享一个钩子 |
代码围栏加 {#id num= caption=} |
code-block 钩子收到属性 | 围栏本身可以成为编号示例 |
callout 加 {icon= tab=} |
blockquote 钩子同时收到 callout 元数据与属性 | 折叠、标题行内标记、图标和 tab 元数据可以共存 |
| 属性行与目标块之间隔一个空行 | 属性会静默消失 | 源码检查必须拒绝孤立属性行 |
两张相邻表分别带 tab= |
每个 table 钩子收到自己的 tab 标签 | 相邻块 tab 机制可以扩展到代码围栏之外 |
容器边界
Hugo 的 % shortcode delimiter 会把 .Inner 渲染成 Markdown,但模板必须在内部 Markdown
前后各输出一个空行。缺少任一空行时,后续列表可能被当作 HTML block 的字面内容,而不是 Markdown。
把多行 % 容器放进 CommonMark 列表项还有更硬的限制:生成的 HTML 不会随列表内容缩进,列表会在
容器之前闭合,并在容器之后重新开始。因此,当步骤中必须放另一个全量容器时,OINK 仍保留全量
Steps 形态。普通富块、围栏与 < shortcode 不受这一限制。
在相关收集器形态中,嵌套 % shortcode 收到的也是已经渲染好的内部 HTML。需要保留子项原始
Markdown 的收集器应使用 < delimiter,再通过共享的作用域块渲染器处理捕获到的正文。
属性归属
每个钩子执行明确的属性白名单,拒绝 style 与内联 on* 处理器,并通过共享 URL 策略校验携带 URL 的值。只有下游 CSS 已属于既有扩展机制的界面,才保留站点 class。
实验还表明:gallery 列表项中的图片可以被视为块图,但图片钩子不会收到父列表的 marker。因此运行时需要依赖主题显式输出的标记或特定的结构回退。
边界与验证
这些结果覆盖 Hugo 0.160.1、0.164.0 与上述 Goldmark 设置。调整 Hugo 兼容下限时,应先重跑组件、Book、表格、gallery 与 Markdown 输出检查,再更新这份快照。
7.8.2 - OINK 1.3.2 与 Hugo 0.168 消费站点盘点
本记录盘点 ~/pgsty 与 ~/www 下的本地站点。
当前构建规则由架构契约与
诊断决策维护。
基线
OINK v1.3.2 于
2026-10-18 13:37 UTC 发布,对应提交
bd2e856e6e2f7027a9189af009701f5b8cf562cf。
维护中的站点统一使用该版本及 Hugo Extended 0.168.0。CI、本地检查、预览与部署
命令均移除遇警告中断的开关,HUGO_PANICONWARNING 也必须保持未设置。
告警保持可见并接受审阅,上游 LibSass 弃用提示允许出现。普通站点不需要外部 Sass
编译器,不再以旧 Hugo 版本为开发与适配目标。
本地实际工具为 macOS arm64 上的 Hugo 0.168.0+extended+withdeploy 与
Go 1.27.2。模块核验禁用了环境替换及 Go、Hugo 两种 workspace;经典主题消费
站点的运行时源码与公开标签逐一比对。37 个站点均保留 Paper 默认外观及显式四款
预设的读者菜单。
站点
路径相对于用户主目录。每一行都使用 OINK v1.3.2 与 Hugo Extended 0.168.0。
“模块”表示实际解析的公开模块;“副本”和“子模块”表示经典 themes/oink 消费站点。
| 目录 | 主题来源 | 本地生产构建 |
|---|---|---|
pgsty/barn.pgsty.com |
模块 | 通过 |
pgsty/boar.pgsty.com |
模块 | 通过 |
pgsty/caps.vonng.com |
模块 | 通过 |
pgsty/cloudexit.org |
模块 | 通过 |
pgsty/exp.pgsty.com |
模块 | 通过 |
pgsty/oink-starter |
模块 | 通过 |
pgsty/oink.pgsty.com |
模块 | 通过 |
pgsty/pgetcd.pgsty.com |
模块 | 通过 |
pgsty/pgext |
模块 | 通过 |
pgsty/pgredis.pgsty.com |
模块 | 通过 |
pgsty/pgsilo.pgsty.com |
模块 | 通过 |
pgsty/pgsty.com |
模块 | 使用本地报告数据通过,见下文 |
pgsty/pgsty.pro |
模块 | 通过 |
pgsty/pig.pgsty.com |
模块 | 通过 |
pgsty/pigsty.cc |
模块 | 通过 |
pgsty/pigsty.io |
模块 | 通过 |
pgsty/repo.pgsty.com |
模块 | 通过 |
pgsty/silo.pgsty.com |
模块 | 通过 |
pgsty/sow.pgsty.com |
模块 | 通过 |
pgsty/wok.pgsty.com |
模块 | 通过 |
www/aiempire |
模块 | 通过 |
www/ca.vonng.com |
子模块 | 通过 |
www/ddia |
模块 | 通过 |
www/dharma |
模块 | 通过 |
www/doc.pg.center |
模块 | 通过 |
www/fzb/site |
源码副本 | 通过 |
www/lzj |
模块 | 通过 |
www/muse.vonng.com |
源码副本 | 通过 |
www/pg-internal |
模块 | 通过 |
www/pg36g |
模块 | 通过 |
www/pgweekly |
模块 | 通过 |
www/piglet.run |
模块 | 通过 |
www/sg.vonng.com |
子模块 | 通过 |
www/tpme |
模块 | 通过 |
www/us.vonng.com |
子模块 | 通过 |
www/vonng.com |
模块 | 通过 |
www/zj |
模块 | 通过 |
边界
pgsty.com拉取单独发布的报告清单时遇到 unexpected EOF 或超时。使用现有 本地报告数据及HUGO_REPOORT_USE_LOCAL=1后构建通过。sow.pgsty.com在既有的发布审阅分支验证。无 Git 元数据或使用 源码副本的站点均原地验证。pgsilo/work/silo-docs按所属仓库的说明属于冻结交付基线,不纳入维护站点升级。- 本记录涵盖基于已发布
v1.3.2标签的本地源码、模块解析、构建与渲染菜单检查。
7.8.3 - Ink 与 Terminal 实验,2026-10-05
Ink 与 Terminal 已编入主题样式表并使用外观控件。两者仍为需要显式开启的实验,等待视觉定稿。
输入与方法
本实验基于 10 月 5 日工作树中的 Paper/Slate 实现。 工具为 macOS ARM64 上的 Hugo Extended 0.166.0、Go 1.27.1、Node 26.9.0、 Playwright 1.62.1。集成检查使用文档站与同级本地主题;站点公开 pin 仍为 v1.1.0。
首页、配置、提示块与标签页以相同内容比较四套预设、EN/ZH、390/1440 px 和浅深色。 检查真实字体、溢出、外观控件与本地字体请求。进一步的组件检查覆盖代码、参数字段、 Blog、Book、API、Mermaid、ECharts、搜索与打印。沿用站点既有边界,axe 不检查 API 供应商自有 DOM。
设计选择
| 选择 | 改善 | 代价 / 限制 |
|---|---|---|
| Ink:黑白画布、Inter、红色标记、正文链接下划线与粗标题线 | 少量装饰即可形成清楚的层级与链接信号 | 高字重标题与重复分隔线仍需长页编辑式评审 |
| Terminal:等宽控件与标题,无衬线正文与表格,青色链接与琥珀强调 | 保留段落可读性,同时形成明确的技术界面 | 长英文导航会更早换行,中文使用平台回退字体 |
| Ink 直角、Terminal 2 px 圆角,组件无阴影 | 相同内容与布局也能呈现明显不同的界面 | 局部组件规则增加 CSS,未抽离全局间距/圆角 token 层 |
| 只压紧 Terminal 桌面导航 | 展示更多有效导航行,不缩小正文 | 密度随预设固定 |
| 复用本地字体与状态管理 | 不新增字体文件、外部字体服务、框架或持久化机制 | 所有预设 CSS 仍在同一个样式表内 |
| 显式实验菜单选项 | 评审者可即时切换,普通菜单范围保持稳定 | 开启四张卡片后菜单更高 |
Ink 使用 #ffffff / #0b0b0b 画布、#141414 / #ededed 正文与
#c8102e / #ff5c4d 强调。Terminal 使用 #f4f5f2 / #0c0f0e 画布、
#1d211f / #d3dbd6 正文、#0a6560 / #4cc9bd 链接与
#935400 / #f0a73a 强调。站点与栏目强调色覆盖仍然优先。
Terminal 的标题标记使用空的无障碍替代文字,不支持的引擎省略标记。首页光标为
静态图形,不引入打字、闪烁、扫描线或发光效果。
试用
本地文档站已开启此列表。在外观菜单选择 Ink 或 Terminal,再独立选择亮色、暗色或
跟随系统。10 月 5 日菜单修订后,四个选项统一使用图标与名称按钮,不显示实验标记。
站点可以直接将任一实验设为 preset,不依赖读者菜单。preset_menu: true 仍提供
Paper/Slate 与站点默认值。选择站点默认预设会清除保存的预设。
字体覆盖、系统排版与 CSS 加载前初始化沿用
Paper/Slate 的契约。
验证
| 已执行检查 | 结果与范围 |
|---|---|
check-presets.py |
三类背景的正文/链接/强调色 AA、浅深色 token 对称、建议性背景亮度、冻结的 Slate v1.1.0 色板;七次严格构建、28 个文档根元素 |
| 主题检查 | 参数、字体角色、32 个语言包的 205 个键、生成 schema、组件/输出契约、运行时隔离与命名空间通过;52 个既有输出 golden 未改动 |
| 运行时测试 | 49 项 Node 测试通过 |
make check |
57 项非浏览器测试通过;EN/ZH 覆盖 142/142,Markdown、渲染内容与站内链接检查通过 |
| 标准浏览器套件 | 八套通过 170 项;修复下述默认 Terminal 构建问题后,外观套件 49 项全通过(初次运行加专项重跑合计 219 项) |
| 外观覆盖 | 四预设 × EN/ZH × 390/1440 px × 浅深色,检查首页、配置、提示块与标签页;本地字体请求、菜单 axe、状态、键盘、打印、Giscus 资源;四组实验/明暗检查覆盖 11 类页面与搜索,并检查共享 Mermaid 对比度 |
| 字体/配置构建 | 真实文档站以 Terminal 为默认值重建:系统字体、不带与带显式覆盖,以及 technical 显式字体覆盖,三组均通过 |
| 浏览器引擎 | Chromium、Firefox、WebKit 在 390/1440 px 下六项通过,覆盖 CSS 前状态、键盘依次选择 Ink/Terminal、持久化与焦点返回;桌面 Chromium 另用 4 倍 CPU 降速 |
| 视觉复核 | 96 张真实输出视口截图,查看了代表性首页、文档与手机菜单;本地对照页按内容、语言、尺寸与明暗选择截图,不注入样式 |
标准 sitemap axe 检查覆盖 15 个路径:EN/ZH 首页、配置、提示块、标签页、OpenAPI;
英文搜索、Mermaid、ECharts、Blog 与 /book/04-design/。既有响应式 axe 矩阵另行
运行。标准浏览器检查使用 4173 样例服务器;引擎专项使用同级主题 1313 开发服务器。
外观矩阵未观察到外部字体请求。
首次默认 Terminal 字体构建发现建议性背景亮度表漏项,造成强调色对比度误报警。 现已登记两套实验背景,并由检查器断言与显式强调色样例覆盖。修正后仅重跑受影响的 外观套件。新增研究索引和更新的提案描述经逐项审阅后,刷新 LLMS golden 中对应的 两处变化。
实验暴露了两处新增样式问题:全局禁用下划线的规则覆盖了 Ink 链接,Terminal 搜索
选中行的摘要仍使用弱化文字色。两者均作了局部修正。另一个继承问题是 Mermaid 深色
标签(#cccccc 配 #585858,4.43:1),在 Paper 与 Slate 中同样复现。共享的
明暗色板默认标签背景现改为 #404040,作者显式配置仍优先。
后续工作
晋升稳定选项前仍需真实 Windows 与 Android 设备评审,包括中文回退字体、下划线、 等宽标题换行与长参数表。人工屏幕阅读器朗读和首屏逐帧截图仍未验证。
Mermaid/ECharts 保留明暗色板,API 组件保留供应商样式;Giscus 检查针对生成的 色板资源。是否晋升 Ink/Terminal、是否重做图表色板仍待决定。
7.8.4 - OINK 1.2 发布前审查,2026-10-05
本次审查覆盖 v1.2.0 打标签前的 10 月 5 日工作树。审查时公开主题标签与文档站固定版本仍为 v1.1.0。
范围与输入
主题起点为 a1979a4,文档站起点为 ed2d0e3。受检工作树还包括 CJK 关键词
摘要、字面百分号大纲与仓库源文件路径修复,站点既有的英文编辑修改,以及下列
清理。独立可选 CLI 不属于本次主题发布范围。
多数检查使用 macOS ARM64 上的 Hugo Extended 0.166.0、Go 1.27.1、 Node 26.9.0 和 Playwright 1.62.1。选定兼容性检查使用经校验和验证的官方 Hugo Extended 0.160.1、0.165.0 二进制。
审查发现与清理
| 发现 | 修正 | 影响 |
|---|---|---|
| 1.2 发布草案遗漏新默认值与外观控件 | 同步两种语言的发布草案和升级说明,补充 Paper、Slate 兼容配置、独立持久化、当前状态图标及实验预设选择 | 读者升级前能明确看到外观变化 |
| 部分当前提案、决策、实验记录与源码注释仍描述字母预览、实验标记、独立 Default 卡片或未定发布版本 | 当前说明对齐紧凑图标/名称按钮、站点默认值恢复与 1.2 发布准备;保留有日期的历史测试证据 | 当前指南与已接受界面一致,并保留历史测试结果 |
工作树的一条忽略规则屏蔽站点整个 tests/,仅放行两个文件 |
删除宽泛规则,保留已有生成产物排除项 | 新增回归测试正常出现在 Git 中 |
| 开发预览不会暴露仅生产环境加载的统计服务 | 检查严格生产产物,区分核心本地资源与显式配置服务 | 在生产产物上核验本地优先边界 |
这些清理没有要求新增运行时修改。工作树原有运行时修复由各自检查器及最终集成 测试覆盖。
已执行验证
本地候选版本通过下列技术性发布前检查,在受检范围内没有发现阻塞发布的主题 缺陷。数字均为本次工作树审查的带日期快照。
| 检查 | 结果与范围 |
|---|---|
| 预设检查器 | 通过:7 次告警即失败的配置构建、28 个文档根节点、浅深色变量对齐、三类界面的文字/链接/强调色 AA 检查及冻结的 Slate v1.1.0 基础色板 |
| 运行时测试 | 49 项 Node 测试通过,覆盖当前状态图标、搜索摘要、大纲跟踪、剪贴板与对话框焦点 |
| 主题回归与工具检查 | 40 项检查器/工具命令通过,包含 90 项迁移测试、快照/下游站点安全与现有产物 golden;随后指定浏览器复跑 PDF 检查,3 项隔离测试全部通过 |
| 文档检查 | 最终 make check 的 57 项测试通过;检查 143/143 份双语文件、1,197 个源标题、228 个渲染内容页及站内链接;Markdown golden 仅按审阅结果更新新增研究索引条目及发布标题/描述 |
| 标准浏览器套件 | 一次完整 make browser 运行的 9 个套件、219 项检查全部通过,包含 372 个路由的完整 sitemap axe 扫描;无失败、不稳定或跳过项 |
| 文档补充检查 | 用新构建单独扫描 14 个修订后的中英文路由,包含本报告新增双语页面,axe 检查通过 |
| 浏览器引擎 | Chromium、Firefox、WebKit 在 390/1440 px 的 6 项外观检查通过;覆盖 CSS 前状态恢复、键盘选择、持久化与焦点返回;桌面 Chromium 还使用 4 倍 CPU 限速 |
| 视觉抽查 | 查看本轮真实产物截图:中文移动端 Paper 菜单、英文桌面深色 Paper 菜单,以及中文 Terminal 移动/桌面阅读页;确认两列图标/名称选项、当前状态图标与阅读布局 |
| Hugo 兼容下限 | 0.160.1 通过预设及阅读/数学检查器,以及真实文档站严格压缩生产构建 |
| CI 使用的 Hugo 版本 | 0.165.0 通过真实站点严格压缩生产构建、Hugo Module/include/static/print 检查、系统字体、旧 Sass 字体覆盖,并按预期拒绝非法字体预设 |
| 生产资源 | 四套风格、七类路由共 28 次访问;核心字体与脚本来自站点自身 origin,显式外部服务另行记录 |
| 生产产物安全 | 按已记录的第三方集成策略,最终严格生产构建的 921 个文件检查通过 |
| Book 出版 | 根路径与子路径 EPUB 均通过主题检查器及 EPUBCheck 5.3.0,零错误、零告警;两份 PDF 通过 23 页、5 章结构检查;根路径 PDF 脚本隔离探针通过 |
| 已发布下游版本 | 禁用环境替换及两个 workspace 后,既有 v1.1.0 解析与站点 release-pin 检查通过 |
完整 sitemap 扫描沿用站点现有 axe 策略:检查 OINK 维护的界面,阻断 Giscus 请求,排除 Swagger UI/Redoc 的供应商 DOM。响应式检查覆盖 360、768、820、1024、 1200、1440 px,以及中英文、浅深色。
外观检查在四套预设、中英文、390/1440 px、浅深色下使用相同首页、配置、提示块 与标签页内容;也覆盖搜索、代码、表格、输入/焦点状态、Blog、Book、API、图表 与打印。Ink、Terminal 仍需显式选择。
出版使用本地 Pandoc 3.11、Java 26 和 Chrome headless-shell 151.0.7922.34。 第一次使用完整 Chrome for Testing 应用时在本机超时。像 CI 一样显式选择 headless-shell 后,生成的 PDF 通过验证。CI 在 Linux 上固定 Pandoc 3.10 与 Java 21。
本地优先的资源边界
IBM Plex Sans、Inter、IBM Plex Mono、Chakra Petch、图标、KaTeX 字体与 核心浏览器库均已本地化。预设切换没有引入运行时字体服务或 CDN 脚本依赖。 系统字体模式与显式字体角色覆盖保留其约定优先级。
生产审计在每套预设下访问首页、中文配置、数学、Mermaid、Markmap、ECharts 及 OpenAPI,再切换浅深色。测试保留生产 base origin,由本地产物提供响应。 请求追踪记录并阻断跨 origin 请求;本地字体与图表仍加载成功,没有未捕获 JavaScript 异常或本地 HTTP 错误。
两个已配置服务会请求外部脚本:Giscus 与 Google Analytics。Giscus 是可选评论集成, 其 OINK 色板文件来自本地。文档站配置了统计 ID,因此生产产物包含 Google Tag Manager 脚本。二者都不是新预设的必要依赖。作者引用的远程媒体与显式选用的图表服务保留既有可选边界。
复现检查
先运行主题对应检查,再检查真实站点。以下命令通过约定的 Make 目标选择同级主题:
完整主题检查器与出版命令定义在 .github/workflows/ci.yml。用新构建的 fixture
运行所有对应检查,覆盖参数/Schema、vendor/字体、导航/搜索/操作、组件、
产物/命名空间/golden、迁移、快照保护、下游工具及 PDF 隔离。
文档站的独立引擎套件为 npm run test:appearance:engines。
兼容性检查将选定 Hugo 二进制放入 PATH,禁用继承的 Go/Hugo workspace,
并明确同级模块替换。真实站点用 --environment production --minify --printPathWarnings --panicOnWarning 构建到独立输出目录。检查已发布 pin 时,
还须禁用模块替换。
剩余发布步骤与边界
本地技术性发布前验收通过。正式发布仍需把受检修改整理为提交, 在这些提交上运行 CI,发布标签及模块归档,更新下游站点版本并完成托管部署验证。
尚未验证真实 Windows/Android 设备上的字体表现、人工屏幕阅读器朗读与首绘 逐帧画面。Windows 源文件路径行为使用确定性 fixture 验证。Ink/Terminal 设计后续项仍见 实验记录。
7.8.5 - 视觉预设验收,2026-10-05
本记录覆盖 v1.2.0 打标签前同级主题 checkout 中 Paper 与 Slate 预设的真实输出验证。
输入
2026-10-05 的主题与文档站工作树;macOS ARM64,Hugo Extended 0.166.0、
Go 1.27.1、Node 26.9.0、Playwright 1.62.1。常规浏览器套件使用 Chromium;
另有 Chromium、Firefox 与 WebKit 专项检查。执行时站点固定 v1.1.0,
make check、make browser 与 make dev 使用同级本地主题。
已执行检查
| 证据 | 结果与范围 |
|---|---|
check-presets.py |
Paper 明暗 token 对称与正文、链接、代码、铜色 AA 对比度;冻结的 v1.1.0 Slate 基础色板;四种严格配置构建与 16 个 HTML 根元素,包括 404 和打印 |
| 现有主题检查器 | 参数、字体角色、vendor 清单、32 个语言目录、动作、外壳、输出、命名空间、Landing 和运行时隔离均通过;生成的 schema 与源码一致 |
check-goldens.py |
52 个界面通过;已审阅并更新根属性、首绘颜色、菜单及其动作与运行时影响的 34 份 HTML/打印期望,其他输出格式未变 |
| 严格站点构建 | 真实本地主题中英文站以 --panicOnWarning 构建成功;翻译、渲染 Markdown 和站内链接检查通过 |
node --test 'tests/js/**/*.test.js' |
49 个运行时测试通过 |
appearance.spec.mjs |
25 个测试通过:Paper/Slate × EN/ZH × 390/1440 px × 浅深色,覆盖首页、配置长文、提示块和标签页;菜单 axe、键盘、持久化、恢复默认、跨语言导航、跨标签同步、禁用存储、非法值、无 JS、打印、命令面板、阅读锚点与断点处理、生成的评论样式表 |
appearance-engines.spec.mjs |
6 项通过:Chromium、Firefox、WebKit × 390/1440 px;CSS 前恢复状态和浏览器栏颜色、原生键盘选择、焦点返回及跨语言导航。Chromium 桌面另加 4 倍 CPU 限速 |
| 字体请求 | 观测到的字体均来自本地;Paper 不请求 Inter,Slate 不请求 Plex Sans。两次真实文档站配置覆盖构建证明系统排版不请求内置文字字体,显式字体角色可覆盖两套预设 |
make check |
完整非浏览器套件通过:57 个测试、141/141 篇翻译页面,以及既有 Markdown、渲染内容与站内链接检查 |
make browser |
九个常规套件共 197 个测试通过;sitemap axe 扫描限定为下述 15 条路由 |
| 视觉抽查 | 基于浏览器测试截图检查真实 Paper 桌面首页、手机 Docs 长文、中英文浅深色外观面板,以及 Slate 深色首页 |
浏览器套件的 sitemap axe 扫描通过 A11Y_PATHS 限定为 15 条代表性路由:中英文
首页、配置、提示块、标签页与 OpenAPI,以及英文搜索、Mermaid、ECharts、Blog
和一个 Book 章节。另运行现有响应式 axe 矩阵。跨域 Giscus 与 API 供应商组件 DOM
沿用既有排除规则。
整合检查发现并修复了 Paper 深色高亮代码行的行号对比度,以及平滑滚动干扰阅读 锚点恢复的问题。章节强调色测试现在分别断言 Paper 的暖色不透明选中底和 Slate 原有的半透明选中底。
限制与后续检查
手工读屏播报和逐帧绘制追踪尚未验证。阻塞样式表及 CPU 限速断言用于验证初始化顺序。 Slate 比较冻结基础色板并检查渲染字体行为。
Mermaid/ECharts 继续仅随明暗变化。Ink 与 Terminal 仍为研究项;衬线展示标题、完整 几何与密度 token、图表随预设配色留待后续。
7.8.6 - 消费站与迁移证据
这些计数记录 2026 年 8 月检查过的仓库样本,用于指导 OINK 外壳、组件与 Book 迁移设计。
语料
创作语料盘点扫描了十一个 OINK 下游站点的 content/ 树:共 5,325 个 Markdown 文件,其中
5,293 个带 YAML front matter。样本同时包含单语言英文与中文参考站、双语产品站、发布归档、
自定义落地页,以及独立的 Book 下游站点。
盘点直接统计源码 Markdown 中的 shortcode 调用、代码围栏属性、 callout、表格 marker、原始 HTML、front matter 键、内容类型与站点自有 layout。随后针对五个 长篇内容下游站点又做了一轮 Book 专项盘点。
改变设计的结论
| 证据 | 形成的选择 |
|---|---|
| 内容从近乎纯 Markdown 到大量嵌套组件同时存在 | 原生 Markdown 是默认形态;只有明确能力缺口才保留全量形态 |
| 文档、Blog、Landing、发布与书籍反复在站点侧重做导航或卡片 | 扩展共享外壳、注册表和内容原语,不增加并行系统 |
| 站点自有表格 class 很常见,匹配 canonical Fields 表头的表格却很少 | 钩子属性使用白名单,但保留文档明确的站点 class 扩展点;不能从任意二列表格猜测 Fields |
| Book 站各自拥有图、表、公式、示例和交叉引用约定 | 编号原语与迁移 profile 必须确定性分类、保留稳定 ID,并验证渲染目标 |
| 站点同时存在单语言、对页双语和生成式语言内容 | 必须明确语言权威与生成边界;迁移不能把未跟踪的生成树当作源码 |
| 富 HTML 页面仍要提供 Print、Markdown、订阅源和 Agent 输出 | 接受交互 HTML 之前,每个组件先声明所有输出中的降级行为 |
证据也排除了一些新增项:文档站不需要第二套 Landing 系统;Book 站不需要新封面 组件;连载归档不需要独立 shell type;远程 API 采集属于站点侧 CI,放在专注于本地构建的 Hugo 主题之外。
块与表格证据
针对十一个站点与 Book 下游站点的专项盘点共发现 11,484 张 pipe table。只有 11 张匹配严格的
Fields 表头词汇,约 874 张属于参考型表格,约 1,300 张属于兼容矩阵。因此 OINK 采用显式
.fields 与 .matrix marker,不按表格形状猜测语义。
同一轮盘点在十一个站点中发现 18 个 Steps 块,均使用带标题和富内容的全量形态。平台探针表明,
原生有序列表可以承载其中大多数内容,却不能在列表项内安全容纳另一个全量 % 容器。因此 OINK 按技术能力边界同时保留两种形态。
确定性 Book 迁移
三个带日期的干跑 profile 用于验证迁移规则能否覆盖每个识别出的来源且不篡改语义:
| Profile 快照 | 分类结果 | 人工边界 |
|---|---|---|
| DDIA v2 | 106 张图、3 张表、22 个代码示例,相关 304 条链接全部入账 | 1 条题注链接降级为可见文本,无未解释跳过项 |
| DDIA v1 | 90 张编号图与 203 条匹配引用 | 14 张装饰性或无编号图片刻意不处理 |
| TPME | 31 张图、10 张表、44 条编号引用与 1,018 条通用稳定引用 | 被识别项目零跳过 |
| 私有 Book profile | 119 张图、5 张表与 136 条编号引用 | 3 张歧义图片保留人工复核 |
每个 profile 都先干跑,只在歧义边界明确后写入;第二次执行变更数为零;随后以警告即失败的模式 构建,并通过渲染后的 kind、编号和锚点检查。公开迁移工具与当前 profile 边界见 创作书籍 和 迁移契约。
出版采纳快照
2026-08-24 的隔离验证在两个下游站点上运行了已发布的通用 Book 出版链路:
| 下游站点 | 通用出版证据 | 下游状态 |
|---|---|---|
| DDIA | 23 个有序页面、131 个带类型目标与 292 条已解析交叉引用;EPUBCheck、内部检查与 PDF 检查均通过 | 该快照中仍保留语义预处理器,等待下游站点独立接受新的门禁 |
| TPME | 18 个有序页面、41 个带类型目标与 1,062 条已解析交叉引用;同一套通用检查通过 | 第二个下游站点验证了可移植性,无需新增上游迁移门禁 |
这些结果记录了下游站点对通用出版管线的采用情况。
边界
重做研究时,需要重新确定仓库清单并生成新的带日期快照。本公开记录省略了本机路径、未提交内容、私有仓库名称与生成的构建产物。
7.8.7 - OINK 全面审查(2026-08-26)
本文记录 2026-08-26 对 github.com/pgsty/oink 主线与本站集成面的审查证据。
OINK 0.7.1 随后修复了代码发现 F01–F06(见 0.7.1 发布说明)。当前行为以 Design 契约为准。
审查结论
OINK 的主干质量明显高于一般 Hugo 主题:默认路径可构建、双语完整、组件测试广、输出与安全意识强,
真实站点在桌面、移动端、深浅色和无障碍主路径上没有发现普遍性崩坏。当前 main 与远端一致,
主题 CI 和本站 CI 都是绿色;本次重新执行的主题检查、迁移单测、浏览器单测、全站链接、
Playwright 与 axe 也全部通过。
尽管现有检查全部通过,本次审查仍发现 4 项 P1、9 项 P2、5 项 P3。 最重要的共同原因是:项目已经建立了一套很强的原则,却仍有若干早期/边缘实现没有接入这套原则; 而现有门禁主要证明已选中的正向场景不回归,不能系统发现配置空间、静态输出和公开文档的语义漂移。
建议在下一个版本标签前至少完成以下四项:
- 关闭 Swagger UI 默认在线 validator,并用非 localhost 的浏览器请求测试锁定「零隐式外联」;
- 把所有公开配置和 Landing 数据纳入统一的类型、范围、URL 与 CSS 值验证;
- 重做 Swagger、Redoc、Asciinema 的 HTML/Print/Markdown/RSS 降级和 runtime gate;
- 修复生成 Schema,并让公开配置/Front matter 参考重新与当前实现对齐。
基线与方法
审查基线
| 项目 | 快照 |
|---|---|
| 主题仓库 | main = fe439fdb1d7c2df745088c9bfcbb8c350403ee63,工作树干净,与 origin/main 一致 |
| 当前稳定标签 | v0.7.0 = cbb6f4e0bfe47e17ba7aa41d04b8651c943cf858 |
| 文档站仓库 | main = fd5fcde,工作树干净,公开 pin 为 github.com/pgsty/oink v0.7.0 |
| 本机工具 | Hugo Extended 0.164.0、Python 3.14.6、Node 26.4.0、npm 11.17.0 |
| 远端 CI | 主题 HEAD 的 GitHub Actions run 32792753866 成功 |
实际执行的验证
- 31 个主题 checker 全部通过;
- 85 个迁移单测全部通过;
- 38 个主题浏览器运行时单测全部通过;
- 40 个 HTML/Print/Markdown/RSS/LLMS golden 表面通过;
tests/site严格 Hugo 构建通过;- 真实双语站点的
npm test通过:121/121 中英页面配对、886 个标题 ID、24,860 个站内链接与 3,172 个 fragment 均通过; - 真实站点的完整 Playwright 套件通过:全站 sitemap axe 扫描、29 个无障碍场景、45 个响应式/ 导航场景、16 个键盘场景、10 个内容组件场景、18 个代码块场景、4 个 PRD5 场景与 5 个主题色场景;
- 额外在 320 CSS px 下人工检查 EN 首页、ZH 配置页、ZH Book 页、OpenAPI/Redoc 页,未发现页面级水平溢出;
npm audit对本站 79 个 npm 依赖报告 0 项漏洞;对VENDOR.json的 26 个精确 npm 版本调用 OSV Query API 未返回已知公告;measure-baseline.py assets --fixture-site的严格隔离构建通过。
判级
| 级别 | 含义 |
|---|---|
| P1 | 违反核心产品承诺、安全/隐私边界或普通编辑可用性;应在下一标签前修复 |
| P2 | 明显功能/契约/兼容性缺陷;短期内修复并增加行为门禁 |
| P3 | 维护性、性能、流程或文档治理债务;排入结构化改进 |
发现摘要
| ID | 级别 | 发现 | 默认站点是否受影响 |
|---|---|---|---|
| F01 | P1 | Swagger UI 在生产 URL 上默认启用在线 validator | 仅使用 swagger 的页面 |
| F02 | P1 | 多组非法配置会让普通 Hugo 直接失败或静默生成坏输出 | 取决于配置输入 |
| F03 | P1 | Swagger/Redoc/Asciinema 违反静态输出和 runtime 隔离契约 | 使用这些 shortcode 的页面 |
| F04 | P1 | Landing 将未验证数据送入 safeCSS,其它错误值静默通过 |
使用相关 Landing 字段的页面 |
| F05 | P2 | 自定义页面动作与归档版本 URL 绕过共享 URL 策略 | 配置这些可选项的站点 |
| F06 | P2 | 生成 JSON Schema 的默认值、类型、描述和候选键存在实质错误 | 使用编辑器 Schema 的作者 |
| F07 | P2 | 「完整」配置与 Front matter 参考大量落后于 v0.7 实现 | 全部维护者/消费站作者 |
| F08 | P2 | Design 契约与提案生命周期内部出现双重答案 | 维护者 |
| F09 | P2 | OpenAPI 无障碍缺口被测试排除,Redoc 推荐与实测不一致 | OpenAPI 页面读者 |
| F10 | P2 | 严格 CSP 文档没有覆盖主题自己的 inline script/style | 启用严格 CSP 的站点 |
| F11 | P2 | 浏览器兼容性没有公开基线,自动化只跑 Chromium | Firefox/Safari/RTL/强制色用户 |
| F12 | P2 | 输出安全与「Rendered Markdown」门禁存在系统盲区 | 依赖门禁判定安全/输出纯度的站点 |
| F13 | P2 | 跨仓库真实集成仍是人工、非原子的发布步骤 | 每次公共行为改动 |
| F14 | P3 | checker 体系重复且过度依赖源码字符串 | 维护者与并行工作树 |
| F15 | P3 | 全局 CSS/字体仍是首访主要负担 | 全部 HTML 页面 |
| F16 | P3 | vendor 完整性强,但漏洞/SBOM 与 CI 供应链门禁不足 | 发布维护者 |
| F17 | P3 | Changelog、已实现提案和无行为元数据造成治理噪音 | 维护者与升级读者 |
| F18 | P3 | Print isHTML 的 FIXME 已不能准确说明真实依赖 |
Print 模板维护者 |
详细发现
F01 — Swagger UI 会隐式联系在线 validator(P1)
证据。 layouts/_shortcodes/swagger.html 初始化 SwaggerUIBundle 时没有声明
validatorUrl: null。随主题内置的 swagger-ui-bundle.js 把默认值设为
https://validator.swagger.io/validator;它只对包含 localhost 或 127.0.0.1 的 spec URL
跳过在线校验。部署到真实域名后,Swagger UI 会创建在线 validator badge,请求参数包含 spec URL。
影响。 这违反「主题自有网络功能默认关闭」「本地优先」「同源 spec 在浏览器中不访问外部服务」三项承诺。 内网站点尤其会把内部主机名/spec 地址暴露给第三方。由于上游特意跳过 localhost,当前所有本地浏览器测试都看不到它。
建议。 初始化时显式写 validatorUrl: null。若未来允许在线 validator,应做成明确 opt-in 的 URL 配置,
走共享 URL 验证并在隐私/CSP 文档中说明。浏览器测试应使用一个非 localhost 的虚拟 origin,拦截全部请求,
断言同源 spec 页面只请求首方资源。
F02 — 非法配置没有统一 warn/fallback,甚至击穿普通预览(P1)
ui-param.html 明确写着「caller validates the type」,但多个 caller 没有验证。最小复现得到:
| 输入 | 实际结果 |
|---|---|
ui.blog_index_size: nope |
普通构建失败:.Paginate 要求正整数 |
ui.sidebar_expand_levels: nope |
普通构建失败:add 无法处理字符串 |
ui.sidebar_menu_truncate: nope |
普通构建失败:first 无法转成整数 |
offline_search_summary_length: nope |
普通构建失败:truncate 无法转成整数 |
ui.sidebar_width_min: "1; color: red" |
零告警成功,输出 --td-shell-sidebar-min: ZgotmplZpx |
ui.sidebar_width_min: -50 |
零告警成功,输出 -50px |
blog_index_columns: 2.5 / section_index_columns: 2.5 |
零告警成功,把 2.5 送入 CSS repeat() |
ui.sidebar_item_overflow: clip |
零告警成功,静默当成 ellipsis |
ui.sidebar_menu_foldable: definitely |
零告警成功,非布尔字符串按 truthy 启用 |
ui.blog_index_size: 0 |
被 Hugo default 静默吞掉,回到 12 |
Landing 的 marquee.rows、capabilities.columns 和 Asciinema 的数字参数也直接调用 int/float,
错误文本会终止模板执行。print.toc、offline_search_max_results 等错误类型则静默改变行为。
影响。 这是对 Diagnostics decision 的直接反例:普通 hugo server 可能整体不可用,而错误输入也可能在
--panicOnWarning 下零告警上线。
建议。 为整数、正整数、范围、成对范围和 CSS grid count 增加共享 validator;先归一化再参与运算或输出。
每个公开键至少需要四态用例:合法站点值、合法 page override、非法普通构建(warn+fallback)、非法严格构建(失败)。
对 min <= max、分页大小 >= 1、列数为合理整数等交叉约束加领域 resolver,不要依赖浏览器吞掉坏 CSS。
F03 — OpenAPI 与 Asciinema 仍是 HTML-only 岛(P1)
Architecture/Components 规定 Markdown/LLMS 不含 td-* 组件标记,Print 静态展开且不依赖交互,RSS 只保留安全静态内容或明确省略。
但当前实现与公开示例表明:
redoc在生成.md中原样输出<style>、<div class="td-redoc">与<redoc spec-url=...>;swagger把可执行 inline initializer 直接写在 shortcode 中;asciinema的.md输出包含整套td-asciinemaHTML 与 JSON script;- Asciinema 的 Print 仍加载约 185 KB 的 player JS/CSS,只能碰巧打印某一帧;
- Swagger/Redoc 在 Print 里留下空容器,并仍可能装载 1–2 MB runtime;
- 这些 shortcode 没有进入 Markdown/RSS/Print golden 矩阵。
影响。 Agent 输出被主题 HTML 污染;纸面/EPUB 读者拿到空壳;Print/PDF 负担无意义的大 runtime; Swagger inline script 也破坏 CSP。当前用户文档把这些缺陷写成「输出形态」,等于让 reader guide 与规范契约相互否定。
建议。 三者都先读取 tdOutputFormat:HTML 输出完整组件;Print/Markdown/RSS 输出一个有标题的静态链接、
spec/cast 地址与必要的文字说明,或者明确省略。只有交互 HTML 才设置 capability flag。Swagger initializer 应移入稳定 chunk,
Redoc 的样式移入 stylesheet,新增四输出 golden 与 runtime-absence 断言。
F04 — Landing 的 CSS/URL/数值入口没有同一安全边界(P1)
layouts/_partials/landing/sections/hero.html 对 title_size 做了 CSS 长度验证,却把
media.ratio 与 media.max_width 原样拼进字符串,再整体 safeCSS。最小输入:
普通和严格构建均零告警,输出:
Landing 允许把 sections 直接写进页面 front matter,因此作者可以直接传入这些值。
其它 section 的 columns、rules、宽高、style、icon 与 URL 也各自处理;非法 javascript: 通常被 Go template
变成 #ZgotmplZ,但没有 warning,严格门禁仍通过;字符串列数会变成 ZgotmplZ,某些 int 转换则直接终止构建。
建议。 为 Landing 建立一层 section schema/normalizer:所有类型共享 class、icon、URL、CSS length、grid count、
boolean、enum 解析;section partial 只消费规范化结果。hero.media.ratio 应是两个受限 track 值而不是任意 CSS 片段,
max_width 走 CSS length validator。所有 link/action 复用 content/url.html,并给每种 section 一个负向用例。
F05 — 两个配置 URL 面绕过共享策略(P2)
params.ui.page_context_menu.links 经 url-template.html 替换占位符后直接 safeURL;
url_latest_version 也被当作「trusted site configuration」直接 safeURL。它们没有检查 scheme、host、空白或 protocol-relative URL。
最小配置可零告警产出:
点击该 URL 会执行 JavaScript,违反了公开的「safe URL」配置模型,也让复制来的配置片段拥有不必要的执行能力。
建议。 自定义动作只允许 http/https 与明确支持的站内相对 URL,并复用 content/url.html;
归档版本 URL 也应验证。浏览器 action registry 的二次检查值得保留,但 progressive-enhancement 的 <a> 不能绕过它。
F06 — 生成 Schema 与真实 YAML 不一致(P2)
generate-config-schema.py 的小型 YAML parser 不剥离行尾注释,至少 11 个默认值被生成成字符串,例如:
print.toc的默认值是字符串"true # ...",不是 booleantrue;print.section_break_wordcount、section_index_columns、blog_index_columns变成字符串;footer_style、blog_index、typography的 enum 默认值包含注释正文。
注释关联也会漂移:解释「breadcrumb 没有全站默认」的注释被挂到 section_index;解释 quick_links 的注释被挂到
sidebar_icon_policy;taxonomy icon 注释被挂到 pager_types;本地 chrome 注释被挂到 image_zoom。
Front matter Schema 还会把探测器读到的已移除键 release、upstream_attribution、downstream_modified 暴露给编辑器,
并把 navbar menu 的 Params.columns 误判成 page front matter。--check 只比较「同一个有 bug 的生成器」与已提交产物,
所以会稳定地保持错误。
建议。 不要继续扩展 ad-hoc YAML parser。使用能保留注释的正式 parser,或为默认值/描述建立显式机器元数据标记; scanner 需要区分 page、menu、shortcode 和 legacy detector 上下文。生成测试必须拿 Schema 默认值与 Hugo 实际解析值逐项比对, 并维护「禁止出现在补全中的已移除键」列表。
F07 — 配置与 Front matter 参考不是当前实现的完整参考(P2)
content/docs/customize/config.md 与 content/docs/write/frontmatter.md 都自称「每个主题实际读取的键的唯一完整参考」,
但当前存在多类实质错误:
- 日期默认仍写成长英文日期,而
hugo.yaml已是 ISO2006-01-02; - Blog 只写
none|banner|wash和list|cards,遗漏hero、table、toggle、size、toc_style、toc_taxonomies; - Front matter 仍把已移除的
releasemap、release_products、release_group_by_product当现行 API,遗漏release_url; images: []被写成「没有 featured image」,但契约明确 bundle resource discovery 仍继续;upstream_modified被写成新增一行,而现行契约是改变 credit verb,不新增行;- 大量页面说非法参数「直接失败」,与 warn/fallback decision 混在一起,普通预览与严格发布门禁没有说清;
- Book guide 仍说主题止于 Print HTML,而 v0.7 已发布 BookManifest、EPUB 与 PDF 工具;
- Asciinema/OpenAPI guide 将污染静态输出的现状写成产品契约;
- Features 页仍写 28 个 vendor 依赖,权威清单是 26 个。
中英文在这些旧答案上通常保持一致,所以 translation parity 不会报错。
建议。 先把配置参考与 Front matter 参考作为一次专门的契约迁移处理;从实现/Schema 生成一份可比对的 key inventory,
人工维护语义文字。发布门禁应检查:现行键全部出现、removed 键只出现在迁移章节、enum/default 与 hugo.yaml/resolver 一致。
F08 — Design 树出现互相冲突的权威和未退休提案(P2)
最直接的矛盾是:Shell 契约声明 navbar columns/mega panel 已退役、配置会 warning 并保持单列;
Landing 契约却仍声明「Navbar mega-menu columns accept 1–4」。实现与 checker 支持前者。
提案生命周期也没有按自己的规则执行:config-schema 已标记 implemented,仍位于 Active proposals;
Book publication 已把 manifest、EPUB、PDF 和 CI 做完大半,却仍以 Draft proposal 与正式 Architecture contract 重复描述;
media-convergence 把已实现里程碑和未完成 M4 混在一份原始设计记录中。
建议。 修正 Landing 契约;把已实现的 config-schema 稳定事实移到 Architecture/Decision 后退休提案; Book proposal 只保留尚未完成的 consumer migration 问题,或拆成新的窄提案。Active proposal 中不应存在第二份现行 API。
F09 — OpenAPI 无障碍承诺与测试排除项不一致(P2)
本站 axe 套件明确排除 .td-swagger-ui 和 .td-redoc。注释记录的已知问题包括 Swagger UI 的无名称 server select、
不可键盘访问的 scrollable version stamp,以及 Redoc operation description 的颜色对比度。
OpenAPI guide 却只公开 Swagger 的问题,并把「真正渲染的 Redoc」作为替代;这会让读者误以为 Redoc 满足本站的零违规门禁。
建议。 立即在 EN/ZH guide 中公开两者的真实边界。短期可通过主题 CSS 修复可修的 Redoc contrast, 对 Swagger 的可修 DOM 用 narrow post-render adapter;不能修的上游问题应有版本化 waiver、issue 链接和单独 axe 报告, 而不是把整块 DOM 排除后仍称全站零违规。
F10 — 当前主题不能直接配合严格 CSP(P2)
部署指南说同源资源使 strict CSP 可行,却只列作者 inline script、ECharts callback、analytics、远程 spec/diagram 和 Giscus。 主题自身在普通 Docs 页就输出两段可执行 inline script(颜色首绘与 shell prepaint)和 inline style;Markmap、Swagger、Algolia、 Google CSE 还增加主题自有 inline initializer。项目没有 nonce 参数、hash manifest 或完整的 CSP 示例。
影响。 script-src 'self' 会阻止主题自己的首绘与 shell 状态恢复;style-src 'self' 会阻止主题色、字体角色、Landing
和多个 inline custom property。站点只能加 'unsafe-inline'、自行维护 hash,或覆盖模板;当前文档没有说清。
建议。 把稳定初始化逻辑移到同源外部 chunk,以 data/JSON 传递页面配置;剩余必须 inline 的内容提供可生成的 CSP hash 清单, 或统一 nonce hook。文档应给出「最小核心」「带 Markmap/OpenAPI」「带第三方集成」三套策略,并明确 style-src 需求。
F11 — 浏览器兼容性承诺缺少基线与跨引擎证明(P2)
Playwright CI 只安装 Chromium;仓库和产品文档没有写最低 Chrome/Firefox/Safari 版本。
但实现依赖或增强使用 :has()、dialog、inert、color-mix()、@property、logical properties、
discrete display transition 等新能力。部分功能有 fallback,但没有一个浏览器矩阵证明它们。
RTL 主要依靠源码 marker、少量 JS 单测和一个临时给元素设置 dir=rtl 的几何测试;没有完整 RTL 语言站。
forced-colors 多数只检查 SCSS 中是否出现字符串,没有浏览器 computed-style/交互测试。
建议。 发布一个小而明确的支持矩阵,并至少对核心 shell/导航/内容/对话框跑 Chromium + Firefox + WebKit。
增加一条真正 languageDirection: rtl 的集成配置,以及 forced-colors、reduced-motion、320px、200% zoom 场景。
F12 — 输出安全和 Markdown 门禁没有检查自己宣称的全部表面(P2)
check-output-security.py 对 .md 只匹配 Markdown link 语法,不把其中 raw HTML 送入 HTML scanner;
因此 Redoc/Asciinema 的 <script>、spec-url 与 raw href 不会被发现。它也不检查 style 中的 url()、JSON config 中的 URL,
而 theme fixture 以全局 --third-party 运行,降低了第三方元素检查的区分度。
本站的 check-rendered-markdown.mjs 名字也容易误导:它扫描的是生成 HTML 的文本节点里是否残留 Markdown 标记,
并不读取生成 .md。真正的 md-output golden 只有 15 个页面,未覆盖 OpenAPI/Asciinema。
建议。 将门禁拆成三个明确工具:HTML trust、machine-output purity、rendered-text residue。
.md 中允许的 raw HTML 应有极窄 allowlist;CSS URL、form/action、JSON URL 与非可执行 JSON script 需要分别解析;
每个 public shortcode 至少进入一个 Markdown/Print/RSS 行为用例。
F13 — 两个仓库之间没有自动的候选提交集成门禁(P2)
主题 CI 只对 tests/site 合成夹具运行;文档站 CI 则只测试 go.mod 固定的公开标签。
主题 PR 的真实 EN/ZH/Playwright 验证依赖维护者本地执行 HUGO_MODULE_REPLACEMENTS,两个仓库的变更也无法原子提交。
这次的结果说明两边可以分别全绿,而公开参考仍与实现漂移。现有 release-state 文字区分是正确的,但自动化没有执行 「实现 + owning checker + EN/ZH contract」同一交付规则。
建议。 增加一个只读的跨仓库候选 workflow:主题 PR checkout 当前 SHA,同时 checkout 文档站指定 main SHA,
用临时 module replace 跑 npm test 与关键浏览器套件;反向也让 Design contract PR 指向待验证主题 SHA。
发布仍保持 tag/pin/deploy 分离,但候选提交应有一个可追溯的联合验证结果。
F14 — checker 维护成本和源码耦合过高(P3)
当前 checker 覆盖面值得肯定,但 34 个 check-*.py 中有 546 次 read_text();多数脚本重复实现 require、临时站点、
写文件、Hugo 命令和错误聚合。大量断言锁定模板/SCSS 的源码拼写、注释附近结构或整文件相等,而不是最终行为。
一部分 helper 又硬编码 theme: oink + --themesDir <repo-parent>,使 checkout/worktree 目录名成为隐藏前提。
项目没有统一的 Python lint/type gate。结果是新增 checker 很快,却更容易出现「门禁全绿但共同盲区没有人拥有」。
建议。 建立共享 fixture builder 和 assertion library;把负向 case 作为表驱动数据; 只给真正的 topology invariant 留源码检查,其余转到解析后的 HTML/JSON/computed style。 测试主题应通过显式 symlink/module replace 装载,不依赖仓库 basename。
F15 — runtime 拆分成功,但基础 CSS/字体仍占主要首访成本(P3)
严格隔离 fixture 基线:
| 指标 | 数值 |
|---|---|
| 冷/热构建 | 1.256 s / 1.273 s |
| 页面 | 249 |
| stable JS chunks | 18 |
| main + Font Awesome CSS | 549.8 KB raw / 91.1 KB gzip |
| 字体总量(其中 FA) | 999.7 KB raw / 248.5 KB gzip |
| Docs 页 JS 中位数 | 176.9 KB raw / 55.3 KB gzip |
| 生成 public | 26.2 MB |
| v0.7.0 Go module zip | 7.8 MB(展开约 20.5 MB、1,140 文件) |
第一方 capability chunk 已经消除了 2^N 组合包,这是正确方向;大第三方 runtime 也按页面隔离。
剩余主要成本来自所有页面都加载的 Bootstrap/主题/Landing CSS 与完整 Font Awesome 分发。
建议。 不要违背现有合同去按模板用量裁剪 Font Awesome。优先测量可独立缓存/按 surface 加载的 Landing、Book、Swagger CSS, 检查真实首访实际加载的 font subset,并给预算建立趋势报告而非武断阈值。
F16 — vendor 可复现,但漏洞与 CI 供应链仍靠人工(P3)
正面证据:VENDOR.json 精确记录 26 个包、56 个 artifact、31 个 license 文件和 tree hash,
check-vendor.py 通过;本次 OSV 与 npm audit 均未发现已知漏洞。
缺口:custom manifest 没有进入通用 SBOM/OSV gate,npm audit 也天然看不到这些 vendored 浏览器包;
文档站两个 workflow 通过 curl 下载 Hugo .deb 后直接 sudo dpkg -i,没有校验摘要;Actions 用可移动的 major tag,
主题 CI 的 Python 是浮动 3.x。
建议。 从 VENDOR.json 生成 CycloneDX/SPDX SBOM,增加定期 OSV 扫描;Hugo archive/deb 固定 SHA-256;
高信任 release workflow 的 action 固定 commit SHA;选择明确 Python 版本或建立版本矩阵。
F17 — 设计记录与发行文字的信噪比下降(P3)
CHANGELOG.md 已有 1,768 行,0.7.0 单节约 300 行;Unreleased 用约 20 行解释一次 checker retry。
这些叙事对工程复盘有价值,但升级读者很难快速找到 breaking change、迁移和行为差异。
同时,book_kind/book_part 被契约「认可」并出现在大量内容 front matter,却明确不被模板读取;
它们给作者增加了类似 API 的负担但没有行为。已实现提案仍留在 Active proposals 又放大了重复答案。
建议。 Changelog 保留用户可观察变化、breaking/migration 与修复摘要;长设计故事移到 Blog/Research,并从 changelog 链接。 没有行为的 metadata 要么定义消费者和 schema,要么从公共契约降级为站点自有字段。
F18 — Print isHTML FIXME 已经失真(P3)
hugo.yaml 说「等 Hugo 修复 #14381 前保持 isHTML 未设置」。该 Hugo issue 已于 2026-01-17 修复,
修复进入 OINK 兼容性下限之前的 Hugo 0.155 系列;OINK floor 是 0.160.1。
但在当前主题上简单启用 isHTML: true 仍会产生 page/section/landing print layout missing warnings,
严格构建失败。这说明真实依赖已经从「等待 Hugo alias fix」变成「当前 Print 模板命名依赖 non-HTML lookup 规则」。
建议。 不要直接删除 workaround。先为 HTML-classified Print 补齐 lookup matrix 与 alias/subpath 测试; 若继续保持 false,就更新注释说明当前真实原因,并增加一个测试防止未来维护者依据已关闭 issue 做错误清理。
做得好的地方
- 主题、文档站、发布标签和消费站 pin 被明确区分,没有把本地 replacement 当成发布;
- Hugo floor 0.160.1 与 0.164/0.165 的主题矩阵覆盖扎实;
- 大多数新组件已经遵循 warn/fallback、四输出、共享 URL/attribute policy 与 capability flag;
- 32 个 locale schema 一致,EN/ZH 真实页面、标题 ID、站内链接和窄屏导航有强门禁;
- 搜索、键盘、surface coordinator、页面动作和主题色测试既有单测也有浏览器行为测试;
- vendor license/hash、EPUB/PDF 的路径边界、PDF loopback+CSP 与不可覆盖默认值设计认真;
- 320px 人工复核未发现页面级水平溢出,当前核心视觉质量良好;
- 构建性能很好,第一方 JS 已从组合 bundle 迁移到稳定 capability chunk。
建议修复路线
阶段 0:下一个标签前
- Swagger 写死
validatorUrl: null,增加 production-origin no-network test; - 建立公开参数 inventory,为 F02/F04 中所有字段补 validator 与负向矩阵;
- 重做 Swagger/Redoc/Asciinema 四输出和 runtime gate;
- 修复自定义 action/归档版本 URL;
- 修复 Schema parser/scanner,并重新生成两份 Schema;
- 同步 EN/ZH Config、Front matter、OpenAPI、Asciinema、Book、Features 与 Landing contract。
阶段 1:契约门禁
- 为 29 个 shortcode 建立最小 HTML/Print/Markdown/RSS coverage map;
- 拆分并增强 output trust / machine-output purity 检查;
- 将 Landing section 输入统一归一化;
- 外部化 theme-owned inline initializer,发布 CSP 参考;
- 建立跨仓库候选提交 workflow。
阶段 2:兼容性与结构
- 加 Firefox/WebKit、真实 RTL、forced-colors、200% zoom;
- 收敛 Python checker harness 和源码字符串断言;
- 评估按 surface 拆 CSS 与字体实际请求;
- 生成 SBOM、定期 OSV、固定 CI 下载摘要;
- 退休已实现提案并精简 Changelog。
完成判据
- 使用同源 Swagger spec 的生产 origin 除首方资源外无请求;
- 每个公开配置错误在普通构建中 warn+fallback/omit,在严格构建中失败,且不出现 Go template
ZgotmplZ; - 生成
.md不含td-*、theme<script>/<style>或空交互容器; - Print 不加载 Swagger/Redoc/Asciinema runtime,并给读者可理解的静态替代;
- Schema 默认值类型与 Hugo 实际解析完全一致,removed key 不出现在补全中;
- EN/ZH 配置和 Front matter 参考的 key/enum/default 与实现 inventory 一致;
- 核心 Playwright 在 Chromium、Firefox、WebKit 通过,真实 RTL 与 forced-colors 有行为断言;
- 主题候选 SHA 有一条可追溯的真实文档站联合验证记录。
审查边界
本次没有逐一审查全部消费站仓库、真实生产响应头/CDN 缓存、Firefox/Safari 实机、读屏器, 也没有人工逆向 13 MB minified 第三方源代码。漏洞查询是 2026-08-26 的快照,之后可能变化。 DDIA/TPME 的 EPUB/PDF 真实消费站结果引用自现有 CI 与契约记录。
7.8.8 - 社区 Issue 与 PR 调研,2026-09-19
本文记录 2026-09-19 的源码审查、GitHub 状态、本地构建与定向浏览器观察,文末附有同日实施与验收补记。
以下保留最初的调研快照。随后维护者决定先合并 PR #43,再直接在 main 实施其余修复; 最新进展见同日实施与验收补记。
判断结论
这些反馈都有值得处理的内容,但不能全部归为同一种缺陷。应优先修复隐藏导航仍能获得焦点的问题。 PR #43 的小修复方向正确,明确边界并补齐测试后可以接收。搜索尾部扩展与侧栏状态公开接口则属于 新增 API,应分别设计和验收。
| 项目 | 判断 | 建议 |
|---|---|---|
| PR #43,MagicFollower | 收集自根分区时忽略显式的 sidebar_root_menu: false,已复现 |
有条件接收:补丁正确修复全站候选集合,合并前说明当前根例外、补回归测试并取得 CI 成功记录 |
| #41,imbajin | 整个侧栏隐藏后仍可聚焦;展开状态存在多处写入,缺少公开 API | 拆成无障碍修复与可选 API 两项工作,前者优先 |
| #44,lloydsun | 点击后按键出现边框的现象真实,已在另一平台复现 | 改进正文容器的焦点样式,保留滚动区域必要的键盘提示;浏览器的判断机制本身符合预期 |
| #42,aucru | 现有隐藏和分隔选项不能完整表达“不可跳转的分区标题,下面保留子页” | 先解释配置区别并获取作者的最小示例,确认后补齐分组能力 |
| #40,imbajin | 当前确实没有受支持的接口,向搜索结果追加依赖本次查询的操作 | 合理的小范围扩展需求,不是现有本地搜索失效;优先级低于正确性修复 |
基线与方法
通过 GitHub API 查到 4 个未关闭的外部 Issue 和 1 个未关闭的外部 PR。另一个未关闭的 #37 是维护者自己的版本发布跟踪项。 截至本次快照,这些外部反馈尚无讨论评论,PR 也没有已提交的 review。
| 输入 | 已核实的快照 |
|---|---|
主题远端 main |
93ac292014a3cd81f7c41caec4df98ed9d2dc45a |
| 本地主题 | 75ddc95,相对远端仅有 CHANGELOG.md 发布文字差异 |
| PR #43 HEAD | 8eeb8ecaf525097cc56572fe22234db381bfc16a,只改一行模板 |
| 本地文档站 | ff0ba39,go.mod 仍依赖 OINK v1.0.0 |
| 公开版本 | GitHub 最新 Release 为 v1.0.0,远端查询没有返回 v1.1.0 标签 |
| 构建工具 | Hugo Extended 0.166.0、Node 26.9.0、npm 11.19.1 |
| 浏览器观察 | macOS、Chromium 153.0.0.0、浅色主题;真实同级文档站通过单次命令的模块替换使用本地主题 |
调研覆盖完整 Issue/PR 正文与评论、精确 diff、双语 Design 契约、相关模板和 JavaScript、
部署在 /sub/ 下的临时双语站点,以及真实文档站的定向浏览器交互。共享工作区中的主题实现未改动。
PR 43:接收小修复,明确它解决到哪里
root-menu-roots.html
第一轮收集顶层分区时会检查 sidebar_root_menu;第二轮收集
sidebar_root_for: self 分区时没有检查。于是第一轮已排除的节点又被第二轮加回来。
PR 给第二轮补上同样的显式 false 判断:
这保留了未配置和配置为 true 时的默认行为,也保留了分区类型约束与 URL 去重,不改变侧栏树及 翻页顺序,不破坏按语言缓存。没有必要为这一个遗漏重构整个导航系统。
但是,后面的 root-menu-entries.html 还会把不在集合中的当前根追加回来。这是已有行为,导航指南也提到最后追加当前根,不能误报为 PR 引入的新回归;但它意味着不能宣称“设成 false 后,在所有页面都不再显示”。
临时复现站包含一个顶层 Blog 自根、一个嵌套 Docs 自根,二者均设
sidebar_root_for: self 和 sidebar_root_menu: false,另有可见 Docs 根和未覆盖可见性的自根。
英文、中文结果一致,URL 都正确保留 /sub/ 与语言前缀:
| 当前浏览页面 | 修改前 | 应用 PR 后 |
|---|---|---|
| 不相关的 Docs 页面 | 两个隐藏自根都出现 | 两个都消失 |
| 隐藏 Blog 根下的页面 | Blog 出现 | Blog 仍被当前根回退逻辑加回来 |
| 隐藏嵌套根下的页面 | 嵌套根出现 | 嵌套根仍被当前根回退逻辑加回来 |
| 可见自根 | 出现 | 继续出现且不重复 |
建议把契约明确为:false 将节点排除在全站可选根集合之外,但当前根可为位置提示而保留。 保留这个已有例外是改动最小、最兼容的解释,需要在两种语言中写清楚。如果真正希望 false 表示绝对排除,就要另行协调修改当前根回退和切换器标题逻辑,并检查零入口、单入口状态。 只再加一个条件,不足以完成这项语义变化。
合并前应补齐:
- 在
bin/check-shell.py增加输出测试,覆盖顶层与嵌套隐藏自根、未设置/true、去重、当前根 例外、单入口退化,以及 EN/ZH 子路径。 - 同步更新 Shell 契约与导航指南。PR 描述示例里的 YAML 注释
//也应改成#,保证可复制。 - 处理工作流的
action_required 状态,在最终 HEAD 上运行
必要检查。截至快照,PR HEAD 尚无完成的 check run(API 状态为
MERGEABLE、UNSTABLE)。
维护者可以保留贡献者的提交并补上这些收尾工作。不应把实现 #40 或完整 #41 作为接收这行修复的前提。
Issue 41:先修隔离,再公开状态
这里有两件不同的事。
第一,整栏隐藏主要依靠 transform,桌面还使用 opacity;抽屉和折叠控制器没有将隐藏控件移出键盘
导航。在浏览器实测中,点击折叠按钮后,焦点留在已经透明的折叠按钮上,再按 Tab 就进入隐藏的根
切换按钮。此时面板 opacity 为零,也没有生效的 inert 或 aria-hidden 祖先。这是可复现的键盘可用性缺陷。移动端关闭面板的实现同样仅移到屏幕外,没有显式隔离。
第二,展开状态分别由 点击控制器、响应式搬迁 和 缓存活动路径补全 直接写入,没有公开 setter、getter 或状态提交事件。现有对整栏折叠、宽度和滚动位置的存储,不会跨页面保存每个分支的展开状态。创作指南中“读者的展开状态保存在本地”也需要说明这个区别。
建议分步处理:
- 集中处理整栏隔离,在初始化及打开、关闭、折叠、悬浮展开、恢复和断点切换时同步更新。 先解除隔离再把焦点移进去;关闭时先把焦点归还给可见的外部控件,再使内容 inert。
- 隔离内容区,同时保留外部恢复按钮和桌面边缘的悬浮感应区域。直接把感应区域一起 inert 会破坏
已有悬浮行为。仅用
aria-hidden不能阻止键盘进入; HTML 的 inert 定义 同时约束交互与无障碍树暴露。 - 将分支展开修改另行收敛到一个提交函数:更新
aria-expanded、展开 class 和本地化标签后, 再发送一次事件;重复写入同一状态不重复通知。 - 在定义稳定 ID、非法 ID 处理、初始化就绪信号和恢复次序后,再公开最小 setter/getter 与事件。 版本和语言的存储命名空间继续由下游管理,恢复后以当前活动路径展开为准。
原提案有一处范围需要校正:TOC、反向链接和分类法分组在宽屏时会从侧栏移回右栏。 如果控制器只查询“当前侧栏 DOM 下的后代”,就无法同时管理宽屏时的这些写入。 应按 OINK 管理的目标注册元素,不依赖其当下 DOM 父节点;对外的侧栏 API 则只开放约定的注册子集。
验收必须检查隐藏状态下的真实 Tab 顺序与无障碍树、初次载入时恢复折叠、悬浮进出、焦点归还、 Escape、遮罩关闭、滚动解锁,以及 768/1200 断点。还要保留 #24 已修好的无 JavaScript 导航。
Issue 44:现象真实,部分行为符合预期
在文档站配置页分别点击文章标题、表头单元格和代码块,会让 main#td-main-content、
div.td-table-scroll、pre.chroma 获得焦点。三者在点击后都不匹配 :focus-visible,按下未绑定
快捷键的字母 z 后都开始匹配。正文和代码块出现浏览器的 auto outline,表格使用主题的实线
outline。这说明不需要作者的 Linux 桌面或 Super 键,也能复现相同机制。
Selectors 规范 明确描述了这种情况:键盘交互可以改变焦点提示,即使焦点元素没有变化。因此,这是一条有价值的 阅读体验反馈,但“鼠标聚焦之后,无论再按什么键都不该出现框”不是浏览器必须遵守的正确性要求。
建议这样处理:
- 保留 main 作为跳过导航的目标以及它的可聚焦性,把包围整栏的 outline 改成正文入口附近的局部 可见提示,例如标题区提示,并实际验证 skip link。
- 保留可滚动表格和代码块的键盘焦点提示,必要时统一其视觉样式。可聚焦性使键盘滚动成为可能。
- 不做全局
outline: none,不删除所有tabindex,不在任意按键后主动 blur。 - 如果产品仍决定抑制“鼠标先聚焦再阅读按键”这一路径,需要明确新增语义,只限定这些非编辑容器, 并验证鼠标、Tab、skip link、程序聚焦、深色及强制颜色模式。为这条反馈建立全站输入模式框架 并不划算。
建议对正文焦点样式做小范围改进,同时保留表格和代码块的键盘焦点提示。
Issue 42:区分隐藏与分组
提问写的是 _index.json,并依赖截图而没有提供源码复现。本次未能成功完成原始截图的视觉核验,
第一项需求具体想改变哪里仍需澄清。回复时应请作者提供小目录树及实际 index/front matter,
不能没有证据就断言 _index.json 是受支持的页面源文件或只是笔误。
现有选项的含义并不相同:
| 选项 | 当前行为与限制 |
|---|---|
no_list: true |
隐藏分区正文里的子页面列表,不隐藏其侧栏节点 |
hide_summary: true |
隐藏父分区正文列表中的某一项,不改变侧栏分组 |
toc_hide: true |
内容树遍历在递归前过滤该节点,也会从这棵树中移除其子树 |
sidebar_root_menu: false |
控制顶部根切换器候选,不控制阅读树中的行;另见 PR #43 |
sidebar_root_link_self: false |
将自根的链接指向父节点,不会变成不可跳转的分组标题 |
sidebar_divider: true |
输出无链接标题,但共享渲染器在这个分支中没有输出已传入的子项 |
build.render: link |
不生成分区 HTML,但保留 permalink;当前侧栏仍会输出指向它的链接,不能单独解决问题 |
临时站点确认:带 divider 的分区,其子页面 HTML 仍然存在,但侧栏子链接消失;仅设置
build.render: link 的分区没有自己的 HTML,侧栏中却仍有可点击的分区链接和子页。
这与 Hugo 的
构建选项定义
以及主题的
共享节点渲染器
一致。
如果真实需求就是“保留分组标题和子链接,但标题不跳转到目录页”,优先考虑补齐
sidebar_divider 在分区节点上的行为:叶子分隔项保持原样,有子项的分区保留 children,允许折叠
时使用真正的 disclosure button。决定前应检查已有消费站;只有现有 divider 契约无法兼容表达时,
才增加独立的节点级开关。“是否生成分区页面”与“导航标题是否可点击”应分别处理。
实现必须保留两套遍历器中的层级和活动路径展开,使子页继续进入翻页顺序。如果目录页确实不发布, 还要检查面包屑、搜索、根切换器、Print 和机器可读导航,不能留下死链接。直接用 CSS 隐藏整个节点 解决不了这个需求。
Issue 40:可以接受范围受限的扩展方向
源码核实了提案指出的限制: groupsFor 只组合内置页面和操作;公开 Palette 对象没有 provider 注册接口; registerExecutor 只允许内置 action ID。静态 URL command 无法替代携带当前查询的结果行。
提议的 search-tail 位置是合理的上游接口边界:同步返回纯数据行、异步执行操作、本地结果优先, 由 OINK 管理渲染、选择、键盘和 ARIA。OINK 不需要因此内置 AI 服务商、凭据、远程搜索或通用插件系统。
接受 API 前,需要定清并测试:
- 只在哪些已完成的文本搜索状态调用 provider;保留空查询、命令、选择、加载中,以及未注册扩展时 的原有行为。
- 保存生成每一行时的 query/locale 快照,保留原生空结果、索引错误及重试提示,区分本地页面数与 全部可选择操作数。
- 校验并复制 descriptor,把标题和描述当文本渲染;隔离 provider 异常、重复 ID 和非法描述符。
- 同时处理同步 throw、Promise rejection、pending 释放、重复激活、旧会话取消,以及 ID 重用后的 旧 unregister 句柄。
- 验证向另一个对话框移交焦点。现有 Palette 关闭逻辑已经会在焦点移出后避免强行归还,应保留这个 判断。还要区分“成功把交互交给新界面”和“取消”,否则“任何 close 都 abort 激活”的规则可能 把刚打开的助手操作一并取消。
- 保持默认查询不出浏览器和按需加载资源的行为,并在文档中说明注册扩展以受信任的首方脚本身份在页面上下文中运行。
实现前,应把接受的 API 形状沉淀为双语 Design 提案。下游 Ask AI 包装可以继续使用,直到公开标签 包含该接口。它不应成为小型正确性修复的发布前提。
实施顺序与归属
| 顺序 | 交付内容 | 对应验收与文档 |
|---|---|---|
| 第一批 | PR #43 收尾与隐藏侧栏隔离,分别交付 | check-shell.py;文档站响应式、键盘、无障碍用例;双语 Shell 契约与导航指南 |
| 第二批 | 正文焦点样式,以及确认后的分组行为 | 对应内容、阅读、导航检查器;浏览器焦点、滚动、skip link 用例;双语架构、外壳与创作说明 |
| 后续 | 公开展开状态控制器,再做 search-tail API | 主题 JS 测试及 check-navigation-contract.py / check-palette.py;真实站点 fixture;已接受的双语 API 契约 |
每项公共行为修改先跑对应检查器,再通过同级文档站的 make check、make browser、make dev
做相关集成与视觉验收。不要把这些不同需求绑成一次大规模侧栏/搜索重写,也不要等待所有新功能
完成才交付小修复。
建议回复内容,尚未发送:对 #43 承认过滤遗漏并解释当前根例外;对 #41 接受隔离缺陷、拆开 API 诉求;对 #44 确认复现并说明标准焦点机制;给 #42 解释配置区别并索取最小输入;把 #40 归为受限 扩展需求,而非本地搜索故障。
历史外部反馈已经关闭:#22 通过开启 Goldmark passthrough 解决,提问者明确确认有效;#21 已提供 Mermaid 查看器与 固定居中展示,任意右对齐选项则明确没有纳入。二者不应被算作新的待处理缺陷。
验证结果与边界
本次实际执行:
- 本地基线及 PR #43 精确 HEAD 的隔离 checkout 均通过
python3 bin/check-shell.py。 - Palette 控制器和模型测试通过,共 2 个测试文件,零失败。
- 对真实 PR diff 应用前后分别进行严格 Hugo 临时构建,在 EN/ZH 与
/sub/下复现自根过滤和 当前根回退;同一 fixture 验证了分组配置的限制。 - 真实双语文档站使用本地主题,通过带
--panicOnWarning的构建;Chromium 定向交互复现三个 容器的焦点边框,以及桌面侧栏隐藏后的焦点问题。 - 文档站双语覆盖、渲染与链接检查通过,非浏览器套件的 57 项测试均已通过。首次
make check因新增报告改变llms.txt索引而停在快照检查;确认仅新增报告这一行、同步快照后,重跑该组 及其余测试组全部成功。
本地验证使用 macOS 上的 Hugo 0.166.0。Linux Super 键、移动端无障碍树隔离、深色/强制颜色模式等场景在下方实施补记中完成验收。
实施与验收补记
维护者决定先合并贡献者补丁,再直接在 main 完成修复与扩展,不另提 PR。
PR #43 已合并为
6e814089,
随后拉取到本地,保留原有发布说明提交。第二阶段实施提交为
56bfe37。
| 项目 | 已实现的行为 | 对应验收 |
|---|---|---|
| #43 | 两条根收集路径都遵守显式 false,保留当前根的位置提示;分隔项和未发布分区不成为切换器链接 | 严格 EN/ZH 子路径 fixture 覆盖顶层/嵌套隐藏根、未设置/true、去重、当前根回退以及零/单入口 |
| #41 | 单一控制器统一提交 ARIA、类名、标签和 inert 状态;提供晚加载安全的 API,支持下游持久化;隔离隐藏侧栏内容,保留悬浮恢复和抽屉行为 | 运行时与浏览器测试覆盖事件原子性、重复写入、作用域、活动路径、存储禁用、响应式搬移、焦点归还、真实 Tab 遍历、Escape、遮罩与断点 |
| #44 | 记录指针来源焦点,抑制后续无关按键触发的容器边框;Tab 与新程序化焦点保留提示,跳转正文时突出标题 | 正文、表格、代码块在亮色/暗色/强制颜色下的浏览器测试,以及键盘与 skip link 回归 |
| #42 | 分隔分区显示不带链接的标题并保留子页,配合 build.render: never 省略自身页面;面包屑、搜索、翻页、导航 JSON、Book 目录/Markdown、Print 保持一致;显式导航兼容双语子路径 |
内容树/数据树严格 fixture、Book 三级标题检查、EN/ZH 浏览器 fixture 和无 JavaScript 遍历 |
| #40 | 受信任站点脚本可注册同步纯数据搜索尾部行及异步激活;排序、ARIA、校验、异常隔离、取消、注销和焦点移交仍由 OINK 管理 | 运行时生命周期测试,以及中文页面的鼠标/键盘选择、纯文本显示、上下文快照、外部对话框焦点和注销场景 |
使用同级主题 checkout,在 macOS、Hugo Extended 0.166.0、Node 26.9.0 与 Chromium 上完成:
- 主题 JavaScript 测试 44 项全部通过。
check-shell.py、check-reading.py、check-palette.py、check-keyboard.py通过。 按主题 CI 配置扩大检查,另外 31 条命令中 29 条通过;本地两项失败来自媒体检查器固定的图片处理 指纹,以及四份黄金文件中 Hugo 0.166 改变的 KaTeX 输出。保留原有空白格式后,普通导航标记与 黄金文件一致。固定 CI 工具链的结果单独核验,不因此改写无关预期。make -C ../oink.pgsty.com check通过:双语源文件、渲染与链接检查,以及 57 项非浏览器测试。make -C ../oink.pgsty.com browser共 141 项通过:无障碍 30,响应式/博客/Palette 45, 键盘 16,内容 10,代码块 18,场景 4,主题色 5,社区回归 13。无障碍套件包含完整双语站点地图逐页扫描。- 主题 fixture 严格构建、输出与命名空间检查通过。Book 本地打包生成五章 EPUB,检查零错误; PDF 为 23 页,包含全部五个预期 Book 页面,检查零错误。
- 通过
make dev在真实文档站实测桌面亮色、中文暗色、折叠侧栏恢复,以及 375px 移动抽屉。 Escape 关闭后焦点回到可见的打开按钮。验收后已恢复浏览器视口并清理临时开发服务。
已接受的契约写入 Shell 与架构, 中英文同步,并更新导航、组织内容、Palette 和 Print 指南。集成回归留在文档站仓库,主题只保留 专项检查器和合成输入。
PR 合并提交的固定工具链 CI
三个任务全部通过。最终实施提交的 CI
也已全部通过,精确版本为 56bfe37092a43fc12c0e16f865d3d3407c55cbde:Hugo 0.165.0、
浏览器运行时测试与 Book 出版三个任务均成功,包含本地 Hugo 0.166.0 出现差异的媒体和
四状态黄金文件检查,以及根路径/子路径出版。声明的 0.160.1 兼容性下限没有额外复测;
本项目固定的持续测试工具链仍为 0.165.0。
在该检查点,文档站仍固定 v1.0.0 等待 v1.1.0 标签发布,Issue #40、#41、#42、#44 保持打开状态等待正式版发布后关闭。
图片复制补记
维护者另行报告:把博客文章复制到富文本编辑器后,图片下面出现预览提示。博客固定
OINK v1.0.0,并开启 params.ui.image_zoom。该版本与本次审查的 main 都会在每张
符合条件的图片后插入一个视觉隐藏的文字 span。Chromium 原生复制已复现剪贴板额外
带入“打开图片预览”及对应英文文字,因此这是独立于目标编辑器的主题缺陷。
主题提交 75052f8 把图片描述与本地化
操作改放到按钮的 aria-label,不再向文章插入辅助文字节点。图片 alt、作者图注、
原生按钮操作和对话框焦点归还均保留。组件契约
与图片指南 已同步说明复制行为。
check-image-zoom.py 与站点全部 57 项非浏览器测试通过。专项浏览器验收 16 项通过:
14 项内容组件测试,其中包含新增的中英文图片/画廊四项剪贴板回归,以及桌面亮色/
手机暗色两项对话框无障碍检查。回归读取纯文本与 HTML 两种剪贴板格式,检查脱离样式
后的文字,并核对图片地址、alt、图注和按钮的无障碍名称。
下游博客升级主题依赖并重新构建后即可获得该修复。
7.8.9 - OINK 1.1 发布审查,2026-09-20
本文记录 OINK 1.1 发布前的验证快照与五项运行时修复,文末附有 v1.1.0 发布后续记录。
范围与基线
审查从主题提交
75052f8a3106d13ef313644836a5ad545135f484
开始,此时社区反馈与图片复制修复已经进入 main。范围包括 v1.0.0..main 差异、
#40、
#41、
#42、
#44 与已合并
PR #43 所要求的行为,以及双语文档和发布边界。
前一轮调研
记录了原始反馈及其实施过程。
方法是检查拥有相关行为的 JavaScript、模板与契约,通过定向回归覆盖状态转换,比较修复前 失败断言与修复后的实现,再运行主题检查器和真实同级文档站的集成、浏览器套件。
截至本次快照,公开版本、文档站 go.mod 固定依赖与配置中的公开版本仍为 v1.0.0。
博客消费站固定的主题版本保持不变。
发现与修复
本轮复现并修复了五项 P2 正确性缺陷,已提交为
08f6563
并推送 main。它们涉及新增 API 的时序和既有焦点、导航行为,
不需要新增配置格式或迁移正文内容。
| 发现 | 触发条件与观察到的问题 | 最小修复 |
|---|---|---|
| P2:侧栏就绪早于活动路径补全 | 消费代码等待 OinkSidebar.ready 或监听 oink:sidebar-ready;就绪微任务可能在两个 DOMContentLoaded 监听器之间执行,早于缓存侧栏的活动路径补全,读到不完整的初始状态。 |
把就绪通知放入下一任务,等待初始化监听器与右侧内容搬迁完成,保留 ready Promise 和事件契约。 |
| P2:待完成操作仍可进入原生选择菜单 | 启动异步搜索扩展操作后,再激活主题切换等原生选择项;pending 检查位于选择分支之后,选择菜单会替换待完成操作的结果行。 | 将 pending 检查移到所有行类型分支之前;操作完成后恢复正常的选择项激活。 |
| P2:右侧 TOC 整栏折叠后仍可聚焦 | 桌面端收起右栏后,隐藏的按钮与链接仍是键盘目标,焦点也可能留在隐藏面板内。 | 为右栏面板设置 inert 和 aria-hidden,把焦点交给可见恢复按钮,恢复时归还栏内按钮;右侧内容搬迁后不继承原栏隔离。 |
| P2:方向键跳过无链接分组 | 从分隔分组的子页按 Left/a,无法稳定回到父分组按钮并折叠;分组按钮不在树的焦点序列中。 |
将分组展开按钮纳入树焦点导航,按直接父级回退;Right/d 展开或进入分组,上一页/下一页仍只遍历页面链接。 |
| P2:抽屉焦点循环计入 inert 后代 | 在移动抽屉中折叠右侧搬入的分组,再按 Shift+Tab 循环;隐藏后代仍被计作可聚焦元素,可能导致循环失败或焦点停滞。 | 从抽屉可聚焦集合排除 inert、hidden 祖先内的元素,以及 visibility 为 hidden/collapse 的控件。 |
实现与回归归属:
| 发现 | 主题实现 | 拥有该行为的回归检查 |
|---|---|---|
| 就绪时序 | assets/js/sidebar-state.js |
tests/js/sidebar-state.test.js;站点 tests/browser/community-feedback.spec.mjs 在两种就绪信号中读取 EN/ZH 活动路径快照 |
| 待完成选择 | assets/js/command-palette.js |
tests/js/command-palette.test.js 覆盖扩展待完成 → 原生选择 → 完成 → 选择恢复可用 |
| 右栏焦点 | assets/js/docs-shell.js |
站点 tests/browser/community-feedback.spec.mjs 覆盖 EN/ZH 折叠、Tab 遍历、恢复、重载和桌面/平板/手机搬迁 |
| 分组按键 | assets/js/keyboard-nav.js |
tests/js/keyboard-nav.test.js 覆盖 LTR/RTL、方向键/WASD 与仅链接翻页;站点社区回归覆盖真实 EN/ZH 分组 |
| 抽屉循环 | assets/js/docs-shell.js |
站点社区回归折叠已搬迁分组,执行 Shift+Tab 与 Tab 循环,并断言焦点不进入 inert 或 hidden 子树 |
修复前,就绪时序与待完成选择的新增单测在旧实现上失败;右栏隔离的浏览器断言也在中英文 两种页面上失败。修复保持局部:调整就绪通知时序、提前一项 pending 检查、明确右栏隔离、 补齐树焦点目标和抽屉可见性过滤。
这些改动保留已有结果:两个根收集器都遵守 sidebar_root_menu: false;无链接分组保留
子页;指针聚焦不引入正文大边框,键盘提示仍然可见;搜索扩展保留取消与交接契约。图片预览
继续通过无障碍名称表达操作,不向复制的正文插入辅助文字。最终回归结果记录在下表中。
文档准备
当前使用文档更新覆盖 28 个文件、14 组 EN/ZH 页面:
- 六组 Design 契约使用
candidate-v1.1.0,描述 main 已实现行为,不提前声明正式发布。 - 导航、布局和 front matter 指南对齐根过滤、分隔分组、双语部署路径、居中顶栏、窄屏抽屉 与原位淡入的顶栏行为。
- 内容组织和命令面板指南说明运行时加载顺序、就绪、能力检测,以及站点负责的持久化和集成。 键盘与图片指南说明修复行为和旧版本边界。
- 安装指南区分验收工具链与公开版本;既有标题 ID 保持稳定,新增侧栏 API 标题的中英文 ID 匹配。
另已准备两份 1.1.0 发布注记和两份升级指南。发布注记保持 draft/candidate 状态,首页中英 发布入口都回到已公开的 1.0 版本。
验证快照
下表数字是主题 08f6563 在 2026-09-20 的验证快照。站点验收通过命令作用域内的模块
替换使用同级 checkout。
本地站点检查使用 Hugo Extended 0.166.0、Node 26.9.0 和 Playwright 1.62.1;候选版本
CI 使用固定的 Hugo 0.165.0 工具链。0.160.1 下限使用官方二进制单独验证。
| 检查 | 结果与范围 |
|---|---|
基线 75052f8 CI |
三项全部通过:固定 Hugo 工具链、浏览器运行时测试和 Book 出版。精确基线运行。 |
| 修复后的 JavaScript 单测 | 通过:44 项。 |
| 官方 Hugo Extended 0.160.1 | 定向 i18n 与 shell 检查通过;i18n 覆盖 32 份语言包 × 194 条消息。真实文档站也以本地候选主题通过 --panicOnWarning 生产构建,每种语言 376 页;不输出发布草稿,两个首页入口均指向 1.0.0。 |
| 双语源码与样式检查 | 包含本报告后通过:129/129 组页面、988 个源标题、Markdown 样式和 git diff --check。 |
| 最终定向主题检查器 | shell、palette、keyboard 与 image zoom 全部通过。 |
| 最终真实站点非浏览器套件 | make check 全部 57 项通过;检查 200 个正文页面、331 个含链接 HTML 页面、39,803 条内部链接与 3,863 个锚点链接。仅更新发布摘要和文档索引这两份预期变动的 Markdown golden。下限版本的生产构建也通过链接检查:327 页、39,059 条内部链接与 3,833 个锚点。 |
| 最终浏览器套件 | make browser 的八组 Chromium 测试全部 149 项通过:无障碍 30、响应式/博客/Palette 45、键盘 16、内容组件 14、代码块 18、场景 4、主题色 5、社区回归 17。包含完整多语言 sitemap、六种屏宽、深浅色、强制配色、剪贴板与无脚本场景。 |
| Agent 文档抽样 | 50 个同源页面得分 93/100(A);抽样链接均可解析,49 页提供 Markdown,270 处代码围栏均正确闭合。检查器提示 HTML 中的 llms.txt 发现提示缺失或位置过深。 |
| 渲染审查 | 查看了中文发布注记的桌面深色布局与英文窄屏浅色布局。右栏收起后,其后代从无障碍树移除,焦点交给恢复按钮;恢复后焦点返回栏内可见按钮。 |
| 最终主题版本及其 CI | 08f6563 的 Hugo 0.165.0、浏览器运行时测试和 Book 出版三项全部通过。精确候选运行。 |
| 公开 v1.1.0 标签、消费站点升级与部署 | 未执行。 |
在本轮审查与验收范围内,未发现尚未解决的实现阻断项,可以进入下方发布流程。
在同级 checkout 中复核,开发期间保留公开依赖固定版本:
待执行发布步骤
尚未执行发布。最终版本通过验收后:
- 确定
CHANGELOG.md、发布日期与发布记录,从通过验证的主题提交发布 v1.1.0 标签 和 GitHub Release。 - 验证模块代理可将该标签解析到预期提交。
- 一并更新文档站固定依赖、版本配置、首页发布入口、契约状态,以及发布注记的
draft: false状态。 - 不使用
HUGO_MODULE_REPLACEMENTS,从已发布依赖重新构建并验收文档站,再部署 并检查公开路由。
后续可以为从 HTML 进入的 Agent 提供位置更靠前、一致的 llms.txt 发现提示。
限制
浏览器验证基于 Chromium。无障碍门禁覆盖主题自有界面,保留对 vendored Redoc 与 Swagger UI 的既有排除;Hugo 0.160.1 兼容下限检查覆盖了定向 i18n、shell 与生产构建路径。
发布后续
审查之后,v1.1.0 正式版 已于
2026-09-20 从提交
3a18234
发布。该版本相对已验收的 08f6563 实现只修改更新日志。创建附注标签并发布稳定版
GitHub Release 前,全部三项发布提交 CI
均已通过。
仅使用官方 Go 模块代理、从全新缓存下载的版本准确解析到该提交。.info、.mod、
.zip、版本列表条目与签名校验和记录均已验证。模块校验和为
h1:121L5g57ChRCPyidzEBBcln2Co+0zYRQ+XDDXjymd0Q=,go.mod 校验和为
h1:pHvbUhJCfseB41n5RGwsF7abT3i32VSTpofLQoq4b7Y=。
公开记录见代理版本信息与
校验和条目。
文档站发布更新在 go.mod 与 go.sum 中固定 v1.1.0,同步公开版本标识和双语首页
入口,公开两篇发布注记,并将六对契约标记为 released-v1.1.0。上方历史验收表继续
描述此前的同级 checkout 运行。公开依赖的验证单独记录在本站的
Site checks 和
Browser quality
工作流中,两者均关闭 Go 与 Hugo 模块工作区。
本地使用该公开模块通过了全部 57 项非浏览器测试、26 项命令面板与社区问题浏览器测试,
以及严格生产构建(每种语言 378 页)。这些检查均设置 GOWORK=off、
HUGO_MODULE_WORKSPACE=off,且未使用 HUGO_MODULE_REPLACEMENTS。完整的 149 项
浏览器套件另由发布提交的 Browser quality 工作流执行。
7.8.10 - 2026-10-03 CLI 维护验收
范围与证据规则
维护路线图 定义已授权的 R1–R8 范围,当前 CLI 契约 定义兼容基线。原路线图 中的 Docsy 迁移、版本生命周期、OpenAPI、主题发布或条件性 E1–E4 扩展不在本计划内。 Hugo 继续作为外部渲染器,生成站点仍是普通 Hugo 项目。
各阶段按依赖顺序验收。每阶段都需要完整可用的流程、对应测试、相关真实 Hugo 集成、已知限制、可审阅 diff,以及已验收的中英文契约和指南更新。 新公开行为在实现和验收证据齐备后移入对应契约。
下表中的 已有,未重跑 表示已经检查代码或具名测试,但尚未确认本轮运行结果。 部分已有 表示首个候选提供了所需行为的一部分。未完成 表示缺少新实现或决定性验收证据。 后续记录 通过、失败、未验证 和 不支持 时,均标明具体执行输入和范围。
已检查输入与工具
2026-10-03 的初始审计读取了两个仓库的指令、文档站 README 和翻译规则、 维护 PRD 的中英文文件、原提案、当前 CLI 契约,以及已有 Go 包和测试名称。 初始审计阶段仅执行版本与 Git 检查命令。
| 输入 | 初始观察状态 |
|---|---|
| 主机与 Go | darwin/arm64;go version go1.27.1 darwin/arm64 |
| Hugo | hugo v0.166.0+extended+withdeploy darwin/arm64;Homebrew 构建日期为 2026-09-09 |
| Node 与 npm | v26.9.0;11.19.1;属于贡献者/文档工具,不是 CLI 下游运行依赖 |
| Git | 2.54.0 (Apple Git-157) |
| CLI 源码 | e623d93d589c49e5c58b8fae1bd5db720fc904cb,main;初始 tracked/untracked 状态干净;生成的 bin/、dist/、tmp/ 已忽略 |
| 文档源码 | 907d873eb05cfc2e194f492462dfa94849e93474,main;初始 porcelain 状态有 184 项,含已有提案、契约、指南及无关内容修改 |
| 内嵌 Starter | 137843b25bacd76ddd1f7ce71330bf2e3155b954;internal/starter 已记录来源和许可证 |
| 声明的主题基线 | Starter 与三个选定站点声明 github.com/pgsty/oink v1.1.0;有效解析字节在每次验收中确认 |
2026-09-29 验收记录 包含首个候选的历史检查,可提供复现输入。
阶段需求与实现证据
| 阶段 | 所需完整流程与不变量 | 初始实现证据 | 仍需验收证据 |
|---|---|---|---|
| R1 | 由 Hugo 提供共享页面身份、语言、发布状态、来源、实际输出、翻译和观察到的引用;oink.yaml 只管理检查政策;链接/翻译/风格共享分析;严重程度和排除项不能隐藏必需未完成;位置可信 |
部分已有:internal/site 隔离快照与 Page.OutputFormats 探针、internal/outputcheck、internal/report;初始审计时尚无共享翻译/页面事实或政策命令 |
真实 Hugo 路由、别名、挂载、未列出/生成来源场景与语言关系;公共分类检查/政策用例;必需未知、工具、构建、输入失败仍为 2;仅在可靠时报告源码位置 |
| R2 | 三种语言组织;严格/手册和本地化政策;重复、缺失和草稿状态;显式版本化审阅记录绑定源语言及源/译文哈希;有语法边界的原生规则;有效主题覆盖;可见版本化基线;审阅修复先验证再窄范围应用 | 未完成:初始审计时无翻译/审阅/原生规则/基线公共命令;可复用产物引用检查 | A04–A07;经审阅的有效/无效内容语料;不用 mtime 推断审阅;处理禁用/本地化语言;已确认问题仍可见;必需检查缺失仍为未完成;修复保留文件 |
| R3 | 保留默认透明 build/dev;build --check 在一次严格 Hugo 输出上检查和生成 manifest,只导出到新建/空目标;摘要/来源 manifest 和可选最小公开身份;两种本地 CI 模板上传同一树;发布诊断;显式联网公网验证 |
部分已有:直接封装、严格隔离检查和有许可证的工作流输入;初始审计时缺少受管理构建/导出、摘要验证、CI 计划和公网 verify | A08–A10;恰好一次 Hugo 构建;拒绝字节漂移;revision/dirty/input/theme/tool/settings/coverage 来源不含秘密或本机路径;工作流定制/冲突/来源及不可变源码输入;示例地址政策;回退/语言/资源/canonical/超时/认证/限流 HTTP 夹具 |
| R4 | new、片段和编辑器配置创建普通输入且不覆盖;docs/blog/book/project 配置组合复用一个有许可证的 Starter;升级提供可读 diff 及新旧路由、别名、启用输出;不支持迁移给出人工操作;保留原有保护 |
部分已有:固定归档语言配置、绑定哈希的单站模块升级、候选验证、备份,以及脏文件/workspace/replacement/vendor 保护 | A11–A12;所有新增配置/语言组合可用普通 Hugo 构建;保留未知编辑器设置;升级路由/能力回归和可读 diff;保留来源与许可证 |
| R5 | inspect、impact --since、受限 context、默认预览 move;共享计划包含文件、diff、基线哈希、翻译、附件、输出/路由变化和别名建议;候选验证与过期/并发安全恢复;含糊引用要求审阅 |
部分已有:模块专用升级计划/应用基础;初始审计时无共享内容计划或 inspect/impact/context/move 流程 | A13–A15;删除 B 包含未改入站 A;翻译/附件/派生产物影响;不确定/全局变化强制全量检查;不执行内容;候选/过期/写入失败保护及含糊链接处理 |
| R6 | 显式版本化站点注册复用单站引擎;逐站与汇总完成状态;只写选定站点;已配置且预先供应的 markdownlint/Vale/lychee 适配器规范发现项并声明语法/网络覆盖 | 未完成:初始审计时无 workspace/adapter 公共命令 | A07/A15/A18;直接/逐站一致;无同级发现、隐式安装或默认格式化写入;必需工具缺失为 2,可选遗漏可见,外网不确定性明确区分 |
| R7 | 只读 loopback Studio,提供概览、问题、翻译比较、页面关系和发布视图;筛选、已知来源、真实 Hugo 预览、比较与复制操作;CLI 一致;预构建资源;显式 allowlist、独立预览 origin、Host/Origin/session 保护 | 未完成:初始审计时无 Studio 服务或资源 | A16;真实浏览器/键盘/读屏/移动端/深浅色/长列表流程;使用相同 CLI 结果;拒绝未授权 host/origin/session 和预览到管理接口请求;消费者运行不需要 Node |
| R8 | Markdown/文本和 front matter 表单、选定组件与不覆盖附件复用计划;授权允许范围内写入须有可见 diff、哈希和候选验证;无修改字节及未知字段/注释/顺序/编码/空白保留;表单不支持的语法保留文本模式 | 未完成:编辑在只读 R7 验收后实施;初始审计时无编辑 API | A17/A14;无修改字节一致、YAML 字段定点更新与文本回退;外部编辑器过期保存、路径穿越/符号链接逃逸及预览请求安全失败;附件不覆盖;静态发布无管理 API |
证据分页
各阶段的验收记录保存在两个配套页面中。为了让每个页面都能被读者和代理工具完整读取, 它们于 2026-11-06 原样迁移至此:
- R1–R5 证据:R1 至 R3 的本地验证、 编写与升级验收(R4),以及实现与文档验收(R5)。
- R6–R8 与 A18 证据:工作区与适配器验收 (R6)、只读 Studio 候选(R7)、已接受的审阅编辑(R8),以及 2026-10-04 的运行时 完成补充(A18)。
验收用例台账
下表结合初始审计、已接受 R1–R7 证据与已验证 R8 候选门禁。
| 用例 | 所需结果 | 代码或检查证据 | 状态与缺少的决定性证据 |
|---|---|---|---|
| A01 | 单个 oink.result/v1 JSON;stderr 日志;政策为 1,必需未完成为 2 |
协议/公共 R1–R8 命令、冻结归属测试及精确二进制 CLI/API 报告;未改变结果 Schema;当前八项归属门禁/Linux 验收与未变运行时复用的此前语料见 #a18 | 受支持当前命令范围通过 |
| A02 | Hugo 解析 slug/url/permalinks/aliases、挂载、未列出页面和语言根 | 真实 PageFacts/manifest/自定义挂载/translationKey 夹具;普通产物保留;最终消费站事实 | R1 范围通过 |
| A03 | 确定本地缺失路由失败;真实分类 origin/path 外引用和声明外部范围 | 真实产物引用夹具、子路径/政策回归及最终真实站点 | 所需 A03 范围通过;外链可访问性保持未检查 |
| A04 | 文件名、目录和 translationKey;重复/缺失/草稿;严格/本地化政策 |
R2 翻译引擎、真实 Hugo/公共命令、最终报告和数值补充 | 所需 R2 范围通过 |
| A05 | 无记录为未知;源/译文哈希变化可见;不依赖 mtime | R2 哈希/status/diff、公共预览/应用和最终报告 | 所需 R2 范围通过 |
| A06 | 真实围栏、行内代码、短代码、HTML、属性、未知字段和受保护文本边界 | R2 真实语法/来源夹具、经审阅语料和最终报告 | 所需 R2 范围通过;目录及不支持源码限制仍明确 |
| A07 | 已确认问题可见;新问题按政策阻断;必需工具缺失不能通过 | R2 基线/公共计划;R6 假/实际协议、缺失/不安全/离线/网络不确定及必需优先级夹具通过 | 受支持范围通过;必需不可用/不确定工具保持 2 |
| A08 | 检查后字节变化使 manifest 无效;服务商上传已验证树而不再次构建 | R3 manifest/导出/篡改/公共单构建测试,最终普通 Hugo 对比及服务商演练 | 所需 R3 本地范围通过 |
| A09 | 两种 CI 模板;保留定制工作流;权限/变量/来源和过期计划保护 | R3 离线生成/bootstrap、公共预览/应用/过期输入、定制工作流补充及本地演练 | 所需 R3 本地范围通过;定制工作流未知且不变 |
| A10 | 拒绝 HTTP 200 回退、错误语言/构建、缺失资源/canonical 差异;超时/认证/限流为未完成 | R3 显式联网本地 HTTP 和公共结果夹具,含必需身份缺失 | 所需 R3 夹具范围通过 |
| A11 | 所有声明配置/语言;目标保护;普通 Hugo;保留未知编辑器设置 | R4 24 次普通 Hugo/公共配置、完整 Starter 创作/编辑器流程、片段、实际挂载、来源身份、JSONC 保护与外部 Schema 重新证明 | 所需受支持 R4 本地实现/语料范围通过;已声明不支持编辑器输入仍明确 |
| A12 | 可读 diff 和路由比较;脏文件/workspace/replacement/vendor;恢复/并发 | 冻结真实 Hugo 七个固定合成模块用例、公共升级、源码/外部保护、实际 alias 改指向与受保护部分回滚 | 所需有界 R4 本地实现/语料范围通过;未知重定向/多主机仍未完成 |
| A13 | 删除 B 找到未改入站 A;翻译/附件/派生产物;全局全量范围 | R5 已提交 Git/实际 Hugo 删除、alias 入站、全局/不确定输入和不可用基线夹具;精确二进制消费者报告 | 所需受支持 R5 范围通过;不可用或未证明历史输入明确为 2 |
| A14 | 应用前候选;过期/哈希/写入失败保留后续编辑;含糊引用不变 | R2/R4 共享保护、R5 完整模式/清单移动及 R8 重新生成意图/新输入候选验证、保护写入器及过期/后续编辑/附件测试;当前八项归属门禁/Linux 验收与未变运行时复用的此前语料见 #a18 | 受支持 R5 CLI 与 R8 CLI/Studio 编辑范围通过;含糊或必需不可用输入仍阻断 |
| A15 | 工作区/直接一致;只写选定站;上下文受限且有路径/版本/原因;不执行内容 | R5 有界捕获源码/context 夹具与四站查询;R6 登记/直接/汇总一致及显式名称保存应用、其他站保留 | 受支持 context/工作区范围通过;无隐式批量写入 |
| A16 | 五个实用 CLI 一致视图;键盘/移动端/深浅色;来源/预览隔离 | 已接受 R7 证据保留;历史源码绑定 R8 只读 14 项 axe/截图及 Editor 12 项 axe/截图,UI 字节未变;当前后端门禁、ARM 与语料独立验证、原生/API 一致、预览隔离、四消费者及规范渲染通过;当前八项归属门禁/Linux 验收与未变运行时复用的此前语料见 #a18 | 受支持本地视图通过;必需局部预览未完成/原生发现仍可见 |
| A17 | 无修改字节;YAML 未知/注释/顺序保留;拒绝过期保存和附件冲突 | 修正冻结核心/公共/保护写入器 race、实际 Hugo/工具、源码绑定 Editor/只读浏览器、精确二进制四消费者提议一致性/保护及受保护规范源码/渲染在 #r8 通过;当前八项归属门禁/Linux 验收与未变运行时复用的此前语料见 #a18 | 受支持本地编辑范围通过;必需局部预览/原生发现仍阻断 Apply;最终 A18 独立 |
| A18 | 实测声明 macOS/Linux 环境、子进程信号、已供应离线运行与明确不支持输入 | 当前冻结/源码与五归档重复复现;Darwin arm64、原生 Linux arm64、模拟 Linux amd64 非 root ext4/完整离线单元-vet/选定 Hugo/原生归档/信号 smoke 在 #a18 通过 | 当前声明运行时/归档范围通过;可选 guest 前置条件保持明确跳过;Darwin amd64 实验/未验证,Windows 不在范围 |
候选站点与源码保护
选定验收输入为内嵌 Starter 和三个不同的维护中下游站点,复用历史语料但不写入站点源码。 Starter 源 checkout 是来源输入;生成的配置试验使用临时目录。
| 同级 checkout 布局中的输入 | 初始观察身份与用途 | 本轮候选验收 |
|---|---|---|
oink-starter / 生成 Starter |
源码 137843b,初始状态两项;有许可证的固定归档,语言/配置/根路径/子路径试验 |
R1 双语 init/check 与 R4 全配置普通/公共创作流程通过;归档/许可证不变 |
oink.pgsty.com |
源码 907d873 和已有修改;双语文档/回归与显式本地主题试验 |
最终 R1 检查与源码保护通过;本地主题证据仍与公开固定版本分开 |
pig.pgsty.com |
源码 75050c0,初始状态五项;Docs/Blog 根路由重写、不渲染侧栏条目;声明 v1.1.0 |
最终 R1 检查与源码保护通过 |
repo.pgsty.com |
未产生提交的 main,无 HEAD revision;已物化的未跟踪源码、生成目录和声明的 v1.1.0 |
最终 R1 检查与源码保护通过;revision 仍未知 |
每次运行记录有效模块来源和版本、参数/网络政策、退出码/结果/覆盖、原始证据位置和保护结果。 前后清单必须包含所有 tracked 和未被忽略的 untracked 源码字节与模式、Git 状态/index、 workspace/replacement 文件及有效 vendor 输入。比较精确清单。 报告、隔离候选、产物和缓存放在站点源码外,并保持不入 Git。 全量构建耗时基于同一份当前输入比较。
归属检查与文档验收
先执行最小受影响 Go 包和公共行为测试。仓库现有门槛为 make test(离线测试和 vet)
及 make test-hugo(真实 Hugo Starter、快照、manifest 与公共命令夹具)。
归属 Hugo 门禁现运行全部归属包,不再使用旧的窄测试名称过滤器;新增夹具必须保持在门禁中。
并发计划/服务修改在归属测试需要时使用 race 检查。单元夹具保持离线,联网须显式调用。
文档保留中英文标题数量、顺序和稳定显式 ID。最小源码检查为:
添加本记录及中文对应文件后,两项源码检查均通过:八个中文研究文件通过风格检查; 翻译源码覆盖为 137/137 组,共检查 1,082 个源码标题。
构建相关站点后,完成产物文档验收:
make build 验证声明的公开固定版本;make check 选用同级主题执行完整非浏览器回归套件。
Studio 使用自身的真实浏览器和无障碍验收。
交付状态与剩余限制
| 状态 | 当前完成证据,历史保留于上文 |
|---|---|
| 本地实现 | 有限 R1–R8 受支持实现本地完成,包含只读 Studio 与显式受审阅编辑;当前 A18 运行时/归档通过。规范生命周期渲染独立绑定这些准确字节 |
| 本地验证 | 历史 R1–R8 归属/浏览器/语料/渲染记录保留;2026-10-04 当前后端修正和八项当前归属门禁、三个实际目标运行时/归档,以及运行时未变的此前四消费者保护/一致性证据复用在 #a18 通过。必需仓库发现/局部预览保持可见。渲染导航/URL 检查具有独立准确字节收据边界 |
| 提交 | 已识别 CLI 基线提交 |
| 归档与运行环境验收 | 当前修正源码:Darwin arm64、原生 Linux arm64 与 QEMU TCG 模拟 Linux amd64 的安装归档/离线/信号/文件系统流程通过,两个新构建复现全部五归档;Darwin amd64 实际执行失败,保持实验/未验证 |
| 公开分发与消费站采用 | 本轮本地验证未执行 |
| 部署与公网内容验证 | 已通过本地 HTTP 夹具验证 |
有限 R1–R8 实现与必需当前 A01–A18 运行时/归档范围已有本地验收证据。 已知仓库发现及必需预览未完成保持为可见诊断输出。
7.8.11 - 2026-10-03 CLI 维护验收:R1–R5
带日期记录中的 R1–R5 小节于 2026-11-06 原样迁移至此,使每个页面都能被完整读取。 范围、证据规则、验收案例台账与交付状态仍保留在主页面。
R1 本地验证
R1 现已提供共享 Hugo 页面/翻译/来源事实、产物引用与锚点证据、严格
oink.policy/v1 输入、check links、--format json、可见的经审阅排除/外部范围,
以及必需工作优先级。翻译和风格选择明确报告必需但不支持的覆盖。
默认 build/dev 继续直接调用 Hugo。
| 需求 | 已执行证据 | 当前结果 |
|---|---|---|
| 公共结果/政策与未完成优先级 | make test:所有包与 vet;公共严重度/排除/未实现分组/JSON 别名测试;TestEveryRequiredUncompletedCoverageFails |
R1 范围通过;任何必需未完成状态(含 not_checked)仍为 2 |
| 一次构建与共享事实 | TestPublicCheckSharesOneBuildAndRenderedFacts |
通过;一次严格 Hugo 构建提供页面和实际目标/锚点事实 |
| Hugo 权威与来源映射 | 真实 TestPageFacts* 夹具:translationKey、实际路由/别名、未知生成节点、自定义挂载、未发布页面分析及失败保护 |
通过;独立分析保留生产事实/产物字节及源码字节/模式 |
| 可复现真实 Hugo 验收 | 修正后的 make test-hugo 包含 TestPageFacts*、TestHugoRendered* 及 Starter/manifest 夹具 |
通过;覆盖范围内的路由/引用、经审阅外部范围和原始产物保留场景 |
| 新 Starter | 双语 init、check links,使用隔离且已供应的 v1.1.0 模块归档运行普通严格 Hugo |
退出码 0;223 个文件、4,461 个引用、66 个页面事实;依赖预备仍须显式进行 |
| R1 文档源码与 Schema | content/docs Markdown 风格;双语源码检查;CLI/文档结果 Schema JSON 解析及相同检查;限定 diff 空白检查 |
通过:88 个中文 docs、137/137 组源码、1,085 个标题;Schema 保持可增补的 oink.result/v1 和退出码 0/1/2 |
| 最终候选报告与中英文产物 | 刷新后的当前二进制下游站点报告;下文真实产物源码/Markdown/链接归属检查 | R1 范围通过;生产环境中已有草案发布页缺失单独记录 |
较早离线 R1 试验在三个下游站点上均返回 0 且无发现项:
| 较早试验 | 源文件 | 产物文件 | HTML 文件 | 引用 | 页面事实 | 字节/模式/Git 清单 |
|---|---|---|---|---|---|---|
| OINK 文档站 | 421 | 1,139 | 512 | 74,689 | 341 | 前后精确相同 |
| PIG 项目站 | 858 | 1,392 | 424 | 64,440 | 248 | 前后精确相同 |
| 软件仓库目录 | 2,294 | 3,287 | 1,635 | 851,535 | 1,572 | 前后精确相同 |
这些较早报告把可选未选中覆盖写为 not_selected,不属于已有结果 Schema 的枚举值。
最终代码已修正为 not_checked 并包含 project.pages 覆盖。
原始证据保留在站点源码外、带任务名称的本地验收目录。
最终 R1 二进制由基于 e623d93d589c49e5c58b8fae1bd5db720fc904cb 的 CLI
工作树重新构建。记录的输入清单包含文件哈希、模式和 Git 状态身份,
按排序后的 JSON 序列化计算 SHA-256 为
518260f07f3c916468ee3d56c4eeca03c131514155aa82539564ccd2f3c1f664。
实际运行二进制 SHA-256 为
3deb7e357fc86f6907df60da0769d93f2d41ba5e01949b641548a67d7f459d12。
| 最终当前二进制试验 | 源文件 | 产物文件 | HTML 文件 | 引用 | 页面事实 | 验收 |
|---|---|---|---|---|---|---|
| OINK 文档站 | 421 | 1,139 | 512 | 74,755 | 341 | 退出码 0、结果有效、页面事实完整、源码字节/模式/Git 精确保留 |
| PIG 项目站 | 858 | 1,392 | 424 | 64,440 | 248 | 退出码 0、结果有效、页面事实完整、源码字节/模式/Git 精确保留 |
| 软件仓库目录 | 2,294 | 3,287 | 1,635 | 851,535 | 1,572 | 退出码 0、结果有效、页面事实完整、源码字节/模式/Git 精确保留 |
最终结果均有必需的 check.links: complete 和 project.pages: complete。
未选中翻译/风格覆盖为可选的 not_checked。最终离线 make test 与 vet 通过,
修正后的真实 Hugo 归属目标也通过。记录的工具仍为 macOS arm64 上的 Go 1.27.1、
Hugo Extended 0.166.0、Git 2.54.0。编写本记录时独立比较了三个精确前后清单。
生产产物通过 Markdown 和链接检查。全站翻译检查返回 1,唯一原因是已有草稿
content/blog/release/1.2.0.md / .zh.md 没有进入生产产物。
R1 修改页面均已双语渲染。另一个显式分析构建将 HUGO_BUILDDRAFTS、
HUGO_BUILDFUTURE 和 HUGO_BUILDEXPIRED 设为 true,三项检查均通过:
137/137 组源码、1,085 个标题、产物 Markdown 与产物链接。
R2 本地验收
本地候选已实现翻译策略、状态、diff、哈希审阅、有界原生内容规则、可见的经审阅基线,
以及共享 oink.plan/v1 预览、验证与应用。默认 check 要求链接、翻译和风格。
生产输出与显式草稿、未来和过期分析相互独立。稳定行为与示例见
契约。R2 本地门禁已通过对应检查、最终冻结输入
站点报告及生成的双语文档。下文分别记录实测二进制和后续有界相等比较修复。
| 需求 | 已执行归属证据 | 结果与限制 |
|---|---|---|
| A04 翻译关系/策略 | 真实 TestHugoFilenameDirectoryAndTranslationKeyLayouts;范围、重复/缺失/禁用语言、草稿、严格/本地化和选定约束测试 |
归属测试通过;不要求全局标题、代码或本地化一致 |
| A05 显式审阅和 diff | 完整字节哈希/当前/源/译文/双方变化、不依赖 mtime、未知/不可读/含糊和错误记录;公开 status/diff/review 预览/应用 | 归属测试通过;审阅状态记录变更情况 |
| A06 源码边界和来源 | 真实 Hugo 规范 title/块属性开关和配置透传夹具;front matter/CRLF/BOM/短代码/代码/HTML;每个公开 v1.1.0 源码/许可证 SHA 验证 |
归属测试通过;不支持语法仍报未完成,自定义钩子不在目录检查范围内 |
| A07 基线范围 | 捕获/可见确认/新问题/未完成优先级及错误记录;公开基线预览/应用 | R2 基线范围通过;外部工具适配器验收归 R6 |
| A14 共享元数据计划 | 过期字节/模式/存在/保护条件;验证中编辑;排他提交碰撞;部分恢复;后续字节/模式/删除;旧打开 inode 写入;新目录子文件;范围/身份/diff | 归属测试与 vet 通过;拒绝候选/源码重叠;move/引用歧义仍归 R5 |
| 冻结运行门禁 | macOS arm64 make test/vet、归属真实 Hugo、聚焦 race |
通过;日志 /tmp/oink-r2-frozen-go-gate.log、/tmp/oink-r2-frozen-hugo-gate.log、/tmp/oink-r2-frozen-race-gate.log;最终全部归属包 Hugo 门禁 /tmp/oink-r2-owning-hugo-final.log 明确包含配置透传 |
| 双语文档 | 限定源码风格/配对/ID、相等结果 Schema、范围内空白及真实生产/分析 node 检查 | 范围门禁通过:88 份中文文档、137/137 源码配对、1,092 标题;生产草稿缺失在下文单独记录 |
冻结解析器语料位于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r2-source-corpus-lqx25kwr/summary.json,
解析器输入 SHA-256 为
a601200ec4fe275d2bd4baf11d4db7d46a2cc6f1674900c1fd801769e55d12de。
每项范围均完整解析且无问题项。核心范围使用已记录配置下 Hugo 实际站点源码身份;
补充 Markdown 包含禁用和未发布文件,不自动补齐路由或关系。这些捕获早于 R2 文档修改。
| 语料 | Hugo 实际源码去重文件数 | 补充本地 Markdown | 源码清单文件数 | 字节/模式/Git |
|---|---|---|---|---|
| Starter | 52 | 78 | 97 | 精确前后相等 |
| OINK 文档站 | 272 | 274 | 421 | 精确前后相等 |
| PIG | 212 | 212 | 858 | 精确前后相等 |
| 软件仓库目录 | 1,568 | 1,572 | 2,294 | 精确前后相等 |
初步公开命令报告位于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r2-final-qu_zprps/summary.json,
使用二进制 c8d87e6d73d3101fefcb62c5d6845518573c02c400f474dc9f4603afafc774d5,
CLI 输入清单为 d8a75be0e074365a4164b7aaaa27d82a1e844e04406a36c3dd6d39ff2b6e873f。
这些报告早于最终解析器与文档冻结。最初 Starter 命令误选了
外层证据目录并返回 2。改选其实际 site 子目录后返回
0,证据位于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r2-starter-27vmi0w5。
| 初步检查 | 退出码 | 页面事实 | 构建文件 | 引用 | 翻译状态 | 结果 |
|---|---|---|---|---|---|---|
| 正确 Starter 子目录 | 0 | 66 | 223 | 4,461 | 28 | 完成;97 个源码文件/清单不变 |
| 文档站 | 0 | 341 | 1,139 | 74,755 | 144 | 完成;421 个源码文件/清单不变 |
| PIG | 0 | 248 | 1,392 | 64,440 | 120 | 完成;858 个源码文件/清单不变 |
| 软件仓库目录 | 1 | 1,572 | 3,287 | 851,535 | 788 | 已完成策略检查:既有合并打印输出中有 10,462 项实际 HTML_ID_DUPLICATE;2,294 个源码文件/清单不变 |
软件仓库目录返回了完整的问题诊断结果。
期间未降级策略,也未修改下游站点源码,提示级审阅状态仍保持可见。
最终冻结输入报告位于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r2-candidate-6xzcs7fk/summary.json,
实测二进制 SHA-256 为
ff88b407a6cddb9007f94275c65a80ed4c9c4fd13f5e821f9b7a4a8973abaa56,
CLI 输入清单为 bd8c71b55250a89dc15c7534924bb82a5447d6f2628eba23c8cb3d864309ee9f。
独立比对确认四份源码字节/模式/Git 清单操作前后均精确相等,替代初步公开命令试验:
| 最终检查 | 退出码 | 源码文件 | 页面事实 | 构建文件 | 引用 | 翻译状态 |
|---|---|---|---|---|---|---|
| Starter | 0 | 97 | 66 | 223 | 4,461 | 28 |
| 文档站 | 0 | 421 | 341 | 1,139 | 74,825 | 144 |
| PIG | 0 | 858 | 248 | 1,392 | 64,440 | 120 |
| 软件仓库目录 | 1 | 2,294 | 1,572 | 3,287 | 851,535 | 788 |
最终报告均无未完成诊断。软件仓库目录保留 10,462 项实际合并打印
HTML_ID_DUPLICATE 和 788 项提示级审阅状态;其他站点保留提示级的未知审阅状态。
保留的生产文档位于
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-site-2201601475/public,
生成的 Markdown 和链接检查通过。全站翻译检查返回 1,仅因既有草稿 release 1.2.0
双语页面未进入生产。另一个草稿/未来/过期分析构建位于
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-site-3698773232/public,
三项 node 检查均通过:137 配对/1,092 标题、216 内容页面/41,586 文本节点,
347 页面/48,682 内链/4,171 片段。证据日志为
/tmp/oink-r2-docs-production-{translations,markdown,links}.log 和
/tmp/oink-r2-docs-analysis-{translations,markdown,links}.log。
最终审阅发现可选 equal_fields 仍按表示形式比较 JSON 7.0 和 YAML/TOML
数值 7。有界补充现将已解码数值递归规范为精确有理数标签,保留字符串与数值、
映射键及数组顺序的区别。测试覆盖小数/指数、负零、超出 float64 精度的整数、
嵌套差异、源码字节保护,以及不可表示值导致必需未完成。
真实 Hugo 翻译测试在 /tmp/oink-r2-numeric-translations-gate.log 中通过,
全部公开维护真实 Hugo 用例在 /tmp/oink-r2-numeric-public-gate.log 中通过,
归属 vet 和空白检查通过。补充源码 SHA-256 为:
| 源码 | SHA-256 |
|---|---|
internal/translations/check.go |
24664377e14b4ae2fc554d0d7fde2ec33cc987707250e130fd88d9a25d5e1637 |
internal/translations/translations_test.go |
f58f4a305fe9fe3f5500ddfcf85faf3cfa37d72f8c220a1cb16ce4ccfbddb74d |
冻结真实站点报告及 Linux 验收早于该补充。对应站点未配置数值相等约束, 已记录的输出不受影响,因此未为这项有界修复重跑。
该阶段结束时 A18 仍待完成。macOS arm64 已实测;本机尝试 Darwin amd64 运行时,
arch -x86_64 因缺少 Rosetta 返回 posix_spawn: Bad CPU type in executable
(/tmp/oink-r2-darwin-amd64-gate.log)。原生 Linux arm64 和 Docker
Desktop Rosetta 模拟的 Linux amd64 均实际执行,同一运行/Schema/许可证输入
SHA-256 为 0f786df68ef3c4844c983a51595f79242d1cb1d2bf6c5b5eb7f2c6415fb8d861。
证据保留在
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-a18-linux-ajbbnvki
的 arm64-results、amd64-results、commands.json、candidate-inputs.json、
preparation.json 和 qualify.sh。每个目标均通过 270 个测试/子测试,无失败,
仅跳过两项可选外部语料/来源验证:完整真实 Hugo go test ./...、vet、构建 CLI
版本/双语 init/doctor/完整 check/翻译 status,以及缺失 Hugo 退出 2 冒烟测试。
JSON stdout 和源码字节/模式清单均核对。Go 1.27.1 运行在 Linux arm64,
Hugo Extended 0.166.0 各架构资源已核对 SHA。
R3 本地验证
R3 新增受管理 build --check、oink.artifact/v1 封存/导出/本地验证、
显式联网 HTTP 验证、发布诊断和受保护的本地 CI 生成。默认 build/dev 保持普通 Hugo。
已执行运行和中英文契约/指南门禁通过,R3 已本地接受。
| 需求 | 已执行归属证据 | 结果与限制 |
|---|---|---|
| A08 单份检查产物 | 公共模拟/真实 Hugo 单渲染器测试;精确导出、manifest/标记、检查后字节/模式/缺失/新增/符号链接篡改、失败/并发及源码保护测试 | 本地范围通过;失败/未完成检查不能封存或导出;本地产物验证不重建 |
| A09 两种 CI 服务商 | 离线确定性生成、固定源码/Hugo 归档和 action revision;安全 bootstrap 归档;受保护公共预览/应用/过期输入;真实 Hugo 原始输入绑定 | 本地配置范围通过;拒绝所有已存在生成目标,定制工作流不变 |
| A09 上传身份 | 两种本地服务商演练及 TestProviderUploadRehearsalPreservesActualSealedManifestIdentity |
通过:一次受管理构建、单独验证、再使用同一树;GitHub tar 包含隐藏标记,Cloudflare 演练接收已验证目录 |
| A09 定制工作流诊断 | 已生成加其他定制、无元数据定制公共测试,真实 Hugo 预览与归属 vet | 补充通过,日志为 /tmp/oink-r3-ci-custom-owning-gate.log 和 /tmp/oink-r3-ci-custom-vet-gate.log;未被元数据表示的工作流保持 unknown、信息级及可选 release.ci: not_checked,有效生成元数据旁也可见 |
| A10 部署身份 | 本地 HTTP 全部记录文件/路由/语言、标记、canonical/base/惰性 template、HTTP 200 回退、错误字节/语言/构建、缺失资源/Markdown/搜索 JSON 夹具 | 本地夹具范围通过;确定差异为 1,浏览器 JavaScript 未检查 |
| A10 网络未知状态 | 显式联网/凭据拒绝、响应头前/响应体中超时、认证/限流/服务错误、必需标记缺失、有界响应/gzip、重定向/无 cookie 夹具 | 本地夹具范围通过;未完成为 2,剩余请求为未知 |
| 冻结运行门禁 | 完整测试/vet、归属真实 Hugo 和聚焦 race | macOS arm64 通过,日志为 /tmp/oink-r3-frozen-go-gate.log、/tmp/oink-r3-frozen-hugo-gate.log、/tmp/oink-r3-frozen-race-gate.log;后续定制 CI 修改由上述聚焦补充覆盖 |
最新单二进制语料位于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r3-ci-final-ahc4csjk/summary.json。
从精确捕获的 CLI 输入副本编译,二进制 SHA-256 为
425845c1d2db7b1cd3c3cdb5f28475cb06ba6f656054909759e2359a39925dd2,
67 个运行/Schema/许可证输入 SHA-256 为
6789a3a0235ff8d81453b9bfde37824979eac7d56af3710da390e4d2ef8479dc,
108 个更广 CLI 输入 SHA-256 为
4ca473a4cb586d232baeb4cee029b281469c5bb03c831cc199b95451e6832c60。
全部运行后运行输入仍精确相等。实时工具为 Darwin arm64 上 Go 1.27.1 和
Hugo Extended 0.166.0;manifest 的规范 Hugo 版本排除发行方构建文本与私有路径。
每次离线 build --check 使用站点外的新导出/manifest 路径、可选标记和保留隔离
目录。已有本地站点未使用 --release,保留其配置的 workspace。每份原始报告
均恰好一次严格 Hugo 渲染、零未完成诊断、零必需未完成覆盖。
| 最终受管理构建 | 退出码 | 源码文件 | 复制源码输入 | 页面事实 | 构建文件 | 引用 | 导出文件 | 本地产物验证 |
|---|---|---|---|---|---|---|---|---|
| Starter | 0 | 97 | 94 | 66 | 223 | 4,461 | 224 | 0 |
| 文档站 | 0 | 421 | 427 | 341 | 1,139 | 74,825 | 1,140 | 0 |
| PIG | 0 | 858 | 861 | 248 | 1,392 | 64,440 | 1,393 | 0 |
| 软件仓库目录 | 1 | 2,294 | 2,299 | 1,572 | 3,287 | 851,535 | 无 | 未导出 |
两份清单均精确比较操作前后字节、模式与文件类型,主清单还比较逻辑 Git 状态。 Git 站点包括 tracked 和未被忽略的 untracked 源码;无 Git Starter 包括既有生成 文件与锁。补充复制源码清单还包括快照读取的被忽略 workspace/编辑器元数据, 排除已有生成输出/缓存树。四份对比均精确相等。
软件仓库目录保留 10,462 项既有合并打印 HTML_ID_DUPLICATE,未创建导出或 manifest。
生产审阅状态为 28/143/120/786;
分析包括未发布页,对应此前 R2 的 144/788。既有定制工作流信息仍可见,
Starter 示例地址在此次非发布运行为警告。
三份新导出在全部原始文件 SHA-256、大小与模式上匹配保留的独立普通 Hugo 产物。
唯一新增文件为 .well-known/oink-build.json。原始源码清单与完整 manifest 输入哈希
均匹配此前捕获,复用普通产物未替换输入。辅助源码 SHA-256 为
13e4957a3d7847eb28c8b1eeba3588a4f4a9982c2bfca2ebc729ab2827159607,
二进制 SHA-256 为
b2699fe7a7aa3a34c41f9e4aba4b22d39cf8d0c156a369f3dfc4ca8c8c0fbce5。
原始辅助与普通产物证据位于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r3-candidate-wb643dhh;
编译辅助程序后删除临时构建源码。
此前 R3 捕获保留为历史:首次捕获早于运行冻结,首份冻结捕获
oink-r3-final-pisrr21h 早于定制 CI 诊断。最初选择 /Users/vonng/pgsty/PIG
返回 2;改用 pig.pgsty.com 后通过。
CI 模板/bootstrap 根据已记录服务商第一方契约独立编写。
限定 R3 文档门禁通过,证据为
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r3-docs-render-pljj5aqd/summary.json。
十份成对契约/指南/路线图/索引/概览修改在匹配原始字节/模式后安装,哈希位于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r3-doc-drafts-s0b3g48l/applied-files.json。
源码风格通过 88 个中文文档,翻译通过 137 配对/1,099 标题,Schema 保持相等且限定
空白检查通过。真实生产 Markdown 通过 214 页面/41,871 文本节点,链接通过
345 页面/48,344 内链/4,171 片段。生产翻译仅因未修改的草稿 release 1.2.0
未进入生产返回 1。另一个明确不可发布的草稿/未来/过期分析通过 Hugo 及三项
归属检查:137 配对/1,099 标题、216 页面/42,177 文本节点、347 页面/48,720 内链/
4,199 片段。产物检查期间 421 个规范源码和 488 个复制源码文件均保持精确字节/模式。
R4 创作与升级验收
受支持 R4 实现与只读语料范围在冻结归属/全量门禁后已本地接受。本记录覆盖配置、 普通创作/编辑器/片段和有界升级视图。受保护规范文档推广与新产物限定验证也已通过, 详见下文。
| 已执行配置证据 | 结果与限制 |
|---|---|
| 同一固定许可证归档 | 针对提交 137843b25bacd76ddd1f7ce71330bf2e3155b954 的快照核对未使用 --write 且通过;归档 SHA e55bde279715f6d8d19d3d88671a2cf7561b515be46915b0f12c640d0ce1d958 和 MIT 许可证不变,投影元数据/脚本匹配 |
| 组合与保护 | 默认/显式 project 字节一致,选定归档模型/本地化首页、无效配置、非空目标、并发验证/发布和取消恢复测试通过;单元/vet/race 门禁通过 |
| 普通 Hugo | 四种配置 × 三种语言 × 根/子路径,共 24 次真实严格离线构建通过,使用预备的公开 OINK v1.1.0;完整源码字节/模式/无额外文件及产物引用检查通过 |
| 公共 init 流程 | 四种配置 en/en,zh、后续根/子路径实际 Hugo URL 事实/检查、工作流/许可证保护与默认一致通过;未知/非空拒绝 1,Hugo 缺失/失败 2,空/不存在目标及纯 JSON/独立日志已验证 |
| 公共创作与来源身份 | 实际候选/应用/普通 Hugo、未知审阅与源码保护通过,见 /tmp/oink-r4-authoring-public-gate.log;新目录/站点保护与 vet 通过,见 /tmp/oink-r4-new-input-race.log、/tmp/oink-r4-public-core-vet.log。实际被忽略输入即使源码检查组关闭也拒绝 2,不保存计划、不写源码;选定译文草稿仍强制分析来源身份,见 /tmp/oink-r4-authoring-sourceproof-gate.log。受支持归属范围通过 |
| 有界升级归属门禁 | 七个真实 Hugo 固定合成模块用例、观察流摘要/产物清单一致、独立跨页面 alias 改指向阻断,以及源码/并发/排他写入和保留后续编辑的回滚保护通过 race;vet 通过。最终加固日志 /tmp/oink-r4-hardening-owning-gate.log、/tmp/oink-r4-hardening-final-focused.log、/tmp/oink-r4-hardening-vet.log;最终公共/全量冻结门禁通过 |
| 创作/编辑器集成加固 | 实际 Hugo 语言目录计划/应用/普通构建、link/never 新来源拒绝、外部 Schema/许可证/完整模式/模块身份及旧 Schema 重新证明、共享翻译/基线/CI 回归,以及完整 Starter docs→新译文草稿→编辑器→检查→普通 Hugo 流程通过。/tmp/oink-r4-app-authoring-hardening-gate.log(58.241s),聚焦 race/vet 通过;候选后外部修改证明 /tmp/oink-r4-app-external-during-validation.log 通过。不透明保存输入哈希与规范 workspace 来源保护见 /tmp/oink-r4-external-plan-binding-final.log、/tmp/oink-r4-workspace-origin-gate.log 及其 vet 日志。冻结全阶段、语料和限定规范产物文档门禁通过 |
| 实际语言挂载 | 独立的逐语言 contentDir 和显式站点矩阵夹具均通过 config/mounts/严格构建,源码字节/模式不变。Hugo0.166 输出 sites.matrix.languages,不同物理文件具有互为译文的公共关系。/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r4-language-mounts-lgmve1sk/summary.json;公共实际语言目录计划/应用/普通 Hugo 集成通过 |
归属证据保留于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r4-starter-owning-0pv41lw5/summary.json。
日志为 /tmp/oink-r4-starter-{unit,hugo,vet,snapshot,race}-gate.log、
/tmp/oink-r4-public-init-gate.log 与 /tmp/oink-r4-public-init-vet-gate.log。
生成源码数量为 project 94、docs 58、blog 40、book 34。
最终冻结门禁均为 0:make test/vet 见 /tmp/oink-r4-frozen-go-gate.log,
make test-hugo 见 /tmp/oink-r4-frozen-hugo-gate.log,真实 Hugo 核心 race 见
/tmp/oink-r4-frozen-core-race-gate.log。最终公共流程覆盖 Starter docs → 主页面/
译文草稿 → 编辑器 → 检查 → 普通 Hugo;候选验证后的外部 Schema 修改仍在写源码前
拒绝。17 份归属与三份最终门禁日志及哈希原样保留在最终语料 owning-gates.json。
准确四站证据为
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r4-corpus-lw2cjyq6/summary.json,
另有有界 summary.compact.json、原始 JSON/日志和逐命令清单。二进制 SHA 为
c169b3d4d046c811dca80867068b86fb66ada5c8ce6910dd5cda7353c406f377;82 份运行时输入
SHA 为 fdff7f50d49b44f03fa1db79eec6b6c9b9bd5e52f8967a88aed84b3207a7b3c6,
与最后根清单完全相等,无运行时修改。139 份完整 CLI 输入 SHA 为
562e838d9eccb628eac86ae59b9b9587c1e23ad52991ec50eafb1e604e3924da。
驱动 SHA 为 d3ac41dc2e18295bfb26134d1a696935c8174913e2801a5766dbf7a1139d89f8。
实际工具为 macOS arm64 上的 Go 1.27.1 与 Hugo 0.166.0 Extended。
| 冻结消费站 | 主/复制源码文件 | 页面;输出文件;引用 | 受管理构建 / 产物验证 | 只读 upgrade / new / editor |
|---|---|---|---|---|
| Starter | 97 / 94 | 66;223;4,461 | 0 / 0;含 marker 导出 224 份 |
0 / 0 / 0 |
| 文档站 | 421 / 427 | 341;1,139;74,937 | 0 / 0;含 marker 导出 1,140 份 |
0 / 0 / 0 |
| PIG | 858 / 861 | 248;1,392;64,440 | 0 / 0;含 marker 导出 1,393 份 |
0 / 0 / 0 |
| Repository | 2,294 / 2,299 | 1,572;3,287;851,535 | 已完成发现 1;无导出/manifest |
已完成发现 1;阻断后未尝试 new/editor |
每次受管理构建恰好使用一次严格生产 Hugo 渲染,没有必需未完成或未完成的必需覆盖。
Git 可见主源码字节/完整模式/逻辑 Git 状态、补充复制输入以及源码目录模式在每条命令
和完整站点流程前后均精确相等。Repo 既有 merged_print 中 10,462 处重复 HTML ID
保持可见。
下游站点升级预览选择已有公开 v1.1.0 pin,不应用写入;跨版本路由/alias/输出回归采用 合成夹具 pin。New/editor 计划为已验证预览,未在下游站点 保存或应用计划。多主机及未知相对 alias 身份保持未完成。非确定性产物可能需要重新 预览 v2 计划。
应用十份受保护文件后,新的规范文档验收通过。证据为
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r4-docs-render-v01eima0/summary.json;
推广清单为
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r4-doc-drafts-3i8bw994/applied-files.json。
冻结 c169b3… CLI 执行一次严格生产构建,定点链接检查返回 0。
| 新规范文档门禁 | 已执行结果 |
|---|---|
| 源码归属检查 | 翻译 0:137/137 对、1,104 标题;完整规范样式 0:137 份中文文件、181 处加粗、无强调;十文件空白检查与公共 JSON Schema 一致检查通过 |
| 生产 Markdown/链接产物 | 均为 0:214 个内容页面 / 42,214 个文本节点;345 个页面 / 48,360 个内部链接 / 4,187 个片段 |
| 生产翻译 | 1 仅因既有草稿 content/blog/release/1.2.0.md 不在生产产物中;无新增配对/标题问题 |
| 单独不可发布分析 | 新普通 Hugo 使用实际原快照环境/重定位路径及显式草稿、未来、过期选项,返回 0;三个归属检查均 0:Markdown 216 页 / 42,520 节点,链接 347 页 / 48,736 链接 / 4,215 片段,翻译 137/137 对 / 1,104 标题 |
| 源码保护 | 规范 Git 清单 421 份文件和复制输入 427 份的字节、模式、Git 状态与目录模式完全不变;生产复制 428 份、分析复制 427 份文件在检查中不变;分析构建也保留复制文件完整模式 |
临时辅助程序复制冻结核心:辅助源码 SHA
7faea7e726a6c6fb2e0747be1a4428f4c5fb5734fa52b6f981157a5fe37d9989,
辅助二进制 SHA
532638e76f96f8b173c122e512b3bf5fc2c4d4a7130f59c99c2c69e135e87073,
与原始日志一并保留。
R5 实现与文档验收
R5 受支持范围在聚焦公开命令/核心、修正冻结全阶段、精确二进制只读消费者及 受保护规范源码/渲染文档门禁后已本地接受。
实际公开 Git/Hugo 流程在 /tmp/oink-r5-public-final-flow.log 以 53.963 秒通过。
已提交的合成站点拥有本地主题、双语页面及二进制附件;普通 0640、0600 保持为
完整当前事实,历史 Git 比较仅使用可执行位。删除乙后纳入未改入站甲、剩余翻译、
删除的附件与实际 RSS 输出。实际 alias 入站归属不确定性、全局配置/模板/数据及
未知输入变更扩大为全范围。
完成的 inspect/impact/context 返回 0,单独展示当前检查发现 1;check-since
保留当前质量 1 与完整验证范围。缺失、未提交或外部历史返回 2,保留全部已知
当前页面、附件、引用、输出。已测试无效选择器/限额、缺失工具
及失败渲染器日志。有界上下文提供理由、版本、源码及摘要哈希、可见遗漏/截断。
保存移动预览/应用及随后普通 Hugo 已通过,保留二进制字节、原始完整模式、无关
文件与 Git index/revision。实际不透明 HTML/shortcode 引用保持人工动作;inline、
fence 与不透明片段保持不变。它们的最终断链候选返回 1,无保存计划或源文件
写入。源码、配置、附件、模式或新目标漂移返回 2,保留后续编辑。实际候选渲染器
之后的确定性变更同样在写前拒绝,保留编辑者字节/模式。聚焦实际移动 race 在
/tmp/oink-r5-public-move-race.log 以 8.286 秒通过;app vet 在
/tmp/oink-r5-public-vet.log 通过。
缓存模块补充之前,冻结父级 make test/vet 与 make test-hugo 分别在
/tmp/oink-r5-frozen-go-gate.log、/tmp/oink-r5-frozen-hugo-gate.log 通过
(实际 app 夹具 185.709 秒)。实际移动/源码 race 与 vet 在
/tmp/oink-r5-move-hugo-gate.log、/tmp/oink-r5-source-move-race-gate.log
及其 vet 日志通过;完整清单/模式/选择器计划保护在
/tmp/oink-r5-plan-owning-final.log 通过。
首轮冻结消费者试验发现实际缓存公开模块保护缺口:原始关系图含已解析模块输入,
新的外层候选哈希却未纳入它们,产生错误未完成 2,没有源码写入。内容计划现先
解析/捕获同一模块输入再比较,并保留旧元数据/创作计划范围。独立的校验和验证公开
OINK v1.1.0 回归在 /tmp/oink-r5-public-cached-module-move.log 以 27.42 秒
(package 28.220)通过预览、重新验证已保存应用与普通双语 Hugo,保留原始模式、
二进制字节、无关输入与 Git。
修正当前候选的完整 make test/vet 在
/tmp/oink-r5-corrected-frozen-go-gate.log 通过;实际 make test-hugo 在
/tmp/oink-r5-corrected-frozen-hugo-gate.log 通过(app 278.787 秒)。缓存公开/
已提交站点内部移动保护 race 在 /tmp/oink-r5-public-cached-seam-race.log 以
38.578 秒通过,app vet 也通过。
/tmp/oink-r5-corrected-runtime-freeze.json 记录 96 个运行输入,SHA-256 为
e5b6e0eda972116dbb94a8086668e6ef34bfaa56138cf31f1f71f4832c477842;
165 个较广 CLI 输入的 SHA-256 为
4965a0c92cb6126f67a6dabd548c7c25ee5d7c9c57e14cce5e55ebb7a22fca2d。
修正二进制 SHA-256 为
d7675aecca2f77b1eb37bb4f664c3314cf5207149e6abbb86523686c5c50bff0。
修正四消费者捕获
在 831.825 秒内完成 16 个命令,证据位于
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r5-corpus-corrected-y2eue81h。
有界 final-receipt.json 的 SHA-256 为
b86e0e6c7dbfbaed62c845c03a55d963068f7d9a16771de5ff3b0974e76fce3b;
记录保留 12 份归属门禁日志、全部 20 条实际移动路由,以及完整原始 JSON/日志和
各移动分类文件的位置。
| 站点 | 主源码/复制输入/目录 | Inspect/context | 当前检查 | Impact | 移动预览 |
|---|---|---|---|---|---|
| Starter | 97/94/21 | 0/0 |
0 |
2:无 Git 基线 |
0:已验证,未应用 |
| 文档站 | 421/427/109 | 0/0 |
0 |
0:完整历史比较 |
1:六条候选缺失引用 |
| PIG | 858/861/52 | 0/0 |
0 |
2:历史外部输入来源未完成 |
1:32 条候选缺失引用 |
| 仓库站 | 2294/2299/48 | 0/0 |
1:已有 10,462 个重复 HTML ID |
2:未提交 HEAD 基线不可用 |
1:同一批已有重复 ID |
Starter 与仓库站 impact 保留已知当前事实。PIG 实际基线
完整(主题 v1.0.0 对当前 v1.1.0),但必需外部输入来源未完成,因此比较扩大为
全范围并返回 2。文档站 impact 完成,包含 192 个捕获输入
变更、343 个受影响旧/当前页面并采用全范围。已完成事实查询独立展示当前质量发现;
仓库站 inspect/context 仍为 0。
文档站移动证明 18 处重写及四条路由。content/docs/customize/repository.md
第 216、313 行两个普通字面 /docs/admin/comments/ 目标保持人工动作,因为
普通/打印输出的重复源码/输出出现位置无法精确归属。六条候选缺失引用阻止验证。
PIG 移动两个 Markdown 文件及四个二进制附件,证明八条页面/处理后资源路由。
配对且字节相同的输出证明处理后 featured_hu_* 资源,但未证明四个原始绝对图片
引用 /article/pgext-day/{featured,topic,venue,schedule}.webp 的新 URL。
32 条候选缺失引用阻止验证。
仓库站移动证明十处重写及四条路由;候选只有同一批已有 10,462 个重复 ID 发现,
没有新缺失引用或必需未完成发现。Starter 无入链的双语移动已验证。四次移动均未
应用,未保存计划或写入站点源码。失败候选为 validated: false。
全部 JSON stdout 纯净;主源码、复制输入、完整模式、目录清单及逻辑 Git/index
状态保持不变,包含忽略的复制输入。Git 元数据清单不包含不可变对象存储。
运行与较广 CLI 清单仍匹配捕获身份。
首次十文件受保护规范晋升使用上述修正冻结二进制/运行哈希,在 62.37 秒内
完成验证。独立渲染记录为
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r5-docs-render-ks2tw82c/summary.json,
SHA-256 为 06ae844a4b3f1c01bb5faa8a21091d5461c28aab592d12c4310fb34bc176c5d4。
| 规范文档门禁 | 已执行结果 |
|---|---|
| 源码归属检查 | 翻译 0:137/137 对、1,109 标题;样式 0:137 份中文文件、181 处加粗、无强调;限定空白与公共 JSON Schema 一致检查通过 |
| 冻结 CLI | 使用精确修正二进制的生产 check links 返回 0 |
| 新普通生产 Hugo | 构建 0;Markdown 0:214 页面/42,571 节点;链接 0:345 页面/48,376 链接/4,203 片段 |
| 生产翻译归属检查 | 1 仅为普通生产输出中已有 draft release-1.2 缺失;无新 R5 差异 |
| 独立普通分析 Hugo | 新的不可发布 -DFE 构建 0,不使用 CLI probe;Markdown 0:216 页面/42,877 节点;链接 0:347 页面/48,752 链接/4,231 片段;翻译 0:137 对/1,109 标题 |
| 输入保护 | 全部逐命令及总体保护条件通过:421 主源码、427 复制输入、109 目录、36 可变 Git 文件保留字节/完整模式/逻辑 Git 状态;两个隔离源码副本均不变 |
首次晋升的永久 applied-files.json 位于
/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r5-doc-drafts-t3_klnck,
保留十个授权文件及其原始模式。
核心归属日志 /tmp/oink-r5-frozen-core-hugo.log、
/tmp/oink-r5-owning-race.log、/tmp/oink-r5-owning-vet.log 已通过。A13 影响
与 A14 移动保护所需受支持 CLI 范围通过;A15 有界上下文通过。
7.8.12 - 2026-10-03 CLI 维护验收:R6–R8 与 A18
带日期记录中的 R6–R8 与 A18 小节于 2026-11-06 原样迁移至此,使每个页面都能被完整读取。 范围、证据规则、验收案例台账与交付状态仍保留在主页面。
R6 工作区与适配器验收证据
R6 受支持范围在冻结归属/运行时、精确二进制消费者一致性/保护及受保护规范 源码/渲染门禁后已本地接受。受支持登记/工具字段归属 契约与 命令契约。 A07 适配器与 A15 工作区/直接/context 受支持范围通过下列门禁。
登记独立版本为 oink.workspace/v1:严格单文档普通 YAML、1–64 个条目、最多
256 KiB、准确 ASCII 名称、字面相对/绝对目录、已证明的规范身份,以及重叠拒绝。
缺失站点保持逐站未完成,后续选定站点继续运行。选择保留登记顺序;不提供默认登记
站点、同级发现、Hugo 设置复制或自动多站应用。可选工具扩展 oink.policy/v1,
固定协议版本,提供配置/完整模式来源,以及类型化遗漏/覆盖。
工作区归属收据
| 聚焦门禁 | 已执行本地证据 |
|---|---|
| 登记核心 | 严格字段/文档/大小/名称/字面路径、现存别名/大小写 inode 祖先、重复/重叠拒绝、缺失目录列出与准确子集顺序;go test -race ./internal/workspace -count=1 通过,1.414 秒,/tmp/oink-r6-workspace-core-race.log |
| 公共实际 Hugo | OINK_TEST_HUGO=1 go test ./internal/app -run '^TestPublicR6Workspace' -count=1 -v 通过,10.498 秒,/tmp/oink-r6-workspace-public-hugo.log |
| 公共 race | 相同公共工作区套件加 -race 通过,12.426 秒,/tmp/oink-r6-workspace-public-race.log;排除的命令明确拒绝登记选择 |
| Vet | go vet ./internal/workspace ./internal/app 退出 0,/tmp/oink-r6-workspace-vet.log |
| 公共结果 | 实际双语已提交夹具站点在 links 与完整检查中保留直接诊断/覆盖/退出一致性。首个缺失站点为 2,后续干净/有问题站点分别为 0/1;显式子集保留登记顺序,错误的未登记同级站点保持原样,人类输出保留发现项 |
| 选定应用 | 保存翻译审阅预览已验证、未应用;改选其他登记名称在写入前拒绝,保留计划/源码字节/完整模式/Git。显式匹配名称应用只写计划中的审阅文件;其他登记及未登记站点保持原样 |
已检查核心
workspace.go 的 SHA-256 为
cf2cbc9509e8c83eedf6d8833c9eb0ea6492de9a85c959798112fa3f105213f4;
归属测试为
9070a8e2c3e58680f6567f2394160ec682bf0457c068c2addf354921e7612d6b;
公共测试为
3e57a6417ae2e7604f7cb06933759bb06a2f40758ff7059848593cedbaa6570a。
这三份已检查文件均保留 0600 模式。
修正协议与阶段门禁
| 协议或门禁 | 记录状态 |
|---|---|
实际 markdownlint-cli 0.49.1 与 Vale 3.24.0 |
修正公共试验通过:恰好一个发现项映射到原始 UTF-8/BOM/CRLF 行;排除的 front matter/短代码/数学公式/原始 HTML/已启用属性/代码不产生错误原文归因 |
实际 lychee 0.24.2 |
修正试验实际到达本地 HTTP 夹具:200 → 0,404 → 1,401/403/429/503/超时 → 必需 2。可选离线 → 0,必需离线 → 2,两者均零 HTTP 请求 |
| 最终聚焦实际工具收据 | /tmp/oink-r6-public-actual-tools-final.log 通过,15.192 秒;先前修正后的 14.686 秒运行保留为已有证据。Node 预加载与发现的 JS 配置没有执行;源码完整模式/Git 保留 |
| 假工具/协议失败收据 | /tmp/oink-r6-public-fake-tools-final.log 通过,13.089 秒:错误输出、版本不符、超时、不安全配置、必需缺失/可选/检查组遗漏与原始 stderr 规范化 |
| 聚焦公共 race/vet | /tmp/oink-r6-public-tools-race.log 假工具和实际用例通过,30.273 秒;/tmp/oink-r6-public-tools-vet.log 退出 0 |
| 冻结运行时输入 | 在 2026-10-03T10:58:01.807947Z 冻结,/tmp/oink-r6-runtime-freeze.json:103 运行时输入绑定 b85affd96378b45bfc56a996b0c5672d02ee4c6cc9bc95335fa5072f6c42a03b;179 更广 CLI 输入绑定 fbb8176ebc58f1aa26336f4e6036cf9bd5f7a0d62b142f16532b50a8071e9fbe。实际运行的 0.3.0-r6-local 二进制 SHA-256 为 aa8b347fbe01071f9da729f4d98aa2f50d7264456be6c5f05771bcfadadc371f |
| 冻结归属套件 | 完整 Go/vet 退出 0,/tmp/oink-r6-frozen-go-gate.log;完整实际 Hugo 与固定工具退出 0,/tmp/oink-r6-frozen-hugo-gate.log(app 382.832 秒)。工作区/核心/协议/源码遮蔽/政策/报告 race 与 vet 收据通过,副本保留在最终收据中 |
| 四个消费者站点 | 已验证:全部四站精确二进制直接/汇总诊断、覆盖、退出、身份及登记顺序一致;逐命令/整体源码字节/完整模式/类型/逻辑与可变 Git/被忽略输入/目录保护通过。汇总完成 4,有问题 1,未完成 0,退出 1 |
| 规范中英文 | 通过:受保护首次十文件晋升、源码归属检查与新鲜普通生产/不可发布渲染证据;仅保留已知生产 draft 发布文档缺失 |
| 阶段决策 | 必需收据齐备后 R6/A07/A15 受支持范围已本地接受 |
永久聚焦工具收据位于
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r6-tools-6bf67ltv/r6-public-tools-acceptance.json,
SHA-256 为 16b6e47fc0618c76d2f9e3680a4112b6e47b478af8aabd3f2fc84821f840cc8a。
它绑定工具准备记录、可执行文件/配置证据与 1,422 个已解析 Node 包文件。Markdownlint
报告原始 content/tools.md 第 7 行,bytes[80:92](ppears here.);Vale
报告同一行,bytes[71:78](BADTERM)。七个网络用例每个均实际发出一个 HTTP
请求。
精确二进制消费者收据位于
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r6-corpus-59_asiyr/final-receipt.json,
42,212 字节,SHA-256 为
0ad86afaf235bdcff0c474e76b08e0591591a7b22c7992b02e20fb17975d029e;
完成摘要绑定
467b66eb6d178829508115050d4243909313acf1b4d59317ecda37ab7383ca55。
它保留 14 份归属/完成门禁日志副本。六个原始操作合计 314.912887 秒,不计候选编译
和仅收据修正。workspace list 返回 0;四个直接完整检查返回 0/0/0/1;
汇总返回 1,全部四站完成。
| 站点 | 保留源码文件 | 直接/汇总子结果退出 | 诊断/覆盖 | 已记录发现项边界 |
|---|---|---|---|---|
| Starter | 97 | 0/0 |
28/29 | 仅翻译审阅信息 |
| 文档站 | 421 | 0/0 |
144/34 | 仅翻译审阅信息 |
| PIG | 858 | 0/0 |
120/41 | 仅翻译审阅信息 |
| Repository | 2,294 | 1/1 |
11,250/29 | 已有 10,462 重复 ID 发现及 788 翻译审阅信息项 |
直接和汇总子结果的身份、顺序、每项诊断与覆盖记录均一致。全部四消费者源码在每次 操作后及整体保留完整模式/类型、逻辑与可变 Git 元数据、被忽略复制输入和目录清单; 完整根 CLI 清单也仍等于冻结捕获。478,603,149 字节 repository 直接 JSON 与 635,470,795 字节汇总 JSON 通过流式完整验证,没有截断。可选工具协议归属独立 固定工具夹具;消费者登记只存在任务临时目录,不写站点政策。
初始验收驱动将摘要结果的 command 字符串覆盖为调用 argv,六个 CLI 操作及其
逐操作保护全部完成后,产生错误的一致性异常。失败驱动与摘要仍保留为
pre-correction.r6_qualify.py 与 pre-correction.summary.json。收据完成只修正
调用元数据,验证原始结果 SHA-256 和头部命令不变,保留全部原始完整流诊断/覆盖
摘要,并重查整体消费者/根目录保护。无需 CLI 运行时修正或 Hugo/CLI 重跑。仅收据
完成耗时 2.002 秒,/tmp/oink-r6-corpus-receipt-completion.log 退出 0。
实际执行驱动 SHA-256 为
4d2a360c6f7f6f96c38698bd189bc4d4b2cb02a7509858920d752897fdd85988;
修正后驱动为
4870f5c0374fcc11ad1a6b2e3aefe36f493b6f9f4666c293993dc6a59df8a11b;
收据完成驱动为
cd50d3fe704370f73fa4e7d94ce8e4bc925d11ec8ef04d463aacec37c2053daf。
流式辅助程序绑定
144f778cdb7907372797b47b97f817f340e70423701a2a958dee589281a9a11c,
清单辅助程序绑定
d3ac41dc2e18295bfb26134d1a696935c8174913e2801a5766dbf7a1139d89f8。
此收据验证本地 darwin/arm64,使用 Go 1.27.1、Hugo Extended 0.166.0、Node
26.9.0 与 Apple Git 2.54.0。
初次实际工具试验暴露了 Darwin /var 与
/private/var 暂存身份、实际回环代理路由,以及 Vale 夹具中无效的行内块属性/行号
断言。暂存现在使用规范路径;Vale 夹具改为真实独立行块属性,源码遮蔽边界不变。
已验证子环境传入字面的 NO_PROXY/no_proxy 主机列表数据,不传代理 URL/凭据
和 Node 预加载设置。
受支持源码诊断需要已证明原始范围;渲染 lychee 位置仍是输出文件/DOM pointer, 不推断 Markdown 行号。离线时不调用 lychee。鉴权/限流/服务器/传输不确定性不能 通过严重度、排除项或基线确认变成必需成功。
首次受保护十文件晋升及其新鲜渲染验证现已完成。收据位于
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r6-docs-render-dcwtcmyl/summary.json,
555,297 字节,SHA-256 为
ee153932900dc6f1ec62beef1a75927fc60b857efccfbcc558bf0e2c2b12cc04。
64.17 秒运行使用上方记录的精确已验证 aa8b347f…371f 二进制及不变的
103 输入 b85affd9…a03b 运行时清单。
| 首次晋升文档归属检查 | 实际结果 |
|---|---|
| CLI 生产链接 | 0;一次严格生产 Hugo 渲染器,无分析构建 |
| 普通生产 Hugo / Markdown / 链接 | 0 / 0 / 0;214 内容页、43,376 文本节点;345 HTML 页、48,438 内部引用、4,259 片段 |
| 普通生产翻译 | 1 仅为已有 draft content/blog/release/1.2.0.md 未进入生产;不是新增 R6 失败 |
| 独立普通不可发布 Hugo / Markdown / 链接 / 翻译 | 全部 0;216 内容页、43,682 文本节点;347 HTML 页、48,814 内部引用、4,287 片段;137/137 组、1,118 标题 |
| 源码归属 / Schema | 翻译、风格与空白全部 0;137/137 组、1,118 标题;137 中文文件、181 粗体标记、零强调标记;CLI/文档结果 Schema 均绑定 7468c2d04cde8a368ce0ba44a1f27125b5fca364b6d4672353519b9545b3bdda |
| 保护 | 全部 12 个归属命令、CLI/Schema 检查及整体比对保留 421 主源码、427 复制输入、109 目录、36 可变 Git 文件的完整模式/类型/字节及逻辑 Git;两个私有普通源码副本和全部 103 运行时输入不变 |
首次晋升安装器收据 oink-r6-doc-drafts-ymjop499/applied-files.json 绑定
13c965592d64056d8365aed1927d2d422fadec8adc54ee7050b22e2ea0ad6270。
它在私有临时存储中保留捕获的实际原始 inode,保护后续旧打开句柄写入;恢复也保留
后续目标修改或删除。这些门禁后,R6/A07/A15
受支持范围已本地接受。
R7 只读 Studio 候选证据
R7 已实现契约描述的内嵌五视图浏览器及鉴权回环 API 候选。 冻结核心/浏览器及精确二进制四消费者验收 及受保护规范渲染门禁在声明范围内通过;R7/A16 受支持只读范围已本地接受。
聚焦原生与浏览器证据
| 归属边界 | 证据状态 |
|---|---|
| 原生/公共一致性 | 实际共享检查在 0/1/2 下保留诊断/覆盖/退出身份;显式选定工作区启动、清理/信号及无源码写入证明由归属测试收据单独记录 |
| HTTP 管理/源码/预览 | 字面回环选择;准确 Host/origin/Bearer 检查;无任意请求路径/写入;捕获源码/diff 限制及源码模式/输出清单保护;下方聚焦核心/新浏览器及本轮语料收据绑定该受支持范围 |
| 首次保持界面浏览器 | 14 次 axe 零违规、14 张截图;五个桌面浅色视图、捕获 BOM/CRLF 源码/diff、桌面深色、移动深色、全部五个 320 像素浅色视图及捕获变化。合成实际 Hugo 夹具保留 228 原生诊断和覆盖一致;建议复制使用私有测试剪贴板,不改宿主剪贴板 |
| 浏览器预览攻击 | 实际攻击脚本在隔离预览执行,但 parent 访问、管理 fetch 与弹窗被阻断;拒绝 token query。仅 draft 页面在生产仍为 404 |
| 快照保护 | 捕获源码指令/HTML 保持字面数据;显式任务夹具外部编辑后、刷新前,初始捕获仍保留旧字节。除已声明夹具编辑外,源码完整模式/Git/目录保留;changes 展示实际修改捕获输入 |
| 前一份保持浏览器 | 刷新保持 UI/后端收据通过:全部 14 次 axe 零违规、14 张截图,包含声明/捕获主题行 |
| 新局部预览浏览器 | 新冻结局部预览运行时通过:14 次 axe 零违规、14 张截图;实际 Hugo 正常 HTML 200 与 67,108,865 字节产物 413;原生 1/228 条诊断及覆盖保留,必需局部覆盖可见,Studio/刷新 2 |
首次浏览器收据位于
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-studio-browser-AfwaYi/summary.json,
SHA-256 为 930fbd1ab86806069b963bff2e3e95aaa07e3634400cec65e2b8c7922e2a1707;
准确二进制绑定
92e5b962e40fbe828a0b006f3ae76a2bddf4ad7e8b1c5b6967e365b8f1827879。
本地验证版本为 Node 26.9.0、
Playwright 1.62.1、@axe-core/playwright 4.13.0、Chromium 151.0.7922.34;
它们是贡献者测试依赖,不是下游运行时需求。剪贴板证据覆盖
实际 UI 点击和私有剪贴板实现。
刷新保持 UI/后端浏览器收据位于
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-studio-browser-vn0ofb/summary.json,
SHA-256 为 520603779f712539865c6e9ef7a9ad3ad21ec1906adfb067ec607cc69d071b7e,
准确二进制为 73bf90c69dce84849ee20ddfbfe825b9f2dd46f0cd37a228ce1b041a83afa33f。
全部 14 次 axe 与 14 张截图通过,包含声明/捕获主题元数据及全部五个 320 像素
视图。
初次并行完整套件试跑
/tmp/oink-r7-frozen-go-gate.log 与
/tmp/oink-r7-frozen-hugo-gate.log 失败。失败原因是并行包负载下
既有 CI 测试十秒截止时间,以及图测试观察器刷新自身 Git 索引。单独 CI 目标组随后
分别用 12.149、2.291 秒通过;受控 Git 观察器图运行用 1.354 秒通过。仅
internal/projectgraph/hugo_test.go 改动:其只读观察器关闭 Git optional locks、
filesystem monitoring 与 untracked cache。该测试修正后,普通实际 Hugo 图运行
用 1.562 秒通过。运行时与内嵌 UI 字节均未改动。
修正冻结收据为 /tmp/oink-r7-corrected-runtime-freeze.json:113 运行时输入保持
15a7de85a1ae9e6a73d8ea6570aa4f97bdd0ad5677ad7ca996fdd081ad43f7b5;
193 更广输入现绑定
67c6d36cf91d175f208f79cdd4d337aab6d2ef71e43453b20394b677678725e8,
相对前一份 85ad60d24c93e899020fbdcd34f8252c578253ce5afaf8652e561a432ecc8067
冻结只改变测试观察器文件。修正后的串行 Go 测试与 vet 已通过,日志为
/tmp/oink-r7-corrected-go-gate.log。修正后的串行实际 Hugo/固定版本工具套件也
已通过,日志为 /tmp/oink-r7-corrected-hugo-gate.log,SHA-256 为
2aed822ff6fc8be04919aa74ca6ada721789232c14c1d77f1d44113bc0d235a7;
应用包耗时 220.782 秒。独立 Go 后源码保护审计收据为
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r7-postgo-audit-qygh7bcc/receipt.json,
SHA-256 为 5ee45fe041536243bc1229054516835c61b27909a4f296ac226b1a138e4bc8dc。
持久修正归属门禁收据为 /tmp/oink-r7-corrected-owning-gates.json,SHA-256 为
c7f94a740e33a7349886b3f3689419719f857f39031375e80ca384a3dac36e67。
预备文档渲染驱动
现要求完整消费者收据证明私有捕获源码重建与最终浏览器二进制字节相同。独立
源码审计 oink-r7-docdriver-audit-ig_r8ud1/receipt.json 绑定 SHA-256
858cc48bf602fbdb26fcbda03c78ca485338b1295d64257d32cfb14b24f1ade3,
以及预备驱动
f25d9ac4bf1a7ef43d5526b7b3cbadf84dd64ad8a57fd82d1c82e76fdb2b3435。
首次消费者驱动试跑 oink-r7-corpus-h1lOFl 因
KeyError('preview_base_path') 停止:驱动直接索引实际预览基路径为空时合理省略的
字段。该私有重建与最终浏览器二进制
73bf90c69dce84849ee20ddfbfe825b9f2dd46f0cd37a228ce1b041a83afa33f
字节相同;四消费者源码清单与根清单全部保留。新 oink-r7-corpus-corrected-cByXTa 驱动仅将这两个访问改为
get(..., ''),SHA-256 为
4c409acacbb9b82e658e6705eddefd9a3541def5c63c340b65678a6c9a8354e4。
该新运行随后因仓库产物中一个清单文件超过 64 MiB 而失败:前一运行时拒绝全部
生产预览。原生检查保持结果 1,必需预览不可用使 Studio 结果为 2。
Starter、docs、PIG 在本次运行完成并返回 0;四消费者源码清单与根清单保持
不变。空基路径驱动修正均未
改动运行时或消费者源码。
局部预览的窄运行时/测试修正保持 64 MiB 限制,开放限制内受保护
生产文件,准确跳过的超大路径返回 413,必需 studio.preview 覆盖保持未完成。
原生检查结果不变;必需预览未完成仍使 Studio 返回 2。内嵌 UI 保持不变。
新的有界覆盖 detail 记录实际省略相对路径、大小与总数。
预备实际 Hugo 浏览器夹具增加 64 MiB 加一字节的 static/oversized.bin,用于
验证可用受保护 HTML 预览、准确跳过文件的 413、原生结果 1 及必需局部视图
结果 2。
新局部预览冻结收据为 /tmp/oink-r7-partial-preview-runtime-freeze.json,SHA-256
为 c426ce3e641ed7b39bb711a26006306cab22e761c5062f2164f10deb4bea8765。
113 运行时输入绑定
4900ae05abbdf4409b0be54f276fb4135269cf0a49e9071013ccf42544d35c84;
193 更广输入绑定
8b172cef2b228e2642f0139d6cc569136e86843f818e52e412fa4a2d56add25d。
运行时输入仅改动 internal/studio/preview.go;更广改动还包含其测试及
scripts/test-studio.mjs。三个 UI 文件字节与模式全部保持相同。聚焦核心最终 race
用 1.748 秒通过,vet 与限定空白检查也通过。收据
oink-r7-partial-preview-owning-a56dunn4/receipt.json 绑定 SHA-256
7b6ecb491f283d04fe54347e564dba426b1a84d152040a1d945af54bc67756ac。
初次稀疏夹具模式试跑中,宿主 umask 0077 使请求 0640 的文件实际为 0600;
夹具显式 chmod 到 0640 修正该设置,未改变生产行为。聚焦证明覆盖正常 200、超大
GET/HEAD 的 413、身份变化 409、私有路径 404 及其他未知产物错误拒绝。
新局部预览冻结的完整串行 Go 测试随后用 61.481 秒通过,vet 用 0.571 秒通过。
完整日志为 /tmp/oink-r7-partial-preview-go-gate.log,SHA-256
be7d6eccf99a6f4c1b8f09d1fb782455c7cbd3bad4a2f37e2f0e9da916bcb313,
以及 /tmp/oink-r7-partial-preview-vet-gate.log,空文件 SHA-256 为
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。
独立保持输入审计 oink-r7-partial-held-audit-o6p98i1l/receipt.json,SHA-256
e567efc5f580db9395afab8ad36c4db842c3db95db442eb1cb1c740dbd43ec31,
在 Go/vet 运行期间验证全部 113/193 输入及物理/逻辑身份。新完整实际 Hugo/固定版本工具调用随后用 285.649 秒
完成,退出 1。唯一失败是使用不可用 Markdownlint 准备路径
/md/node_modules;其他实际案例全部通过。该日志保留为失败调用:
/tmp/oink-r7-partial-preview-hugo-gate.log,SHA-256 为
1ab6b8cfd399d484e08a1d1f05d25475754caa731991dd1eec1cca03cf6ce970。
唯一归属案例改用准确预备的 /markdownlint/node_modules 可执行文件重跑,源码/
运行时不变,通过:应用包 2.317 秒,墙钟 3.265 秒。收据为
/tmp/oink-r7-partial-preview-corrected-tools-gate.json,SHA-256
62b75e563e8074995ed9dd354434e653b2f5f2c6d20767226286d0c08d4c667c;
日志 SHA-256 为
e9bddac210654d219d9c5d6ebabaa3b91a0f5f4de4daf228b3ae21aeaac7673a。
可执行文件来自预备收据
268e601e81bc03a263296d57257b85635371bda642d0632532a7d9318c981461。
独立案例矩阵/保持源码审计验证失败完整调用加该修正案例形成累计已执行实际
归属案例覆盖 0。收据 oink-r7-partial-case-matrix-audit-16_bl9hr/receipt.json
绑定 SHA-256
0a9e4a1a1e1a08f597becb2f27e743c9f23df672c713c2757241704edb16b51e。
全部 113/193 物理/逻辑输入保持冻结。可选 TestArtifactCorpus 与
TestPublishedRuleSourceProvenance 案例明确跳过。
新 Go 后输入审计 oink-r7-partial-postgo-audit-g1hcqdtl/receipt.json,SHA-256
b369737ec48456f673c850ea702cb3cb7efffb8ecc129d87e00dc03af82b2e3b,
随后确认 Go/vet 后全部保持的 113/193 物理/逻辑输入。
新局部预览浏览器基于准确二进制
f39d6754f7ad13599e4e849394e0f470b2c6f26edf96ce40f199d27b65a8030e
通过,版本 0.4.0-r7-local,16,000,578 字节、模式 0700。摘要为
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-studio-browser-cE9be3/summary.json,
SHA-256 为 9099e6c407fd0f9de3c29ce80e03f034a4223d7d7a7c1f1378052e8b9084e0ae;
来源收据 SHA-256 为
85b9f537fb09eecbb09d133b53a297c78184c11200ab0938734c0d10f3449095。
私有 oink-r7-browser-partial-ZZIqzZ/source-binding.build.json,SHA-256
7a89ab8318c3a38455ab6ce12bcdbc53ae5ce0674fb5aaaa1df0fcf68a093399,
将捕获/构建前后全部 113 运行时及 193 更广源码输入和物理身份绑定到新冻结;
根输入保持不变。全部 14 次 axe 零违规、14 张截图通过,保留上方键盘/移动/深浅色/
源码/剪贴板/安全检查。实际 Hugo 生成 67,108,865 字节 oversized.bin,经渲染
/sub/oversized.bin 链接实际到达并返回 413;普通实际 HTML 返回 200。
必需局部预览覆盖保持可见;228 条类型化原生诊断、原生覆盖/结果 1 在 CLI/API/UI
中一致,Studio 及随后刷新返回 2。源码保护仍仅排除明确任务夹具外部编辑。
另一份预备语料驱动曾假定正常可用预览总会附加 studio.preview 覆盖行。
实际正常 Starter/docs/PIG Overview 不输出该行;准备假设已修正,未改变原生覆盖。
修正后的新 oink-r7-corpus-partial-pZLwY0 驱动,SHA-256
47778df62505beeb7432985be927f1b001e03824e9dee3a6dbed9d9b2dbe049c,
已针对这三份保留实际 Overview 及当前局部浏览器捕获复核。正常可用仍需实际
预览 URL 和独立 HTML 200;局部捕获保留真实必需覆盖行、省略总数/身份和 413。
准备审计为 oink-r7-partial-driver-correction-audit-sa98cm6k/receipt.json,SHA-256
a519a5bc6ae83438146ff4710d53f5edb0e656a05d0532c02123e5771416f07e。
新四消费者验收用 234.6425 秒完成,驱动结果 0。本轮摘要为
/private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r7-corpus-partial-pZLwY0/summary.json,
SHA-256 d7b5a4f1607b6f75ae6a596c19cbab28685fb67dac750096173060ed097c8bf5;
日志 /tmp/oink-r7-partial-corpus-gate.log 绑定 SHA-256
a7b724500569bd594d8e01502ec1956eb089cc9153b04b693992a9322013c811。
持久本轮语料 qualification.receipt.json 绑定 SHA-256
4e5df7c3fda9f0b763091af3e6cb85c68c736c319a1de68030b81d5cd5b384bc;
主源码清单分别包含 97/421/858/2,294 个文件。
私有捕获源码重建与新浏览器二进制
f39d6754f7ad13599e4e849394e0f470b2c6f26edf96ce40f199d27b65a8030e
字节相同。113 运行时/193 更广输入及根物理身份保持冻结;每次操作与整体边界均
保留四消费者字节、完整模式/类型、逻辑/可变 Git、被忽略捕获输入和目录。
| 消费者 | 原生结果 | 类型化诊断 | Studio 结果 | 实际捕获页面 |
|---|---|---|---|---|
| Starter | 0 |
28 条审核信息记录 | 0 |
66 |
| docs | 0 |
144 条审核信息记录 | 0 |
343 |
| PIG | 0 |
120 条审核信息记录 | 0 |
248 |
| repo | 1 |
10,462 条既有重复 ID 发现加 788 条审核信息记录 | 2 |
1,576 |
四站嵌套原生 header、类型化诊断、覆盖及退出与直接 CLI 检查准确一致。Issues
完整分页;其他视图采用有界样本,四站均有捕获物理源码及翻译 diff。前三站实际
生产预览 HTML 返回 200;它们不输出 studio.preview 省略行。
仓库正常 HTML 返回 200、60,100 字节。当前捕获准确暴露四个超大 print 路径;
每个实际 HEAD 返回 413,响应体零字节:
| 捕获省略相对路径 | 捕获字节大小 |
|---|---|
_print/pkg/index.html |
73,976,221 |
_print/pkg/pgsql/index.html |
69,903,999 |
zh/_print/pkg/index.html |
73,086,240 |
zh/_print/pkg/pgsql/index.html |
69,052,754 |
必需
studio.preview 保持未完成,仓库 Studio 2 因而保留原生 1。docs 与 repo 实际
仅分析 draft 路由在生产返回 404;Starter/PIG 缺少唯一捕获 draft 路由,该测试
不适用。工作区子集/全集/健康子集会话仅选登记站点,不捕获并关闭监听器;
未知或选定缺失站点在启动前返回 2。环境为本地 Darwin/arm64,使用
Hugo 0.166.0 Extended、Go 1.27.1、Git 2.54.0。独立最终语料审计
oink-r7-final-corpus-audit-xr3_u5dt/receipt.json,SHA-256
b8a8eedf5c899fe5830bdde959783c46b3f191ab144c0d3798077555d55238fc,
不重渲染、不新增 HTTP 请求,验证原始类型化原生/API/退出一致、132 项操作保护
比较及四项整体保护。
临时磁盘容量准备期间,仅清理三个私有 Go build cache,共
366,184,826 字节,收据为 /tmp/oink-r7-private-cache-retirement.json。
源码、二进制与验收证据均保留。
剩余阶段门禁与晋升边界
| 必需门禁 | 当前状态 |
|---|---|
| 冻结运行时输入/二进制身份 | 新局部预览冻结绑定 113 运行时输入 4900ae05abbdf4409b0be54f276fb4135269cf0a49e9071013ccf42544d35c84 和 193 更广输入 8b172cef2b228e2642f0139d6cc569136e86843f818e52e412fa4a2d56add25d;所有 UI 字节/模式保持不变。源码绑定浏览器二进制 f39d6754f7ad13599e4e849394e0f470b2c6f26edf96ce40f199d27b65a8030e 已通过;新私有消费者重建字节相同 |
| 完整 Go/vet 与实际 Hugo | 新完整串行 Go/vet 与浏览器已通过。新实际 Hugo/固定版本工具完整调用保持因准备路径退出 1;唯一修正归属案例通过 0,独立验证累计已执行实际案例覆盖 0;两个可选案例明确跳过 |
| 四消费者 | 精确二进制 CLI/API 验收完成;原生 0/0/0/1、Studio 0/0/0/2,准确嵌套原生一致及源码字节/完整模式/类型/Git/被忽略输入/目录保护;仓库局部预览保持必需未完成 |
| 规范配对源码/渲染 | 首次受保护 TEN 晋升及限定实际渲染通过 |
| 阶段决策 | R7/A16 受支持本地范围已接受;R1–R7 已本地接受 |
下一份预备文档安装器在成功与恢复时都将捕获的实际旧 inode 保留在规范源码外, 不会在较早目标身份检查后取消其最后名称。
首次晋升渲染门禁完成,收据为 /private/var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/oink-r7-docs-render-n8tw2tbw/summary.json,SHA-256 35e79f51d39803b3e4cdf134ed277957dd627acba42e0e0dc785e4745ec3c481,日志 SHA-256 de3a07eac661c15805070e0ed2e364a71ebbd38e15d8907aab3bdf716e95131d,耗时 63.33 秒。准确已验证二进制 f39d6754f7ad13599e4e849394e0f470b2c6f26edf96ce40f199d27b65a8030e 在一次严格 Hugo 构建下通过生产 CLI 链接。无 probe 普通生产 Hugo 通过渲染 Markdown(214 页/44,075 文本节点)及链接(345 页/48,482 内链/4,303 片段)。其翻译归属仅因既有未发布 release 1.2.0 draft 保留退出 1。独立 draft/future/expired 分析通过 Markdown(216 页/44,381 节点)、链接(347 页/48,858 内链/4,331 片段)及翻译(137 对/1,129 标题)。源码翻译/样式/空白检查通过,CLI/文档 schema 7468c2d04cde8a368ce0ba44a1f27125b5fca364b6d4672353519b9545b3bdda 保持相同。十二项操作、schema 及整体保护均保留 421 主文件、427 捕获输入、109 目录、36 可变 Git 文件和 113 运行时输入。
首次受保护晋升收据 oink-r7-root-promotion-p9g1u7pz/summary.json,SHA-256 323a5ce267e39aaf8f97dc4a12cccbdde83730155a199efb5f3815e29b334e4a,验证实际原始 inode 保留在规范源码外。
R8 已接受受审阅编辑证据
R8/A17 受支持编辑范围在下文修正冻结归属/浏览器/语料及受保护规范渲染门禁
后已本地接受。CLI edit text、field、
snippet、attachment 预览与显式 studio --edit 相同的绑定 oink.edit/v1
意图。保存计划应用或 Editor 显式确认 Apply 拥有选定源码写入;默认 Studio 会话
保持只读。
候选范围与保护
已知站点所有 UTF-8 Markdown 上限 1 MiB。完整文本及受支持普通顶层 YAML 标量
表单保留声明的 BOM/换行及源码区间保护边界,不支持表单形态保留文本。准确
value_json 数值避免浏览器 Number 舍入。标量表单把数值字面量限制为 4,096 字节、
十进制指数绝对值 10,000,更大/非有限构造保留手工文本。字段 JSON 最多 1 MiB;
转义孤立 surrogate 拒绝,有效 Unicode 对受支持。目录组件使用原始 UTF-8 正文字节偏移;
附件要求实际 leaf-bundle 身份、最多 4 MiB、独占干净新 basename。源码哈希、
完整模式、全部站点/外部输入、重新生成意图及新实际 Hugo 验证绑定同一共享保护
应用路径。
Editor 展示完整 UTF-8 审阅、选中文件基准/结果身份及原生候选结果;审阅上限
2 MiB,确认前对准确可见字节验证哈希。实际选定候选 HTML 是 draft/future/expired
分析,明确不可发布,与原生产预览独立。必需候选视图未完成可以把提议/会话升为
2,而不改写原生发现。页面文件编辑的选定候选源码哈希/完整模式须匹配已审阅
After 状态;附件/no-op 提议的选定页面保持已审阅 Base 状态。
过期/重放计划、附件冲突及不可信预览请求会拒绝;已应用但刷新失败保持明确已应用。
聚焦准备收据
| 候选证据 | 当前观察与边界 |
|---|---|
| 纯编辑核心 | 归属聚焦准确数值测试通过:18446744073709551615、7.12345678901234567890123456789、准确 no-op 原始字节及末位小数改动;更广冻结收据待完成 |
| 实际 DFE 输出所有权 | 活跃普通输出通过受限 os.Root、独占目标文件及保护流式读取复制;超过 64 MiB 的文件可捕获,服务限制仍不变 |
| 复制取消/race | 上下文 helper 聚焦 0/0.703 秒、race 0/1.856 秒、vet/空白 0;实际首块取消保留部分输出,源码字节/模式/身份不变;源码 FIFO 替换不能在 fd 证明前阻塞 |
| Helper 日志身份 | 聚焦 c227a88210ab0dc46b24eaff50a347d5c494e9ce23f5bdef5d5b822efab4976f;race 13f0616d55fd4df791ecded0712a18096392c88cb9b849383414c305e50b6779;vet 为空 SHA-256 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
| 只读候选集成审查 | 审查选定实际 HTML URI/base 前缀/清单、保留私有 DFE 生命周期、源码 SHA/完整模式一致、原生与视图覆盖及取消;本次代码审查范围未发现新实质缺陷 |
| 首次 Editor 浏览器试验 | 驱动因全局 Open editor 选择器歧义停止;保留失败试验 |
| 修正选择器 Editor 试验 | 桌面字段/组件/二进制应用及 axe 检查通过后,320 px draft 审阅横向溢出失败;保留原试验 |
| 定点布局修正 | Editor 审阅哈希/收据文本可换行,固有宽度受限;此前 CSS 与失败证据独立保留。开发重跑通过 12 项 axe/截图,包含真实 320 px 暗色审阅及亮色收据/拒绝;最终冻结源码/二进制重跑待完成 |
前述准备行记录于首次完整 R8 冻结之前。后续首次完整冻结
门禁通过,二进制为 84b804d3246a5be581e44884ed910fa3f45d8be29734b8babdeeb763a11fa882
(0.5.0-r8-local),运行时 123/58517b8e98b80df6642be4ee6275a0074ec187768b20e009f41da2607b635d46,
更广源码 212/d81335c78413acc60e27adee0ac794862285e41cb2a3a7687ec820c1065ba003。
完整门禁摘要为 b49f4a3272af3e3dcc92e7e9b38d4289bb49cb19aa3e9354ea148d2d8cb2cea8:
Go 测试 0/56.523 秒、vet 0/0.904 秒、完整实际 Hugo 及三个固定工具
0/320.044 秒、核心 race 0/16.610 秒、公共 R8/helper race 0/48.319 秒。
源码保护通过。
首次冻结浏览器收据
eb6977235ef9ae6cec28651b5654eb101685af67abe0cbed0acb0f8375458d9c
绑定相同二进制及源码冻结。Editor 12 项 axe 零违规、12 张截图;保留只读
Studio 为 14/零/14。实际表单保留 1e400 字面量、计划 after 哈希及 diff,随后
丢弃;指数 ±10,001 与 4,097 字节数值在本地拒绝,不发送 API 请求。四次确认的
字段/组件/二进制/draft 应用只发生于一次性夹具。默认只读拒绝、过期输入保留、
no-op 源码字节、预览隔离及原生结果独立性通过。
首次准确二进制消费者试验随后在 Starter 停止,耗时 37.533 秒。不可变失败试验
收据为 853a8397ba4c527c03aa3cc9ac7cacc41c0ab0379549d145b874f1d1ecc3901c。
两项失败分别记录:驱动事件哈希依赖 JSON 对象键顺序,但递归比较证明 API/CLI
数组相等,均为 28 条诊断、29 条覆盖、原生退出 0/0。另一个是真实运行时
问题:重复解析缓存捕获产生 .gitattributes 模块输入重复;必需候选图捕获不完整,
修改结果为 2,原生检查仍为 0,没有实际选定 DFE HTML 或可应用 lease。
公共发布缓存夹具重现该缺陷,保留失败日志为
50ab704e715665096e0f36391bb1364841c3a2b2ac88dd262915ce53fb66afa6。
全部 48 份已记录逐操作源码证明及四份消费者整体保护保留字节、完整模式、类型、
Git、忽略但复制的输入与目录,根输入准确不变。没有 Apply 或保存计划。
定点运行时修正先收集完整解析模块行,再提交新增记录。相同重复或重新排序的
捕获保留原清单。既有范围内哈希/完整模式变化、路径增删、整个范围缺失、身份
冲突或捕获错误都拒绝,不刷新此前证据或追加部分结果;非模块行保持准确。
site 归属收据 e7b284b9dece1c5f2b696cd76166d2286fcbec69e6c48912ab9a78204bdb980b
记录聚焦 0/0.746 秒、site 0/2.123 秒、聚焦 race 0/1.958 秒、vet
0/0.167 秒。
修正公共发布缓存收据
6358dc81f06e34b789cb47a0f4442d6d036d2e33423a06f5dbe85b3064766da6
使用任务本地复制、校验过的 github.com/pgsty/[email protected] 归档,没有下载或
replacement。原始/候选图各有 1,256 条唯一输入,含 1,198 条模块输入。完整
API/CLI 类型化诊断、覆盖、退出及计划身份一致,实际选定 DFE HTML 返回 200,
源码字节/完整模式/Git 不变,没有 Apply 或保存计划。归属 race 30.255 秒通过,
vet 通过;修正 race 日志为
c7d21a30b8af141d9d9604a80ddf9cf3f608320b97a441a06a742376e2119551。
当前完整修正冻结为
fb276500a3d2643bd0aa220f8bebb380fce2c98493d62b0502b6497b2f02949f,
运行时 123/cdf629eeb4bbef6d4d88ee27fe3fb0a73b07b6bf6438336e033a18fb7feb1c17,
更广源码 212/f6e305e792733a550814eb841615d12fa14a9a6bb2a97c4ada85f7275183e579。
相对首次冻结只变动 source_inputs.go、其归属测试及公共发布缓存测试;受控
UI/helper 字节及全部完整模式不变。重建候选为
bd25f9e0b35ec10e227aabf9582ae40b0b367390f64b93668de6ae85222c3d71
(0.5.0-r8-local)。已观察的修正源码绑定浏览器收据
33a698985a55c14c3e64e981da1f8e74c686497083dc0edb241406f185e3eeb8
再次记录 Editor 12/零/12、只读 14/零/14,完整 123/212 前后源码保护通过。
下一次证据观察时,修正完整门禁已完成,绑定前述受控 bd25f9e0…22c3d71
二进制及 fb276500…02949f 冻结。摘要
208f156c0954e803eccbada678a4689683dc1576c543c379e5cc04b3497ef772
记录 Go 测试 0/57.826 秒、vet 0/0.521 秒、完整实际 Hugo 及三个固定工具
0/378.847 秒、核心/site/Studio race 0/17.937 秒、公共 R8/附件/输出 helper
race 0/85.159 秒。每项门禁源码前后保护通过。实际 Hugo 日志有 434 项顶层
通过、零失败;两个可选外部夹具 TestArtifactCorpus 与
TestPublishedRuleSourceProvenance 明确保留跳过状态。
修正完整实际 Hugo 日志为
4eb1a2afdce2adbe570b10922fd53b6d8954f7c95747370c3c661e94d2f71a05;
Go 日志 ea59463e9649ffe2f8aff9da66c91cf6895c86fde96a524db23c89cd4eb35925,
核心 race cdd3d761b5ca7b5e986b25aee3129d65663e3e5ebb83eecb6fbb080387298a58,
公共 race bb610bdcd7a299cb9b66f4c69e30e246c20546efae47653c01d350b1026ea2de。
前述 434 项通过与两个可选跳过是顶层计数。同次完整调用还跳过嵌套 Unix
socket 拒绝夹具,原因是 Darwin 临时路径超过 socket 限制。首次缩短私有路径
试验仍跳过:收据
93106854ca890b497d3c74522b895f597ac60cec55ced42cad7b187d334da200
保留进程退出 0 并记录未实际执行 socket。
后续不解析别名的短私有 TMPDIR 在 race 下实际执行相同冻结 socket 夹具,
没有跳过,0/2.954 秒。收据
531a503b3b91e1b423c2be61b92ed806d3a813738c38d57e5ec122577b4337f9
与日志 236c84f1842ffce76174c834f3888718a109cec6377ada6f3242b02f551f00b3
绑定 fb276500…02949f,全部 123/212 逻辑/物理/Git 输入前后不变。
修正四消费者试验随后于 845.705 秒
完成。摘要
af4fc53326163c4a03aa2982c1f01363fbbdd5a447c9baed3639bd8599d46370
与验证收据
4d6fd02543c1920497e1a1bb0a68fcf89b0546130fd9cc12c1df391b7e673e75
绑定逐字节相同私有重建 bd25f9e0…22c3d71、完整受控
123/cdf629ee…feb1c17 运行时及 212/f6e305e7…5183e579 输入;首次失败试验全部
2,383 项准确保留字节/完整模式/类型。
| 修正消费者 | 原生当前及原生候选退出 | API 候选结果 | 完整诊断/覆盖 | 实际选定分析 HTML |
|---|---|---|---|---|
| Starter | 0 / 0 |
0 |
28 / 29 |
200,48,149 字节,/blog/design/content-model/ |
| 文档 | 0 / 0 |
0 |
144 / 34 |
200,61,738 字节,/blog/oink/immersive-reading/ |
| PIG | 0 / 0 |
0 |
120 / 41 |
200,55,641 字节,/404/ |
| 仓库 | 1 / 1 |
2 |
11,250 / 29 |
200,92,352 字节,/blog/infra/2020-12/ |
这些是独立、明确不可发布 draft/future/expired 候选视图中的实际选定 Hugo HTML
路由,预期候选 marker 均存在。API 与 CLI 的完整类型化诊断/覆盖、原生退出、
计划 ID、Base/After 哈希及完整模式、unified diff 和选定页面提议源码一致。
完整准确 API 审阅及其哈希独立验证。
没有消费者 Apply 或保存计划,没有遗留监听器。仓库保留 10,462 条已有重复 ID
发现和 788 条信息记录,四个实际 PRINT 输出仍为必需局部预览未完成:
_print/pkg/index.html 73,976,221 字节、
_print/pkg/pgsql/index.html 69,903,999 字节、
zh/_print/pkg/index.html 73,086,240 字节、
zh/_print/pkg/pgsql/index.html 69,052,754 字节。每次有界 HEAD 请求均返回
413、正文为零,选定限内 HTML 保持 200。原生 1 未改动,提议/会话 2
及拒绝 Apply 保持可见。其余三站完整预览以实际受保护
HTML 200 作为证据。
准确 53 次受保护操作每次检查全部四站:212 次逐操作源码证明加四次整体证明, 源码字节/完整模式/类型、复制的忽略输入、目录及逻辑/可变 Git 均未改变。每次源码 证明比较四类清单,因此含整体比较共 864 对原始清单。全部 53 次根目录保护及 最终完整 123/212 逻辑/物理输入也相同。所有 issues 与 pages 分页遍历,其他五个 视图端点仅取前 50 项,每站一个已知源码及一个有界 diff。完整原生/CLI 记录 一致性使用已声明有界完整记录 codec;对象顺序规范化,数组顺序、类型、null 和 字段存在性仍有意义。
独立审计收据
6b72ca06d8392a5271fc40757f176f26e1144c21eec93dbd035e0a1bd645657b
验证 69 项产物哈希、完整有界 API/spool/关闭类型化记录及大型原生/CLI 原始文件
摘要绑定。追加收据
335f137663d4ec0b2a0d3e49c8d70b9918f86078ab6264855171a2644d8aa6c6
还把当前根目录完整逻辑 Git 清单与冻结重新验证,原审计保持不可变。
经审阅首次 TEN 晋升随后通过
受保护保留 inode 安装器,根收据为
7cd9b4604d2340b9e46965a26281c921b967060909d518b8b4b31e5f42d0120c。
实际原源码 inode 保留在文档站外;未选中源码/复制输入、目录和 Git,以及完整
CLI 123/212 逻辑/物理输入保持不变。
规范渲染随后于 67.21 秒完成,摘要为
bb0d0710294f810fb14284f7b5b0329befbd290b66c21397b9a45e8382287fb6,
验证收据为
32d3ffeca43bc9ad4615edcca0d3cc47cc932bbc93c6576724c800e0a62405b1。
它使用准确已验证 bd25f9e0…22c3d71 二进制及修正 123/212 冻结。实际 CLI 生产
链接通过 0,只有一次严格 Hugo 构建。独立普通无探针生产 Hugo/Markdown/链接
通过:214 个 Markdown 页面/44,691 个节点,以及 345 个链接页面/48,532 条内部
引用/4,351 个片段。生产翻译归属仅因已有 release/1.2.0 draft 不在生产而保持
1。独立明确不可发布 draft/future/expired Hugo 分析的 Markdown(216 页面/
44,997 节点)、链接(347 页面/48,908 引用/4,379 片段)及全部翻译均通过 0。
源码翻译通过 137/137 对、1,143 个标题;中文样式通过 137 文件/181 个加粗区间/
零强调,空白通过,Schema SHA-256
7468c2d04cde8a368ce0ba44a1f27125b5fca364b6d4672353519b9545b3bdda
准确相同。全部 12 个命令、Schema 及整体保护保持 421 个主源码文件、427 个复制
输入、109 个目录和 36 个可变 Git 文件,以及全部 123 运行时/212 更广 CLI
逻辑/物理输入不变。验证收据绑定 60 对规范清单、15 对 CLI 保护及六对私有复制
源码。
R8/A17 受支持本地编辑范围在修正归属、socket、源码绑定浏览器、精确二进制
四消费者保护及这些受保护规范门禁后已接受。R1–R8 已本地接受;仓库原生发现及
必需局部预览 2/拒绝 Apply 保持可见。独立平台权限依据审计
762571dab9a07651ac8e4c71764bfef292f8d5eba729a089e72d9d755b7e2d7c
确认初始契约验证实际执行架构:macOS arm64、原生 Linux arm64 与模拟 Linux
amd64。Darwin amd64 保持实验归档,实际执行失败/运行时未验证。
必需门禁矩阵
| 必需门禁 | 当前状态 |
|---|---|
| 最终不可变运行时/源码冻结与准确 CLI 二进制 | 修正完整冻结 123/212 与 bd25f9e0…22c3d71 绑定完整归属/浏览器/语料/规范范围;首次冻结试验保持独立 |
| 公共 CLI/JSON/退出、过期源码/配置/外部输入及保护写入器测试 | 修正公共 R8/附件/输出 helper race、完整 Go/vet/实际 Hugo 及规范阶段门禁通过 |
| 冻结完整 Go/race/vet 与选定应用后实际 Hugo/普通 Hugo | 修正完整 Go/vet/实际 Hugo 及核心/公共 race 通过;434 项顶层通过、零失败、两个可选外部夹具跳过明示,首次试验保持独立 |
| Editor 浏览器五视图一致、文本/表单/组件/二进制附件、准确数值/no-op、过期拒绝及预览隔离 | 修正二进制源码绑定 Editor 12 项 axe 零违规/12 截图及只读 14/零/14 通过;已绑定完成语料与规范接受 |
| 准确二进制四消费者只读验证 | 修正全四站于 845.705 秒完成;完整类型化原生/API/CLI 候选一致,212 次逐操作源码证明加四次整体,无 Apply/保存/源码写入;首次失败试验保留 |
| 受保护规范 TEN 晋升、中英文源码/schema/样式/空白及实际生产/分析渲染 | 受保护首次晋升与独立准确二进制 67.21 秒渲染通过;生产仅保留已知 draft 翻译遗漏;渲染字节与本次状态字节分别绑定 |
| R8/A17 阶段决策 | 受支持本地范围在修正完整归属/浏览器/语料/规范门禁后接受;R1–R8 已本地接受 |
| 最终 A18/平台/归档交付 | 已在下文 2026-10-04 增补中完成 |
先前 R1–R7 节、完整调用失败及限定接受 收据保持不变。临时验收文件留在规范内容和 Git 外。
2026-10-04 当前运行时完成增补
本增补记录 2026-10-04(Asia/Shanghai)的当前候选。带日期页面 URL 与初始 2026-10-03/R1–R7 记录保持不变。三个 UI 文件与已通过浏览器验证的字节/完整模式 准确相同;当前 Go、Hugo、平台、归档与四消费者检查刷新变动后端。
首次当前 ARM 离线单元运行发现真实产物复制完整性缺口:ext4 上增加目录项时,
父目录分配大小和实际观察时间戳可能不变。该失败运行在后续验收前停止。有界
修正捕获并重新验证实际排序目录成员及项身份,同时保留常规文件字节/完整模式
证明。仅 internal/app/studio_output.go 与其归属测试变更。失败收据和独立审计
保留。
前一完整性修正验收的完整冻结为 683daca0e522193c7ff1b0de6ac2fee5d2fca080811bf184a8dfd5b90a33f224:
123 个运行输入哈希为 d346ad15cd4239004e32e1b9f30d727eaf156be0187dc165ca874032a7cf962a,
212 个完整 CLI 输入哈希为 2abd1a044d8192b07f9bbc06b55dc8b4544d66ca17b8867971cec702ba3af088。
0.5.0-r8-local Darwin arm64 候选为
74ad94e73557f6538cd64edd1766d6df92c596d98411031159d94af072c186ec。
下列已观察的完整性修正收据绑定前一源码范围。
后续单个归属夹具修改具有独立完整源码身份和已完成的正式验收边界,如下记录。
当前完整源码冻结现为
196245a3ba09305e34b86539c8eb79f1473e4373ee47aa1f56f8933b04a42d43。
123 个运行时输入保持
d346ad15cd4239004e32e1b9f30d727eaf156be0187dc165ca874032a7cf962a;
212 个完整 CLI 输入为
2c487bfb4c65ed40ff78356b2860de627e6ac1afa0da2df433b09345dab7f5b0。
只修改发布缓存归属测试,其源码为 54c10ef89310256b5f4c165c7de5de9668e1d4d2991b71751076680141dbe779。
夹具修改有独立保护,生产字节与全部语义断言保持不变。该完整源码的八项当前
归属门禁、刷新归档与完整 plain-Go AMD/ARM 正式验收已通过。根 A18 证明
2c018cb2afa3f26699a9e6b5a0971096246b12405fde5a27e43a9e213e46da60 绑定全部三个声明支持目标与五份复现归档。
| 当前证明与保留的此前输入边界 | 已观察结果与绑定收据 |
|---|---|
| 新完整源码的正式验收 | 冻结 196245a3ba09305e34b86539c8eb79f1473e4373ee47aa1f56f8933b04a42d43、归属测试 54c10ef89310256b5f4c165c7de5de9668e1d4d2991b71751076680141dbe779、运行时 123 个输入未变。当前八门禁、host/归档及完整 plain-Go 双 Linux 流程通过,由根 A18 证明 2c018cb2afa3f26699a9e6b5a0971096246b12405fde5a27e43a9e213e46da60 绑定 |
| 有界完整性修正 | 归属收据 6966d768025497b45958073d4c53a2a2981065c8a95857834dcf6a4faa4f0201;独立审计 6cb5d2eabf57b41079026a38a674f46def9f56a15df17ad27e671e4f765798df |
| 前一源码六项归属门禁 | 构建、完整离线 Go 单元/vet、完整实际 Hugo/固定工具、核心 race 与公共 R8/输出 helper race 均为 0;耗时 3.501/68.257/3.909/333.801/37.070/79.670 秒。汇总 b40b7787b3da8dc1e0763812b6dde529b4b5b69fe479d79940f1161223124e1d;独立审计 20780662b7ff35019b2c8c84e6dc763f9351ae0816f6ef7a7789f7a15be99167 |
| 八项当前冻结归属门禁 | 发布缓存实际 Hugo 与 race、构建、完整离线单元/vet、完整实际 Hugo/固定工具、核心 race 与公共 R8/输出 helper race 均为 0。当前汇总 d6272fcc4dfab114aecfcdf19a7e2b78f1e931817331b460f43ff2056bf754a4;完整 Hugo 原始记录 435 项顶层通过、零失败、两项可选顶层跳过及明确长路径 socket 子项跳过,运行时/二进制字节未变 |
| 前一源码 Darwin arm64 与归档 | 当前提取候选在 checkout 外运行,无消费者 Node 要求。17 个命令和八项实际进程测试(含子进程信号)通过,进程测试无跳过。两个新 release 目录中的五份归档/校验和字节相同,源码/许可证/来源/规范 tar 验证通过。汇总 bcb4d7599e965c1b3cfe7fe698ca14061ad53d45e7a194337aeebb8d37aa77c1;独立审计 60a04771365d8be15ac91fbbd8d485b019aae081598e468018861ca5734e387c |
| 当前 Darwin arm64 与确定归档 | 17 项提取归档/普通 Hugo/进程命令达到预期退出,缺少 Hugo 明确为 2;八项实际信号/进程案例无跳过。两个独立新构建从当前完整源码复现五份字节相同归档。汇总 3890fd8468b6bce5271bb32ffa1a18bd5daf99c19c43becac0be8e3b908a5d57;源码、工具、模块缓存与 smoke 源码保护准确一致 |
| 前一源码 Linux arm64 | 在 ext4 上以非 root 用户实际运行 Linux arm64,Go 1.27.1、Hugo Extended 0.166.0、Git 2.47.3:完整离线 Go 单元/vet、13 项必需纯测试顶层通过记录,目录成员项及四个子项无跳过、10 项选定实际 Hugo(无跳过)、原生重构归档身份、安装后二语言/离线/普通 Hugo/缺少 Hugo 为 2 的 JSON 与信号/源码模式检查均通过。guest 汇总 409990bc1425f4bf219f8911a71581af6e68729865580121dbeb6d85a06d2ea7;外层收据 a022e40f068703cd59ce6d6a7fb6530cce6907681baa26eb1dfc77c09f0c8898;导出记录审计 24afc50f6f860394d1ebfa7a8b754ddd9cb97f9e88a0dcfcbcb659193ecbfe5f |
| 当前 Linux arm64 | 当前 Linux arm64 在 ext4 上以非 root 用户实测(QEMU HVF 原生 ARM),Go1.27.1/HugoExtended0.166.0/Git2.47.3:完整离线单元/vet(370 项顶层通过)、13 项决定性纯测试及四项成员子项无跳过、10 项选定实际 Hugo 无跳过、当前归档原生/安装字节身份、双语/离线/普通 Hugo/信号流程通过。24 命令达到预期退出,含缺少 Hugo 为2。guest 268102f69c0950f9d2994d22cd2fd290fc11e24bd6d6f916fd70a93ca4946c74;外层 f3c066fdc9b97feff92160346185a1af978a5172eed5c81904ac7c0e5fc6c982;源码/SDK/借用输入/旧任务保护准确一致,独占 VM 回收。默认可选单元跳过保留具名门控原因 |
| 前一源码 Linux amd64 失败试验 | 当前 TCG 试验失败;原外层收据 3543664ba5590f2ba5a8f676b196bb636b72bc819913289f415d0a8a841c1bdb、guest 汇总 16075204d287713c7f7650c0a65dd289dd4bd83db07c9ba4b85b3f21244d5240 保持不变。完整离线单元(370 项顶层通过)、vet 和前三项选定 Hugo 案例通过;发布缓存候选请求触发测试 HTTP 客户端的 90 秒截止时间,候选一致性、其余六项选定 Hugo、原生重构归档及安装归档 smoke 尚未执行。截止时间审查 12917b9eb89e3abc5893e08da3b6b6e20743dcb4c14e6f7ba8561628e3566934 |
| 当前 Linux amd64 | 当前 Linux amd64 在 ext4 上以非 root 用户实测(QEMU TCG 模拟),Go1.27.1/HugoExtended0.166.0/Git2.47.3:完整离线单元/vet(370 项顶层通过)、13 项决定性纯测试及四项成员子项无跳过、10 项选定实际 Hugo 无跳过、当前归档原生/安装字节身份、双语/离线/普通 Hugo/信号流程通过。49 命令达到预期退出,含缺少 Hugo 为2。guest 3a1a32979efc843de8b95b7c13824026e17f71c06d4c458b738c0b9583fb4723;外层 30cf4950cc83fa0732047d9a0f89bb59e68779ee2e8f5c755724c9679be265e3;源码/SDK/借用输入/旧任务保护准确一致,独占 VM 回收。默认可选单元跳过保留具名门控原因 |
| 运行时等价的此前四消费者候选语料 | 源码时期 683daca0…33f224;运行时 123/二进制 74ad 与当前 196245a3…42d43 字节相同,单测试修改后未重跑语料。853.249 秒;原生/候选原生 0/0/0/1,API/视图 0/0/0/2;诊断 28/144/120/11250、覆盖 29/34/41/29。汇总 a1e98ca3e10095a1134381666bacf256f8e8827cd3900c9e811b7120de4c2974、收据 05c4562a50d9f83ba2c99879ec841870c5e753199e41792bd5bc718cf8046e7b、独立审计 3a1b0b6e3a8c6b1a0d82c5f82b46c84b1e44d6c30bab655610cb9e86e6a30b47 |
| 最终规范生命周期与渲染检查 | 晋升 TEN 字节使用独立规范渲染及导航/URL 收据 |
此前六门禁 b40b7787b3da8dc1e0763812b6dde529b4b5b69fe479d79940f1161223124e1d、host/归档 bcb4d7599e965c1b3cfe7fe698ca14061ad53d45e7a194337aeebb8d37aa77c1 与 ARM 外层 a022e40f068703cd59ce6d6a7fb6530cce6907681baa26eb1dfc77c09f0c8898 / guest 409990bc1425f4bf219f8911a71581af6e68729865580121dbeb6d85a06d2ea7 / 审计 24afc50f6f860394d1ebfa7a8b754ddd9cb97f9e88a0dcfcbcb659193ecbfe5f 与新准确源码证明共同保留。历史 26 项 axe/截图与 22 项 codec 案例依据未变 UI/codec/运行时输入复用。
首次 max CPU 的 AMD 试验保持失败:收据 3543664ba5590f2ba5a8f676b196bb636b72bc819913289f415d0a8a841c1bdb、guest 汇总 16075204d287713c7f7650c0a65dd289dd4bd83db07c9ba4b85b3f21244d5240。测试客户端等待候选响应头 90 秒后超时,候选一致性、其余六项选定 Hugo、原生重构/安装 smoke 未执行。guest 输入保持准确;host 保护只记录 .git 目录时间戳变化。独立 qemu64 单案例 preflight 也在未变更的 90 秒 HTTP 客户端截止时间失败:外层收据 fc68173ccdfd8ce263ecdf082a533d9da666a4cc2e1e5e29880ee827286132ac、guest 汇总 e350ff65feeee166ffac1d337db9bbd70d3895b1fb6d93df0a30ca4de09019fc。具名案例耗时 177.71 秒,首次为 176.64 秒。其输入准确保留、VM 已回收。
随后 Go overlay 诊断保留相同生产源码与全部原语义断言。
外层收据 0bc6d563b7cd9ca862717c2123ee0836d83b6b927d00204a0b031049c38e93f0
与原始记录绑定分类
66a1422cdb79ab9f1cf683f441ade0ce4adb4a7a666d524c4b9ed98ebee28708
记录具名案例在 352.40 秒内通过。原始捕获耗时 26.254 秒、Studio 捕获 26.211、
候选 HTTP 94.312、直接预览 94.318、CLI 预览 81.962。两份图均保留 1,256 个
唯一输入,其中模块输入 1,198 个。HTTP 返回时旧的原始捕获 context 已过期;
独立的新直接/CLI context 正常完成。全部 20,564 项 host 保护与五对 guest 命令
保护准确一致,独占 VM 正常回收。限定归属夹具修改现为该候选请求及独立直接/CLI 操作各提供 300 秒,
约为已观察最慢操作的 3.18 倍。一般/原始捕获 90 秒限制、共享客户端恢复、
15 秒 shutdown 与 Go 默认十分钟上限不变。
消费者 driver 比较 API 与 CLI 的完整类型化诊断、覆盖、原生退出、PlanID、所选 Base/After/完整模式、统一 diff 与所选源码,另外独立验证完整字面 API 审阅及其 哈希。附件和 no-op 的所选页面保持审阅 Base,页面文件编辑匹配审阅 After。 非问题视图为有界样本,所有问题/页面分页读取。53 个保护操作具有 212 次全四站 逐操作源码证明与四次整体证明(四种清单类别共 864 对原始清单),以及 53 对 根清单。绑定 71 份保留产物。没有 Apply、保存计划或消费者写入。已完成语料的 纯文件 collector 因旧试验/self-test 文件不在新目录而保留两次元数据修正;未重跑 CLI/Hugo 操作。
仓库保留 10,462 项已有重复 ID 发现和 788 项审阅信息。所选实际 DFE HTML 可用,
四份过大实际 PRINT 文件保持不服务(413,响应正文零字节):
_print/pkg/index.html 73,976,221 字节、_print/pkg/pgsql/index.html 69,903,999、
zh/_print/pkg/index.html 73,086,240、zh/_print/pkg/pgsql/index.html 69,052,754。
逐文件 64 MiB 预览上限不变:必需局部预览未完成仍为 2,原生发现仍为 1,
Apply 被拒绝。
Linux 前置条件在独占私有 guest 中依据签名 Debian 元数据预备:准确十个新包和 三个获准既有包升级,安装前后均验证。SDK/Hugo/模块缓存独立供应后离线复用。 验收在 ext4 上以普通用户运行,缓存输入及全部 212 个源码文件的字节/完整模式受保护。 默认单元跳过保留实际门控或 不适用原因,所有必需纯测试顶层、无跳过目录成员子项、选定 Hugo 与信号案例必须执行。Linux amd64 在 ARM 主机上通过 QEMU TCG 明确模拟。Darwin amd64 保持实验归档:实际执行 返回 Bad CPU type(errno 86)。Windows 不在声明范围。
当前 Linux 归档摘要为 ac883e54a1df0b820696279c63881ba75a00d279f507330128fe8d5aff59c52e
(arm64,4,552,687 字节)与 2dde43bf94ef35aac2111b07dcb9b2766fbf9f883fe39ccd646d14a98b94d734
(amd64,5,034,668 字节)。此前 683daca0…33f224 的摘要
c191383af21913be6940ec41be11755b3d985344bbc0f65cc3f5de16424a96a4 与
6531b27d889260afe804c1f49f37541fbae46e57b5d17a20178c28cb51968794 保持历史范围。SDK/guest 准备失败、
首次 ext4 成员检查失败及此前私有 host 元数据/resources 试验保持不可变证据。
未启动 E1–E4 是独立非活动范围。
7.8.13 - 文档对照研究,2026-10-29
范围与证据
本次问题是:OINK 如何为约 30–40 个本地站点提供通用对照阅读模式,覆盖译文、八语言集合、校对书籍、任意语言,以及同一语言的新旧版本。研究检查了 Docusaurus、VitePress、Material for MkDocs 与 mike、Read the Docs、Hugo、Sefaria、MediaWiki ContentTranslation、OmegaT、Weblate、Joplin、VS Code 和 CodeMirror。证据来自官方文档与固定源码版本;没有启动上游应用,也没有把仅凭源码检查推导出的行为称为浏览器验证。未固定修订的官方文档仅记录 2026-10-29 当日所读内容,不是不可变源码快照。OmegaT 证据限于 6.1.0 手册,不代表已检查实现。
| 项目 | 固定文档/源码修订 | 已核实的相关性 |
|---|---|---|
| Hugo | v0.168.0 源码 e27a6760c5547237f87d353aebe0580feadb20f4 |
页面关系与原生版本维度轮换(.Rotate),不提供段落对齐。 |
| Docusaurus、VitePress、Material/mike | Docusaurus v3.10.2、VitePress v1.6.4、Material 9.8.0、mike v2.2.0 | 稳定文档 ID、显式锚点、版本元数据与目标存在性感知导航。 |
| Read the Docs 与 VS Code | RTD 9b012b1df15fc153590b4db5f4da7bd3eb8ae74a、Addons d8f99069d9a8b9647f5bd68fdedb95ffa11e63ce、VS Code d7658021df8289f549bf2af8c8dec131f2580d69 |
真实 HTML/Markdown 渲染对照和映射预览;不能证明通用 OINK 阅读器。 |
| Sefaria 与 ContentTranslation | Sefaria af96a1a1695a6307a3d616493455a6802455be1b、ContentTranslation 8d5dde493528330efcb373dc448f352e81815115 |
独立窗格和严格段落行是两种产品。 |
| Joplin、OmegaT、Weblate、CodeMirror | Joplin b48e75856d81152c32e71f21075e1c043d9ea922、OmegaT Standard Manual 6.1.0、Weblate 503129cadb056e2ea0db629fe8e37907b41ff054、CodeMirror 53c8f7b213332743a1f4f7d26bf40cfe2b512b53 |
逻辑滚动映射、人工对齐、稳定消息上下文和几何留白。 |
| DDIA 消费站样本 | Vonng/ddia a6b2e6f85e1249720c8a349273455190d2fc9515 |
四个 Hugo 成员拥有重复中文 locale 和不同版次。 |
身份与关系建模结论
原始的通用对照范围需要区分候选集合身份、成员身份、Hugo site key、实际 locale 与方向、edition/version、规范 URL 以及锚点对应关系。语言代码不是文档身份。固定修订的 DDIA 配置提供了具体案例:zh、tw、v1、v1_tw 是四个配置成员,包含重复的中文 locale 和不同版次。按 locale 去重会删除合法选择,也会把版次错误标成译文。
Hugo 0.168.0 可以作为可靠起点。.AllTranslations 包含当前页,.Translations 排除当前页;显式 translationKey 可以关联路径不同的页面。.Rotate "version" 可以在保持其它维度(包括语言和 role)的前提下适配原生版本维度。这个适配器对对照是可选的,不要求消费站重组内容,也不能与 OINK 的 params.versions 菜单混淆:后者是导航配置,不是语义伙伴解析器。自定义版本或 role 维度仍需显式适配,避免静默接受含义不明的原生匹配。
自动译文候选应先取实际 Hugo 页面关系,再通过资格过滤:已发布、已构建、可渲染的 HTML 兄弟页面;排除当前页面;排除作为语言导航回退的首页;当集合有边界时排除其它 role 或 version。导航链接证明目标地址存在,却不能证明它是译文或可比较文档。缺失译文必须保持缺失,不能在读者不知情时复制源文。
原始的通用设计为八语言集合提供两个选择器,不需要预生成 28 个配对页面,也不需要同时加载八份文档。同语言 v1/v2 对照使用两个成员 ID 和明确标签。同一身份模型也支持章节结构不同的校对书籍,不会把相同文件名或标题文字误认为对应关系。
现有工具实际比较了什么
Docusaurus 的文档身份、本地化与版本管理将 ID、slug、locale 和版本树分开,其标题指南建议本地化页面固定标题 ID。VitePress 的语言 helper按当前相对路径构造语言链接,并支持自定义锚点,但该 helper 没有建立语义伙伴注册表。Material 使用 mike 版本元数据和目标 sitemap 查找对应 URL;这能确认地址可用,不能确认语义等价。这些是身份和校验模式,不是原生双栏滚动证据。
Read the Docs 提供了本次检查中可以直接核对的渲染对照证据:其 visual-diff 文档 和 Addons 的 docdiff.js 对已渲染 HTML 正文进行比较、标记变化的 DOM 区域,并用 URL 状态保存模式。这是面向审阅流程的单页渲染差异。VS Code 的官方 Markdown diff 文档 和 diffScrollSync.ts 展示了两个渲染预览、逻辑映射和反馈抑制,说明渲染对照与映射滚动可组合,但 OINK 的内容身份和静态托管边界仍需自行解决。
Sefaria 明确区分独立滚动的 Compare Text 和共用 segment 的双语阅读:固定源码 TextRange.jsx 把两种文本放进共享 segment,TextColumn.jsx 则让比较列独立滚动。MediaWiki ContentTranslation 的 alignSectionPair 使用稳定 section ID,并在一个编辑流程中对成对翻译区域进行高度与边距补偿。OmegaT 的 Align Files 手册 把自动匹配与人工拆分、合并、移动和接受结合起来。Weblate 的 Unit 模型 将 message ID、上下文和文本 hash 分开保存。CodeMirror 的 updateSpacers 展示了严格行对齐的几何成本。这些证据说明自动相似度可以提出关系建议,却不能为任意多语言 HTML 静默发布段落映射。
三种阅读语义
设计必须命名三种不同的行为。独立滚动 让每个窗格保留自己的位置,是每个有效配对的可靠回退。按锚点联动 保留两个独立窗格,在共同语义锚点之间估算对应进度;它只是邻近章节参考,不保证永久同行,也绝不能称为“逐段齐行”。严格行对齐 把实际块放入成对行,用最大行高和短侧留白,对应关系还需要一对多映射、缺失块占位和明确顺序策略。这是后续独立功能。
对 OINK,默认策略应检查映射质量:满足下述保守条件时才启用锚点联动,否则使用独立滚动。控制条始终允许读者关闭联动。零个或一个有效内部锚点、重复显式目标、缺失显式目标或移动后顺序非单调的配对,都保持独立阅读。普通未匹配章节属于内容缺口,不是无效目标。即使连续同步不可用,仍可提供“跳到对应章节”。
渲染与隔离结论
OINK 的 v1.4.0 Docs 外壳、正文渲染、Book ID 处理、Tabs、表格阅读器、图表和按能力选择的脚本,使任意正文拼入单 DOM 成为较大的重构。两个同源文档 frame 能保留每页自己的 lang、dir、资源基址、渲染上下文、ID 和阅读器所有权。frame 是文档边界,不是独立进程或安全沙箱;同源内容仍然属于受信内容。参见 iframe 参考与同源策略。
最初考虑的路径先在两个同源 frame 中加载普通合资格 HTML 页面,并为导航、键盘快捷键、analytics、评论和递归对照入口增加守卫,再考虑可选的 content-only HTML view。收窄后的第一阶段实现继续使用普通页面;专用输出只有在测量证明值得时才作为未来优化。Hugo 参数本身不能创建 output format;辅助输出需要明确的 output plumbing、Page Store/能力处理、相对资源规则和 SEO 策略。
滚动模型与有界结论
可复用的引擎模式是 Joplin 的逻辑映射和 VS Code 的渲染预览映射。Joplin 的 scrollmap.js 和 VS Code 的 diffScrollSync.ts 支持收集有效布局锚点、在区间内线性插值并限制滚动边界。收窄后的译文 MVP 从共同稳定标题 ID 自动推导严格单调的映射,不需要另行编写或审阅配对映射。锚点交叉或重排就拒绝该映射;引擎不能静默选择 LIS、丢弃移动锚点或重排文档。字体、图片、图表、Tabs、details 或宽度改变布局时,几何必须失效。状态 owner 与 sequence token 防止程序滚动事件回传。用户应保留焦点和窗格所有权;普通滚动不能打开隐藏内容或移动焦点。
普通未匹配章节在页面和两侧包围它的锚点都有效时属于内容缺口;如果缺口被有效锚点夹住,阅读器可以标明这是近似区间并进行插值。在未来的人工映射功能中,显式目标不存在、重复或与另一个显式目标交叉,则属于无效映射;这会关闭该配对的连续同步,而不是回退到序号匹配。显式跳转仍可报告具体的无效目标。
这些证据支持一份聚焦提案,却不构成实现声明。这轮上游源码调查没有运行候选对照功能、服务器、浏览器、无障碍检查或八语言消费站,也没有证明每个框架都不存在插件或实验性对照模式。这些限制会进入提案的验收矩阵,推荐也因此保持克制:先验证普通 Docs/Book 双栏阅读,再决定更复杂输出或严格行对照是否值得维护成本。
第一阶段范围修正
根据 2026-10-29 的简化要求,配套提案现在假定已有翻译兄弟页和相同标题 ID。
唯一站点设置是 params.ui.compare: true。Hugo 提供现有翻译链接,浏览器选择一个
目标,用两个同源 frame 显示普通页面,并提供同步滚动开关。当前语言固定,仅目标
语言需要选择器。
上文较广泛的身份讨论保留为未来版本对照的研究。第一阶段无需 compare set、member ID、人工锚点映射、CLI、新布局、新输出或预生成语言配对。Frame 加载普通页面就是 推荐的首版实现,不代表后续必须增加正文专用输出。原宿主页保留在阅读面之后,因此 两个子文档加上宿主页共占三个文档;先测量这个成本,再决定是否增加渲染路径。
来源链接
7.8.14 - 2026-09-29 CLI 验收快照
本文记录 2026-09-29 对本地 0.1.0-dev CLI 候选(提交 e623d93)的验收检查。当前 CLI 行为以 CLI 与结果契约为准。
输入与方法
CLI 位于独立的 oink-cli Go 仓库。已接受的边界见
CLI 与结果契约,可复现的用户步骤见
CLI 简介。Hugo 继续作为外部渲染器,生成站点保留普通
Hugo 输入。
| 输入 | 观察到的基线 |
|---|---|
| 主机 | macOS,darwin/arm64 |
| Go | go1.27.1 |
| Hugo | 0.166.0+extended+withdeploy |
| CLI | 0.1.0-dev,本地提交 e623d93d589c49e5c58b8fae1bd5db720fc904cb |
| 内嵌 Starter | 提交 137843b25bacd76ddd1f7ce71330bf2e3155b954,完整、保留许可证的 Git 归档 |
| 生成站点的主题 pin | 公开 github.com/pgsty/oink v1.1.0,使用已记录的 Go 校验和 |
| 文档站主题 | 本地主题 HEAD b0af631 加未提交修改 |
Starter 归档哈希为
e55bde279715f6d8d19d3d88671a2cf7561b515be46915b0f12c640d0ce1d958。
已记录的投影包括选择已有语言配置、固定 OINK v1.1.0,以及为新目录设置
enableGitInfo: false。最后一项来自真实故障:原有 enableGitInfo: true 会让
尚无第一次 Git 提交的站点在严格构建中因警告而失败。没有通过创建 Git 仓库或提交
掩盖这一问题。
检查使用临时源副本、模块及渲染缓存、输出目录。CLI 检查没有写入原始 Starter 或 消费站源码,并保留了主题与文档已有的无关修改。下列数量是 2026-09-29 对应输入与 CLI 修订的快照。
Starter 与普通 Hugo
预备模块并隔离缓存后,六组普通 Hugo 用例均通过
--environment production --panicOnWarning:
| 语言配置 | 根 URL | /manual/ 子路径 |
Hugo 报告的页面数量 |
|---|---|---|---|
en |
通过 | 通过 | EN 90 |
en,zh |
通过 | 通过 | EN 91、ZH 89 |
all |
通过 | 通过 | EN 91、ZH 89、FR 89 |
测试检查了预期语言根与代表性 Docs、Blog、Book 产物,并比对 Hugo 构建前后的
生成源码字节。公共 CLI 的 init 命令还分别通过了三种语言配置验证,每组均无诊断,
生成 94 个源文件。三种配置的区别在于选定的根配置;其他语言示例仍保留在快照中,
通过既有语言配置禁用。
Starter 包的单元测试、race 与 vet 检查通过。失败用例覆盖非空及符号链接目标、候选 验证失败、计划后目标替换、取消回滚、并发修改或删除,以及归档路径拒绝。重生成脚本 精确复现了固定归档、来源清单与许可证。
真实站点检查快照
下列每次运行均返回 CLI 退出码 0,零诊断。
| 站点形态与主题来源 | 文件 | HTML 文件 | 引用 | 机器产物 |
|---|---|---|---|---|
三语 Starter,公开 v1.1.0,在 /manual/ 做发布检查 |
316 | 142 | 7,042 | 6 |
OINK 文档与回归站,本地主题 HEAD b0af631 加未提交修改 |
1,127 | 506 | 72,562 | 8 |
| PIG 项目站,根 Docs/Blog 路由重写,公开 v1.1.0 | 1,392 | 424 | 64,440 | 4 |
| 仓库文档与生成式目录,公开 v1.1.0 | 3,287 | 1,635 | 851,535 | 12 |
后三项是本地消费站仓库:PIG 与目录站验证公开 pin 的解析,OINK 文档站验证本地主题 checkout。
检查覆盖 HTML 链接、锚点、资源及已输出的机器产物。最终 Hugo 清单 分别枚举了 261、766、662、3,192 项输出声明,按实际语言和 URL 要求每个受支持且 已启用的机器输出。前后清单逐项比对 tracked 与未被忽略的 untracked 源文件字节、 模式和 Git 状态:四站全部未变,分别覆盖 94、415、858、2,294 个源文件。
本轮修复了两项真实回归。仅生成英文的 NAVJSON 模板原本会掩盖中文产物缺失,
现在会返回政策退出码 1 并给出产物位置。PIG 有意使用的 build.render: link
侧栏项最初被误认为缺失页面,现在依据 Hugo 的生效参数排除,并有直接声明与
cascade 继承回归测试。双语 Starter 的普通构建与探针构建对照还证明,全部 223 个
原有产物字节完全一致。
离线执行与升级
macOS 上,在依赖齐备后,已初始化双语 Starter 的全站 check 在
sandbox-exec 的 (deny network*) 限制下通过。结果为退出码 0、零诊断、
223 个文件、95 个 HTML 文件、4,461 条引用、4 个机器产物。另一次英语 init
也在相同操作系统网络禁止条件下通过,返回退出码 0、零诊断,并生成预期的 94 个
文件。
一个依赖 example.invalid/[email protected] 的冷缓存夹具返回 CLI 退出码
2,并保留 Hugo 原始的 module lookup disabled by GOPROXY=off 证据。
依赖缺失因此被报告为必要工作未完成,没有静默启用联网解析。
另一个临时站点执行了真实公开模块从 v1.0.0 到 v1.1.0 的升级,原始消费站没有作为 写入目标:
| 操作 | 观察结果 |
|---|---|
| 预览 | 退出码 0;候选验证通过;applied: false;计划只包含 go.mod 与 go.sum |
--write --expect-plan |
退出码 0;匹配的计划验证通过并应用 |
| 重复同一目标版本 | 退出码 0;候选验证通过;没有待修改内容,applied: false |
无关的已修改 README.md 和未跟踪的 user-note.txt 在三次操作后均保留。预览与写入具有相同计划 ID,以及相同
模块文件前后哈希。replacement、workspace、脏目标文件、回滚及失败保护场景通过了最终聚焦
Go 测试与 race 检查。写入只改变 go.mod、go.sum,备份清单保留原始字节;
预览与重复执行保留全部源码字节。
另在临时初始化站点运行了真实薄包装验收:build --json 返回 0 并生成
index.html;dev --json 提供 HTTP 200,将 SIGINT 转发给 Hugo,并关闭监听。
Hugo 返回 0,被取消的包装进程按约定返回 2。这些运行使用预备缓存,未启用
--network。
最终 make test(全部包与 vet)、make test-hugo(普通 Hugo、workspace/配置
优先级、输出探针及语言缺失回归)和 go test -race ./... 全部通过。三种公开
init 配置均在操作系统禁止网络的条件下重跑通过,冷依赖夹具再次返回 2。
归档与安装准备
一个冻结的 CLI 源码快照生成了四份二进制归档、一份源码归档,以及 SHA256SUMS。
从源码归档独立重建后,全部五份归档的 SHA-256 均一致。此次打包验证的输入哈希为:
最终快照替代中途打包实验。全部五份归档的校验和均已核对,并从解压后的源码归档
精确复现。源码与二进制归档均包含版本化 JSON Schema、许可证、依赖 pin 和 Starter
来源记录。make install 安装到临时前缀及安装后二进制的 --version 检查通过。
| 目标 | 证据 |
|---|---|
darwin/arm64 |
已编译、执行本机二进制,并验证本地安装路径 |
darwin/amd64 |
仅交叉编译,未在该架构执行 |
linux/amd64 |
仅交叉编译,未在 Linux 执行 |
linux/arm64 |
仅交叉编译,未在 Linux 执行 |
归档构建器记录工具链、参数、源码输入哈希与目标平台。
复现相关检查
在依赖已经预备的 CLI checkout 中执行:
按同级目录布局复现站点产物检查时,将 JSON 与日志保存在各消费站源码之外:
在提供 sandbox-exec 的 macOS 主机上,初始化双语站点之后执行:
命令契约说明预览、计划审查与显式应用步骤。 写入路径测试应使用单独的审查副本。归档实验需保持 Go 工具链和发布版本一致:
限制与交付状态
| 状态 | 本快照中的情况 |
|---|---|
| 本地实现 | 六个首期命令与版本化结果格式已存在 |
| 已执行验证 | 上述运行针对其记录的输入通过 |
归属检查与文档站 make check |
实现与双语文档更新后通过 |
| 提交、标签、推送 | CLI 已本地提交 e623d93;没有标签、remote 或推送。文档修改与既有工作一起保留在本地 |
| CLI 公开发布或分发 | 未执行 |
| 消费站源码采用或生产部署 | 本次检查未执行 |
| 独立用户研究或采用 | 没有已测量的“五人中四人/15 分钟”研究、留存或独立团队采用数据 |
原始 JSON、日志、源码保留清单、升级恢复证据与归档核验结果保存在 CLI checkout
已忽略的 tmp/acceptance/ 目录下,归档文件位于 dist/first/。
首期候选聚焦现有 OINK 站点;Docsy 评估及后续迁移、主题能力描述、版本生命周期、 OpenAPI、MCP 与 Studio 继续在路线图中跟踪。
7.8.15 - 组件精修验证,2026-10-25
本轮组件修改的 第 0–3 阶段与第 5 阶段最终验收 已完成。滑动选中背景试验继续暂缓。
输入与方法
最终评审使用隔离的同级 checkout,基于主题 7f9589f 和文档 855d890,只叠加本任务的源码修改。并行进行的 PlantUML、工具链和工作流修改未混入验收候选,并保留在共享工作区。已验收的实现对应主题提交 15c39ef;指南、契约、示例和浏览器覆盖对应文档提交 81e2be9。
验证环境为 macOS ARM64、Hugo Extended 0.168.0 与内置 LibSass、Go 1.27.3、Node 26.12.0、npm 11.20.0。浏览器预览使用隔离的同级主题和 hugo.yml,tests/fixtures/browser.yml,关闭两种 workspace 机制、文件监听和自动刷新。截图来自真实文档输出,可复用示例归属 双语对照指南。
已执行验证
| 检查 | 结果与范围 |
|---|---|
| 主题组件归属检查 | SCSS/编译器钩子、字体、预设、内容原语、组件、代码块与 Landing 均通过。不支持的变体和不适用的命令条会告警并保留内容。 |
| 主题外壳与输出检查 | 外壳、动作、命令面板、键盘、运行时隔离与输出检查通过。输出安全检查覆盖 501 个 fixture 文件,显式允许 fixture 原有的第三方嵌入。 |
| 真实站点 | make check 的 64 项非浏览器测试全部通过,包括双语标题、Markdown 输出和内部链接。站点 Markdown 快照的变化来自已核对的导航与反链新增。 |
| 完整浏览器套件 | make browser 的 409 项 Chromium 测试通过。最后修正无 JavaScript 时的空命令操作列后,又在新构建上通过全部 20 项呈现测试及 SCSS 检查。 |
| 呈现覆盖 | 四种预设 × 明暗 × 中英双语,1440/768/390/320px 布局,原文复制,原生与完整卡片独立链接,原生折叠,无 JavaScript,打印,触摸目标,减少动态效果,强制颜色,以及 Landing 反馈宽度与对比度。 |
| 补充引擎复核 | Chromium 151.0.7922.34、Firefox 153.0、WebKit 26.5 通过 48 组预设/明暗/语言组合,覆盖 Tabs、Fields、卡片链接及 1440/390/320px 布局。最终的无 JavaScript 命令条也在三个引擎中通过。 |
| 视觉复查 | 查看 Paper、Slate、Ink、Terminal 的真实卡片、折叠块、标签页、命令条和参数名,覆盖明暗与桌面/移动端输出。待视口稳定后截图,确认各预设的几何特征和阅读层级保留。 |
| 生产构建 | 最终候选的压缩生产构建通过。下文资源测量读取生成页面实际引用的 CSS,前后两次构建使用相同文档源码。 |
完整套件的 A11Y_PATHS 覆盖中英双语的组件呈现、卡片、提示块、标签页、代码、字段,以及英文徽章和按键,共 14 条路由,另有原响应式无障碍矩阵。呈现 axe 检查扫描 390px 下的文章区域;Landing 检查扫描全部八种预设/明暗组合的命令框。Giscus 请求沿用现有套件做法予以阻止。
资源代价
导出的主题基线与最终候选使用相同文档源码和生产构建模式:
| 资源 | 修改前 | 修改后 | 差异 |
|---|---|---|---|
| 压缩后的主 CSS | 528,665 字节 | 539,450 字节 | +10,785 字节 |
| 主 CSS,gzip | 79,541 字节 | 80,887 字节 | +1,346 字节 |
| 生成的 JavaScript | 39 个文件 | 哈希相同 | 0 字节 |
最终测量替代早期精修候选的 539,437 字节/80,856 字节记录。未新增框架、字体、远程资源或 JavaScript 依赖。
Tab 评审修订
维护者不接受凸起的选中标签。最终样式让单个标签平时背景透明,仅在支持悬停的设备上显示淡底色,以链接色和 2px 底边线标识选中。文字保持相同的中等字重和位置;键盘焦点另有轮廓,粗指针设备的点击目标高度仍至少为 44px。最终引擎复核检查了这些状态、方向键切换和窄屏布局。
参数名与最终修正
Terminal 参数名现在与其他预设一致,使用无边框的加重等宽文字。原生 Markdown 表格和完整 Fields 短代码均检查了中英双语与明暗输出。正文中的普通行内代码与按键边框保留各自的表达作用。
最终评审还复现并修正了两个边界问题:
- 原生卡片的整卡标题链接会拦截嵌在强调文字中的次级链接。现在次级控件保持在整卡目标之上,指针检查确认嵌套链接和卡片其余区域均跳转正确。标题的键盘焦点轮廓沿用卡片自身形状。
- 禁用 JavaScript 后,命令条仍保留一小列空白操作区域。现在复制控件可用之前,其包装容器保持隐藏。在三个引擎中,390px 样例的源码列均占满 356px 内部宽度,启用脚本后的复制控件仍正常可用。
基线失败与限制
以下两项主题检查在导出的基线和组件候选中完全相同地失败:
check-namespace.py:已提交的博客画廊使用无前缀的data-height、data-width、data-mode、data-size,并读取对应的无前缀 dataset 属性。check-goldens.py:现有主题快照尚未包含已提交画廊的首屏初始化条件、工具栏图标与画廊标记。用检查器自身的规范化规则对比基线和候选,全部 52 个快照表面均无新增差异。
组件检查器另有一处定向修正:按配置键识别已移除功能,避免把任何 gallery 字样都当作旧功能,允许合法的 blog_gallery 配置。
本地验收覆盖本轮组件修改和两项最终修正。Ink 与 Terminal 保持实验状态。运行时 JavaScript 未修改。
7.8.16 - 审查修复,2026-11-06
本文记录 v1.5.0 之后的本地修改,不代表新版本已发布、消费者已升级或站点已部署。原报告约 170 项意见并未全部确认或修复。
输入与方法
本次审查以主题 84b93e4 与文档站 a8eeb92 为起点。候选修改保留 Hugo
Extended 0.168.0 与内置 LibSass;本机环境为 macOS ARM64、Go 1.27.3、Node
26.12.0、npm 11.20.0、Python 3.14.8。普通系统路径上的 Hugo 0.169.0 不属于
本轮验证基线,它产生的快照差异不能直接证明 0.168.0 上存在缺陷。
构建与输出判断使用主题归属检查器中的合成输入验证;交互判断使用同级文档站
的 Playwright 回归验证。浏览器预览采用本地主题替换及
hugo.yml,tests/fixtures/browser.yml,关闭两种 workspace 机制、文件监听和
自动刷新。记录有意的输出变化前,先审核已有快照的差异。
已确认的缺陷与修复
| 领域 | 已确认行为与修复 | 可复现证据 |
|---|---|---|
| 无效输入 | 位置参数进入仅支持 map 的短代码操作,导致构建失败。现在先保护命名参数,警告后安全省略或回退;负例必须以零退出码结束。 | check-content-primitives.py、check-book.py |
| Book 编号 | 无效编号生成 fig- 等空目标。现在保留可读的无编号内容,不注册目标。 |
check-book.py:HTML、打印、Markdown、BookManifest 和索引条目,并用有效编号作对照 |
| 嵌套组件 | 独立 Markdown 文档与同级短代码容器重复使用局部序号。自动 ID 及引用现在包含所属容器作用域;作者显式指定的代码 ID 保留。 | check-code-blocks.py:重复嵌套代码、字段、图表与 FileTree 的 HTML/打印输出 |
| Feed | 按权重排列的页面先被截断,随后才处理日期;无日期条目输出公元元年;部分组件输出原始 Markdown。两种 Feed 现在先排序再限量,省略缺失日期,组件使用静态 HTML 或约定的省略行为。 | check-content-primitives.py、check-book.py、RSS 快照 |
| 引用与版本 | 缺失手册引用可能让 Hugo 失败,版本链接可能重复部署前缀。共享解析器现在提供警告回退,并只拼接一次版本路径。 | check-agent-indexes.py、check-actions.py |
| 子路径导航 | 语言中立的导航 ID 与分区索引查找键保留了部署前缀。共享解析器现在移除部署及语言前缀,对外链接仍保留两个前缀。 | check-agent-indexes.py:根路径/子路径、中英双语,以及默认语言有/无独立目录的组合 |
| Landing | 错误记录结构与可选字段可能破坏 Markdown 输出。HTML 与 Markdown 现在共享记录及 URL 校验,保留片段与查询参数,补齐单独 CTA 链接;危险目标不会变成空链接。 | check-landing.py |
| 博客导航 | 列表页继承文章阅读顺序的导航,并使用英文分页。现在由本地化的语义分页统一管理列表导航与 head 链接。 | check-reading.py、check-shell.py、浏览器分页回归 |
| 焦点与菜单 | 原生禁用会移除控件焦点;隐藏锚点无法到达;由焦点打开的菜单在 Escape 后重新打开。带操作保护的 aria-disabled 控件保留焦点,锚点可达,Escape 先恢复焦点再关闭菜单。 |
JS 单测及浏览器代码块、外壳、对照视图和 Expo 套件 |
| 移动外壳 | 仅有导航栏的文章缺少移动工具入口,关闭的侧栏抽屉泄漏阴影。站点抽屉现在提供语言、版本和仓库链接;阴影仅在打开时出现。 | check-shell.py、响应式浏览器回归 |
| 布局 | Book 目录重复编号,正文间距污染组件列表,Landing 内边距被覆盖,全局链接规则压掉打印下划线。现在收窄各自规则的作用范围。 | 浏览器审查回归及 SCSS 检查 |
| 语言与 schema | 分页及面包屑缺少翻译,部分语言文案与外观分组标题有误,schema 拒绝语言代码形式的译文提示。现在全部 32 种语言包含相应标签,生成的 schema 与解析器一致。 | check-i18n.py、generate-config-schema.py --check |
| 预设及替代呈现 | Terminal 当前行箭头选择器错误,焦点环半透明或被裁切,强制颜色丢失当前项标记,RTL 样式覆盖字体角色。定向规则现在保留这些状态;Marquee 重复行对辅助技术隐藏。 | 预设/SCSS 检查及浏览器无障碍覆盖 |
原审查需要限定的判断
原报告的优先级将构建失败、无障碍缺陷、文案和设计偏好放在同一组,不能全部 视为阻塞发布的问题。可复现的构建与输出缺陷应优先修复;外观一致性需要另行 确定验收标准。
“没有 TOC”不等于“右栏为空”。博客索引仍可能包含作者、分类、系列和标签云。 直接删除备用控件会让右栏无法收起,并使既有 RSS 浏览器回归失败。最终修复 只省略真正空的右栏;有内容但无 TOC 的右栏保留控件,标签改为侧栏操作,保留 收起、焦点恢复与 RSS 访问。
RSS 修复不要求每种组件都出现在 Feed 中。发布资产仍明确省略。Book 检查器
现在验证真实的分区 Feed,不再将原始 RenderShortcodes 输出当作 RSS 契约。
圆角不同、外壳模板重复、原生与短代码布局有差异,在确认具体契约或使用故障前, 属于维护或设计议题。仅因文件逐字节相同而合并 base 模板,可能改变 Hugo 模板 查找优先级。本轮不包含这类大范围合并。
验证
主题归属检查器、schema 一致性、32 种语言键一致性、80 个 JavaScript 单测和 90 个迁移单测均使用固定工具链执行。输出变化覆盖 52 个 HTML、打印、Markdown、 RSS 与 LLMS 快照。它们属于本地单元与输出证据,不宣称执行了独立 CI 的 PDF/EPUB 出版任务。
文档站最后一轮完整 make check 已通过,包含全部 64 个非浏览器测试;渲染链接
扫描使用了更大的 Node 堆。本地生产构建也已通过,每种语言生成 462 页,只有
约定接受的上游 LibSass 弃用警告。首轮浏览器验证完成全部 30 个无障碍测试;
保留分类右栏控件后,响应式分组的 117 个测试
通过。其余标准浏览器分组也分别通过:键盘 16、内容组件 70、代码块与 Landing
37、PRD5 8、主题颜色 10、社区反馈 42、外观 53、Expo 50、画廊 8、PlantUML
8、图表 19、表格查看器 15、标准对照 19,以及八语言/RTL 对照 6。标准浏览器
套件在这些运行中合计通过 508 个用例。后续针对新研究页及博客页的无障碍检查
通过全部 30 个用例。Escape 修复后,新预览上的外壳、博客与菜单回归为 56/56;
metrics 链接接入统一 URL 策略后的 Landing 检查也已通过。
上述浏览器计数对应本轮修复候选版本;共享工作树随后出现的独立对照界面改动 不包含在本记录的浏览器验收范围中。完成审计另行验证了语言及部署前缀下的导航 查找键,以及 Book 无效编号在 HTML、打印、Markdown、BookManifest 和生成索引 中的回退行为。
剩余工作与顺序
本轮是有范围的修复,以下事项尚未纳入验收:
- 单字符快捷键的读者开关、命令面板交接与错误信息本地化,以及贡献者、搜索、 代码行数的多语言复数规则。
- 非默认语言 Google 搜索目的地,以及内容相对 URL 的文档语义。
- 其余对比度用例与历史记录中的窄屏、大字号页脚溢出,需要各自的渲染复现和 浏览器检查。
- 几何、交互状态及弹层令牌、其余手册链接实现、共享外壳结构和样式表体积。 这些需要有范围的设计决策与测量,不适合一次性替换所有字面量。
7.8.17 - Ink 与 Terminal 仍需阅读验收
本文保留 OINK 1.4.0 发布准备前的输入与测量。在 320px 视口、32px 根字号/默认字号下,页脚链接使全部四个预设出现整页溢出。Ink 与 Terminal 仍为实验预设;稳定化提案规定目标行为与晋升门禁。
Ink 应保持中性无衬线编辑风格,Terminal 应成为易读的工程手册。 两者可以通过既有字体角色和共用外壳形成连贯的官方风格。新增衬线身份会削弱 Ink 与未来 Folio 的区别,模拟终端则会削弱工程长文阅读。近期工作应聚焦共用文字布局韧性、克制的层级和组件一致性。
证据对应一个明确的本地快照
本次 2026-10-26 评审结合了一手文字/HTML/CSS 资料、导出的源码快照、真实文档渲染和有限的注入 CSS 对比。主题 HEAD 为 7bc8658bc1be13ef0ad3050b9d9f6862f8f1f65f,文档 HEAD 为 99751179eefada3fa7a91318c792ddac46d7e6bf。快照还包含未提交修改(含并行表格工作),保留的源码清单记录了完整状态。公开标签查询观察到 v1.3.2,解引用为 bd2e856e6e2f7027a9189af009701f5b8cf562cf;文档固定 v1.3.2。历史背景见 早期实验 与 组件评审。
文档服务器使用同级快照及 hugo.yml,tests/fixtures/browser.yml,关闭两种工作区机制,采用内存渲染,不监听、不注入实时刷新。由于导出目录没有 Git 元数据,设置 enableGitInfo=false。工具为 macOS ARM64、Hugo Extended 0.168.0、Go 1.27.3、Node 26.12.0、npm 11.20.0 与 Playwright 1.62.1;引擎为 Chromium 151.0.7922.34、Firefox 153.0、WebKit 26.5(Playwright WebKit 范围)。
复现方法是在各宽度加载相同真实路由、语言、模式与预设,比较基线及所列覆盖样式,记录整页尺寸和计算样式,再截取相关组件。源码清单、review-metrics.json、api-supplement.json、text-resize-findings.json、stress-metrics.json、reference-rendering.json 及截图保留在 Git 外。
整页溢出优先于专项检查通过
在 320px/32px 根字号下,英文 Ink 的 scrollWidth=424(超出 104px),Terminal 为 408(超出 88px)。中文 Chromium 分别超出 12px/21px;英文 Terminal 在 768px 下也超出 11px。覆盖样式产生相同缺陷。Firefox 的真实 font.size.variable.x-western=32 偏好复现了全部四个预设的英文溢出;Paper 达到 413px,Slate 约 400px。“Customization”“Components”“Docsy discussions”等页脚链接超出了两列窄网格。这是全部预设共有的 基线阻塞项。
这些探测使用 CSS 根字号放大和原生默认字号偏好。320px 与放大文字叠加,比独立重排要求更严格。完整的手动 200% 文字放大和 400% 浏览器缩放仍待完成(文字放大、重排)。
归属检查:check-presets.py 通过三个界面的文字/链接/强调色、深浅模式一致性、冻结 Slate 与配置输出检查;check-font-tokens.py 通过。
既有套件:28 项外观引擎、文字重排及组件测试通过;其页面上下文/顶部断言未覆盖页脚。加入文档前,源码快照的 make check 通过 64 项非浏览器测试。加入文档后,站点构建、Markdown、双语标题与站内链接检查通过;64 项非浏览器测试中 63 项通过,剩余 llms.txt golden 仅多出两份新英文文档的条目。
主要测量矩阵:Chromium:11 条有效路由 × 两个预设 × 两种模式 × 中英文 × 320/390/768/1440px × 基线/覆盖样式。Firefox/WebKit 仅覆盖 Presentation/Configuration/Book,宽度 320/1440px。正常字号未观察到整页溢出。
计数校正:初始账本的 960 项是计算状态观察。错误 API 路由造成 32 次测试工具 404;纠正后的 /docs/write/openapi/ 补充了 64 项有效观察,均为 HTTP 200 且无整页溢出。
补充压力探测:64 项同时覆盖间距的观察使用行高 1.5、段落 margin 2em、字距 .12em、词距 .16em,未观察到整页/标题溢出。16 项 320px 连续长标题/Field 标签探测保留了文字,未出现整页溢出。
axe:32 次 390px 正文扫描覆盖 Presentation/Configuration、两种语言/模式/预设及基线/覆盖样式。未报告违规,仅在包含非文字字符且带 aria-hidden=true 的翻页箭头 span 上留有对比度待复核项,需人工核对图形对比度。
视觉材料:归档含 352 张对比图,包括 Blog/Expo/Mermaid/ECharts 场景,另有 8 张外部参考图,从中抽检了代表性桌面/移动、深浅色页面与控件。
长文阅读:真实中英文 /blog/oink/oink-implementation-diary/ 长文检查四组条件:Ink 英文/1440/浅色、Ink 中文/390/深色、Terminal 英文/390/浅色、Terminal 中文/1440/深色。基线/覆盖样式各取顶部/中段/页脚,共新增 24 张图片;正常字号的整页溢出均为零。检查了 8 张代表图,包括英文正文前后对比、中文正文、Terminal 英文手机页脚及 Ink 英文桌面开头。
Chromium 路由包括首页、Presentation、Configuration、Fields、Code、Table、Book 结构、沉浸阅读 Blog、Expo studies、Mermaid、ECharts,以及纠正后的 OpenAPI 指南,并覆盖中文对页。后续截图还验证了首个 Mermaid 渲染图与 ECharts canvas。对比画廊有 11 个场景,其中 7 个控件场景覆盖两个预设、两种模式、两种语言与两种宽度。主要矩阵阻止了包括 Giscus 在内的站外请求。
覆盖样式只验证有限的编辑调整
Ink 保留配色/字体,将正文 h1 从 800/-.035em 改为 750/-.025em,h2/h3 从 750/-.025em 改为 700/-.012em,将 2px 强调色顶线改为既有强线条角色的 1px 线。取消次级/表格/提示块标题的强制大写与字距,中文标题字距归零;原生下划线比较使用 max(1px,.06em)/.22em,中文另比较 .25em 与 skip-ink: none。
Terminal 保留青绿/薄荷色及琥珀色,标题使用 600 字重/零字距,移除生成的标题前缀与提示块括号,将描述性表头改为正文 0.875rem/600、搜索标题/摘要改为正文角色,并将装饰性代码/Tabs/表格/提示块边框降为既有边框角色。技术路径/控件仍为等宽,Field 名称仍无框。此前漏入预设形状规则的 FileTree 固定 0.6rem 圆角,改为 Ink 0px/Terminal 2px。扁平 Tabs、移动目标及 vendor 边界保持不变。覆盖实验仅验证 CSS 样式调整,未新增主题模板、字体、运行时或配置。
真实页面的作者强调色为文档蓝色 #245f94/#5da2dd、Book 橙色 #9a3412/#fb923c、Blog 紫色 #6d28d9/#a78bfa。单独标注的配色探针在两个变体上仅把作者强调色重置为预设默认值,用于受控对照。架构契约 保留覆盖优先级与共用外壳。
配色计算揭示状态风险
不透明 sRGB 配对与 alpha 合成计算使用源码 token 和 WCAG 亮度公式;展示比值经过四舍五入,是否通过则使用未舍入值(对比度方法、配色源码)。
| 配对,浅色/深色 | 计算对比度 | 含义 |
|---|---|---|
Ink 强调色 #c8102e/白色,#ff5c4d/#0b0b0b |
5.88 / 6.46 | 保留分模式红色;控制红色装饰线的出现频率,保持重点突出。 |
| Terminal 正文/画布 | 14.88 / 13.64 | 中性阅读配对具有充足对比度。 |
| Terminal 链接/画布及次级界面 | 6.31 / 9.54;5.75 / 8.73 | 既有青绿/薄荷色适用于这些具体界面。 |
| Terminal 第三级文字/次级界面 | 4.63 / 5.93 | 浅色弱化文字继续加色或降低不透明度的余量较小。 |
| Terminal 琥珀色/画布 | 5.47 / 9.44 | 琥珀色继续承担独立强调角色。 |
| Terminal alpha 32% 边框/画布 | 1.97 / 2.36 | 适合装饰性分隔线;不足以单独标识必要控件或状态。 |
| Terminal alpha 55% 强线条/画布 | 3.63 / 4.73 | 可作为这些画布上的功能边界候选。 |
白色/深色模式薄荷色 #4cc9bd |
2.02 | 避免在浅薄荷色填充上使用白色文字标签。 |
跨模式复用不满足普通文字对比度:薄荷色/浅色画布为 1.85,深青绿/深色画布为 2.79。语义色、作者强调色、语法注释、选中搜索摘要、焦点与文字选择合成色仍需逐状态评审。非文字对比度针对必要的识别边界(WCAG 1.4.11)。
成熟先例支持角色分工,不支持照搬
所有外部参考访问日期均为 2026-10-26。外部 Chromium 在 1440/390px 渲染 Tufte、GOV.UK Headings、Effective Go 和 A List Apart,均返回 HTTP 200。保存了 8 张截图,重点检查了 Tufte 桌面、GOV.UK 移动、Go 移动、A List Apart 桌面 视图;其余先例通过源码审查。
Practical Typography。优先处理正文,提出面向拉丁文字的字号、间距与行长建议;其 下划线观点 反对链接下划线。
先审查完整段落,再调整展示标题,同时保留 OINK 的双语排版节奏与开源字体约束。
Tufte CSS。已检查的桌面视图具有窄衬线阅读栏和下划线。CSS 声明 ET Book、浅色 #fffff8/#111、深色 #151515/#ddd 及移动端注释开关。
借鉴解释与插图的邻近关系及注释层级;衬线与边注布局留待后续 Folio 探索。
GOV.UK Headings。已检查的移动视图有充足的无衬线标题与下划线链接,Cookie 横幅可见。当前 字号阶梯 在小屏/大屏均使用 19px/25px 正文。链接 区分正文与导航,表格 强调结构。
对 Ink 无衬线层级及持续链接提示最有直接参考价值,并适配 OINK 的双语字号与深色模式。
A List Apart:A Dao of Web Design。已检查的桌面视图区分衬线正文与无衬线标题/控件。当前 CSS 使用 Georgia 1.125rem/1.7、红色 #ba3925 h2 与蓝色链接。
分离阅读/界面/代码角色并保持内容适应性,同时维持 Ink 的无衬线定位和克制的红色强调频率。
Rust Book:Ownership。源码呈现编号章节、解释与代码;CSS 分离 Open Sans 正文与等宽代码。
是 Terminal 区分解释/语法及保留真实章节编号的重要先例,同时保留 OINK 的等宽标题与标准浏览器快捷键。
Effective Go。源码/CSS 分离平台无衬线与等宽;已检查的移动目录密集且为两列。
借鉴直接的技术层级,同时保留 OINK 自身的响应式目录。
man7 open(2)。稳定的参考分区、签名、链接及预格式化文字;(2) 表示手册分区。
保留可预测的查阅结构与完整标识符,不把双语正文排成固定终端字符格。
CLI Guidelines。有导航结构的解释性网页指南,建议有意且有限地使用颜色。
借鉴克制与清晰标签,同时保持标准网页交互语义。
Paper 的温暖通用阅读、Slate 的既有技术展示、Ink 的中性编辑克制、Terminal 的技术角色对比,可以在同一外壳中保持鲜明区分。
语言与无障碍需要独立证据
macOS 上的 Chromium 逐字形检查确认:Ink 拉丁文字使用 Inter,Terminal 正文使用 IBM Plex Sans,标题使用 IBM Plex Mono,两者中文均使用 PingFang SC。Windows 与 Android 的回退字体表现仍需在真实设备上核验(CSS 字体匹配、Windows 字体、Android 回退)。
中文下划线与字形的间距仍待定稿。CSS Text Decoration 工作草案解释了汉字碰线与跳墨碎裂问题;CLReq 工作组说明草案讨论行间标记,但没有禁止超链接下划线。后续应比较按语言限定的 text-underline-position: under 加小偏移,先使用 skip-ink: auto,覆盖英文页内中文和混排跨行链接(CSS 草案、CLReq)。
WCAG A/AA 规定非颜色语义、文字/非文字对比度、键盘操作、放大、重排、间距、语言及状态播报。OINK 拟议的 44px 常用触控目标、7:1 正文及更强的 2px 面积焦点处理,作为高于 AA 下限的项目目标单独追踪(WCAG 2.2、axe-core、焦点外观)。
仍待完成:真实 Windows/Android 的字体加载/阻止/系统字体对比,品牌版 Safari/iOS 渲染,人工 NVDA、VoiceOver、TalkBack 任务,完整手动 200%/400% 缩放,以及覆盖导航、Book 编号、表格关系、字段错误、中文搜索输入、状态反馈及对话框返回焦点的全面键盘、强制颜色和状态对比度审查。
稳定化应以完整阅读验收为前提
页脚溢出表明,除了正文顶部检查外,还必须补齐整页回归测试。因此共用修复与整页回归应先于预设升级。FileTree 的圆角遗漏和 Terminal 装饰标点竞争属于可以在现有模块内收敛的调整。
1.4 应精修既有实现,待门禁关闭后分别决定 Ink 或 Terminal 是否升级;否则保持实验状态。Folio(学术衬线)与 Canvas(创作者几何风格)继续暂缓,避免在本周期引入额外的字体许可与 QA 成本。
7.8.18 - OINK 1.5.1 发布就绪审查,2026-11-06
下述三个已复现的回归已在工作树中修复,针对性检查、主题、站点和浏览器检查全部通过。这份带日期的审查不代表版本发布、消费者升级或部署。
输入与方法
审查范围为主题 v1.5.0(84b93e4)至 c55849e:标签后的全部六个提交、186 个变更文件。文档站版本为 43990ac。主题 main 比 origin/main(d9ad654)领先三个已提交的变更。初始审查前,两棵工作树均干净。下文保留初始证据,并单独记录后续修复。本轮未打标签、推送、升级消费者或部署。
正式本地工具链为 macOS ARM64 上的 Hugo Extended 0.168.0 及其内置 LibSass、Go 1.27.3、Node 26.12.0、npm 11.20.0 和 Python 3.14.8。CI 使用 Node 24。补充运行的 Hugo 0.169.0 检查不替代固定基线;允许的上游 LibSass 弃用警告保持可见。
方法包括实现与契约检查、主题 CI 有序命令、同级站点检查、生产构建、Chromium 测试,以及带对照组的最小复现。临时夹具和生成产物保存在 Git 外。本地主题和已发布模块分别验证,两种 workspace 机制均关闭。
已确认的回归
P2:RSS 格式恢复污染其他渲染
layouts/_partials/content/rss-description.html 的最后一次赋值,把先前捕获的 tdOutputFormat 写回页面 Store,短代码也读取这份状态。复现中,摘要渲染把标志恢复成 html 时,页面自身的 RSS 短代码仍在使用该 Store,产生静态与交互内容混杂的输出。
正式运行的 bin/check-release-assets.py 失败:RSS 校验和表头缺失,并出现 data-td-asset 和 <button>。随后单独重跑完整检查通过,单页最小复现也连续八次通过;不能用重跑变绿来忽略第一次失败。
更可靠的复现方式是把该检查器的 RSS 夹具扩成 24 个仅输出 RSS 的页面,每页包含 release-card 和 20 个 release-assets 块。保留其自定义 layouts/docs/single.rss.xml:先设置 tdOutputFormat 为 rss,再调用 .RenderShortcodes;同时保留普通首页订阅源。用 Hugo 0.168.0 构建全新输出,检查各页面 XML 的交互标记。
候选版本连续三次都污染了 docs/rss-9/index.xml。其中一次包含六张静态表格、十四张交互表格和 28 个按钮。同一夹具改用已发布的 v1.5.0,连续三次正常;只在临时覆盖模板中去掉候选摘要 partial 最后的 Store 恢复,也连续三次正常。
已确认范围是必跑检查器覆盖的自定义 RSS 页面渲染,不能据此推断所有普通订阅源都受影响。修复应保证格式状态在对应渲染上下文中稳定,增加多页覆盖,并保留 HTML、Print 和普通 RSS 摘要的正确行为。临时去掉赋值只是因果验证,不是已验收的修复。
P2:博客分页生成重复的 Print 聚合文档
layouts/_partials/head.html 对所有博客分区调用 shell/blog-index-state.html,包括 Print。后者调用 .Paginate,使 Hugo 为每个归档页生成一份包含整个分区的 Print 文档。
使用三篇有日期的博客文章,设置 outputs.section: [HTML, print] 和 params.ui.blog_index_size: 1。候选版本生成 _print/blog/index.html、_print/blog/page/2/index.html、_print/blog/page/3/index.html,三份都包含全部三篇正文。只把 head.html 覆盖为 v1.5.0 版本,就只生成一份聚合文档。两个构建在 Hugo 0.168.0 下均成功。
文档站生产构建也在中英文博客根分区和发布索引中多生成四份聚合文档,总计 1,120,141 字节。大型归档会放大浪费。应把博客索引分页及其 head 链接限制在 HTML 输出,并验证 Print 只生成一份完整文档,HTML 分页链接仍正确。
P2:Duo View 文档请求挂起后没有重试入口
assets/js/document-compare.js 在 iframe 地址为 about:blank 时直接返回,即使这是 15 秒超时后调用的 ready(true)。主文档请求一直 pending 时,会消耗超时回调,却没有调用失败处理;加载和 busy 状态一直保留,没有重试按钮或直达链接。
在同级站点浏览器夹具中,打开 /tests/compare-reader/,用 Playwright route 挂起 /zh/tests/compare-reader/?oink_compare_pane=1。打开双列视图并等待 17 秒:源窗格已就绪,目标仍为 about:blank,有一个加载提示、零个失败面板和零个重试按钮。
现有 404 和慢图片测试没有覆盖这种情况。超时应把仍为空白的文档判为失败,同时忽略初始空白加载事件,并保留慢图片下已解析正文的可读性。应在站点对比视图套件中增加文档挂起场景,保留外壳契约要求的本地化回退。
初始验证
| 检查 | 结果与范围 |
|---|---|
| 主题 CI 命令 | Hugo 0.168.0 下,本机可执行的 49 个步骤有 48 个通过。发布资产 RSS 步骤失败,主题验收并非全绿。 |
| 主题测试 | 81 项 JavaScript、90 项迁移、22 项快照与消费者工具测试通过。语言键一致性、schema、vendor 清单、输出 golden 和 Sass 边界检查通过。 |
站点 make check |
通过,包括 67 项 Node 测试,以及源文件、翻译、渲染 Markdown、渲染链接检查。 |
站点 make browser |
一次完整运行通过 518 项 Chromium 测试,约 23 分钟。未设置 A11Y_PATHS,站点地图扫描未缩小范围。 |
| 候选版本生产构建 | 本地主题与普通生产配置构建通过,仅有允许的 LibSass 警告。另行检查确认了 Print 重复。 |
| 出版隔离 | 真实 Chromium 的三项 PDF 安全测试全部通过,包括阻止远程 CSS 和禁用脚本。 |
| 出版冒烟测试 | 当前候选的 EPUB/PDF 生成及制品检查通过:五章、31 个 EPUB 目标、23 页 PDF。本地 Pandoc 为 3.12.1;未重跑 CI 固定的 Pandoc 3.10、Java 21、EPUBCheck 和子路径矩阵。 |
| 已发布基线 | Go 代理将 v1.5.0 解析到 84b93e4,校验和与站点记录一致。关闭模块替换和两种 workspace 后,发布 pin 检查与生产构建通过。 |
各浏览器组通过数量为:无障碍 30、响应式外壳 117、键盘 16、内容组件 70、代码与 Landing 37、PRD5 8、主题色 10、社区反馈 42、外观 53、Expo 50、博客画廊 8、PlantUML 8、图表 19、表格阅读器 15、常规对比视图 26、多语言对比视图 9。本地同级主题使用 hugo.yml,tests/fixtures/browser.yml,对比视图另有八语言夹具。本轮未重跑 Firefox 和 WebKit。
初始审查时的套件没有覆盖上文额外复现的文档请求挂起和 Print 输出数量问题。先前的审查修复记录明确说明,其 508 项浏览器结果不包括后续 Duo View 工作;本轮为当前候选提供了新的证据。
修复验收
修复基于主题 c55849e 与站点 43990ac 的工作树,尚未提交,也不在下文所链接的远端 CI 覆盖范围内。
- RSS 摘要渲染不再恢复陈旧的格式值。发布资产检查器新增 24 个 RSS 页面,每页 20 张资产表,与首页订阅源在同次构建中生成;逐项断言静态表格、完整校验和及没有交互控件。临时覆盖为修复前的 partial 时该检查失败,修复后的 partial 通过。
- 共享 head 中的博客分页仅用于 HTML。阅读检查器验证一份完整 Print 聚合、三个独立 HTML 归档页、正确的 HTML
prev/next链接,以及 Print 不含分页链接。临时覆盖为修复前的 head 时会生成三份 Print 聚合并失败,修复后的 head 通过。 - Duo View 区分文档超时与初始空白 load 事件。新增浏览器用例挂起目标文档,核对 Retry 文案和直达链接,并验证请求恢复后重试能重新打开窗格。修复后用例通过;既有慢图片用例继续覆盖正文就绪不必等待全部资源的行为。
双语架构与外壳契约、主题 Unreleased changelog 随修复同步。站点 LLMS golden 仅增加本研究页面的目录条目,更新前已审阅输出差异。
| 修复验收 | 结果 |
|---|---|
| 主题 CI 命令 | Hugo Extended 0.168.0 下,本地可执行的 49 步全部通过,含 81 项 JavaScript、90 项迁移、22 项快照与消费者工具测试。 |
站点 make check |
源文件与渲染检查、67 项 Node 测试全部通过。 |
站点 make browser |
最终完整运行通过 519 项 Chromium 测试:主套件 510 项,八语言对照套件 9 项。未设置 A11Y_PATHS,站点地图无障碍扫描未缩小范围。 |
| 生产构建 | 使用本地主题通过。中英文博客与发布索引均只保留一份 Print 聚合且没有 Print 分页链接;四份重复聚合均已消除。 |
浏览器验收使用已安装的 Playwright 浏览器,不覆盖可执行文件路径,让 bfcache 用例选择完整 Chromium。该用例与新增的文档挂起重试用例也分别通过。工具版本与上文一致,初始验证中的出版工具链限制仍然适用。
三个已复现的回归均已在本地解决。后续发布关卡仍是提交并推送已审查变更、针对该精确修订取得远端 CI 全绿,然后分别完成打标签、Go 代理验证、消费者 pin、部署与生产一致性状态。
发布与采用
本轮观察到的最新公开标签仍是 v1.5.0。成功的主题 CI包含 Book 出版任务,但对应 d9ad654,不是本地 c55849e。成功的站点检查和浏览器工作流也只测试当时可取到的远端主题,不能替三个后续本地提交背书。
只读运行 bin/update-consumers.py v1.5.0,发现 50 个主 checkout:42 个 current、三个模块消费者因非默认分支而跳过、五个经典 themes/oink 消费者。45 个模块 pin 都是 v1.5.0。四个经典主题仓库干净且精确位于 v1.5.0 标签;剩余源代码副本的抽查实现文件一致,但这不是完整一致性审计。本轮没有升级或部署消费者,正式采用时须逐一处理跳过的分支和经典副本。
发布放行条件
三项修复、归属检查、浏览器覆盖与双语契约已在本地落地,上文记录的工作树验收已全绿。下一关是在最终发布提交上取得远端 CI 全绿。先前审查留下的设计与无障碍改进继续作为独立待办,本记录不宣称所有历史发现都已修复。
代码验收后,再整理 1.5.1 changelog 和当前版本引用,推送已审查提交,核对该版本的 CI。打标签、推送与 Go 代理验证、消费者 pin 和外观检查、部署、生产一致性验证,分别属于后续状态。这些是正常发布准备,应与本地修复验收分别记录。
7.8.19 - 2026-10-13 主题审查
本记录描述基于 v1.2.0 下游版本基线的本地源码修改与测试结果。
范围
审查开始时主题位于 f8f476e,共享工作树中已有下游站点维护、紧凑侧栏修改,以及进行中的英文文档修订。这些工作均被保留。审查期间,原有侧栏修复被单独提交为 8f1f08d,对应文件内容未发生变化。
本次覆盖主题自有检查器、运行时单元测试、合成输出夹具、真实双语文档站,以及两类下游站点:DDIA 图书和 SILO 大型文档树。源码检查重点包括 URL 处理、图片输出、消息边界、焦点切换、运行时隔离与导航。视觉检查使用真实下游页面和文档站。
已复现缺陷与修复
| 缺陷 | 触发方式与表现 | 修复与回归 |
|---|---|---|
| 图片 URL 逃出 HTML 属性 | 普通图片或 Gallery 的 URL 中,引号后跟属性形状的查询值,会生成额外 HTML 属性;与号也可能改变浏览器解码后的 URL。 | URL 校验后再做 HTML 属性转义。check-media-primitives.py 解析 HTML、Print、RSS 中块级和行内图片属性;check-gallery.py 检查含引号的 Gallery URL。无执行代码的额外属性探针在修复前失败。 |
| Draw.io 丢失端点参数且消息处理不严谨 | 已有查询参数或片段的端点被拼接成错误的嵌入地址。空值或异常消息可能抛错,处理消息时也没有要求配置的源。 | 使用 URL 和 searchParams 构造地址;核对当前 iframe 与配置的源;忽略异常数据;向确切源发送消息,仅接受 PNG/SVG 图片导出 URL。新增 Draw.io 单元测试与站点编辑器集成测试覆盖这些路径。 |
| 页面操作菜单的键盘行为不完整 | ArrowDown 打开菜单时,CSS 可见性过渡尚未允许焦点进入;Tab 离开菜单后菜单仍保持展开。 | 可见性立即切换,保留透明度和位移动画;焦点离开整个控件后关闭菜单,且不抢回焦点。运行时和真实浏览器测试覆盖正向、反向遍历。 |
| 外观面板重开后被旧事件清除状态 | 在原生 close 事件排队期间立即重开面板,面板仍可见,aria-expanded 却变成 false。 |
面板已经重新打开时忽略此前的关闭事件。真实浏览器测试在 Chromium、Firefox、WebKit 的桌面与手机宽度覆盖该竞态。 |
两份分类页 HTML 快照也需要跟进已有侧栏修复:完整核对差异后,每份移除了 33 处错误的叶节点隐藏标记。随后 52 个输出面的快照检查通过。
相关行为已经写入双语 架构 与 外壳 契约。此次修改无需配置迁移,也没有新增依赖。
验证
本地使用 Hugo Extended 0.166.0、Go 1.27.1、Node 26.9.0、Pandoc 3.11,PDF 生成使用 Chromium 151.0.7922.34。
| 层级 | 证据 |
|---|---|
| 主题运行时 | 修复后 52 项 JavaScript 测试全部通过。Python discovery 通过 116 项;90 项迁移测试还单独跑了一遍。 |
| 主题检查器 | 执行全部 bin/check-*.py,包括带明确参数的输出安全检查,以及打包后的 EPUB/PDF 检查;配置 schema 生成检查通过。媒体、Gallery、输出与快照相关检查在修改后重新执行。检查器构建有效发布输出时使用警告即失败的门禁。 |
| 文档集成 | 本地主题替换下的 make check 通过:源码与渲染 Markdown、双语标题覆盖、内部链接、替代站点构建、favicon、输出快照和版本锁定契约。 |
| 浏览器集成 | make browser 的 229 项测试 通过,包含完整 sitemap 无障碍扫描、六种响应式宽度、中英文、导航、搜索、组件、代码、社区回归和四种外观预设。额外引擎检查的 6 项测试 通过,覆盖 Chromium、Firefox、WebKit 的 390 和 1440 px 视口。 |
| 出版夹具 | EPUB 包含 5 章、31 个链接目标,内部检查零错误;EPUBCheck 5.3.0 零错误、零警告。PDF 共 23 页,5 个预期标题齐全,内部检查零错误,并抽查了渲染页。 |
| DDIA 抽样 | 本地主题 make check-local 通过,包括 14 章翻译、196 处图片布局及第二版 57 页中的 38,170 个内部引用。抽查桌面、手机、暗色阅读页与四种样式菜单。 |
| SILO 抽样 | 本地主题 make check 通过,包括 1,552 个 HTML 文件中的 536,561 个内部引用。抽查桌面文档和手机搜索结果。 |
首次广泛测试发现了过期快照。一次独立安全检查调用也漏传了必需参数,修正后通过 493 份夹具输出检查。新增 Print 断言最初没有同步测试页面自身的输出列表,补齐测试设置后,HTML/Print/RSS 回归通过。
性能观察
检查了文档站严格构建、压缩后的本地 production 输出。/docs/customize/config/ 引用的本地 JavaScript 合计 62,192 字节 gzip(约 61 KiB,含 Lunr);两个样式表合计 98,838 字节 gzip(约 97 KiB),不含字体、图片、搜索索引和第三方服务流量。
普通文档页未包含大型图表包。Mermaid 样本额外加载 976,006 字节 gzip 的 Mermaid 依赖(约 953 KiB)以及小型页面运行时。运行时隔离与输出检查通过。
限制与交付状态
无障碍套件沿用现有策略,排除上游 Redoc、Swagger UI 的 DOM。下游站点浏览器检查抽样覆盖了 DDIA 与 SILO 的桌面和移动端核心路由。本轮修复按主题与文档分批提交到本地 Git。
7.8.20 - 为大表格腾出阅读空间
通过显式“阅读表格”操作打开大型原生对话框,是最值得继续评估的候选方案。 它给桌面表格增加空间,同时保留文字大小、原表语义和简短的返回路径。许多表格只需在正文内拓宽;实测扩展包样本在桌面端采用这种模式就已能容纳全部列。在 390px 视口下,对话框仅增加 16px 宽度,窄屏下仍保持横向滚动。五个文档框架默认均采用原生表格配横向滚动容器,而第三方表格库在提供数据操作的同时也增加了集成与维护负担。
分清布局、内容与发布状态的证据
当时的公开标签快照为 v1.3.2,标签引用为 68cfbbdc177064e2c28277c09c385d78e5363ffd。研究开始时,主题 HEAD 为 7bc8658bc1be13ef0ad3050b9d9f6862f8f1f65f,站点 HEAD 为 99751179eefada3fa7a91318c792ddac46d7e6bf,两个工作树均有未提交修改。本实验中验证的短标记 .wide 随后纳入 OINK 1.4.0,详见 组件契约。
本地检查的模块包括 oink/layouts/_partials/content/table-render.html、table-body.html、assets/scss/td/_content.scss、_table.scss、预设与字体令牌,以及界面协调器。.wide/.td-table--wide 控制内在尺寸和标识符换行;.full-width 使用文章可用画布。.matrix 提供首列行表头、受限高度的双轴滚动,以及现有粘性表头/首列 CSS。该 CSS 给每个表头设置 top:0,未计算多行累计偏移,也未设置移动端首列宽度上限。.fields 输出定义列表。普通题注位于表格内部,编号 Book 表格题注位于外层 figure(见 表格指南 和 输出契约)。
| 选取的下游站点源文件 | 有限样本与用途 |
|---|---|
pigsty.cc/content/docs/infra/param.md,NGINX 分组 |
4 列 × 14 行正文;带链接的参数标识符与描述 |
pgext/content/list/pkg.md,第 44–69 行 |
6 列 × 24 行正文;扩展包/版本/仓库/分类/RPM/DEB 比较 |
pigsty.cc/content/docs/ref/linux.md,概览 |
8 列 × 14 行正文;OS/架构及 PG18–PG14 支持状态 |
piglet.run/content/docs/reference/vscode/_index.md |
2 列 × 7 行正文;小表格反例 |
下游站点快照分别为:pigsty.cc 的 efaf7b19b442ddb284106418c78854552d949e9e、pgext 的 ab87fc7e5707d57f55129d3c45874abcc16fdef8、piglet.run 的 5a1572e6f9110cd5e8ef6e75cd50d9c4b6429f98;均固定使用 OINK v1.3.2。另一张包含 616 行的扩展目录不在本次原型测试范围内。
这些样本区分了 多列、多行、长单元格 和 复杂表头。拓宽视口主要帮助第一类;粘性定位帮助第二类;换行帮助第三类。多行和合并表头需要显式关联与偏移计算。原型所选表格均只有单行表头且无合并单元格。GFM 管道表格语法本身定义单行表头和行内单元格内容(GFM 表格、WAI 多级表头)。
五个框架以溢出处理为共同基线
调研检查了五个文档框架的官方文档与默认主题源码。五个框架默认均不提供独立的全屏表格阅读器。 文档链接对应 2026-10-26 读取的页面;源码链接锁定具体提交。
| 框架快照 | 默认表格行为 | 可选或自定义边界 |
|---|---|---|
Material for MkDocs,0f044dbe,2026-10-24 |
Markdown tables;将原表包入局部横向滚动容器,并保留未包装回退 |
排序通过按需接入 Tablesort 5.3.0 实现;默认不提供阅读器、固定列或表格复制(见 指南 与 CSS)。 |
Docusaurus Classic 3.10.2,f37f9035,2026-07-10 |
MDX/GFM 渲染表格;Infima 0.2.0-alpha.45 使用 display:block; overflow:auto |
通过 MDX/remark/rehype 提供自定义扩展(见 MDX 映射 与 Infima CSS)。 |
VitePress 1.6.4,1fc537b7,2025-08-05;2.0.0-alpha.21 快照 d6d7a970,2026-10-26 |
两个快照均使用块级表格和横向溢出 | 2.0 快照默认增加 tabindex="0";可通过 Vue 组件定制交互(见 1.6.4 CSS 与 2.0 焦点插件)。 |
Astro Starlight 0.42.7 快照 af9557ea,2026-10-24 |
Markdown 表格样式使用 display:block; overflow:auto |
.not-content 提供样式隔离;整页阅读与代码全屏由社区插件提供(见 CSS 与 插件目录)。 |
Nextra 4.6.5 快照 d6e80e1d,2026-06-23 |
官方 Table 组件包装带样式和横向溢出的 HTML |
字面 MDX <table> 需显式使用 Table 或加入白名单(见 源码 与 HTML 边界)。 |
所检查代码的许可证均为 MIT:Material、Docusaurus、VitePress、Starlight 和 Nextra。Docusaurus 文档另用 CC BY 4.0。
对静态 Hugo 站点,直接适用的模式是语义 HTML、局部滚动、构建期焦点属性和可选渐进增强。Material 的 挂载代码 使用 RxJS;Classic 使用 React/MDX,VitePress 使用 Vue/Vite,Starlight 使用 Astro,Nextra 使用 Next/React。
丰富组件增加数据操作,也增加维护责任
当前版本差异明显。DataTables 3.1.4 已不再依赖 jQuery。TanStack Table 9.3.1 使用 constructTable、按需功能选择和 Store 响应式绑定(见 DataTables v3 依赖边界 和 TanStack v9 公告)。
| 组件 | 适用方向与可用操作 | 集成与维护边界 |
|---|---|---|
| DataTables 3.1.4,2026-10-13 发布;MIT,核心无必选运行时依赖 | 通过内部数据模型增强 HTML 表格;核心提供排序/搜索/分页;Buttons、FixedColumns、FixedHeader、Scroller 可选 | 最接近系统化表格操作的候选方案。滚动可能复制或移动表头;支持复杂 thead,不支持 tbody 跨格(见 发布、发布包、跨格限制)。 |
| Tabulator 6.7.1,2026-10-22 发布;MIT,无必选运行时依赖 | 网格渲染、排序/筛选、冻结列、列分组、剪贴板和虚拟渲染 | HTML 导入器读取首个 tbody 的 td,会丢失 .matrix 的行表头 th 语义,且不保留通用 caption/scope/headers(见 发布、发布包、导入器)。 |
TanStack Table core 9.3.1,2026-10-12 发布;MIT,依赖 @tanstack/store ^0.11.2 |
无界面的排序/筛选/固定/选择/跨格逻辑;支持 vanilla 集成 | 维护者需自行实现 HTML 导入、渲染、控件、样式、焦点、导出和对话框;虚拟化器另行接入(见 元数据、固定版本包、vanilla 指南)。 |
DataTables 的 FixedHeader/FixedColumns 是可选扩展;Scroller 要求等高行,不适合自动换行的技术描述。Tabulator 输出面向单元格导航的 ARIA grid 结构。TanStack 跨格 API 需要显式定义列,不支持任意 HTML 合并格的无损导入。上述发布包均未内置全屏阅读对话框(Tabulator 的 printFullscreen 用于打印布局)(见 Scroller、Tabulator 导航、Print 源码、TanStack 跨格指南)。
复制默认行为也各不相同。Buttons 4.1.2 按当前排序/筛选规则导出显示数据并为跨格位置补空值,默认不开启公式转义。Tabulator 6.7.1 用 TAB 和换行拼接单元格值,不做通用引号包裹或公式转义。Tablesort 5.7.1 是轻量的无依赖 MIT 排序库,保留原表行并支持表头 Enter 键触发,不含阅读器、筛选或导出层(见 Buttons 源码、Tabulator Clipboard、Tablesort 源码)。
比较交付字节时同时说明排除项
测量使用 14 个固定版本 npm tarball,并核对 registry integrity。发布者提供的 minified 文件按原样测量。Gzip 使用 Python 3.14.8、zlib 1.2.12 和 gzip.compress(data, compresslevel=9, mtime=0);合计为各文件分别压缩后相加。
| 测量的交付内容 | Minified 字节 | Gzip-9 字节 |
|---|---|---|
| DataTables 3.1.4 核心 JS + 默认 CSS | 153,523 | 46,181 |
| 上述内容 + Buttons 4.1.2、FixedColumns 6.1.1、FixedHeader 5.1.2 JS/CSS | 249,015 | 72,480 |
| Scroller 3.1.1 JS/CSS,另加 | 13,300 | 4,389 |
| Tabulator 6.7.1 完整 UMD JS + 默认 CSS | 483,872 | 107,512 |
| TanStack 9.3.1 所选阅读器逻辑,本地打包 | 49,505 | 14,248 |
| TanStack 9.3.1 全部 core 导出及绑定/渲染辅助,本地打包 | 115,146 | 30,983 |
| TanStack virtual-core 3.19.7,独立本地打包 | 32,872 | 9,745 |
| Tablesort 5.7.1 核心 + 数值比较器 | 3,756 | 1,763 |
DataTables 核心 JS 为 123,516/42,338 字节,CSS 为 30,007/3,843 字节(minified/gzip)。扩展合计使用 Buttons、FixedColumns、FixedHeader、Scroller 及匹配的 -dt CSS。可选样式适配 JS 为四包合计另加 2,501/1,279 字节,Scroller 适配器另加 650/326,不含初始化、对话框、本地化、JSZip/pdfmake 和 source maps。Buttons 复制实现已在主文件中,不重复计入兼容 shim。Tabulator 完整包 包含全部内置模块;Tablesort 仅提供排序功能。
TanStack 测量来自本地 esbuild 打包:使用 Node 26.12.0/npm 11.20.0、esbuild 0.25.11,将 core 9.3.1 与 Store 0.11.2 以 --bundle --minify --format=esm --platform=browser --target=es2020 --legal-comments=inline 及生产 NODE_ENV 打包。所选入口导出 constructTable、tableFeatures、排序/筛选/固定/跨格功能、排序/筛选行模型、storeReactivityBindings 和 FlexRender。全部核心入口导出 core 命名空间及后两个辅助项;虚拟化入口单独导出 virtual-core。Metafile 确认无外部 import。14,248 字节 gzip 的逻辑样本仅包含无界面状态管理,不含 UI、CSS、HTML 导入器、无障碍控件、对话框、复制器或虚拟化器(见 core 包、Store、Virtual)。
概念验证中的桌面拓宽收益远大于手机
外部原型使用真实同级文档站 Hugo 构建,内容通过仓库外挂载。PGEXT 专用短代码转成显式标签/链接,参数片段链接解析到原始来源 URL,支持状态图标增加“Supported”文字等价项。新增题注和 .wide 作为实验适配,各阅读模式复用相同的生成表格数据。构建使用同级主题替换,关闭两个 workspace 机制,并将输出、缓存和资源目录放在仓库外。
比较外壳采用 230px 侧栏、桌面主区 40px 留白、68ch 正常正文宽度与 1130px 可用画布,表格与预设样式来自实际生成的 OINK CSS 与字体。另行生成的真实表格指南页面测得桌面表格视口为 770px、移动端为 358px。
| 扩展包表格滚动视口 | 正常正文宽度 | 正文内拓宽 | 原生对话框 | 独立页面模式 |
|---|---|---|---|---|
| 桌面 Paper/Terminal | 652.80px | 1130px | 1358px | 1408px |
| 桌面 Slate/Ink | 686.38px | 1130px | 1358px | 1408px |
| 移动端,全部四种预设 | 358px | 358px | 374px | 374px |
这些是本地 qa.json 在 1440×1000 和 390×844、DPR 1 下的测量;各预设深浅色几何尺寸一致。文字始终保持 16px,代码 14px。移动端表格内在宽度约为 969–1009px。桌面正文内拓宽已能容纳六列样本;对话框主要增加受限高度、常驻控件,以及与正文隔离的阅读视野。桌面对话框的表格可见高度为 847.20px,页面模式为 897.20px;移动端因控件换行,对话框高度为 711.02–741.20px。
正文内拓宽的上下文切换成本最低。对话框增加桌面空间,并保留直接返回文章的路径。独立页面在桌面上只多 50px,移动端宽度不变;其优势在于可重新加载与 URL 分享,但返回时不保留原文章滚动位置与焦点。三种模式均不缩放文字,也不调用 Fullscreen API。
通过 Playwright 1.62.1 驱动的 Chromium 151.0.7922.34 检查了 16 组预设 × 深浅色 × 设备组合,每组包含三个阅读概念与基线。扩展包样本通过原节点身份、ID 唯一性、题注/表头 scope、样式/字体加载、标题初始焦点、Enter 打开、Escape/关闭返回、正反 Tab 边界、工具栏稳定性、控件与表格区域分离、页面模式重载检查。未记录整页溢出;另检查了三个移动端样本和 320px Terminal/深色溢出探针。单独检查保留了链接目标与文字选择,并把正文内横向滚动恢复到 180px。截图审阅覆盖 48 张概念截图和总览图,且 Paper/浅色/桌面对话框的 axe 扫描报告零违规。
参数与版本样本的首列分别占 374px 移动端阅读视口的 59.3% 和 58.3%,说明移动端适合加入可选的首列宽度上限。各预设保持鲜明区分:Paper 的 Plex Sans 与克制边框、Slate 的 Inter 与圆角控件、Ink 的平面利落几何,以及 Terminal 的等宽控件和 Plex Sans 正文。
复现材料包括仓库外保存的构建器、源码清单/哈希、生成的参考输出、QA/检查 JSON 和截图集,使用 macOS ARM64 上的 Hugo 0.168.0+extended+withdeploy、Go 1.27.3,以及 Node 26.12.0、npm 11.20.0、Python 3.14.8。
本原型验证聚焦 Chromium 桌面与触屏模拟下的单行表头场景;Firefox/WebKit、屏幕阅读器测试、强制颜色、200/400% 缩放、合并表头、协调器集成及打开状态下打印归入正式实现验收。
小型阅读器更容易保全现有文档
建议首期采用显式 阅读表格 操作、原生对话框,以及原节点的一次移动/恢复生命周期。只靠悬停无法兼顾触屏和键盘发现;整表点击会与选择、链接、滚动冲突。可见标题/关闭控件、具名滚动区、标题初始焦点、受限 Tab 导航、Escape,以及带保护的焦点/滚动恢复,都是核心要求。复用 OINK 界面协调约定。接近全视口的对话框与浏览器全屏不同,后者的标准不允许把 <dialog> 作为请求目标(见 APG 对话框 与 Fullscreen Standard)。
保留 caption/th/scope/headers 和原 ID。移动原表可避免克隆冲突与状态分叉,但继承样式、外层编号题注、外部 ARIA 引用及脚注仍需显式处理。原生表格可以包含链接和排序按钮,无需转为 ARIA grid。先支持单行粘性表头,复杂表头在累计偏移验证前保持普通滚动。默认使用舒适间距,紧凑模式只改内边距、不改字号。可选首列固定需给移动端留出充足的阅读宽度(见 APG table、APG grid、表头语义)。
.wide/.td-table--wide 仍只控制布局,.full-width 仍受文章画布限制,.matrix 保留语义,.fields 不启用阅读器。无 JS 时内容保持可读;Print 展开静态表格,Markdown 保留源内容,RSS 保留静态/摘要边界且不混入阅读控件。
若后续增加 TSV 复制,建议按作者顺序复制 整张符合条件的表格(含视口外的行列):链接转为文本标签,代码保留文本,排除隐藏控件与装饰符号,有实际含义的图标提供文字等价项。首期拒绝隐藏数据行/列、嵌套表格、行长不等、多行表头,以及任何 DOM rowSpan/colSpan 不为 1 的情况(包括 rowspan="0"),使用 TAB 分隔列、LF 分隔行、规范化单元格内空白,并对跳过前导空白后以 = + - @ 开头的单元格前置单引号(见 HTML 表格模型、DOM 文本、Clipboard 2026-06-24 工作草案、OWASP 电子表格注入限制)。
聚焦原生表格的小型阅读器既保留未来引入数据网格的可能,也避免立即维护第二套数据模型。缩减后的实现边界见 接受的决策。
审阅后接受的方向
维护者审阅后选择显式组合 {.wide .expand} 与角落直接展开按钮(悬停或聚焦时显示,触屏常显),排除表格上方工具栏、省略号菜单和 TSV 复制。最终发布行为由 接受的决策 与 当前契约 定义。
7.9 - 设计提案与 PRD
提案描述的行为可能尚不存在。当前行为由契约、已接受决策、实现与归属检查器定义。不能把提案 当作配置参考。
本栏目是 OINK 产品需求文档、RFC 风格设计与未决维护者提案的唯一正本位置。不要在主题仓库或
文档仓库中另建本地 plan/、plans/、proposal/ 或其它并行设计树。
当前提案
| 提案 | 当前边界 |
|---|---|
| 反向链接与知识图谱 | G1(静态反向链接)已接受,已在主题 main 分支实现,随 OINK 0.8.0 发布;局部与全站图谱(G2/G3)保持草案 |
| 媒体收敛 | 部分已实现;media-result 契约与 Landing 资源元数据已交付,M3 决议为原生图片处理,退役(M4)保持开放 |
| OINK CLI 与下一阶段产品路线 | 独立 Go 仓库与首期边界已接受,本地 CLI 候选已实现、尚未公开发布;后续主题、迁移、采用、版本管理、OpenAPI 与平台阶段保持提案 |
| 视觉预设与外观切换 | Paper/Slate 已随 v1.2.0 发布;Ink/Terminal 已提供显式启用的实验,等待视觉定稿;当前行为与证据见已接受决策和带日期验收记录 |
组件精修、统一图表阅读与本地搜索改进已随 1.4.0 发布,现归入已接受决策。译文对照随 1.5.0 发布,记录为已接受决策,草案已退役。
表格阅读草案也已退役:接受的决策
与组件契约定义缩小范围后随 OINK 1.4.0 发布的
.expand 行为。
Agent 批量索引提案已在输出交付后退役。稳定行为现在归属
架构,用户步骤归属
Agent 就绪输出。Book 出版提案也在 BookManifest 与 EPUB/PDF
工具交付后退役。稳定行为归属架构与
创作书籍,带日期的下游采纳证据归属
消费站证据。剩余的消费站采纳工作
不会让上游设计提案继续保持活动状态。两份提案草案均由 Git 历史保存。
生成式配置 Schema 提案已按生命周期退役:行为的规范位置是配置总览, 长期理由进入生成式配置 Schema 决策,草案原文由 Git 历史保存。
CLI 工作区与适配器
显式 workspace 与可选适配器保留在当前收缩后的 CLI 中。 当前契约定义命令边界, R1–R8/A18 记录保留历史验收证据。 有限维护路线已退出活动导航。
新 PRD 放在哪里
创建一份英文主页面及其简体中文对页:
两份文件都使用显式、稳定的英文标题 ID。中文页面中的代码、键、路径、版本与 API 名称保持原样。 提案开头要有可见的草案状态,并包含:
- 状态、负责人、日期和受影响契约面;
- 背景与证据;
- 目标与明确非目标;
- 提议行为,以及输出、无障碍、安全边界;
- 兼容与迁移影响;
- 实现与归属检查器计划;
- 验收标准与待决问题;
- 记录提案自身变化的决策日志。
大型实验可以在 ../research/ 下增加带日期的页面;临时日志与生成
产物不进入 Hugo 内容,也不进入 Git。
生命周期
提案被接受后不会自动成为第二份契约。稳定行为进入归属契约,稳定理由进入 Decisions,用户步骤进入 相关指南,然后把提案退出活动导航。
评审门禁
实施前,评审者确认提案没有重复已有外壳、resolver、组件族或数据权威。实施期间,如果设计改变, 先更新这份双语提案,不能让代码悄悄漂移。验收至少覆盖主题的最窄归属检查、真实文档站、渲染后的 中英文、相关输出、无障碍与响应式检查。
只读 Studio 候选
2026-10-04 当前 CLI 移除 Studio。使用 oink dev、普通编辑器与
inspect/check 结构化报告。R7 记录
保留此前浏览器实现的历史验收。
受审阅编辑
当前 CLI 移除通用源码编辑,保留受保护的 new、move、审阅记录与基线计划。
旧编辑计划会被拒绝。R8 记录
继续作为历史证据,不是当前命令 API。
7.9.1 - 反向链接与知识图谱
2026-08-27 决议 G1 的全部待决问题并接受 G1(静态反向链接)。它已在主题 main 分支实现,随 OINK 0.8.0 发布。局部与全站图谱(G2/G3)保持草案状态,等待 G1 的 真实使用证据;它们的名称和配置在被接受之前不是公开 API。
前提
反向导航与页面连接视图是链接图的属性,不是 [[wikilink]] 拼写的属性。Hugo 已经接受普通
Markdown 链接和 ref / relref。OINK 可以从作者已经在写的内容中派生图谱,无需增加解析器、
Goldmark 扩展或并行创作语法。
首要价值是反向链接,而不是可视化。静态入链列表不需要 JavaScript,在 Print 与 Markdown 中也能 降级。交互图谱应当只是完整列表之上的可选增强。
目标与非目标
目标:
- 每次构建为每种语言派生一份链接索引;
- 在页面上显示确定性的入链;
- 可选显示有界的局部邻接图;
- 可选发布全站视图与机器可读图数据;
- 编辑链接暂时陈旧或不完整时,普通预览仍然可用。
非目标:
- 引入
[[wikilink]]语法; - 索引外链、
mailto:、同页锚点或自链接; - 用 JavaScript 发现正文中已经存在的链接;
- 把可视化变成唯一导航方式;
- 承诺从任意 shortcode 参数或原始 HTML 中完整提取语义图。
交付阶段
| 阶段 | 交付物 | 运行时 | 独立价值 |
|---|---|---|---|
| G1 | 语言内链接索引与反向链接列表 | 无 | HTML、Print、Markdown 中的反向导航 |
| G2 | 当前页面周围的局部图谱 | 既有 ECharts 加一个小型本地运行时 | 以 G1 为无障碍兜底的空间视图 |
| G3 | 全站图谱页与图数据输出 | 同一运行时 | 全站探索与机器可读边 |
每个阶段单独验收。G1 不等待 G2,G2 也不会强迫每一页加载图谱代码。
提取契约
提议的索引按语言扫描源码一次,每对来源与目标只记录一条边。它先剥离代码围栏和行内代码,再提取
普通 Markdown 链接与 ref / relref;随后只解析站内页面,去掉 fragment 以确定页面身份,
排除自链接,并合并重复引用。
实现至少要测试:
- 同一目标的重复链接合并为一条边;
- 围栏与行内代码不产生边;
- 外链、protocol-relative URL、邮件、同页锚点与自链接被排除;
ref与relref被纳入;- 每种语言生成相互独立的图;
- 无法解析的派生边由警告或专项检查报告,但不会让普通
hugo server不可用。
扫描原始源码存在已知遗漏。自定义 shortcode 参数或原始 <a href> 中的 URL 可能不会进入图谱。
必须明确记录这种遗漏,不能声称得到完整语义图。
反向链接输出
G1 在右栏输出一个 aside 组,与目录、分类标签云并列:目录讲这一页写了什么,反向链接
讲哪些页面指向这一页。该组默认展开,先显示前八条,其余折进原生 disclosure,避免被
大量引用的页面把右栏撑满。开关是站点键 params.ui.backlinks(裸布尔,默认关闭),页面用同名去前缀的
front matter 键 backlinks 覆盖,section 可以 cascade。排序必须确定:按稳定页面路径
排序——它与语言无关、与导航自然同组,且不需要第二个排序权威。该组使用普通链接;没有
入链时不渲染。
无法解析的派生边被静默丢弃并作为已知遗漏记录在案:G1 是本地导航增强,不是链接检查器, 让它替站点报告断链只会制造重复告警。
Print 与 Markdown 保留可读列表。除非后续 feed 研究证明反向链接能改善文章订阅而不是制造站点导航 噪音,否则 RSS 省略它。
交互图谱边界
G2 复用本地内置的 ECharts graph series。当前页面是中心,直接入链与出链邻居组成默认深度。硬性 节点上限防止视图不可读或成本失控。键盘焦点、文字替代、reduced motion、forced colors、窄屏和 Print 都是验收要求,不是后续润色。
JavaScript 或 ECharts 不可用时,G1 仍然完整可见。运行时只在真正渲染图谱的页面加载,并进入既有 feature bundle key,避免不同特性页面在资产缓存中撞车。
全站输出
G3 可以新增专用图谱页与 opt-in JSON 输出。JSON schema 包含版本、语言、节点和带稳定 URL 的有向边, 不暴露本机文件路径或未发布页面。它必须和 G1、G2 使用同一索引,避免三种表示各自漂移。
兼容与迁移
普通 Markdown 写法不变,因此无需内容迁移。配置名称继续待定,直到原型证明最小公开面。所有交互 与全站输出默认关闭;静态反向链接列表可以单独讨论,因为它只是本地导航,不涉及网络与浏览器状态。
验收标准
验收需要专项 graph 检查器、提取夹具、HTML/Print/Markdown golden、严格构建负向用例、浏览器无障碍 与响应式测试,以及真实双语站构建,并在有代表性的大站上测量性能。
待决问题
G1 的问题已全部决议(见决策日志)。仍然开放、属于 G2/G3 的问题:
- 局部图只暴露一层,还是允许严格限额的第二层?
- 哪些页面元数据值得进入 graph JSON?
- 在 G1、G2 获得生产证据前,G3 是否值得新增输出格式?
决策日志
- 2026-08-19:起草三阶段设计。
- 2026-08-27:决议 G1 并接受,排入 OINK 0.8.0。G1 是 opt-in:站点键
params.ui.backlinks裸布尔默认关闭,页面覆盖键backlinks,不按 shell type 区分——策略归站点与页面,不归外壳。排序简化为稳定页面路径单键排序,删去 「section → weight → 标题」的三级链:单一确定性权威已经满足反向导航,多级排序 等于第二个导航权威。无法解析的边静默丢弃并记录为已知遗漏,不产生告警。 G2/G3 与图数据输出继续等待生产证据。 - 2026-08-27:设计评审把这一块从页尾移到右栏。反向链接是页面元数据,与目录成对; 页尾是读者的收尾区——分享、反馈、出处、翻页、评论。右栏这一组同时引入八条上限, 其余收进原生 disclosure。
7.9.2 - 媒体收敛
M1(共享 media-result 契约)与 M2(Landing 资源元数据)已在主题 main 分支实现;
M3 已决议为方案 2:图片处理只属于原生 Markdown 图片形态,完整 fig 源形态保持
容器语义,其参数表刻意不含 command/options。M4(兼容退役)在完成消费方盘点
之前保持开放。以下各节为原始设计记录。
当前基线
正文图片钩子、编号 fig、卡片与 gallery 统一通过 content/image-resolve.html 解析页面资源、
section 资源、全局资产、static 文件与显式远程 URL。栅格资源可以提供固有尺寸与处理后派生图。
HTML Zoom 资格使用 data-td-image-zoom 标记;构建期检测只查找主题自己输出的标记。
独占 Markdown 图片已经可以把题注或 Book 编号与图片处理、链接组合起来。编号图片 figure 共用
td-figure 与 td-book-figure 语义。Landing 媒体经过共享 URL 信任策略;代表图片则刻意使用
排序 resolver,因为它的职责是选择代表图片,而不是渲染一个显式来源。
剩余问题
共享安全边界已经比共享媒体模型更成熟。Landing 媒体仍然拿不到与正文图片相同的页面资源元数据和
处理结果;代表图片选择与显式图片解析返回不同结果形状;部分兼容 class 仍保留在标记中;Book 的
全量 fig 形态也不能表达原生图片钩子的所有处理选项。
因此问题已经不再是“替换七种图片入口”,而是:能否在不抹掉各自语义差异的前提下,让剩余表面共享 一份小型结果契约。
目标与非目标
目标:
- 为 URL、原始 URL、尺寸、替代文字、署名、可处理状态与外部状态定义一个规范化媒体结果形状;
- 在来源语义重合处,让显式正文图片、Landing 媒体与代表图片复用这个形状;
- 继续让 figure 标记与 Zoom 资格分别只有一个归属实现;
- 决定全量
fig是否需要处理能力,还是要求处理过的编号图使用原生图片形态; - 只有在完成消费站证据与 release note 后才退役兼容标记。
非目标:
- 增加第三方 lightbox 或远程图片服务;
- 意外把 image Zoom 从 opt-in 改成站点政策;
- 给 gallery 新增题注、序列或轮播模型;
- 把表格、公式、示例等非图片 Book 目标合并进只适用于图片的基类;
- 强迫代表图片排序与显式图片解析完全相同。
提议阶段
M1 — 结果契约
记录正文 resolver 与代表图片 resolver 的返回字段,再把交集提取成一份内部媒体结果契约。代表图片 继续负责来源排序,正文 resolver 继续负责显式来源解析。这是要求字节输出不变的内部重构。
M2 — Landing 资源元数据
允许 Landing 条目中的合格本地资源通过媒体契约解析,获得固有尺寸与相同 URL/安全结论。Landing 数据中显式给出的宽高继续优先。远程与 static 来源仍然合法,但不能伪装成拥有可处理资源元数据。
M3 — 全量 figure 能力决策
从两个答案中明确选择一个:
- 为全量
fig的来源形态增加处理参数,并通过同一处理 helper 规范化;或者 - 处理能力只属于原生 Markdown 图片,把全量
fig明确定义为任意编号块内容的容器。
实现不能让两个答案各完成一半。两种形态的 Markdown/LLMS 输出必须一致地链接到文档规定的原图 或派生图。
M4 — 兼容标记退役
移除旧图片元素 class 或属性之前,先盘点下游 CSS 与 JavaScript。兼容名称仍被使用时,要么保留一个 明确的版本窗口,要么在同一 release train 中迁移归属站点。
安全、输出与无障碍
- 图片 URL 继续遵守共享 scheme 与远程主机策略。
- 缺少必需替代文字时发出警告,且只在现行契约允许处渲染装饰性回退。
- 宽高不能声称 SVG、static 文件或远程来源没有提供的元数据。
- 带链接的图片不是 Zoom 目标;运行时保留 dialog 焦点、键盘关闭、reduced motion 与窄屏约束。
- Print、Markdown、RSS 与 LLMS 去掉交互标记,同时保留目标图片、题注、署名、编号与链接。
验收标准
每个阶段分别拥有 HTML 与 Markdown 字节级证据、正文与 Landing resolver 测试、URL/安全检查、图片处理 测试、Book 目标、gallery/Zoom 浏览器测试,以及真实站中英文窄屏审查。只有 M3 的能力选择明确后, 提案才能被接受。
待决问题
- 一份共享结果结构是否足够,还是共享更底层的 URL/资源记录会让 resolver 归属更清晰?
- Landing 应消费资源署名,还是只消费尺寸与 URL?
- 原生图片已经能组合编号、题注、链接和处理后,全量
fig处理能力是否仍有真实消费需求? - 哪些输出兼容名称仍被真实消费站使用?
7.9.3 - OINK CLI 与下一阶段产品路线
用户于 2026-09-29 授权独立 Go 仓库 pgsty/oink-cli 及首期开发。六个命令已有本地 0.1.0-dev 实现,最终本地验收单独记录。当前行为归属 CLI 决策与结果契约及CLI 简介。主题 1.2、工具能力描述、Docsy 迁移、版本生命周期、OpenAPI、MCP 与 Studio 保持提案状态。
| 记录 | 内容 |
|---|---|
| 状态 | 仓库选择与首期范围已接受;本地候选已实现并验证;后续路线保持草案 |
| 负责人 | OINK 维护者 |
| 日期 | 2026-09-29 |
| 范围 | OINK 主题、独立 CLI、现有 Starter 与文档站 |
| 受影响契约 | 架构、配置与诊断、输出、迁移,以及后续的版本导航与 API 内容 |
| 源码快照 | 主题 HEAD 3a18234、文档站 HEAD 85f16bf、Starter HEAD 137843b,以及下文明确标注的本地工作 |
核心建议
独立建立 oink-cli 仓库,发布名为 oink 的可执行文件,对外继续使用 OINK 这一个产品品牌。主题负责渲染内容;CLI 帮助用户初始化、诊断、校验、升级,随后逐步支持迁移。文档站继续管理公开指南、双语设计记录和集成验收。
首期行为已移入 CLI 契约;带日期的验收记录列出实际执行的检查与剩余限制。本路线图继续承载后续阶段和采用目标。
首发应改善从现有仓库到可靠发布的流程,四项实质性能力是 doctor、check、init、upgrade。dev 和 build 可以提供轻量、透明的 Hugo 快捷入口。根据真实输入仓库的证据,再扩展一条有明确支持范围的 Docsy 迁移路径。版本生命周期和 OpenAPI 生成排在首个可用版本之后,同一时间只推进一个主要内容模型项目。
主题必须允许用户不安装 CLI。对于生成内容,这意味着提交生成后的 Hugo 输入,或者以其它明确方式提供这些输入:移除 CLI 后,普通 Hugo 仍能构建站点。重新生成输入是独立操作。
产品定位与目标用户
建议对外描述为:
OINK 是基于 Hugo 的本地优先文档工具箱,将工程知识发布给读者与 Agent。
安装说明和检索入口仍保留“Hugo 主题”,因为它准确描述用户安装的对象,而“知识编译器”用于概括内部架构。新的叙事不应遮蔽现有 Markdown/Hugo 路径。
优先服务使用 Git 的开源基础设施、开发者工具和多语言技术文档维护者。他们眼前的任务是让站点运行起来、定位故障、安全升级,以及在迁移中保留 URL 和内容含义。
首阶段明确不做可视化 CMS、托管账户、部署控制台、软件包市场、LLM 运行时、语义搜索服务或新渲染引擎。书籍、博客和落地页继续得到支持,但不由这些场景的功能清单驱动本轮路线图。
证据与对研究建议的调整
本提案参考用户提供的战略报告,并对照本地实现、双语 Design 专栏、Starter 和当前官方文档核实。
| 观察 | 产品含义 |
|---|---|
| OINK 已有公开 Starter、生成式配置 Schema、迁移脚本、出版工具和主题检查器 | 应把选定流程产品化,避免另起一套全量实现 |
| front-matter Schema 刻意不包含类型约束 | 严格检查要以对应解析器和真实 Hugo 输出为准 |
| 现有版本功能包括跨站菜单、归档横幅和可选的路径拼接 | 重点补齐生命周期与可靠的页面对应关系 |
| 当前版本文档明确采用各版本独立 Hugo 构建 | 首先延续该模型,不悄悄引入单次构建内的多版本渲染体系 |
| OpenAPI 组件在 HTML 之外只保留规范链接,并有明确的无障碍豁免 | 静态、无障碍的端点内容是一项具体的后续改进 |
| 反向链接已实现,G2/G3 仍为草案 | 图谱可视化仍处于草案阶段 |
bin/update-consumers.py 在当前工作树中作为本地维护工具 |
其版本解析与文件保护规则为 oink upgrade 提供设计参考 |
原报告强调了采用成本和可选工具层。以下四项调整能让它成为可执行计划:
- 将安全升级与初始化、诊断并列。现有用户已经有直接、可测试的维护需求。
- 区分维护者回归检查器与消费站检查。面向固定夹具的脚本不会自动成为通用站点校验器。
- 按明确的输入配置范围定义迁移边界,而非笼统的完整 Docsy 或任意 MDX 转换。
- 将同时开展版本化、OpenAPI、图谱和平台建设,改为逐阶段决策。
同类工具体现了这一方向的工程取舍。Mintlify CLI 提供预览、校验和链接检查;Nimbus 将脚手架与 Agent 可读产物结合,目前仍为 pre-1.0;Docusaurus 明确定义版本快照,也提醒其维护与构建成本。如果照搬 Nimbus 将整套界面源码交给用户的模式,会把升级维护工作转给 OINK 消费者。可以对小型内容模板借鉴该方式,主题本身仍保留可升级模块。
为什么独立建仓
| 方案 | 好处 | 代价 | 建议 |
|---|---|---|---|
继续扩充主题 bin/ 下的 Python 脚本 |
小型维护改进最快,可以同时修改并测试 | 安装分发体验弱,没有统一的公共命令契约 | 保留内部和历史工具 |
在主题根 Go 模块内添加 cmd/oink |
单一 checkout,源码修改可以原子提交 | 混合 Hugo 资源模块、应用依赖、二进制发布和消费站支持 | 不作为公共 CLI 的方案 |
| 在主题仓库中使用独立 Go 子模块 | 保留同仓修改,同时隔离 Go 依赖 | 仍需管理子模块标签与独立发布,也更容易直接调用未发布主题内部实现 | 可用于限时原型,不作为首选产品归属 |
新建 pgsty/oink-cli |
可执行工具边界清楚、独立发布,用户无需克隆主题内部工具 | 必须显式维护兼容性和跨仓验收 | 已接受;本地 Go 仓库已建立 |
这是发布与职责划分,不是说单仓在技术上不可行。嵌套模块能够隔离依赖;分仓也确实会带来协作成本:一次渲染行为变化可能需要两个 PR、配套契约与兼容性测试。OINK 已经采用主题、文档站和 Starter 分仓,只要公共边界足够小,这个成本可以接受。
主题与 CLI 不应强制使用相同版本号。建议 CLI 0.1.x 同时支持经过测试的主题 1.1.0 基线和下一受支持版本,按能力声明兼容范围。遇到不支持的功能应明确报告,不能拿最新主题的全部 Schema 去判断所有旧站点。
现在不另建 linter、迁移引擎、OpenAPI 生成器或共享 SDK 仓库,先作为 CLI 内部包。二进制可以用 Go 编写,同时不引用 Hugo 内部 Go 包,也不让主题模块依赖 CLI。
职责划分
| 范围 | 归属 | 边界 |
|---|---|---|
| 布局、组件、样式、导航、搜索、无障碍、输出语义 | pgsty/oink |
在 Hugo 与静态站点中运行 |
| 主题默认值、对应解析器、生成式 Schema、输出 Schema | pgsty/oink |
主题行为权威及其投影 |
| 主题实现检查与小范围非法输入夹具 | pgsty/oink |
继续作为维护者工具,允许使用 Python 或 JavaScript |
| 环境诊断、消费站检查、初始化、升级,以及后续迁移转换 | pgsty/oink-cli |
首期命令已在本地实现;迁移保持提案 |
| OpenAPI 解析与源码生成、后续版本快照编排 | pgsty/oink-cli 的拟议后续能力 |
生成普通 Hugo 输入,不负责最终渲染 |
| 小型官方站点骨架与语言配置 | pgsty/oink-starter |
CLI 初始化的单一来源;可将固定快照嵌入 CLI 版本 |
| 指南、案例、PRD、已接受理由、双语集成与浏览器验收 | pgsty/oink.pgsty.com |
继续作为公开文档与回归站点的权威 |
| 托管凭据、账户开通、部署授权 | 消费站工作流 | 使用现有 CI 与服务商工具;CLI 首发不执行部署 |
CLI 读取 Hugo 的生效配置、实际解析到的主题所发布的契约文件,以及构建产物。不应通过文件名猜测最终页面树,也不维护第二套导航解析器。Hugo config 已能输出生效配置;模块检查还需覆盖 replacement、workspace 与 vendoring。
下一期主题:建议 OINK 1.2
本节保持草案。本地 CLI 候选使用已发布的 OINK v1.1.0 基线;首期 CLI 决策既未接受主题 1.2 发布或新的工具能力描述,也不以它们作为前提。
本版本以降低采用成本为目标:站点能向工具准确说明配置与输出能力,升级不要求引入新的创作模型。范围应足够小,能够独立于后续大路线发布。
| 优先级 | 需求 | 验收 |
|---|---|---|
| P0 | 在已有 Schema 旁提供小型、带版本的工具能力描述,声明可用 Schema、输出契约与工具链边界 | 描述由对应实现校验;CLI 从实际解析的模块读取;不新增逐页产物或运行时请求 |
| P0 | 让少量高价值配置诊断直接指导修复:参数、非法值、允许形式、回退和对应指南 | 覆盖真实上手故障,如 Goldmark、输出与语言配置;保留普通预览告警、严格发布失败的约定 |
| P0 | 读者界面与机器产物继续共用导航和 Markdown 权威 | 现有输出、导航检查继续覆盖语言、顺序、子路径及可选输出;CLI 不另写渲染器 |
| P0 | 随版本交付经过测试的 Starter 快照与下游采用记录 | 将公开模块解析与同级替换构建分开验证,分别记录消费站 pin 和部署 |
| P1 | 原型证明有必要时,为工具消费的少数诊断加入稳定标识 | 每个标识有对应检查器;CLI 不依赖对所有人工告警文本的解析 |
能力描述属于发布元数据,不是新的配置权威。配置 Schema 继续从现有权威生成,可选形态校验仍归对应解析器与检查器。不要违反现有诊断决策,另建通用改名键注册表。迁移转换应属于明确的 CLI 配置范围,而不是模板中的永久兼容路径。
1.2 不要求增加新的视觉组件族。正确性、无障碍和已经发现的回归仍可驱动修改。现有媒体工作保留其独立验收范围,本路线图不把所有草案完成都变成发布条件。
CLI 首发:建议 0.1
下列命令已在本地 0.1.0-dev 候选中实现。当前参数、结果语义与限制由 CLI 契约及CLI 简介定义。
| 命令 | 用户结果 | 首发边界 |
|---|---|---|
oink doctor |
理解站点为何无法运行,或者本地环境为何与 CI 不同 | 检查 Hugo Extended 与版本、模块 pin 与实际来源、Starter/工具链要求、必要配置、启用输出;默认不修复 |
oink check |
知道发布构建和本地引用是否有效 | 在隔离输出中进行一次严格构建,再检查站内链接、锚点、资源与启用的机器产物;报告覆盖范围与不支持的检查 |
oink init my-docs |
得到一个无需 CLI 也能维护的小型中性站点 | 从固定 Starter 快照生成到新目录或空目录,选择支持的语言配置并固定主题版本 |
oink upgrade --to <tag> |
看清主题升级需要修改哪些内容 | 默认预览,--write 在验证后应用已审阅范围;保护无关模块依赖、用户修改与 vendor 内容 |
oink dev / oink build |
获得容易记忆的入口,无需学习第二套构建系统 | 轻量调用 Hugo,展示生效参数;build 使用发布严格度;始终支持直接使用 Hugo |
check 是统一质量入口。0.1 不同时设计职责重叠的 lint、validate、audit、check。后续确有需求时,用 --scope 区分源码提示和产物校验。
诊断与质量范围
先覆盖高置信度、可行动的失败:工具链不符、主题未解析、必要配置非法、本地链接目标或锚点或资源缺失、已启用输出的引用不一致。路由和锚点以 Hugo 产物为准,覆盖语言与 base path。不能因为编辑器 Schema 未列出某个自定义 front matter 字段,就把合法输入判成错误。
未启用的可选输出不应触发缺失错误。本地候选不检查翻译完整性;后续完整性规则必须使用站点实际声明的语言和覆盖政策。重复标题、孤儿页、缺少描述、文风与新鲜度仍属后续可选观察项,经过真实误报评审后再决定默认值。浏览器无障碍与交互检查继续由站点的 Playwright/axe 套件执行。
本地候选冻结 oink.result/v1:结构化诊断包含稳定规则 ID、严重度、已知位置、解释、行动建议与明确覆盖范围。JSON stdout 只输出结果,日志进入 stderr,所有命令均不等待输入。退出码 0 表示必要工作完成且无阻断项,1 表示政策问题,2 表示必要工作未完成。必需但不支持的检查不能返回成功。详细字段归属结果契约,不为构建派生问题编造行号。
保留 Hugo 原始错误作为子进程证据。将来可以从同一结果投影 SARIF,而不改变规则语义。
升级与文件保护
现有消费站升级脚本提供了有价值的本地先例:区分声明 pin 与解析版本,发布验证时禁用两套 workspace,识别模块 replacement,检查 _vendor。通过聚焦测试将这些行为直接移植进独立 Go 二进制。
0.1 只升级一个明确选择的站点。跨站批量发现暂留维护者脚本,等真实消费者提出需求。普通 check 可以检查有意使用本地主题替换的环境;check --release 必须排除这些替换,验证声明的公开版本。发现 go.mod 中冲突的 replacement 应报告,不应擅自删掉。
先展示将修改的文件,验证候选升级,再应用。只备份涉及的文件;预览后文件又发生变化时拒绝覆盖;保留无关的未提交工作。失败时应说明哪些已应用、哪些未应用,并提供不会覆盖后续编辑的恢复路径。仓库不干净不应阻止只读诊断。刷新 vendor 是单独的显式动作。
初始化和修复不能顺便 commit、push、部署、修改全局 Agent 设置或安装系统包。向明确的新目录初始化,本身就是用户请求的创建动作;修改既有文件则默认预览。
分发与离线行为
本地候选已在 macOS arm64 上提供经测试的源码/Make 安装路径与归档准备,其余归档目标支持交叉编译。公开 Homebrew formula、下载入口及标签安装仍属后续分发工作。
采用 Go 可执行文件,提供发布归档、校验和与 Homebrew 安装方式。先正式验证实际测试过的 macOS、Linux 架构,其它平台在文件系统和进程行为验证前标记为实验支持。安装后的 CLI 本身不要求安装 Go 工具链、Python、Node 或注册账户。Hugo 仍为外部渲染器;首次模块解析仍需要站点文档规定的 Git、Go、Hugo 工具链。
嵌入或随版本提供准确、保留许可证的 Starter 快照,保证初始化可重复。不应每次抓取变化中的 main,也不在 CLI 中手工维护 Starter 配置副本。发布 CLI 时检查内嵌模板与来源的一致性。
区分冷安装与离线运行。未在本地提供时,下载 Hugo、主题或未缓存模板需要网络。依赖齐备之后,本地诊断、检查和构建路径应无需外部服务。离线请求遇到缓存缺失应明确失败,不静默联网抓取。外链检查、远程规范等联网行为单独启用。无需默认遥测或后台更新检查。
第一项扩展:迁移
从实际候选站点中选择一套 有文档说明的 Docsy 输入配置范围,参考现有迁移夹具和报告模型。除 Markdown 语法外,还必须检查配置、导航、资源、语言和路由。
拟议流程为 oink migrate --from docsy --source <site> --output <new-site>。先评估再写入;应用需要显式参数,并输出到独立目录。每个源项目只有一个主要状态:原样兼容、已转换、人工复核、不支持。数量必须能够核对,附理由与源码位置。自定义模板和动态行为应明确列为人工工作。
验收包括源文件保留、支持范围内的转换幂等、不修改字面代码示例、站内引用有效,以及明确的旧新路由对照。优先保持 URL;改变 URL 时须提供适合托管目标的重定向方案。
任意 JSX、import、内嵌 React/Vue 是程序,不能执行不受信任的源码来猜测含义,也不能静默删除无法处理的内容。第一条配置范围在没有维护者救场的情况下得到复用后,再开始第二个框架。完整 MDX 迁移属于后续产品投入。
下一项内容能力:版本生命周期
首个 CLI 有用之后,默认优先考虑版本生命周期,因为它延续 OINK 已有的独立构建模型。只有真实 API 用户提出更强、反复出现的需求时,才把 OpenAPI 提前。单一主维护者不同时实现这两个基础。
主题负责读者界面的版本身份、可靠页面切换、归档状态,以及范围正确的搜索和机器输出。CLI 负责查看版本、准备快照、验证页面对应关系、修改声明的生命周期状态。oink --version 表示可执行文件版本;将来的 oink docs version ... 避免与文档版本混淆。
优先采用小型版本清单,记录版本标签、源引用、base URL、状态和默认选择。保留各版本独立构建及现有外部归档。CLI 管理的清单可以生成纳入 Git 的 Hugo 配置;在该模式中,清单由用户维护,配置是接受检查的投影。现有手工管理的 params.versions 继续受支持。原型必须先确定投影关系,再冻结格式。
页面对应关系需要按文档族、语言、版本区分的逻辑页面键。优先复用合适的 translationKey 或显式稳定键,不先引入通用 UUID。目标版本缺页时应明确说明,并进入约定的版本或分区首页,不能伪造等价页或盲目拼接 URL。路由别名处理页面搬迁,与页面身份分开。
内容有实质差异的历史页通常保留自己的 canonical URL,不能全部指向最新版。语言 alternate 应指向同版本中确实存在的翻译。默认搜索与 Agent bundle 保持在当前语言和版本范围内;将来若有跨版本聚合,必须显式启用。归档应保留源码并记录构建产物如何保存,不等于删除,也不悄悄重新部署不可变归档。
NAVJSON v1 的对象 Schema 当前禁止额外字段。因此增加版本或身份字段需要明确的新 Schema、输出契约或独立产物,不能声称是无影响的 v1 字段扩展。仅仅为未来图谱预留空间,不足以成为修改当前页面身份的理由。
随后的能力:静态 OpenAPI 参考
首个 OpenAPI 产品应是 只读静态参考生成器。CLI 解析本地规范及支持的本地引用,输出普通 Markdown/Hugo data,记录来源。主题提供符合无障碍要求的语义呈现和已有输出流水线,再由普通 Hugo 将生成页构建为 HTML、Print、Markdown、搜索和 Agent 索引。
先支持操作、参数、请求与响应正文,以及带链接的 Schema 描述。解析器原型完成后明确支持的 OpenAPI 版本和构造,不能静默丢弃不支持的构造。操作身份按 API/规范分域;缺少 operationId 时可由 method/path 派生,同时提示路径变化会影响身份。不同 API 复用同一 operationId 不能发生冲突。
人工指南与生成事实分开保存。生成必须确定、记录源哈希和生成器版本、能够检测过期输出,遇到非预期人工修改拒绝覆盖。将生成源码纳入站点 Git,或作为带版本的构建输入提供,使渲染本身仍不依赖 CLI。解析远程引用属于显式准备步骤,普通生成不能遍历任意外部 URL。
验收除玩具示例外,还要有真实用户规范;支持的操作完整可核对,循环引用能够处理,路由稳定,选定输出均包含语义内容,新静态渲染器的无障碍检查不继承 Swagger/Redoc 豁免。承诺吞吐指标前先测有代表性的大型规范。
保留现有 Swagger/Redoc 集成的兼容性。交互请求、凭据管理、SDK 生成、mock server 和 API 测试平台不进入本轮生成器增量。
架构与兼容规则
CLI 内部保持简单:命令处理、Hugo/进程适配、诊断、模板加载、限定范围的文件修改。迁移和 OpenAPI 到达对应阶段后再增加内部包。这只是建议分层,不是插件 ABI 或公共 SDK。
三个边界需要版本化:CLI 的机器结果格式、主题的公共 Schema/输出契约、受支持的迁移或生成输入配置范围。优先按能力检测,不一刀切要求“最新 OINK”。遇到更新但不支持的 Schema 时,必须给出可理解的兼容性诊断。
CLI 不能导入同级主题的私有 Python 模块、依赖特定本地 checkout 布局,或者运行时下载可执行检查器。通过行为测试移植选中的消费站操作。模板内部检查器仍留在主题;公开消费规则的后续变化同步更新其对应契约。过渡期间保留现有脚本,替代实现覆盖受支持场景后再退出重复实现。
初期不要求 CLI 配置文件,渲染配置继续由 Hugo 管理。重复使用证明有必要时,工具政策文件可以包含忽略路径、规则严重性和经过审阅的基线,但不能再维护 params.ui、导航、语言或模块 pin 的副本。Lint 基线不能豁免 Hugo 构建失败、输入不可读或必需检查不受支持。
路线图与人力假设
阶段 0 与阶段 1 已有首期本地候选,验证细节见验收记录。下列 首阶段 8–12 周的规划范围 假设约一名全职实现负责人,并有部分文档与评审支持。工具链验证、用户招募和双语评审都需要时间;应优先缩小范围,避免多线并行。
| 阶段 | 自批准起的时间 | 交付物 | 退出证据 |
|---|---|---|---|
| 0:确定边界 | 第 1–2 周 | 接受仓库选择,收集真实故障,定义结果格式与支持基线,在当前 1.1.0 上原型验证只读 doctor/check | Starter 加至少三个不同形态的真实仓库;记录失败与覆盖缺口 |
| 1:完成日常流程 | 第 3–6 周 | doctor/check、固定模板 init、轻量 dev/build、单站候选升级与文件保护测试 | 新用户能定位预置故障;生成站仍可直接 Hugo 构建;没有无法解释的源码修改 |
| 2:发布有边界的产品 | 第 7–12 周 | 拟议主题 1.2、CLI 0.1、兼容记录、文档、经过验证的安装方式、有限 Docsy 迁移评估与试点 | 首次使用测试与重复升级使用;迁移限制明确;公开 pin 与下游采用单独验收 |
| 3:验证迁移及一个内容模型 | 第 4–6 月 | 完善首条迁移路径;按用户证据选择版本生命周期或 OpenAPI;需要时规划主题 1.3 / CLI 0.2 | 至少两个真实仓库使用所选流程;接受契约后才承诺兼容性 |
| 4:证据支持的扩展 | 第 6 月之后 | 另一项内容能力,再按需求引入模板、来源信息或 Agent 传输层 | 有重复使用与维护能力 |
如果阶段 2 超期,移除该版本中的迁移写入能力,保留评估报告。不能削减升级保护、真实诊断或 CLI 可选性。如果没有独立团队需要这套迁移配置范围,就停止扩充框架覆盖,回到上手体验与定位研究。
新鲜度、归属信息是后续可选质量能力,先以用户确实会处理的报告验证价值。修改日期绝不能冒充验证日期。先提供少量有用的官方页面模板,再考虑 registry。G2/G3 图谱、MCP、分析适配器、可执行示例、Studio 和托管服务,都需要具体用户问题及资源决策,不应现在填入确定日程。已有静态 Agent 输出,使 MCP 的紧迫性低于采用流程。
验收与产品指标
| 范围 | 初始目标或必须满足的性质 |
|---|---|
| 首次成功 | 前置工具已安装时,5 名不熟悉 OINK 的目标用户中,至少 4 名在 15 分钟内无需维护者干预就完成预览与严格检查;冷安装另行记录 |
| 维护价值 | 至少三个真实站点使用诊断与检查,并重复完成受支持升级;每次失败均有可行动报告 |
| 诊断准确性 | 试点中逐项复核阻断发现,在明确计数、人工标注的样本中争取误报率低于 5% |
| 完整性 | 零静默内容丢失;迁移输入逐项可核对;重复转换无差异;用户编辑和无关依赖得到保留 |
| 独立性 | 依赖准备好后,初始化或生成的站点可以直接用 Hugo 构建;CLI 与可选输出仍可选择 |
| 离线行为 | 依赖准备完成后,在禁止出站访问的环境验证受支持本地流程;缓存缺失和显式联网功能分别记录 |
| 兼容性 | 当前经过测试的主题基线与候选版本、固定的站点回归工具链、根路径和子路径、中英文场景 |
| 采用情况 | 首阶段争取五个独立试点团队,跟踪其是否进入生产及在 30/90 天后继续使用 |
主要采用指标使用独立维护的生产站点,通过公开引用或用户自愿确认核实。稳定文档站不应因为 60 天没有提交而退出统计;主题升级时效与留存分别衡量。Star、下载次数、自有消费站数量和 Agent 生成量作为辅助参考信号。
首次本地成功时间、首次生产发布时间、升级成本、迁移人工成本分别记录。不要为收集指标引入默认遥测。
实现归属与验证
| 改动 | 对应验证 |
|---|---|
| 工具能力描述与 Schema 兼容 | 聚焦的主题描述检查器,以及 generate-config-schema.py --check 和相关参数检查 |
| 暴露给消费者的诊断 | 对应解析器与检查器用例,CLI 诊断结果和退出码测试 |
| 已有输出行为 | 按变化范围选择 check-agent-indexes.py、输出、安全和导航检查 |
| 初始化与升级 | 固定 Starter 快照,以及包含 replacement、vendor、无关依赖、目标文件未提交修改的 CLI 测试仓库 |
| 迁移 | 移植并扩充转换用例、源文件保护与重复运行检查,真实站点路由和内容复核 |
| 后续版本与 API 呈现 | 主题输出检查,以及双语文档站的集成、浏览器、无障碍、响应式和视觉评审 |
先运行最小归属检查。公共行为变化仍要求实现、检查器和双语契约协调交付。真实集成与视觉验收使用同级站点的 make check、make browser、make dev 流程。
发布时分别记录本地检查、提交、标签、公开模块或二进制解析、消费站 pin 与部署。主题发布后的采用继续使用现有消费站盘点流程。一个协调 issue 或清单即可连接各仓库。
待决问题与停止条件
仓库选择与首期实现在本地已确定。剩余发布决策包括经过验证的平台、公开分发渠道、有实际执行证据支持的兼容声明,以及独立试点招募。
本地候选的结果与退出语义已在 CLI 契约中冻结。最小主题能力描述与稳定主题警告 ID 保持独立提案。版本 beta 前确定清单投影、归档保存方式和页面对应关系;OpenAPI beta 前确定支持的规范子集与生成源码归属。
如果试点实际只需要一个小型维护脚本,规则必须反复复制模板语义,或者分发维护成本超过测得的用户价值,就重新评估独立 CLI 投入,并保留已经有用的独立脚本。证据变化时可以调整版本化与 OpenAPI 的顺序,不增加同时开展的总范围。
决策日志与来源
| 日期 | 记录 |
|---|---|
| 2026-09-29 | 根据提供的战略研究与本地源码评审创建草案。建议独立可选 CLI、小型采用版本、有边界的迁移和依次推进的内容能力。 |
| 2026-09-29 | 随后用户授权并接受独立 Go 仓库与首期开发。本地 0.1.0-dev 候选已实现 doctor/check/init/upgrade/dev/build;稳定行为移入 CLI 决策与使用指南。CLI 提交 e623d93 已通过本地验收。 |
查阅的本地权威包括:架构、生成式 Schema 决策、迁移边界、现有版本行为、OpenAPI 限制、图谱提案状态。还检查了主题 bin/、schema/nav.v1.schema.json、现有 Starter 与文档站构建检查命令。
外部一手资料核实日期为 2026-09-29,包括上文链接的 Mintlify 命令参考、Nimbus 仓库、Docusaurus 版本指南及 Hugo 配置和模块文档。
7.9.4 - OINK CLI 文档维护路线图
本记录保留 R1–R8 的原始需求排期与锚点。当前 CLI 行为归属 CLI 契约与CLI 简介,历史验收证据归属 2026-10-04 增补。 条件性扩展 E1–E4 尚未启动。
先完成文档维护,再建立本地可视化工作台。产品应当帮助维护者检查修改、理解影响、 审阅安全变更,并发布刚才通过检查的同一份产物。Studio 使用这些相同能力。
| 记录 | 内容 |
|---|---|
| 状态 | R1–R8 与 A18 已在本地实现并完成验收 |
| 负责人 | OINK 维护者 |
| 日期 | 2026-10-03 |
| 已有基线 | 本地 CLI 0.1.0-dev,提交 e623d93;macOS arm64 上的 Hugo Extended 0.166.0 与 Go 1.27.1 |
| 完成范围 | R1–R8 及下文验收用例;条件性扩展另有启动条件 |
| 受影响范围 | CLI 命令与结果契约、Starter 投影、消费站 CI、翻译政策、维护操作、本地 Studio、中英文指南 |
| 排期假设 | 一名全职开发,配合定期文档与评审支持 |
背景与证据
原 CLI 路线图已接受独立 Go
可执行文件,将首次实现收敛为 doctor、check、init、单站点 upgrade、
dev 与 build。本提案增加有明确边界的文档维护计划。Docsy 迁移、版本生命周期、
OpenAPI 生成与主题 1.2 保留各自范围。
2026-10-03 的本地盘点重新执行了 Go 套件与真实 Hugo 集成测试。初始化的双语站点 检查通过,覆盖 223 个文件、4,461 条引用。PIG 消费站检查通过,覆盖 1,392 个文件、 64,440 条引用;858 个源文件及 Git 状态保持不变。
盘点还复现了四项限制:渲染链接缺失时 check 失败而 build 成功;普通 HTML
引用越出配置的 base path 时被标为未检查;doctor --release 接受仍使用
https://example.org/ 的 Starter;两份内嵌部署工作流都直接调用 Hugo,没有执行
CLI 的附加检查。翻译完整性与可读升级 diff 也尚未实现。首批增量由这些发现确定。
产品目标与用户
优先服务多语言工程文档维护者,以及维护多个 Hugo 站点的小团队。高频任务是审阅 翻译、防止发布损坏内容、更新依赖,以及在不丢失引用和公开 URL 的前提下整理文档。
普通消费站能在本地与 CI 使用同一个质量入口,查看受影响页面,审阅并应用修改, 同时保留无关工作,才算实现产品目标。CLI、Studio 与 Agent 调用应得到相同的发现项 和修改计划。
功能取舍
| 原设计能力 | 决策 | 交付阶段 |
|---|---|---|
| 内链、锚点、附件与机器产物 | 加强已有检查,说明未覆盖情况 | R1–R3 |
| 环境诊断、预览与严格构建 | 补齐发布诊断,增加显式的已验证构建流程 | R1、R3 |
| 翻译完整性与受保护结构 | 作为核心产品能力建设 | R2 |
| 初始化与 CI 配置 | 扩展固定 Starter,管理可审阅的 CI 修改 | R3–R4 |
| 原生内容规则与项目风格 | 实现少量确定性核心规则,通用工具按需接入 | R2、R6 |
| 新建内容、片段与编辑器配置 | 生成普通 Hugo 输入,保护已有文件 | R4 |
| 安全升级与迁移预检 | 增加 diff 和候选对比;框架迁移保持独立范围 | R4 |
| 页面移动、重命名与影响分析 | 页面关系与修改计划可靠后再实施 | R5 |
| 问题面板与翻译对照 | 先做只读的本地 Studio | R7 |
| 多站点 | 在单站引擎上增加显式站点登记 | R6 |
| EPUB、PDF 与离线打包 | 对可分发出版工具的条件性适配 | E1 |
| 可执行文档示例 | 使用显式执行配置的条件性功能 | E2 |
| Agent 检查、影响与上下文 | 实现确定性的本地操作 | R5 |
| AI 翻译与语义审阅 | 确定性维护流程可用后,再验证修改提案 | E4 |
| 来源、证据与知识依赖 | 本轮限定为构建及审阅来源、已观察到的页面关系 | 更广的知识管理延后 |
| 富文本编辑、实时协作与原生桌面端 | 只交付安全 Markdown 编辑;更大的平台延后 | R8;其余暂缓 |
范围与非目标
R1–R8 是本 PRD 有限且明确的完成范围,每阶段都能独立产生价值并验收。拟议 CLI
版本 0.2、0.3 与 0.4 仅标识候选交付,不要求创建对应公开标签,也不绑定主题版本。
本计划不包含新渲染器、通用迁移引擎、托管账户管理器、部署 API、内置 LLM、向量 数据库、远程编辑器、实时协作、原生桌面壳或完整所见即所得编辑器。部署由既有 服务商工作流完成;发布权限与凭据继续由消费站所有者管理。
共享项目事实与检查政策
此范围已本地接受。下文保留原始提案需求作为历史;当前行为与参数归 CLI 契约所有。
扩展已有隔离 Hugo 分析,不另建配置解析器或导航权威。拟议内部事实包括页面身份、 语言、发布状态、实际输出 URL、已知源文件、翻译关系及已观察到的渲染引用。
使用 Hugo 公开的 Page.Translations 与 Page.OutputFormats 获取关系和产物。 Page.File可提供来源,但部分页面没有对应文件。 这些发现项必须保留产物位置和源码未知状态。临时探针移除后,普通发布产物的字节 应保持不变。
仅用 oink.yaml 管理检查选择、严重度、翻译政策、已审阅排除项及工具和流程选项。
语言、标题、菜单、URL 与站点配置继续归 Hugo,主题版本归模块文件。先提供共享
同一次分析的 check links、check translations 与 check style。保留 --json;
--format json 可以作为兼容性的新增别名。
阻断错误、警告与建议沿用 error、warning、info 严重度。必需工具或输入形态
不受支持时仍返回退出码 2。政策不能把构建失败、输入不可读或必需检查未完成降级
为成功。源码位置需要可靠映射;无法定位时报告实际产物和 pointer。
翻译维护
此范围已本地接受。下文保留原始提案需求作为历史;当前行为与参数归 CLI 契约所有。
支持 Hugo 解析的文件名语言、独立语言内容目录与 translationKey 关系。覆盖政策
在明确的内容范围内选择必需语言;已禁用语言和有意本地化不能变成缺译错误。
检查重复身份以及政策指定的草稿和发布状态。生产构建未包含评估政策所需的源文档时,
使用明确的分析视图;不能把分析视图当成可发布产物。
提供两类政策:技术手册使用严格对译,博客或产品页面使用本地化内容。严格政策可 要求显式 ID、声明的占位符、指定代码块和必要字段一致;本地化政策只检查明确声明 的共同约束。标题数量相等、所有代码块相等都不能成为普遍要求。
拟议提供 translations status、translations diff <page> 和显式的审阅记录操作。
带版本的记录将译文绑定到源文档内容哈希或 Git 修订,并记录译文哈希与声明的源语言。
没有记录表示未知;哈希改变表示审阅后有变更,不自动断言翻译错误。记录审阅必须
来自用户要求的写入,检查器运行本身不能自动生成已审阅状态。
原生内容规则与问题基线
此范围已本地接受。下文保留原始提案需求作为历史;当前行为与参数归 CLI 契约所有。
先从真实消费站故障中提取少量高置信度规则:受支持组件及属性写法非法、显式 ID 冲突、已知弃用形式,以及项目配置的受保护内容。代码块、行内代码、短代码正文、 原始 HTML 和属性块需要各自的语法边界,不能无差别套用正则。
使用实际生效主题版本的契约。没有类型约束的编辑器 Schema 不能作为完整严格验证器。 缺少兼容元数据时,应明确限制覆盖范围,不能拿最新主题规则验证旧项目。基本检查 完成不以未来主题发布为前提。
可见且带版本的问题基线可以用稳定指纹、原因和审阅元数据确认已有发现项。报告 分别显示已确认项和新增项。基线更新必须显式、可审阅,不能隐藏必需检查未完成。 格式化与文风建议属于可选项。自动修复先生成 diff,再验证候选,最后只应用少量 边界明确的文件。
已验证发布产物与 CI
此范围已本地接受。下文保留原始提案需求作为历史;当前行为与参数归 CLI 契约所有。
保留当前 build 默认的透明调用。增加显式受管理的 build --check 流程:严格运行
一次 Hugo,在同一份输出上执行选定检查,再把已验证产物导出到新目录或空目录。
不能删除任意目录,也不能把旧文件混入已验证产物树。默认 build 必须继续说明
附加检查尚未执行。
本地版本化 manifest 记录已知的源码修订与修改状态、源码输入哈希、实际主题身份、 Hugo/CLI 版本、构建设置、base URL、检查覆盖与文件摘要。秘密和本机路径不得进入 公开元数据。启用公开构建标识时,只保留验证所需的最少身份信息。检查后产物字节 发生变化,已有检查结果就不能继续证明该产物。
拟议提供 ci init github-pages 和 ci init cloudflare-pages --mode direct-upload。
只生成本地配置,解释变量与权限,记录模板来源。发现已有工作流时展示 diff,保留
未知修改,并要求显式应用。两种模板使用相同质量引擎,上传已验证输出,中间不能
再运行一次 Hugo。CLI 尚无公开版本时,模板必须接受明确记录的不可变源码或归档
输入,不能假设某个下载标签已经存在。
发布诊断增加示例地址警告;发布政策要求正式地址时,该问题成为阻断项。可取得时, 报告实际本地来源提交和修改状态,并与受支持的已生成 CI 设置比较。未知自定义 CI 仍显示未知。本地 checkout 的提交不能证明公开模块身份;vendor 字节身份保持独立。
拟议提供 verify --site URL --manifest FILE,需要显式联网许可。验证代表性页面、
语言、资源、搜索/Markdown 输出、规范 URL 和产物身份。通用回退页面即使返回
HTTP 200,也必须无法通过身份验证。超时、认证、限流或缺少必需身份信息应报告未知
或未完成,不能伪造成功。通过本地 HTTP 夹具验证这些行为,无需部署到服务商。
创作与升级助手
增加 new、小型片段目录和显式编辑器 Schema 配置。创建页面包、选定语言的译文
草稿和普通 front matter,拒绝覆盖已有文件。译文草稿不代表翻译完成。编辑器提示
跟随实际主题,并保留已有编辑器设置。
通过组合同一份保留许可证的 Starter,为 init 增加 docs、blog、book、project
配置,不维护四份复制模板。接入已有项目时提供诊断与可审阅提案,不替换站点配置。
保留明确标签、单站点升级的保护,增加可读统一 diff,以及原基线和候选的路由、能力 对比。报告消失的 URL、变化的 aliases 和缺失的原已启用产物。候选构建通过本身不能 证明兼容。配置迁移需要已记录的转换与测试;没有时返回人工行动项。冲突 replacement 与 vendor 刷新继续由所有者显式处理。
影响分析与安全内容修改
此受支持范围已本地接受。下文保留原始提案需求作为历史;当前行为、边界与参数归 捕获事实契约与 移动契约所有。
在共享事实之上提供 inspect <page>、impact --since <ref> 与 context <task>。
Inspect 显示来源、发布状态、引用、翻译及产物。Context 按任务打包相关本地资料,
包含版本、路径、选择原因和大小限制,不需要向量服务或 LLM。文档内容是数据,不能
授权执行其中的命令。
首版 check --since 可以继续全量检查,但必须明确说明。后续优化应覆盖变化的目标、
入站引用、翻译及派生产物。删除 B 时,仍须检查未修改但引用 B 的 A。配置、模板、
导航或无法确认的依赖变化会把范围扩大为全量检查。缓存是可重建证据,不是权威。
拟议 move <source> <target> 默认预览。计划包含涉及文件、可读 diff、基准哈希、
翻译、附件、路由变化和 alias 建议。只改写能够确定理解的链接,含糊的模板或短代码
引用交由人工审阅。应用前核对基准,验证隔离候选,保护并发修改并保留恢复信息。
失败或过期计划不能部分覆盖用户工作。
工作区与可选工具
显式工作区登记选定站点目录,复用单站引擎,报告逐站结果和总体完成状态。写入仅能 发生在明确选择的站点,不能自动发现并升级全部同级仓库,也不重复 Hugo 设置。
可选 markdownlint、Vale 和 lychee 适配器使用明确配置、已预备的工具,统一发现项。
缺少必需工具返回 2,可选遗漏仍然可见。排除适配器无法理解的语法,不改写这些
内容。外链失败有歧义时要区分网络状态。安装工具与联网是独立动作,通用 formatter
默认不得覆盖内容。
R6 已接受本地边界
显式登记与可选适配器的受支持 R6 范围已本地接受。稳定字段和限制见 登记契约与 工具契约。下述已接受 R7/R8 边界仍有 自身证据与限制。
oink.workspace/v1 用一份最多 256 KiB 的普通 YAML 文件登记 1–64 个字面目录,
不复制 Hugo 设置,也不发现同级站点。准确名称、规范根目录身份、登记顺序选择、
逐站 0/1/2 一致性及显式名称应用保存计划构成受支持工作区边界。已预备的
markdownlint-cli 0.49.1、Vale 3.24.0 与 lychee 0.24.2 扩展各站政策,
捕获配置并提供类型化协议/源码/网络覆盖;不安装工具或格式化内容。Lychee 需要
显式联网授权;有歧义的外部失败保留未知,不判为确定断链。
冻结 Go/vet、实际 Hugo/固定工具及归属 race 门禁已经通过。精确二进制也通过 四站直接/汇总诊断/覆盖/退出一致性,以及全部源码字节/完整模式/Git/被忽略输入/ 目录保护。初次预备失败仍不计作验证通过证据;仅验收驱动命令元数据的收据修正 独立记录,没有 CLI 运行时修正或重跑。受保护规范中英文源码/渲染检查通过, R6/A07/A15 受支持范围已本地接受,见 R6 记录。 当前 A18 运行时/归档验证通过;Darwin amd64 保持实验/未验证。
只读 Oink Studio
建设本地 Web 界面,提供项目总览、问题面板、翻译对照、页面关系和发布面板。 这些视图使用与 CLI/CI 相同的核心结果,支持筛选、跳转已知来源、真实 Hugo 预览、 变更对照和复制建议。大型图谱或内置编辑器不是这一阶段验收的前提。
默认仅监听本机,明确允许访问的站点。将不可信渲染内容与管理界面隔离到不同 origin; 增加写 API 前,做好 Host/Origin 检查和会话授权。UI 预构建后随 CLI 分发,Node 是 贡献者构建依赖,不是消费用户运行依赖。覆盖键盘操作、屏幕阅读器标签、移动端布局、 深浅色和长问题列表的可读性。
R7 候选边界
只读 Studio 候选现已基于同一原生检查与捕获 Hugo 事实,提供内嵌五视图浏览器和 鉴权字面回环 API。稳定候选边界见契约。显式现存站点/登记选择、类型化分页发现、源码/ diff/哈希状态、真实生产预览及独立可选分析覆盖保留 CLI 权威。
冻结原生/浏览器/核心案例及精确二进制四消费者收据现已验证受支持只读范围,
包含明确局部预览未完成状态。它们覆盖原生 0/1/2 一致性、键盘/移动端/深浅色、
字面源码数据及独立 origin 预览攻击。R7/A16 已通过受保护规范晋升/渲染门禁并
本地接受。R1–R8 受支持范围已接受;
当前 A18 运行时/归档验证通过。
安全 Markdown 编辑
只读工作台验收后,增加 Markdown 编辑、front matter 表单、选定组件插入和附件。 复用 CLI 修改计划引擎及真实 Hugo 预览,不建立第二套保存与验证机制。
没有修改的打开/保存周期必须保留原始字节。更新一个字段应保留未知字段、注释、顺序、 编码和无关空白。检测外部编辑器修改,拒绝过期保存。表单无法保留某种 front matter 构造时,保留文本编辑并说明表单限制,不通过通用序列化器重新输出整篇文档。
写入需要已授权本地会话、允许目录、基准校验和可见 diff。拒绝目录穿越、符号链接 越界以及来自不可信预览内容的请求。附件不能覆盖既有文件。发布静态站点不会把管理 API 一并发布出去。
R8 已接受编辑边界
R8 已为 CLI edit text、field、snippet、attachment 及显式
studio --edit 实现同一源码保护提议引擎,默认 Studio 保持只读。已知站点所有
Markdown、准确源码哈希、支持顶层 YAML 标量/文本回退、原生目录字节边界插入及
仅新建 leaf-bundle 附件共享同一保存计划和保护写入器。完整可见审阅绑定计划/文件/
完整模式身份;候选 HTML 来自实际选定不可发布 Hugo 分析,原生发现与必需视图
未完成保持不同。
已接受本地接口见契约。修正冻结公共/Go/race/vet、实际/普通 Hugo、Editor 浏览器/无障碍/移动端及精确二进制四消费者保护门禁已通过。 R8/A17 受支持本地范围也通过受保护规范源码/渲染门禁并已接受,记录于 R8 记录。 R1–R8 受支持范围已接受;当前 A18 运行时/归档验证通过。Darwin amd64 保持实验/未验证。
交付顺序与排期
以下按一名开发估算。T0 是范围批准后的实施起点。阶段依赖由顺序验收门禁约束;增加人员可并行独立测试与 UI 工作。
| 阶段 | 有效工作周 | 交付内容 | 验收门禁 |
|---|---|---|---|
| R1 | 第 1–2 周 | 共享事实、检查政策、分类检查、可信位置 | Hugo 拥有路由和关系;必需检查未完成不能通过 |
| R2 | 第 3–5 周 | 翻译政策及审阅状态、原生检查、可见基线 | 三种语言组织方式;严格/本地化用例;审阅修复保留文件 |
| R3 | 第 6–8 周 | 已验证构建产物、CI init、发布诊断、公网站点验证 | 上传同一份已检查产物;检测字节漂移与 HTTP 200 回退 |
| R4 | 第 9–11 周 | 新建内容、配置组合、片段/编辑器配置、升级 diff 与对比 | 普通 Hugo 可构建;脏文件、替换、vendor 与路由回归仍安全 |
| R5 | 第 12–15 周 | Inspect、Impact、Context、Move 与共享修改计划 | 包含未改入站引用及翻译;过期计划不能写入 |
| R6 | 第 16–17 周 | 显式工作区与可选检查适配器 | 逐站结果一致;必需工具缺失为未完成;不隐式安装 |
| R7 | 第 18–20 周 | 只读 Studio 与安全边界 | 五个实用视图;CLI/UI 发现项相同;无障碍与预览隔离通过 |
| R8 | 第 21–24 周 | 安全 Markdown/表单/附件与冲突审阅 | 无修改保存零 diff;注释和未知字段保留;并发保存安全失败 |
另留 4–6 周用于集成、误报审阅、跨平台执行与修复,分配到各验收门禁。总规划范围为 28–30 个有效工作周(半职投入时日历跨度约翻倍)。
R1–R3 形成拟议 0.2 质量与发布候选,计入早期预留后约在第 9–10 周。R4–R6
形成拟议 0.3 维护候选,累计约第 19–20 周。R7–R8 形成拟议 0.4 本地 Studio
候选,累计约第 28–30 周。
条件性扩展
| 扩展 | 启动条件 | 拟议边界 | 独立估算 |
|---|---|---|---|
| E1 出版导出 | 至少两本维护中的书需要重复执行导出流程 | 复用可分发 EPUB/PDF 工具,打包本地产物,声明外部依赖 | R3/R4 后 1–2 周 |
| E2 可执行示例 | 明确的所有者指定可运行示例及可丢弃环境 | 审阅的执行配置、时间和资源上限、默认离线;不自动执行发现的正文 | R5 后 3–5 周 |
| E3 MCP | 既有 Agent 集成确实需要 JSON CLI 调用以外的能力 | 对 inspect/check/impact/context/plans 的薄适配,沿用权限和诊断 | R5 后 1–2 周 |
| E4 AI 审阅与翻译 | 确定性翻译维护可用,且已有审阅过的评估语料 | 用户选择服务商,显式网络及费用设置,提案绑定源码哈希;不自动写源文件 | R5 后 3–6 周的有限实验 |
这些估算不计入 R1–R8 总工期。扩展只在相应场景成立时启动;未来需求不是尚未完成 的核心里程碑。远程 Studio、实时协作、原生壳、通用知识来源管理、向量检索与通用 框架迁移需要独立 PRD 和证据。
架构与兼容
核心操作继续用 Go,通过子进程调用 Hugo 和可选工具。已有包拥有相应行为时就在 其中扩展,新包随真实能力加入。实际消费者需要之前,不建设通用插件平台、公共 SDK 或共享服务层。
保留 oink.result/v1、退出码含义和默认薄包装。诊断详情与命令数据可增加字段,
改变字段语义则需要新结果版本。审阅记录、基线、计划、构建 manifest 和工作区登记
分别版本化。从实际主题检测受支持能力,不强制全部用户安装最新版本。
读取/检查/预览、应用本地文件、联网、执行示例和部署是不同副作用。维护操作不顺便 进行遥测、后台更新、发现凭据、清理任意目录、修改全局配置、提交、推送或部署。 消费站只读试点保留源码、replacement、workspace 和 vendor 字节。
验收用例与归属检查
| 用例 | 必须达到的结果 | 主要归属 |
|---|---|---|
| A01 JSON 与完成状态 | stdout 只有一个 JSON;日志分离;发现问题为 1,必需未完成为 2 |
internal/report、internal/app、Schema |
| A02 Hugo 权威 | Slug/url/permalinks/aliases、自定义挂载、未列出页面与语言根遵循真实 Hugo 结果 | internal/site、internal/outputcheck、真实 Hugo 夹具 |
| A03 子路径 | 确定属于项目的缺失路由失败;越出 origin/path 的引用真实分类;声明外部范围避免误报 | 产物检查与政策测试 |
| A04 翻译 | 文件名、目录及 translationKey;重复/缺失/草稿场景;严格/本地化政策 | 翻译引擎与公共命令测试 |
| A05 审阅状态 | 无记录为未知;源哈希改变可见;mtime 不决定审阅状态 | 翻译与审阅记录测试 |
| A06 内容语法 | 围栏、行内代码、短代码、HTML、属性、自定义字段及受保护文本不产生虚构问题 | 原生规则测试与真实内容语料 |
| A07 基线与适配器 | 已确认问题保持可见;新问题按政策失败;必需工具缺失不能通过 | 政策与适配器测试 |
| A08 产物身份 | 检查后修改文件使 manifest 验证失败;服务商上传同一导出树,不重新构建 | 受管理构建与工作流测试 |
| A09 CI 文件保护 | 两种模板、已定制工作流、权限/变量、预览/应用冲突与来源记录 | Starter/CI 测试与本地流程演练 |
| A10 公网验证 | HTTP 200 回退、错误语言/构建、资源缺失、canonical 差异、超时/认证/限流 | 本地 HTTP 夹具,不强制云账户 |
| A11 初始化与创作 | 支持的配置/语言;空目标保护;生成站直接用 Hugo 构建;编辑器未知设置保留 | internal/starter、创作与 Hugo 测试 |
| A12 升级 | 可读 diff、新旧路由、脏文件、两套 workspace、replacement/vendor、失败恢复及并发修改 | internal/upgrade、公共命令/Hugo 测试 |
| A13 影响 | 删除 B 发现未改 A;包含翻译/附件/派生输出;全局变化扩大范围 | 影响分析与 Git 基线夹具 |
| A14 修改应用 | 应用前候选验证;哈希冲突和写入失败保留后续编辑;不改写含糊引用 | 共享计划/应用与 move 测试 |
| A15 工作区与上下文 | 逐站结果与直接调用一致;只写选定站点;上下文有路径/版本/原因且受大小限制,不执行正文 | 工作区与 context 测试 |
| A16 Studio 一致性 | 五个视图呈现相同 CLI 结果;键盘/移动端/深浅色可用;来源与预览隔离 | Studio 浏览器和无障碍测试 |
| A17 编辑保护 | 无修改保存字节一致;YAML 注释/未知值/顺序保留;拒绝过期保存和附件冲突 | 编辑器/浏览器与共享应用测试 |
| A18 运行与恢复 | 在声明的 macOS/Linux 目标实测;信号结束子进程;缓存齐备可离线;不支持输入明确报告 | 进程/集成/安装测试 |
先运行最小归属测试,再做更广集成。Go 单元夹具保持离线。解析、快照、探针、初始化 或升级改变后,重跑真实 Hugo 测试。保留聚焦检查,不要求消费用户每改一篇文档就运行 主题内部测试或浏览器套件。
每个候选记录工具版本和源码身份,在 Starter 与三个不同维护站点上只读验证。前后 对比源码字节、模式与 Git 状态。原生新规则需要已审阅的合法/非法语料;成为默认阻断 前先修正误报。承诺增量速度前,应在同一当前站点基线上测量完整构建时间。功能正确 优先于检查项数量。
完成条件与发布证据
每阶段交付已实现行为、已知限制、聚焦测试、真实集成结果、更新的中英文契约/指南 和可审阅 diff。逐项记录需求及用例状态。只有 R1–R8 及其必需验收用例满足,本 PRD 才算完成。
受支持行为被接受后,进入归属 CLI 契约、使用指南和长期决策,再按现有生命周期 退役相应提案内容。
待决项与停止条件
将相对工作周转换为日历日期前,确认投入和开始日期。R1 决定支持的运行平台、审阅 记录的具体存储、首批原生规则和精确的兼容新增参数。这些是范围内的有限实现选择, 不应因此重开产品边界或等待整个主题发布。
文件保护或正确性工作超过估算时,把可选便利功能移后,不能删掉过期写入保护、真实 完成状态或普通 Hugo 兼容。多轮语料审阅仍无法可靠的规则保持建议级或移除。表单 无法保留源码字节的语法继续用文本模式。不能因为某个扩展值得尝试,就让它进入关键 交付路径。
决策日志
| 日期 | 记录 |
|---|---|
| 2026-10-03 | 根据当前 CLI 盘点与提供的功能目标创建草案,提出 R1–R8、可选扩展门禁、投入假设及可执行验收用例。 |
| 2026-10-03 | R1 共享 Hugo 事实与检查政策通过本地归属/真实 Hugo 检查。稳定行为移入 CLI 契约;验收记录分别跟踪最终报告刷新与中英文产物证据。R2–R8 及条件性扩展仍未完成。 |
| 2026-10-03 | R2 翻译范围/哈希审阅、有界原生规则和可见基线已使用共享保护文件计划。本地归属、真实 Hugo 与聚焦 race 门禁通过,最终消费站刷新和中英文文档验收仍在记录中待完成。已实现行为见契约。R3–R8 仍未完成。 |
| 2026-10-03 | R2 最终语料与中英文文档门禁通过。R3 一次渲染的已检查导出、准确文件身份、两种服务商的保护 CI 计划、发布诊断和显式联网 HTTP 验证已通过各自本地门禁,包括自定义 workflow 发现。稳定行为移入契约;准确证据与 A08–A10 结果归维护记录所有。R4–R8、最终 A18 运行时/归档刷新及 Darwin amd64 仍未完成。 |
| 2026-10-03 | R4 受支持本地范围通过冻结 Go/vet、真实 Hugo/race 及准确二进制只读 Starter/文档站/PIG/repository 门禁。同一未修改许可证 Starter 组合全部配置/语言;普通 new/editor/snippet 流程与源码/外部输入保护,以及可读有界升级视图和 alias/输出回归保护均通过。稳定行为归契约,准确 A11/A12 证据和边界归记录。R5–R8、最终 A18 运行时/归档刷新及 Darwin amd64 仍未完成。 |
| 2026-10-03 | R5 修正冻结 Go/vet、实际 Hugo/race 及精确二进制四消费者只读门禁已完成。全部站点 inspect/context 完成;历史影响与移动阻断保持明确。受保护规范源码/渲染门禁及阶段接受仍待完成。稳定行为归契约所有;记录标识 A13/A14/context 证据、缓存模块修正及精确保护记录。R6–R8、workspace A15 与最终 A18 保持未完成。 |
| 2026-10-03 | R5 受支持检查/影响/有界上下文与受保护移动范围在修正冻结归属门禁、精确二进制四消费者保护及首次晋升规范源码/渲染门禁后已本地接受。生产翻译检查仅保留已知 draft 发布文档缺失;独立不可发布分析的全部归属检查通过。稳定行为归契约与命令契约所有;记录保留精确结果及单独渲染后证据边界。A13/A14 受支持 CLI 范围通过;A15 context 通过,workspace/direct 一致仍归 R6。R6–R8 与最终 A18 未完成。 |
| 2026-10-03 | R6 显式登记与有界可选工具候选已实现;聚焦工作区与修正实际协议试验通过,预备失败独立记录。最终运行时/语料/规范门禁及 A07/A15 阶段接受仍待完成,见 R6 记录。R7/R8 与最终 A18 未完成。 |
| 2026-10-03 | R6 冻结 Go/vet、实际 Hugo/固定工具、归属 race 及精确二进制四消费者直接/汇总一致性与保护已验证。完成收据记录六个原始操作、repository 已有重复 ID 发现,以及不变原始输出上的驱动命令摘要修正;无需 CLI/Hugo 重跑或运行时修正。规范源码/渲染检查和显式 R6/A07/A15 阶段接受仍在记录中待完成。R7/R8/最终 A18 未完成。 |
| 2026-10-03 | R6 受支持显式登记与可选工具范围在冻结 Go/vet、实际 Hugo/固定工具、race、精确二进制四消费者一致性/保护及受保护规范源码/渲染门禁后已本地接受。A07 适配器与 A15 工作区/直接/context 受支持范围通过;R6 记录区分首次晋升渲染字节与本次渲染后状态/证据修订。R7/R8 与最终 A18 仍未完成。 |
| 2026-10-03 | R7 只读内嵌 Studio 候选及鉴权回环视图已实现;冻结核心/浏览器及精确二进制四消费者验收在声明范围内完成;受保护规范晋升/渲染及显式 R7/A16 接受仍待完成。R1–R6 保持已接受;R8/最终 A18 未完成。 |
| 2026-10-03 | R7/A16 受支持只读 Studio 在冻结累计已执行案例/浏览器证明、精确二进制四消费者一致性/保护及受保护规范源码/渲染门禁后已本地接受。首次晋升渲染字节与本次渲染后状态修订保持不同;完整调用失败与明确局部预览未完成保持可见。R1–R7 已接受;R8/最终 A18 未完成。 |
| 2026-10-03 | R8 CLI/显式 Editor 源码编辑候选已实现;默认 Studio 保持只读。纯核心/流式输出复制聚焦证据已记录,失败浏览器试验保留。最终冻结公共/浏览器/消费者/规范门禁及 A17 阶段接受仍待完成,见 R8 记录。R1–R7 保持已接受,R8/最终 A18 未完成。 |
| 2026-10-03 | R8/A17 受审阅 CLI/显式 Editor 受支持本地范围在修正冻结完整归属/浏览器、精确二进制四消费者提议一致性与源码保护,以及受保护首次规范晋升/实际渲染后接受。R1–R8 已本地接受;R8 记录独立绑定首次渲染与本次状态字节,并保留所有失败试验、原生发现与必需局部预览未完成。最终 A18 当前 Linux/归档验证仍未完成。 |
| 2026-10-04 | 当前后端完整性修正、刷新归属/Hugo/race、未变运行时复用的四消费者保护与三个声明运行时/归档验证通过,见带日期完成增补。有限 R1–R8 实现本地完成;需求记录/锚点保留,退出活动导航,稳定行为归属契约/指南。E1–E4 未启动。 |
7.9.5 - 视觉预设与外观切换
Paper、Slate 与外观菜单已随 OINK 1.2.0 发布。当前行为与证据由 架构契约、 已接受决策和 验收记录管理。 随后的 Ink/Terminal 实验提供真实可切换输出;本提案继续承载两者的设计定稿。10 月 4 日注入样式生成的截图仍是研究原型, 与 10 月 5 日真实主题输出截图分开看待。
状态与影响面
| 字段 | 值 |
|---|---|
| 状态 | 第一阶段随 1.2.0 发布;Ink/Terminal 显式启用 |
| 负责人 | OINK 维护者 |
| 日期 | 2026-10-04 |
| 基线 | OINK v1.4.0 主题与文档站;本行保留 10 月 4 日提案研究的起始背景 |
| 受影响契约 | 架构:信任、CSS 与无障碍(字体角色、强调色角色、行内代码颜色)、外壳(主题控件)、Landing、配置决策、品牌指南 |
| 第一阶段 | Paper 预设、Slate 预设、默认改为 Paper、读者在 Paper 与 Slate 间切换 |
| 后续阶段 | Ink/Terminal 视觉定稿;Folio 与 Canvas 只保留名称 |
背景与依据
以下基线与限制记录 10 月 4 日实施前的研究输入。
OINK 目前只有一套视觉,本文称为 Slate:冷灰蓝画布(#f1f4f8 / #0b1119)、
海军蓝文字、钢蓝链接(#245f94)、铜色点缀;Inter 用于界面与正文,Chakra Petch
用于展示标题与字标,IBM Plex Mono 用于代码与技术标注;Landing 首屏有蓝图网格与
光晕;行内代码为一组深红色。它由 assets/scss/td/_brand.scss 的 Bootstrap 自定义
属性、assets/scss/td/shell/_tokens.scss 的外壳 token,以及
assets/scss/td/_tokens-typography.scss 的字体角色定义。
PG.CENTER 是独立站点,具有维护者希望成为 OINK 未来默认的暖色编辑式阅读风格。
其展示层 token 位于该项目的 media/css/pgsql.css。在本地预览上测量
(2026-10-04,浅色与深色;首页、Docs 索引、长篇手册页、组件手册页):
| 角色 | 浅色 | 深色 | 说明 |
|---|---|---|---|
| 画布 | #f7f6f3 |
#161513 |
暖白 / 暖黑 |
| 抬升表面 | #ffffff |
#1d1c19 |
卡片、代码块 |
| 次级表面 | #efede8 |
#262420 |
表头、悬停 |
| 墨色正文 | #21201c |
#ece9e3 |
|
| 次级文字 | #56534c |
#b6b1a7 |
|
| 线与淡底 | 墨色 4.5–22 % 透明度 | 浅墨色相近透明度 | 不使用带色相的灰 |
| 圆角 | 12 px / 8 px | 相同 | |
| 阴影 | 0 2px 10px rgba(33,32,28,.07) |
以黑色为基 | 暖、柔 |
| 动效 | 160 ms cubic-bezier(.2,.7,.2,1) |
相同 |
字体方面,IBM Plex Sans(可变字重 400–600)用于界面与正文;IBM Plex Mono 用于代码、 日期与版本;Chakra Petch 只用于字标。组件手册页是最好的长文样板:导语 17 px、 最宽 70ch;h2 后跟一条延伸到边缘的细线;带表头底色、无斑马纹的外框表格; 单色提示块加 3 px 竖线。
以下 PG.CENTER 元素属于站点身份,不是可复用的阅读规则:PostgreSQL 品牌蓝
#336791 系列、酒红正文链接、版本状态色、版本条、搜索类型徽标、Wiki 色调、
双色首屏,以及导入的 PostgreSQL 手册约 144 字符的行长。两个值不满足 WCAG AA
(弱化文字 3.67:1、链接悬停 4.22:1),下文予以修正而非照搬。
用于对照的 OINK 文档测量值:正文 16 px / 1.7、行长约 76ch、h1 36 px / 700、 h2 24 px / 600、代码 14 px。PG.CENTER Docs 索引:15.5 px / 1.7,约 120ch。
现有限制
- 颜色只与
data-bs-theme绑定,没有属性能选择第二套调色板;多个表面绕过 token:Landing 主按钮(#2f6793与海军蓝光晕)、网格、遮罩 (rgba(4,10,18,.45))、打印颜色、asciinema 表面与 giscus 样式表。 - 约 85 处字面圆角与若干字面阴影,使扁平预设在没有圆角与阴影尺度前无法实现。
- 明暗控件靠悬停或聚焦展开。触屏读者无法到达“跟随系统”;触发按钮混用
aria-pressed与aria-expanded;Esc 不能关闭。Landing 手机抽屉没有主题控件。 contrast-on-canvas.html硬编码了 Slate 画布亮度,用于theme_color警告。dark_mode默认false,站点不开启就既没有深色调色板也没有菜单。
目标与非目标
目标:
- 一个站点配置键选择默认视觉预设;默认改为 Paper;
- Slate 保留可选,选择它的站点得到与当前一致的输出;
- 读者可即时切换 Paper 与 Slate,无需刷新,并与浅色/深色/跟随系统彼此独立;
- 禁用 JavaScript 或存储不可用时,站点配置的默认风格照常呈现;
- 预设共享模板、组件与布局几何,只改变配色与字体;
- 只用本地字体、普通 Hugo 构建,不引入新的运行时框架或必需构建工具。
非目标:
- 第一阶段实现 Ink、Terminal、Folio 或 Canvas;
- 复制 PG.CENTER 品牌色、版本界面或页面结构;
- 按页面或栏目切换预设(栏目颜色仍由
theme_color负责); - 第一阶段按预设改变布局几何、密度或导航结构;
- 在现有明暗处理之外为 Swagger UI、ReDoc 或第三方嵌入换肤。
预设模型
此表与下文第一阶段配置保留原始范围。后续实验增加显式 ink/terminal 配置及
菜单列表选项;true 仍提供稳定选项与站点默认值。当前行为由
架构契约管理。
| 预设 | 方向 | 第一阶段 | 读者菜单 |
|---|---|---|---|
paper |
温暖的编辑式极简 | 实现,默认 | 是 |
slate |
技术极简(当前 OINK) | 实现 | 是 |
ink |
排版极简,受瑞士风格启发 | 规格 + 研究原型 | 否 |
terminal |
终端工具式功能设计 | 规格 + 研究原型 | 否 |
folio |
学术与书籍出版 | 仅保留名称 | 否 |
canvas |
活泼几何与创作者 | 仅保留名称 | 否 |
保留名称在实现前会被校验拒绝,警告中列出可用的稳定预设。
配置
preset选择站点默认值。无效或保留值通过现有校验路径警告并回退到paper; 发布门禁会把警告变成失败。preset_menu控制读者选择。false不渲染风格分组、不输出预设初始化脚本;true提供所有稳定预设;列表提供子集,且必须包含preset。沿用dark_mode的先例,默认false;文档站开启,Starter 采纳不在本轮范围内。preset只能在站点级设置,不支持页面与栏目覆盖:逐页切换视觉身份会破坏读者 预期与已保存的选择。- 只要
dark_mode.show_menu或风格选择任一开启,外观菜单就存在。dark_mode: false且preset_menu: true的站点只显示“风格”分组。
与现有配置的关系
优先级由低到高:
:root/[data-bs-theme]上的 Slate 基础 token(选择器不变)。[data-td-preset=X]上的预设 token。params.ui.typography: system:在所有预设块之后把字体角色收拢为系统字体, 因此在任何预设下都不请求品牌字体。params.ui.fonts:在样式表之后以:root内联输出;特异性相同、源顺序靠后, 因此覆盖预设字体角色。显式字体永远优先。theme_color/theme_color_dark:只作用于页面与栏目的强调背景,覆盖预设 强调色;从不触碰链接或行内代码。- 站点
_styles_project.scss:位于样式包最后。
typography: technical 仍表示“使用预设自带字体”。具体是哪些字体由预设决定
(Paper:Plex Sans;Slate:Inter + Chakra Petch)。
读者状态
两个彼此独立的维度:
| 维度 | 属性 | 存储 | 取值 |
|---|---|---|---|
| 风格 | <html> 上的 data-td-preset |
localStorage['td-preset'] |
稳定预设名 |
| 明暗 | data-bs-theme(及 .dark-mode、供应商 data-theme 镜像) |
localStorage['td-color-theme'] |
light、dark、auto |
| 情形 | 结果 |
|---|---|
| 初次访问 | 服务端输出 data-td-preset="<站点预设>" 与 data-td-site-preset,不依赖脚本 |
| 读者选择预设 | 立即应用、保存,并派发 td-preset-change |
| 读者选择标有“默认”的预设 | 删除存储键;之后站点默认值变化能到达该读者 |
| 下一页、刷新、切换语言 | head 内联脚本在首次绘制前应用已保存的值 |
| 已保存的值不再提供 | 删除,使用站点默认值 |
| 存储不可用 | 选择只作用于当前页面,菜单提示不会保存 |
| 禁用 JavaScript | 站点默认预设以浅色调色板呈现,与当前主题无脚本时一致;风格与明暗控件不可用 |
| 切换风格 | 从不写入 td-color-theme;切换明暗从不写入 td-preset |
| 其他标签页修改 | 通过 storage 事件同步 |
内联脚本位于样式表之前,与现有明暗脚本并列。它用构建时嵌入的允许列表校验已保存的
值,设置属性,并按预设与明暗更新 theme-color meta 与首绘画布颜色。只有菜单提供
多于一个预设时才输出。与菜单无关,head.html 中静态的首绘 <style> 与单个
解析后的 theme-color meta 改为按站点默认预设的画布颜色渲染,取代原先硬编码的
#0b0d12、#ffffff 与 #000000。
切换时,运行时设置 data-td-preset-switching 一帧以抑制颜色过渡;记录第一个可见
标题或块作为滚动锚点;应用属性后恢复锚点偏移,并在 document.fonts.ready 后再校正
一次,因为 Plex Sans 与 Inter 的字形度量不同。焦点、已打开的菜单与表单状态保持不变。
第一阶段不使用淡入淡出或 View Transition。
外观菜单
比较了三个方案:
| 方案 | 评估 |
|---|---|
| 保留悬停菜单,增加一行风格 | 触屏与键盘缺口仍在;只能靠悬停发现 |
| 风格与明暗分成两个按钮 | 拥挤的导航栏多一个图标;手机抽屉更长 |
| 一个“外观”展开按钮 + 两组单选 | 选定:单一入口,触屏与键盘均可用,可扩展到更多预设 |
行为:
- 触发器:一个图标按钮(
aria-expanded、aria-controls,标签“外观”),替换 导航栏与外壳页脚行中现有的主题按钮。太阳表示当前亮色状态,月亮表示暗色。 快捷键t继续切换浅色/深色。 - 面板:非模态弹出层,包含两个原生
fieldset单选组。10 月 5 日修订后, 风格 使用两列图标与名称按钮,图标采用预设主题色,不显示字母预览或实验标记; 站点默认值在悬停提示与无障碍名称中注明。明暗 为浅色 / 深色 / 跟随系统分段 控件,英文分组名为 Style 和 Light。选择立即生效,面板保持打开以便比较。 - 键盘:Enter/Space 或 ArrowDown 打开并聚焦已选中的单选;方向键在组内移动 (原生单选行为);Tab 在组间移动;Esc 关闭并把焦点还给触发器;焦点离开面板或 点击外部时关闭。
- 反馈:选中项使用淡色背景与强调色边框,键盘焦点另有轮廓线。变化由原生 单选语义播报,不额外增加 live region。
- 恢复默认:选择站点默认预设即清除已保存的选择,无需单独的重置按钮。
- 手机(< 768 px):触发器保留在紧凑页头,并在文档抽屉页脚与 Landing 手机抽屉的
新行中提供。面板以底部表单打开,44 px 触控目标,同样两组,带关闭按钮。底部表单是用
showModal()打开的模态<dialog>,处于顶层:原型显示,粘性页头的backdrop-filter否则会成为position: fixed表单的包含块,抽屉的层叠上下文 也会把它遮住。 - 命令面板:在
switch_theme旁新增switch_preset动作。
dark-mode.js 保留存储键与属性,但需同步明暗单选的 checked 状态并监听其
change 事件,取代目前的 aria-pressed 按钮。
Token 架构
所有预设编译进现有的单一 main.css。字体通过 @font-face 声明,只有规则实际使用时
才下载;因此提供一个预设只增加 CSS 字节,在被选中前不增加字体字节。
规则:
- Token 对等:每个深色块重新声明其浅色块的全部 token,Slate 深色值不会泄漏 到其他预设。由检查器强制。
- 深色孤岛:后代选择器形式覆盖嵌套的
data-bs-theme="dark"孤岛 (Landing 代码板、预览)。 - 字体角色只用 (0,1,0),
params.ui.fonts因而继续优先。 - 强调色间接层:预设设置
--td-preset-accent(及-rgb、-hover),--td-accent默认取它;theme_color继续写入--td-accent,因此在两种明暗下 都能覆盖预设。 - Slate 不依赖属性:
data-td-preset="slate"不匹配任何覆盖块,现有站点对品牌 token 的覆盖行为与今天完全相同。 - 几何共享:第一阶段预设不改变栅格列、侧栏宽度或断点。
- 预设专属规则少而局部:每个预设一个 partial,限定在
[data-td-preset=X]下; 两个预设都需要的东西就提升为 token。
Paper 之前(第一阶段)需要的新共享 token:--td-shell-scrim、Landing 的
--td-grid / --td-glow / 主按钮 token、--td-callout-tint、--td-code-inline-bg、
--td-hairline,以及 brand 字体角色(--td-brand-font-family,默认
var(--td-display-font-family)),使字标保留 Chakra Petch,而 Paper 的展示标题
使用 Plex Sans。
原第二阶段计划提出全局圆角、阴影与密度尺度。10 月 5 日实验改为仅作用于主题 自有组件的规则;更广泛的 token 重构不作为试用设计的前提。
契约变化:之前的架构契约把行内代码固定为一组深红色。第一阶段把 --bs-code-color 改为
预设 token(Slate 保留深红,Paper 使用墨色底片)。theme_color 仍然从不触碰它。
字体
| 预设 | 界面 / 正文 / 标题 | 展示 | 品牌(字标) | 元信息 | 代码 | 新增字节 |
|---|---|---|---|---|---|---|
| Paper | IBM Plex Sans | IBM Plex Sans | Chakra Petch | IBM Plex Sans | IBM Plex Mono | Plex Sans |
| Slate | Inter | Chakra Petch | Chakra Petch | IBM Plex Mono | IBM Plex Mono | 无 |
| Ink | Inter | Inter | Inter | Inter(等宽数字) | IBM Plex Mono | 无 |
| Terminal | 界面用 Plex Mono,正文用 Plex Sans | IBM Plex Mono | IBM Plex Mono | IBM Plex Mono | IBM Plex Mono | Paper 之后无 |
Paper 将 @fontsource-variable/ibm-plex-sans(OFL-1.1)vendor 到 third_party/
并登记 VENDOR.json:拉丁、扩展拉丁、西里尔、扩展西里尔、希腊与越南语子集,正体与斜体,字重 100–700。PG.CENTER
仅正体、400–600 的子集为 40,240 B(latin)+ 25,868 B(latin-ext);准确体积在
vendor 时记录。需要斜体,因为 OINK 正文使用强调,PG.CENTER 的合成斜体不可接受。
完整的小型子集保留现有语言覆盖,浏览器按实际字符范围加载;12 个字体文件均登记于 VENDOR.json。
中日韩文字使用排在拉丁字体之后的系统字体栈:-apple-system, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Noto Sans SC', sans-serif。IBM Plex Sans SC 因文件达到 MB 级被否决。等宽字体栈在通用
monospace 之前插入 CJK 无衬线字体,使混排代码的中文字形可预期。
typography: system 仍不请求任何品牌字体:system 块位于所有预设块之后,并重置
包括 brand 在内的全部角色。
衬线:第一阶段不使用衬线。拉丁衬线标题与中文无衬线标题并列显得不一致;Windows 默认中文衬线在标题字号下渲染较差;衬线还要多一套字体。第一阶段之后可基于本提案的 同内容对照样稿,评审一个可选的、仅用于展示标题的衬线。
预设规格
共享基础
属于所有预设,而不是 Slate:
- 布局几何、断点、侧栏/目录宽度、约 76ch 正文行长;
- 正文 1rem / 1.7,界面 0.875rem,元信息 0.8125rem;
- 字号比例(h1 2.25rem、h2 1.5rem、h3 1.25rem、h4 1rem)——第一阶段预设只调字重与 字距,不调字号;
- 焦点环:2 px 强调色描边、2 px 偏移,绝不移除;强制颜色模式回退不变;
- 语义状态色(note、tip、important、warning、caution)保持色相;预设只改变淡底强度 与边框;
- 第一阶段语法高亮沿用现有 Chroma 浅色/深色调色板;
- 动效 token 100/150/250 ms;
prefers-reduced-motion关闭过渡; - WCAG AA:两种明暗下正文 4.5:1,大字与界面边界 3:1。
Paper
温暖的编辑式极简。 暖纸色、墨色文字、安静的细线、柔和阴影,舒展但不松散的阅读 节奏。它服务长篇阅读:大面积画布蓝光更少,界面对比更克制,Plex Sans 字怀开阔, 16 px 下易读。
| Token | 浅色 | 深色 |
|---|---|---|
画布 --bs-body-bg |
#f7f6f3 |
#161513 |
抬升 --td-brand-elev、--td-pre-bg |
#ffffff |
#1f1e1a / #121110 |
| 次级表面 | #efede8 |
#1f1e1a |
| 正文 | #21201c(15.09:1) |
#ece9e3 |
| 次级文字 | #56534c(7.10:1) |
#b6b1a7 |
| 三级文字 | #6b665d(5.27:1) |
#958f84(5.68:1) |
| 边框 | 墨色 12 % | 浅墨色 13 % |
| 链接 / 悬停 | #2b5f8c(6.23:1)/ #1d68a5(5.43:1) |
#7db5e6(8.36:1)/ #a3cdf3 |
| 强调(铜色) | #9c5530(5.17:1) |
#d99a6c |
| 行内代码 | 墨色字、墨色 6 % 底片 | 浅墨色字、8 % 底片 |
| 阴影 sm / md | 0 2px 10px / 0 14px 38px,墨色 7 % / 13 % |
黑色 35 % / 50 % |
| 圆角 | 代码 12 px、卡片 12 px、控件 8 px | 相同 |
Paper 专属规则:标题 Plex Sans 600,字距 −0.006em(h1 −0.012em);h2 后接延伸到 边缘的细线;外框表格(圆角 10、表头底色、无斑马纹);提示块使用 4 %(深色 6 %)语义 淡底与单条 3 px 竖线;细线引用块;Landing 去掉网格与光晕,主按钮取自 token 并带暖色 阴影,首屏标题 600 / −0.025em;导航选中行使用暖中性底并混入 9 %(深色 12 %)强调色。 链接保持蓝色:这是阅读惯例,不是装饰。悬停与弹出层使用 160 ms ease-out;滚动时 不做动效。
Slate
技术极简。 即当前 OINK 外观,保持不变:冷灰蓝画布、海军蓝墨色、钢蓝与铜色、
Inter 正文、Chakra Petch 展示、Plex Mono 标签与元信息、蓝图网格与首屏光晕、深红
行内代码、8–12 px 圆角。选择 preset: slate 必须复现 v1.1 的 token 值,由检查器
比较。网格、光晕、Chakra 展示标题、等宽元信息与深红行内代码属于 Slate 身份;布局、
焦点、状态色与外壳结构属于共享基础。
Ink
排版极简,受瑞士风格启发的信息设计。 黑、白与中性灰,一个红色强调;层级由字号、 字重与对齐承担,而不是颜色、阴影或圆角表面。
| Token | 浅色 | 深色 |
|---|---|---|
| 画布 | #ffffff |
#0b0b0b |
| 正文 | #141414 |
#ededed |
| 次级 / 三级 | #474747 / #636363 |
#b5b5b5 / #8f8f8f |
| 表面 | #f4f4f4 |
#161616 |
| 链接 | 墨色加下划线;悬停为红 | 浅墨色加下划线;悬停为红 |
| 强调 | #c8102e(5.88:1) |
#ff5c4d |
| 圆角 / 阴影 | 0 / 无 | 0 / 无 |
与 Slate 的区别:画布无色相、无蓝色、无网格纹理、无阴影、无圆角;链接靠下划线而非 色相识别;标题使用 Inter 700–800 紧字距,而不是 Chakra Petch。与 Paper 的区别: 中性而非暖色,平面而非柔和,粗线分隔而非细线,下划线链接而非蓝色链接。标志性规则: h2 上方 2 px 黑线;h1 800 / −0.035em;h4、表头与提示块标题大写加字距;导航选中行用 3 px 红色竖条而不是底色;等宽数字。
Terminal
终端工具式功能设计。 体现在结构与信息表达上,而不是 CRT 特效:等宽界面、命令与 路径表达、紧凑控件、明确的面板边界、琥珀或青绿强调。
| Token | 浅色 | 深色 |
|---|---|---|
| 画布 | #f4f5f2 |
#0c0f0e |
| 正文 | #1d211f |
#d3dbd6 |
| 次级 | #4a514d |
#9aa59f |
| 表面 | #e9ebe6 |
#141a18 |
| 链接(青绿) | #0a6560(6.31:1) |
#4cc9bd |
| 强调(琥珀) | #935400(5.47:1) |
#f0a73a |
| 圆角 | 2 px | 2 px |
等宽范围:导航、标题、标签、元信息、面包屑、按钮与代码使用 IBM Plex Mono。正文段落、
列表与表格正文使用 Plex Sans,中文使用平台回退字体,因为长段等宽文字与中英混排
等宽行都难以阅读。
标志性规则:标题前的 ## 前缀用 content: '## ' / '' 渲染,辅助技术会忽略它;
方括号提示标签([NOTE]);导航选中行反色并带 ▸ 标记;1 px 强边框面板;首屏静态
▍ 光标。没有扫描线、辉光、闪烁或打字动画。
差异矩阵
| Paper | Slate | Ink | Terminal | |
|---|---|---|---|---|
| 色温 | 暖 | 冷 | 中性 | 中性偏绿 |
| 浅色画布 | #f7f6f3 |
#f1f4f8 |
#ffffff |
#f4f5f2 |
| 深色画布 | #161513 |
#0b1119 |
#0b0b0b |
#0c0f0e |
| 正文字体 | Plex Sans | Inter | Inter | Plex Sans |
| 标题字体 | Plex Sans 600 | Inter 600–700 | Inter 700–800 | Plex Mono |
| 展示 / 字标 | Plex Sans / Chakra | Chakra / Chakra | Inter / Inter | Plex Mono |
| 链接信号 | 蓝色 | 钢蓝 | 下划线 + 红色悬停 | 青绿 |
| 强调色 | 铜色 | 铜色 | 红色 | 琥珀 |
| 圆角 | 8–12 | 8–12 | 0 | 2 |
| 阴影 | 柔和暖色 | 海军蓝调 | 无 | 无 |
| 章节分隔 | h2 尾随细线 | 无 | 2 px 顶线 | ## 标记 |
| 选中行 | 暖色底 | 强调色底 | 红色竖条 | 反色 + ▸ |
| 行内代码 | 墨色底片 | 深红 | 墨色底片 | 带框墨色底片 |
| Landing 纹理 | 无 | 网格 + 光晕 | 无 | 无 |
| 界面密度 | 标准 | 标准 | 标准 | 紧凑 |
页面密度
密度跟随页面任务,而不是预设:Landing 首屏允许最大的展示字号与品牌表达;Docs 正文 保持 1rem / 1.7 与约 76ch;侧栏、目录、参数表、搜索结果与命令面板保持紧凑行 (0.875rem,行高 1.4–1.5)。第一阶段预设可以改变这些区域的配色,但不改变间距。 Terminal 的紧凑界面属于第二阶段的密度 token。
运行时表面
| 表面 | 第一阶段影响 |
|---|---|
| Blog、Book、分类 | 仅 token;Book 题注保持正文字体 |
| 搜索对话框与命令面板 | 遮罩 token 化;选中行使用 --td-shell-primary-dim |
| Mermaid、ECharts | 颜色在初始化时依 data-bs-theme 固化。只有图表采用预设颜色时才需观察 data-td-preset;第一阶段保持仅随明暗变化 |
| asciinema | 表面 token;只有代码字体变化才需重新挂载(第一阶段不变) |
| giscus | 每个预设与明暗各需一份样式表,并在 td-preset-change 时重新下发 |
| Swagger UI、ReDoc | 保持供应商样式与现有明暗处理 |
| 打印 | 海军蓝与冷灰 token 化;打印始终使用当前预设的浅色调色板 |
| 404 | 其自有 <html> 必须带上新属性 |
无障碍、安全与输出
- 每套调色板在两种明暗下的正文、次级与三级文字、链接与强调色均满足 WCAG AA(见上文
数值)。
theme_color对比度警告按站点默认预设的画布计算。 - 菜单使用原生单选,不使用
role="menu"。除手机底部表单(模态并恢复焦点)外不捕获焦点。 prefers-reduced-motion与强制颜色模式保持现有行为。- 初始化脚本内联、静态,来自已校验的配置;已保存的值使用前先与构建时允许列表比对。
- 不新增外部字体或脚本请求。输出只增加两个
<html>属性、一段内联脚本与 CSS。
兼容与迁移
默认改为 Paper 会改变所有未设置 preset 的站点。
- 想保留当前外观的站点加上
params.ui.preset: slate;升级说明以这一行开头。 Slate 输出必须等于v1.1的 token。 - 在
_styles_project.scss中覆盖品牌 token 的站点::root上的浅色覆盖在 Paper 下仍按源顺序生效;[data-bs-theme='dark']上的深色覆盖会被 Paper 深色块压过。 这类站点应选择 Slate,或把覆盖改写到[data-td-preset='paper'][data-bs-theme='dark']。升级说明与品牌指南需说明。 theme_color、typography与fonts的含义与优先级不变。dark_mode: false的站点仍只有一套浅色调色板,只是变为 Paper。- 改变默认值的版本必须把它列为可见变化。该版本是次版本(
1.x)还是主版本, 是待决问题。 - 在发布默认值变化前,消费方盘点应报告哪些站点覆盖了品牌 token。
实施计划
第一阶段,按依赖顺序;每步注明负责的检查器。
- Token 化 Slate 泄漏点:Landing 主按钮、网格、光晕、遮罩、打印颜色、asciinema
表面;增加
--td-preset-accent、brand字体角色,以及contrast-on-canvas.html的按预设画布亮度。Slate 的计算颜色必须保持等价。 检查器:check-landing.py、check-output.py、check-font-tokens.py。 - Vendor IBM Plex Sans:
third_party/、VENDOR.json、许可证文件。 检查器:check-vendor.py。 - 预设 token:新增
assets/scss/td/_presets.scss(在_brand.scss之后导入); 当前实现将 Paper 保留在这个文件中,不另建presets/_paper.scss。 字体预设块放在system重置之前。检查器:扩展check-font-tokens.py(Plex Sans 字体族、system 块顺序、浅深块 token 对等)。 - 配置:
hugo.yaml默认值(preset: paper、preset_menu: false);一个 resolver partial,供validate.html、document-attrs.html、layouts/404.html与head.html(初始化脚本、theme-color、首绘画布)使用;重新生成 schema。 检查器:check-params.py(接受、无效、保留值)、generate-config-schema.py --check、check-namespace.py。 - 外观菜单:共享 partial,供
navbar.html、shell/footer-line.html与 Landing 手机抽屉使用;preset.js运行时(或dark-mode.js的一节);dark-mode.js单选同步;命令面板动作switch_preset;32 个语言目录的 i18n 字符串。 检查器:check-shell.py、check-actions.py、i18n 检查器、tests/js/preset.test.js、tests/js/dark-mode.test.js。 - 第三方表面:按预设的 giscus 样式表与重新下发。
- 文档:EN/ZH 架构、外壳与 Landing 契约;品牌指南(预设、迁移、字体); 配置参考;变更日志与升级说明。
- 站点验证:
make -C ../oink.pgsty.com check、browser(增加预设切换、持久化、 存储失败、无 JS、EN/ZH、桌面/手机、浅色/深色用例),以及用于视觉评审的dev。
验收标准
以下保留最初的验收目标,已执行检查与剩余限制分别记录在10 月 5 日验收记录中:
- 未设置
preset时,输出带data-td-preset="paper",禁用 JavaScript 也呈现 Paper。 preset: slate在检查器样例上产生与v1.1相同的计算颜色与字体角色。- 切换风格不改变
td-color-theme;切换明暗不改变td-preset;两者在导航、刷新与 切换语言后保持。 - 无效的已保存值被删除;存储失败时页面可用并显示不保存提示。
- 在 Chromium、Firefox 与 WebKit 的正常及降速 CPU 下,预设之间无首绘闪色。
- 切换后滚动位置与锚点相差不超过一行。
- 任何预设下
typography: system都不触发字体请求;params.ui.fonts覆盖预设字体。 - Paper 与 Slate 下,
theme_color在两种明暗中都覆盖强调色。 - 菜单可完全通过键盘、触屏与屏幕阅读器操作;axe 不报告新增违规。
- 所有调色板在两种明暗下满足对比度表。
- 预设不新增外部字体或脚本依赖;Giscus 等显式配置的服务单独说明。
待决问题
- 第一阶段已选择
preset_menu: false,文档站开启。原问题:false(与dark_mode一样需显式开启)还是true。 - 发布准备目标已确定为
1.2.0:醒目说明 Paper 成为默认,并提供preset: slate兼容设置;已随 1.2.0 正式发布。 - 第一阶段已选择
brand。原问题:字标字体角色命名:brand还是wordmark。 - 第一阶段之后,是否把仅用于展示标题的衬线作为 Paper 选项。
- 第二阶段图表(Mermaid、ECharts)是否采用预设颜色。
Ink 与 Terminal 后续清单
已实验实现:两套色板、现有字体角色、正文链接与选中信号、标题处理、局部几何、 Terminal 紧凑导航、Giscus 色板、打印与现有切换机制。不新增字体文件、动画或 运行时。真实输出与验证范围见实验记录。
晋升稳定预设前,仍需评审 Ink 长页红色强调密度与中文下划线;Terminal 编号标题、 等宽换行与密集参数表;Windows/Android 回退字体,以及人工屏幕阅读器朗读。 本次实验明确保留 Mermaid/ECharts 与 API 供应商组件仅随明暗变化;全局几何与 密度 token、预设图表色板需要另行决定。
决策记录
| 日期 | 变化 |
|---|---|
| 2026-10-04 | 创建草案:Paper/Slate 第一阶段范围、Ink/Terminal 研究规格、外观菜单选择与 token 架构 |
| 2026-10-05 | 第一阶段已在本地实现;默认值、brand 角色、图表仅随明暗的范围已接受 |
| 2026-10-05 | 随后实现显式开启的 Ink/Terminal 实验;保留稳定菜单策略;视觉定稿仍未完成 |
| 2026-10-05 | 按 1.2.0 做发布准备;简洁的风格/明暗控件与当前状态图标取代早期色样方案 |
7.9.6 - 让 Ink 与 Terminal 成为稳定的阅读风格
放大字体会使共用页脚溢出。 在 320px 视口、32px 根字号/默认字号下,英文 Ink 的文档宽度达到 424px,Terminal 达到 408px。对比覆盖样式没有修复这一共用缺陷。两份原型均未稳定,也未获准升级为稳定预设。
状态、归属与范围
状态与日期:草案,2026-10-26;提出设计方向,不是实现契约
最终负责人:OINK 维护者;分别记录每个预设的验收决定
交付归属:主题维护者负责 CSS、解析器与专项检查器;文档维护者负责双语契约、真实站点夹具与浏览器审查;无障碍评审者负责人工任务证据
基线:OINK v1.4.0;本地源码身份与含未提交修改的快照边界见研究记录
版本意向:面向后续版本,完成精修后分别决定是否升级为稳定预设;不建议新增预设
背景与证据
Ink 与 Terminal 已作为显式启用的实验存在。共用外壳让读者切换风格后仍能沿用导航、搜索、外观控制与内容语义,这是它们的优势。剩余问题包括装饰重复、正文以外的字体分工、主题自有组件的形状不一致,以及验收覆盖不足。Ink 已经是 中性无衬线编辑风格;改为衬线会占用 Folio 的独立定位。Terminal 应成为 易读的工程手册,以等宽字体承载技术角色,以比例字体承载解释性正文。
带日期的研究区分了源码事实、实测结果、设计判断和有限 CSS 覆盖实验。正常字号测量、间距探测及专项检查提供了积极证据,但整页页脚溢出仍然存在。手动 200% 文字放大与 400% 浏览器缩放验收仍需独立于 32px 字号偏好探测完成。
目标与明确排除项
| 预设 | 受众与主要任务 | 共用外壳内的独立定位 |
|---|---|---|
| Paper | 广泛的文档读者;指南与持续阅读 | 温暖的 Plex Sans 阅读表面、克制的圆角组件;继续作为默认值 |
| Slate | 既有技术/产品站点;参考导航与产品展示 | 既有冷色调、Inter/Chakra/Plex 字体角色及更丰富的 Landing 表现;保留兼容选择 |
| Ink | 编辑、作者及长文档、随笔、设计论证的读者 | 中性 Inter/CJK 无衬线、方正扁平表面、清晰正文下划线,以及少量红色或作者强调色 |
| Terminal | 工程师与运维人员;操作流程、配置查阅与代码解释 | Plex Sans 解释性正文、等宽标题/控件/标识符、青绿/薄荷色链接、琥珀强调色及扁平 2px 形状 |
两种候选都必须适用于 Docs、Book、Blog、画廊、Expo 与 Landing,覆盖中英文、深浅色及宽窄视口。它们都不是独立外壳、无障碍模式或表格密度模式。本次排除新渲染器、全局间距改动、字体下载、运行时依赖、终端特效、移动选择轨迹、凸起 Tabs、带框 Field 名称和 Paper 标题尾线。
两个可选概念继续留在 1.4 范围之外:
| 概念 | 独立的未来用途 | 成本与处置 |
|---|---|---|
| Folio | 学术与书籍出版:衬线阅读角色、学术图注/注释,无衬线导航/搜索,等宽代码 | 需要匹配的中英文衬线方案、许可、本地字体体积及回退/打印/设备验收。保留共用外壳,不引入侧边注释栏。有研究价值,但不取代 Ink。 |
| Canvas | 面向创作者的活泼几何展示:沿用无衬线字体,控制淡紫/赭色强调及少量几何标记 | 本身不必增加字体,但需新增配色/状态/形状规则、素材指引、菜单与完整回归覆盖。现有证据较弱,与 Slate 重叠明显。等待真实创作任务证明价值。 |
提议行为与边界
保留配色,明确角色
初期保留以下源码颜色。表中为浅色/深色配对,不构成新的配置权威;预设 SCSS仍是实现归属。在改色相之前,先减少重复强调。
| 角色 | Ink 浅色/深色 | Terminal 浅色/深色 |
|---|---|---|
| 画布 | #ffffff / #0b0b0b |
#f4f5f2 / #0c0f0e |
| 正文 | #141414 / #ededed |
#1d211f / #d3dbd6 |
| 强调文字 | #000000 / #ffffff |
#111713 / #edf4ef |
| 次级文字 | #474747 / #b5b5b5 |
#4a514d / #9aa59f |
| 第三级文字 | #636363 / #8f8f8f |
#606b63 / #8b9990 |
| 次级表面/代码标题栏 | #f4f4f4 / #161616 |
#e9ebe6 / #141a18 |
| 第三级表面 | #ffffff / #161616 |
#f8f9f6 / #18201c |
| 代码块 | #fafafa / #111111 |
#ffffff / #0a0d0b |
| 链接 | #141414 / #ededed |
#0a6560 / #4cc9bd |
| 悬停链接 | #c8102e / #ff5c4d |
#084f4b / #7de0d7 |
| 默认强调色 | #c8102e / #ff5c4d |
#935400 / #f0a73a |
| 链接填充上的前景 | #ffffff / #0b0b0b |
#ffffff / #0c0f0e |
| 普通边框 | 正文 RGB,alpha 24% / 24% | 正文 RGB,alpha 32% / 32% |
| 强分隔线 | 正文 RGB,alpha 55% / 50% | 正文 RGB,alpha 55% / 55% |
| 行内代码 | 正文前景;正文 RGB 填充 5% / 6% | 正文前景;正文 RGB 填充 5% / 7% |
成功、警告、错误与信息保留各自的语义色、图标和标签。不能把 Ink 的全部状态变红,也不能把 Terminal 的全部状态变成薄荷色。中性徽章不能看起来像成功状态。装饰分隔线可以较淡;识别控件所必需的边界要独立测量对比度。
作者设置的 theme_color 继续控制强调表面。真实文档使用蓝色 #245f94/#5da2dd,Book 使用橙色 #9a3412/#fb923c,Blog 使用紫色 #6d28d9/#a78bfa。它们不会取代链接/代码文字角色。作者强调色和预设默认值均需审查。
保留阅读角色,验证真实字体回退
| 角色 | Ink 默认值 | Terminal 默认值 |
|---|---|---|
body |
Inter + 平台无衬线 | IBM Plex Sans + 平台无衬线 |
ui |
Inter + 平台无衬线 | Code 角色:IBM Plex Mono + 等宽回退 |
heading |
Body 角色 | UI 角色 |
code |
IBM Plex Mono + 等宽回退 | IBM Plex Mono + 等宽回退 |
display |
Heading 角色 | Heading 角色 |
meta |
UI 角色 | UI 角色 |
brand |
UI 角色 | UI 角色 |
print |
Body 角色 | Body 角色 |
Book 图注、表格描述及供人阅读的搜索标题/摘要使用正文角色。作者编写的章节编号保留其含义,沿用所属标题的字体角色。Terminal 的技术路径、命令控件和标识符保持等宽。Field 名称保持无框、600 字重。描述性 Terminal 表头改用正文角色 0.875rem/600;普通正文保留 1rem/1.7 与共用阅读宽度。展示标题沿用页面尺度并允许换行;不能为了容纳等宽界面而缩小移动端正文。
现有无衬线回退顺序为 -apple-system、BlinkMacSystemFont、PingFang SC、Hiragino Sans GB、Microsoft YaHei、Segoe UI、Roboto、Helvetica Neue、Arial、Noto Sans SC、通用 sans-serif 与 emoji。等宽列表在 IBM Plex Mono 后依次为 SFMono-Regular、Cascadia Code、Roboto Mono、Menlo、Consolas、Liberation Mono、Sarasa Mono SC、Noto Sans Mono CJK SC 和通用 monospace。列出字体名称不会安装字体。YaHei 排在 Segoe 之前,也可能承载拉丁字符。中文不必占用拉丁等宽字符格。保留语言元数据、typography: system、显式角色及旧 Sass 优先级;在真实 Windows/Android 上分别检查字体已加载、被阻止、系统字体和角色覆盖条件。(字体匹配)
建立层级与按语言区分的链接规则
比较 Ink 正文 h1 的 750 字重/-.025em 字距、h2/h3 的 700/-.012em,以及替代现有粗线的 1px 强线条 token 顶线。中文标题字距为零。取消次级标题、表头、提示块标题的强制大写与字距。Terminal 标题使用 600/零字距;移除生成的 ##/### 及提示块括号,保留作者的 Book 编号、语义标签与标题锚点。不添加伪提示符、闪烁光标特效或装饰性标题尾线。
Ink 正文链接静止时保留原生下划线,包括列表、注释与引用。Terminal 保留持续可见的非颜色链接提示;在真实正文中验证既有半粗体提示,提示不足时使用原生下划线。导航依靠位置和持续的当前状态标记。悬停不得改变布局、字重或命中区域。(GOV.UK 链接)
下划线位置仍待评审。覆盖实验使用 max(1px, .06em) 线宽、.22em 偏移,并对中文比较 .25em/skip-ink: none。不接受一律使用 .25em 的规则。 应比较英文 .12–.18em 与中文 text-underline-position: under 加 0–.06em 小偏移,先使用 skip-ink: auto。按实际语言限定,覆盖英文页面内的中文块。测试混合字符、标点、下伸部、行内代码及跨行链接;线条不得碰撞字形或相邻行。(CSS Text Decoration 草案)
统一组件状态
自有组件圆角:Ink 0px、Terminal 2px,无层叠阴影;纳入目前写死 0.6rem 的 FileTree 圆角。保留图片、头像、状态圆点与 vendor 形状。
当前/选中:Ink 保留静态强调色侧线;Terminal 保留中性反色行/标记。保留程序可识别状态。悬停、选中与键盘焦点相互区分,不使用移动胶囊。
焦点:立即呈现静态指示,在每种相邻表面上独立测量,强制颜色模式下可见;不全局清除轮廓。
文字选择:从现有作者强调色填充开始:alpha 浅色 22%、深色 35%;测量合成后的文字/行内代码配对。必要时通过归属角色修复,不能假定该不透明度已经合格。
Tabs:扁平标签、静态底部选中线、独立焦点;无凸起活动标签。保留键盘、hash/存储行为、长标签和触控区域。
Fields 与输入框:技术 Field 名称不加框,说明保持正文。真正的输入控件仍需持续标签、可辨边界和关联错误文字;占位文字不能单独充当标签。
代码与表格:克制的单层边框;保留语法语义、注释、复制、标题、表头关系与数字对齐。描述性表头可换行,不全表大写。保留作者指定的宽度/溢出策略及局部滚动入口。
注释、引用、脚注:重要内容持续可见;语义标记/线条克制,出处紧邻,引用/返回链接可用。不添加巨大引号、全篇中文斜体或侧边注释栏。
卡片、步骤、徽章、折叠块:标签与形状稳定;区分可点击与静态内容,保留真实步骤编号、原生折叠语义及带标签的状态。
搜索与外观:搜索标题/摘要使用正文角色,技术路径等宽;匹配、选中和弱化文字均可读。保留对话框焦点及返回、输入法编辑、加载/错误/无结果反馈,以及移动键盘展开时的可操作性。
Landing、Blog/画廊、Book、Expo:保留现有构图与媒体行为;展示标题、图注、署名与工具适应各自阅读任务。不对图片表面施加全局方角重置。
保留输出、vendor 与安全边界
Mermaid/ECharts 继续 只跟随深浅模式;API 控件保留 vendor 配色及既有模式行为。不向 vendor DOM 增加预设重着色层。Giscus 自动配色跟随预设与模式,显式覆盖仍有优先权,但本次未验收被阻止的远程 iframe。打印在白纸上使用浅色预设;无 JavaScript 时保留站点默认浅色与可读内容。Markdown/RSS 保留语义内容,不泄漏装饰前缀或控件。继续使用现有转义、字体/颜色输入校验、本地素材和外观存储行为,不新增网络权限或脚本依赖。
兼容与迁移
保留预设名称、独立的模式/风格状态、外壳列宽、断点、内容语法与作者覆盖。Paper 继续默认,维护中的消费站保留显式 [paper, slate, ink, terminal] 菜单。精修可以随版本交付,同时继续保持实验状态。
升级为稳定预设会改变 preset_menu: true 的结果:它提供稳定预设与站点默认值,因此任一实验升级都会扩大自动集合。必须在 layouts/_partials/preset-config.html、归属检查器和双语配置/版本指引中明确处理。显式列表仍保留作者选择的成员。升级需要逐预设的维护者验收,不能只改状态标签。保留 Folio/Canvas 保留名称的警告/回退行为。
实现与归属检查器计划
| 阶段/负责人 | 工作与证据 |
|---|---|
| 共用修复/外壳维护者 | 在页脚归属实现中修复收缩/换行;为全部四个预设增加覆盖到页脚的整页滚动宽度与内容可见性断言。不得用裁切或隐藏溢出代替修复。 |
| 精修/主题维护者 | 在既有预设、字体及组件归属实现中落实受审方案。先运行 bin/check-presets.py、bin/check-font-tokens.py 和受影响的组件/输出检查,保留冻结的 Slate。 |
| 集成/文档维护者 | 在真实双语页面上扩展外观、字体、组件与文字重排套件,纳入整页检查和附注视觉审查。使用同级源码运行 make check、make browser,通过 make dev 审查。 |
| 稳定化升级/维护者与评审者 | 关闭下列门禁;在同次交付中更新归属实现/检查器和受影响的双语契约,再记录长期理由及面向用户的迁移说明。 |
运行时修改还需 node --test 'tests/js/**/*.test.js';Schema 修改需 python3 bin/generate-config-schema.py --check。
验收门禁与待决问题
区分无障碍要求与项目强化目标
以 WCAG 2.2 A/AA 为基线(参考资料访问日期为 2026-10-26)。
| 门禁 | 必须完成的评估 |
|---|---|
| 文字与控件 | 普通文字 4.5:1;符合大字条件的文字 3:1(常规 24px 或粗体 18⅔px,中文采用等效尺寸)。识别控件/状态所需的视觉信息与相邻颜色达到 3:1;装饰分隔线另论。纳入代码注释、占位文字及全部选中/悬停状态。(1.4.3、1.4.11) |
| 重排与放大 | 分别测试 320 CSS px 重排、手动 200% 文字放大,以及从 1280px 视口开始的 400% 浏览器缩放。必要的二维区域需逐项说明,代码/表格并非一概豁免。同时关闭独立的 320px/32px 字号压力阻塞项。(1.4.10、1.4.4) |
| 间距 | 同时覆盖行高 1.5、段后距 2em、字距 .12em、词距 .16em,不丢失内容或功能。这些是用户覆盖值,不是默认排版;评估适用的中文/混排属性。(1.4.12) |
| 键盘与语义 | 焦点可见,获得焦点的控件不被作者内容完全遮挡;状态不只靠颜色,语言、标题、标签和表格关系正确。字符快捷键需可关闭、可重映射或仅在所属组件获得焦点时激活,不能仅在输入框内禁用。适用的搜索状态无需移动焦点即可播报。(2.4.11、2.1.4、4.1.3) |
| 最小目标 | AA 要求 24×24 CSS px,或符合例外。间距例外要求:以每个小目标为中心画直径 24px 的圆,该圆既不能与其它目标相交,也不能与另一个小目标的圆相交;另有行内、等效控件、用户代理及必要表现例外。(2.5.8) |
| 拟议强化目标 | 常用触控控件 44×44px;正文 7:1;整个焦点控件/轮廓可见;静态指示面积至少等于 2px 周长线,并在同一像素位置具有 3:1 状态变化,同时满足相邻颜色对比度。这些超出 AA,需明确采纳,也不意味着全页 AAA。(2.5.5、1.4.6、2.4.13) |
完整任务记录先于稳定化升级
两种语言、两种模式都必须覆盖 320/390/768/1440px,包括正常、放大及覆盖间距的文字。审查长文顶部/中段/页脚、标题/Book 编号、URL、Fields、表格、代码、搜索、外观、媒体与 Landing。检查静止/悬停/当前/选中/焦点/文字选择、强制颜色、减少动态效果,以及无 JavaScript/打印/Markdown/RSS。记录实际颜色配对与 axe 不完整结果的人工审查。
真实 Windows Chrome/Edge、Android Chrome 的字体/渲染工作仍待完成,品牌版 macOS/iOS Safari 检查也未完成。记录设备、操作系统、浏览器与字体条件。人工任务仍需 Windows 上的 NVDA、真实 Safari 上的 VoiceOver、Android 上的 TalkBack:遍历标题及有意义编号,读取表格/字段错误,使用中文输入搜索,听取简洁状态反馈,打开/关闭对话框并恢复焦点。
待决事项包括中文下划线方案、项目强化目标、必要的状态颜色修复,以及各预设是否升级。门禁未关闭时,保持实验状态或推迟精修;其它 1.4 工作无需等待新风格。
决策日志
| 日期 | 拟议决策与状态 |
|---|---|
| 2026-10-26 | Ink 保持中性无衬线编辑风格,Terminal 成为易读工程手册;保留当前配色与作者强调色。建议采纳方向,尚未完成稳定性验收。 |
| 2026-10-26 | 优先修复共用放大字号页脚;保留扁平 Tabs、无框 Fields,不恢复 Paper 尾线。通过既有归属实现移除 Terminal 装饰标点。 |
| 2026-10-26 | 中文下划线替代方案与强化无障碍目标继续待评审;探索性探测不关闭稳定化门禁。 |
| 2026-10-26 | 1.4 不新增预设;Folio/Canvas 保留独立的未来定位。逐预设升级及自动菜单扩展需明确验收。 |
8 - OINK CLI
oink 是面向 OINK 站点的可选命令行辅助工具,用于初始化站点、检查 Hugo 构建产物,以及审阅内容或主题变更。站点渲染仍由 Hugo 完成,项目本身保持标准的 Hugo 目录结构。
日常使用 OINK 建站只需标准的 hugo 与 hugo server 命令,无需安装或编译 oink-cli。OINK CLI 在独立的 oink-cli 仓库中开发,本页介绍本地 0.1.0-dev 候选版,目前尚未发布正式版本或公开安装入口,命令接口仍可能调整。
能做什么
| 命令 | 用途 |
|---|---|
oink init |
从固定 Starter 创建站点,选择站点配置与语言。 |
oink doctor |
检查工具链、配置及实际解析的主题来源。 |
oink check |
检查渲染后的链接、翻译配对与源码排版规范。 |
oink dev / oink build |
调用 Hugo 的本地预览服务或生产构建。 |
oink new / oink move |
先预览页面新建或迁移计划,确认后再应用。 |
oink translations |
查看翻译状态与差异,并记录人工审阅结果。 |
oink upgrade |
对比主题版本差异,审阅后再写入模块变更。 |
此外,CLI 支持输出 JSON/YAML 结构化报告、管理多站点工作区,以及生成带校验结果的构建产物。早期草案中的 Studio 界面与通用源码编辑命令已移除:日常写作请用 oink dev 预览、用常规编辑器改稿,并通过 inspect 或 check 获取结构化诊断。
本地试用
在 oink-cli 源码仓库中使用 Go 1.27.3+ 和 Make 构建可执行文件:
将 ../my-docs 替换为目标站点路径。执行站点命令需要 Hugo Extended 0.168.0+,模块操作需要 Go 1.27+。oink init 使用 CLI 内置的 Starter 快照初始化项目;若需升级至 OINK v1.5.1,请参考升级指南。
make deps 会下载 Go 编译依赖。CLI 命令默认离线运行,需要拉取未缓存的远程依赖时请显式传入 --network。检查与变更预览命令(new、move、upgrade)不会直接修改站点源文件,只有执行显式应用步骤时才会落盘;dev 与 build 则会正常写入 Hugo 构建产物与缓存。