Files
aurora-admin/site/m/changelog.html
T
aurora-admin f1fbfc2ddb
Regression / regression (push) Canceled after 0s
feat(品牌标识): 几何 K 图标(favicon/顶栏标记/theme-color) + 并行会话成果入库
## 品牌标识(本次会话)

起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」,
那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」,
移动端站已是「K」;移动端文档站则完全没有 favicon)。

- 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边;
  描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」
- 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark);
  顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28))
- 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg)
- 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留)
- 移动端 7 个模板补 favicon(此前计数 0)

验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/
theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致;
两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。

## 并行会话成果(本次一并入库)

- 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具
  + IconPreview 预览页 + ICON-SPEC.md 冻结规格
- 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁
- PC 组件:103 个大后台组件 / 组件11 批次
- uni-app:PC 端试点 + 移动端端实现 + 真实编译验证

## 工程

- .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库)
- CHANGELOG 补品牌标识条目
- ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
2026-09-21 10:05:48 +08:00

1138 lines
107 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
(function () {
var m = 'auto';
try { var v = localStorage.getItem('kole-mode'); if (v === 'light' || v === 'dark' || v === 'auto') m = v; } catch (e) {}
var dark = m === 'dark' || (m === 'auto' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList.toggle('kole-dark', !!dark);
document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
})();
</script>
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Crect%20width='24'%20height='24'%20rx='6'%20fill='%232F54EB'/%3E%3Cg%20fill='none'%20stroke='%23FFFFFF'%20stroke-width='2.25'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M7%205.5V18.5'/%3E%3Cpath%20d='M17%205.5l-6.25%204.06'/%3E%3Cpath%20d='M10.75%2014.44L17%2018.5'/%3E%3C/g%3E%3C/svg%3E">
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1C1F26" media="(prefers-color-scheme: dark)">
<title>更新日志 — Kole UI Mobile</title>
<meta name="description" content="Kole UI 移动端的变更记录:按时间倒序,来自仓库根 CHANGELOG.md 中与移动端相关的段落(构建时提取,不手抄)。">
<link rel="stylesheet" href="../../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="m-top">
<div class="m-top-inner">
<a class="m-logo" href="index.html">
<span class="m-logo-mark" aria-hidden="true"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 5.5V18.5"/><path d="M17 5.5l-6.25 4.06"/><path d="M10.75 14.44L17 18.5"/></svg></span>
<span class="m-logo-text">Kole UI<em>移动端</em></span>
</a>
<div class="m-ver-picker" id="m-ver-picker">
<button id="m-ver-trigger" class="m-ver-trigger" type="button" data-single="1" aria-haspopup="listbox" aria-expanded="false" aria-label="选择版本" title="文档版本(清单来自部署根的 versions.json)">
<span id="m-ver">v1.0.0</span>
<svg class="m-ver-caret" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div id="m-ver-menu" class="m-ver-menu hidden" role="listbox" aria-label="文档版本">
<div class="m-ver-menu-head" id="m-ver-head">文档版本</div>
</div>
</div>
<nav class="m-nav"> <a href="index.html">组件总览</a>
<a href="guide.html">快速开始</a>
<a href="design.html">设计规范</a>
<a href="faq.html">常见问题</a>
<a href="changelog.html" class="active" aria-current="page">更新日志</a></nav>
<div class="m-mode">
<button id="m-mode-trigger" class="m-mode-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-label="主题模式" title="主题模式">
<svg class="m-mode-icon" id="m-icon-moon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<svg class="m-mode-icon" id="m-icon-sun" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:none"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
<svg class="m-mode-icon" id="m-icon-auto" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:none"><rect x="3" y="4" width="18" height="13" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
<span id="m-mode-cur">自动模式</span>
<svg class="m-mode-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div id="m-mode-menu" class="m-menu hidden" role="listbox" aria-label="主题模式">
<div class="m-menu-head">主题模式</div>
<button class="m-menu-opt" type="button" role="option" data-mode="light" aria-selected="false"><span data-mode-label="light">日间模式</span></button>
<button class="m-menu-opt" type="button" role="option" data-mode="dark" aria-selected="false"><span data-mode-label="dark">夜间模式</span></button>
<button class="m-menu-opt" type="button" role="option" data-mode="auto" aria-selected="true"><span data-mode-label="auto">自动模式</span></button>
</div>
</div>
</div>
</header>
<div class="m-body">
<details class="m-side" open>
<summary><span>移动端导航</span><span class="cur">更新日志</span></summary>
<nav>
<div class="m-side-section"><span>平台</span></div>
<a class="m-side-link m-side-platform" href="../index.html"><span class="sp-top"><span class="sp-name">PC 端组件</span></span><span class="sp-sub">103 个中后台组件 · 5 端</span></a>
<a class="m-side-link m-side-platform" href="index.html"><span class="sp-top"><span class="sp-name">移动端组件</span><span class="sp-badge">当前</span></span><span class="sp-sub">47 个组件 · 6 端 · 含 uni-app</span></a>
<div class="m-side-section"><span>开发指南</span></div>
<a class="m-side-link" href="index.html">组件总览</a>
<a class="m-side-link" href="guide.html">快速开始</a>
<a class="m-side-link" href="design.html">设计规范</a>
<a class="m-side-link" href="faq.html">常见问题</a>
<a class="m-side-link is-active" href="changelog.html" aria-current="page">更新日志</a>
<a class="m-side-link" href="platform.html">平台与端<em>移动端</em></a>
<a class="m-side-link" href="../../tests/mobile/index.html" target="_blank">测试与回归<em>移动端</em></a>
<div class="m-side-section"><span>组件</span><span class="cnt">47</span></div>
<div class="m-side-group"><span>通用</span><span class="cnt">5</span></div>
<a class="m-side-link" href="component/mobile-button.html">按钮<em>Button</em></a>
<a class="m-side-link" href="component/mobile-divider.html">分割线<em>Divider</em></a>
<a class="m-side-link" href="component/mobile-icon.html">图标<em>Icon</em></a>
<a class="m-side-link" href="component/mobile-layout.html">布局<em>Layout</em></a>
<a class="m-side-link" href="component/mobile-link.html">链接<em>Link</em></a>
<div class="m-side-group"><span>导航</span><span class="cnt">6</span></div>
<a class="m-side-link" href="component/navbar.html">顶部导航栏<em>NavBar</em></a>
<a class="m-side-link" href="component/tabbar.html">底部标签栏<em>TabBar</em></a>
<a class="m-side-link" href="component/mobile-grid.html">宫格<em>Grid</em></a>
<a class="m-side-link" href="component/mobile-steps.html">步骤条<em>Steps</em></a>
<a class="m-side-link" href="component/mobile-segmented.html">分段器<em>Segmented</em></a>
<a class="m-side-link" href="component/mobile-sticky.html">吸顶容器<em>Sticky</em></a>
<div class="m-side-group"><span>数据录入</span><span class="cnt">16</span></div>
<a class="m-side-link" href="component/mobile-numberkeyboard.html">数字键盘<em>NumberKeyboard</em></a>
<a class="m-side-link" href="component/mobile-datepicker.html">日期选择器<em>DatePicker</em></a>
<a class="m-side-link" href="component/mobile-input.html">输入框<em>Input</em></a>
<a class="m-side-link" href="component/mobile-search.html">搜索框<em>Search</em></a>
<a class="m-side-link" href="component/mobile-stepper.html">步进器<em>Stepper</em></a>
<a class="m-side-link" href="component/mobile-switch.html">开关<em>Switch</em></a>
<a class="m-side-link" href="component/mobile-textarea.html">多行文本框<em>Textarea</em></a>
<a class="m-side-link" href="component/mobile-checkbox.html">多选框<em>Checkbox</em></a>
<a class="m-side-link" href="component/mobile-form.html">表单<em>Form</em></a>
<a class="m-side-link" href="component/mobile-radio.html">单选框<em>Radio</em></a>
<a class="m-side-link" href="component/mobile-rate.html">评分<em>Rate</em></a>
<a class="m-side-link" href="component/mobile-slider.html">滑动选择器<em>Slider</em></a>
<a class="m-side-link" href="component/mobile-cascader.html">级联选择器<em>Cascader</em></a>
<a class="m-side-link" href="component/mobile-colorpicker.html">颜色选择器<em>ColorPicker</em></a>
<a class="m-side-link" href="component/mobile-picker.html">选择器<em>Picker</em></a>
<a class="m-side-link" href="component/mobile-upload.html">上传<em>Upload</em></a>
<div class="m-side-group"><span>数据展示</span><span class="cnt">8</span></div>
<a class="m-side-link" href="component/cell.html">单元格<em>Cell</em></a>
<a class="m-side-link" href="component/mobile-badge.html">徽标<em>Badge</em></a>
<a class="m-side-link" href="component/mobile-tag.html">标签<em>Tag</em></a>
<a class="m-side-link" href="component/mobile-avatar.html">头像<em>Avatar</em></a>
<a class="m-side-link" href="component/mobile-list.html">列表<em>List</em></a>
<a class="m-side-link" href="component/mobile-collapse.html">折叠面板<em>Collapse</em></a>
<a class="m-side-link" href="component/mobile-table.html">表格<em>Table</em></a>
<a class="m-side-link" href="component/mobile-typography.html">排版<em>Typography</em></a>
<div class="m-side-group"><span>反馈</span><span class="cnt">12</span></div>
<a class="m-side-link" href="component/actionsheet.html">动作面板<em>ActionSheet</em></a>
<a class="m-side-link" href="component/pullrefresh.html">下拉刷新<em>PullRefresh</em></a>
<a class="m-side-link" href="component/swipecell.html">滑动单元格<em>SwipeCell</em></a>
<a class="m-side-link" href="component/mobile-popup.html">弹出层<em>Popup</em></a>
<a class="m-side-link" href="component/mobile-toast.html">轻提示<em>Toast</em></a>
<a class="m-side-link" href="component/mobile-dialog.html">对话框<em>Dialog</em></a>
<a class="m-side-link" href="component/mobile-noticebar.html">通知栏<em>NoticeBar</em></a>
<a class="m-side-link" href="component/mobile-loading.html">加载<em>Loading</em></a>
<a class="m-side-link" href="component/mobile-progress.html">进度条<em>Progress</em></a>
<a class="m-side-link" href="component/mobile-message.html">消息通知<em>Message</em></a>
<a class="m-side-link" href="component/mobile-overlay.html">遮罩层<em>Overlay</em></a>
<a class="m-side-link" href="component/mobile-popover.html">弹出气泡<em>Popover</em></a>
</nav>
</details>
<main class="m-main">
<section class="m-hero">
<h1>更新日志</h1>
<p>移动端的变更记录,<b>构建时从仓库根 <code>CHANGELOG.md</code> 提取</b>(标题含「Mobile / 移动端」的段落),按时间倒序 —— 不手抄,避免站点与仓库两处说法不一致。完整历史(含 PC 侧)在仓库的 CHANGELOG.md。</p>
</section>
<section class="m-block">
<h2 id="entries">移动端相关变更</h2>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">图标系统(PC 5 端 + 移动端 6 端 + 全量图标库 + 预览页)</h3>
<p class="m-note">起因:用户要求「图标组件参考 TDesign / Element 等主流组件库,看他们的 icon 是怎么专门设计的、</p>
<p class="m-note">设计内容有哪些,覆盖他们全部拥有的 icon 图标和功能」。</p>
<p class="m-note">落地:<strong>2576 个图标</strong>(TDesign 线性 1334 + 面性 1020 + Element Plus 293,均 MIT),</p>
<p class="m-note">11 个端实现全部接通,新增 5 个构建/门禁工具与 1 个图标预览页。</p>
<p class="m-note">冻结规格:<code>.design_library/kole-ui/icons/ICON-SPEC.md</code>。</p>
<p class="m-sub">#### 新增:图标数据管线(离线可复现,上游校验和钉死)</p>
<ul class="m-steps">
<li><code>tools/fetch-icon-sources.mjs</code> — 拉取并<strong>归一化</strong>上游 SVG → <code>sources.json</code>。</li>
</ul>
<p class="m-sub"> 逐包校验 sha256(防「某天图标悄悄变了」);纯函数可单独 import(无副作用)。</p>
<ul class="m-steps">
<li><code>tools/build-icons.mjs</code> — 编译出 <code>manifest.json</code>(清单)/ <code>registry.json</code>(渲染数据)/</li>
</ul>
<p class="m-sub"> <code>viewbox.js</code>(viewBox 例外表)/ <code>core.js</code>(内联运行时)/ <code>ATTRIBUTION.md</code>(MIT 归属,分发必带)。</p>
<ul class="m-steps">
<li><code>tools/build-icon-aliases.mjs</code> — 别名层,让 <strong>Element UI 全部 282 个历史图标名</strong>与</li>
</ul>
<p class="m-sub"> 移动端既有 9 个字形名都能落到真实图标(<strong>旧调用点零破坏</strong>)。</p>
<ul class="m-steps">
<li><code>tools/inject-icon-table.mjs</code> — 把 core 表注入 11 个端实现的标记块,杜绝「11 份手抄表必然漂移」。</li>
<li><code>tools/verify-icons.mjs</code> — <strong>11 条静态断言</strong>(产物一致 / 无 <code>view-box</code> 残留 / 描边全 1.5 /</li>
</ul>
<p class="m-sub"> core 可渲染 / 别名不遮蔽 / 各端都能查到名 / 分组覆盖率 / 路径数字与源逐值一致)。</p>
<p class="m-sub">#### 修正:两个上游缺陷在构建期抹平(此处是本仓库的增量,不是照抄)</p>
<p class="m-sub">1. <strong><code>view-box</code> 拼写错误</strong>:TDesign <strong>全部 2356 个</strong> SVG 把属性写成 <code>view-box</code>(无效属性,</p>
<p class="m-sub"> 浏览器忽略 → 图标按默认视口缩放)。构建期两种拼写都读,产物统一写回规范的 <code>viewBox</code>。</p>
<p class="m-sub">2. <strong>描边宽度 2 → 1.5</strong>:上游为 <code>stroke-width=&quot;2&quot;</code>(24 网格),本仓库规范</p>
<p class="m-sub"> (<code>组件1.txt</code>「图标规范:线性图标,描边1.5px,尺寸16/20/24px」)要求 1.5,构建期归一。</p>
<p class="m-sub"> 另丢弃 <code>fill=&quot;transparent&quot;</code> 命中区路径(那是热区不是视觉内容)。</p>
<p class="m-sub">#### 修正:自测发现的数据损坏缺陷(构建期把坐标改错)</p>
<ul class="m-steps">
<li><strong>现象</strong>:归一化把路径数字四舍五入到 2 位小数,看似无害;但 SVG path 允许「隐式分隔」——</li>
</ul>
<p class="m-sub"> <code>q-13.005.48-22.5</code> 是<strong>三个</strong>数(<code>-13.005</code>、<code>.48</code>、<code>-22.5</code>,靠 <code>-</code> 和 <code>.</code> 当分隔符)。</p>
<p class="m-sub"> 把 <code>-13.005</code> 舍成 <code>-13</code> 后,后面的 <code>.48</code> <strong>粘成</strong> <code>-13.48</code> —— 那是另一个坐标,</p>
<p class="m-sub"> 图形画错<strong>且不报任何错</strong>。实测踩到 Element Plus 的 <code>quartz-watch</code>(靠浏览器控制台才发现)。</p>
<ul class="m-steps">
<li><strong>修法</strong>:原本有小数位的数字,输出<strong>至少保留 1 位小数</strong>(<code>-13.005</code> → <code>-13.0</code>),</li>
</ul>
<p class="m-sub"> 隐式相连的 <code>.48</code> 不可能再被吸收;并在 <code>optimizePath</code> 内<strong>复核数字序列</strong>,不一致就原样返回。</p>
<ul class="m-steps">
<li><strong>防复发</strong>:新增门禁断言 <strong>V8「路径数字与源逐值一致」</strong>,比对全部 2576 个图标。</li>
</ul>
<p class="m-sub"> 已做<strong>变异测试</strong>:把该 bug 重新注入 → V8 当场 FAIL(<code>数字个数 461 ≠ 源 462</code>),判据有区分力。</p>
<ul class="m-steps">
<li>代价:<code>registry.json</code> 707KB(修前 677KB,+30KB 换正确性)。</li>
</ul>
<p class="m-sub">#### 修正:非 <code>&lt;path&gt;</code> 图形被静默丢弃(覆盖率审计发现)</p>
<ul class="m-steps">
<li><strong>现象</strong>:解析器只认 <code>&lt;path d&gt;</code>,而上游有 <strong>36 个文件</strong>用 <code>&lt;circle cx cy r&gt;</code> / <code>&lt;ellipse&gt;</code></li>
</ul>
<p class="m-sub"> 表达图形(TDesign 的 <code>circle</code> / <code>round</code> / <code>brightness</code> / <code>image</code> 等)。这些图形被<strong>静默丢掉</strong> ——</p>
<p class="m-sub"> 图标页面上&quot;渲染成功&quot;却是空的,构建与回归都不报错。TDesign 的 <code>circle</code> 与 <code>round</code> 两个图标</p>
<p class="m-sub"> 因此整个消失,是<strong>覆盖率审计</strong>(对三个参考库逐名比对)才暴露的。</p>
<ul class="m-steps">
<li><strong>修法</strong>:解析器同时处理 <code>&lt;path&gt;</code> / <code>&lt;circle&gt;</code> / <code>&lt;ellipse&gt;</code>,把圆与椭圆等价换算成</li>
</ul>
<p class="m-sub"> path 的两段 arc(<code>M cx cy-r A r r 0 1 1 cx cy+r A r r 0 1 1 cx cy-r Z</code>)。</p>
<ul class="m-steps">
<li><strong>结果</strong>:图标数 2574 → <strong>2576</strong>;对三家参考库的覆盖率现为</li>
</ul>
<p class="m-sub"> <strong>TDesign 2356/2356、Element Plus 293/293(各 100%)</strong>、Element UI 280/282(余 2 个是解析碎片名)。</p>
<ul class="m-steps">
<li>校验:两个恢复的图标在浏览器实测有真实几何(path 长度 63 / 44,分别对应 r=10 与 r=7 的圆)。</li>
</ul>
<p class="m-sub">#### 分层与体积(按实测决定,不是拍脑袋)</p>
<ul class="m-steps">
<li><strong>core 151 个内联</strong>(<code>core.js</code> 35KB),<code>standard</code> 1989 / <code>extended</code> 434 按需加载。</li>
<li><strong>为何不全内联</strong>:全量 670KB,内联等于给每一页都加 670KB,直接违背 ROADMAP 的性能预算精神。</li>
</ul>
<p class="m-sub">#### 组件 API(PC 5 端 + 移动端 6 端,语义统一)</p>
<ul class="m-steps">
<li>props:<code>name</code> / <code>size</code>(关键字 <code>small|default|large|xlarge</code> <strong>或任意 CSS 长度</strong>,对齐 TDesign 双模式)/</li>
</ul>
<p class="m-sub"> <code>tone</code>(<code>default|brand|secondary|danger|success|warning</code>)/ <code>spin</code> / <code>label</code>。</p>
<ul class="m-steps">
<li><strong>向后兼容</strong>:PC 既有 <code>icon</code> prop(字形字符,默认 <code>'★'</code>)与移动端既有 9 名字形表全部保留,<code>name</code> 优先。</li>
<li>无障碍:<code>label</code> 有值 → <code>role=&quot;img&quot;</code> + <code>aria-label</code>,无值 → <code>aria-hidden</code>;</li>
</ul>
<p class="m-sub"> <code>spin</code> 在 <code>prefers-reduced-motion: reduce</code> 下停转;语义色对比度 ≥ 3:1(实测 5.10–5.87:1)。</p>
<ul class="m-steps">
<li><strong>viewBox 按「路径串 → 网格」反查而非按名</strong>:别名指向的 1024 网格图标若按名查会取错网格</li>
</ul>
<p class="m-sub"> (实测影响 160 个名字,会导致图标拉伸变形)。</p>
<p class="m-sub">#### 新增:图标预览页 <code>site/icon-preview.html</code></p>
<p class="m-sub">规范 <code>组件5.txt</code>「图标预览 IconPreview:展示系统所有图标 / 网格布局,支持搜索 /</p>
<p class="m-sub">点击图标复制名称或代码 / 可调整图标大小」声明已久但从未实现。本次补齐:</p>
<p class="m-sub">2576 图标网格、搜索、点击复制名称、Shift+点击复制内联 SVG、四档尺寸切换、仅线性过滤;</p>
<p class="m-sub">分两段加载(索引 51KB 先出骨架,路径数据按需);</p>
<p class="m-sub">入口挂在图标组件页代码条 footer,i18n 双语(<code>图标预览</code> / <code>Icon gallery</code>)。</p>
<p class="m-sub">#### 已知未覆盖 / 遗留</p>
<ul class="m-steps">
<li>移动端 uni-app 端用 <strong>CSS mask(data-URI SVG)</strong> 承载字形(小程序无 DOM、无内联 SVG)。</li>
</ul>
<p class="m-sub"> H5 目标已实测;<strong>mp-weixin / app-plus 的遮罩渲染未在真机验证</strong>,已登记进契约 <code>unknowns</code>。</p>
<ul class="m-steps">
<li><code>verify-emits-parse.mjs</code> 的文件数期望值陈旧(158,应为 103×2=206)—— <strong>本次之前就存在</strong></li>
</ul>
<p class="m-sub"> (<code>git show HEAD:</code> 确认),已登记为 ROADMAP <code>S8-P1</code>。</p>
<ul class="m-steps">
<li><code>verify-cross-platform.mjs</code> 把注入的图标数据表误判为组件类名(540 条里 533 条是噪声)——</li>
</ul>
<p class="m-sub"> 已登记为 ROADMAP <code>S8-P2</code>(判据不放宽,让门禁学会跳过数据块)。</p>
<ul class="m-steps">
<li><code>组件5.txt</code> 声明的 47 个工具/模板组件里 <strong>46 个仍未实现</strong>(本次只补了 IconPreview)——</li>
</ul>
<p class="m-sub"> 已登记为 ROADMAP <code>S8-P3</code>,含「走 <code>frameworks/</code> 正式组件还是 <code>site/</code> 工具页」的决策点。</p>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 顶栏对齐 PC + 平台入口去重(移动端站导航栏改造)</h3>
<p class="m-note">起因:用户要求「移动端组件的导航栏参考 PC 端组件导航栏优化(上面跳转 PC 和移动的下拉框也重复了)」。</p>
<p class="m-note">实测确认两条问题,逐项修掉。</p>
<ul class="m-steps">
<li><strong>去重:平台切换从顶栏移除</strong>。移动端站顶栏原有一个 <code>[PC 端] [移动端]</code> 分段胶囊,与同一页左栏的</li>
</ul>
<p class="m-sub"> 「平台」组指向同一跳转(实测 DOM:<code>a.mp-item</code> 与 <code>a.m-side-link.m-side-platform</code> 的 href 均为</p>
<p class="m-sub"> <code>../index.html</code>)—— 同一件事两处入口。PC 站顶栏从来没有这个控件(只有左栏一处),</p>
<p class="m-sub"> 故删顶栏那份,两端现在都是<strong>左栏唯一入口</strong>。改动同时断言「删的是重复的那个,不是删光」:</p>
<p class="m-sub"> <code>verify-mobile-docs</code> 的 E7 从「断言顶栏有平台切换」改为「断言左栏有平台入口」。</p>
<ul class="m-steps">
<li><strong>顶栏逐项对齐 PC</strong>(此前只有结构同名,控件实现不同源):</li>
<li>高度 56px → <strong>60px</strong>;内容加 <strong>1180px 居中容器</strong>(<code>.m-top-inner</code>,与 <code>.m-body</code> 同宽,</li>
</ul>
<p class="m-sub"> logo 与左栏左边缘对齐;此前内容铺满视口、与下方内容左边缘错位)。</p>
<ul class="m-steps">
<li>激活项补 <strong>2px 底部品牌指示条</strong>(<code>.m-nav a.active::after</code>,PC <code>.topnav a.active::after</code> 同款);</li>
</ul>
<p class="m-sub"> 此前只有文字变色。</p>
<ul class="m-steps">
<li>版本角标 → <strong>可点版本下拉</strong>:<code>&lt;span class=&quot;m-ver&quot;&gt;</code> 换成 <code>#m-ver-trigger</code> + <code>#m-ver-menu</code>,</li>
</ul>
<p class="m-sub"> 清单从部署根 <code>versions.json</code> / <code>site/versions.json</code> 两份按序取(不并行 —— 快照页那份必然 404,</p>
<p class="m-sub"> 并行会把 404 记进控制台,而站点门禁的零控制台错误是硬断言);拿不到清单时</p>
<p class="m-sub"> <code>data-single=&quot;1&quot;</code> 退回不可点角标(与 PC 同一语义)。路径校验只放行 <code>&quot;..&quot;</code> 与 <code>x.y.z</code>,防清单把人带去外站。</p>
<ul class="m-steps">
<li>主题触发器补 <strong>三态图标</strong>(月亮 / 太阳 / 显示器,PC <code>.theme-icon</code> 同款):显式选择时按时态给图标,</li>
</ul>
<p class="m-sub"> 自动模式恒为显示器图标;此前只有文字标签。</p>
<ul class="m-steps">
<li><strong>零运行时依赖不变</strong>:新增的版本/主题逻辑是构建期内联脚本(<code>COPY_SCRIPT</code>),不引包。</li>
<li><strong>顺手修掉一处过期硬编码</strong>:移动端站文案里的 PC 组件数写死为「79」,</li>
</ul>
<p class="m-sub"> 而 S6-P21 组件族参数化后 PC 索引已是 <strong>103</strong>(实测 <code>components/index.json</code>)。改为构建期从 PC 索引实测读取</p>
<p class="m-sub"> (模板占位符由 <code>build-mobile.mjs</code> 灌入),拿不到索引时退回中性表述而非编造数字。</p>
<p class="m-sub"> 涉及:总览页、FAQ、平台与端页的正文与覆盖矩阵、以及 <code>dist/mobile/README.md</code> 与 manifest 的 <code>isolatedFrom</code>。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>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 全绿)</code></pre></div>
<p class="m-sub">浏览器实测(Playwright,1440px):顶栏 60px / 内容容器实测 1180px / 激活项 <code>::after</code> 实测</p>
<p class="m-sub"><code>2px rgb(47, 84, 235)</code> / 顶栏内 <code>.m-platform</code> 计数 0 / 版本下拉实测 1 个选项且 <code>data-single=&quot;0&quot;</code> /</p>
<p class="m-sub">切夜间后图标 <code>m-icon-sun:block</code> 且 <code>html.kole-dark</code> 生效;320–768px 四档无横向溢出、零控制台错误。</p>
<p class="m-sub">四道移动端门禁全绿:隔离 31 条 / uni-app 9 条 × 50 SFC / 文档 12 条 · 53 页 / 站点 263 条 · 50 页。</p>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 批次 E/F:移动端组件 36 → 47(两批子 agent 并行 + 新增「磁盘↔索引」双向门禁)</h3>
<p class="m-note">继续 <code>/son</code> 派活推进 TDesign 清单覆盖。批次 E(typography / segmented / sticky / overlay / popover / message)</p>
<p class="m-note">与批次 F(picker / cascader / colorpicker / upload / table)并行产出 11 个组件 × 8 文件 = 88 文件,</p>
<p class="m-note">规格 §37–§47。索引 36 → <strong>47</strong>,实现 216 → <strong>282</strong>,移动端回归 597 → <strong>804 条断言</strong>。</p>
<ul class="m-steps">
<li><strong>给每个组件预分配规格编号</strong>(§37–§47):上一轮两个子 agent 并存时存在编号撞号风险(撞号会被合并脚本按幂等规则整节跳过),</li>
</ul>
<p class="m-sub"> 本轮在派单里逐组件指定编号,合并时零冲突、规格 §1–47 连续。</p>
<ul class="m-steps">
<li><strong>新增反向门禁 B1b(两个子 agent 独立报的同一条缺口)</strong>:<code>verify-mobile-isolation.mjs</code> 原先只查</li>
</ul>
<p class="m-sub"> 「索引里的组件有没有实现文件」,<strong>不查「磁盘上的实现文件有没有进索引」</strong>——于是子 agent 产出的组件在合并前</p>
<p class="m-sub"> 完全不被任何门禁覆盖(不在索引里 = 不被检查),属静默缺口。现补反向断言:磁盘前缀集合必须等于索引前缀集合,</p>
<p class="m-sub"> 不一致直接报「未登记:X, Y(跑 node tools/merge-mobile-batch.mjs 合并)」。断言数 30 → <strong>31</strong>。</p>
<ul class="m-steps">
<li><strong>修掉两处探针误判(都是工具侧,不是内容缺陷)</strong>:</li>
<li><code>verify-mobile-site.mjs</code> 的总览页在 47 个卡片时只渲染 43 个 —— 预览帧 <code>loading=&quot;lazy&quot;</code>,</li>
</ul>
<p class="m-sub"> <strong>一次 <code>scrollTo</code> 到底会跳过中间帧</strong>。改为逐段滚动(每 800px 一次)后再判定,实测 281/281。</p>
<ul class="m-steps">
<li>同一脚本把收集器的 <code>net::ERR_ABORTED</code> 记为控制台错误 —— 那是 <code>_collect.html</code> 在帧装载完成后</li>
</ul>
<p class="m-sub"> <strong>主动</strong> <code>removeAttribute('src')</code> 释放帧造成的在途请求中断,是设计行为。判据改为忽略 <code>ERR_ABORTED</code>,</p>
<p class="m-sub"> 其余(404 / ERR_INVALID_URL 等)仍然计错。</p>
<ul class="m-steps">
<li><strong>子 agent 自修的真实缺陷(都带证据)</strong>:<code>Sticky</code> 的 <code>is-stuck</code> 判定在默认偏移下滚动 0 就已贴合(改为与容器 padding box + 偏移量比较,含 1px 边框补偿);</li>
</ul>
<p class="m-sub"> <code>Message.jsx</code> 把 <code>onClose</code> 写进 <code>useEffect</code> 依赖 → 宿主传内联箭头函数时计时器永远不到点(回调移到 <code>useRef</code>);</p>
<p class="m-sub"> <code>Sticky</code>/<code>Message</code> 演示页缺实际执行的监听器致 <code>data-behavior</code> 静默失效;</p>
<p class="m-sub"> <code>Table</code> 可点行不可聚焦(真实引擎报 <code>matrix:keyboard-reachable</code>)→ 补 <code>tabindex</code>/<code>role</code>/<code>aria-selected</code>/Enter 键路径;</p>
<p class="m-sub"> <code>ColorPicker</code> 的色板 hex 写在行内 style 被 <code>no-hardcode-hex</code> 判据扫到 → 改走行内自定义属性(CSS 零 hex,色值只存数据层)。</p>
<ul class="m-steps">
<li><strong>规划偏差(子 agent 主动对齐既有约定)</strong>:<code>disabled</code> 从变体维度降为状态类 —— 全仓 47 份契约无一把它当变体,</li>
</ul>
<p class="m-sub"> 同意;<code>picker</code>/<code>cascader</code>/<code>segmented</code> 的初判分类经我核对后修正为 input/navigation(PC 同源六分类口径)。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>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 测试页)</code></pre></div>
<p class="m-sub"><strong>部署</strong>:暂存树哈希与本地一致(<code>936742e98d061a30</code>)→ 替换重建 → 五项验收全过、11 个新增组件页全 200、容器 0 error;</p>
<p class="m-sub">公网 <code>data.mobile.json</code> 报 <strong>47 个组件</strong>。</p>
<p class="m-sub"><strong>剩余 23 个</strong>(已写进 ROADMAP S7-P23):config-provider / Fab / BackTop / Drawer / Indexes / SideBar / Tabs /</p>
<p class="m-sub">Calendar / DateTimePicker / TreeSelect / CountDown / Empty / Footer / Image / ImageViewer / QRCode / Result /</p>
<p class="m-sub">Skeleton / Swiper / Watermark / DropdownMenu / Guide / PullDownRefresh。</p>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 批次推进:移动端组件 5 → 36(三批子 agent + 合并脚本 + 修行为断言静默缺口)</h3>
<p class="m-note">起因:用户要求「参考 TDesign 移动端组件清单,按批次补全缺的组件」,随后 <code>/son</code>(派子 agent)、<code>/agi</code>(全自主)。</p>
<p class="m-note">参考清单 67 个组件,本仓库已覆盖 36 个(含此前的 18 个);流程按 <code>PLATFORMS.md</code> §四「六步」固化。</p>
<ul class="m-steps">
<li><strong>三批子 agent 产出 18 个组件</strong>(每个 = 6 端实现 + 规格片段 + 契约 = 8 文件,共 144 文件):</li>
<li>批次 A:<code>mobile-icon</code> / <code>mobile-layout</code> / <code>mobile-link</code> / <code>mobile-loading</code></li>
<li>批次 B:<code>mobile-avatar</code> / <code>mobile-list</code> / <code>mobile-collapse</code> / <code>mobile-progress</code></li>
<li>批次 C:<code>mobile-input</code> / <code>mobile-search</code> / <code>mobile-switch</code> / <code>mobile-stepper</code> / <code>mobile-textarea</code></li>
<li>批次 D:<code>mobile-form</code> / <code>mobile-radio</code> / <code>mobile-checkbox</code> / <code>mobile-slider</code> / <code>mobile-rate</code></li>
</ul>
<p class="m-sub"> 规格从 §19 连续到 §36(共 36 节);索引 18 → <strong>36 个组件</strong>,实现文件 108 → <strong>216</strong>。</p>
<ul class="m-steps">
<li><strong>新增可复用的合并脚本 <code>tools/merge-mobile-batch.mjs</code></strong>:子 agent 一律不写 <code>index.json</code>(避免并发写同一文件),</li>
</ul>
<p class="m-sub"> 由主 agent 用该脚本统一合并 —— 它做三件事且幂等:① 按编号把 <code>spec/parts/*.md</code> 拼进规格原文(同编号则只归档不拼接);</p>
<p class="m-sub"> ② 把 <code>frameworks-mobile/</code> 里未登记的组件(校验 6 端文件齐全 + 契约存在)写进索引;③ 已拼接的片段归档到 <code>parts/_merged/</code>。</p>
<ul class="m-steps">
<li><strong>修掉一个静默缺口(子 agent 复核时发现,价值最高的一条)</strong>:<code>tests/mobile/_behaviors.js</code> 的 <code>PILOTS</code> 是<strong>硬编码白名单</strong></li>
</ul>
<p class="m-sub"> (只有最初 5 个组件),于是批次新增组件即使在演示页写了 <code>data-behavior</code>,引擎也直接 <code>return []</code> ——</p>
<p class="m-sub"> <strong>行为断言静默不跑,断言总数悄悄少一截</strong>。修法:作用范围改为「演示页里<strong>真的</strong>写了 <code>[data-behavior]</code> 的组件」</p>
<p class="m-sub"> ∪ 兜底名单,由 <code>build-mobile.mjs</code> 在生成测试页时把派生结果注入 <code>window.__koleBehaviorSlugs</code>。</p>
<p class="m-sub"> 实测:行为断言覆盖 5 → <strong>31 个组件</strong>,断言条数 8 → <strong>34 条</strong>(全部 pass)。</p>
<ul class="m-steps">
<li><strong>修掉一处判据过窄(上一轮遗留)</strong>:<code>pack-deploy.mjs</code> 的「frameworks-mobile 实现文件数」按索引动态算,</li>
</ul>
<p class="m-sub"> 但打包时批次文件已在磁盘而尚未合并进索引 → 断言误报。现在按「索引组件数 × 端数」与磁盘实际双向核对,</p>
<p class="m-sub"> 不一致时直接指出差额(本次实测:156 vs 186,正是未合并的批次 C)。</p>
<ul class="m-steps">
<li><strong>修掉一处探针误判</strong>:<code>verify-mobile-site.mjs</code> 统计总览页演示帧时只看到 20/26 ——</li>
</ul>
<p class="m-sub"> 预览帧是 <code>loading=&quot;lazy&quot;</code>,<strong>视口外的帧不会加载</strong>(浏览器行为,不是缺陷)。判据改为先滚到底再判定,</p>
<p class="m-sub"> 实测 26/26、现在 36/36 全部正常渲染。</p>
<ul class="m-steps">
<li><strong>子 agent 自修的真实缺陷</strong>(都在报告里给了证据):<code>&lt;button&gt;</code> 嵌在 <code>&lt;label&gt;</code> 里(非法嵌套);</li>
</ul>
<p class="m-sub"> Stepper 根 <code>overflow:hidden</code> 裁掉小尺寸档的热区外扩(命中测试证实);同一状态块挂两条 <code>data-behavior</code> 互相污染;</p>
<p class="m-sub"> Textarea 计数示例是死数据;Slider 根元素 <code>cursor:pointer</code> 让数值文案被判「鼠标专用交互」(真实引擎报的 <code>matrix:keyboard-reachable</code>)。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>node tools/verify-mobile-isolation.mjs → [OK] 30 条断言
node tools/verify-uniapp.mjs → [OK] 9 条断言 · 39 个 SFC
node tools/verify-mobile-docs.mjs → [OK] 12 条断言 · 42 页(站内链接全可解析)
REG_BASE=… verify-mobile-site.mjs → [OK] 208 条断言 · 39 页(0 控制台错误、演示帧 220/220、320–768px 无溢出)
node tools/run-mobile-regression.mjs → 100% | 597/597 | 36/36 页 | N/A 10 | 0 超时
node tools/run-regression.mjs → 100% | 1405/1405 | 103/103 页(PC 侧未被污染)
node tools/pack-deploy.mjs --tar → OK(2349 文件 / 36 组件 × 6 端 = 216 实现 / 文档页 36 / 测试页 36)</code></pre></div>
<p class="m-sub"><strong>部署</strong>:暂存树哈希与本地一致(<code>719d66f2fce6875a</code>)→ 替换重建 → 服务器端五项验收全过、容器 0 error;</p>
<p class="m-sub">公网 <code>data.mobile.json</code> 报 <strong>36 个组件</strong>,抽查 8 个新增组件页全 200。</p>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 移动端组件第二批收尾(弹出层系 + 展示系 + 输入系):Toast / Dialog / Grid / Steps / NoticeBar / NumberKeyboard / DatePicker(索引 11 → 18)</h3>
<p class="m-note">起因:按批次继续开工,把 ROADMAP S7-P23 的剩余 7 个组件做完 —— 规格 §12~§18 本轮新写</p>
<p class="m-note">(契约的唯一授权来源必须先于实现),再按 <code>PLATFORMS.md</code> §四 六步流程落地为 6 端实现。</p>
<p class="m-note">索引 11 → <strong>18</strong>,<code>frameworks-mobile/</code> 66 → <strong>108</strong> 个文件(18 × 6 端)。</p>
<ul class="m-steps">
<li><strong>七个新组件</strong>(规格小节 → slug / 前缀):</li>
<li><strong>轻提示 Toast</strong>(§12 → <code>mobile-toast</code>):<code>tone</code> 五语气 + <code>position</code> 三位置 + <code>mask</code>;</li>
</ul>
<p class="m-sub"> 容器 <code>role=&quot;status&quot;</code> + <code>aria-live=&quot;polite&quot;</code>(结果朗读一次不反复打断);<code>tone=loading</code> 时图标旋转并标 <code>aria-busy</code>。</p>
<ul class="m-steps">
<li><strong>对话框 Dialog</strong>(§13 → <code>mobile-dialog</code>):<code>variant</code> = confirm(两按钮等宽)/ alert(单按钮铺满)+ <code>tone</code> = danger + <code>round</code>;</li>
</ul>
<p class="m-sub"> <code>closeOnMask</code> 可关;<code>role=&quot;dialog&quot;</code> + <code>aria-modal</code> + <code>aria-labelledby</code>;按钮热区 ≥44px。</p>
<ul class="m-steps">
<li><strong>宫格 Grid</strong>(§14 → <code>mobile-grid</code>):<code>columns</code> 2/3/4 + <code>border</code> + <code>square</code>;可点格子是<strong>原生 button</strong>(整块热区 + 键盘可达),</li>
</ul>
<p class="m-sub"> <code>static</code> 格子是 div 且不绑点击 —— 直接规避「鼠标专用交互」这类无障碍缺口;按下反馈一律 <code>:active</code>(触屏没有悬停)。</p>
<ul class="m-steps">
<li><strong>步骤条 Steps</strong>(§15 → <code>mobile-steps</code>):<code>direction</code> 横/纵 + <code>status</code> 四态;<code>role=&quot;list&quot;</code> / <code>role=&quot;listitem&quot;</code> + <code>aria-current=&quot;step&quot;</code>;</li>
</ul>
<p class="m-sub"> <strong>状态不只靠颜色</strong>(进行中加粗、已完成用勾选字符、失败用感叹号)。</p>
<ul class="m-steps">
<li><strong>通知栏 NoticeBar</strong>(§16 → <code>mobile-noticebar</code>):四种语气 + <code>scrollable</code> 跑马灯 + <code>closable</code>;</li>
</ul>
<p class="m-sub"> <strong>无障碍硬要求落地</strong>:<code>prefers-reduced-motion: reduce</code> 下停止滚动并改为换行显示;滚动内容<strong>不用</strong> <code>aria-live</code>(反复朗读会干扰)。</p>
<ul class="m-steps">
<li><strong>数字键盘 NumberKeyboard</strong>(§17 → <code>mobile-numberkeyboard</code>):<code>type</code> = number/digit + <code>showDelete</code> + <code>showConfirm</code> + <code>confirmDisabled</code>;</li>
</ul>
<p class="m-sub"> 键盘<strong>不持有输入值</strong> —— 只 emit 按键事件(数字键回传该字符 / 删除键回传 <code>'delete'</code> / 确认键回传 <code>'confirm'</code>),写入哪个输入框由宿主决定。</p>
<ul class="m-steps">
<li><strong>日期选择器 DatePicker</strong>(§18 → <code>mobile-datepicker</code>):<code>mode</code> = date/month + <code>round</code> + <code>closeOnMask</code>;三列 <code>role=&quot;listbox&quot;</code> / <code>role=&quot;option&quot;</code> + <code>aria-selected</code>;</li>
</ul>
<p class="m-sub"> 年份范围由宿主传入(<code>years</code> / <code>months</code> / <code>days</code>),<strong>不发明</strong>可用年份区间(契约 unknowns 已登记)。</p>
<ul class="m-steps">
<li><strong>配色零发明</strong>(全部既有令牌,脚本实测亮/暗两态):Toast 深底 + 反色文字 15.78:1 / 15.00:1;</li>
</ul>
<p class="m-sub"> Dialog 卡片底 + 正文色 15.13:1 / 10.34:1;Grid / Steps / NoticeBar 沿用既有语义对(浅底 + 同族深字,均 ≥4.5:1)。</p>
<ul class="m-steps">
<li><strong>判据有效性用变异探针验证(5/5 当场捕获,不是「跑了绿灯」就算数)</strong>:</li>
</ul>
<p class="m-sub"> ① 向 CSS 注入 <code>#FF0000</code> → B3 报出文件与色值;② 契约加一个源码没有的 prop → E4 逐端报「源码里没有」;</p>
<p class="m-sub"> ③ 把有默认值的 prop 标成必传 → E4b 逐端报「应相反」;④ uni-app 端注入 <code>document.querySelectorAll</code> → U5 报出两条规则;</p>
<p class="m-sub"> ⑤ uni-app 端去掉全部 <code>rpx</code> → U9 报「未使用 rpx」。每个探针都当场还原并复跑确认绿灯。</p>
<ul class="m-steps">
<li><strong>过程中发现并修掉三处真问题</strong>:</li>
</ul>
<p class="m-sub"> ① 契约 <code>related</code> 里写了裸名(<code>toast</code> / <code>noticebar</code>)而非真实 slug → E6c 逐条报「不存在」,已补 <code>mobile-</code> 前缀(5 处);</p>
<p class="m-sub"> ② <code>tools/pack-deploy.mjs</code> 输出文案与 <code>site/m/_design_template.html</code> 里<strong>写死了组件数</strong>(前一批 8 个组件落地时漏改)</p>
<p class="m-sub"> → 分别改为读索引与构建期占位符注入;</p>
<p class="m-sub"> ③ <code>Dialog</code> 初版的圆角写在基础类上(<code>round</code> 变体于是无意义)→ 改为由 <code>--round</code> 类控制,契约 <code>variantClasses</code> 与 CSS 双向对上。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>node tools/build-mobile.mjs → 18 组件 × 6 端(data.mobile.json 412 KB)· 18 测试页 · 18 文档页 · dist/mobile 全端
node tools/verify-mobile-isolation.mjs → [OK] 隔离门禁全部通过(30 条断言)— B1 18 × 6 端 108 文件 / B8 报告 18 = 索引 18
node tools/verify-uniapp.mjs → [OK] uni-app 门禁全部通过(9 条断言 · 21 个 SFC)
node tools/verify-mobile-docs.mjs → [OK] 文档完整性门禁全部通过(12 条断言 · 24 页)
REG_BASE=… verify-mobile-site.mjs → [OK] 移动端站点全部通过(118 条断言 · 21 页;演示帧 101/101 正常,0 控制台错误)
REG_BASE=… node tools/run-mobile-regression.mjs → passRate 100% | pages 18 (all-pass 18) | assertions 270/270 | N/A 6
node tools/run-regression.mjs → passRate 100% | pages 103 (all-pass 103) | assertions 1405/1405 | N/A 50(PC 零污染)
node tools/pack-deploy.mjs → OK — mobile 108 个实现文件(18 组件 × 6 端)/ 文档页 18 / 测试页 18</code></pre></div>
<p class="m-sub"><strong>未执行</strong>:部署(只改工作区,未走 AGENTS §九 发布流程);uni-app 真实编译(既有状况 S7-P25,需 <code>@dcloudio</code> 依赖)。</p>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 移动端组件第二批(弹出层基座 + 展示系):Badge / Tag / Popup(索引 8 → 11)</h3>
<p class="m-note">起因:批次推进。移动端规格已写到 §9 / §10 / §11(上一轮写好但未实现),本轮按 <code>PLATFORMS.md</code> §四</p>
<p class="m-note">的六步流程把三节规格落地为 6 端实现 —— 组件数 8 → 11,<code>frameworks-mobile/</code> 48 → 66 个文件。</p>
<ul class="m-steps">
<li><strong>三个新组件</strong>(规格小节 → slug / 前缀):</li>
<li><strong>徽标 Badge</strong>(§9 → <code>mobile-badge</code>):<code>shape</code> = <code>dot</code> / <code>number</code> / <code>text</code> + <code>standalone</code>;</li>
</ul>
<p class="m-sub"> 状态 <code>overflow</code>(超过 <code>max</code> 显示 <code>99+</code>)与 <code>hidden</code>(值为 0 且未开 <code>showZero</code> 时不渲染)。</p>
<p class="m-sub"> 角标绝对定位、不改变被包裹元素的布局尺寸;红点 <code>aria-hidden</code>,数字角标带 <code>aria-label</code>。</p>
<ul class="m-steps">
<li><strong>标签 Tag</strong>(§10 → <code>mobile-tag</code>):<code>tone</code> 五语气(default / primary / success / warning / danger)</li>
</ul>
<p class="m-sub"> + <code>size</code>(default / small)+ <code>closable</code>。关闭按钮是<strong>原生 button</strong>、24×24 热区(小尺寸标签内用负外边距扩展),</p>
<p class="m-sub"> <code>aria-label</code> 带上标签文字(否则一串「✕」读屏无法区分);<code>disabled</code> 时置灰且不响应。</p>
<ul class="m-steps">
<li><strong>弹出层 Popup</strong>(§11 → <code>mobile-popup</code>):<code>placement</code> 五方向(center / bottom / top / left / right)+ <code>round</code></li>
</ul>
<p class="m-sub"> (贴边方向在靠内容一侧切圆角,用逻辑属性,RTL 安全);<code>closeOnMask</code> 可关;内容超长时 <strong>body 内部滚动</strong>、</p>
<p class="m-sub"> 遮罩不滚;滑入 240ms <code>cubic-bezier(.32,.72,0,1)</code>;浮层 <code>role=&quot;dialog&quot;</code> + <code>aria-modal</code>,遮罩 <code>aria-hidden</code>。</p>
<ul class="m-steps">
<li><strong>配色零发明(全部既有令牌,脚本实测两态)</strong>:实心底 + <code>--kole-color-text-inverse</code></li>
</ul>
<p class="m-sub"> 亮色 5.57–5.87:1 / 暗色 5.64–8.24:1;Tag 的浅底 + 同族字色亮色 4.68–5.38:1 / 暗色 4.92–6.91:1。</p>
<p class="m-sub"> <strong>未新增任何色值</strong>,因此不需要碰 PC 令牌文件。</p>
<ul class="m-steps">
<li><strong>过程发现并修掉两处陈旧计数</strong>(都是前一批 8 个组件落地时漏改的):</li>
</ul>
<p class="m-sub"> ① <code>tools/pack-deploy.mjs</code> 的输出文案写死「5 组件 × 6 端」(数字本身是算出来的,只有说明文字陈旧)→ 改为读索引;</p>
<p class="m-sub"> ② <code>site/m/_design_template.html</code> 的「移动端 5 个组件」写死在 HTML 里 → 改为构建期占位符(由 <code>build-mobile.mjs</code> 注入实际组件数)。</p>
<p class="m-note">注:本条初版把占位符原文写进了日志正文,而 <code>changelog.html</code> 是<strong>从本文件正文提取</strong>渲染的,</p>
<p class="m-note">于是页面里出现了未替换的占位符 —— 被 <code>verify-mobile-docs.mjs</code> 的 E7(无残留占位符)当场拦下。</p>
<p class="m-note">同一条纪律:日志正文里不要出现占位符形状的字面量。</p>
<ul class="m-steps">
<li><strong>修掉一处误报 skip</strong>:Badge 的「数值为 0(hidden)」演示块把 <code>data-assert</code> 放在<strong>被隐藏的角标</strong>上,</li>
</ul>
<p class="m-sub"> 断言引擎按「默认隐藏(交互后可见)」记为 skip —— 但该块本身就是「隐藏态+可见对照」的设计。</p>
<p class="m-sub"> 已把断言移到可见的容器上,skip 3 → 且该页断言 16 条全 pass(skip 只剩两条静态组件固有的 N/A)。</p>
<ul class="m-steps">
<li><strong>顺带修正 ROADMAP S7-P23 的失败判据</strong>:其中写的 PC 断言总数是 1017,实际当前为 <strong>1405</strong>(103 组件 × 5 端后未更新)。</li>
</ul>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>node tools/build-mobile.mjs → 11 组件 × 6 端(data.mobile.json 238 KB)· 11 测试页 · 11 文档页 · dist/mobile 全端
node tools/verify-mobile-isolation.mjs → [OK] 隔离门禁全部通过(30 条断言)— B1 11 × 6 端 66 文件 / B8 报告 11 = 索引 11
node tools/verify-uniapp.mjs → [OK] uni-app 门禁全部通过(9 条断言 · 14 个 SFC)
node tools/verify-mobile-docs.mjs → [OK] 文档完整性门禁全部通过(12 条断言 · 17 页;E8 站内链接 657 条)
REG_BASE=… verify-mobile-site.mjs → [OK] 移动端站点全部通过(83 条断言 · 14 页;演示帧 64/64 正常,0 控制台错误)
REG_BASE=… node tools/run-mobile-regression.mjs → passRate 100% | pages 11 (all-pass 11) | assertions 172/172 | N/A 4
node tools/run-regression.mjs → passRate 100% | pages 103 (all-pass 103) | assertions 1405/1405 | N/A 50(PC 零污染)
node tools/pack-deploy.mjs → OK — mobile 66 个实现文件(11 组件 × 6 端)/ 文档页 11 / 测试页 11
浏览器实测(1280×1000) → 三页各 14 个小节 / 演示块 6·5·6 / 预览帧全部有内容(0 空帧)/ 0 控制台错误
几何:Badge 角标 absolute + translate(50%,-50%)(不占位);Tag 24px 高;Popup fixed 被展示框 transform 包含块约束(关闭态位移 143.6px)</code></pre></div>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 组件批次推进:18 个组件 × 6 端(108 实现文件)+ 文档站 24 页,全部门禁与回归通过</h3>
<p class="m-note">起因:用户要求「参考 TDesign 移动端的组件清单,按批次补全当前组件库缺的组件」。</p>
<p class="m-note">参考页给出 72 个组件的清单,本仓库按「规格 → 契约 → 6 端实现 → 文档页 → 门禁」的既有流水线分批推进。</p>
<ul class="m-steps">
<li><strong>组件从 5 扩到 18</strong>(<code>frameworks-mobile/</code> 108 个文件 = 18 × 6 端),规格 19 节、契约 18 份:</li>
</ul>
<p class="m-sub"> 按 PC 同源分类:导航 4 · 反馈 7 · 通用 2 · 数据展示 3 · 数据录入 2。完整清单见 <code>site/m/index.html</code> 的覆盖矩阵与侧栏。</p>
<ul class="m-steps">
<li><strong>本会话直接产出的一批(A)</strong>:<code>mobile-button</code>(三档高度 44/36/28、loading 阻止重复触发、<code>aria-busy</code>)、</li>
</ul>
<p class="m-sub"> <code>cell</code>(整行热区 ≥56px、按行分隔线、可点用原生 button / 纯展示用 div)、<code>mobile-divider</code>(水平/垂直/虚线/</p>
<p class="m-sub"> 三种文字对齐,纯装饰 <code>role=separator + aria-hidden</code>)。三者的 6 端实现、契约(含演示分组、相似组件、</p>
<p class="m-sub"> 必传列、CSS 变量)与规格 §6–§8 均由本会话写入;其余批次与并发会话合并完成。</p>
<ul class="m-steps">
<li><strong>合并时修掉两处判据问题(都是「判据太窄」而不是放宽)</strong>:</li>
<li><strong>slug 允许与 PC 同名</strong>:<code>build-mobile.mjs</code> 与 <code>verify-mobile-isolation.mjs</code> 原先要求「移动端 slug 不得与</li>
</ul>
<p class="m-sub"> PC 撞名」,实际两个平台各自有「按钮」「分割线」是正常的 —— 真正要证明的是「同名也各自独立」。</p>
<p class="m-sub"> 改为:报告同名清单,并断言同名组件的实现文件<strong>不在</strong> <code>frameworks/</code> 下。</p>
<ul class="m-steps">
<li><strong><code>dist/mobile/manifest.json</code> 的判据</strong>从「无 PC slug」改为「slug 集合与索引完全一致」——</li>
</ul>
<p class="m-sub"> 后者能同时抓到漏项与串入,比原来更严。</p>
<ul class="m-steps">
<li><strong>移动端令牌层补一条全局焦点环</strong>:<code>:focus-visible</code> 落在移动端令牌层(不能只依赖 PC 令牌文件里那条 ——</li>
</ul>
<p class="m-sub"> 断言引擎读的是<strong>直接 link 的样式表</strong>的 <code>cssRules</code>,<code>@import</code> 进来的规则不在其中)。</p>
<p class="m-sub"> 这既是让静态组件过 <code>focus-visible-defined</code> 断言,也是真实的 a11y 改进。</p>
<ul class="m-steps">
<li><strong>门禁全绿</strong>:隔离 30 条 · uni-app 9 条(21 个 SFC)· 文档完整性 12 条(24 页,站内链接 1121 条全可解析)·</li>
</ul>
<p class="m-sub"> 移动端站点 <strong>118 条</strong>(21 页浏览器实测:0 控制台错误、演示帧 101/101 正常渲染、320–768px 无溢出)。</p>
<ul class="m-steps">
<li><strong>回归</strong>:移动端 <strong>100%(270 通过 / N/A 6 / 18 页)</strong> · PC <strong>100%(1405 通过 / N/A 50 / 103 页)</strong>。</li>
</ul>
<p class="m-sub"><strong>部署(AGENTS §九)</strong>:<code>pack-deploy --tar</code>(2187 文件 / 1.65 MB gzip)→ 暂存树哈希与本地一致</p>
<p class="m-sub">(<code>a962d7c4caa853b2</code>)→ 替换重建 → 服务器端 specs 404 / 根 302 / sitemap 200 / healthz / SPA 深链全过、</p>
<p class="m-sub">容器 0 error;公网抽查 5 个页面全 200,<code>site/m/index.html</code>、<code>site/m/component/mobile-button.html</code>、</p>
<p class="m-sub"><code>site/m/data.mobile.json</code> 逐字节与本地一致。</p>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile docs · 顶栏与左栏对齐 PC(平台 / 开发指南 / 组件 N + 主题三态跨站生效)+ 补常见问题与更新日志页</h3>
<p class="m-note">起因:用户指出「导航栏需要和 PC 一致,左侧栏也是」。移动端文档站此前是自成一体的简版顶栏</p>
<p class="m-note">(品牌 + 平台分段 + 3 个链接)与自创分组的左栏(开发指南 5 项自定名 + 手势/反馈分类),</p>
<p class="m-note">与 PC 文档站的信息架构不一致。</p>
<ul class="m-steps">
<li><strong>顶栏与 PC 同结构</strong>:<code>logo(标记块 + Kole UI + 副标题)</code> + <code>版本角标 v1.0.0</code>(读 <code>data.mobile.json</code> 的</li>
</ul>
<p class="m-sub"> <code>meta.version</code>)+ <code>平台切换</code> + <code>主导航 5 项</code>(<strong>组件总览 / 快速开始 / 设计规范 / 常见问题 / 更新日志</strong>,</p>
<p class="m-sub"> 与 PC 同名同序,组件页高亮「组件总览」= PC 在组件页高亮「组件」的同一口径)+ <code>主题模式三态</code>。</p>
<p class="m-sub"> PC 特有的三件<strong>没有搬</strong>,原因写明在 <code>site/m/style.css</code> 注释里:搜索框(PC 79 个组件才需要)、</p>
<p class="m-sub"> 技术栈选择器(PC 5 端各自成页;移动端 6 端同页展示)、语言选择器(移动端无 i18n 字典)。</p>
<ul class="m-steps">
<li><strong>主题三态与 PC 完全同一约定</strong>:<code>localStorage['kole-mode'] ∈ light|dark|auto</code>、<code>auto</code> 跟随系统、</li>
</ul>
<p class="m-sub"> 反色靠 <code>html.kole-dark</code>(令牌两端共用)。实测<strong>跨站生效</strong>:在移动端站选夜间 → FAQ 页仍是夜间 →</p>
<p class="m-sub"> PC 站同一键也是夜间;<code>&lt;head&gt;</code> 里有同步 bootstrap,避免首帧先白后黑(PC 的 S2-P5 同款处理)。</p>
<ul class="m-steps">
<li><strong>左侧栏三段式与 PC 侧栏一致</strong>:「<strong>平台</strong>」组(PC 端组件 / 移动端组件【当前】,各带副标题)→</li>
</ul>
<p class="m-sub"> 「<strong>开发指南</strong>」组(与 PC 同名同序的 5 项;移动端特有的「平台与端 / 测试与回归」置后并标「移动端」)→</p>
<p class="m-sub"> 「<strong>组件 N</strong>」按 <strong>PC 的六分类</strong>分组与计数(通用 / 导航 / 数据录入 / 数据展示 / 反馈 / 工具与系统)。</p>
<p class="m-sub"> 组件重新归类:navbar、tabbar → 导航;actionsheet、pullrefresh、swipecell → 反馈;</p>
<p class="m-sub"> 分类口径写进移动端索引的 <code>categories</code> 块(真源),构建脚本与文档站都读它。</p>
<ul class="m-steps">
<li><strong>补两页</strong>:</li>
<li><code>faq.html</code> 常见问题:6 组 20 条(接入 / 令牌 / 触控 / uni-app / 排障 / 口径),每条对应仓库里可复现的</li>
</ul>
<p class="m-sub"> 实测或门禁断言;含「uni-app 端真的编译过吗 → 没有」这类如实回答。</p>
<ul class="m-steps">
<li><code>changelog.html</code> 更新日志:<strong>构建时从仓库根 <code>CHANGELOG.md</code> 提取</strong>标题含「Mobile / 移动端」的段落</li>
</ul>
<p class="m-sub"> (自带一个极小的 Markdown 渲染:标题 / 列表 / 引用 / 代码块 / 行内 code 与 strong),不手抄 ——</p>
<p class="m-sub"> 站点与仓库两处说法不一致是这类页面的典型失败模式。</p>
<ul class="m-steps">
<li><strong>改名对齐</strong>:<code>设计令牌</code> → <code>设计规范</code>(<code>tokens.html</code> → <code>design.html</code>),与 PC 的「设计规范」同名。</li>
<li><strong>门禁 11 → 12 条</strong>:E3 从「侧栏列全组件」扩为「<strong>顶栏与左栏与 PC 同结构</strong>」(平台组与「当前」角标、</li>
</ul>
<p class="m-sub"> 开发指南 5 项同名同序、顶栏 7 个必需元素、主导航 5 项);新增 <strong>E8 站内文件链接全部可解析</strong>(332 条)。</p>
<ul class="m-steps">
<li><strong>E8 当场抓到两个真问题(已修)</strong>:① 改名后 <code>_index_template.html</code> 与 <code>_guide_template.html</code> 还指向</li>
</ul>
<p class="m-sub"> <code>tokens.html</code>(快速开始卡片与页脚);② <code>changelog.html</code> 的测试链接少退一级(<code>../tests/…</code> 应为 <code>../../tests/…</code>)。</p>
<p class="m-sub"> E8 已双向验证(临时改坏一处链接 → FAIL)。</p>
<ul class="m-steps">
<li><strong>修</strong>:更新日志页在窄屏横向溢出(375px 溢出 88px、414px 溢出 49px)—— 提取的正文含 64 位哈希、</li>
</ul>
<p class="m-sub"> 长路径这类不可断词的串;给正文/列表/行内 code 补 <code>overflow-wrap: anywhere</code>(表格此前已有同类规则)。</p>
<p class="m-sub"> 修后 7 页 × 4 宽度(320/375/414/768)<strong>0 溢出</strong>。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>node tools/verify-mobile-docs.mjs → [OK] 12 条断言 · 11 页
node tools/verify-mobile-isolation.mjs → [OK] 29 条断言 node tools/verify-uniapp.mjs → [OK] 9 条(8 SFC)
REG_BASE=… verify-mobile-site.mjs → [OK] 53 条断言(8 页 + 窄屏) npm run verify:i18n → OK(16 checks)
浏览器实测:顶栏 = Kole UI 移动端 / v1.0.0 / [PC 端][移动端] / 组件总览·快速开始·设计规范·常见问题·更新日志 / 自动模式
左栏 = 平台(PC 端组件、移动端组件【当前】)/ 开发指南 5 项 + 2 项移动端特有 / 组件 5(导航 2 · 反馈 3)
主题:选夜间 → html.kole-dark + localStorage[kole-mode]=dark;跳 FAQ 仍夜间;PC 站同键亦夜间;0 控制台错误
node tools/run-mobile-regression.mjs → 100% | 77/77 | 5/5 页
node tools/run-regression.mjs → 100% | 1026/1026 | N/A 34 | 79/79 页
node tools/pack-deploy.mjs --tar → OK(39 条必需项,含 6 张移动端根页;组件数改为从索引读取)</code></pre></div>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Deploy · 移动端组件页(TDesign 范式)已上线</h3>
<p class="m-sub">按 AGENTS §九 发布:<code>pack-deploy --tar</code> → 上传 → 暂存<strong>归一化树哈希与本地一致</strong>(<code>f00d3c4ac2e50861</code>,1531 文件)→ 替换 → <code>docker compose build &amp;&amp; up -d</code>。</p>
<p class="m-sub">验收:specs 404 / 根 302→<code>/site/</code> / sitemap 200 / healthz 有 content-type / SPA 深链 200 / 缺快照 404 / 容器日志 0 error;</p>
<p class="m-sub">公网 <code>https://kole-ui.mymoyu.top</code> 抽查 6 个文件(<code>site/app.js</code>、组件薄壳、<code>site/data.json</code>、移动端文档页与演示页)<strong>逐字节与本地一致</strong>。</p>
<p class="m-note">并发提示:本轮期间另一会话把 <code>package.json</code> 版本重置为 <strong>1.0.0</strong> 并重建站点(<code>data.json</code> 的 <code>generated=2026-09-20 07:36</code>、</p>
<p class="m-note"><code>versions.json</code> 单版本)。线上现已与该状态一致 —— 版本号本身不是本次改动;如需回到 2.0.0,请改 <code>package.json</code></p>
<p class="m-note">后重跑 <code>npm run build:site</code> 并再发布一次。</p>
<p class="m-note">另:本轮第一次发布(07:26 打包)后实测到线上落后工作区 906 个文件(并发会话 07:36 重建了薄壳与 <code>app.js</code>),</p>
<p class="m-note">已按 §九 的约定「等稳定后重发」补发一次,现线上与工作区逐字节一致。</p>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile docs · 组件页对齐 TDesign 范式(演示分组 / 独立预览 + 原文代码 / 必传 / CSS 变量 / 相似组件)</h3>
<p class="m-note">起因:用户给了参考页 &lt;https://tdesign.tencent.com/mobile-vue/components/link&gt;。用浏览器渲染后提取其结构:</p>
<p class="m-note">演示按 <code>01 组件类型</code> / <code>02 组件状态</code> 分组;每个演示独立成块(标题 + 预览 + 代码);</p>
<p class="m-note">API 分 Props(<strong>名称 / 类型 / 默认值 / 描述 / 必传</strong>)、Events、<strong>CSS Variables</strong>(名称 / 默认值 / 描述);</p>
<p class="m-note">另有「何时使用 / 组件搭配使用 / 推荐慎用示例 / 相似组件」。按这套范式重排了移动端组件页。</p>
<ul class="m-steps">
<li><strong>演示改为「分组 + 独立块」</strong>:契约新增 <code>demos</code>(id / group / title / desc / variant),5 个组件共 <strong>20 个演示块</strong></li>
</ul>
<p class="m-sub"> (navbar 3、tabbar 4、actionsheet 4、pullrefresh 4、swipecell 4),按 <code>01 组件类型</code> / <code>02 组件状态</code> 分组。</p>
<p class="m-sub"> 每块含:标题 + 变体标记 + 说明 + <strong>375 宽单演示预览帧</strong> + 「查看代码」(演示页<strong>原文</strong>,行数标注,可复制)。</p>
<ul class="m-steps">
<li><strong>演示页支持 <code>?demo=&lt;id&gt;</code> 单块模式</strong>:5 个演示页的每个演示包成</li>
</ul>
<p class="m-sub"> <code>&lt;section class=&quot;demo-block&quot; data-demo=&quot;…&quot;&gt;</code>,并加过滤脚本(只显示该块、隐藏帧内标题行、取消 <code>min-height:100vh</code>)。</p>
<p class="m-sub"> <strong>无参数路径完全不变</strong> —— 测试页与回归走无参数,实测移动端回归仍 100%(77/77)。</p>
<ul class="m-steps">
<li><strong>预览帧高度自适应</strong>:按 <code>body.scrollHeight</code> 收紧到 140–360px。</li>
</ul>
<p class="m-note">踩坑:先用 <code>max(body.scrollHeight, documentElement.scrollHeight)</code> 量,结果<strong>四个演示都锁在 308px</strong> ——</p>
<p class="m-note"><code>documentElement.scrollHeight</code> 等于<strong>帧视口高度</strong>(html 撑满视口),于是「帧高 → 视口高 → 量到的高度」</p>
<p class="m-note">形成反馈环,永远收敛在初始值。只量 body 后:swipecell 140 / navbar 140–166 / tabbar 222 / pullrefresh 242 /</p>
<p class="m-note">actionsheet 262,<strong>0 个演示被裁</strong>。</p>
<ul class="m-steps">
<li><strong>API 补两列/一表</strong>:Props 增「<strong>必传</strong>」列(严格定义:实现里<strong>没有默认值</strong>时才为 Y,门禁逐条核对 4 个框架端);</li>
</ul>
<p class="m-sub"> 新增「<strong>CSS 变量</strong>」表(从组件样式表扫描组件级变量:<code>--kole-m-swipecell-offset</code> /</p>
<p class="m-sub"> <code>--kole-m-swipecell-action-width</code> / <code>--kole-m-pullrefresh-threshold</code> / <code>--kole-m-pullrefresh-offset</code>)。</p>
<ul class="m-steps">
<li><strong>新增「相似组件」表</strong>(契约 <code>related</code>:组件 + 「何时用它而不是本组件」的区分说明,共 10 条),</li>
</ul>
<p class="m-sub"> 并把「用法要点」按参考页改名为「<strong>何时使用</strong>」。</p>
<ul class="m-steps">
<li><strong>组件页新顺序</strong>:演示 → API(Props / 事件 / 插槽 / CSS 变量)→ 何时使用 → 交互与触控 → 无障碍 →</li>
</ul>
<p class="m-sub"> 相似组件 → 规格未定 → 结构 → 变体类名映射 → 代表变体 → 令牌 → 6 端源码 → 测试与回归 → 契约。</p>
<p class="m-sub"> 另在 hero 下加「引入」代码块(令牌 + 本组件样式)。</p>
<ul class="m-steps">
<li><strong>门禁从 8 条扩到 11 条</strong>:新增 E4b(必传 ⇔ 实现默认值)、E6b(CSS 变量表 ↔ 组件 CSS 双向)、</li>
</ul>
<p class="m-sub"> E6c(相似组件 slug 真实存在 + 说明非空);E5 扩为「每个演示块 ↔ 演示页 <code>data-demo</code> 双向一致 +</p>
<p class="m-sub"> 预览帧带 <code>?demo=</code> + 代码区含<strong>转义后</strong>的演示页原文 + 每个演示代码块非空」。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>node tools/verify-mobile-docs.mjs → [OK] 11 条断言 · 9 页
node tools/verify-mobile-isolation.mjs → [OK] 29 条断言 node tools/verify-uniapp.mjs → [OK] 9 条(8 SFC)
REG_BASE=… verify-mobile-site.mjs → [OK] 53 条断言(8 页 + 窄屏)
npm run verify:i18n → OK(16 checks)
浏览器实测(1280×1000):分组 01/02 ✓ · 20 个演示块 ✓ · 每块预览帧只显示对应演示(4/4)✓ ·
每块有代码折叠 ✓ · 帧高自适应 0 裁剪 ✓ · 0 控制台错误
node tools/run-mobile-regression.mjs → 100% | 77/77 | 5/5 页(演示页改动后复跑)
node tools/run-regression.mjs ×2 → 100% | 1026/1026 | N/A 34 | 79/79 页</code></pre></div>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Deploy · 公网站更新到当前构建(移动端文档站四页 + 逐组件页 + PC 顶栏改动)</h3>
<ul class="m-steps">
<li>按 AGENTS §九 流程发布:<code>SITE_URL_BASE=https://kole-ui.mymoyu.top/ npm run build:site</code> → <code>build-mobile</code> + <code>build-uniapp</code></li>
</ul>
<p class="m-sub"> → <code>pack-deploy --tar</code>(1531 文件 / 5.8 MB,1.0 MB gzip)→ 上传 → 备份(目录 <code>/opt/aurora-admin.prev-20260920-0712</code></p>
<p class="m-sub"> + 镜像 <code>kole-ui-showcase:pre-20260920-0710</code>)→ 暂存目录<strong>逐文件哈希对账 0 差异(1531/1531)</strong> → 替换 → <code>docker compose build &amp;&amp; up -d</code>。</p>
<ul class="m-steps">
<li><strong>验收(服务器 127.0.0.1:3311)</strong>:specs 目录 404 ✓ / 根 302→<code>/site/</code> ✓ / sitemap 200 ✓ / healthz 有 content-type ✓ /</li>
</ul>
<p class="m-sub"> SPA 深层路由 <code>/site/component/button/h5</code> 200 ✓;移动端与 uni-app 路径 8 条全 200(含此前 404 的</p>
<p class="m-sub"> <code>site/m/guide.html</code>、<code>platform.html</code>、<code>tokens.html</code>);安全响应头 3 条在位。</p>
<ul class="m-steps">
<li><strong>公网 <code>https://kole-ui.mymoyu.top</code></strong>:<code>/site/</code>、<code>/site/m/</code>、<code>/site/m/guide.html</code>、</li>
</ul>
<p class="m-sub"> <code>/site/m/component/actionsheet.html</code>、<code>/versions.json</code>、<code>/sitemap.xml</code> 全 200;</p>
<p class="m-sub"> <code>data.json</code> → <code>lib=kole-ui</code> / <code>version=2.0.0</code> / <code>generated=2026-09-20 07:09</code>(= 本次构建);</p>
<p class="m-sub"> 抽查 <code>site/m/guide.html</code> 与 <code>site/m/component/actionsheet.html</code> 的公网字节哈希与本地包<strong>逐字节一致</strong>。</p>
<ul class="m-steps">
<li><strong>过程发现并修掉一个会让构建直接失败的仓库状态问题</strong>:本地 <code>Dockerfile</code> 曾带 <code>COPY 1.4.1 /usr/share/nginx/html/1.4.1</code></li>
</ul>
<p class="m-sub"> 行,而仓库根已无 <code>1.4.1/</code> 快照目录(并发改动期间快照被清理)——<code>docker compose build</code> 报</p>
<p class="m-sub"> <code>failed to calculate checksum of ref …: &quot;/1.4.1&quot;: not found</code>。该行与其后注释块(&quot;以后归档后在补一行&quot;)</p>
<p class="m-sub"> 的自洽性由 <code>tools/verify-versions.mjs</code> 双向卡住(磁盘快照 ↔ COPY 行),现已通过(33 checks)。</p>
<p class="m-note">注:本轮第一次发布正是<strong>卡在这条</strong>(打包与 Dockerfile 之间差了几十秒的并发改动),已重新打包发布成功。</p>
<ul class="m-steps">
<li><strong>修掉发布过程中暴露的 500</strong>:快照被移除后 <code>/1.4.1/site/</code> 由「归档站」变成 <strong>500</strong>(容器日志:</li>
</ul>
<p class="m-sub"> <code>rewrite or internal redirection cycle while internally redirecting to &quot;/1.4.1/site/index.html&quot;</code>)——</p>
<p class="m-sub"> <code>nginx.conf</code> 里版本段的 <code>try_files</code> 回落目标又匹配回同一 location,形成内部重定向环;</p>
<p class="m-sub"> 且深链 <code>/1.4.1/site/component/button/h5</code> 同样 500。修法:末位补 <code>=404</code>,前三个参数退化为文件存在性检查</p>
<p class="m-sub"> (快照在 → 发它自己的壳;不在 → 干净 404)。<strong>并给这条失败模式加了门禁</strong>:<code>tools/verify-versions.mjs</code></p>
<p class="m-sub"> 新增「versioned fallback terminates with =404」,双向验证过(改动后 34 checks 全过;把 <code>=404</code> 去掉立刻 FAIL)。</p>
<p class="m-sub"> 实测:<code>/1.4.1/site/</code> 404、<code>/1.4.1/</code> 302(再 404)、其余验收全绿、容器日志 0 条 error。</p>
<p class="m-note">归档的 1.4.1 快照内容本身没有被删除——它仍在服务器上的 <code>/opt/aurora-admin.prev-20260920-0712/1.4.1/</code></p>
<p class="m-note">与 <code>kole-ui-showcase:pre-20260920-0710</code> 镜像里;仓库当前按「不归档任何历史版本」的状态(<code>versions.json</code></p>
<p class="m-note">只列 2.0.0)发布,这是并发会话清理快照后的既定状态。</p>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile docs · 补全左侧栏与页面内容(API / 6 端源码 / 令牌 / 回归状态)</h3>
<p class="m-note">起因:用户指出「移动端的左侧栏内容不完整,页面内容也不够完整」。移动端文档站此前只有顶部导航</p>
<p class="m-note">与 6 个小节的组件页,没有左侧导航,也没有 API、源码、令牌、回归状态这些&quot;读文档的人真正要用的东西&quot;。</p>
<ul class="m-steps">
<li><strong>左侧栏</strong>:所有页面共用一份 IA —— 「开发指南」5 项(移动端总览 / 快速开始 / 平台与端 / 设计令牌 /</li>
</ul>
<p class="m-sub"> 测试与回归)+「组件 5」按分类分组(导航 / 手势 / 反馈)与计数,当前页/当前组件高亮并带 <code>aria-current</code>。</p>
<p class="m-sub"> 桌面恒定展开;<strong>≤1000px 自动收起为可折叠面板</strong>(点标题展开)。</p>
<p class="m-note">踩坑记录:左栏用 <code>&lt;details&gt;</code> 承载时,<strong>不能</strong>用&quot;去掉 <code>open</code> + CSS 强制展开&quot;的写法 ——</p>
<p class="m-note">不带 <code>open</code> 的 <code>&lt;details&gt;</code> 高度按关闭态算成 <strong>0</strong>,<code>overflow:auto</code> 会把里面的 nav 整块裁掉</p>
<p class="m-note">(DOM 里有 10 条链接、屏幕上一片空白,实测截图抓到)。改为 <code>open</code> 默认展开 + 断点脚本切换。</p>
<ul class="m-steps">
<li><strong>新增三页</strong>:</li>
<li><code>guide.html</code> 快速开始:三步接入、按端引入路径表(6 端)、6 端最小示例(React / Vue 3 / Vue 2 / uni-app</li>
</ul>
<p class="m-sub"> 可拷贝代码)、与 PC 端的边界、5 条常见问题(样式不生效 / <code>--kole-m-*</code> 取不到值 / 安全区 / 手势不触发 / 多 fixed 叠层)。</p>
<ul class="m-steps">
<li><code>platform.html</code> 平台与端:两轴覆盖矩阵、uni-app 两列对照、12 行目录与命名映射、6 条隔离规则、</li>
</ul>
<p class="m-sub"> 6 条门禁命令(各自检查什么)、新增组件的六步流程。</p>
<ul class="m-steps">
<li><code>tokens.html</code> 设计令牌:15 个 <code>--kole-m-*</code> 表(名 / 值 / 用途)+ 组件实际引用到的继承令牌表(构建时扫描)、</li>
</ul>
<p class="m-sub"> 单位与安全区与明暗模式三条约定、&quot;改令牌的正确姿势&quot;示例。</p>
<ul class="m-steps">
<li><strong>组件页 6 节 → 14 节</strong>:新增 API(props / 事件 / 插槽三张表)、6 端源码(每端一个折叠块 + 复制按钮)、</li>
</ul>
<p class="m-sub"> 用到的令牌(构建时从该组件 CSS 扫描,区分自有 / 继承 / 组件级变量)、交互与触控、无障碍、</p>
<p class="m-sub"> 变体维度 × 类名映射、测试与回归(<strong>逐组件断言数</strong>,取自回归报告的 <code>pageResults</code>)、设计契约(内嵌原始 JSON)、</p>
<p class="m-sub"> 相关组件。</p>
<ul class="m-steps">
<li><strong>契约补 4 类字段</strong>:<code>interaction</code> / <code>accessibility</code>(逐字取自规格 §x.5 / §x.6)、</li>
</ul>
<p class="m-sub"> <code>api</code>(props / events / slots,写明&quot;实现接口&quot;的来源)、<code>variantClasses</code>(变体取值 → 类名/变量)。</p>
<ul class="m-steps">
<li><strong>新门禁 <code>npm run verify:mobile-docs</code>(8 条断言)</strong>:页面齐全 / 组件页 14 小节 / 侧栏列全组件且恰好一个高亮 /</li>
</ul>
<p class="m-sub"> <strong>契约 API 与 4 个框架端源码逐名一致(正向 + 反向)</strong> / 每个声明的端都有非空代码块 /</p>
<p class="m-sub"> <code>variantClasses</code> 的类与变量在组件 CSS 里真实存在 / 页面壳完整且无残留占位符。</p>
<p class="m-sub"> Rationale:本站是生成物,生成器的 bug 只会让页面<strong>悄悄少一块</strong>,不会报错 —— 这类&quot;缺内容&quot;必须靠断言抓。</p>
<ul class="m-steps">
<li><strong>门禁当场抓到的真实漂移(已修)</strong>:契约声明 <code>PullRefresh.threshold</code>,但 <strong>uni-app 端没有这个 prop</strong>(它用的是</li>
</ul>
<p class="m-sub"> 模块常量)。已给 uni-app 端补上 <code>threshold</code>(rpx 默认 120),并把「浏览器端 px / 本端 rpx」的单位差异写进契约描述。</p>
<ul class="m-steps">
<li><strong>回归报告新增 <code>pageResults</code></strong>(逐页 total/pass/fail):组件页据此显示「断言 N 条 · 全部通过」。</li>
</ul>
<p class="m-sub"> PC 侧报告缺这一块(正是 S5-P17「失败落不了盘」的痛点),移动端先补。</p>
<ul class="m-steps">
<li><strong>令牌计数统一为 15</strong>:安全区两条在 <code>@supports</code> 里各出现两次(0px 默认 + <code>env()</code> 覆盖),</li>
</ul>
<p class="m-sub"> 此前构建日志/数据显示 17 而映射表显示 15;现按<strong>唯一令牌名</strong>计,并给这两条补上用途说明。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>node tools/verify-mobile-docs.mjs → [OK] 8 条断言 · 9 页(含 API ↔ 源码逐名一致)
node tools/verify-mobile-isolation.mjs → [OK] 29 条断言
node tools/verify-uniapp.mjs → [OK] 9 条断言(8 个 SFC)
REG_BASE=… node tools/verify-mobile-site.mjs → [OK] 53 条断言(8 页 + 窄屏 3 页 × 4 宽度)
npm run smoke:site / verify:i18n → OK(16 checks)
浏览器实测:桌面左栏高度 500px / 10 条链接 / 恰好 1 个高亮;窄屏自动收起、点开可见、放大自动展开;
复制按钮写入剪贴板 2233 字符且文案变「已复制」;#api 锚点可定位;0 控制台错误
node tools/run-regression.mjs ×2 → 100% | 1026/1026 | N/A 34 | 79/79 页 | 0 超时
node tools/run-mobile-regression.mjs → 100% | 77/77 | 5/5 页 | 0 超时(逐页 14/15/16/15/17)</code></pre></div>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Docs site · 平台入口打通(PC ↔ 移动端)+ 技术栈选择器美化 + 本地访问方式</h3>
<p class="m-note">起因:用户要求「做一个合理的访问方式」与「当前的组件框架选择的样式需要美化一下」。</p>
<p class="m-note">前者指:移动端文档站上线后没有入口,且本地起服务时端口被旧实例占用会静默失败(实测 403);</p>
<p class="m-note">后者指顶栏那个裸 <code>&lt;select&gt;</code>(系统原生下拉、无悬停/展开态,与旁边的语言/主题选择器不同源)。</p>
<ul class="m-steps">
<li><strong>技术栈选择器改为「胶囊触发器 + 卡片菜单」</strong>(<code>site/index.html</code> + <code>site/style.css</code> + <code>site/app.js</code>):</li>
</ul>
<p class="m-sub"> 图标(层次符号)+ 当前值 + 箭头;菜单沿用语言选择器的卡片样式(标题分隔线、每项带<strong>端文件名角标</strong></p>
<p class="m-sub"> <code>html / jsx / vue2 / vue3</code>、选中项对勾、头部右侧显示当前端文件名)。键盘全路径:<code>↓/↑</code> 移动、</p>
<p class="m-sub"> <code>Home/End</code> 首尾、<code>Enter/Space</code> 选中、<code>Esc</code> 关闭并回焦、<code>Tab</code> 关闭不抢焦点、点击外部关闭;</p>
<p class="m-sub"> 展开态旋转箭头 + 品牌色焦点环;暗色与 <code>prefers-reduced-motion</code> 均适配。</p>
<ul class="m-steps">
<li><strong>原生 <code>&lt;select&gt;</code> 保留为状态真源</strong>(<code>.fw-native</code>:1px 视觉隐藏,不进 Tab 序列):</li>
</ul>
<p class="m-sub"> 选中项写回它的 <code>value</code> 并派发 <code>change</code>,既有的「状态更新 / localStorage / 路由跳转」管道</p>
<p class="m-sub"> 与既有验证脚本(<code>run-site-smoke</code> 读 <code>inputValue()</code> + <code>selectOption()</code>)<strong>一行未改</strong>。</p>
<ul class="m-steps">
<li><strong>侧栏新增「平台」分组</strong>:<code>PC 端组件</code>(带「当前」角标与副标题)/ <code>移动端组件</code>(副标题写清</li>
</ul>
<p class="m-sub"> 5 组件 × 6 端含 uni-app)。移动端入口是<strong>整页跳转</strong>(指向站点根下的 <code>m/index.html</code>)——</p>
<p class="m-sub"> SPA 的路由拦截器只接管无扩展名的地址,带 <code>.html</code> 的链接交回浏览器,两个站各自独立加载。</p>
<ul class="m-steps">
<li><strong>移动端文档站顶栏加平台切换</strong> <code>[PC 端] [移动端]</code>(当前侧实心品牌底),与 PC 侧形成双向入口。</li>
<li><strong>本地访问方式</strong>(<code>site/dev-server.js</code>):端口被占用时<strong>自动向上找空闲端口</strong>(最多 10 档)并说明,</li>
</ul>
<p class="m-sub"> 不再 <code>EADDRINUSE</code> 直接退出;<code>KOLE_PORT</code> 显式指定时不回退(CI/脚本依赖固定端口)。</p>
<p class="m-sub"> 启动即打印 6 个入口:PC 文档站 / 移动端文档站 / 组件总览 / PC 测试总览 / 移动端测试总览 /</p>
<p class="m-sub"> 两个回归收集器,并给出 <code>REG_BASE=</code> 提示。</p>
<p class="m-note">实测踩到过:3311 上挂着<strong>改动前启动</strong>的实例时,新目录一律 403,日志里只有一句 <code>EADDRINUSE</code>,</p>
<p class="m-note">极易误判成「构建没产出」。</p>
<ul class="m-steps">
<li><strong>修:移动端文档站在小屏上横向溢出</strong>(实测 375px 溢出 128px、414px 溢出 89px)—— 一个讲移动端的</li>
</ul>
<p class="m-sub"> 文档站自己在手机上横向滚动说不过去。元凶两处:表格里<strong>不可断行的长路径</strong></p>
<p class="m-sub"> (<code>.design_library/kole-ui-mobile/components/</code> 把表格最小宽度顶到 462px)与组件页固定 375px 的设备帧。</p>
<p class="m-sub"> 修法:单元格与 <code>&lt;code&gt;</code> 允许长 token 断行、设备帧取 <code>min(375px, 100%)</code>、顶栏 ≤720px 换行并把副导航移到第二行。</p>
<p class="m-sub"> 修后实测 320 / 375 / 414 / 768 / 1280 全部 0 溢出、0 控制台错误。</p>
<ul class="m-steps">
<li><strong>新增窄屏门禁</strong>:<code>tools/verify-mobile-site.mjs</code> 加「窄屏无横向溢出」一节(3 类页面 × 4 个宽度 + 控制台错误),</li>
</ul>
<p class="m-sub"> 断言数 38 → <strong>53</strong>。只靠&quot;在桌面宽度看一眼&quot;发现不了这类问题。</p>
<ul class="m-steps">
<li><strong>修</strong>:移动端文档站与规格文件里对 <code>MOBILE.md</code> 的失效引用 → <code>PLATFORMS.md</code>(4 处源码,</li>
</ul>
<p class="m-sub"> 生成物已重跑)。该文档在交付时最终命名为 PLATFORMS.md。</p>
<ul class="m-steps">
<li><strong>验证脚本同步</strong>:<code>verify-nav-responsive.mjs</code> 的字体容忍度元素清单随 UI 更新</li>
</ul>
<p class="m-sub"> (<code>.fw-label</code>/<code>#fw-select</code> → <code>.fw-cur</code>/<code>.fw-menu .fw-opt</code>)—— 清单不跟着改会把余量报得偏乐观。</p>
<p class="m-sub"> 实测余量 5.8%(英文 · 1367px)。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>浏览器功能断言(Chromium 1440×900) 25/25 PASS,全程 0 控制台错误
├ 触发器/菜单/4 选项/端文件名角标/选中态/aria-expanded
├ 选择 Vue 3 → 触发器文案 + #fw-select value + localStorage + 路由 /component/button/vue3 同步
├ 键盘:Esc 关闭、↓ 打开并聚焦当前项、↓ 环形移动、Enter 生效
├ 侧栏平台入口 2 项、href=/site/m/index.html、整页跳转到移动端站、移动端站回链 ../index.html
└ 暗色模式菜单底色 rgb(28,31,38)(跟随主题)
node tools/verify-mobile-isolation.mjs → [OK] 29 条断言
node tools/verify-uniapp.mjs → [OK] 9 条断言(8 个 SFC)
REG_BASE=… node tools/verify-mobile-site.mjs → [OK] 53 条断言(8 页 + 3 页窄屏 × 4 宽度)
npm run smoke:site → OK — all checks passed
npm run verify:nav → OK — all checks passed(余量 5.8%)
npm run verify:playground → OK — all checks passed
node tools/run-regression.mjs ×3 → 100% | 1026/1026 | N/A 34 | 79/79 页 | 0 超时
node tools/run-mobile-regression.mjs → 100% | 77/77 | 5/5 页 | 0 超时</code></pre></div>
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 移动端平台上线:与 PC 端物理隔离(平台 × 端两轴,含 uni-app 端)</h3>
<p class="m-note">起因:用户要求「在组件库额外添加移动端组件,移动端和 PC 端隔离」,并明确「PC 和移动是两个大分类,</p>
<p class="m-note">移动端后面是 H5 移动端、Vue 移动端,而且 uni-app 也会经常用到(uni-app 移动端 / uni-app PC 端)」。</p>
<p class="m-note">据此把组件库组织成<strong>两个正交轴</strong>:平台 <code>pc | mobile</code> × 端 <code>css | html | jsx | vue2 | vue3 | uniapp</code>。</p>
<p class="m-note">新增 <strong><code>PLATFORMS.md</code></strong>(两轴定义、目录与命名映射、隔离规则、新增组件六步流程)。</p>
<ul class="m-steps">
<li><strong>移动端 5 个组件 × 6 端 = 30 个实现文件</strong>(<code>frameworks-mobile/</code>):<code>navbar</code>(顶部导航栏)、</li>
</ul>
<p class="m-sub"> <code>tabbar</code>(底部标签栏)、<code>actionsheet</code>(动作面板)、<code>pullrefresh</code>(下拉刷新)、<code>swipecell</code>(滑动单元格)。</p>
<p class="m-sub"> 每组件六端:CSS / H5 演示页 / React / Vue 2 / Vue 3 / <strong>uni-app</strong>。类名前缀 <code>kole-m-</code>,导出名 <code>KoleM*</code>。</p>
<ul class="m-steps">
<li><strong>移动端规格原文(本仓库自撰)</strong>:<code>.design_library/kole-ui-mobile/spec/移动端规格.md</code>。</li>
</ul>
<p class="m-sub"> PC 端规格来自外部交付的 <code>组件1~10.txt</code>;移动端<strong>没有</strong>外部规范,故契约 <code>sourceKind: authored-spec</code>、</p>
<p class="m-sub"> <code>provenance: authored-in-repo</code>——不冒认外部来源;契约的 <code>doNotInvent</code> / <code>unknowns</code> 逐条对应规格条目。</p>
<p class="m-sub"> 规格共 5 节(另有 §〇 隔离总则),5 份契约全覆盖。</p>
<ul class="m-steps">
<li><strong>令牌层</strong>:<code>.design_library/kole-ui-mobile/colors_and_type.css</code> = <code>@import</code> PC 令牌(颜色/字体/圆角/阴影同源)</li>
</ul>
<p class="m-sub"> + 15 个 <code>--kole-m-*</code>(触控 44px 最小热区、<code>@supports (env())</code> 安全区、移动端字号、手势动效时值)。</p>
<p class="m-sub"> <strong>PC 令牌文件零改动</strong>,改一处令牌两端同时生效。</p>
<ul class="m-steps">
<li><strong>隔离边界</strong>(每一条都有断言,见下):实现目录(<code>frameworks-mobile/</code> vs <code>frameworks/</code>)、契约目录、</li>
</ul>
<p class="m-sub"> 类名前缀、令牌前缀、测试页(<code>tests/mobile/</code> vs <code>tests/</code>)、回归报告(<code>tests/mobile-report.json</code> vs</p>
<p class="m-sub"> <code>tests/report.json</code>)、文档站(<code>site/m/</code> 静态站 vs <code>site/</code> SPA,移动端<strong>不进</strong> PC 路由表)、</p>
<p class="m-sub"> 分发产物(<code>dist/mobile/*</code> vs <code>dist/*</code>)。</p>
<ul class="m-steps">
<li><strong>uni-app 端两处</strong>:移动端 × uni-app(5 个,目标 <code>app-plus</code> / <code>mp-weixin</code> / <code>h5</code>,用 uni 基础组件 +</li>
</ul>
<p class="m-sub"> <code>rpx</code> + <strong>touch 事件</strong>——小程序与 App 端无 PointerEvent);<strong>PC × uni-app 试点 3 个</strong></p>
<p class="m-sub"> (<code>frameworks-uniapp-pc/{Button,Input,Card}.uniapp.vue</code>,类名与 PC 的 <code>frameworks/*.css</code> 逐字一致、</p>
<p class="m-sub"> <code>--kole-*</code> 令牌、px 尺寸、目标 H5/PC 容器),覆盖率 3 / 79 并登记为 ROADMAP S7-P26。</p>
<ul class="m-steps">
<li><strong>构建(Node,跨平台,不碰 Windows-only 的 PS 链)</strong>:<code>tools/build-mobile.mjs</code>(导出 <code>site/m/data.mobile.json</code></li>
</ul>
<p class="m-sub"> 自包含 102 KB = 5 组件 × 6 端源码、<code>site/m/index.html</code> 总览、<code>site/m/component/&lt;slug&gt;.html</code> 逐组件页、</p>
<p class="m-sub"> <code>tests/mobile/<strong></code>、<code>dist/mobile/</strong></code>)与 <code>tools/build-uniapp.mjs</code>(<code>dist/uniapp-pc/**</code>)。</p>
<p class="m-sub"> 两个脚本都带<strong>写入守卫</strong>:越界路径直接抛错(移动端脚本只允许写 <code>site/m/</code> <code>tests/mobile/</code> <code>dist/mobile/</code>)。</p>
<ul class="m-steps">
<li><strong>测试</strong>:移动端测试页以 <strong>375×640 设备帧</strong> iframe 载真实演示页;断言引擎与 PC <strong>共用</strong> <code>tests/_runtime.js</code>;</li>
</ul>
<p class="m-sub"> 行为库为移动端专属 <code>tests/mobile/_behaviors.js</code>,新增 3 个触控动词 <code>swipe-sets-class</code> / <code>swipe-sets-attr</code> /</p>
<p class="m-sub"> <code>pull-triggers</code>(合成 <code>pointerdown → pointermove → pointerup</code> 手势,断言类/属性真实变化)。</p>
<p class="m-sub"> <strong>两端行为库互不加载</strong>,PC 侧 6 试点组件不受影响。</p>
<ul class="m-steps">
<li><strong>门禁与 CI</strong>:<code>tools/verify-mobile-isolation.mjs</code>(<strong>28 断言</strong>:PC 零污染 / 移动端自洽 / 分发隔离)、</li>
</ul>
<p class="m-sub"> <code>tools/verify-uniapp.mjs</code>(<strong>9 断言</strong>:SFC 三段 / <code>node --check</code> 语法 / 标签配平 / 禁 DOM API /</p>
<p class="m-sub"> 手势必须 touch / 只用 uni 基础组件 / 前缀隔离 / 单位策略)、<code>tools/run-mobile-regression.mjs</code>。</p>
<p class="m-sub"> <code>regression.yml</code> 新增:构建分发产物 → <strong>移动端生成物可复现性断言</strong>(<code>git diff --quiet</code> 卡&quot;改了索引忘重跑构建&quot;)</p>
<p class="m-sub"> → 两条门禁 → 移动端回归 → 报告上传与摘要(PC 与移动端两段)。</p>
<ul class="m-steps">
<li><strong>部署</strong>:<code>tools/pack-deploy.mjs</code> 的 <code>CONTENT_DIRS</code> 增 <code>frameworks-mobile</code>、<code>frameworks-uniapp-pc</code>,</li>
</ul>
<p class="m-sub"> 新增 6 条硬断言(移动端 30 实现 / 5 文档页 / 5 测试页 / 3 试点);<code>Dockerfile</code> 增两条 <code>COPY</code>;</p>
<p class="m-sub"> <code>.dockerignore</code> 显式排除 <code>.design_library/kole-ui-mobile/spec</code>(移动端规格原文与 PC 规格同策略不进镜像);</p>
<p class="m-sub"> <code>site/dev-server.js</code> 白名单增两个顶层目录。</p>
<p class="m-sub"><strong>验收(原样)</strong>:</p>
<div class="m-code"><pre><code>node tools/verify-mobile-isolation.mjs → [OK] 隔离门禁全部通过(28 条断言)
node tools/verify-uniapp.mjs → [OK] uni-app 门禁全部通过(9 条断言 · 8 个 SFC)
node tools/run-mobile-regression.mjs → passRate 100% | pages 5 (all-pass 5) | assertions 77/77
node tools/run-regression.mjs ×8 → run 1..8 全为 100% | pass 1017 | fail 0 | na 34 | pages 79/79 | timedOut 0
node tools/pack-deploy.mjs → OK:components 79 薄壳/395 实现 · mobile 30 实现/5 文档页/5 测试页 · uniapp-pc 3 试点</code></pre></div>
<p class="m-sub"><strong>未执行</strong>(写明并给复现命令,见 ROADMAP S7-P25):uni-app 真实编译(H5 / 微信小程序 / App)——</p>
<p class="m-sub">需 <code>@dcloudio/vite-plugin-uni</code>,与零运行时依赖不冲突但会污染主回归 job 的依赖,故本次只做静态门禁;</p>
<p class="m-sub">复现命令写在 <code>tools/verify-uniapp.mjs</code> 头部。</p>
</section>
<footer class="m-foot">
<p>生成物:本页由 <code>node tools/build-mobile.mjs</code> 从 <code>CHANGELOG.md</code> 提取生成,请勿手改。</p>
<p>相关:<a href="platform.html">平台与端</a> · <a href="../../tests/mobile/index.html">测试与回归</a> · PC 侧更新日志见 <a href="../changelog">PC 文档站</a></p>
</footer>
</main>
</div>
<script>
(function () {
/* 左栏:≤1000px 默认收起(手机上不让导航占满一屏),跨断点时同步一次;不监听 resize,
免得跟用户手动的展开/收起抢状态。桌面不设 open 之外的动作。 */
var side = document.querySelector('details.m-side');
if (side && window.matchMedia) {
var mq = window.matchMedia('(max-width: 1000px)');
var sync = function () { if (mq.matches) side.removeAttribute('open'); else side.setAttribute('open', ''); };
sync();
if (mq.addEventListener) mq.addEventListener('change', sync);
else if (mq.addListener) mq.addListener(sync);
}
/* 单演示预览帧按内容高度自适应(同源可读 contentDocument;上限 360px、下限 140px)。
固定高度会在内容少的演示上留一大片空白(实测 SwipeCell 单个演示只用 ~120px)。 */
function fitDemoFrames() {
var frames = document.querySelectorAll('.m-demo-stage iframe');
Array.prototype.forEach.call(frames, function (f) {
var fit = function () {
try {
var d = f.contentDocument;
if (!d || !d.body) return;
/* 只量 body.scrollHeight:documentElement.scrollHeight 等于**帧视口高度**(html 撑满视口),
拿它做 max 会形成「帧高→视口高→量到的高度」反馈环,永远收敛在初始值(实测锁在 308px)。 */
var h = d.body.scrollHeight;
if (!h) h = d.documentElement ? d.documentElement.scrollHeight : 0;
if (!h) return;
f.style.height = Math.min(360, Math.max(140, h + 8)) + 'px';
} catch (e) { /* 跨源或未就绪:保持 CSS 兜底高度 */ }
};
f.addEventListener('load', function () { fit(); setTimeout(fit, 300); });
fit();
});
}
fitDemoFrames();
window.addEventListener('load', fitDemoFrames);
/* 主题模式(三态):与 PC 完全同一约定 —— localStorage[kole-mode] ∈ light|dark|auto,
auto 跟随系统,反色靠 html.kole-dark(令牌文件里已写好该组,两端共用一份令牌)。
用户在 PC 站选夜间,进移动端站也是夜间。 */
var MODE_KEY = 'kole-mode';
var MODE_LABEL = { light: '日间模式', dark: '夜间模式', auto: '自动模式' };
function readMode() {
try { var v = localStorage.getItem(MODE_KEY); if (v === 'light' || v === 'dark' || v === 'auto') return v; } catch (e) {}
return 'auto';
}
function systemDark() {
return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
}
function applyMode(mode) {
var dark = mode === 'dark' || (mode === 'auto' && systemDark());
document.documentElement.classList.toggle('kole-dark', dark);
document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
var cur = document.getElementById('m-mode-cur');
if (cur) cur.textContent = MODE_LABEL[mode] || MODE_LABEL.auto;
Array.prototype.forEach.call(document.querySelectorAll('.m-menu-opt'), function (o) {
o.setAttribute('aria-selected', o.getAttribute('data-mode') === mode ? 'true' : 'false');
});
/* 三态图标与 PC 同口径:显式选择时按时态给图标(日间=月亮 / 夜间=太阳),
自动模式下恒为「跟随系统」的显示器图标(此时两个显式图标都不显示)。 */
var moon = document.getElementById('m-icon-moon');
var sun = document.getElementById('m-icon-sun');
var auto = document.getElementById('m-icon-auto');
if (moon) moon.style.display = (mode === 'light') ? '' : 'none';
if (sun) sun.style.display = (mode === 'dark') ? '' : 'none';
if (auto) auto.style.display = (mode === 'auto') ? '' : 'none';
}
function bindModeMenu() {
var trigger = document.getElementById('m-mode-trigger');
var menu = document.getElementById('m-mode-menu');
if (!trigger || !menu) return;
var close = function (refocus) {
menu.classList.add('hidden'); trigger.setAttribute('aria-expanded', 'false');
if (refocus) trigger.focus();
};
trigger.addEventListener('click', function () {
var open = menu.classList.toggle('hidden');
trigger.setAttribute('aria-expanded', open ? 'false' : 'true');
});
trigger.addEventListener('keydown', function (e) {
if (e.key === 'Escape') close(true);
});
Array.prototype.forEach.call(menu.querySelectorAll('.m-menu-opt'), function (opt) {
opt.addEventListener('click', function () {
var mode = opt.getAttribute('data-mode');
try { localStorage.setItem(MODE_KEY, mode); } catch (e) {}
applyMode(mode);
close(true);
});
});
document.addEventListener('click', function (e) {
if (!menu.classList.contains('hidden') && !menu.contains(e.target) && !trigger.contains(e.target)) close(false);
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && !menu.classList.contains('hidden')) close(true);
});
/* 自动模式下跟随系统切换 */
if (window.matchMedia) {
var mq = window.matchMedia('(prefers-color-scheme: dark)');
var onChange = function () { if (readMode() === 'auto') applyMode('auto'); };
if (mq.addEventListener) mq.addEventListener('change', onChange);
else if (mq.addListener) mq.addListener(onChange);
}
applyMode(readMode());
}
bindModeMenu();
/* 版本下拉:与 PC 顶栏同一实现口径 —— 清单从部署根取(<prefix>/versions.json 与
<prefix>/site/versions.json 两份按序尝试,取有可用条目的那份),拿不到就退回不可点的
纯角标(data-single="1"),不留一个点不开的下拉。
清单不许把用户带去外站:path 只允许 ".."(站点根自身)或 x.y.z 快照目录名。 */
var VERSION_KEY_PATH = '..';
function isValidVersionPath(p) {
if (typeof p !== 'string' || !p.length || p.indexOf('\\') >= 0 || p.indexOf('//') >= 0) return false;
if (p === VERSION_KEY_PATH) return true;
return /^\d+\.\d+\.\d+$/.test(p);
}
/* 清单里的 path 相对部署根,".." = /site/、x.y.z = /x.y.z/site/。
当前页可能不在部署根下(快照页),故先还原部署根绝对路径再算回相对当前页。 */
function versionHref(absSite) {
var i = location.pathname.indexOf('/site/');
var prefix = i >= 0 ? location.pathname.slice(0, i) : '';
if (!prefix || absSite.indexOf(prefix + '/') !== 0) return absSite;
var rest = absSite.slice(prefix.length + 1);
var up = '';
var segs = prefix.slice(1).split('/');
for (var k = 0; k < segs.length; k++) up += "../";
return up + rest;
}
function buildVersionList(doc) {
var raw = doc && doc.versions;
if (!raw || !raw.length) return [];
var i = location.pathname.indexOf('/site/');
var prefix = i >= 0 ? location.pathname.slice(0, i) : '';
var list = [];
for (var k = 0; k < raw.length; k++) {
var v = raw[k] || {};
var ver = v.version ? String(v.version) : '';
var path = v.path ? String(v.path) : '';
if (!ver || !isValidVersionPath(path)) continue;
var absSite = (path === VERSION_KEY_PATH ? '' : '/' + path) + '/site/';
list.push({ version: ver, date: v.date ? String(v.date) : '', latest: !!v.latest, href: versionHref(absSite), absSite: absSite, current: false });
}
if (list.length) {
/* 当前版本 = 绝对路径上命中的最长前缀(最具体的那个)——与 PC 的判定一致,
快照页与根站点都能算对。 */
var here = location.pathname;
var best = -1;
for (var j = 0; j < list.length; j++) {
if (here.indexOf(list[j].absSite) === 0 && list[j].absSite.length > best) best = list[j].absSite.length;
}
for (var m = 0; m < list.length; m++) list[m].current = best >= 0 && list[m].absSite.length === best;
if (best < 0 && list[0]) list[0].current = true;
}
return list;
}
function bindVersionMenu() {
var trigger = document.getElementById('m-ver-trigger');
var menu = document.getElementById('m-ver-menu');
var chip = document.getElementById('m-ver');
if (!trigger || !menu) return;
var close = function (refocus) {
menu.classList.add('hidden'); trigger.setAttribute('aria-expanded', 'false');
if (refocus) trigger.focus();
};
var interactive = false;
var render = function (list) {
interactive = !!(list && list.length);
/* 只有清单拿不到才退回纯角标;只要有一版就是下拉(里面至少能看清当前是哪个版本) */
trigger.setAttribute('data-single', interactive ? '0' : '1');
if (!interactive) { menu.classList.add("hidden"); return; }
var cur = null;
for (var k = 0; k < list.length; k++) if (list[k].current) cur = list[k];
if (chip) chip.textContent = 'v' + ((cur && cur.version) || VERSION);
/* 重建选项(幂等:重复调用不会叠加) */
Array.prototype.slice.call(menu.querySelectorAll('.m-ver-opt')).forEach(function (n) { n.remove(); });
list.forEach(function (it) {
var opt = document.createElement('button');
opt.type = 'button'; opt.className = 'm-ver-opt'; opt.setAttribute('role', 'option');
opt.setAttribute('data-version', it.version);
opt.setAttribute('aria-selected', it.current ? 'true' : 'false');
var tag = it.current ? '当前浏览' : (it.latest ? '最新版' : (it.date || ''));
opt.innerHTML = '<span class="m-ver-opt-num">v' + it.version + '</span><span class="m-ver-opt-tag">' + tag + '</span>';
if (it.href) opt.setAttribute('data-href', it.href);
opt.addEventListener("click", function () {
if (!it.current && it.href) location.href = it.href;
else close(true);
});
menu.appendChild(opt);
});
};
trigger.addEventListener('click', function () {
if (!interactive) return;
if (menu.classList.contains('hidden')) { menu.classList.remove('hidden'); trigger.setAttribute('aria-expanded', 'true'); }
else close(false);
});
trigger.addEventListener('keydown', function (e) {
if (!interactive) return;
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); menu.classList.remove('hidden'); trigger.setAttribute('aria-expanded', 'true'); }
else if (e.key === 'Escape') close(true);
});
document.addEventListener('click', function (e) {
if (!menu.classList.contains('hidden') && !menu.contains(e.target) && !trigger.contains(e.target)) close(false);
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && !menu.classList.contains('hidden')) close(true);
});
/* 两份清单按序尝试(不并行:快照页那份必然 404,并行会把 404 记进控制台,
而站点门禁的「零控制台错误」是硬断言)。 */
if (!window.fetch || location.protocol === 'file:') return;
var i = location.pathname.indexOf('/site/');
if (i < 0) return;
var prefix = location.pathname.slice(0, i);
var urls = [prefix + '/versions.json', prefix + '/site/versions.json'];
var attempt = function (idx) {
if (idx >= urls.length) return;
fetch(urls[idx], { cache: 'no-store' })
.then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(function (doc) { var list = buildVersionList(doc); if (list.length) render(list); else attempt(idx + 1); })
.catch(function () { attempt(idx + 1); });
};
attempt(0);
}
bindVersionMenu();
function done(btn) {
var old = btn.textContent; btn.textContent = '已复制'; btn.classList.add('is-done');
setTimeout(function () { btn.textContent = old; btn.classList.remove('is-done'); }, 1200);
}
document.addEventListener('click', function (e) {
var btn = e.target && e.target.closest ? e.target.closest('.m-copy') : null;
if (!btn) return;
var src = document.getElementById(btn.getAttribute('data-copy') || '');
var text = src ? (src.innerText || src.textContent || '') : '';
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function () { done(btn); }, function () { fallback(btn, text); });
} else { fallback(btn, text); }
});
function fallback(btn, text) {
try {
var ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly', '');
ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta);
ta.select(); document.execCommand("copy"); document.body.removeChild(ta); done(btn);
} catch (err) { btn.textContent = '复制失败'; }
}
})();
</script>
</body>
</html>