这是本节的多页打印视图。 .
设计与开发
- 1: 架构契约
- 2: 组件契约
- 3: 外壳与导航契约
- 4: 落地页契约
- 5: OINK 迁移边界
-
6: 设计决策
- 6.1: 警告与安全回退
- 6.2: 配置模型
- 6.3: Markdown 优先创作
-
7: 设计研究
- 7.1: Goldmark 块属性实测
- 7.2: 消费站与迁移证据
-
8: 设计提案与 PRD
- 8.1: 反向链接与知识图谱
- 8.2: 媒体收敛
- 8.3: Agent 批量索引
本专栏公开随 OINK 0.6.0 正式发布的维护者契约,兼容性下限为 Hugo Extended
0.160.1。唯一的中英文契约源文件位于本站仓库的 content/docs/design/。
本专栏是 OINK 可长期维护的设计记录。站内其它专栏按任务讲解如何搭建站点; 这里集中说明现行不变量、这些选择背后的理由、用于比较方案的证据,以及仍处于 候选阶段的工作。
如何阅读本专栏
| 层次 | 含义 |
|---|---|
| 契约 | 兼容实现必须保留的规范性行为 |
| 决策 | 用于解释现行行为的已接受理由与边界 |
| 研究 | 带日期且不具规范性的证据,必要时应重新验证 |
| 提案 | PRD 与 RFC 草案;公开在这里不代表已经实现 |
契约目录
| 契约 | 权威范围 |
|---|---|
| 架构契约 | 构建、配置、诊断、特色图片、输出、安全、无障碍与性能 |
| 组件契约 | 组件 API、Book 与发布原语、校验和输出降级 |
| 外壳与导航契约 | 导航、搜索、博客展示、操作、分类法与页尾组合 |
| 落地页契约 | 落地页数据、22 种区块注册表、运行时、无障碍与输出 |
| 迁移边界 | 从 0.4 到当前版本所支持的内容与配置迁移 |
设计记录
以后所有 OINK PRD 或 RFC 都必须以中英文页面对的形式放入
content/docs/design/proposals/,不得再在仓库中创建 plan/、plans/ 或
proposal/ 目录。提案被接受后,应同步更新实现、对应检查器与相关契约,把稳定
理由沉淀到“设计决策”,并通过 Git 历史与变更日志退出草案。
权威来源与维护
本目录同时管理英文与中文维护者设计文档。主题仓库管理可执行事实:hugo.yaml
管理公开默认值;对应的解析器与检查器定义可选结构;layouts/ 与 assets/
管理渲染行为;检查脚本与 tests/goldens/ 管理验收;VENDOR.json 管理内置
依赖的版本、许可证、文件与校验和。
公共行为发生变化时,必须在同一次交付中更新实现、对应检查器以及本目录下相关 契约的中英文版本。测试应验证行为和输出,不应只固定某段文字。
1 - 架构契约
这是随 OINK 0.6.0 正式发布的架构契约。本页是权威中文源文件,与英文版本
一同维护在 content/docs/design/。
仓库与装配
仓库根目录是一个完整的 Hugo 模块与主题,不是站点,也不是 npm workspace。
Hugo Extended 负责编译 SCSS 与模板。浏览器运行时与第三方资源都已提交到仓库,
因此普通构建不会访问网络。公开的双语文档、示例与浏览器测试位于同级的
oink.pgsty.com 仓库;主题仓库只在 tests/site/ 中保留范围明确的内部回归
夹具,不再维护独立的公开示例面。
生成的 public/ 与 resources/ 目录绝不是源文件。随主题内置的运行时、字体
家族与 Font Awesome 字形定义属于受支持的发行内容,并非待清理的死代码;
VENDOR.json 与 bin/check-vendor.py 固定其完整性。OINK 发布完整的受支持
Font Awesome 发行包,因为用户编写的内容可能使用主题模板本身没有引用的图标。
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 声明公开默认值;对应的解析器与检查器定义
任何可选配置的结构或范围。
无效输入遵循同一条规则:警告中写明输入值、允许的结构与安全回退,然后使用该
回退,或省略不安全的功能。普通 hugo server 因而仍可使用,而所有发布门禁都
使用 --panicOnWarning。主题绝不调用 errorf,check-params.py 会强制守住
这条边界。不要为无法到达的状态增加臆测式校验。
OINK 没有通用的键名重命名注册表。仍需给出迁移诊断的过渡,应在所属解析器中 添加针对性警告,并配严格的反向测试;已经移除的键绝不能作为兼容路径继续读取。
可能联网的功能必须显式启用,并以关闭方式降级。PlantUML 需要
plantuml.svg_image_url,Draw.io 需要 drawio.drawio_server,Algolia 需要
appId、apiKey 与 indexName;配置不完整时发出警告,而且不产生网络请求。
Draw.io 只在渲染内容含 PNG 或 SVG 候选图片时加载,并且每个不同的图片 URL
只检查一次。
特色图片
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。页面自己的
包资源优先于继承的 cascade 图片。列表缩略图、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- 组件标记 |
| RSS | 安全的静态摘要,或明确省略 |
站点自行选择是否启用自定义输出;OINK 不会强制生成昂贵的整书聚合。HTML 加载 共享操作层、核心层,以及按页面实际能力和语言生成的功能 bundle。Print 保留 操作层,并且只加载渲染打印功能所需的运行时。大型第三方 UMD 文件保持独立; 未使用的功能运行时不会出现。
性能规则如下:
- 若站点级资源或
partialCached结果可以承担工作,不要为每一页遍历.Site.Pages; .Content只渲染一次,完成后再读取 Page Store 标志;- 直接输出正确标记,不要扫描 DOM 后再修复;
- 浏览器工作按资源 URL 分组,而不是按 DOM 实例重复;
- 成本显著的普通输出应保持选择启用;
- 校验确实可达的作者输入,不校验假想的内部状态。
bin/measure-baseline.py 测量构建时间、输出体积、bundle 数量与 shortcode
密度。bin/sites/build-all.py 在隔离快照中构建维护范围内的消费站点。
信任边界、CSS 与无障碍
作者可以启用 Goldmark unsafe,但配置与组件参数不能视作原始 HTML。共享属性
策略使用允许清单、校验 class token、放行 data-* 与 aria-*,并在丢弃
style、srcdoc、on*、保留属性与未知属性时发出警告。需要本地 URL 或明确
绝对 URL 时,URL 帮助模板会拒绝危险协议与协议相对 URL。公开 API 承诺支持的
远程 URL 仍然可用,但构建时绝不抓取它们。
主题输出使用 td- class、data-td-* 属性与 --td-* 自定义属性;.steps、
.cards、.full-width 等作者标记保持无前缀。CSS 支持 RTL、打印、强制颜色、
减少动画、超长 token 与窄视口。主题拥有的装饰图标带 aria-hidden;只有包含
任务列表或原始 Font Awesome 元素的页面才加载作者内容无障碍修复。
字体角色为 ui、body、heading、code、display、metadata 与 print,
通过 --td-*-font-family 暴露。params.ui.typography 可取 technical 或
system;两者编译到同一份样式表,不加载运行时。旧 Bootstrap/Docsy Sass
变量继续为这些角色提供初值。
发布状态
源码完成、本地验证、提交、打标签、推送、消费站点固定版本、部署与生产一致是彼此 独立的状态。一次本地 Hugo 构建只能证明本地验证通过。
2 - 组件契约
这是随 OINK 0.6.0 正式发布的组件契约。本页是权威中文源文件,与英文版本
一同维护在 content/docs/design/。
教程与完整示例位于面向读者的组件专栏。本页定义这些 指南所依赖的 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 |
| Book 目标 | 图片、表格、passthrough、围栏加 {num=} |
fig、tbl、eq、eg |
无 |
| 发布资产 | checksums 数据围栏 |
release-assets |
HTML 中加载复制功能 |
| 图表与数据 | mermaid、plantuml、markmap、math、chem、echarts、infographic 围栏 |
无 | 只加载选中的本地运行时 |
校验
无效的作者输入遵循架构契约:发出警告,使用文档
规定的安全回退或省略组件,再由 --panicOnWarning 在发布门禁中把同一条诊断
变为致命错误。命名参数与位置参数不能混用。Book 目标 ID 匹配
[A-Za-z][A-Za-z0-9_.:-]*,Book 编号匹配 [0-9A-Za-z.-]+,class 必须通过
token 校验。渲染钩子与 shortcode 目标共享同一个页面注册表,因此冲突不会生成
重复的输出 ID。
URL 使用 content/url.html。图片依次从页面资源、分区资源、全局 assets、static
或显式远程 URL 中解析。本地位图带固有尺寸;SVG、static 与远程来源仍然有效,
但不能执行 Hugo 图片操作。
组件行为
提示块与标签页
提示块类型包括 note、tip、important、warning、caution、success、
danger、question、example、quote 与 details;- 表示初始折叠,+
表示初始展开。未知类型会以中性提示块保持可见,不依赖 JavaScript。
只有连续且区块类型相同的相邻标签页才会分组。group 启用
#<group>-<value> hash 与 td-tabs:v1:<group> 存储键;未分组标签页两者都不用。
HTML 在 JavaScript 运行前暴露所有面板,打印输出展开面板,Markdown 保留作者
源文,RSS 接收渲染后的文本摘要。完整形态支持任意 Markdown;tab.label 必填,
父级存在 group 时 value 才严格必填,孤立的 tab 会警告且不渲染。
步骤、卡片、参数表与表格
原生步骤接受普通区块内容。只有某一步必须包含百分号容器时才使用 shortcode。
原生卡片是链接列表;完整形态增加正文、徽章、图标与图片。原生参数表把第一列
映射为名称、最后一列映射为描述,中间列由 meta= 或表头映射;完整形态允许
区块描述。card 与 field 只能放在各自的父容器中。
参数锚点为 field-<name>,名称转小写,连续标点折叠为连字符,因此
params.ui.typography 变成 field-params-ui-typography。重复锚点追加位置后缀。
表格渲染钩子负责响应式包装与 caption。.matrix 把第一列变为行表头;
.full-width 加宽普通表格或矩阵表格。.fields 不能与 matrix、full-width、
编号或标签页组合;编号与标签页也互斥。
图片、画廊、FileTree 与围栏
Markdown 图片钩子是普通图片 API。行内图片保持行内;块图片带 caption 或 num
时变为 figure。允许的图片属性包括 id、num、caption、width、height、
link、command 与 options,以及共享安全属性。command 与 options 必须同时
出现,并对可处理的本地资源调用 Hugo Fit、Resize、Fill 或 Crop。普通
链接图片使用 Markdown 语法,因此 link 属性要求同时有 caption 或编号。链接
图片与装饰图片不加载缩放。
画廊每行接受一张 Markdown 图片,可带描述、链接与 class。FileTree 接受缩进、
- name、可选 /、注释,以及经过校验的 icon、tone、open、type 属性。Markdown
保留作者源文;打印输出渲染展开的静态图片与文件树。
所有代码高亮都使用 Chroma。通用围栏属性包括 title、copy、wrap、
collapse、label、id、行选项、标签页,以及 Book 的 num/caption。复制
操作返回作者源文。ECharts 输入是声明式 JSON/YAML;回调使用
window.OinkEchartsFunctions 中的 $fn:<name>,绝不执行嵌入脚本。
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-* 索引各自收集一种目标。
整书打印会改写跨页链接,并给普通标题与脚注增加命名空间,同时保留显式目标 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,
否则使用页面标题。
校验和可以接受规范行,也可以接受一个源资源,两者不能同时提供;文件名不能是 路径。HTML 增加本地复制功能,静态输出暴露完整 hash。
下载使用 data/download/<key>.yaml。channel 可取 rolling 或 pinned;只有
pinned URL 与命令会插值 ${version} 和 ${tag}。发布前,rolling channel 保持
可用,pinned channel 显示 pending。Markdown 渲染完整 channel 列表;RSS 省略
该组件。
验证
共享输出规则见架构契约,例外随各组件定义。 Markdown 与 RSS 不设置浏览器运行时标志;Print 只保留渲染打印功能需要的标志。 源码检查覆盖参数、渲染钩子策略、运行时隔离与迁移;输出检查比较 HTML、Print、 Markdown、RSS 与 LLMS golden;浏览器测试覆盖交互界面。迁移行为见 迁移边界。
3 - 外壳与导航契约
这是随 OINK 0.6.0 正式发布的外壳与导航契约。本页是权威中文源文件,
与英文版本一同维护在 content/docs/design/。
权威来源与导航
| 关注点 | 权威来源 |
|---|---|
| 全局导航 | Hugo menus.main |
| Docs / Book 侧栏与翻页 | 内容树或 data/docs_nav.json |
| 根栏目切换器 | 解析后的顶层内容根 |
| 内容发现 | 各语言的本地搜索索引 |
| 页面与命令面板操作 | 共享操作注册表 |
任何功能都不能引入另一套菜单或页面树。菜单只允许一层子项交互;更深层级会警告,
并平铺到带链接的分组标题下。外部链接使用
target="_blank" rel="noopener noreferrer";内部链接保持语言与子路径感知。
顶部导航栏的桌面视图与抽屉视图投影同一棵树,每个下拉面板都是一列宽度适中的
“图标 + 标题"行——mega 面板与其 columns 菜单参数已退役,配置 columns
会发出警告并保持单列。菜单描述只是配置数据,不再渲染。链接树在任何宽度都保持居中:
lg 以上是文字链接,之下收缩为图标链接。lg 与 md 之间,右端保留搜索、版本、
语言、主题与 GitHub,没有菜单按钮;md 以下这些工具移入底栏工具组,此时首页
与显式 Landing 页在搜索旁增加一枚抽屉入口,展开完整的带标签菜单树;其余宽度
与页面一律不渲染抽屉入口。语言链接指向页面译文,缺少译文时
指向对应语言首页;多个语言共享主机与 base path 时保持相对链接,只有语言拥有
独立 baseURL 时才变成绝对链接;hreflang 始终使用绝对链接。
navbar_autohide 从 768px 起只对精细指针生效,绝不作用于触控或抽屉宽度;
隐藏的导航栏不交还占位:两种状态下布局都保留导航栏横带,固定顶栏正好占满这条
横带、下边框画在带内,显现时原地淡入、不遮挡静止内容,hero 页面忽略该策略、
保留自己的叠加导航栏。首页与 hero 页面共用同一套柔和边界:导航栏不画下边框、
滚动时不投阴影,改由栏下一小段渐隐过渡收束边缘。
侧栏与翻页共享同一个根和顺序。manual_link、build.render: link、分隔行、
隐藏节点与占位节点保留各自已定义的语义。sidebar_icon_policy 可取默认的 all、
groups 或 none;图标是一对 Font Awesome class。无效策略遵循共享的警告与
回退契约。
沉浸式博客展示
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 断点以下消失。
搜索、操作与运行时
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 返回额外
字符串。
内置操作 ID 包括 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_theme、
switch_language、switch_version 与 open_github。分享栏之外的 copy_link
只出现在命令面板中。站点通过
languages.<lang>.params.ui.command_palette.commands 配置的命令可以打开安全
URL,或调用内置 ID,绝不能注入 JavaScript。
命令面板有空状态、文本搜索状态与 > 命令状态;快捷链接来自导航。它没有历史、
语义搜索、个性化或远程回退。搜索查询留在浏览器内,默认不发送遥测。
OinkSurfaceCoordinator 协调命令面板、抽屉、根栏目、语言与版本菜单。各界面自行
管理焦点恢复与 Escape。键盘导航会忽略可编辑控件与模态框:/、\、f、c
打开搜索或命令;j/k 移动标题;q/e 翻页;h 改变展示方式;l/y、
t、r 分别打开语言、主题与根栏目选项。侧栏 WASD/方向键导航使用真实焦点,
不会改写 Tab 顺序。
页面大纲从同一套标题模型与滚动容器计算后的 scroll-padding-top 推导光标和可见
标题范围;SVG 线条与圆点共享同一组动画值,不会漂移。禁止增加臆测性的 DOM
修复遍历。
分享
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 会被拒绝。发布门禁通过
--panicOnWarning 拒绝这类警告。
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 图标。图标词汇表由 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 系列条:默认收起的一行显示系列名和第 M/N 篇,展开后按阅读
顺序一行一篇,打印时默认展开。单篇系列与非 HTML 输出省略它。编号、交叉引用与
聚合输出仍属于 Book。
默认文章分类法徽章会排除保留的 authors 与 series,因为专属界面已经展示
它们。显式设置 params.taxonomy.page_header 可以恢复任意一项。
博客索引与页面组合
博客分区索引使用 params.ui.blog_index:默认的 list 与 cards 都是按最新优先
排列的一段扁平结果,共享 blog_index_size 分页;元数据行已经显示日期,所以不再
需要年份标题。table 把整个分区显示为日期、标题、标签行,不分页。卡片使用共享
首图、本地化日期/作者/分区元数据、标签与三行摘要。Term 与 taxonomy 页面保持
行列表。
params.ui.blog_index_toggle 为当前分页切片渲染三种形态,并允许读者循环切换。
配置值控制首次绘制,本地存储可以覆盖它,隐藏形态不加载图片。Front matter 或
cascade 可为每个分区覆盖站点模式。没有切换器的 table 仍是完整且不分页的归档。
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。
验证
bin/check-navigation-contract.py、bin/check-shell.py、JavaScript 测试、输出
golden 与消费站点浏览器套件覆盖导航、语言与子路径链接、博客变体、页尾顺序、
键盘行为、无障碍与响应式布局。
4 - 落地页契约
这是随 OINK 0.6.0 正式发布的落地页契约。本页是权威中文源文件,与英文版本
一同维护在 content/docs/design/。
外壳与数据
任何普通页面都可以声明 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 把它规范为
锚点安全值。未知类型遵循共享的警告与安全回退策略,绝不静默消失;发布时
--panicOnWarning 会拒绝它。内置区块由 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。
交互式 HTML 设置 hasLanding,从而只按需添加 landing.js。运行时复用
OinkSurfaceCoordinator,负责出现动画、数字递增、复制、紧凑菜单与主题图片
增强。没有 JavaScript 时,服务端输出仍然完整。
跑马灯只用 CSS 复制;副本带 aria-hidden 与 inert,本地化复选框无需
JavaScript 也能持久保存暂停状态。减少动画会停用动画,强制颜色保留控件,主题
图片响应共享主题事件。顶部导航栏 mega menu 接受 1–4 列。紧凑菜单使用真实链接
与按钮,不捕获焦点,也不复制桌面导航树。
输出与兼容性
| 输出 | 契约 |
|---|---|
| HTML | 完整静态区块加渐进增强 |
| 静态网格与内容,移除控件 | |
| Markdown | 不带主题 class 的标题、正文、列表、表格与代码 |
| RSS | 省略落地页区块 |
非 HTML 输出不设置 Landing 标志或运行时。根相对链接与资源遵循部署子路径;普通 构建不下载图片。
已经移除的 0.4 组件形态属于迁移工具,不是并行的落地页实现。OINK 不增加价格 周期切换、远程事实 API、热点编辑器、可视化构建器或第二套注册表。既有首页数据 与显式自定义区块 partial 继续有效。
5 - OINK 迁移边界
这是随 OINK 0.6.0 正式发布的迁移契约。本页是权威中文源文件,与英文版本
一同维护在 content/docs/design/。
这是源码与配置指南,不是版本发布流水账。本地源码、提交、标签、推送、消费站点 固定版本、部署与生产一致仍是彼此独立的状态。面向读者的升级流程见 版本升级。
工具范围
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;它不是通用解析器。
从 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,取反 |
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。
已移除名称会警告,并采用文档规定的安全回退或不渲染;普通预览可以继续,严格
门禁通过 --panicOnWarning 拒绝它们。blog_index_toggle、
featured_image: hero、toc_style 与 toc_taxonomies 是增量选择启用项,不会
引入内容类型;沉浸式阅读仍使用普通博客外壳。
前置条件与验证
按照组件契约启用 Goldmark unsafe 渲染、块属性与
独立块图片。要使用 \(...\)、\[...\] 或 $$...$$,需要显式启用 passthrough;
Hugo 不会合并主题的 markup 配置。
针对改动的契约运行范围最小的源码与输出检查,覆盖两个受支持的 Hugo 版本;运行时 变化时执行 JavaScript 测试,并严格构建根路径与子路径。对于维护范围内的站点, 在桌面与窄视口检查有代表性的 EN/ZH Docs 与 Blog 路由,再分别记录固定版本、部署 与线上一致状态。
6 - 设计决策
决策记录解释 OINK 为什么在多个兼容方案中选择了当前设计。上方五份契约仍是 现行行为的规范描述;实现与归属检查器仍是可执行事实。
OINK 过去把评审、PRD 与执行记录放在本地 plan/ 目录中。这样既不便发现有价值的
推理,也容易让已经放弃的设计看起来仍有权威。已经接受的理由现在统一进入这座双语、
版本化的文档站,与它所支撑的契约放在一起。
决策地图
| 决策 | 解决的问题 |
|---|---|
| 警告与安全回退 | 为什么普通预览能容忍错误输入,而发布仍保持严格 |
| 配置模型 | 配置放在哪里、页面如何覆盖,以及 OINK 为什么不另造配置命名空间 |
| Markdown 优先创作 | 为什么优先使用原生 Markdown,以及 Docs、Blog、Book、Landing 如何延长共享系统 |
记录格式
一份已接受决策应记录背景、选择、后果,以及证明该选择仍然成立的证据。它不重复参数 参考或教程。每份决策都要链接到归属契约与验证面,中英文页面必须同步修改。
决策发生变化时,应在同一次交付中更新实现、检查器、受影响契约与决策记录。旧答案留在 Git 历史和版本变更记录中,不在导航树里并列保留两套“现行”答案。
相关
6.1 - 警告与安全回退
OINK 不调用 Hugo 的 errorf。作者或站点输入无效时,主题发出警告,并使用文档中
明确的安全回退,或者省略无效片段。版本发布与部署构建使用 --panicOnWarning,
因此同一条警告在发布门禁中仍会导致硬失败。
背景
Hugo 把整座站点作为一次事务构建。编辑一页时触发的 errorf 会让该次重建中的所有 URL
都返回错误,包括无关页面和首页。服务器进程仍然存在,修正输入后也会自动恢复,但多人共享
的预览在此期间完全不可用。
警告的开发成本不同。出错的值可以回退,站点其余部分仍可检查,作者也能看到准确消息。
发布构建则不会放过它,因为 OINK 的 CI 与集成门禁都会加上 --panicOnWarning。
决策
校验遵循四条规则:
- 点明无效键和值、允许的形状以及实际采用的回退值。
- 值来自页面 front matter 时带上页面位置;站点级错误不要在每一页重复刷屏。
- 不允许无效值继续参与后续运算。先校验,再用规范化后的值渲染。
- 没有诚实回退时,警告并且不渲染。不能为了继续构建而编造内容、发起网络请求或输出 不安全 URL。
枚举、布尔、CSS 长度与数字的共享校验形状位于
layouts/_partials/validate.html。领域 resolver 可以增加更窄的规则,但必须保留同一套
警告与回退契约。
安全边界
继续构建不等于继续输出危险内容。被拒绝的 CSS 长度要在进入 style 属性之前回退;远程服务
配置不完整时,要在浏览器可能发起请求之前省略组件;不安全的操作 URL 直接丢弃。真正的保护是
坏输出没有出现,而不是 Hugo 被终止。
这也把编辑与发布清晰分开:
| 阶段 | 无效输入的处理 |
|---|---|
hugo server 或普通本地构建 |
警告、回退或省略,其它页面继续可用 |
| CI、版本验收、部署 | 同一警告在 --panicOnWarning 下让构建以非零状态退出 |
后果
- 每个回退值都是公开契约的一部分,必须与主题声明的默认值一致。
- 从“失败”改成“回退”时,测试也必须改变。负向测试要同时证明普通构建存活、警告文案、 渲染后的回退,以及严格构建失败。
- 检查器必须直接验证被拒绝的输出。例如 URL 安全测试应断言危险 URL 没有进入产物,不能把 任意构建失败当作充分证据。
- 参数源码检查器守住主题 layout 中不存在
errorf调用这一不变量。
验证
本决策的归属参考包括
架构契约、
bin/check-params.py,以及主题夹具与本站的严格构建。
6.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 的不变量。公开参考及其
中文对页由集成站的双语和渲染链接检查覆盖。
6.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 - 设计研究
研究记录测量了什么、使用了哪些输入与工具版本。它可以解释决策,但不能覆盖当前契约或实现。
只有其他维护者能够检查方法、理解边界并复现相关检查时,研究才适合进入公开 Design 内容树。 原始 Agent 对话、临时构建日志和本机绝对路径不符合这一标准。
研究地图
| 记录 | 证据 |
|---|---|
| Goldmark 块属性 | 支持的 Hugo 下限版本上,渲染钩子能看到什么,以及 CommonMark 容器的边界 |
| 消费站与迁移证据 | 带日期的语料盘点与确定性 Book 迁移结果 |
发布规则
研究记录必须说明日期、输入、相关版本、方法、结果与已知边界。容易变化的数字明确标为快照。 涉及外部框架的比较,公开前要依据一手资料重新核验,并提炼成与 OINK 有关的结论,不能直接 复制成竞品目录。
7.1 - Goldmark 块属性实测
这些探针在 Hugo Extended 0.160.1 与 0.164.0 上得到字节一致的相关输出。它们解释 OINK 的原生组件形态;当前组件契约仍是权威。
方法
探针使用一个不带 OINK 模板的最小 Hugo 站点。渲染钩子把上下文字段与 .Attributes 输出为
可见标记。站点开启 Goldmark 块属性、行内与块级数学 passthrough 分隔符,以及为检查原始 HTML
而刻意启用的 unsafe 渲染,并设置 wrapStandAloneImageWithinParagraph: false。
每种源码形态分别用兼容下限版本和当时的当前 Hugo 版本渲染,再逐字节比较相关产物。以下结论 记录平台行为,不涉及视觉样式。
结论
| 源码形态 | 钩子结果 | 设计意义 |
|---|---|---|
含段落、围栏、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 版本不在 承诺范围内。调整 Hugo 兼容下限时,应先重跑组件、Book、表格、gallery 与 Markdown 输出检查,再更新 这份快照。
7.2 - 消费站与迁移证据
这些计数描述 2026 年 8 月被检查的仓库。它们是设计选择的证据,不是实时产品指标或兼容承诺。
语料
创作语料盘点扫描了十一个 OINK 消费站点的 content/ 树:共 5,325 个 Markdown 文件,其中
5,293 个带 YAML front matter。样本同时包含单语言英文与中文参考站、双语产品站、发布归档、
自定义落地页,以及独立的 Book 消费站。
盘点刻意测量源码 Markdown,而不是生成后的 HTML。统计项包括 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 边界见 创作书籍和 迁移契约。
边界
这些数字不能直接用于产品宣传,也不能当作当前站点清单。重做研究时,需要重新确定仓库清单并生成 新的带日期报告。本公开记录刻意排除了本机路径、未提交内容、私有仓库名称、原始 Agent 对话与生成 构建产物。
8 - 设计提案与 PRD
提案描述的行为可能尚不存在。当前行为由契约、已接受决策、实现与归属检查器定义。不能把提案 当作配置参考。
本栏目是 OINK 产品需求文档、RFC 风格设计与未决维护者提案的唯一正本位置。不要在主题仓库或
文档仓库中另建本地 plan/、plans/、proposal/ 或其它并行设计树。
当前提案
| 提案 | 当前边界 |
|---|---|
| 反向链接与知识图谱 | 草案;当前没有 graph 或 backlink 实现 |
| 媒体收敛 | 草案;共享正文 resolver 与 Zoom marker 已落地,只记录剩余跨表面收敛 |
| Agent 批量索引 | 草案;每页 Markdown 与 llms.txt 已存在,全文合集与导航 JSON 尚不存在 |
新 PRD 放在哪里
创建一份英文主页面及其简体中文对页:
两份文件都使用显式、稳定的英文标题 ID。中文页面中的代码、键、路径、版本与 API 名称保持原样。 提案开头要有可见的草案状态,并包含:
- 状态、负责人、日期和受影响契约面;
- 背景与证据;
- 目标与明确非目标;
- 提议行为,以及输出、无障碍、安全边界;
- 兼容与迁移影响;
- 实现与归属检查器计划;
- 验收标准与待决问题;
- 记录提案自身变化的决策日志。
大型实验可以在 ../research/ 下增加带日期的页面;临时日志与生成
产物不进入 Hugo 内容,也不进入 Git。
生命周期
提案被接受后不会自动成为第二份契约。稳定行为进入归属契约,稳定理由进入 Decisions,用户步骤进入 相关指南,然后把提案退出活动导航。本地构建、提交、tag、公开模块、消费站 pin 与部署仍是相互独立 的完成状态。
评审门禁
实施前,评审者确认提案没有重复已有外壳、resolver、组件族或数据权威。实施期间,如果设计改变, 先更新这份双语提案,不能让代码悄悄漂移。验收至少覆盖主题的最窄归属检查、真实文档站、渲染后的 中英文、相关输出、无障碍与响应式检查。
8.1 - 反向链接与知识图谱
OINK 当前没有 backlink 区块、局部图谱、全站图谱页或 graph 输出格式。提案中的名称和配置在 提案被接受、契约发生变化之前都不是公开 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 在页尾附近输出一份短小、有序的列表。排序必须确定:先按 section,再按导航 weight、标题,最后 用稳定路径破平。区块使用普通链接与标题,不把内容只藏在 disclosure 中;没有入链时不渲染。
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 是 opt-in、opt-out,还是只为选定 shell type 开启?
- 局部图只暴露一层,还是允许严格限额的第二层?
- 哪些页面元数据值得进入 graph JSON?
- 无法解析的启发式边应保持静默并由专项链接检查报告,还是显示去重后的预览警告?
- 在 G1、G2 获得生产证据前,G3 是否值得新增输出格式?
8.2 - 媒体收敛
OINK 已经具备共享正文图片 resolver、单一 Zoom marker、可处理的 Markdown 图片、编号 figure 与安全的 Landing URL 处理。本页只提议尚未解决的收敛问题,不能把它读成“当前缺失功能清单”。
当前基线
正文图片钩子、编号 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处理能力是否仍有真实消费需求? - 哪些输出兼容名称仍被真实消费站使用?
8.3 - Agent 批量索引
OINK 已经支持每页 Markdown、语言内 llms.txt、HTML discovery link 与 Copy Markdown。
当前不发布 llms-full.txt 或导航 JSON。本页只提议这两类剩余输出。
当前基线
站点可以为 page 与 section 启用 Hugo 的 Markdown 输出,并为 home 启用生成 llms.txt 的 LLMS
输出。OINK 把 shortcode 渲染成语义化 Markdown,保留源码 URL 和语言内 LLMS 索引发现信息,Copy
Markdown 也读取同一个 alternative output URL。主题声明输出格式,但不强迫站点选择哪些 outputs。
导航已经存在权威链:有显式 data/docs_nav.json 树时使用它,否则使用内容树与 weight。侧栏、
pager 与已声明 section index 共用这一权威。机器导航输出必须从同一棵树派生,不能再造排序。
目标与非目标
目标:
- 为小型站点可选装配语言内全文包,为大型站点可选按顶层 section 分包;
- 可选发布带版本的导航 JSON,供 Agent 与外部工具使用;
- 复用人工站点的同一 Markdown 页面渲染器、页面纳入规则与导航权威;
- 所有输出仍通过 Hugo output 配置 opt-in;
- 验证链接、语言隔离、media type 与确定性顺序。
非目标:
- 替换每页 Markdown 或
llms.txt; - 新建
params.oink.*配置树; - 在 Hugo 构建期间抓取生成好的
public/文件; - 嵌入私有源码路径、草稿页面或跨语言回退;
- 承诺一个巨型全文包适合所有模型上下文。
全文包
提议的 llms-full.txt 输出拼接每页输出所用的同一份语义化 Markdown。页面之间使用稳定、可见的
分隔符与来源 URL。站点从两种部署形态中选择:
| 形态 | 位置 | 适用场景 |
|---|---|---|
| 全站包 | 每种语言在语言根下一个文件 | 小型、聚焦站点 |
| section 分包 | 每个显式启用的顶层 section 一个文件 | 大型参考站与书籍 |
由 Hugo output 配置决定哪些页面获得该格式,而不是由主题参数决定。主题可以提供检查器,报告意图 与实际输出不一致,但不能修改站点输出集合。
全文包在 Hugo 内部通过共享页面渲染 partial 组装,不读取 public/ 中的兄弟产物,也不依赖输出
构建顺序。文件大小作为证据报告;任意阈值不能通过警告让 --panicOnWarning 拒绝原本合法的发布。
导航 JSON
提议的 JSON 包含 schema 版本、语言、根节点与递归有序节点。页面节点包含稳定 ID、标题、HTML URL、 启用时的 Markdown URL、kind/type、weight 与 children。显式外部导航节点只包含标签、URL 与 external kind。
输出遵循渲染侧栏相同的可见性与排序规则,排除 draft、headless resource、隐藏导航项与当前语言 不可用页面,永不序列化本机文件名。
该格式拥有自己的 JSON Schema 与 golden 夹具,并标记为 notAlternative,避免 Hugo 把它广告为
页面级 alternate。
发现信息与输出边界
llms.txt 可以链接已经启用的全文包与导航 JSON。HTML head 继续发现每页 Markdown 和语言内 LLMS
索引,不把每个批量产物塞进每一页。
shortcode、Landing section、Book 目标与交互组件继续使用当前 Markdown 降级。新输出无权增加组件 HTML、脚本、评论、反馈控件或导航 chrome。
验收标准
- EN 与 ZH 输出只包含各自语言的页面和 URL。
- 每个列出的 Markdown URL 都存在;每个导航 URL 都可解析,或明确标记为外部节点。
- 同一根下的顺序与渲染侧栏、pager 一致。
- 固定 Hugo 版本与输入时,相同源码重建得到字节稳定输出。
- 新格式关闭时,HTML、Markdown、Print、RSS 与 LLMS golden 均无回归。
- 大站夹具能证明按 section 分包,而不是为每个嵌套 section 都生成文件。
待决问题
- 两种全文部署形态是否都需要,还是只按 section 分包更安全?
- 导航 JSON 应当是 home output,还是由 resource template 支撑的专用内容页?
- 哪些节点元数据足够稳定,可以进入 schema version 1?
- 导航 JSON 存在时,
llms.txt是否默认列出它? - 检查器应报告哪些体积证据,又不武断执行某个模型上下文上限?