# Changelog
所有显著改动按 [SemVer](https://semver.org/) 记录于此。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
## [Unreleased]
### 变更 · CSS 端接线为样式底座:H5 端单一样式真源 | S9-P7
- 103 个组件中 92 个此前已由演示页 `` 消费组件 CSS;本轮把剩余 **11 页**
(Button / Card / Dropdown / EmployeeCard / Input / Modal / Segmented / Select / Table / Tree / TreeTable)
的内联组件样式并入 `frameworks/.css`,演示页改为「markup + link CSS(+ 交互 JS)」,
内联只保留演示脚手架。CSS 端文件自本轮起是组件样式的**唯一权威**,
v1.4.0 遗留的「框架 CSS + 页面内联」双份维护不再需要。
- 值冲突按 components.css 规范层裁决(令牌优先、逻辑属性优先,裁决表见 ROADMAP S9-P7);
接线顺带修复两处内联版渲染缺陷:Select 触发器缺 `position: relative`(caret 此前锚定错误)、
Table / TreeTable 选择列未按契约居中。
- 移动端 47/47 此前已全部接线,本轮未改动。
- 全局导入形态随本轮重建刷新:`dist/tokens/tokens.css` + `dist/components/index.css`
(103 组件聚合,令牌之后引入即可);单组件 = `dist/components/.css`。
PLATFORMS.md 已把 `css` 列重定义为「样式层/交付物」。
### 新增 · 代码块 JS↔TS 切换 + 逐端专属内容齐备 + 组合场景 | S10
> 用户反馈三点:① 代码块上方的切换器与顶栏技术栈选择器重复,且切出来的内容**实际一模一样**;
> ② 不同组件讲的内容雷同;③ 预览「表现得不好」,希望能看到组件**配合**展示(卡片外壳、登录样例)。
> 本轮把三条都落到可验证产物上。
- **代码块切换器改为 JS ↔ TS**(`site/app.js` 的 `codeForms`):端由顶栏决定,
块内只在该端的 JavaScript / TypeScript 两种写法之间切换。原先的四端 tab 与顶栏功能重复
(实测 `button/react` 页面上重复出现 5 组同样的 tab)。
H5 / CSS 端交付静态标记与类名,没有可标注的类型面,故只给单一形态、不显示切换器。
- **597 段 TypeScript 形态**(`site/examples/*.json` 的 `code.tsx` / `code.vue3ts` / `code.vue2ts`,
199 示例 × 3 端):由 `tools/gen-ts-variants.mjs` 生成,原则是**只标注、不重构**——
在既有 JS 片段上插入类型标注与泛型参数,不移动、不重排任何结构。
首版曾尝试把同构标签提炼成「类型化规格数组 + 遍历渲染」,产出更"TS 味",
但实测生成了大量坏代码(`return (const cities = …)`、缺闭合标签的 Vue 模板、`Vue.extend({ … };` 少括号),
且**全部通过了原门禁**——判据只查结构性规则,查不出「代码结构被破坏」。
因此新增 `tools/check-ts-quality.mjs`:对 597 段做**真实编译**(esbuild 验 tsx 与 script 段、
vue-template-compiler 验 Vue 2 模板),当前 0 失败。
- **逐端专属内容 515 份齐备**(`site/end-content/..json`,103 组件 × 5 端):
此前只有 197 份,缺的时候页面回退显示同一句「本端的专属用法说明尚未提供」——
这正是「不同组件内容雷同」的直接来源。缺额由 `tools/gen-end-content.mjs` 从事实包补齐,
取料只认该端实现源码解析出的 API(`consumedApi`),不引入契约 `dims`(那是设计词汇层)。
- **组合场景 103 份**(`site/scenes/.json` + 预览产物 `site/scenes/.html`):
组件页新增「组合场景」一节,讲该组件与别的组件**配合**出现的形态(表单卡片里的字段、
列表页卡片与工具条、工具条触发的确认流、详情卡片里的内容等)。
门禁 `tools/verify-scenes.mjs` 的第一条就是**组件必须是场景主角**:
`markup.html` 必须用到该组件自己 CSS 里的类名——直接把「外面做卡片、主要部分仍是该组件」编码成判据。
协作者取自组件族的真实耦合关系,外壳类 `ksc-*` 与四端标记由 `tools/gen-scenes.mjs` 生成。
### 修复 · 非 H5 端的示例预览整片 403(路径基准错误)
`site/app.js` 的 `endPreviewSrc` 把逐端预渲染产物的地址写成 `../end-preview/..html`,
在 `/site/` 基址下解析到仓库根 `/end-preview/`,而产物实际在 `site/end-preview/`——
实测 `button/react` 页 5 个示例 iframe **全部 403**,只拿到错误页(body 长度 93)。
产物本身没问题:`frameworks/` 部署在站点上一级,而 `end-preview/` 在 `site/` 内部,
两者基准不同,不能照抄同一个前缀。改为相对站点根的 `end-preview/…`。
### 修复 · 逐端预览渲染的是空组件(预览「表现不好」的根因)
`tools/build-end-preview.mjs` 只给开关型/数据型组件传初始 props,纯展示组件因此渲染成空壳:
Button 的产物是 ``,
305 份产物里 **68 份文本为空、79 份不足 6 字符**——示例卡片里看到的就是一个没有文字的按钮。
修法:从 `site/examples/.json` 的**第一个固化的示例**反推该端的真实初始 props
(只取字符串字面量 / 裸布尔 / 数字,`{表达式}` 与数组对象型 prop 一律跳过,
避免把字符串塞进 `data` 导致 `nodes.forEach is not a function`)。
`button.react` 的产物由空壳变为「主按钮(点击切加载态)」。
### 修复 · 组件页在 h5 端请求了不存在的 end-content 文件
`endKey()` 直接把路由端名拼进文件名,h5 页会去取 `button.h5.json`(磁盘上是 `button.html.json`),
404 后长期走兜底文案——实测 `verify-site-routes` 抓到 37 条控制台报错。
改为经 `END_IMPL_KIND` 映射(它本就是「端 → 该端实现文件键」的定义)。
### 修复 · 若干门禁假红与真缺陷(都是本轮实测发现的)
- `tools/verify-component-page.mjs`:示例代码断言原本假定「代码块是四端 tab」,
已改为断言**只出现本端的 JS/TS 形态**、切换器不出现别的端名、TS 形态确实含类型标注;
另外补了强缓存清理(示例正文用 `force-cache` 取,产物重写后门禁会拿到上一版而假红)。
- `tools/verify-examples.mjs`:新增两条判据——Vue 2 模板**根元素带 `v-for`** 会编译失败
(`Cannot use v-for on stateful component root element`,`alert-ex-2` 实例已修)、
TS 专属标记的识别补上 `Record<…>` 与 `JSX.Element`(原判据只认 `: void|string|…`,把合法写法判成「无标记」)。
- `site/logger.js`:不再把浏览器对 ResizeObserver 的提示(`loop completed with undelivered notifications`)
记为页面错误——它没有 error 对象、不影响渲染,而本站示例/场景 iframe 正是靠 ResizeObserver
做高度自适应,这条噪声会随帧数线性增长(实测 37 条)并淹没真实报错。
- `site/style.css`:组合场景代码区补深底浅字(原先继承默认黑字,在深底上对比度 1.04:1)、
补 `overflow: auto`(320px 下顶出整页 `overflow=263px`);端标识胶囊 `.ex-end` 补独立配色。
- `site/app.js`:end-content 异步渲染后补跑表格包裹(`.kole-doc-table`)——
该节走自己的回调、不在既有的两处刷新点上,实测导致「所有 table 都被包裹」这条**间歇失败**。
- `.dockerignore`:排除 `site/end-content/_facts`(4.7MB 构建期事实包,站点从不 fetch 它,
且内嵌 `frameworks/` 源码全文)。内容文件 `site/end-content/*.json` 照常发布。
### 验证
| 门禁 | 结果 |
|---|---|
| PC 回归 `tools/run-regression.mjs` | **100%(1464/1464,103 页)· 连跑八次一致** |
| 移动端回归 | 100%(807/807,47 页) |
| `verify:examples --require-ts` | 15 项通过(199 示例 / 796 片段) |
| `verify:scenes` | 9 项通过(103 份场景) |
| `verify:end-content` | 8 项通过(515 份内容) |
| `check-ts-quality`(真实编译) | 597 段 · 0 失败 |
| `verify-component-page` | 110 项通过(连跑 4 次一致) |
| `verify:i18n` / `verify:theme` / `verify:site-routes` / `smoke:site` / `verify:playground` / `verify:nav` / `verify:templates` / `verify:api-docs` / `verify:isolation` | 全部通过 |
| 零运行时依赖 | `zero-dep OK` |
### 修复 · 组件页「调用代码」是空壳(生成器兜底 → 真实三端调用代码)|ROADMAP S9-P6
> 103 个组件页此前展示的 jsx / vue3 / vue2 代码块,大量是生成器兜底的零信息量骨架:
> 例如 icon 页 7 个使用场景的三种端代码**逐字完全相同**,都只有
> `import { KoleIcon } from 'kole-ui/react';` 加一句 ``。
> 根因不在文档层:演示页把真实用法写在内联脚本里(`fill('row-size', TIERS.map(...))`),
> 而构建期生成器只读静态标记,于是走「策略 3」兜底输出最小骨架;门禁只校验「片段非空 + prop 可溯源」——
> **空话成了唯一永不失败的解**。
- **修复**:新增固化层。`site/examples/.json` 顶层带 `curated: true` 时,`precompute.mjs`
保留其 `code` / `source` / `notes`,预览层(`html` / `preview`)仍以本次生成为准(演示页改结构不会让产物失真)。
103 个组件的 199 个示例、597 段三端片段改写为可照抄的真实调用,逐条依据演示页脚本的实参,
`notes` 记录「文件:行 + 实参」出处。
- **新增门禁**:`npm run verify:examples` 由 9 条增至 **13 条** —— 同组件同端片段不得跨示例逐字重复;
有 props/slots 的组件不得交零属性无内容骨架;`source` 不得是生成器兜底态 `api-derived`;
Vue2 片段必须是可编译形态(模板恰好一个根元素、无 `` / `v-model:arg`)。
另支持 `--only=slug1,slug2` 分片校验;失败详情带总数,避免前序组件的条目把后续的挤出显示。
- **判据修正**:`contract.dims` 不再作为 prop 来源。实测它是设计词汇层 —— `state` 出现在 35 个组件里
只有 4 个实现消费、`feature` 10/2、`action` / `scan` / `layout` / `selection` 全 0 消费。
当 prop 源会放行实现根本不接收的属性(即「照抄却不生效」的假用法);prop 现须能指到
`api.props` 或组件源码里的标识符。据此修正了 2 个组件:`enhancedtabnav`(去掉 `state`/`feature`)、
`pagetransition`(去掉 `effect`/`direction`,并去掉与组件内部工具栏重复的渲染)。
- **变异验证**:同一份数据在旧门禁下 `exit 0`(9 checks 全过)、新门禁下 `exit 1`,证明新断言确实在抓该缺陷;
Vue2 单根断言用「往模板插入兄弟元素」实测变红(报 `vue2 模板根元素 2 个`),随后按 md5 逐字节还原比对。
- **验收**:`node tools/verify-examples.mjs --verbose` → `13 checks passed(199 个示例 / 796 个片段)`;
退化统计由「跨示例重复 150 / 零属性空壳 327 / api-derived 兜底 130」全部归零;
重建站点后 `site/data.json` 内嵌新片段 199 处、`api-derived` 残留 0,固化标记 103/103 存活,
抽样文件 md5 与构建前逐字节一致。
- **新增规格文档** `EXAMPLES-CURATION.md`:固化产物的字段边界、事实源、三端写法与硬规则。
- **遗留待决**:`逐示例片段固化与待决事项.md`(D1–D6)。
### 修复 · 图标预览页尺寸档(`site/icon-preview.html`)|ROADMAP S9-P4
> 该页自 2026-09-20 上线起,**尺寸档四个按钮全是坏的**:点击只改 SVG 的 `width`/`height` 属性,
> 而 `.ip-cell .glyph svg { width: 1em; height: 1em }` 是作者样式表规则,按 CSS 层叠压过表现属性 ——
> 四档一律渲染成 `.glyph` 的 `font-size`(实测恒为 16px)。用户可见症状:点 24 / 32 毫无反应。
- **修复**:`.ip-cell .glyph svg` 去掉写死的 `width`/`height`,只留 `display: block`,
尺寸交给 SVG 属性驱动(与组件页图标集合区同一手法)。
- **新增门禁** `tools/verify-icon-preview.mjs`(`npm run verify:icon-preview`,**26 条断言**):
该页此前**没有任何功能门禁**(仅 `pack-deploy.mjs` 提过它),这正是缺陷存活三天的原因。
核心判据是**实测渲染宽度**(`getBoundingClientRect` 与属性值同时校验)——旧判据只断言
「按钮有响应 / 属性已改 / 无控制台错误」,按钮确实响应了、属性也确实改了,因此漏过。
另含搜索收窄 / 仅线性(1304,与组件页同源)/ 剪贴板复制 / 暗色无白底残留 / 0 个 4xx。
- **变异验证**(证明门禁有区分力):把 CSS 还原成修复前形态重跑 → `尺寸档 20/24/32 FAIL
—— 属性改了但渲染尺寸没跟随`,退出码 1;16 档仍通过(恰好与 1em 的 16px 重合,正是原缺陷的形态)。
- **验收**:`npm run verify:icon-preview` → `OK — all checks passed`(四档 attr 与 rendered 均 = 16/20/24/32)。
### 文档站 · icon 组件页补「图标集合」区(对齐 Element 图标页形态)
> 起因:用户点名 `element.eleme.cn/#/zh-CN/component/icon` ——「会显示组件用法和每个组件样式的展示」。
> 实测该页两节:**使用方法**(用法示例)+ **图标集合**(全部图标网格,每格图形 + 名字);
> 本站 icon 组件页此前只有前者(7 段用法示例),图标全集要跳去独立工具页 `site/icon-preview.html` 才看得到。
- **新增「图标集合」区**(仅 icon 组件页,其余 102 页不受影响):工具条(搜索 / 尺寸 16·20·24·32 /
仅线性 / 实时计数)+ 13 个分组折叠网格,**全部 2576 个图标**,每格渲染真实 SVG 并标注图标名。
- **交互**:点击复制图标名、Shift + 点击复制内联 SVG(判据与独立预览页同源,实测「仅线性」
两处计数一致:1304 / 2576);搜索命中实时收窄并自动展开命中分组;空结果给提示而非空白。
- **数据链路**:`site/icons.json`(名字/分组,52KB)+ `.design_library/kole-ui/icons/registry.json`
(路径数据 708KB,进入视口才取)。实测全量 2576 格建 DOM 约 75ms、布局 90ms、堆 10.7MB,
故不做虚拟滚动;折叠组浏览器整块跳过布局(实测 3ms)。非 icon 页实测 **0 次**图标数据请求。
- **降级**:路径数据取不到时给统一失败态(计数条与区块主体同一说法),页面其余章节完好 ——
实测踩到过「计数条说加载失败、主体停在『加载中…』」的自相矛盾,已修并纳入门禁。
- **门禁扩容**:`verify-component-page.mjs` 新增 28 条图标集合断言(全量渲染数 = icons.json 的
names 长度、每格有真实 path、点击复制读**剪贴板**、尺寸档读**实测渲染宽度**、降级路径、其它页零请求),
总断言 79 → **107 条**;`verify-theme.mjs` / `verify-dark.mjs` 的对比度路由清单加入
`/site/component/icon`(实测该区占探针 600 节点预算里 524 个,是站点文本最密的一块)。
- **实测发现并登记**:`site/icon-preview.html` 的尺寸档按钮自上线起就是坏的(CSS `width: 1em`
压过 SVG 的 width 属性,四档全渲染成 16px)→ 已追加 ROADMAP **S9-P4**(含实测数据与修法)。
- **回归**:PC 100%(1464 通过 / 0 失败 / N/A 50,共 1514 条)· 移动端 100%(807 通过 / 0 失败 / N/A 10),
八次连跑一致,0 超时;移动端与既有端实现零污染(`git status` 空)。
### 计划 · S9 PC「组件 × 端」独立页(批次台账 + 单批门禁)
> 起因:用户判断「PC 端的组件内容层几乎都一样,每端都该有组件自己的页面,批数 = 组件 × 端数,
> 一批一批独立来」。本节只交付**计划与判据**,页面渲染逻辑未动。
- **实测坐实问题**:Playwright 对 6 个组件的 4 个端详情页做 DOM 比对 —— 9 个章节里
**7 个四端逐字相同**;预览 iframe 四端**全部**是 `../frameworks/.html`(H5 演示页);
端切换实际只改「代码 tab 默认选中」与「实现资源表行数」。
- **根因**:PC 索引 103 个条目**既无 `ends` 也无逐组件 `files`**(移动端索引两者齐备),
端清单硬编码在 `site/app.js:39` 的 `FW_FILES` —— 数据层不表达端,渲染层只能退化成 tab 切换器。
- **可行性的实测**:React(esbuild + react-dom/server)、Vue 2(vue-template-compiler +
vue-server-renderer)、Vue 3(@vue/compiler-sfc + vue/server-renderer)三条离线预渲染链路
全部跑通,产物类名与 H5 端逐字一致;工具链装在 `.tmp/` 隔离目录,**主仓库零运行时依赖不变**。
- 新增 **批次台账** `tools/lib/pc-end-batches.mjs` → `tools/pc-end-batches.json`:
**618 批 = 103 组件 × 6 端**(可开工 518 / 受阻 100,受阻项全部是 uni-app 端依赖 S7-P26)。
波次按端排(W1 H5 → W2 React → W3 Vue 2 → W4 Vue 3 → W5 CSS → W6 uni-app),
档位按分类排(通用 → 反馈 → 数据录入 → 数据展示 → 导航 → 系统)。
- 新增 **单批门禁** `tools/verify-pc-end-batch.mjs`(`npm run verify:end-batch`):
12 项检查分六组(A 路由与端声明 / B 预览真分端 / C 示例代码逐字 / D 资源只列该端 /
E API 来源可追溯 / F 无跨端串味)。红灯基线实测:`react/button` 6/12,六组件一致。
空集判失败(`--all` 选不到批次时不报假绿)。
- 新增 **`S9-P1` 数据层补端维度 / `S9-P2` 单端渲染管线 / `S9-P3` 逐端内容层分栏** 三个
前置任务包(写入 `ROADMAP.md`),完成后 518 批可按波次逐批推进。
- 交付说明:`S9-PC端独立页批次计划.md`。
- **回归**:PC 1464/1464 = 100%(103 页);`verify:isolation` 31 条全过;`zero-dep OK`。
`site/` / `frameworks/` / `tests/` 零改动。
### 发布脱敏 · 对外产物滤掉基础设施细节(安全)
> 起因:抓一次公网 `/site/m/changelog.html` 就能拿到内网网段、服务器目录布局与部署时序 ——
> 站点是**公网可下载的静态文件**,而 `CHANGELOG.md` 是仓库内部变更流水,两者此前是直连的。
- 新增 `tools/lib/redact-publish.mjs`(零依赖):构建期把「仅供内部」的基础设施细节替换成
不含事实的等价说法。**不改 `CHANGELOG.md`** —— 内部可追溯性完整保留,过滤只发生在写产物那一步。
- 接入 3 处注入点:`tools/precompute.mjs`(PC 侧 `data.json` / `changelog.json`)、
`tools/build-mobile.mjs`(移动端更新日志页)。`build-site.ps1` 不碰 —— ASCII-only 铁律。
- 只处理 `changelog` 字段:`components[].sources` 是规范实现源码,逐字保真不动
(详情页代码区还**主动高亮注释**,剥注释会破坏该功能)。
- 实测命中并已消除:`/opt/aurora-admin.prev-*`、`kole-ui-showcase:pre-*`、`docker compose build`、
`192.168.5.7`、16 位产物指纹、`1531/1531` 对账数,以及「并发会话 / 本会话 / 派子 agent」等
暴露开发方式的用语。发布集 2465 文件全量扫描:**0 命中**。
- 修正 `site/scenario/settings.html` 的演示占位 IP:原写 `192.168.5.0/24`,与真实服务器网段一致,
改为 RFC 5737 文档段 `192.0.2.0/24`。
- 模块自带**幂等性**与**误伤防护**:只在与替换词相邻时才处理,正文代码跨度(如 `pack-deploy --tar`)不受影响。
实测 30 行真实 CHANGELOG 敏感行:0 残留、0 非幂等、0 标点畸形。
`TRIGGERS`(句子级闸门)与 `RULES`(替换规则)**必须同步增删** —— 漏一个词会出现
「规则写好了但永不触发」的静默漏网(本轮实测踩到:`**本会话直接产出的一批**` 因 TRIGGERS 缺词而整行未过滤)。
- 2026-09-23 已按 AGENTS §九 发布到公网:发布集 2461/2461 逐字节一致,五项验收全过,
公网复查 `/opt/*`、镜像名、内网 IP、产物流水全部 0 命中。
### 品牌标识 · Kole Cup 饮料杯标记(最终定稿)
- 品牌图形由几何 K 改为**饮料杯 + 负空间 K**:圆角杯盖 + 上宽下窄圆角杯身,K 以
`fill-rule="evenodd"` 从杯身挖空。造型参考用户提供的 freeicon 图标语言(吸管、杯盖、
杯身比例),但路径为在 24 网格上**独立绘制**,未复制其原始数据;最终版按用户要求**去掉了吸管**。
- `brand-mark.json` 升到 schemaVersion 3:`geometry.paths` 支持 `{ d, evenodd }` 对象项,
供负空间挖空使用(旧字符串写法仍兼容)。
- `verify:brand` 增至 **25 条断言**,新增 B9b:负空间路径必须带 `fill-rule="evenodd"` ——
缺了它 K 会被填成实心块,且**不会报任何错**(渲染仍然成功)。
- 顶栏与 favicon 的资产路径、盒尺寸(PC 32×32 / 移动 26×26)、主题 mask 机制均不变。
### 品牌标识 · K-Node 图标真源重构
- 新增 `.design_library/kole-ui/brand/brand-mark.json`,统一定义 K-Node 的三段几何、尺寸与 favicon 颜色。
- 新增 `tools/build-brand-mark.mjs` / `tools/verify-brand-mark.mjs`,生成并校验独立 favicon、单色顶栏标记及生成数据。
- PC 与移动端文档站改用同一组 SVG 资产;顶栏通过 `currentColor` mask 跟随亮/暗主题,保持原有导航盒尺寸与断点不变。
- `npm run build` 纳入品牌资产构建,部署发布集显式要求品牌 SVG 与生成数据存在。
- 2026-09-22 本地验收:品牌门禁 24 条、移动文档 12 条、移动站浏览器 263 条全部通过;主题、导航、路由、站点 smoke 与暗色检查全部通过。
- 回归以本地 `http://127.0.0.1:3400` 为目标:PC 1464/1464(103 页,N/A 50)、移动端 807/807(47 页,N/A 10),各连续 8 轮一致,0 失败、0 超时。未部署。
### 品牌标识 · 几何 K 图标(favicon / 顶栏标记 / theme-color)
> 起因:品牌此前**没有任何图形标识** —— 唯一 favicon 是内联 data-URI 里的字母「A」,
> 那是 v2.0.0「Aurora Admin → Kole UI」改名时漏掉的一处(PC 顶栏也是「A」,
> 而移动端站已是「K」;移动端文档站则完全没有 favicon)。
> 落地:几何 K(三笔画断笔描边),两端同源,含 favicon / 顶栏标记 / theme-color。
#### 设计
- **几何**:24 网格上三个**互不接触**的笔画(竖笔 + 上斜 + 下斜),圆头描边。
笔画不接触既表达「独立组件拼组成系统」,也保证 16px 浏览器标签页尺寸下不糊。
- **描边 2.25**:在 24 网格下,16px 渲染时正好是 **1.5px** —— 等于规范原文
「线性图标,描边1.5px」(`.design_library/kole-ui/specs/组件1.txt`)。
实测比对了 1.5 / 1.875 / 2.25 三档在 20px 顶栏与 16px 标签页下的观感。
- **圆角 rx=6**(24 网格)= 32px 下 8px,即令牌 `--kole-radius-large`。
#### 取色分两套(刻意,不是不一致)
- **favicon 硬编码** `#2F54EB` + `#FFFFFF`:它渲染在**浏览器标签栏**,不继承 `html.kole-dark`。
若走令牌,暗色下 `--kole-color-text-inverse` = `#14161C`,会变成「亮蓝底 + 近黑标记」,
在浅色标签栏上反而发闷。
- **两端顶栏标记走 `currentColor`**:实测暗色下自动解析为 `rgb(20,22,28)`、
底色转 `rgb(107,140,255)`(令牌化行为保真,与同页其它图标一致)。
#### 新增
- `theme-color` 双条(light `#FFFFFF` / dark `#1C1F26`,取 `--kole-color-card-bg`,
即 `.topbar` / `.m-top` 的 background,色值与视觉连续)。PC 写在 `site/index.html`;
移动端由新增的 `__BRAND_HEAD__` 占位符经 `tools/build-mobile.mjs` 注入 7 个模板。
#### 修正
- `site/index.html` 的 favicon 字母 `A` → 几何 K;顶栏 `.logo-mark` 的 `A` → 几何 K
(**类名与 32×32 盒模型不变** —— ≤1366px 时它是顶栏唯一品牌标识)。
- `tools/build-mobile.mjs` 移动端顶栏标记 `K` 文字 → 几何 K 图形(保留 `class="m-logo-mark"`,
`verify-mobile-docs` 会逐页断言它存在)。
- `site/app.js` 首页 hero 标语 `KOLE ADMIN DESIGN SYSTEM` → `KOLE UI DESIGN SYSTEM`
(同一改名的变形残留)。
- 移动端文档站 7 个模板补 favicon(此前 `rel="icon"` 计数为 0,标签页显示默认地球图标)。
#### 验收
- 门禁:`verify-site-routing` OK(103 / 412 / 521)· `verify-site-routes` OK(零 4xx)·
`verify-mobile-docs` OK(12 条 · 53 页)· `verify-mobile-site` OK(263 条 · 50 页,
控制台 0 错误)· `verify:isolation` OK(31 条)· `verify:theme` / `verify:nav` /
`verify:i18n` / `verify-icons` 全 OK。
- 回归:PC **1464/1464** · 移动端 **807/807**,**各连跑 8 次一致**。
- 浏览器实测:PC 与移动端 favicon **405 字节逐字节一致**;页面 DOM 中 `>A<` 清零;
PC 站控制台错误从 1(favicon 404)降为 **0**。
- 已知边界:`og:image` / `apple-touch-icon` / web manifest 未做(需位图管线,
见 ROADMAP S8-P4)。
### 图标系统(PC 5 端 + 移动端 6 端 + 全量图标库 + 预览页)
> 起因:用户要求「图标组件参考 TDesign / Element 等主流组件库,看他们的 icon 是怎么专门设计的、
> 设计内容有哪些,覆盖他们全部拥有的 icon 图标和功能」。
> 落地:**2576 个图标**(TDesign 线性 1334 + 面性 1020 + Element Plus 293,均 MIT),
> 11 个端实现全部接通,新增 5 个构建/门禁工具与 1 个图标预览页。
> 冻结规格:`.design_library/kole-ui/icons/ICON-SPEC.md`。
#### 新增:图标数据管线(离线可复现,上游校验和钉死)
- `tools/fetch-icon-sources.mjs` — 拉取并**归一化**上游 SVG → `sources.json`。
逐包校验 sha256(防「某天图标悄悄变了」);纯函数可单独 import(无副作用)。
- `tools/build-icons.mjs` — 编译出 `manifest.json`(清单)/ `registry.json`(渲染数据)/
`viewbox.js`(viewBox 例外表)/ `core.js`(内联运行时)/ `ATTRIBUTION.md`(MIT 归属,分发必带)。
- `tools/build-icon-aliases.mjs` — 别名层,让 **Element UI 全部 282 个历史图标名**与
移动端既有 9 个字形名都能落到真实图标(**旧调用点零破坏**)。
- `tools/inject-icon-table.mjs` — 把 core 表注入 11 个端实现的标记块,杜绝「11 份手抄表必然漂移」。
- `tools/verify-icons.mjs` — **11 条静态断言**(产物一致 / 无 `view-box` 残留 / 描边全 1.5 /
core 可渲染 / 别名不遮蔽 / 各端都能查到名 / 分组覆盖率 / 路径数字与源逐值一致)。
#### 修正:两个上游缺陷在构建期抹平(此处是本仓库的增量,不是照抄)
1. **`view-box` 拼写错误**:TDesign **全部 2356 个** SVG 把属性写成 `view-box`(无效属性,
浏览器忽略 → 图标按默认视口缩放)。构建期两种拼写都读,产物统一写回规范的 `viewBox`。
2. **描边宽度 2 → 1.5**:上游为 `stroke-width="2"`(24 网格),本仓库规范
(`组件1.txt`「图标规范:线性图标,描边1.5px,尺寸16/20/24px」)要求 1.5,构建期归一。
另丢弃 `fill="transparent"` 命中区路径(那是热区不是视觉内容)。
#### 修正:自测发现的数据损坏缺陷(构建期把坐标改错)
- **现象**:归一化把路径数字四舍五入到 2 位小数,看似无害;但 SVG path 允许「隐式分隔」——
`q-13.005.48-22.5` 是**三个**数(`-13.005`、`.48`、`-22.5`,靠 `-` 和 `.` 当分隔符)。
把 `-13.005` 舍成 `-13` 后,后面的 `.48` **粘成** `-13.48` —— 那是另一个坐标,
图形画错**且不报任何错**。实测踩到 Element Plus 的 `quartz-watch`(靠浏览器控制台才发现)。
- **修法**:原本有小数位的数字,输出**至少保留 1 位小数**(`-13.005` → `-13.0`),
隐式相连的 `.48` 不可能再被吸收;并在 `optimizePath` 内**复核数字序列**,不一致就原样返回。
- **防复发**:新增门禁断言 **V8「路径数字与源逐值一致」**,比对全部 2576 个图标。
已做**变异测试**:把该 bug 重新注入 → V8 当场 FAIL(`数字个数 461 ≠ 源 462`),判据有区分力。
- 代价:`registry.json` 707KB(修前 677KB,+30KB 换正确性)。
#### 修正:非 `` 图形被静默丢弃(覆盖率审计发现)
- **现象**:解析器只认 ``,而上游有 **36 个文件**用 `` / ``
表达图形(TDesign 的 `circle` / `round` / `brightness` / `image` 等)。这些图形被**静默丢掉** ——
图标页面上"渲染成功"却是空的,构建与回归都不报错。TDesign 的 `circle` 与 `round` 两个图标
因此整个消失,是**覆盖率审计**(对三个参考库逐名比对)才暴露的。
- **修法**:解析器同时处理 `` / `` / ``,把圆与椭圆等价换算成
path 的两段 arc(`M cx cy-r A r r 0 1 1 cx cy+r A r r 0 1 1 cx cy-r Z`)。
- **结果**:图标数 2574 → **2576**;对三家参考库的覆盖率现为
**TDesign 2356/2356、Element Plus 293/293(各 100%)**、Element UI 280/282(余 2 个是解析碎片名)。
- 校验:两个恢复的图标在浏览器实测有真实几何(path 长度 63 / 44,分别对应 r=10 与 r=7 的圆)。
#### 分层与体积(按实测决定,不是拍脑袋)
- **core 151 个内联**(`core.js` 35KB),`standard` 1989 / `extended` 434 按需加载。
- **为何不全内联**:全量 670KB,内联等于给每一页都加 670KB,直接违背 ROADMAP 的性能预算精神。
#### 组件 API(PC 5 端 + 移动端 6 端,语义统一)
- props:`name` / `size`(关键字 `small|default|large|xlarge` **或任意 CSS 长度**,对齐 TDesign 双模式)/
`tone`(`default|brand|secondary|danger|success|warning`)/ `spin` / `label`。
- **向后兼容**:PC 既有 `icon` prop(字形字符,默认 `'★'`)与移动端既有 9 名字形表全部保留,`name` 优先。
- 无障碍:`label` 有值 → `role="img"` + `aria-label`,无值 → `aria-hidden`;
`spin` 在 `prefers-reduced-motion: reduce` 下停转;语义色对比度 ≥ 3:1(实测 5.10–5.87:1)。
- **viewBox 按「路径串 → 网格」反查而非按名**:别名指向的 1024 网格图标若按名查会取错网格
(实测影响 160 个名字,会导致图标拉伸变形)。
#### 新增:图标预览页 `site/icon-preview.html`
规范 `组件5.txt`「图标预览 IconPreview:展示系统所有图标 / 网格布局,支持搜索 /
点击图标复制名称或代码 / 可调整图标大小」声明已久但从未实现。本次补齐:
2576 图标网格、搜索、点击复制名称、Shift+点击复制内联 SVG、四档尺寸切换、仅线性过滤;
分两段加载(索引 51KB 先出骨架,路径数据按需);
入口挂在图标组件页代码条 footer,i18n 双语(`图标预览` / `Icon gallery`)。
#### 已知未覆盖 / 遗留
- 移动端 uni-app 端用 **CSS mask(data-URI SVG)** 承载字形(小程序无 DOM、无内联 SVG)。
H5 目标已实测;**mp-weixin / app-plus 的遮罩渲染未在真机验证**,已登记进契约 `unknowns`。
- `verify-emits-parse.mjs` 的文件数期望值陈旧(158,应为 103×2=206)—— **本次之前就存在**
(`git show HEAD:` 确认),已登记为 ROADMAP `S8-P1`。
- `verify-cross-platform.mjs` 把注入的图标数据表误判为组件类名(540 条里 533 条是噪声)——
已登记为 ROADMAP `S8-P2`(判据不放宽,让门禁学会跳过数据块)。
- `组件5.txt` 声明的 47 个工具/模板组件里 **46 个仍未实现**(本次只补了 IconPreview)——
已登记为 ROADMAP `S8-P3`,含「走 `frameworks/` 正式组件还是 `site/` 工具页」的决策点。
### Mobile · 顶栏对齐 PC + 平台入口去重(移动端站导航栏改造)
> 起因:用户要求「移动端组件的导航栏参考 PC 端组件导航栏优化(上面跳转 PC 和移动的下拉框也重复了)」。
> 实测确认两条问题,逐项修掉。
- **去重:平台切换从顶栏移除**。移动端站顶栏原有一个 `[PC 端] [移动端]` 分段胶囊,与同一页左栏的
「平台」组指向同一跳转(实测 DOM:`a.mp-item` 与 `a.m-side-link.m-side-platform` 的 href 均为
`../index.html`)—— 同一件事两处入口。PC 站顶栏从来没有这个控件(只有左栏一处),
故删顶栏那份,两端现在都是**左栏唯一入口**。改动同时断言「删的是重复的那个,不是删光」:
`verify-mobile-docs` 的 E7 从「断言顶栏有平台切换」改为「断言左栏有平台入口」。
- **顶栏逐项对齐 PC**(此前只有结构同名,控件实现不同源):
- 高度 56px → **60px**;内容加 **1180px 居中容器**(`.m-top-inner`,与 `.m-body` 同宽,
logo 与左栏左边缘对齐;此前内容铺满视口、与下方内容左边缘错位)。
- 激活项补 **2px 底部品牌指示条**(`.m-nav a.active::after`,PC `.topnav a.active::after` 同款);
此前只有文字变色。
- 版本角标 → **可点版本下拉**:`` 换成 `#m-ver-trigger` + `#m-ver-menu`,
清单从部署根 `versions.json` / `site/versions.json` 两份按序取(不并行 —— 快照页那份必然 404,
并行会把 404 记进控制台,而站点门禁的零控制台错误是硬断言);拿不到清单时
`data-single="1"` 退回不可点角标(与 PC 同一语义)。路径校验只放行 `".."` 与 `x.y.z`,防清单把人带去外站。
- 主题触发器补 **三态图标**(月亮 / 太阳 / 显示器,PC `.theme-icon` 同款):显式选择时按时态给图标,
自动模式恒为显示器图标;此前只有文字标签。
- **零运行时依赖不变**:新增的版本/主题逻辑是构建期内联脚本(`COPY_SCRIPT`),不引包。
- **顺手修掉一处过期硬编码**:移动端站文案里的 PC 组件数写死为「79」,
而 S6-P21 组件族参数化后 PC 索引已是 **103**(实测 `components/index.json`)。改为构建期从 PC 索引实测读取
(模板占位符由 `build-mobile.mjs` 灌入),拿不到索引时退回中性表述而非编造数字。
涉及:总览页、FAQ、平台与端页的正文与覆盖矩阵、以及 `dist/mobile/README.md` 与 manifest 的 `isolatedFrom`。
**验收(原样)**:
```
node tools/verify-mobile-docs.mjs → [OK] 12 条断言 · 53 页(E7 判据已同步去重)
REG_BASE=http://127.0.0.1:3457 node tools/verify-mobile-site.mjs → [OK] 263 条断言 · 50 页 · 控制台错误 0 · 演示帧 281/281
REG_BASE=http://127.0.0.1:3457 node tools/run-mobile-regression.mjs → passRate 100% | pages 47 (all-pass 47) | assertions 804/804 | N/A 10
REG_BASE=http://127.0.0.1:3457 node tools/run-regression.mjs → passRate 100% | pages 103 (all-pass 103) | assertions 1405/1405 | N/A 50
PC 与移动端回归各连跑 8 次,结果逐次一致(40/40 全绿)
```
浏览器实测(Playwright,1440px):顶栏 60px / 内容容器实测 1180px / 激活项 `::after` 实测
`2px rgb(47, 84, 235)` / 顶栏内 `.m-platform` 计数 0 / 版本下拉实测 1 个选项且 `data-single="0"` /
切夜间后图标 `m-icon-sun:block` 且 `html.kole-dark` 生效;320–768px 四档无横向溢出、零控制台错误。
四道移动端门禁全绿:隔离 31 条 / uni-app 9 条 × 50 SFC / 文档 12 条 · 53 页 / 站点 263 条 · 50 页。
### Mobile · 批次 E/F:移动端组件 36 → 47(两批子 agent 并行 + 新增「磁盘↔索引」双向门禁)
> 继续 `/son` 派活推进 TDesign 清单覆盖。批次 E(typography / segmented / sticky / overlay / popover / message)
> 与批次 F(picker / cascader / colorpicker / upload / table)并行产出 11 个组件 × 8 文件 = 88 文件,
> 规格 §37–§47。索引 36 → **47**,实现 216 → **282**,移动端回归 597 → **804 条断言**。
- **给每个组件预分配规格编号**(§37–§47):上一轮两个子 agent 并存时存在编号撞号风险(撞号会被合并脚本按幂等规则整节跳过),
本轮在派单里逐组件指定编号,合并时零冲突、规格 §1–47 连续。
- **新增反向门禁 B1b(两个子 agent 独立报的同一条缺口)**:`verify-mobile-isolation.mjs` 原先只查
「索引里的组件有没有实现文件」,**不查「磁盘上的实现文件有没有进索引」**——于是子 agent 产出的组件在合并前
完全不被任何门禁覆盖(不在索引里 = 不被检查),属静默缺口。现补反向断言:磁盘前缀集合必须等于索引前缀集合,
不一致直接报「未登记:X, Y(跑 node tools/merge-mobile-batch.mjs 合并)」。断言数 30 → **31**。
- **修掉两处探针误判(都是工具侧,不是内容缺陷)**:
- `verify-mobile-site.mjs` 的总览页在 47 个卡片时只渲染 43 个 —— 预览帧 `loading="lazy"`,
**一次 `scrollTo` 到底会跳过中间帧**。改为逐段滚动(每 800px 一次)后再判定,实测 281/281。
- 同一脚本把收集器的 `net::ERR_ABORTED` 记为控制台错误 —— 那是 `_collect.html` 在帧装载完成后
**主动** `removeAttribute('src')` 释放帧造成的在途请求中断,是设计行为。判据改为忽略 `ERR_ABORTED`,
其余(404 / ERR_INVALID_URL 等)仍然计错。
- **子 agent 自修的真实缺陷(都带证据)**:`Sticky` 的 `is-stuck` 判定在默认偏移下滚动 0 就已贴合(改为与容器 padding box + 偏移量比较,含 1px 边框补偿);
`Message.jsx` 把 `onClose` 写进 `useEffect` 依赖 → 宿主传内联箭头函数时计时器永远不到点(回调移到 `useRef`);
`Sticky`/`Message` 演示页缺实际执行的监听器致 `data-behavior` 静默失效;
`Table` 可点行不可聚焦(真实引擎报 `matrix:keyboard-reachable`)→ 补 `tabindex`/`role`/`aria-selected`/Enter 键路径;
`ColorPicker` 的色板 hex 写在行内 style 被 `no-hardcode-hex` 判据扫到 → 改走行内自定义属性(CSS 零 hex,色值只存数据层)。
- **规划偏差(子 agent 主动对齐既有约定)**:`disabled` 从变体维度降为状态类 —— 全仓 47 份契约无一把它当变体,
同意;`picker`/`cascader`/`segmented` 的初判分类经我核对后修正为 input/navigation(PC 同源六分类口径)。
**验收(原样)**:
```
node tools/verify-mobile-isolation.mjs → [OK] 31 条断言(含新增 B1b 反向断言)
node tools/verify-uniapp.mjs → [OK] 9 条断言 · 50 个 SFC
node tools/verify-mobile-docs.mjs → [OK] 12 条断言 · 53 页
REG_BASE=… verify-mobile-site.mjs → [OK] 263 条断言 · 50 页(0 控制台错误、演示帧 281/281)
node tools/run-mobile-regression.mjs → 100% | 804/804 | 47/47 页 | N/A 10 | 0 超时
node tools/run-regression.mjs → 100% | 1405/1405 | 103/103 页(PC 侧未被污染)
node tools/pack-deploy.mjs --tar → OK(282 实现 / 47 文档页 / 47 测试页)
```
**部署**:暂存树哈希与本地一致(`936742e98d061a30`)→ 替换重建 → 五项验收全过、11 个新增组件页全 200、容器 0 error;
公网 `data.mobile.json` 报 **47 个组件**。
**剩余 23 个**(已写进 ROADMAP S7-P23):config-provider / Fab / BackTop / Drawer / Indexes / SideBar / Tabs /
Calendar / DateTimePicker / TreeSelect / CountDown / Empty / Footer / Image / ImageViewer / QRCode / Result /
Skeleton / Swiper / Watermark / DropdownMenu / Guide / PullDownRefresh。
### Docs · 模板页库 1 → 5(S4-P11:把组件组合成可复制的后台页面)
> 起因:用户要求「为组件库设置一个合适的 UI」。定位到 ROADMAP 中唯一「🟢 可开工」的任务 —— S4-P11 模板页库。
> 其依据写得很直白:「模板页是『能否直接用』的关键 —— 用户要的不是组件,是页面」。此前只有 1 个场景页。
- **新增 4 个模板页**(`site/scenario/`,纯 HTML + 原生 JS、零依赖):
- `login.html` — 输入类组件 + 表单校验。字段级错误提示(指出字段、说明原因、给修正方向)、
密码显隐切换、提交加载态 → 成功态、账号锁定等业务错误分支。
- `dashboard.html` — 侧边导航 + 4 张指标卡 + SVG 趋势图 + 渠道占比进度条 + 待办表格 + 分页。
时间范围切换会重算指标与图表;含折叠面板。
- `order-list.html` — 筛选表单 + 表格 + 批量操作条 + 分页 + 详情抽屉。
37 条示范订单;筛选/全选/批量审核/抽屉(Esc 关闭、焦点归还)全部可用。
- `settings.html` — 侧边分区 + 表单 + 开关组 + 单选/复选组 + 危险操作二次确认。
- **接入首页**:`site/app.js` 首页新增「模板页库」区块(5 张卡片,新窗口打开);`site/style.css` 补 `.home-note`。
- **sitemap**:`build-site.ps1` 把 5 个模板页写入 sitemap(521 URL)。新增行全为 ASCII,守住 PS5.1 的 ASCII-only 约束。
- **i18n**:`site/i18n.js` 补 13 条英文(模板页库标题与 5 张卡片),中文源串作键的既有约定不变;`verify:i18n` 316 键全覆盖。
- **新增门禁 `npm run verify:templates`**(`tools/verify-templates.mjs`,54 条浏览器断言,已接入 CI):
令牌生效 / 非白屏 / 零控制台错误 / 零 4xx / 逐页交互 / 375–1024px 无横向溢出。
- **判据修正(`tools/verify-site-routing.mjs`)**:sitemap URL 总数原为硬编码 `1 + 组件数×5`,
现改为「**按 `site/scenario/` 磁盘上实际存在的 .html 逐个核对 + 计入总数**」。
理由:硬编码在新增模板页时只会静默漏检(sitemap 少了页面而断言仍绿);
改后「新增/删除模板页却忘记同步 `build-site.ps1`」会直接红。**这是加强而非放宽** —— 新增了 5 条逐文件断言。
**模板页实测(原样)**:
```
node tools/verify-templates.mjs → [templates] OK — 54 checks passed
node tools/verify-site-routing.mjs → [routing] OK: 103 components / 412 platform shells / 521 sitemap URLs
node tools/verify-i18n.mjs → [i18n] OK — 16 checks passed(316 keys covered)
node tools/run-regression.mjs → 100% | 1405/1405 | 103/103 页 | N/A 50(八次连跑一致)
```
**过程中被测试逮到并修掉的 4 处真实缺陷**(都不是判据问题):
1. **进度条高度渲染为 0** —— `components.css` 只聚合 6 个**核心**组件,`ProgressVariants` / `DashboardCard`
这类扩展组件的样式根本没被加载(类名在、样式缺,页面不报错、只是画不出来)。
修法:4 个模板页按需 `` 各自的 `frameworks/*.css`(这些文件均为纯令牌自包含),
而不是在模板页里重画一遍组件。
2. **指标卡标题与图标未两端对齐**(`display:block` 而非 `flex`)—— 与上条同根因。
3. **设置页脏检查存在永久闩锁** —— 原写法 `if (saved) return` 导致「保存过一次」之后表单再也不会被标记为脏;
且「放弃修改」只硬编码还原 5 个字段,IP 白名单与通知勾选漏在还原之外。
修法:改为**快照式**脏检查(基线 = 上次保存值,逐字段比对),于是「改回原值自动回到干净态」,
日后新增字段也不必再补还原代码。
4. **保存竞态** —— 保存有 700ms 模拟延迟,期间「放弃修改」仍可点,会拿到上一份基线,表现为数据被还原成旧值。
修法:保存进行中两个按钮都禁用。
### Mobile · 批次推进:移动端组件 5 → 36(三批子 agent + 合并脚本 + 修行为断言静默缺口)
> 起因:用户要求「参考 TDesign 移动端组件清单,按批次补全缺的组件」,随后 `/son`(派子 agent)、`/agi`(全自主)。
> 参考清单 67 个组件,本仓库已覆盖 36 个(含此前的 18 个);流程按 `PLATFORMS.md` §四「六步」固化。
- **三批子 agent 产出 18 个组件**(每个 = 6 端实现 + 规格片段 + 契约 = 8 文件,共 144 文件):
- 批次 A:`mobile-icon` / `mobile-layout` / `mobile-link` / `mobile-loading`
- 批次 B:`mobile-avatar` / `mobile-list` / `mobile-collapse` / `mobile-progress`
- 批次 C:`mobile-input` / `mobile-search` / `mobile-switch` / `mobile-stepper` / `mobile-textarea`
- 批次 D:`mobile-form` / `mobile-radio` / `mobile-checkbox` / `mobile-slider` / `mobile-rate`
规格从 §19 连续到 §36(共 36 节);索引 18 → **36 个组件**,实现文件 108 → **216**。
- **新增可复用的合并脚本 `tools/merge-mobile-batch.mjs`**:子 agent 一律不写 `index.json`(避免并发写同一文件),
由主 agent 用该脚本统一合并 —— 它做三件事且幂等:① 按编号把 `spec/parts/*.md` 拼进规格原文(同编号则只归档不拼接);
② 把 `frameworks-mobile/` 里未登记的组件(校验 6 端文件齐全 + 契约存在)写进索引;③ 已拼接的片段归档到 `parts/_merged/`。
- **修掉一个静默缺口(子 agent 复核时发现,价值最高的一条)**:`tests/mobile/_behaviors.js` 的 `PILOTS` 是**硬编码白名单**
(只有最初 5 个组件),于是批次新增组件即使在演示页写了 `data-behavior`,引擎也直接 `return []` ——
**行为断言静默不跑,断言总数悄悄少一截**。修法:作用范围改为「演示页里**真的**写了 `[data-behavior]` 的组件」
∪ 兜底名单,由 `build-mobile.mjs` 在生成测试页时把派生结果注入 `window.__koleBehaviorSlugs`。
实测:行为断言覆盖 5 → **31 个组件**,断言条数 8 → **34 条**(全部 pass)。
- **修掉一处判据过窄(上一轮遗留)**:`pack-deploy.mjs` 的「frameworks-mobile 实现文件数」按索引动态算,
但打包时批次文件已在磁盘而尚未合并进索引 → 断言误报。现在按「索引组件数 × 端数」与磁盘实际双向核对,
不一致时直接指出差额(本次实测:156 vs 186,正是未合并的批次 C)。
- **修掉一处探针误判**:`verify-mobile-site.mjs` 统计总览页演示帧时只看到 20/26 ——
预览帧是 `loading="lazy"`,**视口外的帧不会加载**(浏览器行为,不是缺陷)。判据改为先滚到底再判定,
实测 26/26、现在 36/36 全部正常渲染。
- **子 agent 自修的真实缺陷**(都在报告里给了证据):`