Anarlog 产品更新邮件实战指南:基于 changelog 的事实源写作与 Loops 编辑器自动化
2026/9/16 13:03:26 网站建设 项目流程

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 合并、版本正式发布之后才执行。它依赖同一目录体系下的另外两个技能按顺序完成前置工作:

  1. new-changelog 技能:生成稳定版或 Nightly 的桌面 changelog;
  2. 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,提取datesummary两个字段;
  • fixImageUrls把正文中/api/assets/...形式的图片路径重写为对象存储的公开地址。

2.2 增量同步原则

技能明确提示:changelog 在首次发布之后经常被再次编辑。当被告知 changelog 发生变化时,正确的做法是:

  1. 重新获取最新版本;
  2. 与当前邮件正文做 diff;
  3. 只应用变化的部分(delta)
  4. 绝不重新执行一遍整篇重写。

这是为了保持邮件与发布说明严格一致,同时避免引入无谓的文案漂移。

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 assistantseight connect directlyabout a second
  • 能用事实表达的地方就删掉形容词;
  • 使用简短、主动语态的 H3 标题,例如Automations do the work nowTranscripts you can fixSync 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 平台中,技能规定活动的搭建方式是复用而非新建:

  1. 从 Loops Home 页面复制最近一期 "Anarlog update (...)" campaign
  2. 这样发件人(sender)、回复地址(reply-to)、小节标题、Download 按钮和页脚都会被继承;
  3. 将 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直接删除被选中的文本先按EndHome

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过滤列表使其进入视野;
  • EnterArrowDown+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+% Completedocument.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)规则

技能要求编辑完成后按以下方式汇报:

  1. 平实的语言重述改了什么;
  2. 引用新的文案内容;
  3. 附上已编辑小节的截图;
  4. 主动披露任何自我造成的文档损坏及其修复过程,而不是隐瞒。

十二、沉淀:这套流程对自动化 Agent 的可复用要点

product-update-newsletter技能本身就是一个可被 Agent 逐步执行的"操作手册",其可复用要点可以归纳为三层:

  1. 事实层:一切文案以已发布 changelog(packages/changelog/content/<version>.md)为源,增量同步、不做全量重写;稳定版与 Nightly 严格分渠道,Nightly 公告需先验证安装包、更新源与下载页再行宣传。
  2. 写作层:产品邮件风格契约(开门见山、事实与数字、主动态短句、90~140 字符预览文本)+ 固定的H3 → SP → IMG → SP → P块结构,配合 Playwright 脚本做结构校验。
  3. 操作层:针对 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询