☰
Hexclave 视觉化 PR 描述写作指南:基于 pr-body-template 的 Before/After 截图矩阵与 GitHub PR 正文编排
2026/10/9 7:17:13 网站建设 项目流程
  • 后端
  • 认证鉴权
  • 前端

【免费下载链接】hexclave

The user infrastructure platform. You choose the frontend, backend, and database. Hexclave handles everything else.

项目地址:https://gitcode.com/gh_mirrors/stack/hexclave
点击查看免费下载

导读

视觉证据是代码评审中最有说服力的沟通形式,而一张结构清晰、可复现的截图对比表,比一段文字描述更能让评审者快速理解「这次改动改了什么」。本文以 Hexclave 仓库中.agents/skills/pr-visual-writeup技能内置的 PR 正文模板为骨架,完整讲解视觉密集型 GitHub PR 描述的撰写方法:从 Summary、Scope 到旗舰页的 Light/Dark × Before/After 二维截图矩阵,再到长尾页面的紧凑表格、alt text 与配对规则。读完本文,你将掌握一套可直接落地的「截图 → 托管 → 排版 → 提交」流水线,并了解其背后的并行捕获、红框标注、滚动 GIF 与 gist 托管实现。

模板在整体工作流中的定位

该模板并非孤立文档,而是pr-visual-writeup技能流水线中「撰写与提交」阶段(Phase 5)的骨架。整个技能把一次视觉化 PR 写作用划分为六个阶段:

  1. Scope——确定 PR 号、仓库、改动的 UI 路由、开发服务器端口、登录方式、新增 UI 的选择器,以及基线分支;
  2. Capture——先对 head 分支做「after」并行捕获(页面 × 主题 × 视口),再切换到 base 分支做「before」捕获;
  3. Process——并行把滚动视频 WebM 转成可内联播放的 GIF;
  4. Upload——用 PAT 将全部素材推入一个公开 gist,取得 raw URL;
  5. Compose + set——按本模板编写 Markdown 正文,用gh pr edit --body-file写回 PR;
  6. Restore——恢复用户的原始分支与 stash,让工作区回到原状。

模板对应的references/目录下共有三个配套文档:pr-body-template.md(正文结构)、capture-patterns.md(截图捕获配方)、gist-upload.md(素材托管配方),另有三个可直接调用的脚本:detect_dev_server.sh、convert_clips.sh、upload_gist.sh。

模板开篇就声明了它的性质:「Markdown structure for a visual-heavy PR description. Adapt freely — these are patterns, not a rigid form.」——这是一套可自由改编的「模式」,而非必须逐字照抄的「表单」。

正文顶部模板:Summary 与截图矩阵

Summary 段落

正文第一块是 Summary,要求用1~2 段话说明这个 PR 做了什么、为什么做,而不是罗列 commit 列表。紧接着给出两条结构化信息:

**Base:** `<base>` → **Head:** `<head>` **Scope:** <N> files, ~+<M>k additions
  • Base → Head:用一条箭头交代分支流向(例如dev→feature/xxx),评审者一眼就能看出 diff 的方向;
  • Scope:文件数与新增代码量,用于快速评估 PR 体积,属于纯信息性指标,无需展开解释。

Screenshots 区块的完整骨架

模板给出了从头部到滚动行为节的完整 Markdown 骨架:

## Screenshots Captured from the local dev server (viewport: **<W>×<H>** standard, **<W2>×<H2>** widescreen). Assets hosted in this gist. > 🔴 Red outlines on the "after" shots mark the new or changed UI introduced by this PR. ### <Flagship page 1> — <short descriptor> | | Before | After | | --- | --- | --- | | Light | <page>-before-light | <page>-after-light | | Dark | <page>-before-dark | <page>-after-dark | Widescreen: | | Before | After | | --- | --- | --- | | Light | <page>-before-light-wide | <page>-after-light-wide | | Dark | <page>-before-dark-wide | <page>-after-dark-wide | ### <Flagship page 2> <...same before/after pattern...> ### Other migrated surfaces (after only) | Page | Light | Dark | | --- | --- | --- | | <name> | <page>-after-light | <page>-after-dark | | <name> | <page>-after-light | <page>-after-dark | ### <Optional: scroll behaviour / sticky header / interactions> | Page | Light | Dark | | --- | --- | --- | | <name> | <page>-scroll-light | <page>-scroll-dark |

