record-browser-gif
每个改动 GUI 行为的 PR 都必须附一个演示 GIF——从该 PR 真实的服务端与模型流程里录,再发布到专用 assets 分支。
这是 .agents/skills/record-browser-gif/SKILL.md 的中文精读。它规定的规则相当激进:每个改动了产品可见 GUI 行为的 PR,都必须附一个演示 GIF——而且从该 PR 真实的服务端与模型流程里录。
什么时候触发
被要求制作、录制或生成一个演示浏览器工作流的 GIF 时使用;以及对每个改动产品用户可见 GUI 行为的 PR——必须用它录一个 GIF,通过 assets 分支流程嵌进 PR 正文。
每个 GUI PR 都要带 GIF
用从该 PR 分支树构建的真实服务器、真实 API key、真实模型轮次。绝不用 fixture 查询、mock 传输、合成事件注入或测试专用 hook 替代——除非用户明确要的是 fixture 录制。嵌入图旁边要写明:演示的精确 commit SHA、服务它的树与来源、任何模式标志或浏览器状态例外、是否跑了真实模型轮次——让 reviewer 确切知道这段录像证明了什么。
「为什么一个 storyboard 只能来自一次隔离运行」「为什么发布时要重新校验产物和被演示的 PR head」的理由,由「browser-gif-evidence-chain」决策记录持有。
录制与发布分开
- 录制只产出帧图片和一个本地
.gif文件,从不改动远端状态; - 发布——把 GIF 推上 assets 分支并嵌进 PR 正文——是单独的最后一步,仅当任务包含「附到 PR」才执行;它从不碰 PR 自己的分支;
- 保住被要求的录制条件。真实服务器/真实 API 的演示不许用 fixture、mock、合成注入或测试 hook。凭证或服务器不可用时,汇报这个限制,而不是拿 fixture 顶替;
- 绝不读取或暴露凭证值。走应用的正常配置路径,用无害的演示 prompt。
搭好应用
给某个 PR 的 GIF 演示的是那个 PR 的树,所以按 PR 搭:
- 要求干净 worktree,用
git rev-parse HEAD记下精确 commit,然后构建这棵树——这里是pnpm run build && pnpm run build:web。对着另一个 commit 的构建录 GIF 等于错标证据。 - 每个端口从那棵树起一个服务器,用全新的 scratch
DSH_HOME、DSH_AGENTS_HOME、工作区和 session 状态。浏览器也给全新的隔离 context 或 profile;浏览器工作流做不到就先清掉该 origin 的 cookie 和站点存储。API key 通过应用的正常路径从根.env注入;绝不回显 key。 - 一个 storyboard = 一次证据运行:每个发布的帧都来自同一台服务器、同一组状态根、工作区、session 和模型支撑的流程。捕获自动化失败就丢弃它的帧、从全新根重跑;绝不拼接来自不同次运行的帧。
- 切换 PR 时按 PID 或精确命令行匹配停掉旧服务器。宽泛的
pkill -f模式可能匹配并杀掉启动它的 shell——包括你自己的。
录制流程(节选要点)
- 调用可用的浏览器控制 skill 并遵守其设置、交互、清理指令。只有在被要求或必需时才用用户现有的 Chrome 状态——并在出处里声明这个例外。浏览器控制不可用时,用仓库声明的 Playwright 依赖跑隔离的无头浏览器;出处里声明这次回退。
- 录制前确认:精确 origin、应用是构建产物还是 dev 模式、传输方式、有没有 fixture/mock 模式。只录观察到的 setup 支撑得起的断言。
- 生产默认会打开无头自动化驱动不了的原生系统界面时,通过应用正常配置选一个官方的可浏览器操作的生产后端,并在出处里声明这个覆盖。
- 挑 3 到 6 个能讲一个故事的的状态:已输入、运行中、已结算、详情。优先语义状态变化,而非连续截屏;对观众没帮助的加载抖动不录。
- 所有帧保持同一视口与裁剪,按字典序命名:
00-initial.png、01-typed.png…… - 帧存在仓库 gitignore 掉的
.playwright-mcp/下——先mkdir -p .playwright-mcp/gif-frames-<label>,写进不存在的目录会在捕获时 ENOENT。 - 每次截图前等一个具体 UI 条件:唯一标签、可用控件、文档标题变化、响应完成。定位器必须恰好解析到一个元素;Playwright 的 accessible-name 定位器在要求相等时用
exact: true。不要用固定延时当作「应用已到达该状态」的证据。 - 完成判定要匹配精确文本元素(如裁剪后文本等于预期回复的元素),绝不用
body.textContent.includes(...)这类子串检查——用户自己 prompt 的回显也能满足它。 - 断言涉及工具调用、拒绝或恢复时,要有一帧详情/轨迹显示工具身份、状态或稳定错误码及下游结果——只有聊天结果证明不了工具路径为什么那样走。
- 捕获瞬态(spinner、运行中行):驱动一个慢的前台操作(如
sleep 15),并在同一次浏览器脚本调用里轮询具体 DOM 标记(data-*属性)加截图——跨工具调用轮询的状态会丢,因为 turn 在调用之间就结算了。 - 设计 prompt 让你要的状态真的发生:模型会把慢命令丢后台时,指示它前台等待;给它一个结算哨兵,比如「回复单词 done」,锚定完成判定。
- 不捕获任何秘密、个人数据、无关标签页、瞬时通知。没必要长的真实 API 运行,在演示状态可见后就停。
编码 GIF
需要 python3、ffmpeg、ffprobe;缺媒体二进制就报依赖,不擅自装。注意 GIF_SKILL_DIR 必须单独一行 export——内联 GIF_SKILL_DIR=... python3 "$GIF_SKILL_DIR/..." 会失败,因为参数在赋值生效前展开:
$ export GIF_SKILL_DIR=/absolute/path/to/this/skill $ python3 "$GIF_SKILL_DIR/scripts/encode_gif.py" \ /absolute/path/to/frames \ /absolute/path/to/demo.gif \ --durations 1.5,1.5,1.5,3.5 \ --fps 10 --max-width 1200 --colors 128
单一时长应用于所有帧;否则给每帧一个逗号分隔的正时长,最后的结算状态停最久。编码器会拒绝:少于两帧、尺寸或时长不匹配、非法上限、意外覆盖、超出 --max-bytes。产物太大先降 --max-width,再降 --colors 或 --fps;文字要可读,末帧要停得够久。
验证产物
- 读编码器的 JSON 摘要,确认输出路径、源帧与编码帧数、尺寸、时长、字节数。
- 亲眼看编码后的 GIF 本身,不只看源帧:过渡可读、末态停得够久、无敏感内容。查看器只渲染首帧时,用
ffmpeg从编码后 GIF 解码代表性帧来看——编码前的截图证明不了编码后的顺序、调色板或末帧停留。 git status --short确认帧和产物只落在被忽略的路径。- 返回 GIF 绝对路径,客户端支持就渲染出来,并声明录制用的是真实 API、fixture 还是别的传输。任务不含「附到 PR」就到此为止。
发布到 assets 分支
仅当任务包含「把 GIF 附到 PR」时执行。
提交进任何会合入长寿分支的二进制媒体,会让每个未来的 clone 都为它膨胀历史。GIF 住在专用的 orphan assets 分支上——没有父 commit、只有媒体;一个分支服务整个 PR 系列(命名 <series>-assets;用 git ls-remote --heads origin '*assets*' 列已有的)。
两种工作流推送前都要验证:assets 分支只含媒体、待推 GIF 的校验和与验证过的本地产物一致。已有 assets 分支就在浅克隆的 scratch 副本里操作(发布碰不到你的工作树):
$ git clone --branch <assets-branch> --single-branch --depth 1 <repo-url> /tmp/assets-checkout $ cp /absolute/path/to/demo.gif /tmp/assets-checkout/<name>.gif $ cd /tmp/assets-checkout $ git add <name>.gif $ git commit -m "assets: <what it shows> gif (#<pr>)" $ git push origin <assets-branch>
新系列:浅克隆 scratch 副本后 git switch --orphan <assets-branch>,然后同样 add / commit / push。
推送后用带认证的 GitHub API 或原始请求确认远端路径、字节数、校验和、200、image/gif content-type。匿名 404 不能证明私有仓库资产不存在——带认证验证。这证明的是「仓库成员的 review 路径」,不是公开可用性。
编辑 PR 正文前一刻重新读 live head,与 GIF 旁记录的 commit 比对:动过就停下重录。编辑后再读一次 live head,要求它仍在那个 commit。另外把正文过 GitHub 的 Markdown API 渲染,确认预期的 <img> 在。
用 raw blob URL 嵌入正文;?raw=true 后缀必需——裸 blob URL 渲染的是 GitHub 文件页而不是图:

绝不删除或重写 assets 分支,绝不 force-push——已合并 PR 的正文永远引用它的 URL。只追加新 commit。