Facade 建设笔记:从 Research Archive 到写作发布链路
Facade 不是从零开始的。代码仓库里已经有一套能跑的 Research Archive 页面——自定义的 research.ts 数据层直接用 Node.js 文件系统读取 Markdown,列表页能看到报告标题、日期和文件大小,详情页用 markdown-it 渲染全文。甚至导航栏里”研究”这个入口都已经注册好了。
问题在于,这套东西只是”能看”。160 份报告没有结构化的元数据,没有搜索,slug 是 SHA-1 哈希的前 12 位(类似 0b6b92759c67 这种对人类毫无意义的字符串),同一个 campaign 的报告之间也没有任何导航关系。它绕过了 Astro Content Collections 的整个类型系统,意味着没有 Zod 校验、没有 TypeScript 类型推导,和旁边已经跑得很好的 6 个 Collection(writing、moments、notes、projects、gallery、radar)完全是两套架构。
所以 Maple 最近这一轮工作的核心不是”建新功能”,而是升级:把 ad-hoc 的文件读取升级为正式的 Content Collection,把 160 份报告从”能看”变成”能找、能筛、能串联”。顺便把视觉方向彻底定下来,再搭一条从调研报告到博客文章的半自动写作链路。
我把这三件事的来龙去脉整理成下面这篇笔记,一个接一个聊。
视觉方向:不再在 107 个原型间摇摆
先说视觉。Facade 仓库里积累了 107 个原型方向——这个数字本身就说明了问题。探索是必要的,但无限探索就是拖延。
Maple 最终定在了 ink 主题上。
做这个决定的过程,反而不是在 107 个方向间逐一对比,而是先去看行业里做得好的人在做什么,再回头审视手里已有的东西。2025-2026 年的个人技术站设计,极简主义已经不是”留白加无衬线加灰色调”那种刻板印象了,它分化出了三个子方向:排版极简(字体即设计,空间感来自间距而非装饰)、结构极简(信息架构清晰,允许局部密度)、视觉极简(大面积色块加少量强调元素)。Facade 需要的是前两种的结合——排版足够克制,但 Research Archive 那 160 份报告需要一定的信息密度。
看了一圈标杆站点之后,回头发现 ink 主题和这些最佳实践高度吻合。暖灰底色、Noto Serif SC 做中文标题配 Inter 做正文、行高 1.95——这些排版选择在技术上已经到位了。暗色模式的表面层级用亮度递增而非加深阴影(--c-base: 18 16 12 到 --c-surface: 28 24 18 再到 --c-elevated: 38 32 24),正文用偏暖灰的 #D2C8B6 而不是纯白,强调色在暗色模式下提高了饱和度——这些都符合 Muzli、Material Design 和 Close.com 色彩系统总结的暗色模式最佳实践。
ink 相比另外两个方向(edge 偏冷科技风、press 偏杂志 Brutalist 风格),在中英文混排上的优势也很明显。Noto Serif SC 加 Inter 的组合,在技术站里不常见,却天然带着一种”东方学术加现代技术”的双重性格。这恰好是”灵枢”这个品牌想传达的东西。继续在其他原型间摇摆的机会成本,远大于在一个方向上深化。
强调色:从蓝紫到暖金
ink 主题原来的强调色是蓝紫 #4F6BF6。安全,但没有辨识度——Stripe 用紫色,Vercel 用黑白,Linear 用蓝紫,再来一个蓝紫就泯然众人了。
在 DIRECTIONS.md 的 8 个方向探索中,有个有趣的发现:天文金(#C9A84C)这个色调在 Direction 1(Observatory)和 Direction 7(Silk Road)里反复出现。同一种直觉在不同的探索路径中独立涌现,通常说明它触及了某种真实的偏好。
从技术角度看,暖金和 ink 的暖灰基底属于同色温体系,搭配起来比冷色调的蓝紫更和谐。在暗色模式下,金色在深棕底上的可读性也优于蓝紫。从品牌角度看,“灵枢”的意象偏东方、偏精密,金色比蓝紫更贴切。
具体的色值还需要在浏览器里实际测试。初步方向是亮色模式用沉稳琥珀 #B8943E,暗色模式提亮 15% 到 #D4AA50 以保证对比度,辅助强调用 #8B7335 做 hover 和 pressed 状态。
设计系统要补全的部分
ink 的基础已经相当扎实,但有几个缺口需要补。语义色只有 9 个 token(base、surface、elevated、line、copy、heading、muted、accent、accent-soft),缺少 success、warning、error 三个状态色——Research Archive 的报告状态标记(draft、reviewed、archived)直接需要这些颜色。交互态变量(hover、active、disabled)也没有系统化。
另一个决定是:三套主题只保留 ink,移除 edge 和 press 的 CSS。它们作为”品味测试”的历史价值已经实现了,继续维护三套主题只会拖慢后续所有的页面开发。
至于组件库——个人站不需要。现有 14 个 Astro 组件已经覆盖了核心场景。内容页面是纯 HTML 加 CSS,需要 JavaScript 的场景极少(主题切换、搜索框、可能的代码交互 demo)。与其维护一套完整组件库,不如把散落在 global.css 里的设计原语(间距比例、圆角规则、阴影层级、排版节奏、交互态过渡时间)提取出来,统一命名,做一份轻量的设计参考。
Research Archive:从”能看”到”能找”
回到 Research Archive 的升级。
选 Content Collection 还是保持自定义数据层
摆在面前的有三条路。方案 A 是写一个转换脚本把报告复制到 src/content/research/ 并注入 frontmatter,然后用 Astro 的 glob() loader 加载——和现有 6 个 Collection 完全一致的模式。方案 B 是写一个自定义 loader 直接从 docs/research/ 源目录读取,在 load() 函数中解析 blockquote 元数据,好处是不需要维护两份文件。方案 C 是保持现有的 lib/research.ts,只做增量改进。
Maple 最终选了方案 A,主要考虑的是一致性。Facade 已经有 6 个 Collection 全部用 glob loader,引入一个自定义 loader 就多了一种架构概念需要维护。而且 Astro 的 Content Loader API 仍在演进中,文档标注了多处变更,把核心功能押在一个还在变的 API 上不太稳妥。转换脚本是一次性工作,后续可以通过 campaign runner 的 post-hook 自动化。
元数据从哪来
160 份报告的元数据分散在好几个地方。Markdown 文件的 # H1 标题是 title 的来源,所有报告都有,很可靠。Campaign 报告的 blockquote 头部有 > Campaign:、> Topic:、> 写作时间: 这些字段,格式一致,可以用正则匹配。INDEX.md 提供了 102 份 autonomous 报告的 A-Q section 分组和 high/medium/archive 三级价值评估。state.json 存着所有 campaign 的模型名、运行时长和摘要。
转换脚本要走两条路径:campaign 报告从 blockquote 加 state.json 拼出完整元数据,autonomous 报告从 INDEX.md 查表加正则拼出基础元数据(没有 campaign 和 model 信息)。有一个已知风险:102 份 autonomous 报告只抽样了少数文件确认格式一致性,可能有偏差导致元数据提取失败。脚本需要输出详细的跳过和失败日志。
最终的 Zod schema 是这样的:在共享的 meta 基础上扩展 campaign、section(17 个枚举值,从 architecture 到 misc)、status(draft/reviewed/archived)、value(high/medium/archive)、linearIssues、model、readingTime、tldr 等字段。和现有 6 个 Collection 的 schema 风格完全一致。
搜索和筛选分阶段做
搜索方案比了三家:Pagefind、Fuse.js、简单 tag filter。
Pagefind 是构建时生成分块索引,浏览器按需加载,160 篇报告预计索引大小 50-100KB。它的杀手特性是 faceted filter——可以按 section、value、campaign 做组合筛选,还能返回每个 filter 值的匹配数量。Astro 生态有官方集成 astro-pagefind,Starlight 默认就用它。
Fuse.js 是运行时全量加载到内存做模糊匹配,160 篇报告的可搜索文本大约 200-500KB。在静态站场景下,Pagefind 全面优于 Fuse.js。
但第一版不上 Pagefind,而是用 20 行 JavaScript 做一个简单的 tag filter。给每个报告卡片加上 data-section 和 data-value 属性,用 pill 按钮 toggle display:none,支持 section 和 value 两个维度的组合筛选。160 篇报告的 DOM 操作在现代浏览器上完全无感。全文搜索是第二版的事——报告正文平均 1500-2300 行,tag filter 找不到正文中的关键词时再上 Pagefind。
UX 参考:从 Gwern 到 Anthropic
研究 UX 方案时 Maple 看了不少标杆。收获最大的是 Gwern。
Gwern.net 有一个叫”信息冰山”的设计理念:页面表面只露标题和摘要,往下通过折叠段、弹窗预览、反向链接提供任意深度的探索。读者可以在标题、摘要、段落标题、旁注、正文、折叠区、交叉引用之间自由切换深度。这解决了”文章太长不想读”和”文章太短信息不够”之间的矛盾。对 Research Archive 那些动辄两千行的报告来说,这个思路特别合适。
不过 Gwern 的很多实现(弹窗预览、段落级反向链接、宽屏旁注系统)成本太高,第一版不该碰。从 Gwern 里提取出来、可以立即用的设计原则有三条:视觉差异应该是语义差异(tag pill 的颜色应反映 section 含义,不做纯装饰)、信息密度优先(元数据紧凑展示在一行内,不占过多垂直空间)、支持渐进阅读(TL;DR 到目录到正文到附录)。
Gwern 还有一套认知置信度标记系统,每篇文章带 status(notes/in-progress/finished)和 confidence 评级。Research Archive 已有的 value: high/medium/archive 三级评估,可以直接映射为类似的视觉状态标记。Maggie Appleton 的 seedling/budding/evergreen 成熟度系统也是同一思路。
看了 Anthropic、DeepMind、OpenAI 三家的研究页之后,一个发现是:三家全部采用时间降序加类别筛选的列表模式,没有一家用网格卡片展示研究论文。 这验证了 Research Archive 用 full-width 行列表而非网格卡片的设计决策——报告标题长、元数据多,卡片放不下。
详情页的信息分层最终定为五层:标题加元数据条(section、value、date、reading time),TL;DR 一段话摘要,可折叠的目录,正文,以及底部的”同 Campaign 相关报告”导航。实现方式和 writing collection 的 SeriesNav 组件完全一致,用 getCollection 过滤同一个 campaign 的报告就行。
Report-to-Article:四步 Pipeline
160 份调研报告是给 AI 和内部流程看的,不是给人类读者看的。想在 Facade 上发布面向读者的文章,得有一条从研报到博客文章的转换链路。
核心架构是四步 pipeline。
第一步:remark 预处理。 这一步完全不需要 LLM,用 unified 的 remark 插件链做确定性的文本处理。去掉 MER-xxx 这类 Linear issue 引用,去掉内部文件路径链接(./ 和 ../ 开头的),去掉置信度标注((待确认)、(已验证)、confidence: 0.9),提取所有外部 URL 供 frontmatter 使用,统计表格和代码块数量为下一步的 prompt 提供结构化元数据。
remark 的好处是它在 MDAST(Markdown 抽象语法树)层面操作,不是做字符串替换。每个节点有 type、children、value 属性,可以精确地只去掉链接节点而保留链接文本,不会误伤正文内容。Astro 本身就原生集成了 remark/rehype 插件链,pipeline 输出的 MDX 文件放入 src/content/writing/ 后,构建时还会再走一次 remark/rehype 做最终的格式修正。
第二步:LLM 改写。 这一步交给 Claude,用 XML 结构化 prompt(task、style_guide、style_samples、source_material、output_format、rules)。关键是不要试图让 LLM 一次性完美模仿写作风格——接受 70-80% 的匹配度,剩余部分人工调整。风格转换的学术研究也印证了这一点:当前方法在捕捉深层风格特征(作者特有的论证逻辑、修辞偏好)方面仍然不足。
Prompt 需要收集 3-5 篇 Maple 满意的已发布文章作为 style samples,提供完整文章而非片段,让模型捕捉段落级的节奏。还需要一份 AI 腔黑名单——那些不想在文章中看到的表达。
不需要 LangChain 或 CrewAI 之类的重型框架。pipeline 是线性的,只有一个 LLM 调用步骤,不需要 DAG 调度或向量检索,Anthropic SDK 直接调用就够了。
第三步:AutoCorrect 格式化。 这是在 LLM 输出和人工审核之间新加的一步,处理机械性的排版问题。AutoCorrect 是一个 Rust 实现的中文排版工具,处理中英文间距、中文与数字间距、全角半角标点、专有名词大小写这些规则。处理一个 Markdown 文件大约 1.22 毫秒,有 GitHub Action 集成,配置粒度可以精确到每条规则的开关和严重级别。
选 AutoCorrect 而不是 pangu.js(只做中英文空格)或 zhlint(功能介于两者之间),主要因为它覆盖面最广、性能最好、CI 集成最完善。markdownlint 也会在这一步跑一遍,确保输出的 Markdown 格式规范。
第四步:人工审核和发布。 前三步把机械性工作全部自动化了,Maple 只需要关注”声音”层面的调整——读起来像不像自己写的、论证逻辑是否通顺、段落节奏是否舒服。审完之后,文件移到 src/content/writing/,Astro 构建,发布。
Andy Matuschak 和 Maggie Appleton 的启发
在 UX 层面,Andy Matuschak 的 evergreen notes 系统提供了两条可以直接融入改写规则的原则:声明式标题(标题是明确的陈述而非模糊的主题词)和原子性(每条笔记、每个段落封装一个完整想法)。这两条都会写进改写 prompt 的 rules 部分。
Maggie Appleton 总结的 Digital Garden 六原则中,有一条对内容发布特别重要的教训来自社区批评:没有日期的内容让读者无法判断时效性。这是 Digital Garden 模式被抱怨最多的问题。所以 writing schema 里 pubDate 是必填字段,这一点不会动。
Gwern 的语义缩放理念在 pipeline 的输出端也有体现:文章正文可以是压缩后的叙事,旁注可以链回原始研报中的详细数据,读者按需下钻。不过旁注系统(宽屏边注、窄屏浮动脚注)的实现成本不低,排在第二阶段。
接下来做什么
这三件事的推进顺序已经比较清晰了。
视觉方向的决策已经收敛:ink 定稿,强调色切到暖金色系,移除另外两套主题的 CSS,补全语义色和交互态变量。
Research Archive 的升级分三期:第一期写转换脚本、新增 Collection schema、重写列表页和详情页,加上 tag filter 和 campaign 内报告关联;第二期上 Pagefind 全文搜索;第三期做 section 维度的相关推荐和 Linear issue 交叉索引。
写作链路先做最小可行版本:remark 预处理加 Claude 改写加 AutoCorrect 格式化的串联脚本,拿三篇不同长度和类型的研报做端到端测试。如果效果稳定,后续包装成一个 CC skill。
有几个悬而未决的问题需要后续验证。Pagefind 在 Astro 6.1.2 上的兼容性没实测过。暖金强调色的具体色值需要在浏览器里看实际效果。Claude 写中文的自然度和专用中文模型(Kimi、DeepSeek)相比到底差多少,需要 A/B 测试才能判断。102 份 autonomous 报告的格式一致性只抽样了少数文件,转换脚本跑全量时可能遇到格式偏差。
这些不确定性都不阻塞当前的推进。Maple 的节奏一向是:先做,遇到问题再调。