Anarlog 产品更新邮件实战指南:基于 changelog 的事实源写作与 Loops 编辑器自动化
【免费下载链接】anarlogOpen source Granola AI Alternative项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog
本文完整解读 Anarlog 仓库中用于「桌面版发布 → 用户更新邮件」的
product-update-newsletter技能:它以已发布的桌面版 changelog 为唯一事实源,规定了一整套从文案风格、活动搭建、正文结构契约,到 Lexical 编辑器程序化编辑、配图生成与发送前审计的完整流程。读完本文,你将掌握如何为 Anarlog 每个稳定版桌面发布撰写、修订并预检一封面向真实用户的产品更新邮件,并能直接复刻仓库中的 Playwright + Lexical 自动化编辑方案。
一、技能定位与文档体系
Anarlog 仓库内维护了一套面向 Agent 的「技能(Skill)」文档体系,其中product-update-newsletter负责在桌面版发布时,起草、修订或审计一封基于 changelog 的产品更新邮件,并发布到名为 Loops 的营销平台(app.loops.so,仓库内称 campaign)。
该技能在仓库中有两个入口:
- 兼容入口:.claude/skills/product-update-newsletter/SKILL.md —— 这是 Claude 专用技能发现所需的兼容条目,正文只有几行,明确声明"通用权威指令"位于下方路径,并要求不要在兼容条目里维护第二份检查清单;
- 权威入口:.agents/skills/product-update-newsletter/SKILL.md —— 全部规范指令的真正所在,包括事实源、文案风格契约、活动搭建、正文结构契约、Lexical 编辑器操作、配图、发送前审计与报告规则。
技能的使用时机在文档开头就写得很清楚:必须在 changelog 合并、版本正式发布之后才执行。它依赖同一目录体系下的另外两个技能按顺序完成前置工作:
- new-changelog 技能:生成稳定版或 Nightly 的桌面 changelog;
- release-new-version 技能:发布版本。
也就是说,一封产品更新邮件的素材链是:new-changelog(产出 changelog)→release-new-version(发布版本)→product-update-newsletter(撰写邮件)。技能强调:已发布的 changelog 是邮件的文案来源,而不是原始 commit 日志。
二、事实源:以已发布 changelog 为唯一依据
2.1 事实源的定义
技能规定,邮件的 Source of Truth 是anarlog.so/changelog/<version>渲染出的已发布 changelog 页面,该页面在仓库中由 packages/changelog/content 目录下的<版本号>.md文件渲染而成。
网站 changelog 的解析流水线位于 packages/changelog/src/process.ts:
parseFrontmatter解析每个 changelog 文件头部的---frontmatter,提取date与summary两个字段;fixImageUrls把正文中/api/assets/...形式的图片路径重写为对象存储的公开地址。
2.2 增量同步原则
技能明确提示:changelog 在首次发布之后经常被再次编辑。当被告知 changelog 发生变化时,正确的做法是:
- 重新获取最新版本;
- 与当前邮件正文做 diff;
- 只应用变化的部分(delta);
- 绝不重新执行一遍整篇重写。
这是为了保持邮件与发布说明严格一致,同时避免引入无谓的文案漂移。
2.3 结构映射规则
来自 changelog 的条目要映射到邮件已有的结构,而不是简单追加一段"倾倒式"罗列:
| changelog 条目类型 | 邮件中的落点 |
|---|---|
| 功能级(Feature-level)条目 | 成为或扩展一个 H3 小节 |
| 次要 UX 改进 | 变为 "Small things that add up" 下的要点 |
| 可靠性与隐私类条目 | 追加到要点之后的 fixes 段落 |
同时要求诚实标注未完全上线的功能:如果某个执行链路尚未接通,邮件里必须如实说明,不能把半成品写成已发布功能。
2.4 真实 changelog 示例
以 1.4.8 版 changelog 为例,其 frontmatter 与正文结构如下:
--- date: "2026-08-10" summary: "Anarlog 1.4.8 connects eight meeting assistants for automatic imports, makes cross-device sync near-instant, syncs appearance preferences across devices, and improves recording recovery, window placement, transcription errors, and billing." --- ## Sync - Upload local changes within about a second of settling instead of waiting for the next 30-second interval, and pull promptly when you switch back to the app - Wake your other devices as soon as a sync lands ... - Sync your theme, app icon, and week start choices across devices, end-to-end encrypted like the rest of your notes ## Importing - Connect Granola, Circleback, Fireflies.ai, Krisp, Read AI, Fellow, Tactiq, or Jiminny directly to bring your existing meeting history into Anarlog - Keep new meetings importing automatically while Anarlog is running, with controls to sync now or disconnect and export files available as a fallback这份 changelog 展示了邮件文案的素材形态:summary是一句面向用户的概述(预览用),正文按主题分组(Sync / Importing / Recording and transcription / Window and settings),每条要点都聚焦"用户能感知的变化"。邮件写作就是在这一素材上做"面向邮件读者"的重组与精简。
三、稳定版与 Nightly:邮件的发布节奏契约
3.1 只跟随稳定版发布
技能第一条节奏规则:产品更新邮件只跟随稳定版(stable)发布,不针对每个 Nightly 发布邮件。这与 new-changelog 技能 中的"Channel contract"完全一致——网站 changelog 目录packages/changelog/content/只接受<major>.<minor>.<patch>.md格式的文件名,Nightly 的更新说明维护在 packages/changelog/nightly.md,构建时被嵌入应用并快照到 GitHub prerelease,绝不出现在网站 changelog 中,也不会为每个 Nightly 发邮件。
3.2 Nightly 复活公告
Nightly 并非永远不发——技能要求在下一个稳定版邮件中附带一个可选的 Nightly 公告(opt-in announcement),其文案草稿已经放在 nightly-announcement.md 中:
Anarlog Nightly is back
Try upcoming improvements before they reach stable and help us catch bugs earlier. Nightly installs as a separate app, updates more frequently, and may be less reliable. Your current Anarlog installation stays on stable unless you choose to install Nightly.
草案还写明 Nightly 与稳定版的关系:Nightly 以独立应用形式安装、更新更频繁、可靠性可能更低;Nightly 与稳定版共用本地笔记数据,但登录与设置互相独立;两个应用不能同时运行,反馈时需要附上 Nightly 版本号和操作系统。
3.3 发布前必须先验证
技能特别强调:在把 Nightly 描述为"可用"之前,必须先验证:
- Nightly 的安装包(installers)已发布;
- 更新源(update feed)可用;
- Nightly 下载页已上线。
这一点与 release-new-version 技能 互相印证:Nightly 构建由desktop_nightly.yaml每天 15:00 UTC 触发,版本号形如<共享版本>-nightly.<n>(如1.4.24-nightly.1),Nightly 与稳定版是各自独立签名的包,绝不允许把 Nightly 二进制说成与稳定版逐字节相同。技能还明确:Nightly 公告文件本身并不授权发送或排期该 campaign——发送前仍需走完整流程。
3.4 邮件来源的版本校验
在进入写作之前,需要先确认真实版本与 changelog 对应关系,可参考 release-new-version 技能 的校验方式:
VERSION=<version> [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] node scripts/release-version.mjs --check "$VERSION" test -f "packages/changelog/content/$VERSION.md"稳定版发布从不"推断"版本号:必须精确匹配release-version.json,且对应版本必须存在 changelog 文件。
四、文案风格契约(Copy Style Contract)
技能把邮件文案的写作标准固化为一份"契约",核心观点是:写一封产品邮件,而不是一篇论文。要求紧凑、具体、易于扫读(crisp, concrete, easy to scan)。
4.1 开头:直入主题
邮件开头必须开门见山地说明发了什么、什么时候发的。技能给出正反例:
- 推荐写法:
We shipped four releases since last week. Anarlog 1.4.8 is here. - 避免写法:
If you've been putting off updating, this is the one to take这类场景铺垫式开头。
4.2 升级理由:用功能绑定"损失"
如果鼓励用户升级,要用具名功能来绑定错失的收益,而不是空洞的 hype 或内疚感:
If you aren't updating, you're missing out on Automations that run after every meeting, editable transcripts, meeting imports, and near-instant sync.
禁止使用模糊的夸大、内疚或指责语气。
4.3 用事实和数据说话
- 用结果与具体数字领起:
30 assistants、eight connect directly、about a second; - 能用事实表达的地方就删掉形容词;
- 使用简短、主动语态的 H3 标题,例如
Automations do the work now、Transcripts you can fix、Sync you stop thinking about。
4.4 段落与句法
- 大多数段落控制在 1~3 句;
- 拆开过长的链条,去掉重复的铺垫,不要在引言和 fixes 段落里重复同一套框架;
- 使用缩略语和直接动词;
Last week ... This week ...是连接相邻两期更新的有效节奏; - 避免故作可爱的过渡(如
Now they earn it)和含糊短语(如Accuracy got quieter improvements)。
4.5 主题行与预览文本
主题行要紧凑、名词主导,例如Anarlog 1.4.8: automations, meeting imports, instant sync。预览文本(preview text)要前置加载具体结果,长度保持在约 90~140 个字符——这是主流邮件客户端截断预览的典型区间。
4.6 自我批评语言的边界
自我批评(道歉)语气只在 Anarlog 或公司确实有过错时使用,绝不能为了显得亲切而强行道歉。
4.7 终稿朗读检查
最终把成稿朗读一遍,凡是删掉后不影响事实、指令或观点的内容一律删掉。技能同时允许 changelog 列表式写法——不要为了硬凑散文而强行改写本来就适合列表的内容。
五、Loops 活动搭建(Campaign Setup)
在 Loops 平台中,技能规定活动的搭建方式是复用而非新建:
- 从 Loops Home 页面复制最近一期 "Anarlog update (...)" campaign;
- 这样发件人(sender)、回复地址(reply-to)、小节标题、Download 按钮和页脚都会被继承;
- 将 campaign 重命名为新日期。
技能特别指出一个 Loops 行为细节:campaign 的指标页(metrics pages)只显示发送/打开/点击统计,因此内容编辑必须在 compose 视图(撰写视图)中进行。
六、正文结构契约(Body Structure Contract)
6.1 固定的块序列
邮件的每个小节必须严格遵循同一套块序列,任何编辑都必须保留它:
H3 heading P (empty spacer) DIV.editor-image P (empty spacer) P (body text)即:H3 标题 → 空段占位 → 编辑图片块 → 空段占位 → 正文段落。
6.2 结构自动校验
在完成编辑后,技能给出了一段可在 Playwright 中执行的校验脚本,逐块检查每个 H3 之后是否满足SP,IMG,SP模式:
await page.evaluate(() => { const blocks = [...document.querySelector(".ContentEditable__root").children]; return blocks .filter((b) => b.tagName === "H3") .map((h) => { const i = blocks.indexOf(h); return ( h.innerText + " -> " + blocks .slice(i + 1, i + 4) .map((x) => x.className.includes("editor-image") ? "IMG" : x.innerText.trim() === "" ? "SP" : "TXT", ) .join(",") ); }); });这段脚本直接读取ContentEditable__root根节点的子块序列,对每个 H3 后面的三个块依次判断是图片块(editor-image)、空段(SP)还是文本(TXT),用于在发送前确认结构契约未被破坏。
七、Lexical 正文编辑:程序化操作与失败模式
7.1 编辑模型认知
邮件正文是一个div.ContentEditable__root,页面上存在多个[contenteditable="true"]元素(Sender、From、Reply、Subject、Preview、正文)。技能警告:必须匹配具有辨识度的既有文本,绝不能依赖缓存的索引或 ref;任何多块编辑前都要重新扫描完整的块映射并重新解析目标,因为用户可能正在并发编辑,一个先前正确的索引会漂移并破坏标题、要点或问候语。
正文编辑器的语义:###创建 H3,-创建列表项。
7.2 可靠的中段替换:Range 选择后输入
技能给出的推荐做法是"Range 选择 + 键入":
async function selectTextInBody(searchStr) { return await page.evaluate((str) => { const root = document.querySelector(".ContentEditable__root"); const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { const idx = node.textContent.indexOf(str); if (idx !== -1) { root.focus(); const range = document.createRange(); range.setStart(node, idx); range.setEnd(node, idx + str.length); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); node.parentElement.scrollIntoView({ block: "center" }); return true; } } return false; }, searchStr); }该函数通过TreeWalker遍历正文的所有文本节点,找到目标字符串后用Range精确框选,并滚动到可视区域。对于版本号这种会出现多次的替换,需要对每个命中位置重复执行。
7.3 四个实测失败模式(Failure Modes)
技能特别强调,以下四种失败模式都会静默损坏文档,且全部是实践中真实观测到的:
| 失败模式 | 现象 | 对策 |
|---|---|---|
| 首块 Range 选择 | 键入字符被反转("Hi," 变成 ",iH"),Lexical 每次按键都把 caret 重置到偏移 0 | 使用原生鼠标三击,然后键入 |
Meta+ArrowUp不可靠 | 无法可靠到达文档开头,内容被追加到当前块末尾 | 改为三击目标块 |
同次evaluate()内读坐标 | scrollIntoView之后坐标是陈旧的,点击落在错误的块上,按键损坏无关小节 | 滚动后sleep(600),在第二次evaluate()中重新读取 rect |
| 有激活 Range 时按 Enter | 直接删除被选中的文本 | 先按End或Home |
7.4 键入前的 caret 校验
在键入前必须确认 caret 确实落在目标块中:
const ok = await page.evaluate(() => { const sel = window.getSelection(); const blocks = [...document.querySelector(".ContentEditable__root").children]; const target = blocks[TARGET_INDEX]; const anchorBlock = sel.anchorNode ? (sel.anchorNode.nodeType === 1 ? sel.anchorNode : sel.anchorNode.parentElement ).closest(".ContentEditable__root > *") : null; return anchorBlock === target; });如果返回false,应点击目标块文本的水平中点而非左边缘。
7.5 结构修复后的扫描
完成任何结构性修复后,必须扫描所有块,清理:游离的空段落、重复的问候语、残留的/或/image文本。
八、插入图片:slash 菜单与文件选择器
8.1 完整操作流
图片插入通过正文的 slash 菜单触发原生文件选择器。技能要求先武装监听器,再打开菜单:
const fcPromise = page .waitForEvent("filechooser", { timeout: 15000 }) .catch(() => null); await page.keyboard.type("/", { delay: 200 }); await sleep(700); await page.keyboard.type("image", { delay: 120 }); await sleep(700); const btn = await page.evaluate(() => { const menu = [...document.querySelectorAll("div.fixed")].find((el) => el.className.toString().includes("z-[100]"), ); const img = menu && [...menu.querySelectorAll("button")].find( (b) => b.textContent.trim() === "Image", ); const r = img.getBoundingClientRect(); return { cx: r.x + r.width / 2, cy: r.y + r.height / 2 }; }); await page.mouse.click(btn.cx, btn.cy); const fc = await fcPromise; await fc.setFiles("/abs/path/to/image.png");8.2 关键操作要点
- caret 必须位于空段落中,
/的键入延迟必须>= 150,否则会键入一个字面/; - 菜单是一个可滚动的
div.fixed.z-[100];未过滤时,MEDIA 分组下的 Image 项位于可视滚动区之外,点击其报告的坐标会点到页面背景并静默关闭菜单——因此先键入image过滤列表使其进入视野; Enter与ArrowDown+Enter都不能激活该项,只有鼠标点击有效;- 轮询直到
img.src包含images.vialoops.com以确认上传完成; - 图片插入会消耗掉那个空段落,所以插入后要重新补一个尾部空段占位。
8.3 替换与恢复
替换图片:点击选中图片块 → 按 Backspace → 重新执行上述流程。由于编辑过程中图片可能被误删,技能要求在编辑前先捕获所有images.vialoops.comURL,这样被删的图片可以重新下载并恢复。
九、章节配图(Section Art):Midjourney 风格生成
9.1 家装风格提示词
邮件小节的配图使用 Midjourney 按既有"house style"生成:
dithered. [short concept]. bright white sunshine, cheerful, high key. --ar 4:3 --profile aofpoq2技能给出的风格约束:
- 目标是明亮、高调、通透、浅色背景的效果,拒绝昏暗或 dystopian 输出;
- 优先场景构图(阳光房间里的物体),不要扁平网格或 UI 线框图——网格与现有配图集合不匹配;
- Personalize 的 "P" 开关可能激活失败(
data-active="false"),把--profile aofpoq2直接追加到提示词文本即可生效并产生 profile chip; - 轮询完成状态:观察
\d+% Complete从document.body.innerText中消失。
9.2 全分辨率下载
cdn.midjourney.com对 Node 侧的fetch()会返回 Cloudflare 质询,因此必须在已认证的浏览器标签页内下载全分辨率图,jobId来自网格缩略图 URL:
const b64 = await mjPage.evaluate(async (u) => { const r = await fetch(u); const blob = await r.blob(); return await new Promise((res) => { const fr = new FileReader(); fr.onload = () => res(fr.result.split(",")[1]); fr.readAsDataURL(blob); }); }, `https://cdn.midjourney.com/${jobId}/0_${variant}.png`);十、发送前审计(Pre-Send Audit)
发送前必须逐项运行审计,并对每一项报告 pass 或 fail。
10.1 自动化检查项
- 版本字符串在主题行与正文中保持一致,且正文中不残留任何旧版本号;
- changelog 中的每个条目都在邮件文案中有对应呈现,包括来自 Fastrepl 组织之外贡献者的致谢;
- 所有 H3 小节都满足
SP,IMG,SP模式; - 所有图片都满足
complete && naturalWidth > 0(即加载完整、非零宽度); - 无残留瑕疵:
/image、孤立的/、双空格、重复的问候语; - 签名块完好。
10.2 人工判断项
- Sender / From / Reply 字段正确。特别要标记任何 reply-to 变更——因为正文承诺"个人回复",任何招募测试者的呼吁都路由到同一个地址;
- 预览文本长度:客户端在约 90~140 字符处截断,重要内容必须前置;
- Download 按钮 URL:它存在于编辑器状态而非 DOM 中,需要点击按钮块后在右侧边栏的 Link 文本框里读取;
- 除非同时检查了 Audience、Schedule 和 Goals,否则必须明确声明"仅审计了 Compose 步骤"。
10.3 与 changelog 的质量要求呼应
发送前审计中"changelog 每一条目都要有对应呈现"与 packages/changelog/content/AGENTS.md 的规则形成闭环:changelog 本身必须"对应用用户值得一读"、排除内部重构与 CI 噪音;来自组织外 PR 的贡献者致谢要放在条目末尾(author_association不为MEMBER/OWNER/COLLABORATOR且非 bot 时致谢),且致谢不进入summary。邮件写作直接继承这一事实链,保证用户在邮件里读到的与网站上看到的一致。
十一、报告(Reporting)规则
技能要求编辑完成后按以下方式汇报:
- 用平实的语言重述改了什么;
- 引用新的文案内容;
- 附上已编辑小节的截图;
- 主动披露任何自我造成的文档损坏及其修复过程,而不是隐瞒。
十二、沉淀:这套流程对自动化 Agent 的可复用要点
product-update-newsletter技能本身就是一个可被 Agent 逐步执行的"操作手册",其可复用要点可以归纳为三层:
- 事实层:一切文案以已发布 changelog(
packages/changelog/content/<version>.md)为源,增量同步、不做全量重写;稳定版与 Nightly 严格分渠道,Nightly 公告需先验证安装包、更新源与下载页再行宣传。 - 写作层:产品邮件风格契约(开门见山、事实与数字、主动态短句、90~140 字符预览文本)+ 固定的
H3 → SP → IMG → SP → P块结构,配合 Playwright 脚本做结构校验。 - 操作层:针对 Lexical 编辑器总结了实测有效的 Range 替换、caret 校验、slash 菜单插图的自动化方案,并把四种静默损坏的失败模式与对策固化进流程,最后以自动化 + 人工的双层审计收尾。
这套方案的价值在于:它把"一封高质量的发布邮件"从依赖人的临场发挥,变成了有事实源、有风格契约、有结构校验、有失败预案、有发送前审计的可重复工程流程——这正是 Agent 能在 Loops 里稳定产出并维护产品更新邮件的原因所在。
相关仓库路径速查
- 技能权威文档:.agents/skills/product-update-newsletter/SKILL.md
- 技能兼容入口:.claude/skills/product-update-newsletter/SKILL.md
- Nightly 公告文案草稿:.agents/skills/product-update-newsletter/nightly-announcement.md
- changelog 生成技能:.agents/skills/new-changelog/SKILL.md
- 版本发布技能:.agents/skills/release-new-version/SKILL.md
- 网站 changelog 内容:packages/changelog/content
- changelog 写作规则与自定义标签:packages/changelog/content/AGENTS.md
- 真实 changelog 示例:packages/changelog/content/1.4.8.md
- changelog 渲染流水线:packages/changelog/src/process.ts
- Nightly 累积更新说明:packages/changelog/nightly.md
【免费下载链接】anarlogOpen source Granola AI Alternative项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考