拆开来看,这个骨架由四层信息构成:

  1. 来源与图例:声明截图来自本地开发服务器、标准视口与宽屏视口的尺寸,并标注素材托管位置;随后的引用块说明after 图上的红色轮廓标记本次 PR 新增/改动的 UI;
  2. 旗舰页(Flagship):每个旗舰页独占一个 H3 小节,内含标准视口下的 2×2 表格(行 = Light/Dark,列 = Before/After),以及单独的宽屏表格;
  3. 长尾页(Long-tail):以「Page | Light | Dark」紧凑表格汇总,仅放 after 图;
  4. 可选交互节:适合表格、长列表、吸顶 header 等需要展示滚动行为的页面,用 Light/Dark 两张 GIF 呈现。

在 Hexclave 的具体实践中,文件名与表格单元格一一对应:after 截图文件名形如<route>-after-light.png/<route>-after-dark[-wide].png,before 截图形如<route>-before-light[-wide].png(参见SKILL.md中 Phase 2 的文件名约定),这意味着只要命名规范,表格里的图片 URL 几乎可以机械地从截图目录生成,无需人工一一对应。

视觉之外:不可省略的三段常规内容

模板特别强调:视觉内容之后,必须补上常规 PR 应有的全部内容——What's new、Notes for reviewers、Test plan。原文档给出了非常明确的告诫:

Everything normal for a PR body:What's new,Notes for reviewers,Test plan. Don't skip these — the visuals sell the PR but reviewers still need a map of the code.

翻译成实操要求就是:截图负责「说服」,文字负责「指路」。一个 90% 是截图、10% 是文字的 PR 正文读起来像营销文案而不是工程沟通;评审者需要知道改了什么、哪些地方需要重点看、以及如何验证。

旗舰页与长尾页的选择标准

模板给出了一套非常实用的「分配资源」策略:

旗舰页(Flagship)待遇——独立小节、附带宽屏变体、可选滚动 GIF:

  • 本次 PR 中内容最丰富的页面;
  • 评审者最可能第一时间打开的页面;
  • 通常控制在3~5 个以内——超过这个数量正文就会变得嘈杂。

长尾页(Long-tail)待遇——在「Other surfaces」表格中占一行:

  • 同模式页面,截图里大部分是既有 dashboard 骨架(chrome);
  • 空状态或近乎空白的页面(seed 数据未能填充)。

这套取舍的核心是信息密度的控制:把最「有戏」的页面做成大图展示,把「没差别」的页面压缩进表格,让 PR 正文在可读性与完整度之间取得平衡。

Alt text 规则:文件名即描述

模板规定 alt text直接使用基础文件名(例如users-after-light):

  • 可搜索(greppable)、一致性高;
  • 图片加载失败时仍能显示有意义的文字;
  • before/after标记至关重要:如果 gist 被清空,评审者仍能凭 broken-image 的 alt 分辨表格中每个单元格对应的是改动前还是改动后。

这一规则与前述文件名约定形成闭环:捕获阶段的命名规范直接决定了 PR 正文阶段 alt text 的质量。

Before/After 配对规则

配对是整张截图矩阵的逻辑基石,模板给出了三条硬规则:

  1. 每个旗舰节的 after 图必须配对同一主题、同一视口的 before 图——保证 Light/Dark、标准/宽屏四个维度上「改动前后」严格可比;
  2. 没有 before 的情况(全新路由):使用单行「After only」表格,并在下方注明*New route — no base equivalent.*(全新路由——没有基线对等物);
  3. 长尾页 before/after 像素级一致的情况(纯重构、未触及该表面):直接把这页从正文里删掉,不要用无意义的空对填充。

这三条规则的共同目的,是杜绝「为了凑数而配对」——截图矩阵的每一格都应当携带评审者需要的新信息。

