跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

博客

Crib 的设计思路、工程记录和开发进展。

1 - 文章

Crib 的设计思路与工程细节。

1.1 - 为什么做 Crib

放在手边的手册应该离线、分版本、中英文对得上,还能记下自己的结论。

PostgreSQL 的手册写得好,但查起来常常不顺手。Crib 想解决的是几个反复出现的小麻烦,而不是再做一个文档网站。

版本要对得上

线上跑着 9.6、12、14 的人很多。搜索引擎给的往往是最新版的页面,参数默认值、视图的列、函数的签名都可能已经变了。要查旧版本,就得记住 URL 规则,或者翻到页面角落去切换。

Crib 收录 6.3 到 20devel 共 31 条版本线,每条固定到一个确切的内容版本。搜索时在前面写上 PG96:,结果就只来自 9.6 的手册和 Wiki;同一个概念在多个版本里出现时,结果折叠成一组,展开就能逐版对比。pg18 这样的别名总是解析到一个确切的发布,比如 18.6;目标版本没装,就直接说没装,不拿 18.4 冒充。

中英文要对得上

中文手册降低了阅读门槛,但译文总有拿不准的地方,这时需要看英文原文的同一段。在两个网站之间来回翻目录,十几秒就过去了,思路也断了。

Crib 把每条版本线的中英文手册打成一个双语包,构建时从手册源码里找出两种语言共同的章节标识,只有两边都能唯一确定时才建立对应。阅读时一键切到另一种语言的同一节,或者两栏并排对照。62 套手册之间有 77,580 对这样的章节对应;没有对应的地方,就回到父节并告诉你,不靠标题去猜。

断网也要能用

机房、内网、飞机上,在线文档打不开,下载的 PDF 又没法跨版本搜。Crib 的手册、Wiki 和搜索索引都随应用安装,第一次打开就能断网阅读和搜索,不需要先下载、先建索引。完整离线版随附全部内容;Core 版只带 18.6,其余版本需要时再从签名目录下载,下载、校验和重建索引都在后台完成,验证通过才切换。

->>、@@、\dC、23505 这类写法是 PostgreSQL 用户最常查的东西,也是通用搜索最容易丢掉的东西。中文又没有空格分词,“锁”“索引”这样的短词很难搜准。Crib 先按精确名称查,再搜正文;中文按双字切分,单个汉字走名称索引。细节见另一篇文章。

自己的结论要留得住

排障时查到的结论、发现的译文问题,记在别处很快就和原文脱节了。Crib 的笔记、书签和本地更正挂在具体章节上,存在独立的个人资料库里,官方原文不动。内容更新后,笔记按原来的锚点重新定位,找不到就列出来等你处理;本地更正则暂停显示,列入“待重新定位”。所有个人资料都可以导出成 Markdown 加 JSON。

为什么是原生 macOS 应用

需要的是原生窗口和标签页、系统服务菜单、可靠的输入法处理和键盘操作,以及不依赖本地 HTTP 服务的离线阅读。所以界面用 SwiftUI 和少量 AppKit,正文用 WKWebView 通过受限的内部地址读取资料包,搜索用 SQLite FTS5。构建端用 Go 生产和验证资料包,两边共用同一套格式合同和测试样例。

现在到哪一步了

功能已经实现,正在做最终候选的验收、许可核对和正式签名的准备,还没有公开安装包。进展会写在开发日志,当前清单见发布状态。

1.2 - 让符号、中文和版本号一起被搜到

符号、两个字的中文词和版本前缀分别怎样处理,以及我们用什么来检验。

PostgreSQL 用户搜索的东西很杂:参数名 work_mem,运算符 ->>,psql 命令 \dC,错误码 23505,中文词“复制延迟”,还常常带着版本号。通用的全文检索对这些都不友好。这篇说明 Crib 的做法。

先查名字,再查正文

排序分四层,前一层的结果总排在后一层前面:

  1. 当前范围内的精确名称和别名;
  2. 名称前缀;
  3. 标题和章节名;
  4. 正文。

精确名称存在一张普通的 B-tree 表里,键按类型规范化:配置参数、函数、运算符、psql 命令、SQLSTATE 各有自己的规则。\dC 和 \dc 是两个不同的命令,大小写保留;->> 和 @@ 按原样保存,不会被当成标点丢掉;SQLSTATE 代码按字符串处理,前导零不丢。同名的函数重载各自保留,不会因为名字相同只剩一个。

这张表由构建端从手册源码里提取,比如参数定义列表中的 max_connections 记为“定义”,正文里提到它的地方只算“提及”。所以搜 max_connections 时,第一条是参数定义本身,而不是某段恰好提到它的正文。

