# HTML Mockup

> 一页 HTML 讲清一件事,写成自包含单文件放原型目录、走 8008 公网链接给人看。两种用法:(A) 需求原型——把一个需求的几个界面 + 规则说明 + 状态流转拼成一页,用来评审、拍板、贴 issue;(B) 改动汇报——把一次任务的全部代码改动讲成大白话:改了什么、动了哪些表字段、改了哪些原有业务规则、影响哪些业务点、怎么回滚。当用户说 '出个 X 的原型 / 效果图' / '画一下 X 长什么样' / '把这个需求画出来给我看' / '原型加一版 / 改一下原型' / '给 PM 看看 X 的样子',或做需求评审要配图时,走 A。当用户说 '总结一下当前任务的所有改动' / '这次改了什么' / '把改动讲清楚给我看' / '出个变更说明 / 交付报告 / 评审报告' / '动了哪些表 / 哪些业务规则' 时,走 B。多界面、要写说明文字、要画流转图或对比表的一律走这条;只有需要真能点、要对齐生产端真实组件样式、要入 git 给团队 review 时才不走这条。

- Skill: `zoneasetech/html-mockup` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add zoneasetech/html-mockup`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zoneasetech/html-mockup/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: ZonEaseTech (https://skillmd.com/u/zoneasetech)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zoneasetech/html-mockup

---


# 单页 HTML:需求原型 / 改动汇报

## 这是什么

**一页 HTML 讲清一件事。**不是界面图集,是「说明书 + 图」混排:该画的画出来、规则写旁边、这次改了啥标绿色。对方打开一个链接就能看懂、能评审、能拍板。

两种用法共用同一套地基(单文件、原型目录、8008 公网链接、`?v=N`、颜色语义、积木库),只是骨架不同:

| 用法 | 讲的是 | 读者 | 骨架在 |
|---|---|---|---|
| **A. 需求原型** | 「要做成什么样」 | PM / 老板 | 本文件 §5 |
| **B. 改动汇报** | 「已经做成了什么样,动了什么,谁受影响」 | 你自己 / 评审人 / PM | `references/change-report.md` |

`~/www` 下已经这么出过 29 个(2026-07 起),是团队出原型的主力路子。

## 先选路子:这个 skill 还是写真代码

| 情况 | 走哪个 |
|---|---|
| 讲需求、评审、拍板、贴 issue | **本 skill**(默认) |
| 多个界面 + 规则说明 + 流转图 / 对比表混排 | **本 skill** |
| 改前改后对比、方案 A/B 对比 | **本 skill** |
| 汇报一次任务改了什么、影响什么 | **本 skill**(模式 B) |
| 要真能点、能跳、走一遍主流程 | 写真代码的可交互原型 |
| 要 1:1 对齐生产端真实组件样式 | 写真代码的可交互原型 |
| 要入 git 让前端同事接着改 | 写真代码的可交互原型 |

拿不准就用本 skill:半小时出一版,改起来也快。可交互原型要写框架代码、编译、部署、提交分支,一版下来成本高一个量级。

## 环境自举(第一次用,或链接打不开时,先跑这个)

```bash
bash ~/.claude/skills/html-mockup/assets/bootstrap.sh
```

**它会先判断这台要不要起 8008**:

| 机器类型 | 起 8008 吗 | 原型走哪 |
|---|---|---|
| Coder 工作区(有公网端口转发) | 起 | `preview_html` 与 8008 按 §4.5 二选一 |
| **mac 开发机 / 其它没有转发通道的机器** | **不起** | **一律 `preview_html`** —— §4.5 的 ①② 在这台本来就做不到 |

没有转发通道的机器上,起了服务也只有同网段能看,还白占端口;积木库和模板**直接读 `assets/` 下的文件**即可,不用起服务。真要局域网自用:`PROTO_SERVE=1 bash .../bootstrap.sh`。

起服务时它做五件事,幂等,重复跑安全:

1. 建原型根目录(默认 `~/www`,已存在或已是软链则沿用)
2. 装 / 更新 `_template/`(起手模板)和 `_blocks/`(积木库)
3. 起 8008 静态服务(`assets/serve.py`,零依赖 stdlib,自带 no-cache)
4. 本地 `curl` 自检
5. **打印本机真实的 BASE_URL**,末行形如 `BASE_URL=https://8008--main--<workspace>--<owner>.<你的 Coder 域名>/`

