---
name: screenshot-annotate
description: >-
  截图标注的标准化流程。触发场景包括但不限于：
  「给截图加标注」「截图标注」「标注截图的箭头」「给截图画箭头」「截图圈出来」「标注哪个区域」「标注关键区域」
  「加箭头和文字」「给这张截图标注一下」「用箭头标注截图」「截图上的红框指着哪」等任何「对截图做标注/圈选/加箭头文字」的说法。
  当截图规范要求「箭头标注」但需要保证「标注得准」（坐标换算、不靠肉眼估）时，也自动使用本 Skill。
  核心：坐标必须来自浏览器 DOM 测量（getBoundingClientRect + 滚动量）精确换算到截图像素，再用统一脚本标注，
  禁止肉眼看图估坐标。
---

# 截图标注标准化流程（Screenshot Annotate）

⚠️ **本 Skill 已触发。在用户回复中第一句话必须输出：「🔧 已触发 `screenshot-annotate`，按坐标换算流程标注截图…」然后严格按照以下步骤执行。**

## 核心原则

- **标注必须准时归一到「坐标换算」**：箭头/框/文字放哪，由浏览器告诉我们的元素坐标（`getBoundingClientRect()` + `scrollX/scrollY`）经公式换算成截图里的像素位置；**禁止肉眼看图估坐标**。坐标对不上 = 标注就是歪的，这是以前「强制 4K 时标注不准」的根因。
- **坐标必须是页面 CSS 坐标或浏览器直接给的缩放信息**，不能是截图里目测出来的点。

## 标注坐标换算方法

### 情形 A：真实视口 + `fullPage` 全页截图（推荐做法）

浏览器 `window.innerWidth` 就是截图宽度（设了截图画布时也一样）。对全页截图：

```
元素在「整页」中的 CSS 位置 = rect.left + scrollX
                         rect.top  + scrollY
```

fullPage 截图宽高 = 整页 CSS 宽高 × `devicePixelRatio`（DPR）。因此：

- **DPR = 1**：整页 CSS 坐标 = 截图像素坐标，直接给 annotate.js 传 `x = rect.left + scrollX`、`y = rect.top + scrollY`，无需任何换算参数。
- **DPR ≠ 1**（`set viewport <W> <H> 2` 提高了 DPR）：必须传 `--fullpage --dpr N`（N = `window.devicePixelRatio`），annotate.js 按 DPR 等比缩放，`w/h` 一并缩放。

⚠️ **禁止**在 fullPage 场景用 `--viewport "cssW,cssH"` 让 annotate.js 按 `imageH / cssH` 算 Y 轴——`cssH` 是视口高不是整页高，页面越长标注越往下偏。

⚠️ **坐标捕获命令会返回 `dpr`**：截图时记下 `set viewport` 是否用了 DPR>1，若用了必须把捕获到的 `dpr` 一并传给 annotate.js（`--fullpage --dpr <dpr>`），不要依赖记忆。

### 情形 B：设置了自定义 viewport 视口的截图（截图宽度 = viewport 宽）

浏览器把 viewport 设成 X×Y，截图宽高 = X×Y × DPR（`window.innerWidth` / `innerHeight` 是 CSS 宽高）。元素坐标直接用 `rect` 的值即可，但传给 annotate.js 时：
- DPR = 1：直接传，无需参数；
- DPR > 1：传 `--viewport "cssW,cssH"`（cssW/cssH = `window.innerWidth/Height`），annotate.js 按截图/视口换算。

### 情形 C：一个普通（非拼接、无缩放）截图

如果截图就是某视口的渲染结果，且截图宽 ≠ CSS 视口宽，说明有 `devicePixelRatio` 缩放。
此时标注坐标 = CSS 坐标 × (截图宽 / 视口 CSS 宽)（横向），(截图高 / 视口 CSS 高)（纵向）——等价于传 `--viewport "cssW,cssH"` 给 annotate.js 自动换算。

## 坐标捕获命令（在打开的浏览器标签页上执行）

用 `agent-browser --cdp 9226 --namespace <NS> eval` 捕获目标元素的精确坐标：