不要这样做:四条反模式

模板的「Don't do these」清单是实践中最容易踩的坑,逐条展开:

  • 不要内嵌 20+ 张图片:UI 页面多时,应该分成少数几个旗舰页 + 一个长尾表,而不是铺一面图片墙;
  • 不要混用托管源:如果一部分图片放在user-attachments、另一部分放在 gist,评审者无法理解原因,且混用显得草率。选定一种托管方式并保持一致;
  • 不要忘记非视觉部分:90% 截图 + 10% 文字 = 营销文案,不是工程沟通;
  • 不要用 HTML<video>或<details>嵌视频:GitHub 对两者都会做清理,除非视频放在user-attachments。正确做法是使用GIF(以图片形式内联渲染)。

截图捕获流水线:模板背后的实现

模板假设你手上已经有一批高质量的截图,而pr-visual-writeup技能在capture-patterns.md中给出了产出这批截图的具体配方。理解这些配方,才能让 PR 正文里的表格真正「可复制、可复现」。

并行捕获矩阵

捕获阶段按(主题 × 视口)拆成多个并行子代理,各自持有独立的--session-name浏览器会话:

  • after-light-standard/after-dark-standard:1920×1200,红色边框标注开启;
  • after-light-wide/after-dark-wide:2560×1440,仅旗舰页。

关键约束是并行发生在子代理之间,而不是单个会话之内——一个agent-browser会话只有一个导航上下文,无法并发打开两个 URL。文件名后缀统一为-before-<theme>[-wide].png与-after-<theme>[-wide].png,供 Phase 5 配对使用。

等待页面真正就绪