**永远用它打印的 BASE_URL,不要背域名。**每台机器的 workspace / owner 不同,写死的域名到别的机器上就是死链。脚本从 `$VSCODE_PROXY_URI` 推导,推导不出来才回落到 `CODER_WORKSPACE_*` 拼接。

> **给外部人(PM)之前必须做一次**:Coder 端口转发默认 owner-only,别人打开是登录页(bootstrap 会探测并报出返回码,非 200 就是没放开)。工作区里的 `coder` 是精简 agent 版,没有 `port-share` 子命令,只能去面板改:`<你的 Coder 地址>/@<owner>/<workspace>` → Ports → 8008 → Share → **Public**。设一次就长期有效。

## 硬约束(环境定死的,不是风格问题)

- **单文件、零外部依赖**。不引 CDN、不引 Google Fonts、不外链图片、不引 mermaid.js。端口转发带 `Cross-Origin-Embedder-Policy: require-corp`,跨源资源会被浏览器直接拦掉;国内网络加载 CDN 也会白屏。字体一律用系统字体栈(模板里已有),图标用 Unicode 字符(✓ ✕ › ▼ ☑ ⓘ ↻ ① ★)。
- **静态,不写交互**。这是给人看的说明书,不是能点的 app。要能点就得写真代码。
- **原型目录不入 git,是孤本**。机器重装、目录清理就没了 —— 需要长期留存的结论,写进 issue / PR / 知识库,别指望原型本身。
- **产物形态是 `preview_html` 与 8008 二选一**,判定见 §4.5。默认 `preview_html`,只在它结构上做不到时才走 8008。

## 4.5 产物形态:`preview_html` 还是 8008(自动二选一)

**默认 `preview_html`。**它零部署、即时可见 —— 不用起服务、不用设 public、不用带 `?v=N`、不用等对方点链接。

**只在下面四种情况让位给 8008。**前三条是 `preview_html` **结构上做不到**的事,与内容长短无关;第四条才是体量问题:

| 情况 | 为什么 preview_html 不行 |
|---|---|
| ① 要给第三方看(PM、同事、贴进 issue) | 它只存在于当前对话,别人打不开 |
| ② 要留存、要改版迭代 | 它是一次性的:没有链接、没有 `?v=N`、下一版无处可覆盖 |
| ③ 已经发过,用户说"只看到一半" | 确认被截断了,立刻改走 8008 重发链接 |
| ④ 内容确实很大(经验值 **≈50KB 以上**) | 大概率触发截断,先走 8008 省一次来回 |

**「短内容才能用 preview_html」这条已废除。**中等篇幅的说明、单张界面帧、几节内容、单张 mermaid 黑底卡,直接 `preview_html`,不要因为"看起来有点长"就预先绕道 8008。

判定顺序:先看 ①②(意图),再看 ④(体量);③ 是事后信号。**①② 命中就直接 8008,不必先试 preview_html。**

> ⚠️ 50KB 是**经验估计,不是实测值** —— skill 坑表只记了"出长原型被截断过",没记在多大体量翻的车。真遇到截断时记下当时的字节数,把这里的数字换成实测值。参考:模板 9KB、积木库 22KB、26 文件改动报告 59KB。

## 5. 模式 A:需求原型

### A1. 定名 + 查有没有旧的

```bash
ls -d ~/www/*/ | rg -i "关键词"
```

**同一个需求已有原型就改那一份,别新建。** 对方手里的链接是旧的,新建一个他打不开。
真要废弃合并,在旧目录留一个 meta refresh 跳转页(见 §8)。