粘贴进来的对象名也能认出来:pg_catalog.max_connections; 会命中 max_connections 的定义,并在结果和正文里高亮这个名字。运算符、psql 命令和 SQL 表达式则保持原样,不做这种改写。

中文按双字切分

SQLite 自带的 unicode61 分词器会把一整段中文当成一个词;trigram 分词器不匹配少于三个字符的查询,“事务”“索引”这样两个字的词就搜不到。

Crib 用自己的切分规则:连续汉字按重叠双字切分,“事务隔离”切成“事务、务隔、隔离”;拉丁字母的词和标识符另外提取。切分结果编码成带类型前缀的 ASCII 词项再交给 FTS5,构建端(Go)和应用(Swift)用同一套规则,并用同一组测试样例核对。规则有自己的版本号,写在每个资料包的清单里,规则一改,旧包和新包不会混用。

双字切分只能证明两个字出现过,不能证明短语成立。所以加了引号的短语还会回到原文核对顺序和连续性。

单个汉字没有双字可切。“锁”这样的查询走标题和名称的字符索引,在有限的候选里查,并说明范围,不做全库扫描。

输入法全角模式打出的 vacuum 会在查询端折成半角再搜;全角逗号、括号是中文正文的一部分,保持原样。版本前缀和筛选条件里的全角冒号(pg18:)也能识别。

版本前缀和过滤器

查询先解析成结构化条件。PG96:、[email protected]:、pgdevel: 是版本前缀,lang:、kind:、branch:、collection:、domain:、project: 是过滤器,名单是固定的,其余都当作文本。pg_stat_activity: 或者粘贴进来的 ERROR: 不是版本前缀,照常当文本搜。

别名由已签名的版本目录解析:pg18 指向 18.6,pg96 指向 9.6.24。目标版本没有安装或被停用时,搜索直接说明,不会悄悄换成同一条线的其他小版本。显式写了 lang:en,就只返回英文正文。

多个版本折叠成一组

work_mem 在 31 条版本线里都有。如果每个版本各占一行,第一页就全是它。Crib 只在已经证明是同一个对象时才折叠:同一组里,当前选中的版本排在前面,其他版本展开后可见。出现在 31 个版本里不会让它的相关性变高;没有证明是同一对象的同名条目,各自独立。

用什么来检验

我们维护了一组 369 条标注过的查询,覆盖精确名称、符号、psql 命令、SQLSTATE、中文、中英混合和版本过滤。每条的期望结果由独立的审核者对照固定来源确认。2026-11-02 在当前源码和索引上的完整运行结果:

指标结果
前 10 条内找到期望结果369 / 369
要求排第一的查询270 / 270
过滤、分组、摘要等约束违例0
单版本搜索,热缓存 p9531.3 ms
全部版本搜索,热缓存 p9521.8 ms

延迟是在开发机(Apple M5 Max)上测的,不代表其他机器。这组查询会随着问题报告继续扩充;它证明的是这些查询的行为,不是所有查询都同样好。

搜索语法的完整写法见搜索。

2 - 开发日志

Crib 发布前的阶段进展、改动和尚未完成的事项。

2.1 - 11 月第一周:深度评审与界面重设计

修完一轮深度设计评审,重做界面与文案,资料包改用 .crib 扩展名。

这一周做了三件事。它们都还停留在本地开发构建,没有发布。

深度设计评审的修复

11 月 5 日对整个项目做了一轮深度设计评审,列出了 9 个最高优先级(P0)和 8 个次高优先级(P1)问题。本周修完了其中 8 个 P0 和全部 P1,以及各子系统的大部分中低优先级问题;复核修复本身时又发现二十多处问题,也逐项修复并补了回归测试。源码也是在这一轮第一次进入 Git,按主题分批提交。

几处读者能感受到的变化:

  • 开发签名的内容只在声明过的构建里被接受。 开发构建在资料库标题旁显示“非正式发布”徽标;正式构建拒绝开发密钥签名的内容。
  • 签名密钥可以限定用途和到期时间。 一把密钥可以只用于资料包、出厂索引或版本目录之一,到期后不能再用于获取新内容,但已安装的内容不会因此失效。
  • 本地 API 先验证服务端再发送密钥。 如果 Crib 崩溃后端口被别的程序占用,客户端不会把访问密钥发给它。
  • 个人资料库打不开时只停用个人功能。 阅读和搜索照常工作,并提供从备份恢复或移走后新建的选项,默认什么都不删。
  • 版本目录更宽容。 某个非默认版本的条目校验失败时,只丢弃这一条并提示,不再整份目录作废。

剩下的一个 P0 是生产密钥:机制已经就位,但生产密钥、两级密钥和撤销流程还没有建立,所以经过公证的正式版暂时还不能携带可下载的内容。