```js
(function () {
  const el = document.querySelector('选择器');   // 替换成真实目标元素的选择器
  if (!el) return { error: '未找到元素', selector: '选择器' };
  const r = el.getBoundingClientRect();
  return {
    x: Math.round(r.left + window.scrollX),        // 整页内 CSS 像素
    y: Math.round(r.top + window.scrollY),
    w: Math.round(r.width),
    h: Math.round(r.height),
    url: location.href,
    viewport: { w: window.innerWidth, h: window.innerHeight },
    dpr: window.devicePixelRatio,
  };
})()
```

上次截图若用了 `set viewport` 设视口，在 eval 里把 `window.innerWidth/Height` 作为 viewport 一并读出，确认截图宽高与之一致。

## 统一标注脚本 annotate.js

标注由 `annotate.js`（本 skill 目录内的零依赖 Node 脚本）完成。它把截图 + 坐标转成自包含标注 HTML（截图 base64 内嵌、SVG 箭头/框/文字精确放在换算后的坐标上），再用无头浏览器渲染为 PNG。

脚本与 SKILL.md 位于同一 skill 目录（随 agent-rules 同步到各项目 `.claude/skills/screenshot-annotate/`）。调用时定位到该目录（在项目根目录下直接粘贴即可执行，禁止用 `BASH_SOURCE[0]`——那只在脚本文件内有效，粘贴到终端时为空）：

```bash
# 从项目根目录定位 skill 目录（git rev-parse --show-toplevel 返回仓库根，与 loop-review 里 check-review.sh 的取法一致）
ROOT="$(git rev-parse --show-toplevel)"
SKILL_DIR="$ROOT/.claude/skills/screenshot-annotate"
node "$SKILL_DIR/annotate.js" \
  shot.png shot-annot.html \
  --json '[{"x":412,"y":1240,"w":160,"h":48,"text":"新增的保存按钮","color":"#22c55e"}]' \
  --label "✅ 修复后" --labelColor "#f43f5e" --url "https://api-test.xxx/gateway/dashboard"
```

- 坐标默认视为**设备像素**（截图内坐标）；`--viewport "cssW,cssH"` 传入时视为 CSS 视口坐标自动换算（非 fullPage）；`--fullpage --dpr N` 用于 fullPage 全页截图（坐标视为整页 CSS 坐标，按 DPR 缩放）
- `--label` 顶部标题条（适合「修复前/修复后」对比）、`--url` 在标题条显示 URL 满足「URL 可见」
- `text` 文本框自动排布在目标旁空闲侧，不遮挡内容；`w/h` 存在时画高亮圆角框指向其中心

## 渲染标注 HTML 为 PNG（固定流程）

用 `SKILL_DIR/annotate.js` 生成标注 HTML 后，用无头浏览器渲染为 PNG。

```bash
NS="claude-截图标注"
# 1. 无头打开标注 HTML
agent-browser --cdp 9226 --namespace "$NS" tab new "file:///<上一步生成的绝对路径>/shot-annot.html"
# 2. 设视口为标注画布尺寸（annotate.js 会打印「画布尺寸 WxH」）
agent-browser --cdp 9226 --namespace "$NS" set viewport <W> <H>
# 3. 全页截图
agent-browser --cdp 9226 --namespace "$NS" screenshot --full shot-annot.png
# 4. 关闭标签页
agent-browser --cdp 9226 --namespace "$NS" tab close <tabId>
```

确认渲染图尺寸与画布一致、无黑边（`sips -g pixelWidth -g pixelHeight`），标注清晰后交付。

## 校验

- 箭头尖端指向的元素，肉眼应与 eval 捕获到的目标一致（坐标没歪）
- 文本框不遮挡关键内容
- 标注入口处能确认 URL（标题条或截图内地址栏）
- 修改坐标/文字后重新跑脚本 + 渲染，不要在原图上手工补。

## 重要规则

- ⚠️ 坐标必须有浏览器测量来源，禁止目测
- ⚠️ 每种「关键区域」都要箭头/框标注到位，禁止只标一处；多个改动点逐个覆盖
- ⚠️ 若截图是真实视口截出的，尽量一图一文；整页图定位大区域，局部区域用放大图单独标注
- 渲染用无头实例（不抢焦点）；如需给用户看效果用截图