新建时目录名用功能英文短名(`cost_recalc`、`brand_purchase_min_amount`、`invoice_archive`),不带版本号、不带 issue 号。

### A2. 拉真实素材,别凭印象画

动手前必须查到位,不然画出来的东西对方一眼看出不是他那个系统:

- issue 正文和评论的原话(`gh issue view`)
- 现在这个界面长什么样 —— 去前端仓库找对应页面,或看对方给的截图
- 涉及的字段、状态、口径 —— 去后端仓库核实,别编字段名

术语按系统真名:**「物品」不叫「商品」**,「外部采购 / 调拨 / 品牌采购」用系统里的原名。

### A3. 拼页面

**必须从 `~/www/_template/index.html` 复制起手。**

> ⚠️ **不要照着旧页面抄 CSS。**旧页面带着它生成那天的字号、宽度、缺失的 `.bleed` —— 抄过来就把过时样式一起继承了,而且**页面看着正常,没人会发现**。实测:模板已 16px,照旧页抄出来仍是 14px。
> 模板是唯一起点;旧页面只能抄**内容结构**(哪些节、什么顺序),不抄 `<style>`。

骨架固定这几块:

1. **页头** — 功能名 + 一句话说清这版讲什么 + 日期
2. **sticky 锚点导航** — 界面超过 3 个必加,读者靠它跳
3. **「本次更新」绿框** — 第二版起必加,列这次改了几条,每条带锚点跳过去
4. **分节** — 一节一件事,`.st-h` 编号标题 + `<small>`「从哪进 / 什么时候看到」,复杂的节再加一句 `.lead` 副标题定调
5. **说明块** — 规则写 `.hint` 块里,别塞进界面冒充 UI 文案

节里放什么,从 `~/www/_blocks/index.html` 抄。**先按「要表达什么」选积木,别一上来就画界面** —— 很多需求用流转图 / 矩阵表 / 时间线讲比堆界面清楚得多。积木索引见 `references/blocks.md`。

### A4. 本地验

```bash
# 有 mermaid 卡就先渲染,再验(顺序不能反,渲染会改文件)
python3 ~/.claude/skills/html-mockup/assets/render-mermaid.py <目录>/index.html
# 横向溢出:页面上看不出来,不量就发现不了
python3 ~/.claude/skills/html-mockup/assets/check-overflow.py <目录>/index.html
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8008/<feature>/
```

不是 200 就重跑 `bootstrap.sh`(Coder 工作区重启后服务会掉)。
`grep -c '<svg' <目录>/index.html` 要等于 `.mmd` 卡片数,不等就是有图没渲出来。
`check-overflow.py` 报撑破就照 §7「宽度」给那几个元素加 `.bleed`。

**改已有页面时,先把它对齐到当前模板**(幂等,重复跑安全):

```bash
python3 ~/.claude/skills/html-mockup/assets/align-page.py <目录>/index.html
```

它补两样:字号阶梯(对齐 16px 正文)、布局兜底 CSS(`.bleed` / `.mmd` 横滚 / `code` 断行 / `table.cmp` 横滚 / `dd dt` 的 `min-width:0`)。**内联 SVG 一律跳过并断言前后一致。**
跑完**必须再跑一次 `check-overflow.py`** —— 字号变大会把并排双栏、宽表撑得更宽。

### A5. 交付链接

```
<BASE_URL><feature>/?v=N
```

- **必须给 bootstrap 打印的公网地址**,不能给 `localhost:8008` 或内网 IP —— 对方在自己电脑上打不开。
- **改过就带 `?v=N` 破缓存**(每改一版 N+1)。不带的话对方看到的还是旧的。
- 能直接给锚点:`.../cost_recalc/?v=3#s4` 跳到第 4 个界面。
- 交付前 `curl` 公网地址确认 200 再发。

### 交付话术:**永远把完整 URL 明文写出来**

**只要有地址,就把地址完整贴出来**,不要用 `[文字](url)` 把它藏在链接文字后面 —— 那样读者没法复制、没法转发、也不知道自己要打开的是哪台机器的哪个原型。裸 URL 在 markdown 里本来就是可点的,明文和可点两者不冲突。