界面与文案重设计

界面换了一套设计规范,参考 Cloudflare Kumo 的设计语言和 Fluid Functionalism 的动效原则,落在 macOS 原生控件上:

  • 无色相的灰阶加一种墨蓝强调色,只用于链接、当前项和主要操作;
  • 图标全部改为 SF Symbols 线性图标;
  • 标题用 semibold,不用粗体;平面之间用细线分隔,不叠阴影;
  • 悬停变色不做动画,展开和弹出用短弹簧动画,开启“减弱动态效果”时全部取消。

阅读器的样式也改用同一套色板,顺带修好了深色模式下一部分加粗字发灰的问题。

源码中约 1,700 条中文字符串逐条按新的语气和术语表重写或确认,最长的提示从约 130 字缩到约 60 字。原则是只在必要时说话:正常状态不提示,提示只说发生了什么、可以怎么做。比如版本目录里的 active 状态译为“常规”,不写“受支持”,因为它只表示出现在普通列表里,不是上游的维护承诺。

本站的截图就来自重设计后的开发构建。

资料包改用 .crib 扩展名

资料包原本暂用 .pgdocpack 扩展名,现在新发布一律写 .crib,例如 pg/18.6-r4.crib、pgwiki/r5.crib。已经签发的 .pgdocpack 路径写在签名目录里,作为签名身份继续有效,应用和发布工具都接受;其他扩展名一律拒绝。包内的格式和内容哈希都没有变。见资料包格式。

还没做完的

  • 真实桌面窗口、工具栏、实体键盘与输入法的目测验收(本周只有离屏快照和隐藏窗口测试);
  • A01–A10 验收项绑定到最终候选构建;
  • 生产密钥、Developer ID 签名与公证;
  • 应用代码许可证和内容再分发许可;
  • 应用图标、.crib 文件类型注册(双击导入)、应用自动更新、界面本地化。

完整清单见发布状态。

2.2 - 从 PGDash 改名为 Crib

哪些名字变了,哪些签名身份保持不变,旧的用户资料怎样迁移。

Crib 开发前期叫 PGDash。2026 年 10 月 28 日决定正式改名为 Crib,10 月 31 日在本地完成迁移并验证。如果你没用过 PGDash 的开发构建,这篇可以跳过。

改了什么

项目原来现在
应用PGDash.appCrib.app
Bundle IDcom.pgsty.pgdash.*com.pgsty.crib
用户目录~/Library/Application Support/PGDash~/Library/Application Support/Crib
深链接pgdash://crib://
MCP 程序与配置键PGDashMCP、pgdashCribMCP、crib
环境变量PGDASH_*CRIB_*
导出文件名PGDash-*Crib-*
签名版本目录pgdash-signed-catalog-1crib-signed-catalog-1(v3)

没有改的

已经签名的内容不能改名,否则签名和哈希就对不上了。下面这些标识保持原样,它们只是格式的名字,不代表产品名:

  • 资料包清单里的切分规则 pgdash-han2-v1 和格式版本;
  • 资料包内的章节键、锚点和内容哈希;
  • 阅读器读取资料包用的内部地址 pgdoc://;
  • 2026-11-06 之前签发的 .pgdocpack 文件路径。

旧的 v1、v2 版本目录照样能验证和读取;从旧目录到 v3 目录,签名修订号继续递增,不因改名归零,防止有人用旧目录做回滚。

旧资料怎样迁移

Crib 第一次启动时,如果发现旧的 PGDash 用户目录、而 Crib 目录还不存在,就做一次迁移:

  1. 把旧目录中的所有文件复制到一个临时目录,包括不认识的文件和全部会话;
  2. 个人资料库用 SQLite 在线备份复制,正在写入的 WAL 数据也能完整带上;
  3. 核对文件清单、哈希和数据库完整性,全部通过才原子地启用新目录;
  4. 记录一份迁移回执,重复启动不会再用旧数据覆盖新数据。

旧目录始终只读,不修改也不删除。迁移失败时,Crib 不往新目录写任何东西,下次启动再试;也可以选择跳过迁移。旧的 API 访问密钥不会被带过来,需要在 Crib 里重新启用本地 API。

细节见用户数据与迁移。

兼容旧的写法

  • PGDash 时期复制的 pgdash:// 链接仍能打开;新复制的链接一律是 crib://。
  • PGDASH_* 环境变量仍会读取,同名的 CRIB_* 优先。
  • 旧的片段导出文件按内容格式识别,不看文件名,可以直接导入。
  • MCP 客户端里的旧配置不会被自动改写,请在 Crib 的“设置 → 集成”重新复制一份。