html原型 PRD 标注

SKILL商业运营
商业运营 未标注权限 许可证不明确
安装这个技能

HTML 原型 PRD 标注工具:在 HTML 原型的指定元素旁注入数字角标,每条角标关联 AI 预写的需求说明(需求描述、业务规则、数据来源、边界与异常),让原型自带可交互的 PRD,研发打开原型就能点开看需求。 核心能力: 标注定位:支持文字描述或截图圈选两种方式指定位置,同一份文件可混用;自动生成 CSS 锚点,角标渲染在独立图层、不受容器 overflow 裁剪 双角色模型:读者单击角标只看只读富文本说明;作者快捷键唤出工具栏(Mac ⇧A / Windows Alt+A),Ctrl/⌘ 点击角标直接编辑 富文本编辑:加粗 / 斜体 / 标题 / 引用 / 列表 / 链接 / 高亮 / 撤销重做 / 纯文本粘贴 多页面支持:每个页面各自从 1 编号,工具栏计数、上一个 / 下一个、管理列表默认只作用当前页面 标注管理:拖拽排序、按位置重排、定位、编辑、删除,序号自动连续 导出:一键导出 Word PRD(按页面分组、保留富文本),或导出最新 HTML AI 协作闭环:一键复制标注 JSON 信封(含页面名、序号、文件指纹),粘贴给 AI 即可回写文件;AI 也可离线读浏览器缓存直接取回标注 数据安全:标注自动镜像到浏览器缓存,误关页面可恢复;注入 / 升级 / 回写均有防呆脚本(防重复注入、自动备份、写完复核)

一句话选型结论

按资料字段整理

html原型 PRD 标注是商业运营类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类商业运营 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_fddbdcfc/prd-annotation
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

商业运营
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_fddbdcfc仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验2026-11-06上游最后核验时间 · 数据更新 2026-11-06
正文体量59.0 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
写运营方案

按目标产出活动方案与执行清单。

分析数据与指标

从报表里找问题、给结论。

整理客户与销售材料

把客户信息与话术整理成可直接用的材料。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在商业运营场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
html原型 PRD 标注当前 未标注权限 未标注 未标注 未标注 有
MX 股票筛选器 联网访问 ClawHub 市场 79.9 万 OpenClaw 有
交易 未标注权限 ClawHub 市场 58.1 万 OpenClaw 有
营销技巧 只读 ClawHub 市场 54.0 万 OpenClaw 有
百灵鸟好 联网访问 GitHub 仓库 52.6 万 Codex、Claude、Cursor 有
股票信息浏览器 接触敏感数据 ClawHub 市场 40.5 万 OpenClaw 有

SKILL.md 全文

59.0 KB
模型实际读到的说明书原文,来自技能仓库

PRD 原型标注(v1.1.1)

目的

让 HTML 原型自带可交互的需求说明:数字角标定位到具体元素,点击弹出需求详情;
同一份文件对作者是可编辑工作台,对读者是只读文档。
v1.1.0 在旧版基础上整合了 html-annotation-pro 的优势:独立图层角标(免疫 overflow 裁剪)、
隐藏式作者工具栏(快捷键唤出)、点击新建(拾取模式)、标注管理面板(拖拽排序/按位置重排)、
按页面独立的动态序号与页面级导航/筛选;并去除一切动效,保持零依赖单文件。

交互模型(谁看到什么)

角色 操作 结果
读者 单击角标 只读富文本弹层(链接高亮、新标签页打开);无任何编辑入口
作者 Mac ⇧A / Windows Alt+A 唤出/隐藏右下角工具栏(读者默认看不到任何作者 UI)
作者 忘记快捷键 工具栏已唤出时,最左端灰底小字显示当前快捷键;看它就知道
作者 Ctrl/⌘ + 点击角标 打开富文本编辑弹窗;「保存」后提示保存成功并自动关闭
作者 工具栏「点击新建」 拾取模式:点页面任意元素 → 自动生成 CSS 选择器 → 打开编辑弹窗
作者 工具栏「上一个 / 下一个」 只在当前页面的标注间循环(计数 = 当前页面条数);单页原型即全文件
作者 工具栏「☰ 管理」 右侧滑出管理抽屉:默认只列当前页面的标注(可切「全部页面」);定位/编辑/删除/拖拽排序/按位置重排;底部两个 Word 导出按钮
作者 管理抽屉「导出本页面 PRD」 只导出当前页面标注的 Word 文档(docx)
作者 管理抽屉「导出文件 PRD」 导出本文件全部标注的 Word 文档,按页面分组
作者 工具栏「保存并导出」 把最新 JSON 固化回 HTML 并下载新文件(仅此一处导出 HTML)
作者 工具栏「⧉ 复制全部JSON(M)」/「⧉ 复制本页JSON(N)」 复制一份信封(scope + items + baseHash + 页面名/序号/是否为空)到剪贴板,粘贴给 AI 即可回写进文件(v1.1.0 首选通道,不用导出)。括号里是条数,随切页实时刷新
作者 工具栏「⟲ 恢复缓存(N)」 只在检测到浏览器缓存与文件内容不一致时出现;点击把缓存里的标注找回内存(多用于误关页面后),需再「保存并导出」才固化
作者 工具栏「✕ 丢弃缓存」 与「⟲ 恢复缓存」成对出现;确认磁盘文件才是最新的,清掉浏览器里的旧缓存,避免误点恢复把它盖回去(只清缓存,不动文件)
AI 离线读浏览器 leveldb 兜底通道:用户没复制 JSON 时,AI 直接取回标注(见「从浏览器读回标注」)

> 为什么 Mac 改用 ⇧A:Mac 内置键盘上 ⌥Option 在 ⌘Command 外侧,而外接 Windows
> 布局键盘插到 Mac 后,那个外侧位置是 Win 键、真正的 Option 落在贴着空格的 Alt 键上。
> 也就是说「同一个手指位置,内置键盘是 ⌥、外接键盘是 ⊞」,外接用户按习惯位置常常按错。
> Shift 键在所有键盘上的位置都一致(左下/右下最外侧),换成 ⇧A 就不存在这个歧义。
> Windows/Linux 的 Alt 位置没有该问题,沿用 Alt+A。
>
> 快捷键的例外(都是刻意设计,别当成 bug):
> 1. 焦点在 输入框/文本域/可编辑区 时不触发——否则原型里打不出大写字母 A;点页面空白处
> 或按 Esc 让焦点离开输入框即可恢复
> 2. 编辑弹窗开着时不触发——避免写标注文案时手滑把工具栏叫出来
> 3. Windows/Linux 上 ⇧A 不触发(那边只认 Alt+A),跨平台不会误唤出;
> Mac 的 ⌥A 是备用键,会触发
>
> Mac 上 ⇧A 唤不出时的排查:先看焦点是不是卡在输入框;再看「系统设置 → 键盘 → 键盘快捷键」
> 是否占用了 ⇧A。另外 macOS 自带中文拼音输入法默认用 Shift 切换中/英,⇧A 可能顺带把输入法
> 切成英文(preventDefault 拦得住字符输入,拦不住输入法状态切换)——这种情况改用备用键 ⌥A。
所有作者操作(新增/编辑/删除/排序)在内存生效并即时镜像到浏览器 localStorage:
镜像只是「找回 + 让 AI 读回」用的中转,交付仍以文件里的 JSON 数据区为准,
改完必须「保存并导出」固化。Word 导出是只读行为,不会改动内存数据。

回写(v1.1.0):把浏览器里的标注写回原型文件