```
✅ 正确:地址明文,一眼能看见、能复制
   跨店分页改动汇报:
   https://8008--main--myws--alice.coder.example.com/cost_recalc/?v=2
   本次更新 3 条,顶上绿框里,点条目能跳到对应界面

❌ 错误:地址被藏起来了
   [打开改动汇报](https://8008--main--...)
   点这里查看原型
```

别嵌 iframe、别用 `preview_html` 再包一层。锚点也直接写进 URL:`...?v=3#s4`。


## 6. 模式 B:改动汇报

**触发**:「总结一下当前任务的所有改动」「这次改了什么」「出个变更说明」「动了哪些表 / 业务规则」。

**完整执行手册在 `references/change-report.md`,做之前必须读。**这里只放不能违反的铁律:

1. **不漏。**以 `git diff --stat` 的文件清单为唯一底账,**每个文件都要出现在 S1 的 ASCII 树里**(带 ±行数和一句话注释)。收尾跑覆盖率自检:底账文件数 == 树里出现的文件数,差一个都要补。
2. **逐行看,不看摘要。**每一行改动都要回答「它动了哪条原有业务规则」。没动规则的行归为「无规则影响」,也要显式说出来,不能沉默跳过。
3. **分派多个代理并行采集**,六条线同时跑(diff 清点 / 数据库字段 / 业务规则逐行 / 影响面与调用方 / 文案与 i18n / 测试与风险)。手册里有现成的分工提示词。
4. **大白话优先。**每一节开头先用一句人话说结论,再上表格和图。别让读者自己从 diff 里悟。
5. **必须有的四块内容**:动了哪些**表字段**、改了哪些**原有业务规则**(原规则 → 新规则 → 触发条件 → 谁受影响)、影响哪些**业务点**、**回滚**怎么做。
5.5 **每条改动都要落到「页面 → 操作 → 结果」**,并且**把页面画出来**:
   - **页面**:用户实际看到的那一屏,用积木库的界面帧 / 后台外壳 / 列表页 / 小票画出来 —— 不是描述,是画。
   - **操作**:在这一屏上做什么(点哪个按钮、填什么、选哪个筛选、翻到第几页)。
   - **结果**:改前会怎样 → 改后会怎样。界面怎么变、数据怎么变、谁什么时候能感知到。
   用户侧真的无感知时,明确写一句「用户无感知,仅 xxx 变化」,**不要跳过这一节**。
   **Why**:只写"改了 xxx 函数的判断条件",读者无法验收、测试无法复现;落到页面和操作,PM 能验、测试能复现,你自己也会发现"这个改动用户其实碰不到"。
6. **图的分工**:结构和清单用 **ASCII**(见 §7),状态流转和调用链用 **mermaid 黑底**(见 §7),界面变化用积木库的双栏对照。

## 7. 视觉规范

### 字号:正文 16px 起,别更小

模板的字号阶梯(2026-08-16 连上调两档,原来 14px 正文明显偏小):

| 用途 | 字号 |
|---|---|
| 正文 `body` | **16px** |
| 节标题 `.st-h` | 18px |
| 说明块 `.say` / `.hint` / `.lead` / 规则卡正文 | 15px |
| 表格 `td` / `th` | 14px |
| 行内 `code`、小标签 | 12.5~13px |
| 辅助文字(`small`、`.stats .l`) | 11~12px |

**中文比拉丁字母更吃字号**,13~14px 的说明块在 900px 栏里读着累。加大字号还顺带把行长拉回来(14px 时 62 字/行 → 16px 时 **54 字/行**)。

**下限就是 16px,不要再往回调。**要更紧凑就减内容,不是缩字。

> ⚠️ **改字号时必须跳过内联 SVG。**mermaid 渲出来的 SVG 里节点框尺寸是渲染那一刻烤死的,字号一改文字就顶出框被裁(实测「700 个文件提交掉」变成「提交挂」、「canary 跑迁移演练」被切掉两字)。批量调字号要先把 `<svg>…</svg>` 抠成占位符,改完再放回去。图要变大就重跑 `render-mermaid.py`,不要动 SVG 里的 CSS。