在 Next.js 开发模式下,networkidle不足以判断页面可截图——按需编译(on-demand compiler)和骨架占位(skeleton)都在 networkidle 之后才完成。wait-for-ready配方给出了一个 30 秒硬上限的轮询门控,逐项检查:

  • document.readyState === 'complete';
  • Next.js 编译指示器(#__next-build-watcher、[data-nextjs-dialog])不存在;
  • 正文不匹配^Compiling\b|building...;
  • 无加载占位([data-loading="true"]、[aria-busy="true"]、.skeleton);
  • 无 Tailwindanimate-pulse(Hexclave dashboard 中加载行的主力骨架信号);
  • 连续两次 250ms 轮询读取到的 body HTML 长度一致——单次 ready 闪烁可能落在骨架消失与真实内容换入之间,两次连续稳定读取才能确认 DOM 真正稳定。

wait-for-ready返回ok后还需再睡约 300ms,让滑入、淡入等最终动画落到静止帧。另外,正式捕获前每个子代理都要对自己的路由做一次warm-up 遍历,把按需编译的成本支付给一次「炮灰」访问,第二次访问才能截出干净画面。

红色边框标注:pr-visual-highlight 注入器

after 截图要标出新增 UI,注入器通过一段页面内脚本完成:

const selectors = $SELECTORS_JSON; // e.g. ['[data-testid=foo]', 'section:has(> h2)'] document.getElementById('pr-visual-highlight')?.remove(); const style = document.createElement('style'); style.id = 'pr-visual-highlight'; style.textContent = selectors.map(s => `${s} { outline: 3px solid #ef4444 !important; outline-offset: 2px !important; border-radius: 6px; box-shadow: 0 0 0 1px rgba(239,68,68,0.25) !important; }`).join('\n'); document.head.appendChild(style); return Array.from(document.querySelectorAll(selectors.join(','))).length;

设计细节值得注意:

  • 用outline而不是border——border会改变布局、破坏与 before 图的像素对齐;outline绘制在盒子外部;
  • 亮红#ef4444(Tailwindred-500)在深浅两套主题下都可读,不随主题切换颜色,保证一致性优先于对比度微调;
  • !important覆盖应用自身在 focus/hover 时设置的 outline;
  • 注入器返回匹配元素数量,若返回0说明选择器已失效,应记录告警并为该路由跳过高亮,而不是发一张与 before 毫无差别的 after 图;
  • 路由间必须移除<style id="pr-visual-highlight">,避免样式经缓存泄漏到下一页。

主题切换与视口设置

优先点击应用内的主题切换按钮(用agent-browser snapshot -i | grep -i 'theme'定位),再用document.documentElement.className验证期望的 class 已生效;直接改 class 可能无法触发应用级主题水合(rehydration),导致下次导航时闪烁。视口通过agent-browser set viewport 1920 1200(标准)与agent-browser set viewport 2560 1440(宽屏)设置,且必须在登录之后设置——部分登录流程在宽视口下会有不同的响应式渲染。

滚动动画:逐帧截图 + ffmpeg 拼接

不要使用agent-browser record——它创建全新的浏览器上下文,会丢失开发模式的登录态。正确做法是先定位 dashboard 布局内的内部滚动容器(侧栏 + 固定 header + 可滚动主区,通常不能直接滚动window):

agent-browser eval "(() => { const el = Array.from(document.querySelectorAll('*')).find(x => { const s = getComputedStyle(x); return (s.overflowY === 'auto' || s.overflowY === 'scroll') && x.scrollHeight > x.clientHeight + 100 && x.clientHeight > 400; }); window.__SCROLL_EL__ = el; if (el) el.scrollTop = 0; return !!el; })()"

window.__SCROLL_EL__被暂存后,后续滚动调用变成一行命令:按0, 100, ..., 900步进滚动 → 每步睡 150ms → 截图一帧 → 再反向滚回,最后用 ffmpeg 拼接:

ffmpeg -y -framerate 8 -i /tmp/frames/frame-%03d.png \ -c:v libvpx-vp9 -crf 32 -b:v 0 /tmp/pr-<N>-visuals/clips/<name>-scroll-<theme>.webm

滚动动画只挑 2~3 个最有代表性的页面做,而不是每页都做。

素材托管:gist + PAT,全程不碰浏览器 Cookie

截图与 GIF 的托管方式决定了正文里的图片能否内联渲染。.agents/skills/pr-visual-writeup/references/gist-upload.md给出了完整的 PAT-only 方案,核心权衡是:

  • GitHub 的user-attachments端点(拖拽上传图片时会用到)需要浏览器会话 Cookie,其权限范围比 PAT 更宽,除非用户明确同意,不应使用;
  • gist 托管只需 PAT:gh gist create只需要gistscope,git push只需 PAT;
  • gist.githubusercontent.com/<user>/<id>/raw/<file>形式的 URL 能在 PR 正文中内联渲染为图片/GIF。

完整上传流程(gist 的 raw URL 可直接作为正文表格里的<url>):

# 1. 创建公开 gist GIST_URL=$(gh gist create --public --desc "PR #<N> screenshots + scroll clips" \ -f README.md - <<< "PR #<N> assets" | tail -1) GIST_ID=$(basename "$GIST_URL") # 2. 本地克隆 git clone "https://gist.github.com/$GIST_ID.git" "gist-$GIST_ID" # 3. 拷贝全部素材并提交 cp /tmp/pr-<N>-visuals/shots/*.png /tmp/pr-<N>-visuals/clips/*.gif ./ git add -A git -c user.email=noreply@github.com -c user.name=<your-username> \ commit -m "Add PR <N> visuals" # 4. 用 credential helper 喂 PAT 推送 TOKEN=$(gh auth token) git -c credential.helper= \ -c credential.helper="!f() { echo username=<your-username>; echo password=$TOKEN; }; f" \ push

其中credential.helper=先清空既有 helper,再用自定义 helper 喂入 PAT,避免把 token 写进~/.gitconfig或凭据存储。仓库里的upload_gist.sh脚本把以上流程封装为一行:upload_gist.sh "PR #1338 visuals" /tmp/pr-1338-visuals/shots /tmp/pr-1338-visuals/clips,输出每行<basename>\t<raw-url>,并把 gist id 存到./gist-id.txt供后续重推。

上传后应当使用不带 commit SHA 的 raw URL——/raw/<file>始终解析到最新版本,这意味着更新某张截图后无需改动 PR 正文。嵌入前用curl -sI -L <raw-url>抽查一个资源,确认返回HTTP/2 200且 content-type 为image/png或image/gif。

文件大小红线

gist 单文件上限为 10 MB,总量控制在 10 MB 左右比较稳妥;GIF 膨胀极快,应通过fps=8、scale=960、时长 <5 秒把单支 GIF 压在 100~400 KB——这正对应convert_clips.sh的默认参数(其 ffmpeg 命令还使用palettegen/paletteuse双通道调色板来优化 GIF 画质)。gist 清理用gh gist delete $GIST_ID,但删除会破坏 PR 正文中的所有图片链接,通常落地后保留即可。

落库:gh pr edit与工作区布局

正文组合完成后,通过以下命令写回 PR:

gh pr edit <N> --body-file <path-to-md>

若 PR 位于公开仓库,推入前需与用户确认(这是共享状态操作);个人 fork 或 draft PR 可直接执行。

整个流程的中间产物统一放在/tmp/pr-<N>-visuals/工作区:

/tmp/pr-<N>-visuals/ ├── scope.md # Phase 1 输出 ├── shots/ # 捕获的 PNG ├── clips/ # webm + gif 滚动动画 ├── body.md # 组合好的 PR 描述 ├── gist-id.txt # 后续追加截图时重推用 ├── urls.txt # 每个文件的 raw URL ├── orig-branch.txt # Phase 1.5 记录的原分支 └── stash-ref.txt # Phase 1.5 记录的 stash ref

PR 正文设置完成后,PNG/GIF 永久保存在 gist 中;本地副本可删可留——如果想迭代重拍,保留更稳妥。

结合 Hexclave 仓库的落地要点

把模板落进 Hexclave 的实际 PR 流程时,有几个仓库特有的注意点:

  • 路由识别:Phase 1 用gh pr diff <N> --name-only过滤出页面文件,Next.js 项目关注**/page*.tsx/**/*page-client.tsx,再按 App Router 约定映射 URL 路径;只改后端/共享组件且没有明显 UI 表面的改动,直接忽略;
  • 骨架信号:Hexclave dashboard 大量使用 Tailwind.animate-pulse作为加载行占位——这是wait-for-ready选择器列表中信号最强的一项;实际使用时还应结合本次 diff 补充项目特有加载标记(如Spinner组件类名);
  • 开发服务器定位:detect_dev_server.sh遍历 node 监听的端口并读取页面<title>,输出<port>\t<title>\t<url>,帮助你在 dashboard、API、docs、mock-OAuth 多个并行进程中准确选出要截图的那一个;
  • 并行登录的边界:若 mock-OAuth 服务器串行处理登录,或总共只有 1~2 个页面,就不值得做并行扇出——按原样串行执行即可。

总结

pr-body-template.md的价值不在于它是一份「标准表单」,而在于它把「如何让评审者高效理解一次 UI 改动」抽象成了一组可复用模式:Summary + Base/Head + Scope 的头部三要素、旗舰页 2×2 截图矩阵 + 宽屏变体、长尾页紧凑表、可选滚动 GIF 节,以及 alt text、before/after 配对与反模式清单。配合capture-patterns.md的捕获配方与gist-upload.md的 PAT-only 托管方案,它形成了一条从「diff 到截图、从截图到正文、从正文到 PR」的完整链路。实际写作时,记住三条底线:每个表格单元格都要携带新信息(宁缺毋滥)、每张 after 图都要有可比的 before 基线(新路由要显式标注)、每个 PR 都不能只有图而没有文字地图。

  • 后端
  • 认证鉴权
  • 前端

【免费下载链接】hexclave

The user infrastructure platform. You choose the frontend, backend, and database. Hexclave handles everything else.

项目地址:https://gitcode.com/gh_mirrors/stack/hexclave
点击查看免费下载
上一篇:【亲测免费】 探索神奇宝贝世界的宝藏——Pokedex.org
下一篇:探索数据库管理新维度:Visual Studio Code 的强力工具

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询