作者标完 → 快捷键唤出工具栏(Mac ⇧A / Windows Alt+A)→ 点「⧉ 复制全部JSON(M)」或「⧉ 复制本页JSON(N)」→ 把信封粘贴到对话 → AI 写回目标 HTML 文件。
两个按钮只差范围:全部装整套标注(scope:"all");本页只装当前可见页面那几条(scope:"page",多带一个 page 字段)。
单页原型(没有 data-prd-page)下"本页"恒等于"全部",此时「本页」按钮会自动隐藏。
这是首选通道:比「AI 去读浏览器 leveldb」可靠(不依赖 Chrome 版本、存储格式、snappy 压缩),
也比「用户点保存并导出、再把新文件发回来」少一步,且不产生重复文件。

三种指令(第 3 条只是第 2 条加限定范围)

指令 AI 行为
「已标注,请回写」 只把浏览器标注原样写回文件,不做任何预填
「已标注,请回写并预填PRD」 写回 + 为内容为空的角标预填 title/body;已有内容的角标一字不动
「…并预填「XX页面」第N个角标的PRD」 同上,但只预填指定页面 + 指定序号那一条,其余保持原样

用户没说页面名 → 按当前页面理解;单页原型可省略页面名。
「空则预填、非空不动」让它天然幂等:同一份信封反复回写,结果都一样。

信封结构(作者复制出来的东西)

{
  "tool": "prd-annotation",
  "version": "1.1.1",
  "scope": "all",
  "file": "/C:/Users/xxx/原型.html",
  "baseHash": "h1a2b3c4d-520",
  "at": 1758000000000,
  "items": [ { "anchor": "#tbl th:nth-child(9)", "pos": "tr", "page": "设备管理", "title": "", "body": "" } ],
  "view":  [ { "page": "设备管理", "no": 1, "anchor": "#tbl th:nth-child(9)", "title": "", "isEmpty": true } ]
}
  • scope —— "all"(复制全部JSON)或 "page"(复制本页JSON;此时多一个 page 字段写明页面名)
  • items —— 标注内容,写回时就是它;scope:"page" 时只是文件里的一部分,不是全部
  • view —— 给人(和 AI)读的清单:页面名 + 页面内序号 + 是否为空。
    序号运行时才现算、不落盘,所以"第N个角标"的 N 只能以 view.no 为准(同页面内从 1 开始)
  • baseHash —— 页面加载那一刻,文件数据区里标注的规范化指纹(决定覆盖还是合并)。
    ⚠️ 它只在加载时算一次:此后在页面里新增/编辑标注都不会重算,所以它恒等于"文件基线",
    不能用它推断 items 是不是完整的(加过角标后 items 比 baseHash 对应的多)
  • file —— 浏览器里的页面路径,用来对上磁盘文件(Windows 下形如 /C:/...,去掉开头那个 /)

⚠️ 两种信封:all 与 page(处理方式不同,别混)

信封 scope 来源按钮 items 范围 AI 必须怎么做
"all" ⧉ 复制全部JSON(M) 全部标注 baseHash 与文件一致 → 可整份覆盖;不一致 → merge
"page" ⧉ 复制本页JSON(N) 只有当前页那 N 条 禁止整份覆盖(会把其他页面的标注删掉);一律走 merge 局部合并

scope:"page" 时,文件里"信封内没有"的标注属于其他页面,不是被删除的。
writeback.js 已据此设防:plan 明确禁止覆盖、merge 跳过删除清扫并原样保留其他页面、apply 直接拒收局部信封(要求先 merge)。

空角标判定

title 与 body 去掉 HTML 标签、 、空白后都是空串 → 空角标(view.isEmpty: true),
也就是"作者只点了位置、还没写内容"。预填只作用于这些条目;任何一边有内容就算已写,不动。

baseHash 比对算法(AI 侧必须一字不差地复刻)

组件内实现(djb2 + 长度,作用在规范化后的 JSON 上):

function normalizeList(list) {
  return JSON.stringify(list.map(x => ({ anchor: x.anchor || '', pos: x.pos || 'tr',
    page: x.page || '', title: x.title || '', body: x.body || '' })));   // 字段顺序固定、无缩进
}
function hashText(s) {
  let h = 5381;
  for (let i = 0; i < s.length; i++) h = (((h << 5) + h + s.charCodeAt(i)) >>> 0);
  return 'h' + h.toString(16) + '-' + s.length;
}

算「文件当前基线」时,还要先复刻组件的加载处理:
读数据区 JSON → 剔除 deleted 为真的条目 → 若存在数字 id 则按 id 升序排序 → normalizeList → hashText。
(s.length 与 charCodeAt 在 Node 与浏览器里同为 UTF-16 语义,可直接对齐。)

合并规则(哈希自适应)

情况 判定 做法
payload.baseHash === 文件hash 浏览器加载的就是当前最新文件 整份覆盖:items = payload.items(绝大多数情况走这条,最省心)
两者不等 浏览器页面是旧文件(AI 改过文件后作者没刷新),或文件被别的流程改过 按 anchor 合并,规则见下

合并的匹配键 = anchor;同一 anchor 有多条时按各自出现顺序配对;page 不同视为不同条目。

  • 浏览器非空 + 文件空 → 取浏览器的
  • 浏览器空 + 文件非空 → 保留文件(AI 之前预填的内容不会被空条目冲掉)
  • 两边都非空 → 以浏览器为准(那是作者最后编辑的)
  • 两边都空 → 要预填就预填,否则保持空
  • 只在文件里、浏览器已没有 → 视为作者在浏览器里删掉了;默认删除,但必须在回显清单里单独列出让用户确认
  • 只在浏览器里、文件没有 → 新增,直接采纳

写回步骤(不得跳步)

落地一律用 scripts/writeback.js(plan → merge → apply),下面 7 步就是它内部做的事;
手工时必须逐步照做:

  1. 定位文件:用 payload.file 对上磁盘路径(去掉前导 /);对不上就问用户,别猜。
  2. 算文件 hash,按上表决定「整份覆盖 / 合并」——writeback.js plan 会替你算并直接给出结论。
  3. 回显清单再写(红线):先把将要写入的内容列给用户——
    每条一行 页面名 / 第N个 / 锚点 / 状态(新增·更新·预填·保留·删除),
    末尾给统计:总条数、其中预填 N 条、删除 N 条。用户确认后才落盘。
  4. 写前自动备份(apply 默认已做):原文件复制为 <原名>.bak-<时间戳>.html(同目录),确认备份成功再写。
  5. 只替换数据区(apply 只做这一件事,其余字节零改动):用 id="prd-annotations" 定位那个 JSON script,
    只替换两个标签之间的内容(JSON.stringify(items, null, 2)),其余字节一个都别动。
    ⚠️ 不要写 id/number 字段——序号不落盘。
  6. 校验:重新读回文件,确认 JSON 可解析、条数正确、无重复锚点;
    再用浏览器打开验证角标数 = 条数、控制台无报错。
  7. 提醒用户刷新:AI 改的是磁盘文件,浏览器里还是旧页面。
    刷新后工具栏若出现「⟲ 恢复缓存(N)」,说明 localStorage 里那份是旧的 →
    点「✕ 丢弃缓存」以文件为准。
    ⚠️ 千万别点「⟲ 恢复缓存」——那会把旧缓存盖回刚写好的文件。

预填模板

见 references/annotation-writing.md 的「回写预填」小节:
title 是与该元素一一对应的短标题(≤12 字),body 按
需求描述 / 业务规则 / 数据来源 / 业务限制 / 边界异常 五段组织。
预填必须基于原型里真实存在的元素与字段来写;原型里看不出来的(接口名、表字段、枚举值)
写「待确认」,绝不编造。

从浏览器读回标注(leveldb 兜底通道)

> 优先让作者复制 JSON(上一节)。本节只在作者没复制、或需要核对浏览器实际状态时用。