### 宽度:按页面类型选档,同一页内不许出现断层

**先按页面类型定正文栏,再谈宽内容。**

| 页面类型 | 正文栏 | 判断依据 |
|---|---|---|
| **模式 A 需求原型**(界面帧 + 说明为主) | `.wrap` **900px** | 主体是散文和单个界面帧,16px 下 54 汉字/行 |
| **模式 B 改动汇报 / 调查报告**(规则卡 + 表格 + 并排双栏为主) | `.wrap rep` **自适应** `clamp(900px, calc(100vw - 64px), 1920px)` | 主体不是散文,900 装不下;跟着屏幕长,不浪费空间 |
| 画完整后台外壳的原型 | `.wrap desk` 1180px | 后台界面本身就宽 |

```html
<div class="wrap rep">   <!-- 模式 B 一律加 rep -->
```

**报告档是自适应的,所以 `.bleed` 在它里面不再挣脱**(`width:100%`)—— 外壳已经跟着屏幕长了,再挣脱只会让左边缘错位 49px。`.bleed` 只在**模式 A 的 900 栏**里才需要挣脱(上限 1440px)。

**文字块单独收住行长**:`.rule dd` / `.uv` / `.say` / `.hint` / `.lead` 加 `max-width:52em`(≈48 汉字/行)。**这是自适应能成立的关键** —— 不收的话 2560 屏上正文会变成 87 汉字/行。收完的效果是「正文一列、图表更宽」,左边缘全部对齐,右边缘错开,读起来像正常文档。

实测同一页在三种屏幕上:正文栏 1216 / 1504 / 1920px,并排双栏 599 / 663 / 871px 各一屏,**文字始终 48 汉字/行**。

> ⚠️ **别单独优化每一块,要看它们上下摞在一起是什么样。**
>
> **这是翻过车的**:按行长把正文栏定 900、按装得下把 `.bleed` 定 1400,每块单独看都对,**摞在同一页就是断的** —— 规则卡文字挤在 701px,紧贴的双栏铺开 1400px,左侧 250px 死白。**行长规则管的是段落散文**,而这类页面主体是规则卡、表格、代码引用,散文只占一小部分,用错了地方。
>
> **判断标准:同一页里最窄的内容块和最宽的内容块,差距不该超过一档**(现在是 1280 vs 1440,差 160px,看不出来)。

**收尾必跑**(页面上看不出来,不量就发现不了):

```bash
python3 ~/.claude/skills/html-mockup/assets/check-overflow.py <目录>/index.html
```

多视口检测横向溢出。模板已内置这几条兜底,别删:

| 兜底 | 挡住什么 |
|---|---|
| `.wrap{overflow-wrap:break-word}` | 裸长标识符(没包 `<code>`)撑破整页 |
| `code{overflow-wrap:anywhere}` | 长表名 / 带下划线的字段名 |
| `@media(max-width:900px){.wrap table{display:block;overflow-x:auto}}` | 窄屏下宽表 —— 逐个枚举 `.cmp`/`.mx`/`.dt` 挡不住 |
| `.wrap dd,.wrap dt,.wrap dl > *{min-width:0}` | grid 子项的 `min-width:auto` 不肯收缩到轨道宽度 |
| `.mmd{overflow-x:auto}` | 宽图没容器兜 |



### 架构图要更高规格时（可选）

改动汇报里的配图用 mermaid 就够。**只有「图本身就是交付物」**（架构评审、给外部看的系统地图）才值得升级到 [archify](https://github.com/tt-a1i/archify) —— 它渲染前用 schema + 布局约束校验,视觉高一档,但**只有 `sequence` 几乎零成本,`workflow` 中文塞不进去**。

用之前读 `references/archify.md`(四种图型的实测成本、`boundary` 的坑、合并 tab 页的做法、源码引用怎么挂)。

### 颜色是有意义的,别乱用

| 用法 | 颜色 |
|---|---|
| 现状、已有的 | 灰白常规样式 |
| 这次新增 / 改的 | 绿色 `#16a34a`(`.tag-new` 徽标 / `.newf` 绿虚线框) |
| 主操作按钮 | 琥珀 `#f59e0b`(默认主色,按你自家产品调) |
| 编号、强调 | 橙 `#ea580c` |
| 警告 / 失败 / 破坏性 | 红 `#dc2626` |
| 删除掉的 | 红色 + 删除线 |

读者看原型第一件事是分清「哪些是现在就有的、哪些是这次要加的」。绿色标记就是干这个的。

### ASCII 图:只用半角

中文是双宽字符,和半角混排必然错位,浏览器字体一换更歪。

- **框线里只放半角**:表名、字段名、文件名、类名本来就是英文,直接用。
- **中文说明放框外**,或用框内右侧 `# 注释` 形式单独成列,不参与对齐。
- ASCII 块必须包在 `<pre class="ascii">` 里(模板已带等宽字体 + 深灰底 + 横向滚动),不要放进普通 `<div>`。
- 树用 `├─ └─ │`,箭头用 `→ ←`,层级缩进 2 空格。

```
order_pay
  ├─ pay_amount        decimal(10,2)   unchanged
  ├─ discount_amount   decimal(10,2)   unchanged
  └─ coupon_deduct     decimal(10,2)   NEW      # 券抵扣额,新增
```

### mermaid:唯一用黑底的地方,而且必须渲染成图

页面整体是浅色,**只有 mermaid 图用黑底**,让它在一片白里一眼跳出来。

**写完源码卡不算完 —— 必须跑渲染器。**只贴源码等于没画:手机上是一坨代码,读者不会自己粘到别处渲染。

每张图的源码第一行必须带这段主题(渲染出来才是黑底):

```
%%{init:{'theme':'base','themeVariables':{'background':'#0b0f14','primaryColor':'#111827','primaryTextColor':'#e5e7eb','primaryBorderColor':'#334155','lineColor':'#64748b','secondaryColor':'#1e293b','tertiaryColor':'#0f172a','fontFamily':'ui-monospace,SFMono-Regular,Menlo,monospace'}}}%%
```

先按积木库 `_blocks` 的 `.mmd` 卡片写源码,**页面写完后收尾时跑一次**:

```bash
python3 ~/.claude/skills/html-mockup/assets/render-mermaid.py ~/www/<页面>/index.html
# 多个页面可一次传多个;--force 强制重渲(改了源码后用)
```

它做四件事:①`mmdc` 渲成 SVG **内联**进 HTML(SVG 是纯文本,不违反单文件约束,不需要 CDN);②源码收进 `<details>` 折叠,仍可复制去 issue / PR;③按 viewBox 宽度分档 —— ≤900px 撑满容器(`.mmd fit`),>900px 保持原尺寸 + 横向滚动 + "可左右滑动"提示(`.mmd wide`),**这是手机上宽图不被压成蚂蚁字的关键**;④幂等,重复跑只重算尺寸不重渲。

依赖是 `@mermaid-js/mermaid-cli` + 系统 chrome。**缺依赖时它保持源码卡原样、不会改坏页面**,但那样图就没画出来 —— 先装:

```bash
npx --no-install mmdc --version || npm i -g @mermaid-js/mermaid-cli   # 需要 google-chrome/chromium
```

渲染器内部已用 `PUPPETEER_EXECUTABLE_PATH` 指向系统 chrome —— 不这么做 puppeteer 会去找自己缓存的特定版本然后报 `Could not find Chrome (ver. …)`。

**收尾自检**:`grep -c '<svg' 页面` 的数量要等于 `.mmd` 卡片数;还剩几张没渲染就是几张图读者看不到。

## 8. 合并 / 废弃旧原型

旧目录 `index.html` 换成跳转页,对方手里的旧链接照样能用:

```html
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=../invoice_archive/?v=7#r1">
<title>已合并</title></head>
<body style="font-family:sans-serif;padding:40px;color:#555">
原型已合并到一页,正在跳转… <a href="../invoice_archive/?v=7#r1">点此打开</a>
</body></html>
```

## 9. 坑(全是真翻过车的)

| 坑 | 后果 | 正确做法 |
|---|---|---|
| 写死 `8008--main--myws--alice...` 这类域名 | 换台机器就是死链 | 用 `bootstrap.sh` 打印的 BASE_URL |
| 因为"内容看起来有点长"就绕开 preview_html 直接走 8008 | 白起服务、白设 public、白等对方点链接,自己也多花一轮 | 默认 preview_html;只有 §4.5 那四种情况才走 8008 |
| 内容很大(≈50KB+)还硬用 preview_html | 被截断,对方只看到一半 | 命中 §4.5 第 ④ 条就直接走 8008;已经发出去才发现截断,立刻补一个 8008 链接 |
| 给 localhost 或内网 IP | 对方打不开 | 给公网 BASE_URL,且确认端口已 share 成 public |
| 改完原地覆盖不带 `?v=N` | 对方看到的是旧版,以为没改 | 每版 N+1 |
| 引 CDN 字体 / 外链图 / mermaid.js | 白屏或资源被 COEP 拦掉 | 系统字体栈 + Unicode 图标,单文件自包含 |
| ASCII 框线里混中文 | 浏览器里必然错位 | 框内只放半角,中文放框外 |
| 要截图 / 转 PDF 时用了彩色 emoji(🆕 ✅ 🔁) | 这台机器没 emoji 字体,渲出来是豆腐块 □ | 看链接没问题;**只要截图或转 PDF,就换成 Unicode 符号**(① ★ ▼ › ⓘ ✓ ✕ ☑ 实测正常) |
| 从积木库抄了两个积木,样式互相串了 | 后代选择器压不住顶层同名类:写了 `.mx .note` 也照样吃到 `.note` 的橙边框 | **抄之前先搜一遍类名有没有撞**。已知三处已在积木库里改好名:列表页表格 `.dt`(非 .tb)、小票小计 `.amt`(非 .st)、矩阵说明列 `.rmk`(非 .note) |
| mermaid 源码里的 `<br/>` 直接写进 `<pre>` | 浏览器把它当成真的换行标签渲染掉,读者复制走的源码**缺了 `<br/>`**,贴到 issue 里节点不换行 | 写成 `&lt;br/&gt;`。收尾用 HTMLParser 扫一遍,`</br> 栈顶是 <pre>` 这类报错就是它 |
| 删一处规则后，只在「改的那份文件」里查残留 | **同一条规则往往写在多份文档里**，漏掉的那份会长期指错。删「逐文件清单」时只查了 SKILL.md 和 change-report.md，README 里那句「逐文件交代」挂了三次删除都没发现 —— 而 README 恰恰是给外部人看的门面 | 删规则要**全仓 grep**，不是只查手上那份 |
| 搜残留只用一种写法 | 同一个概念常有多种写法,**只搜一种必然漏**。实测:改「九节→八节」时按中文数字搜,README 里写的是阿拉伯数字「9 节」,没搜到;脱敏时 `grep -E ttpos` 报 0 处,换 `-i` 才发现大写 `TTPOS` | 中文/阿拉伯数字、大小写、全角/半角**一起搜**;清理审计类扫描一律加 `-i` |
| 循环里改字符串,却用循环前算好的偏移量 | 第一次插入后,后面所有 `match.start()` 全错位 —— 实测只处理了 5 个中的 2 个,**而且不报错** | **倒序处理**(`reversed(list(re.finditer(...)))`),earlier 偏移量才不受影响 |
| 把 `<a href="https://…">` 也算成「违反单文件约束」 | 自检误报。单文件约束管的是**资源加载**(CSS/字体/图片会被 COEP 拦),`<a href>` 是**导航**,两回事 | 检查只匹配 `<link>/<script>/<img>/<iframe>` 的 `href`/`src` |
| 拼接时既用了自带结尾换行的常量,又手动补 `"\n"` | 首次注入是两个换行、后续替换只剩一个 —— **第二遍跑就少一行,不幂等且不报错**。实测 `render-mermaid.py` 这么错过 | 常量统一 `.rstrip("\n")` 后再拼;**幂等性要连跑三遍比 md5**,跑一遍看不出来 |
| 工具把自己的 CSS 塞进页面已有的 `<style>`,再用「标记 → `</style>`」范围替换来更新 | **把标记之后、`</style>` 之前的用户 CSS 全吃掉** —— 页面看着正常,只是布局悄悄退回旧样子 | 工具的产物**独占一个容器**(自己的 `<style data-xxx>` / 自己的文件 / 成对哨兵),整块替换 |
| 拿输出文件名或工具产物的 `<title>` 当标题 | 输出叫 `index.html` 时标题就成了「index」;工具的 `<title>` 常带自己的尾巴 | 文件名是 `index` 时退回**目录名**,或显式传标题;已知尾缀剥掉 |
| **用 XML 解析器(`ElementTree`)验含内联 SVG 的页面** | 报 `not well-formed (invalid token)` —— 但页面完全正常。archify 之类的工具会输出**无值布尔属性**(`<text data-detail-anchor x=…>`),HTML5 合法、XML 不合法 | **含内联 SVG 的页面不能用 XML 解析器验。**用 HTMLParser(见下条)或直接浏览器测量。这已是同类假报错第三次:`</path> 栈顶 <marker>`、`scrollWidth > foreignObject width`、本条 |
| 合并多份工具产物时不给 `<button>` 写 `color:inherit` | **深色主题下 tab 文字是黑字,几乎看不见** —— `<button>` 默认不继承父级 `color`,用的是浏览器默认色 | 凡是 `background:none;border:0` 的按钮,**必须显式 `color:inherit`** |
| 用 HTMLParser 扫含内联 SVG 的页面 | 一堆 `</path> 栈顶 <marker>` 假报错 —— SVG 的 `<path>` 是配对写法,不是 HTML 空元素 | 内联 SVG 单独用 `ElementTree.fromstring` 验;HTMLParser 扫剩下的部分 |
| 图上留讲解标注、写「保持原样」清单 | 对方以为那些字是界面上的 | 界面帧里只放界面上真有的字,解释放 `.hint` 块 |
| 跟人说「图 1 / 图 2」 | 对方不知道你指哪个 | 说界面名字,或直接给锚点链接 |
| 凭印象画现有界面 | 对方一眼看出不是他的系统,白做 | 先去代码 / 截图核对真实样子和字段名 |
| 术语写「商品」「内部编码」 | 跟系统对不上 | 用「物品」「物品编码」等系统真名 |
| 同需求新建一个目录 | 对方手里旧链接还是旧内容 | 改原目录;废弃的留 meta refresh 跳转 |
| 改动汇报只讲改了什么、不讲影响谁 | 评审看不出风险,等于没审 | 表字段 / 业务规则 / 业务点 / 回滚,四块缺一不可 |

## 10. desktop 原型

shop 后台页(网页版)要套完整后台壳,别用移动式窄卡片:`.wrap` 放宽到 1180,画出左侧菜单 + 顶栏 + 内容区三段,橙色主题。参照积木 A(桌面后台外壳)。

## 11. 参考现成的

| 位置 | 看什么 |
|---|---|
| `~/www/_blocks/` | **积木库**,浏览器直接打开 `<BASE_URL>_blocks/` |
| `~/www/_template/` | 空白起手模板,`<BASE_URL>_template/` |
| `references/blocks.md` | 积木索引:「要表达什么」→「用哪个积木」 |
| `references/change-report.md` | 模式 B 完整手册:多代理分工 + 八节骨架 + 逐行规则比对 |
| `assets/bootstrap.sh` | 环境自举(建目录 / 装模板 / 起 8008 / 打印 BASE_URL) |