用户往往会直接说「我在 xxx.html 上标好了,你读回来 / 预填 PRD」。
不要问用户导出文件,也不要临时另造通道——组件已经把标注镜像进 localStorage,按下面步骤读即可。

数据在哪

键 内容
prd-annos::<encodeURIComponent(location.pathname)> 该项目原型当前全部标注(JSON 数组,字段同数据区)
prd-annos::__last__ { path, n, at }:最后一次写入的文件路径、条数、时间戳 → 用它定位目标文件
prd-annos::__snapshot__::<路径编码> 检测到未固化缓存时自动留的历史快照(主键后来被覆盖时从这里救)。v1.1.1 起按路径隔离——旧版是不带路径的全局单键,同时打开多个原型时,后加载的文件会把先加载文件的快照覆盖掉
prd-annos::__view__ { path, at, baseHash, count, view }:带页面名 + 页面内序号 + isEmpty 的可读清单,序号不落盘,只能从这里拿到;baseHash 与复制信封里的同源

__view__ 让你不用打开浏览器也知道「第 N 个角标在哪一页、锚点是什么、是不是空角标」,
是「预填第N个角标」这类指令在兜底通道下的定位依据。

键里含完整文件路径 → 不同原型互不串味;file:// 打开时所有本地页面共用 file:// 桶。

读回步骤(Windows + 本机 Chrome 实测通过)

  1. 复制 leveldb(Chrome 正在跑也能复制,文件被独占锁不影响 copyFileSync):
    <Chrome User Data>/Default/Local Storage/leveldb/* → <tmp>/Default/Local Storage/leveldb/。
    ⚠️ 必须落在 Default 子目录下,否则 Chromium 认不到这套存储。
  2. 用 Chromium 打开任意 file:// 空白页(这一步同时解决 .ldb 是 snappy 压缩、纯文本读不出值的问题):
    js const require = createRequire('<node 工作区路径>/'); // ESM 不读 NODE_PATH const { chromium } = require('playwright-core'); const ctx = await chromium.launchPersistentContext(tmp, { channel: 'chrome', headless: true }); const page = await ctx.newPage(); await page.goto('file:///<任意本地空白页>'); const data = await page.evaluate(() => { const out = {}; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k.startsWith('prd-annos::')) out[k] = localStorage.getItem(k); } return out; });
  3. 筛 prd-annos:: 键:非 __ 后缀的那个就是目标文件的标注数组;
    先读 __last__ 确认 path 与用户说的文件一致(不一致说明他标的其实是另一个文件);
    再读 __view__ 拿「页面名 + 页面内序号 + isEmpty」和 baseHash。
  4. 写回:走上一节「写回步骤」的第 2~7 步(算文件 hash → 回显清单 → 备份 →
    只替换数据区 JSON.stringify(items, null, 2) → 校验 → 提醒刷新)。
    序号不落盘,不要写 id/number 字段。
  5. 若用户还要「预填 PRD」(他标了位置、没写内容),按 references/annotation-writing.md
    的「回写预填」小节补 title/body——只补 view.isEmpty 为真的条目。

环境坑(踩过的,别再试)

  • 企业 DLP 加密下载目录:Downloads 里的文件(浏览器下载产物)在磁盘上是密文
    (文件头固定为 17 da 5f a0 16 33 cd 9a bc 5b 6c bf 14 aa 1e 33),readFileSync 得到乱码、zipfile 报
    "not a zip file"。但 copyFileSync 复制到项目目录后就是明文(DLP 在非受控目录落盘时透明解密)
    → 用户真点了「保存并导出」时,先复制一份再读,别断言"文件坏了"。
  • .ldb 读不出值:leveldb 已 compaction 的数据被 snappy 压缩,纯文本只能搜到键名;
    用第 2 步的 Chromium 解析即可拿到全部键值。新写入的数据在 .log(WAL)里可明文读。
  • localStorage 有提交延迟(约 5s):刚标完就复制可能拿不到最新值,等 7s 以上更稳。
  • 别用 WorkBuddy 内置预览标注:预览页是 srcdoc iframe、所有原型共用同一个存储桶(必串味),
    且无 CDP 端口、无法被 playwright 接管。统一引导用户走 present_files 卡片的「在浏览器中打开」
    (系统 Chrome + file:// 地址,键按文件区分)。
  • channel: 'chrome' 若报错,改用 executablePath:
    Windows C:/Program Files/Google/Chrome/Application/chrome.exe
    macOS /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

命令行脚本(防呆层,优先使用)

skill 目录下有三个脚本,把最容易出错的三件事(注入/升级、回写、验证)变成一条命令。
凡是要注入、升级、回写,一律先走脚本,不要手工改文件——下面每个坑都是踩出来的,
脚本已经把它们变成硬拒绝或自动规避。

node 用绝对路径,按系统取:
- macOS: ~/.workbuddy/binaries/node/versions/<版本>/bin/node
- Windows: C:/Users/<你>/.workbuddy/binaries/node/versions/<版本>/node.exe
(旧版写死了某台开发机的 C:/Users/<某用户名>/...,换机即失效。)

scripts/inject.js —— 注入 / 升级组件

命令 用途
node scripts/inject.js <目标.html> 首次注入;文件里已有标注层会直接拒绝并解释原因
node scripts/inject.js <目标.html> --upgrade 换新版组件,保留数据区里的标注内容
node scripts/inject.js <目标.html> --upgrade --dry 只报告会做什么,不写盘
node scripts/inject.js <目标.html> --no-backup 不备份(不建议)

它挡掉的坑:

坑 脚本怎么挡
重复注入 → 两份数据区 → 新标注全部看不到 检测到 id="prd-annotations" 直接退出并提示改用 --upgrade
用 indexOf('<style>') 定位组件样式 → 把原型自己的样式块删掉 只用界标 PRD-CSS-START/END 或 .prd-marker 特征定位
升级时用备份覆盖 → 连原型改动和标注一起冲掉 先取出数据区 JSON,剥组件 → 注入新版 → 原样写回并比对是否一致
忘记备份 默认备份到同目录 _备份/<原名>.bak-<时间戳>.html
</head> / </body> 取第一次出现 → 注释里的同名标签毁掉文件 一律 lastIndexOf
注入完没验证、写了没生效 落盘前 5 项自检 + 写后重新读盘复核(防外部进程回滚)

自检任一项不过就不写盘、退出码非 0,此时原文件一字未动。

scripts/writeback.js —— 回写标注

三步走,plan 不写盘,先看清楚再动:

node scripts/writeback.js plan  <目标.html> <信封.json>
node scripts/writeback.js merge <目标.html> <信封.json> -o merged.json
node scripts/writeback.js apply <目标.html> <items.json> [--base-hash h] [--no-backup]
坑 脚本怎么挡
拿到信封就闷头写 → 用旧页面数据盖掉文件里已预填的内容 plan 先打印文件 hash vs 信封 hash、逐条清单(页面/序号/锚点/状态)、统计;hash 不一致就明确建议走 merge
拿「⧉ 复制本页JSON」的局部信封去整份覆盖 → 静默删掉其他页面的标注 plan 识别 scope:"page" 并明确禁止覆盖;merge 跳过删除清扫、其他页面原样保留;apply 直接拒收局部信封并要求先 merge
两边都有改动不知道听谁的 merge 按哈希自适应规则合并:浏览器非空→以浏览器为准;浏览器空+文件非空→保住文件;只在文件里→视为删除并显式提醒
顺手改坏了原型 apply 只替换数据区两个标签之间的内容,其余字节零改动
忘记备份 默认备份到 _备份/
写完没生效 / 被回滚 写后重新读盘复核条数与数据区份数,不符直接报错让你从备份恢复
忘了提醒用户刷新 apply 结尾固定打印两条必须转告用户的话(刷新 / 别点「⟲ 恢复缓存」)

⚠️ apply 吃的是 items.json(纯数组),不是信封 —— 把信封直接喂给 apply 会被防呆拦下
(scope:"all" 的信封也一样,脚本只看"是不是数组")。完整链路:
plan → 拿到纯数组:hash 一致时把 env.items 落成一个数组 json(或直接跑 merge 产出,二者等价;
hash 不一致、或 scope:"page" 的局部信封则必须 merge)→ AI 只为 isEmpty 的条目预填 title/body → apply。

scripts/verify.js —— 验证(三档)

档 命令 端到端 做什么
L1 verify.js <文件> ~0.3 s 静态:数据区份数 / 起止界标 / JSON 可解析 / 组件版本 / 运行时节点是否固化进原型 / 页面标记 / 字段完整性。不开浏览器
L2 verify.js <文件> --browser ~4.5 s 打开页面做 DOM 断言:版本一致 / 条数一致 / 每个锚点命中数(0 = 失配,>1 = 可能串页) / DOM 实际所在页 vs 数据里声明的页 / 可见角标数 / 控制台报错。不点击、不写数据
L3 verify.js <文件> --full ~15 s 真的点:给每个页面临时各建 1 条 → 逐页切换验证「每页只显示本页角标」→ 新建/保存/复制 JSON → 刷新 → 验证「⟲ 恢复缓存」出现 → 点「✕ 丢弃缓存」→ 断言按钮消失 + 磁盘文件一字未动

各档抓不到什么(别把低档当高档用):

档 抓不到
L1 锚点能否命中 DOM、角标位置、跨页串页、任何交互(快捷键唤出 / 新建 / 保存 / 复制 / 导出 / 缓存按钮)
L2 交互功能(新建、保存、复制、缓存按钮)、导出产物是否干净、角标是否被容器裁剪、跨页切换后的隔离(L2 只看当前页)
L3 拖拽排序 / 按位置重排 / 管理抽屉细节 / Word 导出内容正确性 / 真实导航点击(L3 用强制显隐切页,不是点菜单)

什么时候必须跑 L3 —— 不靠记忆,脚本自己会提醒:
L3 通过后会把「组件指纹」写进 scripts/.verify-state.json;之后跑 L1/L2 时,
若「从未跑过 L3」或「组件代码改过」,会打印 ⚠ 并以退出码 2结束,提示必须重跑 --full。
组件不稳定期就靠这个兜底:只要组件动过,系统自己要求跑完整档。
退出码:0 = 通过且组件已完整验证 / 1 = 有 FAIL / 2 = 本档通过但组件未经 L3。

⚠️ L3 会在每个页面临时建 1 条标注(只进内存与浏览器缓存),跑完点「✕ 丢弃缓存」清掉,
并断言磁盘文件一字未动——可以放心在真实交付文件上跑。

改文件后的复核习惯(agent 侧,别偷懒)

  • 用脚本或 node fs 写文件,写完立刻 readFileSync 断言关键字符串确实在里面。
    本环境实测:Edit 工具的写入有被外部进程吞掉的情况(返回成功、磁盘没变),
    不复核就会把"改好了"当交付,实际跑的还是旧内容(曾因此连出两次假失败)。
  • 不要在 node -e "..." 里写反引号:bash 先做命令替换,把内容搅乱,
    表现为退出码 0 但文件被写坏。要改文件就写成 .js 脚本再跑。
  • 注入 / 升级 / 回写之后,浏览器打开验证(角标数 = 条数、控制台无报错)不能省。

工作流程

  1. 用户说「我已经标好了」时:先问一句"方便的话点工具栏「⧉ 复制全部JSON」把信封粘贴给我"(首选通道);
    若他只想让你看某一页,让他点「⧉ 复制本页JSON」——那份信封是局部的,只能 merge,别整份覆盖;
    他没复制就走「从浏览器读回标注」兜底。拿到标注后按指令分流:
    - 「请回写」 = 原样写进数据区,不预填;
    - 「请回写并预填PRD」 = 保留他的锚点与已写内容,只为 isEmpty 的角标补 title/body;
    - 「…第N个角标」 = 只补指定页面 + 序号那一条。
    写完一律走「写回步骤」(回显清单 → 备份 → 只替换数据区 → 校验 → 提醒刷新)。
    不要回头再问他要标在哪个元素上。
  2. 确定标注位置(用户还没标、需要 AI 代劳时):用户用两种方式指明——
    - 文字说明(如"在表格的操作列标注")→ 解析为目标 HTML 中对应元素
    - 截图并圈画标记 → 根据截图中元素的文字/表头/按钮内容,映射到 HTML 中的对应节点;
    被圈选的元素,角标默认放在该元素右上角(pos: tr),除非用户另行说明
    若目标元素有多种可能,选择最语义化的一个并说明依据,不反复追问。
  3. 注入标注层:用脚本,不要手工改 —— node scripts/inject.js <目标.html>
    (文件里已有标注层会自动拒绝;升级组件加 --upgrade)。详见「命令行脚本(防呆层,优先使用)」。
    脚本不可用必须手工时,这三条是硬要求:
    - 读取 assets/annotation.html,把其中 <style> 段放入目标 <head> 末尾,
    数据区和逻辑 <script> 段一起放在 </body> 之前。
    - 注入定位必须用最后一次出现(如 Python 的 rfind):注释/字符串里可能含有
    </head>、</body> 字样,取第一次出现会把内容注入到错误位置,毁掉整个文件。
    - 两段各加一层注释界标(便于日后升级/移除时精确定位):
    样式段包在 <!-- PRD-CSS-START --> 与 <!-- PRD-CSS-END --> 之间,
    数据区+逻辑段包在 <!-- PRD-LAYER-START --> 与 <!-- PRD-LAYER-END --> 之间。
    - 旧版 → v1.1.0 升级:直接用 inject.js --upgrade;手工时以"含 prd-marker 样式的旧 style 块 +
    id 为 prd-annotations 的数据块 + 其后的旧逻辑 script"整体识别替换。
    - 注入后必须在浏览器中实际打开验证:角标数量 = 标注条数、无 JS 报错,方可交付。
  4. 多页面 / 多级页面原型:若原型有多套页面(菜单切换),先给每套页面的容器加
    data-prd-page=&quot;页面名&quot;(页面间通常互斥显隐),再给每条标注写上对应的 page 字段。
    二级页面用 父/子 命名并嵌在一级容器内(见「多页面 / 多级页面机制」)。
    ⚠️ 页面名只写在页面容器上,菜单项等导航元素不要用 data-prd-page。
    不分页的单页原型不需要这些标记。
  5. 写标注数据:在 JSON 数据区追加条目,字段见 assets/annotation.html 注释:
    anchor 用 CSS 选择器锚定元素(优先 #id、语义化 class;nth-child 兜底);
    page 填所属页面名(多页面原型;多级页面写 父/子)。
    序号不落盘,且每个页面各自从 1 开始:展示序号 = 该标注在本页面内的顺序;
    新增直接追加到数组末尾;删除、拖拽排序、按位置重排后,各页面内序号自动连续重排,
    AI 不需要维护任何编号字段。
  6. 预写需求说明(body 字段):基于原型上下文预写,结构按
    references/annotation-writing.md 的「结构模板」(需求描述 / 业务规则 / 数据来源 /
    业务限制 / 边界与异常),用 &lt;p&gt;&lt;strong&gt;&lt;ul&gt;&lt;li&gt; 基础标签组织;用户可再改。
    回写场景下的预填用同一文件里的「回写预填」小节(含 title 生成与"待确认"约定)。
    这些内容会直接出现在导出的 Word PRD 里,避免写 web 专属样式。
  7. 交付说明:告知用户——读者直接点击角标查看;作者用快捷键唤出工具栏(Mac ⇧A、Windows Alt+A,Mac 上 ⌥A 也可作为备用),
    Ctrl(Mac 为 ⌘)点击角标编辑,编辑器内点「保存」即存内存并关闭弹窗;
    固化需在工具栏点「保存并导出」;管理抽屉底部两个按钮导出 Word PRD。
    标注完要让 AI 写回文件时:工具栏点「⧉ 复制全部JSON」(或只给某一页时点「⧉ 复制本页JSON」),把内容粘贴给 AI 即可
    (比导出文件更快,也不会多出一份副本)。AI 改完文件后刷新浏览器;
    若工具栏冒出「⟲ 恢复缓存(N)」,点旁边的「✕ 丢弃缓存」以文件为准。

修复记录

v1.1.1(2026-09-22 全面回归后修复,共 5 项)

  1. verify.js 浏览器档硬编码 channel:&#x27;chrome&#x27;(跨机器必炸):机器上只有系统 Chromium
    (Linux 的 chromium / Mac 的 Chromium.app)时,--browser/--full 直接抛未捕获异常退出码 1,
    把 L1 结果一起掩盖,还违背「退出码 3 = 浏览器档没跑起来」的语义。修复:launch 多候选降级
    (PRD_CHROMIUM_EXECUTABLE 环境变量 → playwright 自带浏览器 → PATH 上的
    chromium / chromium-browser / google-chrome → 最后才是 channel:&#x27;chrome&#x27;),
    全失败时按原语义退出码 3 并给出提示。候选解析用 which/where 取绝对路径
    (playwright 的 executablePath 不解析 PATH 裸命令名,这是修的时候踩到的第二层)。
  2. inject.js --upgrade 剥旧逻辑段用 lastIndexOf(&#x27;&lt;/script&gt;&#x27;)(数据丢失级):
    无界标旧文件(v1.0 注入)升级时,若组件之后还有原型自己的 &lt;script&gt;,或原型脚本字符串里
    含假 &lt;/script&gt;,会把原型脚本一起剥掉 → 升级后原型功能静默丢失。修复:逻辑段终点改为
    「数据区 &lt;/script&gt; 之后、含 window.PRDAnnotation 的主逻辑 script 的 &lt;/script&gt;」。
  3. Word 导出丢高亮:execCommand(&#x27;hiliteColor&#x27;) 在 Chrome 下把 background-color 写到
    任意元素的 style 上(选中文本含链接时直接落在 &lt;a&gt; 自身),而旧解析只认 &lt;span&gt; 的内联样式
    → 链接上做高亮导出 Word 后静默丢失。修复:把内联样式解析提取为 styleMods(),
    对每个元素都先并入(加粗/斜体/下划线/删除线/高亮/颜色),再叠加标签语义。
  4. 数据区写 &lt;/script&gt; 字面量即坏(健壮性缺口):标注 body 里含 &lt;/script&gt;(粘贴代码示例、
    AI 预填都可能出现)时,&lt;script type=&quot;application/json&quot;&gt; 提前闭合,浏览器加载、
    writeback.js/inject.js/verify.js 的读取全部截断 → 标注整体丢失。修复:所有写数据区的入口
    (组件 exportFile、writeback.js apply、inject.js --upgrade 写回旧数据)统一把
    &lt;/script&gt; 转义为 &lt;\/script&gt;(JSON 合法转义,JSON.parse 自动还原,读取端零改动);
    inject.js 升级后的写回一致性复核改为按「解析后的语义」比较(转义后字符串不再逐字相等)。
  5. localStorage 快照键串味:prd-annos::__snapshot__ 是不带路径的全局单键,同时打开多个
    原型时,后加载的文件会把先加载文件的未固化快照覆盖掉,主键一旦被新编辑覆盖就永远找不回。
    修复:快照键改为 prd-annos::__snapshot__::&lt;路径编码&gt;,与主键一致按文件隔离;
    「✕ 丢弃缓存」同步按路径清理。

复验:全部修复后在同一 3 页面原型上 L1 / L2 / L3 全档 FAIL 0,退出码 0;
另跑 51 项深度测试(读者只读红线、Ctrl 编辑、富文本、管理抽屉拖拽/重排、Word 导出含高亮、
导出 HTML 洁净度、复制信封结构、多级页面、单页原型、overflow 裁剪免疫)、14 项边界测试
(公共区域锚点、拾取 Esc、隐藏角标、恢复缓存、旧版数据兼容、hash 跨端一致)、9 项富文本测试
全部 PASS。版本号统一升至 1.1.1。

v1.1.0-patch2(2026-09-22 拆按钮:「复制本页JSON」/「复制全部JSON」)

原工具栏只有一个「⧉ 复制JSON」(整份)。拆成两个,信封加 scope 区分全量 / 局部。

  1. 组件:⧉ 复制本页JSON(N)(data-op=&quot;copy-page&quot;)+ ⧉ 复制全部JSON(M)(data-op=&quot;copy-all&quot;)。
    条数随切页实时刷新 —— 刷新函数 refreshCopyBtns() 挂在 updateCounter() 里,
    而 updateCounter() 由 syncPageState()(约每 10 帧 ≈160ms)在页面名变化时调用,所以切菜单立刻跟上。
    新增 scopeItems(scope)(取子集)与 viewOf(list)(子集的可读清单;页内序号按子集重算,与全量口径一致)。
    单页原型下"本页"恒等于"全部" → 自动隐藏「本页」按钮,避免两个一模一样的按钮。
  2. writeback.js 防呆(关键,缺了会静默删数据):scope:&quot;page&quot; 是局部信封,
    若仍按"baseHash 一致 ⇒ 可整份覆盖"处理,会把其他页面的标注删掉。三处设防:
    plan 识别后明确禁止覆盖;merge 对局部信封跳过"文件里有、浏览器没有 → 视为删除"的清扫、
    把其他页面条目原样 push 回结果;apply 拿到非数组时统一拦下 —— scope:&quot;page&quot; 直接报错并要求先 merge,
    连 scope:&quot;all&quot; 的全量信封也一并拦住并提示"apply 只吃纯数组、请先跑 merge"。
    (后半条是回写实战中自己撞到才补的:plan 的结束语写着"hash 一致可直接 apply",
    容易被理解成"可以把信封直接喂给 apply",实际 apply 只接受纯数组 —— 现在报错信息会说清楚。)
  3. verify.js L3 第 3 段:从"点 [data-op=&quot;copy&quot;]"改为断言两个按钮的文字与条数
    (⧉ 复制全部JSON(总数);有页面容器时 ⧉ 复制本页JSON(本页数),否则断言「本页」按钮已隐藏),
    再分别点击两个按钮验证 toast / 降级复制框。
  4. 教训(这次差点判错):baseHash 是页面加载那一刻算的,此后页面内新增/编辑标注都不重算。
    所以它恒等于"文件基线",不能用它推断 items 是否完整;也不能靠「恢复缓存」按钮的显隐去反推内存里有几条
    ——那个按钮只证明"缓存 ≠ 内存"。要判断条数,直接读信封的 items.length 和 baseHash 尾部的长度位。

v1.1.0-patch(2026-09-22 装机复验时修的两处脚本缺陷)

  1. inject.js --upgrade 在「空数据区」文件上必然失败(假失败,脚本保护生效、原文件未动):
    判据原为 logic === logicBody,把「替换后没有变化」误当成「没匹配到空数组占位」。
    旧数据区本身就是 [](还没标任何东西)时,dataJson 也是 &#x27;[]&#x27;,替换是无变化操作,
    于是脚本报「写回旧数据失败」退出 —— 任何空数据区的原型都升不了级。
    改为先判占位符是否存在(logicBody.includes(&#x27;[]\n&#x27; + &#x27;&lt;/script&gt;&#x27;)),再做替换。
  2. L2「输入框里大写 A 能正常输入」在 Windows 上恒假 FAIL:
    该用例原先拿平台主键 HOTKEY 去断言「能打出大写 A」—— Mac 的 HOTKEY 是 ⇧A(确实产出字符),
    但 Windows 的 HOTKEY 是 Alt+A,而带 Alt 的组合浏览器本就不产出字符,
    于是 Windows 档永远失败,把正常组件误判成有 bug。
    改为显式按 Shift+A 验字符输入,并追加一项断言「输入框里按 ⇧A 也不唤出工具栏」
    (Mac 上 ⇧A 是主键、必须被输入框挡住;Windows/Linux 上本不该触发)。
  3. .verify-state.json 不再写绝对路径:原来记 path.resolve(target),绝对路径含本机账号名,
    属不该落盘的个人信息(打包发布时本就排除该文件)。现在只记 path.basename(target)。
    该字段仅作审计留痕,指纹判定只读 compHash,改动不影响逻辑。

复验:在 3 页面原型(设备管理 / 设备详情 / 折旧方案)上 L1 / L2 / L3 全档 FAIL 0 / WARN 0,退出码 0。

v1.1.0-mac2(2026-09-21 快捷键分平台)

  1. Mac 端快捷键 Alt+A → ⇧A:外接 Windows 布局键盘插到 Mac 后,⌥Option 落在贴着空格的 Alt 键上,
    与内置键盘位置不同(内置 ⌥ 在 ⌘ 外侧),作者按习惯位置常按错。Shift 键在所有键盘上位置一致,
    故 Mac 主用 ⇧A;Windows/Linux 无此歧义,沿用 Alt+A。三者由 IS_MAC 自动判定。
  2. ⇧A 的两个副作用已挡:① 焦点在 input/textarea/可编辑区时不吃快捷键(inEditableField()),
    否则原型里打不出大写字母 A —— 已用真实断言守住「输入框里 A 能正常输入」;② 编辑弹窗开着时不触发。
  3. Mac 保留 ⌥A 作备用键:macOS 自带中文拼音输入法用 Shift 切中/英,preventDefault() 拦得住字符、
    拦不住输入法状态切换,此时 ⇧A 会抖输入法。⇧A 与 ⌥A 在 Mac 上都能唤出,任一可用。
  4. 工具栏显示当前快捷键:唤出后最左端有一枚灰底等宽小字(⇧A / Alt+A),作者不必记住。
  5. L2 新增 11 项快捷键常驻用例:双平台主/备用键可用性、toggle 开关、输入框不被抢键、
    焦点恢复后可用、「L2 收尾把工具栏复原为隐藏态」——最后这条是实测踩出来的:
    快捷键是 toggle,若上一段把工具栏留在显示态,下一段那次按下会变成「关掉」,L3 会连锁失败。

v1.1.0-mac(2026-09-21 本机实测后修复,均可重新投入使用)

跨平台 / 脚本层(scripts/verify.js):

  1. 硬编码 Windows 路径导致浏览器档静默失效:createRequire(&#x27;C:/Users/&lt;某用户名&gt;/...&#x27;) 在非开发机上必然失败,
    且失败时只打印 SKIP、结论却仍写「L1+L2 通过」——最容易误判交付。
    现改为多候选解析(环境变量 PRD_PLAYWRIGHT_DIR → ~/.workbuddy/binaries/node/workspace → 常规 require),
    并区分新退出码 3(想跑浏览器档但没跑起来),同时结论不再谎报通过。
  2. L2 角标数断言误报:把「当前页条数」直接与可见角标数比较,页面里只要有未打开弹窗的标注就恒失败
    (这类标注角标本就不该渲染)。改为只比对该可见条数,并 INFO 提示「另有 N 条在隐藏容器/弹窗属正常」。
  3. L3 序号断言误报:断言「新建标注必须是本页第 1 个」,只在空页面成立;
    迭代中的文件(页面已有标注)必然假 FAIL —— 而 L3 的用途恰恰是验收正在迭代的文件。
    改为校验「落到本页且序号 = 本页总条数」,并用组件自己的 buildView() 口径计数(弹窗外元素靠 declared page 兜底归属)。

组件层(assets/annotation.html):

  1. Word 导出丢加粗/斜体/下划线:编辑器调 execCommand 且开了 styleWithCSS,
    产出的是 &lt;span style=&quot;font-weight:bold&quot;&gt; 而非 &lt;b&gt;;导出解析只认标签 → 加粗在 Word 里静默消失
    (实测 &lt;w:rPr&gt; 为空)。已补 span 内联样式解析(含 font-weight / font-style / text-decoration / line-through / background-color / color)。

打包 / 发布注意:

  1. zip 用反斜杠做路径分隔符(Windows 打包):macOS 解压会报「appears to use backslashes as path separators」并可能写不出文件,
    中文文件名还会乱码(未置 UTF-8 flag)。发布前建议用 zip -X -r(macOS/Linux)或让打包工具用 / 分隔。

复验结论

修复后在同一份 7 页面 + 10 弹窗原型上:L1 / L2 / L3 全档 FAIL 0,
另跑 24 项独立测试(读者只读红线、快捷键唤出、⌘点击编辑、富文本、弹窗显隐、管理抽屉、Word 导出、导出 HTML 洁净度、回写、组件升级保数据)全部 PASS。本次又补 11 项快捷键用例(双平台主/备用键、toggle、输入框不被抢键、跨阶段状态不污染),随 L2/L3 常驻。

原型演进与组件升级(什么时候才需要重新注入)

一句话:改原型内容不用重新注入,只有升级组件本身才需要。

你做了什么 要重新注入吗 说明
改文案 / 改数据 / 改样式 / 改原型自己的 JS 不用 标注住在数据区里,与原型内容完全独立。实测:注入后把正文改掉,2 条标注与角标都原样还在
新增 / 删除页面 不用 给新页面容器加 data-prd-page=&quot;页面名&quot;,下次打开组件自动识别(实测新增页被识别为第 4 个页面)。不加页面标记的话,新页面上的标注会被归到"无页面",序号与页面筛选会乱
改了被标注元素的 id / class / 结构 不用 但锚点选择器可能失配 → 控制台 [PRD] 未找到锚点: xxx,角标消失(不会错位到别的元素上)。手工改 anchor 即可,不用动组件
组件升级 / 组件修了 bug 要 走下面的升级流程
文件里出现两份数据区 / 组件代码损坏 要 先备份,再按升级流程重做

⚠️ 组件修了 bug,不会自动同步到已经注入过的原型 —— 每个原型文件里都内嵌了一份组件代码,
不是运行时引用。新注入的文件自动带修复;老文件必须跑 inject.js --upgrade 才会换上新组件
(标注数据会保住)。判断某个原型带的是不是修复版:node scripts/verify.js &lt;文件&gt; 看版本与自检结果,
或直接搜组件源码里的特征函数(如修复跨页串页的 isUniqueFor / scopeOf)。

升级流程(保住标注,也保住你对原型的改动)

⚠️ 千万不要"用备份覆盖当前文件再注入"——那会连你后来改的原型内容一起冲掉。
组件代码与标注数据是两回事,升级只换前者。

直接跑 node scripts/inject.js &lt;目标.html&gt; --upgrade 即可(它做的正是下面 7 步)。
手工时照此执行:

  1. 备份当前文件
  2. 取出数据区 JSON:id=&quot;prd-annotations&quot; 两个标签之间的内容,原样留着
  3. 按界标剥掉旧组件:&lt;!-- PRD-CSS-START --&gt;…&lt;!-- PRD-CSS-END --&gt;、
    &lt;!-- PRD-LAYER-START --&gt;…&lt;!-- PRD-LAYER-END --&gt;
  4. 没有界标的老文件用特征定位:样式块找含 .prd-marker 的那个 &lt;style&gt;;
    逻辑段从 &lt;script type=&quot;application/json&quot; id=&quot;prd-annotations&quot;&gt; 起到组件主逻辑 script
    (含 window.PRDAnnotation 的那个)的 &lt;/script&gt;
    结束。
    ⚠️ 别用 indexOf(&#x27;&lt;style&gt;&#x27;)——那会命中原型自己的样式块,把原型样式删掉;
    ⚠️ 也别用整份文件的 lastIndexOf(&#x27;&lt;/script&gt;&#x27;)(v1.1.0 的旧做法)——组件之后若还有
    原型自己的 &lt;script&gt;(或原型字符串里的假 &lt;/script&gt;),会被一起剥掉,升级后原型功能静默丢失。
    inject.js --upgrade 已按此实现,手工时请勿倒退。
  5. 注入新版组件(样式 → &lt;/head&gt; 前;数据区+逻辑 → &lt;/body&gt; 前),并补上界标
  6. 把第 2 步取出的 JSON 写回数据区
  7. 浏览器打开验证:条数一致、标题内容一致、无 JS 报错

实测:升级组件后 2 条标注完整保留,原型自己新增的页面也没被冲掉。

耗时实测(2026-09-21 本机,3 次中位数)

环节 自身耗时 端到端(含 node 启动 ~280ms)
注入(读文件 + 字符串替换 + 写文件) 28 ms 298 ms
L1 静态验证 5 ms 0.3 s
L2 浏览器巡检(打开 + DOM 断言) — 4.5 s
L3 完整模拟(逐页切换 + 真实点击) — 15 s
常规一次「注入 + L1 + L2」 — 约 5 s
组件改过后「注入 + L1 + L3」 — 约 15 s

让人觉得久的是另外两件事,都不是注入本身:

  1. 发现 bug → 修 → 重跑的循环(今天大部分时间花在这);
  2. 我跑的完整模拟:新建标注 → 保存 → 切页 → 复制剪贴板 → 等 toast,几十秒。

分级策略:
- 只改标注数据(回写)→ L1 即可,不用开浏览器;
- 常规注入 / 原型改动 → L1 + L2,约 5 秒;
- 组件代码改过 → 必须 L3(verify.js 会以退出码 2 主动提醒,不用靠记忆);
- 首次给某个结构复杂的原型注入 → 建议直接 L3。

前期不稳定时,不要为了省时间停在 L1/L2 —— 那两档抓不到交互与跨页问题。
省时间的正确做法是让 L3 自动化(现在就是),而不是跳过它。

多页面 / 多级页面机制

  • 标记页面:给每套页面的容器加 data-prd-page=&quot;页面名&quot;(如菜单切换的内容区),
    切换时各容器互斥显隐。组件自动把「当前可见的那个」认作当前页。
    ⚠️ data-prd-page 只加在页面容器上——侧边栏菜单项等导航元素请换个属性
    (如 data-prd-nav)。若把页面名写在菜单上,菜单会被当成页面容器:
    既干扰「当前页」判定,也会在按属性做互斥显隐时把菜单一起隐藏掉。
  • 多级页面(二级/三级):页面名用 父/子 表达层级,例如
    data-prd-page=&quot;设备管理/设备详情&quot;,并让二级容器嵌在一级容器内部;
    进入二级页时隐藏一级页里的列表内容(由原型的切换脚本负责)。
    此时两个容器同时可见,组件取层级最深(最具体)的那个作为当前页,
    因此菜单高亮、页面级导出都能落在正确的二级页面上。
  • 菜单高亮:建议切换脚本用前缀匹配(name === nav || name.startsWith(nav + &#x27;/&#x27;)),
    这样停在二级页面时父级菜单保持高亮。
  • 标注归属(决定序号与筛选):组件按「锚点元素落在哪个页面容器内」判定归属
    (多级取最深容器,与角标跟着元素显隐的表现完全一致);锚点在容器外(页头、侧边栏
    等公共区域)或锚点已失效时,退回该标注 page 字段(创建时的当前页)。
    用「点击新建」建的标注会自动写入当前页。
  • 序号每页独立:每个页面的角标都从 1 开始编号,不是整个文件 1..N;
    管理抽屉的行号、角标数字、读者弹层标题、Word PRD 里的编号全部用同一套页面内序号。
  • 工具栏计数与上一个/下一个:只在当前页面的标注里循环(如「本页 3 条」「2 / 3」),
    不会跳到别的页面去。
  • 管理抽屉默认只看当前页:抽屉顶部「显示」分段控件默认停在「本页面」,
    可切到「全部页面」查看/整理整个文件(此时行内会附带页面名,便于区分)。
  • 角标自然联动:不在当前页面的锚点元素变为不可见 → 其角标自动隐藏,切回来自动出现。
    进入二级页面时,一级页面里的标注会随其容器一起隐藏。
  • 页头/侧边栏等公共区域(不在任何 data-prd-page 容器内)的标注,在所有页面都显示。
  • 单页原型:没有任何 data-prd-page 时不分页,全部标注算同一页,序号就是 1..N,
    抽屉不显示范围切换,「导出本页面 PRD」与「导出文件 PRD」结果相同。
  • 按位置重排(告别手排):按页面分组各自排序(页面块不会互相穿插),
    组内不可见元素(未打开的弹窗、别的视图)排到本页末尾,不参与视觉排序。

Word PRD 导出

管理抽屉底部两个按钮,导出为真正的 .docx(组件内置手写 ZIP + OOXML,零依赖):

  • 导出本页面 PRD:仅当前页面的标注,文件名 &lt;原型名&gt;-&lt;页面名&gt;-PRD.docx
  • 导出文件 PRD:全部标注,按页面分组,文件名 &lt;原型名&gt;-PRD.docx
  • 排版:一级标题 = 页面名称;二级标题 = 角标序号.角标标题;正文 = 该标注的需求说明。
    序号与页面上看到的角标完全一致(页面内序号,每页从 1 开始),每次导出按当前数组顺序重算。
  • 富文本映射:加粗/斜体/下划线/删除线保留;行内代码用等宽字体;链接显示为蓝色下划线;
    无序列表加 • 前缀、有序列表加 1. 2. 前缀并缩进;引用块用 Quote 样式。
    Word 的导航窗格可直接按标题层级跳转。

锚定与渲染机制

  • 每条标注存一个 anchor CSS 选择器;角标渲染在独立固定图层(position fixed,
    不挂在锚点元素下),每帧按锚点元素的视口位置重摆——
  • 锚点元素移动/变宽变窄 → 角标自动跟随,始终贴其右上角(pos 可选 tr/tl/bl/br)
  • 不受任何容器 overflow 裁剪(旧版锚定表头被表格滚动容器裁半截的问题已根治)
  • 锚点元素 display:none(如未打开的弹窗)→ 角标自动隐藏,元素显示后自动出现
  • 元素被删除或选择器失配 → 渲染层打印 [PRD] 未找到锚点: xxx 并跳过该条,
    不会错位到其他元素上,需人工确认新锚点
  • 相邻角标自动避让:优先向元素内部横向错开(最多 52px),同元素内仍冲突才向上错开
  • 多页面原型的选择器会自动带页面作用域(v1.1.0):各页结构常常雷同(都有 table &gt; thead &gt; th 之类),
    不带作用域的选择器会命中别的页面 → 标在 A 页、角标却归到 B 页(串页)。
    组件生成后做双重唯一性校验(匹配数 = 1 且确实命中该元素),
    不唯一就向上找最近的 id 或 data-prd-page 容器加前缀(祖先链最多扫 10 层);
    仍不唯一才原样返回。手工写 anchor 时同样建议以页面容器 id 开头。
  • 原型结构大改后:重跑一次注入(幂等,只重建角标层、不动原型内容),
    必要时把失配条目的 anchor 更新为新结构的选择器

历史经验(旧版遗留文件排查时仍适用)

v1.1.0 之前旧版的角标是锚点元素的子节点、absolute 定位,会被带 overflow 的容器
(典型:表格滚动容器)裁掉上半截。若必须维护旧版遗留文件:把 anchor 指向被裁剪
元素内层的文字节点容器(没有包裹元素时先注入 span 再锚定)。
排查用 document.elementFromPoint 在角标矩形多点探测命中链,再对比
getBoundingClientRect 与各层祖先矩形,区分"溢出裁剪"还是"层级遮挡"。

红线(必须遵守)

  • 读者模式零编辑入口:不按 Ctrl 点击时不出现任何编辑 UI;工具栏默认隐藏,
    仅快捷键唤出(Mac ⇧A / Windows Alt+A),且编辑器打开、或焦点在输入框时不触发
  • 角标无动效:角标(prd-marker)不加任何动画、脉冲、hover 缩放;弹层即点即显
    (管理抽屉/工具栏的滑入过渡属界面外壳,不受此限)
  • 运行时节点必须全部带 data-prd-runtime=&quot;1&quot;,导出时从克隆文档中整体剥离——
    否则序列化活 DOM 会把角标/工具栏固化进文件,重开一次叠一层(旧版已踩坑);
    组件内置的「保存并导出」已按此实现,导出后的文件必须验证不含 prd-marker、
    prd-tb、prd-mgr、prd-editor 等运行时节点
  • 修改只在内存:交付前必须在工具栏点「保存并导出」。
    localStorage 镜像只是「误关找回 / AI 读回」的中转,不是交付载体
  • 读回优先走镜像:用户说「标好了,你读回来」时,先请他点工具栏「⧉ 复制全部JSON」(首选);
    他不方便就按「从浏览器读回标注」读 localStorage 兜底;
    只有镜像里确实没有该文件的数据(例如他标的是没装本组件的旧文件)才另想办法,别一上来就要求用户导出
  • 组件代码改过之后必须跑 verify.js --full,不能停在静态检查;脚本会用组件指纹自动提醒(退出码 2)
  • 注入 / 升级 / 回写优先走 scripts/ 下的脚本:重复注入、误删原型样式、覆盖已预填内容、
    忘记备份、写完不复核这些坑,脚本里都是硬拒绝或自动规避;手工只在脚本不可用时使用
  • 改完文件必须复核:写盘后用 readFileSync 断言关键字符串确实存在,别把"工具返回成功"当成已生效
  • 回写必须先回显清单再落盘:把每条的「页面 / 序号 / 锚点 / 状态(新增·更新·预填·保留·删除)」
    连同统计列给用户,确认后才写文件。禁止"拿到信封就闷头写"
  • 回写必须写前自动备份:&lt;原名&gt;.bak-&lt;时间戳&gt;.html 放同目录,备份成功才动原文件
  • 回写只替换数据区:只允许改 &lt;script type=&quot;application/json&quot; id=&quot;prd-annotations&quot;&gt; 两个标签之间的
    内容,文件其余字节一个都不动;不写 id/number 字段
  • 回写完必须提醒刷新,且警告别点「⟲ 恢复缓存」:AI 改的是磁盘文件;
    刷新后若出现「⟲ 恢复缓存(N)」,正确操作是「✕ 丢弃缓存」,点恢复会用旧缓存盖掉刚写回的内容
  • 预填只填空角标:title 与 body 去标签后都为空才算空;已有内容的角标一字不动
  • 预填不编造:原型里看不出来的接口名、表字段、枚举值一律写「待确认」
  • 导出 HTML 只有一处入口:工具栏(管理抽屉里的旧导出按钮已移除,改为两个 Word 导出)
  • 链接一律 target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; 并高亮可点
  • 序号页面内连续:每个页面的角标都从 1 开始,删除/排序后页内自动 1..N,无需人工维护;
    多页面原型里不要按"全文件序号"写文案(如"第 7 条"),编号只在页面内有意义
  • 零外部依赖:不引入任何 CDN/外部库、不写任何外部文件,单文件可离线打开
  • 标注内容只写需求说明,不写入敏感业务数据、内部系统地址、人名

排障速查

  • 数据解析失败 → 控制台 [PRD] 标注数据解析失败
  • 锚点失配 → 控制台 [PRD] 未找到锚点: &lt;selector&gt;(每条只报一次)
  • 角标跑到别的页面去了 → 锚点全局不唯一(多页面原型各页结构雷同)。组件已有作用域兜底;
    仍错就把 anchor 手工改成以页面容器 id 开头(如 #page-scheme &gt; ...)
  • 注入后标注总是不生效 / 一直为空 → 文件里可能有两份数据区(重复注入导致),
    getElementById 只取第一个,新写的数据全部看不到。
    注入前必须先查是否有 id=&quot;prd-annotations&quot;,有则只改 JSON,绝不重复注入
  • 读回时找不到目标文件的键 → 看 prd-annos::__last__ 的 path,用户很可能标的是另一个文件(或那个文件没装本组件)
  • 打开页面后角标是旧内容 → 不是 bug:加载时绝不自动套用缓存(防止旧缓存盖住文件里刚更新的数据);
    工具栏上会出现「⟲ 恢复缓存(N)」,点它才把缓存取回内存
  • 误关页面丢了未固化的标注 → 同上,点「⟲ 恢复缓存」;若主键已被新编辑覆盖,从 prd-annos::__snapshot__::&lt;路径&gt; 里找
  • AI 回写后页面还是旧内容 → 正常:AI 改的是磁盘文件。刷新页面;
    刷新后工具栏出现「⟲ 恢复缓存(N)」= 浏览器缓存是旧的 → 点「✕ 丢弃缓存」,不要点恢复
  • 复制 JSON 没反应 → 组件会退化为弹出文本框让你手动全选复制(Ctrl/⌘+A 再 Ctrl/⌘+C);
    也可能是 file 协议下剪贴板权限被拦,换手动复制即可,不影响数据本身
  • 信封 file 字段对不上磁盘文件 → 用户可能标的是另一个副本;别猜,直接问他要路径
  • 预填后角标变多/重复 → 检查是不是把运行时 DOM 序列化进了数据区,或组件被重复注入
    (组件有运行时哨兵,重复注入会自动 return,多半是文件被手工复制了一份组件代码)
  • inject.js 报「自检失败,未写入文件」 → 这是保护生效(原文件一字未动)。
    按它打印的失败项排查:数据区恰好 1 份 / 起止界标 / 样式段内含 .prd-marker / 逻辑段内含 window.PRDAnnotation / 文件应变大
  • inject.js 说「该文件已包含标注层」 → 不是报错。要换组件加 --upgrade;
    只想改标注内容就直接改数据区 JSON,或用 writeback.js
  • 旧版数据兼容:deleted 为真的条目自动剔除;带数字 id 的按升序作初始顺序
  • 需要在页面里程序化获取某元素的选择器时,可用 window.PRDAnnotation.getSelector(el)
    (拾取模式内部同款逻辑);window.PRDAnnotation.items 为当前内存数据(只读用途),
    window.PRDAnnotation.persistNow() 可手动触发一次镜像写入(调试用)

常见问题

5 问
答案由本站依据条目字段整理
html原型 PRD 标注怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_fddbdcfc/prd-annotation。安装方式为未标注。装完刷新技能列表或重开客户端。

html原型 PRD 标注支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

html原型 PRD 标注权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

html原型 PRD 标注收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

html原型 PRD 标注用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 2026-11-06。