Files
UI/.ua/backlog.json
T

3353 lines
323 KiB
JSON
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.
{
"$schema": "迭代点子库,status: open=待做 / done=已完成;按数组顺序取第一条 open 执行",
"items": [
{
"id": "b01",
"title": "新增 tooltip 组件",
"motivation": "皮肤包 16 组件缺少悬浮提示,表单与工具栏场景常用;可复用自绘弹层与微动画既有模式",
"acceptance": "packages/skin/ui 新增 tooltip.ts:content/message 简单 API,hover/focus 触发,延迟约 150ms 出场,带出入场微动画与 prefers-reduced-motion 兜底,只消费 --ua-* 令牌;导出并注册进 ua.manifest.json,文档站 props 表与 llms-full.txt 同步更新;npm run check 全绿",
"status": "done",
"notes": "API 定为 tooltip(props, trigger) 包裹式;delay 默认 150ms,Esc 立即收起,气泡 pointer-events:none 防抖,aria-describedby + role=tooltip 保证读屏可读;demo 新增 sec-tooltip 区块。顺带修复 ua style 对 https:// 的误判(先剥离字面量再定位 //)",
"domain": "new"
},
{
"id": "b02",
"title": "新增 toast 消息提示组件",
"motivation": "alert 是常驻块级元素,缺少轻量转瞬即逝的全局反馈;操作成功/失败场景刚需",
"acceptance": "新增 toast.ts:toast.success/error/warning/info 四个工厂函数,入参 {message, duration?},默认 3s 自动消失,右上角堆叠,语义色复用四档 variant,出入场微动画 + prefers-reduced-motion 兜底;导出、manifest、文档、llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "对外暴露 toast.info/success/warning/error(message, duration?) 四工厂 + showToast(props) 完整入参版;全局堆叠容器懒创建,role=status 无障碍;退场用 transitionend 移除并带 320ms 兜底(覆盖 reduced-motion 下 transition 不触发的情况);样式复用 alert 语义色 color-mix 模式,reduced-motion 列表已补 .ua-toast;demo 新增 sec-toast 区块",
"domain": "new"
},
{
"id": "b03",
"title": "新增 skeleton 骨架屏组件",
"motivation": "异步加载占位是数据表格与卡片的标配,配合 dataTable 空状态形成完整加载体验",
"acceptance": "新增 skeleton.ts:skeleton({variant: 'text'|'rect'|'circle', rows?}),呼吸/扫光微动画 + prefers-reduced-motion 静态化,只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt 同步;demo 页加一节演示;npm run check 全绿",
"status": "done",
"notes": "skeleton({variant, rows}):text(多行,末行缩短)/ rect / circle 三形状,扫光动画(::after 位移 + 渐变),reduced-motion 下静态灰块(动画列表已补 .ua-skeleton 与 ::after);demo 新增 sec-skeleton 卡片组合演示",
"domain": "new"
},
{
"id": "b04",
"title": "新增独立 pagination 分页器组件并与 dataTable 演示联动",
"motivation": "table-core 已有 pagination 插件,但皮肤包没有独立分页器;dataTable 演示也未展示分页联动",
"acceptance": "新增 pagination.ts:受控 API {page, pageSize, total, onChange},页码过多时折叠省略号,键盘可达,禁用态完整;demo 页 dataTable 一节增加分页联动演示;导出、manifest、文档、llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "pagination(props):page 信号双向绑定(受控),≤7 页全展开、更多时首尾 + 当前页 ±1 折叠省略号(renderList 重建),原生 button 键盘可达,aria-current / aria-label 完整,首末页禁用 ‹ ›;dataTable 新增可选 page 信号桥接(双向同步 + 越界钳制,防无效派发);demo sec-table 新增 pageSize 3 × 24 行的联动卡片",
"domain": "new"
},
{
"id": "b05",
"title": "文档站演示区由 manifest 驱动自动生成",
"motivation": "目前 apps/docs/index.html 手写 props 表与演示,每加组件都要人肉同步;由 manifest 单一来源驱动可消除重复维护",
"acceptance": "ua CLI 新增 docs-gen 逻辑:读取各组件 ua.manifest.json 中的 components 参数表,自动生成 props 表 HTML 片段注入文档站模板;生成结果与现有手写版信息量对齐(props 名/类型/默认值/说明);npm run check 全绿且文档站可正常打开",
"status": "done",
"notes": "核查确认 props 表/示例/步骤/演示 iframe 早已由 ua docs 从 manifest docs.components 单一来源驱动;真正的缺口是信息量不含默认值——本轮 CLI propsTable 新增「默认值」列(props 条目可选 df 字段,缺省渲染 —),llms-full.txt 参数行同步追加「默认 x」标注;16 处可机读默认值从描述文案提取进 df;check 全绿 + 文档站结构完整性校验通过",
"domain": "tooling"
},
{
"id": "c01",
"title": "button / checkbox / switch 补 disabled 禁用态",
"motivation": "三个基础交互组件缺禁用能力,表单只读/权限控制场景刚需",
"acceptance": "ButtonProps / CheckboxProps / SwitchProps 新增可选 disabled(boolean | Signal<boolean>):静态布尔与信号绑定都支持,禁用时事件不触发、置灰 + not-allowed 光标;demo 表单区加静态禁用与信号联动示例;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "零新增辅助代码:复用 frame h() 对 false/undefined 移除属性、Signal 函数走响应式绑定的既有语义,disabled 透传即可;CSS 补通用禁用态(opacity 0.45 + not-allowed,开关轨道同级置灰);demo 用同一 locked 信号绑定 switch 的 checked 与 checkbox 的 disabled 展示联动",
"domain": "tune"
},
{
"id": "c02",
"title": "textField / textarea 补 disabled 禁用态",
"motivation": "输入类组件与 c01 的交互组件对齐,表单只读场景闭环",
"acceptance": "TextFieldProps / TextareaProps 新增可选 disabled(boolean | Signal<boolean>),禁用时不响应输入、置灰 + not-allowed 光标;demo 禁用示例区补一个信号绑定输入框;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "与 c01 同模式:disabled 透传 h() 即得布尔/信号双支持;禁用态 CSS 追加 .ua-input / .ua-textarea;demo 复用 locked 信号联动开关与输入框",
"domain": "tune"
},
{
"id": "d01",
"title": "新增下拉菜单 menu 组件",
"motivation": "工具栏/行操作场景缺省可折叠的动作集合,select 只能选值不能派发动作;可复用自绘弹层与微动画既有模式",
"acceptance": "packages/skin/ui 新增 menu.ts:触发器 + 自绘弹层,items 支持 disabled / danger / divider,点条目 / 点外部 / Esc 关闭,↑↓ 环形导航(禁用项跳过),Enter/Space 选中,Esc 收起后焦点回触发器;只消费 --ua-* 令牌并带 prefers-reduced-motion 兜底;导出 + manifest + 文档 + llms-full.txt + demo 同步;npm run check 全绿",
"status": "done",
"notes": "API menu({ label, items, variant, align, onSelect });MenuItem = { value, label, disabled?, danger?, divider? };弹层 ua-open 同时挂在根与弹层(根管箭头旋转、弹层管出入场),危险条目取 danger 色;键盘 ↑↓ 环形导航 + Esc 回焦触发器;demo 新增 sec-menu 双示例(含禁用/危险/分隔线与右对齐)",
"domain": "new"
},
{
"id": "d02",
"title": "select / radioGroup 补 disabled,修复 select 箭头旋转失效",
"motivation": "c01/c02 已覆盖 button/checkbox/switch/输入类,选择类仍缺禁用;同时发现 select 的 .ua-select.ua-open 选择器永不命中(ua-open 只挂在弹层),展开时箭头不旋转",
"acceptance": "SelectProps / RadioGroupProps 新增可选 disabled(boolean | Signal<boolean>);禁用时 select 触发器置灰且不展开弹层,radioGroup 整组置灰不可选;select 箭头在展开时旋转 180°;demo 加信号禁用与静态禁用示例;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "disabled 仍走 h() 透传(原生 button/input 拦截交互)零辅助代码;select 根节点补 class 函数加 ua-open,箭头旋转选择器命中;CSS 通用禁用态列表并入 .ua-select-trigger 与 .ua-radio,另加 :has() 让单选行整行弱化、禁用触发器 hover 不改边框色",
"domain": "tune"
},
{
"id": "d03",
"title": "新增折叠面板 accordion 组件",
"motivation": "长表单/设置页需要分区收起,皮肤包无折叠容器;grid 行高过渡可零测量实现展开动画",
"acceptance": "新增 accordion.ts:items(value/title/content/disabled)+ 单开(手风琴)与 multiple 多开,active 信号可选受控,标题为原生 button(aria-expanded/controls + region 关联),内容区 0fr→1fr 过渡且 reduced-motion 静态化;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done",
"notes": "contents 走 appendChild 既有语义,字符串/DOM/响应式函数通吃;未传 active 时内部 signal<string[]> 持有展开集合,受控模式只认 active 单开;CSS 用 grid-template-rows 0fr→1fr 过渡免测量,reduced-motion 列表补 ua-acc-*;demo 新增 sec-accordion(多开 + 禁用面板)",
"domain": "new"
},
{
"id": "d04",
"title": "dataTable 接加载态(骨架行 + 交互禁用)",
"motivation": "异步取数时表格直接闪空状态,缺少 loading 反馈;skeleton 已就位但未与表格打通",
"acceptance": "DataTableProps 新增 loading?: boolean | Signal<boolean>;为真时表体渲染骨架行(行数随 pageSize,列数含选择列)、筛选输入与上下页按钮禁用、容器 aria-busy=true;demo 加开关演示;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "busy() 读取器兼顾布尔与信号(信号在 renderList 内调用可被依赖收集);骨架行数量 Math.min(pageSize,6) 防过长;加载态优先于数据判断,避免闪出旧行;disabled 仍走 props 透传,零新增辅助组件",
"domain": "tune"
},
{
"id": "d05",
"title": "新增面包屑 breadcrumb 组件",
"motivation": "多层级页面的位置指示缺失;顺带补齐导航类组件的语义结构(nav/ol/li + aria-current)",
"acceptance": "新增 breadcrumb.ts:nav + ol 语义结构,末项自动为当前页(不可点、aria-current=page),条目有 href 渲染 <a>、无 href 渲染 button 走 onNavigate,分隔符可配;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done",
"notes": "API breadcrumb({ items: {label, href?}[], separator?, onNavigate? });分隔符 aria-hidden;reduced-motion 列表补 .ua-crumb-link;demo 新增 sec-breadcrumb('›' 分隔 + toast 反馈跳转);首轮 style 门禁拦下多行 /*** 注释(检查器只认紧邻单行块),已改单行",
"domain": "new"
},
{
"id": "d06",
"title": "门禁新增 ua verify:清单 ↔ 文档 ↔ 演示锚点一致性校验",
"motivation": "新增组件靠人工同步 manifest/docs/demo,易漏(本轮已出现 badge/alert/spinner 手把手只有 2 步);把不变量机器化,防回归",
"acceptance": "CLI 新增 verify 子命令:docs.components 的 id 唯一且 api.exports 有 `${id}(` 或以 `${id}.` 开头条目、props 条目必备 n/t/d、steps ≥3、example 必填、demo 锚点必须存在于 apps/demo/main.ts;纳入 npm run check 链条;AGENTS.md 同步;npm run check 全绿",
"status": "done",
"notes": "锚点校验改看演示源码而非打包产物(demo.html 是 esbuild 产物,锚点在运行时生成);顺带补齐 badge/alert/spinner 的第 3 条手把手(说明与 button/skeleton 的分工);check 链变为 build→style→typecheck→smoke→index→verify",
"domain": "new"
},
{
"id": "d07",
"title": "新增可交互气泡卡片 popover 组件",
"motivation": "tooltip 只做纯展示(pointer-events:none),筛选面板/快捷操作需要可点击的浮层;与 tooltip 形成展示/交互分工",
"acceptance": "新增 popover.ts:popover(props, trigger) 包裹式,默认点击开合,trigger:'hover' 走延迟出场,点外部 / Esc 收起(Esc 回焦触发器),content 支持任意 DOM 且内部点击不误关,onOpenChange 回调;aria-haspopup/aria-expanded 正确;只消费 --ua-* 令牌 + reduced-motion 兜底;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done",
"notes": "触发器 click 内 stopPropagation,避免刚开就被 document 的外部点击关掉;aria-expanded 用 effect 写回(setAttribute 不吃函数值);弹层点击已阻止冒泡;demo 新增 sec-popover(含 checkbox+button 的筛选面板与 hover 触发示例)",
"domain": "new"
},
{
"id": "d08",
"title": "演示页加侧边目录与滚动高亮",
"motivation": "演示页已 14 个区块,长页滚动找区块靠手滑;目录同时给文档站 iframe 外的直接访问提供导航",
"acceptance": "demo 页改为 shell 双栏布局(左 sticky 目录 + 右内容),目录项挂载后从 .sec 自动反推(不手写清单),IntersectionObserver 高亮当前区块,窄屏(≤820px)隐藏目录,reduced-motion 下关平滑滚动;npm run check 全绿、demo 可正常构建",
"status": "done",
"notes": "目录由 buildToc 在 mount 后扫描 section.sec 生成(id + h2 文案),新增区块自动入目录无需维护;rootMargin -10%/-80% 让高亮更跟手;.page 去掉 max-width 由 .shell 统一控宽 1160",
"domain": "tune"
},
{
"id": "d09",
"title": "新增滑块 slider 组件",
"motivation": "设置类场景(音量/阈值/预算)需要范围输入,皮肤包只有文本类输入;原生 range 自带键盘与触屏,接入成本低",
"acceptance": "新增 slider.ts:value 信号双向绑定(拖动写回 + 外部赋值同步),min/max/step 可配,disabled 支持布尔或信号,showValue 可关,onChange 回调;样式只消费 --ua-* 令牌(accent-color)且带聚焦光环与禁用态;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done",
"notes": "组件不持有数值,一律委托调用方信号;effect 内等值不重写 input.value,避免拖动过程被打断;轨道配色用 accent-color 令牌化,无需自绘 thumb;demo 新增 sec-slider(音量 step 5 + 与 locked 信号联动的禁用滑块)",
"domain": "new"
},
{
"id": "d10",
"title": "toast 补手动关闭与同屏堆叠上限",
"motivation": "自动消失前用户无法提前关掉(如已看清错误),且连续触发会无限堆叠淹没页面",
"acceptance": "ToastProps 新增 closable(显示 × 立即退场);同屏最多 4 条,超出挤掉最早一条;退场逻辑抽为内部 dismiss() 供自动/手动共用;is-closable 才接收指针事件,其余保持 pointer-events:none;manifest/文档/llms-full.txt/demo 同步;npm run check 全绿",
"status": "done",
"notes": "dismiss() 统一 transitionend + 320ms 双保险;超限用 firstElementChild.remove() 立即腾位(不走动画,保证硬上限);demo sec-toast 增加「可关闭」按钮演示 showToast({closable:true})",
"domain": "tune"
},
{
"id": "d11",
"title": "modal 补 size 档位与 footer 操作区",
"motivation": "弹窗内容宽窄差异大(确认框 vs 表单),底部操作按钮只能塞进 body 导致分割线语义缺失",
"acceptance": "ModalProps 新增 size?: 'sm'|'md'|'lg'(340/420/640)与 footer?: unknown;footer 渲染右对齐操作区带上分割线;缺省行为与旧版一致;manifest/文档/llms-full.txt/demo 同步;npm run check 全绿",
"status": "done",
"notes": "宽度档位用 .ua-modal-sm/lg 覆盖基准 md,基础宽 min(420px,90vw) 不动;footer 仅在传入时渲染(null 分支不进 DOM);demo 的弹窗改为 size:'lg' + 取消/确定 footer 按钮",
"domain": "tune"
},
{
"id": "d12",
"title": "新增可关闭标签 tag 组件",
"motivation": "badge 纯展示无法移除,筛选条件/已选关键字需要带 × 的标签;补齐 badge 的交互版",
"acceptance": "新增 tag.ts:label + variant 五档 + closable/onClose;closable 时渲染带 aria-label 的 × 按钮;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done",
"notes": "组件无状态:onClose 只通知,节点由调用方按信号重渲染移除(与 form/formItem 返回句柄、dataTable 委托内核的哲学一致);demo 用 signal<string[]> + renderList 演示真实删除链路,另有四档语义色静态展示",
"domain": "new"
},
{
"id": "d13",
"title": "dataTable 列定义支持对齐与单元格格式化",
"motivation": "皮肤层只能 String() 原始值,数字列无法右对齐、年龄/金额无法加单位;内核不关心展示,字段应留在皮肤层",
"acceptance": "新增皮肤层 TableColumn<T> extends ColumnDef<T>,追加 align?: left|center|right 与 format?: (value,row)=>string;DataTableProps.columns 改用它;表头与单元格共用对齐类;format 走 textContent 天然免注入;demo 表格演示年龄右对齐+单位、城市居中;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "严格分层:内核 ColumnDef 不动,展示字段只在 skin 的 TableColumn 上扩展(extends 而非改核心);对齐类同时作用于 th(取代原 class 函数,合并 ua-sortable);demo 首表展示 align:right + format(v=>`${v} 岁`)、城市居中",
"domain": "tune"
},
{
"id": "d14",
"title": "新增抽屉 drawer 组件",
"motivation": "详情查看/编辑需要不打断主视图的侧滑容器,modal 是强打断确认;移动端尤其常用",
"acceptance": "新增 drawer.ts:open 信号驱动,placement 左右、width 可调(窄屏 92vw 上限)、footer 操作区、遮罩 / × / Esc 关闭、打开聚焦首个可交互元素;只消费 --ua-* 令牌 + reduced-motion 兜底;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done",
"notes": "遮罩开合用 opacity+visibility(与 tooltip/popover 同模式),面板靠 transform:translateX 滑入;width 走 inline style(调用方数据驱动的表现值不进 CSS);demo 新增 sec-drawer(订单详情 + footer 关闭 + tag/badge 组合)",
"domain": "new"
},
{
"id": "d15",
"title": "CLI 包补 ua.manifest.json 并修正文档里的过期组件数",
"motivation": "工具链自身不在注册表里,Agent 无法通过 MCP/llms 发现 ua 的 6 个子命令;AGENTS.md 与 ARCHITECTURE.md 仍写 17 个组件(实际 27)",
"acceptance": "packages/tools/cli/ua.manifest.json(type: tooling)描述 index/list/info/docs/style/verify/mcp 七个子命令与约定,索引包数变 5;AGENTS.md 与 ARCHITECTURE.md 的组件数改成 27;llms.txt / llms-full.txt 重新生成且 CLI 段落可读;npm run check 全绿",
"status": "done",
"notes": "tooling 类型在 ORDER 表外,排序自然落到末尾(??9 兜底),无需改 CLI 排序逻辑;CLI 清单 docs.components 留空,故不参与 verify 的组件级校验;顺带确认 docs 生成对无组件包不报错",
"domain": "tooling"
},
{
"id": "d16",
"title": "dataTable 落地内核已定义的列宽 width",
"motivation": "table-core 的 ColumnDef 声明了 width?: number | 'flex',但皮肤层从未消费,列宽完全靠浏览器猜;契约与实现脱节",
"acceptance": "dataTable 渲染 colgroup:数字 width 落成固定像素 <col>,'flex' 或不设交给自适应,选择列固定 44px;demo 表格演示 name 160 / age 100 / city flex;manifest 列定义说明与手把手同步;npm run check 全绿",
"status": "done",
"notes": "用 colgroup 而非逐 th 设宽,保证表头与表体一致且不入侵单元格渲染;未开 table-layout:fixed(nowrap 内容会被裁),宽高仍由内容兜底;pinned 固定列仍未消费(多列 sticky 偏移需测量),留作后续条目",
"domain": "tune"
},
{
"id": "d17",
"title": "表单错误态覆盖包裹式控件并修正校验时机事件",
"motivation": "① 现有 blur 不冒泡,checkbox/select/radioGroup 这类包裹式控件失焦永远不校验;② ua-invalid 只加在控件根,自绘 select 的边框在内部触发器上,视觉上无错误提示;③ 缺 aria-invalid,读屏感知不到",
"acceptance": "formItem 用 focusout 取代 blur、补 change(select/checkbox 变更即校验);错误态同时落到控件根与内部原生控件,并同步 aria-invalid;CSS 覆盖 .ua-invalid 后代 .ua-input/.ua-textarea/.ua-select-trigger/.ua-radio/.ua-slider-input;demo 表单加必填 select 演示;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "marked() 统一收集控件根 + 内部 input/textarea/select/.ua-select-trigger 两处落点;.ua-invalid 后代选择器让包裹式控件描红,无需组件各自改签名(低侵入);demo formlab 新增城市必填 select,提交结果显示城市",
"domain": "tooling"
},
{
"id": "d18",
"title": "把「皮肤包禁止硬编码颜色」做成 ua verify 门禁",
"motivation": "AGENTS.md 写着皮肤禁硬编码颜色,但一直靠自觉;ui.css 实际残留 5 处 #fff(按钮/徽标/开关滑块),暗色下对比也不理想",
"acceptance": "checkSkinCss():skin-dom 类型包里所有 .css 出现 #hex 字面量即报错并指出文件行号;5 处 #fff 改为 var(--ua-color-bg)(信号色底上的反色,暗色自动翻转);负向测试(临时加 .ua-tmp{color:#fff})能拦下,移除后回归全绿;AGENTS.md 门禁说明同步;npm run check 全绿",
"status": "done",
"notes": "rgba(...) 未禁:仅用于阴影与遮罩透明度叠加,不承载品牌色,禁掉会逼出更难读的写法(已在函数注释写明边界);实现上先随 python 写文件把正则转义搞坏两次,改用编辑工具直写并做负向验证确认门禁真实生效",
"domain": "tooling"
},
{
"id": "d19",
"title": "dataTable 选中态受控(selected 信号)",
"motivation": "选中集只在内核里,外部拿不到也无法预设,批量操作/清空选择只能靠内部工具栏;与 page 受控桥接形成对称能力",
"acceptance": "DataTableProps 新增 selected?: Signal<RowKey[]>,与内核选择双向同步(内容比较避免来回派发,外部数组拷贝保护内核);demo 首表传 picked 信号,展示已选计数与「清空选择」按钮;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "复用 page 桥接的同一套思路:两个 effect 各管一向,sameKeys 内容比较收敛;内部写回拷贝 [...internal],防外部改动内核数组;demo 需 import type { RowKey } 才能对齐 Signal 的逆变",
"domain": "tune"
},
{
"id": "d20",
"title": "progress 支持不确定态(未知时长加载)",
"motivation": "请求时长未知时只能硬编造一个假百分比,或用 spinner 但失去条状形态;不确定态是进度条的标准形态",
"acceptance": "ProgressProps.value 改为可选;缺省时渲染不确定态(35% 填充块 1.2s 往复)、不显示百分比、role=progressbar + aria-label=进行中;定量态补 aria-valuemin/max/now;reduced-motion 静态化;demo 加不确定态示例;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "以「不传 value」作为不确定态的开关,不新增布尔参数(少一个可矛盾的状态);定量态的 aria-valuenow 用函数属性随信号更新;reduced-motion 下靠既有 .ua-progress-fill 规则把动画归零,退化为静态 35% 条",
"domain": "tune"
},
{
"id": "d21",
"title": "无障碍收尾:× 按钮 aria-label、spinner/skeleton 语义、tablist 标签",
"motivation": "审计发现 modal 的 × 按钮无 aria-label(读屏只念「×」)、spinner 无 role=status(不播报加载)、skeleton 未 aria-hidden(占位文本被读出来)、tablist 缺 aria-label 与 aria-disabled",
"acceptance": "modal 关闭按钮补 type/aria-label、spinner 补 role=status + aria-label、skeleton 全形态 aria-hidden、tabs 的 tablist 补 aria-label 且禁用页签标 aria-disabled;manifest conventions 记录无障碍基线;npm run check 全绿",
"status": "done",
"notes": "纯属性级修复,不动结构与样式;tabIndex 漫游(仅激活页签可 Tab)此前已实现,本次只补语义;分页器审计后无需改动(已有 nav aria-label、页码 aria-current、‹› aria-label)",
"domain": "tune"
},
{
"id": "d22",
"title": "新增头像 avatar 组件",
"motivation": "用户列表/评论区需要头像位,缺 component 只能手拼 img + 圆角;首字占位是缺图时的标准兜底",
"acceptance": "新增 avatar.ts:name 必填(alt / title / 首字来源),src 可选(有则图片、无则首字占位),size 可调,variant 四档语义底色;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
"status": "done",
"notes": "首字占位对中文取首字、英文大写,空名兜底 ?;flex:none 保证不被挤压;首次跑 verify 被自身新门禁拦下(声明 sec-avatar 但 demo 缺区块),补独立区块后通过——门禁有效性再次得到验证",
"domain": "new"
},
{
"id": "d23",
"title": "alert 补标题行与可关闭,并补 role=alert",
"motivation": "常驻提示无法消除(错误已处理完仍占版面),且只有单层正文放不下「结论 + 详情」;role=alert 缺失导致读屏不播报",
"acceptance": "AlertProps 新增 title? / closable? / onClose?;关闭时先退场(opacity+上移,200ms)再从 DOM 移除;容器 role=alert;只消费 --ua-* 令牌;demo 新增 sec-alert 展示无标题与可关闭带标题两种;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "与 toast 的分工保持:alert 常驻在页面流里、toast 全局浮动;关闭后由调用方自行决定是否重新渲染;body/title/message 三层结构让 flex 布局能把 × 对齐到首行;对现有 alert({message}) 调用完全兼容",
"domain": "tune"
},
{
"id": "d24",
"title": "重写 README 并把组件清单纳入 verify 防漂移",
"motivation": "README 只有标题一行,新人/Agent 从仓库首页拿不到任何信息;而文档里的组件数此前已经漂移过(17 vs 27)",
"acceptance": "README 覆盖定位 / 快速开始 / 28 组件清单 / 目录表 / 三道门禁 / Agent 接入 / 分支流水线;新增 <!-- ua:components --> 标记区块,ua verify 校验区块 id 集合与全部清单 docs.components 完全一致(缺/多都报);负向测试(删 avatar)能拦下;AGENTS.md 同步;npm run check 全绿",
"status": "done",
"notes": "不做自动写入(避免工具偷偷改文档),只做一致性校验:新增组件时 verify 报「README 组件区块缺少 xxx」,由人补一行;标记区块用 · 分隔便于人读与机器切分",
"domain": "tune"
},
{
"id": "d25",
"title": "menu 条目支持快捷键提示",
"motivation": "菜单里展示快捷键是桌面级体验的标配;缺字段时调用方只能把快捷键拼进 label,文案与对齐都失控",
"acceptance": "MenuItem 新增 shortcut?: string;条目改为 flex 两端对齐,label 单行省略,右侧 kbd 灰字提示(不绑定按键);demo 文件操作菜单加 F2 / ⌘D;manifest/文档/llms-full.txt 同步;npm run check 全绿",
"status": "done",
"notes": "明确边界:快捷键只做展示,实际按键仍需调用方全局监听(组件不做全局键盘陷阱);两端对齐让长文案截断而快捷键始终可见;kbd 用 inherit 字体避免等宽字跳脱整体排版",
"domain": "tune"
},
{
"id": "d26",
"title": "select 键盘导航:↑↓ 环形移动、Home/End 跳转、Esc 回焦触发器",
"status": "done",
"domain": "tune"
},
{
"id": "d27",
"title": "modal 焦点陷阱:Tab/Shift+Tab 回绕、role=dialog/aria-modal、关闭归还焦点",
"status": "done",
"domain": "tune"
},
{
"id": "d28",
"title": "drawer 焦点陷阱:Tab/Shift+Tab 回绕、关闭归还焦点(对齐 modal)",
"status": "done",
"domain": "tune"
},
{
"id": "d29",
"title": "menu 补 Home/End 跳转,与 select 键盘导航对称",
"status": "done",
"domain": "tune"
},
{
"id": "e01",
"title": "新增 inputNumber 数字输入组件",
"motivation": "数量、份数等离散数值输入是表单常见场景,纯 textField 缺少步进语义;加减按钮 + 键盘调节可复用既有 button 与信号双向绑定模式",
"acceptance": "packages/skin/ui 新增 inputNumber.ts:−/+ 按钮 + 输入框三段式,value 信号双向绑定,min/max/step 超界自动夹取,键盘 ↑↓ 调节,非法输入回滚,disabled 支持布尔或信号;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt 同步;demo 加一节演示;npm run check 全绿",
"status": "done",
"notes": "inputNumber(props):−/+ 复用 ghost button(自带微动画与 disabled 透传),输入框 inputmode=decimal,change 时 parseFloat 解析、非法回滚当前值;↑↓ 步进并 preventDefault;clamp 统一夹取 min/max(缺省 ±Infinity);aria-label 三件套 + role=group;CSS 步进按钮收窄、tabular-nums;demo 新增 sec-inputnumber(与禁用态实例共用 qty 信号);manifest/docs/llms-full/README 同步为 29 组件",
"domain": "new"
},
{
"id": "e02",
"title": "新增 stepper 步骤条组件",
"motivation": "下单、注册等多步流程需要进度指示与回跳,现有导航类组件(tabs/breadcrumb/pagination)都不覆盖分步语义;纯展示 + 可选点击回跳,改动范围可控",
"acceptance": "packages/skin/ui 新增 stepper.ts:stepper({steps, active, clickable?, onChange?}),done/current/upcoming 三态视觉,clickable 时已完成步骤可点回跳并写回 active 信号,aria-current=step;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿",
"status": "done",
"notes": "stepper(props):active 信号变化整条重渲染(effect 首跑赋值 + replaceWith,条目少开销可忽略);done 显示 ✓、current 光环聚焦、upcoming 弱化,aria-current=step;clickable 时已完成步骤渲染为 button 回跳写回信号;连接线用 .ua-step + .ua-step::before 横向短线;demo 新增 sec-stepper(上一步/下一步按钮 computed 禁用联动);README 同步 30 组件",
"domain": "new"
},
{
"id": "e03",
"title": "新增 rate 评分组件",
"motivation": "评价、打分场景常用,现有组件无覆盖;星标交互可完整复用信号双向绑定与微动画审美约定,单文件可控",
"acceptance": "packages/skin/ui 新增 rate.ts:rate({value, max?, disabled?, onChange?}),max 缺省 5,hover 预览移出还原,键盘 ←→/↑↓ 调节,aria 星标语义;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿",
"status": "done",
"notes": "rate(props):hover 预览用组件内局部 hover 信号(容器 mouseleave 归零),shown = hover || value 决定点亮数;value/hover 变化整条重渲染(与 stepper 同款 effect + replaceWith 模式);键盘 →/↑ 加分 ←/↓ 减分并 clamp [1,max];role=slider + aria-valuenow;选中色取 --ua-color-warning 金黄;demo 新增 sec-rate(可交互 + 只读两实例共用 score 信号);README 同步 31 组件",
"domain": "new"
},
{
"id": "e04",
"title": "新增 empty 空状态组件",
"motivation": "列表、表格、搜索无结果时需要统一的占位提示,dataTable 内置空态样式但独立场景(卡片、面板、筛选结果)没有可复用组件",
"acceptance": "packages/skin/ui 新增 empty.ts:empty({title?, description?, action?}),居中排版,action 为可选 DOM 操作区(常见放按钮);只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿",
"status": "done",
"notes": "empty(props):纯静态组件(无信号),role=status 由读屏播报;装饰符号 ⌀ 弱化为边框色且 aria-hidden;min-height 120px 撑留白(令牌只到 space-4);demo 新增 sec-empty(信号切换有数据/无结果对比,内容用响应式函数渲染);README 同步 32 组件",
"domain": "new"
},
{
"id": "e05",
"title": "新增 divider 分隔线组件",
"motivation": "表单分组、卡片内区块、工具栏之间需要统一分隔语义,目前只有 menu 内部私有分隔线,独立场景无组件可用",
"acceptance": "packages/skin/ui 新增 divider.ts:divider({label?, vertical?, dashed?}),水平/垂直两种方向,可带居中文案标签;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt、README 清单同步;demo 加一节演示;npm run check 全绿",
"status": "done",
"notes": "divider(props):水平线用 ::before/::after 双线夹 label 居中文案;vertical 单线(伪元素清空,dashed 走 border-left);dashed 水平线 border-top 替代背景色;role=separator + aria-label;demo 新增 sec-divider(文案线/虚线/工具栏竖线三种用法);README 同步 33 组件",
"domain": "new"
},
{
"id": "f01",
"title": "组件数漂移治理:AGENTS/ARCHITECTURE 数字纳入 verify 门禁",
"motivation": "组件数第三次漂移(AGENTS.md 与 ARCHITECTURE.md 仍写 27,实际 33),ua verify 只锁 README 区块锁不住散文数字",
"acceptance": "ua verify 新增规则:扫描 AGENTS.md 与 ARCHITECTURE.md 中的「NN 个组件」模式数字,与 manifest docs.components 长度比对不一致即报错;当前两处 27 改为 33;npm run check 全绿",
"status": "done",
"notes": "来源:2026-10-23 第二次冷启动审计应修 #1",
"domain": "tooling"
},
{
"id": "f02",
"title": "check 链描述同步:AGENTS.md 与 ITERATION.md 补 verify 第六步",
"motivation": "AGENTS.md:10 与 ITERATION.md:31 写 5 步,package.json 实为 6 步(含 verify),文档与实际门禁不一致",
"acceptance": "两处描述改为 build → style → typecheck → smoke → index → verify 六步;npm run check 全绿",
"status": "done",
"notes": "来源:审计应修 #2",
"domain": "tune"
},
{
"id": "f03",
"title": "新增 ua lint-iterations 门禁:留痕格式机器校验",
"motivation": "iterations.log 41 行中 36 行违规(time 字段混用、UTC 时间戳、缺 commit),靠人工反复修补,规范缺机器校验",
"acceptance": "ua 新增 lint-iterations 子命令:校验每行 JSON 字段恰为 {ts,id,result,commit,summary}、ts 为本地 +08:00 ISO 且可用 Date 解析、commit 为 7-40 位哈希或 aborted 时允许为空、result 枚举 done/aborted;并入 npm run check;历史 36 条违规行按规范修复(ts 以对应 git 提交的 author date 本地时区回填,commit 补哈希);npm run check 全绿",
"status": "done",
"notes": "来源:审计应修 #3;历史行修复用 git log --format=%cI 按条目 id 关联提交时间回填",
"domain": "new"
},
{
"id": "f04",
"title": "commit-lint 收严:chore/docs 等强制 scope,杜绝无 scope 放行",
"motivation": "COMMITTING.md 规定 <type>(<scope>) 格式,但 chore:、docs: 无 scope 被 lint 放行,历史还有 17 条 iter: 缺 scope",
"acceptance": "commit-lint 规则收严为:除 Merge 外所有 type 必须带 scope(scope 允许 cli/skin/core/backlog/留痕等枚举或条目 id);反向用例 chore: xxx / docs: xxx / iter: xxx 必须被拒;正例全部通过;npm run check 全绿",
"status": "done",
"notes": "来源:审计应修 #4;历史 iter 缺 scope 的提交不回改(已发布历史不动),只管增量",
"domain": "tune"
},
{
"id": "f05",
"title": "ua index 幂等化:generatedAt 仅内容变化时更新",
"motivation": "ua index 每次重写 .ua/index.json 的 generatedAt,npm run check 必然弄脏工作区,与「提交前 check」工作流冲突",
"acceptance": "index 命令改为:若除 generatedAt 外内容与现有文件一致则不写入(或 generatedAt 改为内容哈希派生);连续两次 npm run check 后 git status 必须干净",
"status": "done",
"notes": "来源:审计应修 #5",
"domain": "tune"
},
{
"id": "f06",
"title": "pnpm-lock.yaml 入库 + 目录树补全",
"motivation": "lockfile 缺失导致依赖树不可复现;ARCHITECTURE.md 目录树缺 scripts/、COMMITTING.md、ITERATION.md、.ua/ 清单等条目,新代理接手有盲区",
"acceptance": "pnpm-lock.yaml 提交入库;ARCHITECTURE.md 目录树补 scripts/(gitea.mjs/gitea-api.mjs 一句话说明)、COMMITTING.md、ITERATION.md、.ua/backlog.json、.ua/iterations.log、pnpm-workspace.yaml;npm run check 全绿",
"status": "done",
"notes": "来源:审计应修 #6 + 建议 #7",
"domain": "tune"
},
{
"id": "g01",
"title": "ua docs 幂等化:llms-full.txt 时间戳不弄脏工作区",
"motivation": "llms-full.txt 头部含 generatedAt,每次 ua docs 重写必然弄脏工作区,与 index 幂等化(f05)同类问题",
"acceptance": "内容与现有文件仅时间戳之差时不写入;npm run docs 连跑两次 git status 干净",
"status": "done",
"notes": "自补:延续 f05 方案",
"domain": "tune"
},
{
"id": "g02",
"title": "新增 image 图片组件(缩略图 + 点击预览)",
"motivation": "皮肤包缺常规媒体组件,img 缩略图 + 遮罩预览是常规表单/详情页刚需",
"acceptance": "image({src, alt, width?}) 返回缩略图,点击开遮罩大图(Esc/点击遮罩关闭,reduced-motion 兜底);manifest/docs/llms-full/README/demo 同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充方向",
"domain": "new"
},
{
"id": "g03",
"title": "textField/textarea/inputNumber 补 prefix/suffix 附加内容槽",
"motivation": "搜索框放大镜、金额单位等前后缀是输入组件高频需求,目前只能外部拼装",
"acceptance": "三个输入组件新增 prefix/suffix(string|HTMLElement),渲染为输入框内侧前后附加块;manifest 参数表与 llms-full 同步;npm run check 全绿",
"status": "done",
"notes": "自补:可用性细节方向",
"domain": "tune"
},
{
"id": "g04",
"title": "dataTable 筛选框用 prefix 放搜索图标",
"motivation": "g03 落地了 prefix 附加块,dataTable 工具栏筛选框是最佳实战落点,同时验证包装不影响事件冒泡",
"acceptance": "dataTable 筛选输入框带 🔍 prefix;筛选功能回归正常(smoke 不变绿转红);npm run check 全绿",
"status": "done",
"notes": "自补:g03 实战验证",
"domain": "tune"
},
{
"id": "g05",
"title": "ua verify 增加 llms-full.txt 生成同步校验",
"motivation": "llms-full.txt 是手工触发 ua docs 生成的产物,改 manifest 后忘记跑 docs 会产生文档漂移,目前无门禁",
"acceptance": "verify 内联重渲染 Agent 文档(时间戳归一化)与根目录 llms-full.txt 比对,不一致即报错提示 npm run docs;npm run check 全绿",
"status": "done",
"notes": "自补:工具链防漂移方向",
"domain": "tooling"
},
{
"id": "g06",
"title": "组件数门禁扩展 README 计数文本",
"motivation": "f01 的计数漂移校验只覆盖 AGENTS/ARCHITECTURE,README 的「N 个 DOM 组件」文本仍可漂移",
"acceptance": "checkCountDocs 扫描列表加入 README.md,漂移即 verify 失败;npm run check 全绿",
"status": "done",
"notes": "自补:f01 收尾",
"domain": "tooling"
},
{
"id": "g07",
"title": "新增 timeline 时间线组件",
"motivation": "皮肤包缺少按时间顺序展示事件流的组件,日志 / 审批流 / 版本记录场景常用;只需轴线 + 语义色节点,可复用既有令牌与微动画模式",
"acceptance": "新增 packages/skin/ui/src/timeline.ts:items(title/content/time/variant) + mode(left|right) + pending 尾部节点,节点色只走 --ua-* 令牌,入场淡入带 prefers-reduced-motion 兜底;导出 + manifest props 表与 steps + demo sec-timeline + README 与文档同步;npm run check 全绿",
"status": "done",
"notes": "API 定为 items(title/content/time/variant) + mode(left|right) + pending 尾部虚节点;节点语义色走 --ua-* 令牌,0.18s 入场淡入 + 虚线脉冲动画,prefers-reduced-motion 全部关停;demo 新增 sec-timeline(按钮切换 pending);组件数 34→35 三处计数与 README 清单同步",
"domain": "new"
},
{
"id": "g08",
"title": "新增 segmented 分段控制器组件",
"motivation": "tabs 是页签语义(下划线 + 面板),视图 / 单位切换需要紧凑的胶囊分段控件;与 radioGroup 的差别是不承载表单语义",
"acceptance": "新增 src/segmented.ts:options / value(信号双向) / size(sm|md) / disabled,滑块指示条随选中平移,←→ 键盘导航 + roving tabindex,选中态低视觉比重;导出 + manifest + demo sec-segmented + 文档同步;npm run check 全绿",
"status": "done",
"notes": "API 为 options + value(必传信号) + size + disabled(布尔|信号) + onChange;滑块几何由 getBoundingClientRect 实测(减去容器 clientLeft 边框),挂载后 rAF 二次测量;←→ 环形导航 + 漫游 tabindex;整组禁用用 is-disabled 类 + 逐按钮 disabled;demo sec-segmented 用两个实例共用 range 信号演示联动",
"domain": "new"
},
{
"id": "g09",
"title": "新增 descriptions 描述列表组件",
"motivation": "详情页与抽屉里高频需要 label:value 两列布局,目前只能借用 form 或手写 DOM;需要列数自适应与 bordered 两种形态",
"acceptance": "新增 src/descriptions.ts:items(label/value/span) + column(1-4,窄屏自动降列) + bordered + size;label 走 text-muted 令牌,值支持任意 DOM;导出 + manifest + demo sec-descriptions + 文档同步;npm run check 全绿",
"status": "done",
"notes": "API 为 items(label/value/span) + column(1-4) + bordered + size + title;列数通过 CSS 变量 --ua-desc-col 注入,样式表在 ≤640px 覆盖为单列;span 在 min(span, column) 处截断防越界;标题为可选外层 wrap;demo sec-descriptions 展示纯网格(含 tag 值与 span 占两列)与 bordered 小尺寸两种形态",
"domain": "new"
},
{
"id": "g10",
"title": "新增 statistic 统计数值组件",
"motivation": "看板需要大号数值 + 单位 + 环比趋势,现在只能手写;数值变化时的滚动动画能强化数据反馈",
"acceptance": "新增 src/statistic.ts:title / value(Signal<number>) / precision / prefix / suffix / trend(up|down),值变化用 rAF 从旧值滚到新值(约 400ms),prefers-reduced-motion 直接落终值,涨红跌绿;导出 + manifest + demo sec-statistic + 文档同步;npm run check 全绿",
"status": "done",
"notes": "API 为 title + value(信号) + precision + prefix + suffix + trend;滚动用 rAF 逐帧改文本(400ms easeOutCubic),新目标先 cancelAnimationFrame 打断,收尾强制对齐终值防浮点残差;matchMedia prefers-reduced-motion 直落终值;trend 按 A 股涨红跌绿;demo sec-statistic 用随机涨跌按钮演示双向滚动",
"domain": "new"
},
{
"id": "g11",
"title": "浮层定位空间不足时自动翻转",
"motivation": "popover / tooltip / select / menu 固定按 placement 定位,视口底部会溢出被裁切,缺少统一 flip 判定",
"acceptance": "新增 src/position.ts:flip(trigger, panel, preferred) 依据 getBoundingClientRect 与视口高度决定是否上下翻转,四个浮层组件接入;导出 + 文档说明 + demo 覆盖说明;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g12",
"title": "verify 增加 backlog 与 iterations.log 交叉校验",
"motivation": "lint-iterations 只校验行格式,无法发现『条目置 done 但无留痕』或『留痕指向不存在的条目』",
"acceptance": "ua.js 新增交叉校验并挂进 cmdVerify:每条 done 条目必须在 iterations.log 有 result=done 记录,每条日志 id 必须在 backlog 存在;人为注入违规时 verify 退出码 1;npm run check 全绿",
"status": "done",
"notes": "cmdVerify 增加 checkIterations:done 条目必须有 done 留痕、留痕 id 必须存在于 backlog;行格式仍归 lint-iterations;实测注入幽灵 id zz99 后 verify 报 1 violation 退出码 1,还原后复绿;AGENTS.md 门禁说明同步补一句",
"domain": "tooling"
},
{
"id": "g13",
"title": "新增 ua changelog 生成 CHANGELOG.md",
"motivation": "迭代提交已有规范前缀,但没有面向人的变更汇总,qa/stable 的提交史对使用者不可读",
"acceptance": "ua.js 新增 cmdChangelog:解析 git log 按 iter / feat / fix / chore 分组生成 CHANGELOG.md,带 --check 幂等模式(无变化不写盘、退出码 0);挂进 package.json scripts;npm run check 全绿",
"status": "done",
"notes": "cmdChangelog 解析 git log -n 400(%h\\t%s),按 iter/feat/fix/docs/chore|refactor|test|build|ci 五组 + 其他兜底分组渲染,输出不含时间戳故可幂等比对;--check 只校验不写盘、无变化 skip 不写;package.json 挂 changelog 与 changelog:check 两个脚本;不进 check 链(生成内容依赖提交史,提交后天然落后一步)",
"domain": "new"
},
{
"id": "g14",
"title": "演示站侧边目录支持关键字过滤",
"motivation": "组件增至 35 个后侧边目录变长,定位区块只能靠滚动",
"acceptance": "apps/demo/main.ts 的 buildToc 前置搜索框,输入按标题子串过滤目录链接(无匹配给空态提示),Esc 清空;不破坏 IntersectionObserver 高亮;npm run check 全绿",
"status": "done",
"notes": "buildToc 顶部加 type=search 输入框,input 事件按标题子串小写匹配过滤(hidden 属性 + .toc-link[hidden] 覆盖 display:block),无匹配显示 .toc-empty 空态,Esc 清空后恢复全量;IntersectionObserver 高亮逻辑未改动;样式写在 apps/demo/build.mjs 模板内",
"domain": "tune"
},
{
"id": "g15",
"title": "toast 悬停暂停自动关闭",
"motivation": "toast 到点即消失,用户来不及读完长文案;悬停暂停是主流反馈组件的常规可用性细节",
"acceptance": "showToast 悬停时暂停倒计时、移出后按剩余时长继续;duration 语义不变;manifest 与 demo 说明同步;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g16",
"title": "image 加载失败占位",
"motivation": "图片地址失效时显示破图图标,观感差且无提示;需要统一的失败占位兜底",
"acceptance": "img 加载失败自动替换为虚线占位块,fallback 可自定义文案;样式只消费 --ua-* 令牌;manifest/demo 同步;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g17",
"title": "select 键入字母快跳选项",
"motivation": "选项多时纯方向键逐个移动太慢,主流下拉都支持键入首字母循环跳转",
"acceptance": "select 展开时键入可打印字符循环聚焦 label 前缀匹配项(不区分大小写),1 秒无输入重置缓冲;既有 ↑↓/Home/End 行为不变;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g18",
"title": "textField 可清空按钮",
"motivation": "长文本输入后逐字删除太慢,主流输入框都提供 clearable 一键清空",
"acceptance": "clearable 开启后有值时显示清空按钮,点击清空并同步信号与 onInput;空值自动隐藏;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g19",
"title": "textarea 字数统计",
"motivation": "多行输入常配字数上限,用户需要 n/limit 实时反馈而不是提交后才报错",
"acceptance": "maxCount 传入后 textarea 设原生 maxlength 并在下方附加块显示实时计数,与外部写回同步;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g20",
"title": "inputNumber 长按连发步进",
"motivation": "大步长调节时反复点按钮太累,主流数字输入都支持长按持续步进",
"acceptance": "按住 −/+ 按钮 400ms 后每 100ms 连续步进,松开或移出即停;单击行为不变;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g21",
"title": "pagination 跳页输入框",
"motivation": "页数多时逐页点击太慢,主流分页器都提供直接跳页入口",
"acceptance": "jumpable 开启且总页数 > 1 时末尾显示页码输入 + 总页数,回车跳转并钳制合法范围;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g22",
"title": "menu 键入字母快跳菜单项",
"motivation": "菜单项多时方向键逐个移动太慢,与 select 的键入快跳对齐保持组件族一致性",
"acceptance": "menu 展开时键入可打印字符循环聚焦 label 前缀匹配项(忽略大小写),1s 无输入重置;既有导航不变;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g23",
"title": "tabs 键盘 Home/End 跳转",
"motivation": "页签多时方向键逐个移动太慢,与 select/menu 的 Home/End 行为对齐",
"acceptance": "tabs 聚焦时 Home 跳首个可用页签、End 跳末个可用页签并激活;方向键行为不变;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节;Home/End 直达首末启用页签,方向键环形逻辑不变",
"domain": "tune"
},
{
"id": "g24",
"title": "新增 list 列表组件",
"motivation": "皮肤包缺通用列表容器,审批/消息/搜索结果等场景只能手写 div 拼装,样式与令牌脱节",
"acceptance": "list({ items, header, footer, bordered, emptyText, onItemClick }) 渲染条目 avatar/title/description/extra;空数组回落 empty;组件数 38→39 三处计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;items 为空回落 empty,onItemClick 让条目可点击",
"domain": "new"
},
{
"id": "g25",
"title": "令牌查询能力:ua tokens + MCP get_tokens",
"motivation": "Agent 与人都得翻 tokens.json 才知道有哪些 --ua-* 变量,容易凭记忆写错名;llms-full 里还把 textMuted 当成变量名(实际是 text-muted)",
"acceptance": "ua tokens 列出全部令牌(颜色带暗色值)、--json 输出数组;MCP 新增 get_tokens 工具;llms-full 令牌名统一 kebab-case;npm run check 全绿",
"status": "done",
"notes": "自补:工具链改进;CLI 与 MCP 共用 loadTokenList 单一来源",
"domain": "tooling"
},
{
"id": "g26",
"title": "verify 增加令牌引用校验",
"motivation": "皮肤样式表写错令牌名(如 --ua-color-textt)不会报错、静默失效;#hex 门禁只管硬编码,管不到拼错变量",
"acceptance": "var(--ua-*) 必须是 tokens.css 已定义或组件自设变量,否则 verify 失败;ua tokens / llms-full 令牌清单统一取 tokens.css(补全字号与复合间距);npm run check 全绿",
"status": "done",
"notes": "自补:门禁改进;负向测试 var(--ua-color-textt) 已被拦截",
"domain": "tooling"
},
{
"id": "g27",
"title": "弹层 Esc 栈:多弹层只关最上层",
"motivation": "modal 与 drawer 各自往 document 挂 keydown,叠加打开时一次 Esc 会把所有弹层全关;实例销毁也不摘监听",
"acceptance": "modal/drawer 共用 overlay.ts 的 Esc 栈,后开先关;栈空自动摘全局监听;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节;去掉两处各自挂的 document keydown",
"domain": "tune"
},
{
"id": "g28",
"title": "Esc 逐层关闭:浮层阻止冒泡",
"motivation": "弹窗内的 select/menu/popover 展开时按 Esc,会先收下拉再冒泡到 document 的 Esc 栈,导致弹窗一起被关——一次 Esc 关两层",
"acceptance": "menu/select/popover/tooltip/image 的 Esc 处理各自 stopPropagation,只关本层;演示页在弹窗内放下拉可复现逐层关闭;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节;演示页弹窗内加下拉可复现",
"domain": "tune"
},
{
"id": "g29",
"title": "新增 carousel 轮播组件",
"motivation": "组件库缺轮播,首页 banner/图片墙只能外部引库,样式与令牌体系脱节",
"acceptance": "carousel({ items, index, autoplay, height, label, onChange }) 横排位移 + 指示点/箭头/←→ 键切换 + 悬停暂停 + 环形回绕;组件数 39→40 三处计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;index 信号可外部受控,内部信号兜底",
"domain": "new"
},
{
"id": "g30",
"title": "check 链并入 demo 构建 + 产物锚点校验",
"motivation": "npm run check 不重建 apps/demo/dist/demo.html,改了演示页门禁照样全绿,文档站 iframe 拿到的是旧产物;verify 只查 main.ts 源码锚点,查不到产物缺块",
"acceptance": "check 链为 build → demo → style → typecheck → smoke → index → verify → lint-iterations;verify 校验产物含全部 sec-* 锚点(负向删除锚点即失败);文档四处链路描述同步;npm run check 全绿",
"status": "done",
"notes": "自补:门禁改进;文档四处链路描述同步",
"domain": "tooling"
},
{
"id": "g31",
"title": "新增 tree 树形组件",
"motivation": "文件树/组织架构/权限勾选等场景缺组件,只能手写嵌套 DOM,展开态与键盘可达性都要重造",
"acceptance": "tree({ nodes, expanded, selected, defaultExpandAll, onSelect }) 递归渲染 + 箭头展开收起 + 点选 + Enter/→/← 键盘 + 禁用不响应;组件数 40→41 三处计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;selected/expanded 信号均可外部受控",
"domain": "new"
},
{
"id": "g32",
"title": "tree 补 ↑↓/Home/End 行间导航",
"motivation": "tree 只支持 Enter/→/←,无法用键盘在节点间移动焦点,键盘用户仍得靠鼠标找行",
"acceptance": "↑↓ 在当前可见节点间移动、Home/End 到首末行,只改焦点不改选中,收起分支的子节点不参与遍历;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节;NodeList 需 Array.from 展开(downlevel 迭代)",
"domain": "tune"
},
{
"id": "g33",
"title": "新增 upload 文件选择组件",
"motivation": "表单里选附件只能裸写 input[type=file],样式脱节、无拖拽、无已选清单,也没有统一的 onChange 出口",
"acceptance": "upload({ accept, multiple, hint, disabled, onChange }) 支持点击/拖拽/键盘选择、清单逐个移除、同名同大小去重、disabled 全禁;组件数 41→42 三处计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;不内置上传请求,onChange 回传副本",
"domain": "new"
},
{
"id": "g34",
"title": "ua info 支持组件 id 查询",
"motivation": "ua info 只认包名,Agent 按组件名查(如 ua info list / MCP get_component(list))直接 not found,只能翻整包清单;输出还是原始 JSON,抄参数得自己解析",
"acceptance": "ua info <组件id> 输出 props 表 + 步骤 + 示例,ua info <包名> 出摘要且 --json 保留原始清单;MCP get_component 支持组件 id(返回 package + component);npm run check 全绿",
"status": "done",
"notes": "自补:Agent 查询体验;--json 保留原始清单",
"domain": "tune"
},
{
"id": "g35",
"title": "新增 notification 通知卡片",
"motivation": "toast 是右上角轻量瞬时提示,装不下「标题 + 描述 + 操作按钮 + 需读完」的通知场景,业务侧只能自己写浮层",
"acceptance": "notification({ title, description, variant, duration, closable, action }) 右下角堆叠(与 toast 分区)、超出 3 条挤掉最早、返回 { el, close }、duration 0 常驻;组件数 42→43 三处计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;与 toast 分区避免视觉冲突",
"domain": "new"
},
{
"id": "g36",
"title": "新增 result 结果页组件",
"motivation": "提交成功/校验失败/权限不足这类终态页每次手写图标+标题+按钮,语义色和留白都不统一",
"acceptance": "result({ status, title, description, extra }) 四档语义色 + 居中终态布局 + role=\"status\";组件数 43→44 三处计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充",
"domain": "new"
},
{
"id": "g37",
"title": "llms-full 增加组合配方章节",
"motivation": "清单只能描述单组件参数,Agent 拼多组件(表单+校验+提交反馈、表格+分页+批量、危险操作确认)时没有权威范例,容易各写一套",
"acceptance": "packages/tools/cli/recipes.md 作为唯一来源,ua docs 时追加到 llms-full.txt 末尾;缺少文件时不报错;npm run check 全绿",
"status": "done",
"notes": "自补:Agent 文档;配方文件缺失时静默跳过",
"domain": "tooling"
},
{
"id": "g38",
"title": "select 支持 searchable 搜索",
"motivation": "选项超过十个时只能靠键入首字母快跳,看不到筛掉的候选;业务常自己再包一层搜索框,交互割裂",
"acceptance": "select({ searchable: true }) 弹层顶部带搜索框,打开即聚焦、实时包含匹配、无命中显示「无匹配选项」,↑↓/Home/End 只在可见项间移动;README 分组行补齐新组件;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件可用性细节",
"domain": "tune"
},
{
"id": "g39",
"title": "统一键盘焦点可见性 + 可访问性约定入文档",
"motivation": "多数组件为自绘样式写了 outline:none,只有 14 处补了 focus-visible 光环,漏掉的组件键盘 Tab 过去完全看不出焦点在哪",
"acceptance": "ui.css 顶部统一 [class^=ua-]:focus-visible 描边兜底(不引入硬编码色);README 增「可访问性约定」章节;npm run check 全绿",
"status": "done",
"notes": "自补:a11y;避免组件各自 outline:none 丢失焦点指示",
"domain": "tooling"
},
{
"id": "g40",
"title": "修表单双提交:button 显式 type + Enter 提交",
"motivation": "button() 不写 type,放进 <form> 里默认是 submit:点提交会先跑 click 回调再触发原生 submit(onSubmit 触发两次),点「重置」还会顺带提交表单",
"acceptance": "ButtonProps 增 type(缺省 button);form 内提交按钮用 submit、重置用 button;单行输入框 Enter 等同提交且 textarea 不受影响;文档同步;npm run check 全绿",
"status": "done",
"notes": "自补:缺陷修复;textarea 保留换行语义",
"domain": "tune"
},
{
"id": "g41",
"title": "verify 校验 README 分组行",
"motivation": "README「分组:」行是第二份组件清单,新增组件只改 ua:components 区块时分组行会静默过期(本轮之前已漏 6 个组件)",
"acceptance": "verify 解析分组行并比对清单集合,漏写/多写均报错(formItem 作为别名放行);负向删除 tree 即失败;npm run check 全绿",
"status": "done",
"notes": "自补:门禁改进;把手工维护的第二份清单纳入自动校验",
"domain": "tooling"
},
{
"id": "g42",
"title": "pagination 支持每页条数切换",
"motivation": "分页器只能固定 pageSize,列表页要改条数得业务另做下拉并自算总页数;且每次翻页都在重建跳页输入框",
"acceptance": "pagination({ pageSizes, onPageSizeChange }) 渲染条数切换器、切换后回第 1 页并按新条数重算总页数;跳页区只创建一次;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;新增 sec-pagination 演示区块",
"domain": "tune"
},
{
"id": "g43",
"title": "select/menu/popover/image 的 Esc 接入统一弹层栈",
"motivation": "四个弹层组件各自监听 document 的 Esc:多层叠加(如 popover 内再开 image 预览)时一次 Esc 全部关闭,与 modal/drawer 的 pushOverlay 栈行为不一致",
"acceptance": "四个组件展开期入 Esc 栈、收起即释放;多层叠加时 Esc 后进先出逐层关闭;焦点归还触发器;npm run check 全绿",
"status": "done",
"notes": "自补:一致性修复;复用 overlay.ts 的 pushOverlay 栈",
"domain": "tune"
},
{
"id": "g44",
"title": "accordion 标题行方向键导航",
"motivation": "标题是原生 button 只有 Enter/Space,行间移动要靠 Tab 逐个跳,与 tabs/tree 的 ↑↓/Home/End 键盘约定不一致",
"acceptance": "↑↓ 在标题行间环形移动、Home/End 首尾直达,均跳过禁用行;焦点不在标题行不拦截;npm run check 全绿",
"status": "done",
"notes": "自补:可达性对齐;复用环形回绕取模模式",
"domain": "tune"
},
{
"id": "g45",
"title": "modal/drawer/image 打开时锁定背景滚动",
"motivation": "模态弹层开着时背景页面仍可滚动,长页面里滚动会带着遮罩底下内容跑;非模态弹层(select/menu/popover/tooltip)不受影响",
"acceptance": "overlay.ts 新增 lockScroll 引用计数:嵌套叠加只由最后一层还原 body;modal/drawer/image 预览接入,关闭即解锁;npm run check 全绿",
"status": "done",
"notes": "自补:缺陷修复;引用计数避免多层同开时提前解锁",
"domain": "tune"
},
{
"id": "g46",
"title": "新增 transfer 穿梭框组件",
"motivation": "组件库缺常规的双栏搬运组件:批量选择/授权/字段启用这类场景,调用方目前只能用两个 list + 手写搬运逻辑,勾选态与全选三态都要重复实现",
"acceptance": "新增 transfer(两栏由 dataSource 按 targetKeys 派生、逐栏搜索、全选含半选、禁用条目不参与、信号双向绑定);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;归属只存 key,搬运只改 targetKeys,不删条目",
"domain": "new"
},
{
"id": "g47",
"title": "新增 cascader 级联选择组件",
"motivation": "省市区、组织层级这类逐级下钻的选择场景目前只能靠多级 select 拼接,调用方得自己维护中间态与列联动",
"acceptance": "新增 cascader(多列联动、悬停下钻、点击提交、changeOnSelect、全键盘、Esc 入栈);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;浏览路径与已提交路径分离,避免悬停污染受控值",
"domain": "new"
},
{
"id": "g48",
"title": "新增 splitter 可拖拽分栏容器",
"motivation": "仪表盘/IDE 式布局需要可调整比例的分栏,库内没有任何布局基础件,调用方只能自己写拖拽与边界夹逼",
"acceptance": "新增 splitter(指针拖拽、键盘微调、双击复位、min 夹逼、sizes 信号受控、横纵两向);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:布局基础件;两栏之和恒定,只改相邻一对",
"domain": "new"
},
{
"id": "g49",
"title": "新增 autoComplete 自动补全组件",
"motivation": "搜索式输入(组件名、城市、标签)目前只能用 select 的 searchable 近似,无法做到「边打边联想 + Enter 取首项」的输入法式体验",
"acceptance": "新增 autoComplete(输入即过滤、↑↓ 环形高亮、Enter 选中、Esc 入栈、options 函数式远程联想、空态提示);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;mousedown+preventDefault 保住输入框焦点",
"domain": "new"
},
{
"id": "g50",
"title": "新增 datePicker 日期选择组件",
"motivation": "表单体系里日期只能填文本,缺一个受控的单月网格选择器;min/max 约束与键盘逐日导航是排期/账期场景的刚需",
"acceptance": "新增 datePicker(单月网格、翻月、今天、min/max 闭区间、←→↑↓/PageUp/PageDown/Enter、Esc 入栈、value 信号);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:表单组件补缺;跨月自动翻页,漫游 tabindex 只有焦点日可 Tab 进入",
"domain": "new"
},
{
"id": "g51",
"title": "新增 backTop 返回顶部按钮",
"motivation": "长文档/长表格页面缺少回顶入口,用户只能拖滚动条;进度环还能给出「还剩多少没看」的定位感",
"acceptance": "新增 backTop(阈值浮现、滚动进度环、平滑回顶且尊重 reduced-motion、支持内滚容器 target);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;进度环用 conic-gradient 内联写角度,不引入新令牌",
"domain": "new"
},
{
"id": "g52",
"title": "新增 colorPicker 颜色选择组件",
"motivation": "主题定制、标签配色等场景只能填十六进制文本框;缺省色板应直接消费 --ua-* 令牌,才能天然支持暗色切换",
"acceptance": "新增 colorPicker(预设色板 + 原生取色器 + 手输 CSS 颜色串、值信号、Esc 入栈、触发器实时预览);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;预设缺省为令牌色而非硬编码 hex",
"domain": "new"
},
{
"id": "g53",
"title": "新增 tour 引导漫游组件",
"motivation": "新用户首次进入复杂页面缺少分步指引;库内已有 overlay/position 能力,聚光挖洞式引导是最自然的组合产物",
"acceptance": "新增 tour(返回 {el,start,close} 句柄、按选择器聚光、卡片自动选方位、滚动重算、末步完成/跳过区分回调、Esc 入栈、焦点归还);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;聚光用超大 box-shadow 挖洞,省掉独立遮罩层",
"domain": "new"
},
{
"id": "g54",
"title": "新增 watermark 水印组件",
"motivation": "内部系统的防截图外传诉求没有落地件;水印颜色必须能跟随暗色主题,所以要从 --ua-* 令牌解析出实色再喂给 canvas",
"acceptance": "新增 watermark(props, ...children)(canvas 平铺单元、令牌色解析、多行/旋转/间距可配、覆盖层 pointer-events:none);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;canvas 不认 CSS 变量,先 getComputedStyle 解析",
"domain": "new"
},
{
"id": "g55",
"title": "新增 checkboxGroup 多选组组件",
"motivation": "权限勾选、标签多选这类场景只有单个 checkbox,调用方要自己拼数组、自己算全选与半选、自己处理「最多选 N 项」",
"acceptance": "新增 checkboxGroup(数组信号、min/max 禁用边界、checkAll 半选、纵横向、整组禁用);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:表单组件补缺;边界用禁用表达,不抛错不静默回滚",
"domain": "new"
},
{
"id": "g56",
"title": "ua style 增加单文件 200 行门禁",
"motivation": "CODING.md 写明「单文件尽量 < 200 行」,但一直是人工约定;随着组件数涨到 54,靠人盯不住,应把这条规范变成可执行门禁",
"acceptance": "cmdStyle 增加 sizeCheckFile:只约束分层包 src 内实现文件、types.ts 与 index.ts 豁免;正向全绿、负向(造 210 行文件)退出码 1;AGENTS/CODING/README 同步该门禁说明;npm run check 全绿",
"status": "done",
"notes": "自补:工具链改进;只约束包内实现文件,cli 自身与 demo 单文件不受限",
"domain": "tooling"
},
{
"id": "g57",
"title": "form 校验支持异步规则(validator 返回 Promise)",
"motivation": "邀请码占用、用户名唯一性这类校验必须打后端;原来 validator 只接受同步返回值,调用方只能绕过 form 自己写提交拦截",
"acceptance": "FormRule.validator 允许返回 Promise;formItem 返回 validateAsync 并按轮次丢弃过期结论;form 提交逐项 await、期间禁用提交按钮、有错聚焦首错;同步 validate 行为不变;demo 增异步字段;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件增强;同步路径不被 Promise 拖慢,异步路径防乱序覆盖",
"domain": "tooling"
},
{
"id": "g58",
"title": "新增 affix 固钉组件",
"motivation": "文档站目录、工具栏、筛选条都需要「滚过就贴顶」;直接用 position:sticky 常受父级 overflow 影响,需要占位 + fixed 的可靠实现",
"acceptance": "新增 affix(props, ...children)(阈值固定、外层等高占位不塌陷、宽度横向随容器自适应、支持内滚容器 target);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;固定态只在状态切换时重写样式,滚动中仅刷新宽度",
"domain": "new"
},
{
"id": "g59",
"title": "verify 检测 README 组件清单重复项",
"motivation": "README 区块与分组行原来用 Set 去重后比对,同一组件写两遍也能通过;重复项会让「组件数」与分组导航对不上,属于纯文档漂移",
"acceptance": "checkReadme 保留原始顺序并检测重复:区块重复、分组行重复(整词计数,避开 formItem 命中 form)都报错;正负向验证通过;npm run check 全绿",
"status": "done",
"notes": "自补:工具链改进;分组行按整词边界计数,formItem 不被 form 误伤",
"domain": "tune"
},
{
"id": "g60",
"title": "新增 multiSelect 多选下拉组件",
"motivation": "select 只支持单选,标签多选场景只能退回 checkboxGroup(占空间)或自拼 popover;需要一个「标签式回显 + 可搜索 + 连续多选」的标准控件",
"acceptance": "新增 multiSelect(标签回显与删除、搜索过滤、点选不收起、max 禁用边界、↑↓/Enter/Esc、受控信号);manifest/docs/demo/README/计数同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充;输入框常驻不重建,避免重绘丢焦点",
"domain": "new"
},
{
"id": "g61",
"title": "tree 支持勾选多选与父子级联",
"motivation": "权限树、菜单授权这类场景必须多选;原来只有单选 selected,调用方只能自己拿 DOM 拼一套勾选逻辑",
"acceptance": "TreeProps 增加 multi/checked/onCheck;勾父选子、子全选回勾父、部分勾选半选、禁用项不参与级联;勾选不触发选中,多选下 Enter/空格切换勾选;manifest/docs/demo 同步;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件增强;key→节点/父节点索引在建树时一次建立",
"domain": "tune"
},
{
"id": "g62",
"title": "verify 校验 demo 实际调用每个组件",
"motivation": "原来只校验 section 锚点存在,理论上可以保留锚点却删掉组件调用——文档声称「有演示」而实际没渲染",
"acceptance": "verify 对每个 docs.components 条目要求 apps/demo/main.ts 出现 id( 或 id.(兼容 toast.info 这类命名空间调用);正负向验证通过;AGENTS 同步;npm run check 全绿",
"status": "done",
"notes": "自补:门禁改进;当前 56 个组件全部通过该规则",
"domain": "tooling"
},
{
"id": "g63",
"title": "card 支持标题操作区、底部操作区与悬停态",
"motivation": "card 只有 title + children,业务里最常要的「标题右侧操作 / 底部主次按钮 / 可点击卡片」都得在外部再包一层",
"acceptance": "CardProps 增加 extra / footer / bordered / hoverable;标题行改 flex 头、标题下边距移到头部(视觉不变);demo/manifest 同步;npm run check 全绿",
"status": "done",
"notes": "自补:既有组件增强;不改内部结构,只在有 title/extra 时才输出头部",
"domain": "tune"
},
{
"id": "g64",
"title": "刷新 CHANGELOG 并说明刷新时机",
"motivation": "CHANGELOG 停在 g14,之后 40+ 条迭代都没进日志;同时没人说明它为何不在 check 链里(它包含提交自身,天然自指)",
"acceptance": "npm run changelog 重新生成到最新;AGENTS 增加「变更日志」段说明生成方式与「不进 check」的原因;README 脚本清单补 changelog;npm run check 全绿",
"status": "done",
"notes": "自补:文档完善;changelog:check 只能用于发版前确认漂移,不能作为提交门禁",
"domain": "tune"
},
{
"id": "g65",
"title": "verify 校验文档 props 落到实现,并补齐漏记字段",
"motivation": "文档 props 表与实现参数是两处手写,改了参数名忘改文档时 check 全绿但 Agent 读到的是幽灵字段;反向也有漏记(image.fallback、textField.clearable、textarea.maxCount、pagination.jumpable 都没进文档)",
"acceptance": "ua verify 新增「每个文档 props 名必须在组件实现源码里出现」校验(按同名文件优先、export function 兜底定位实现);补齐四处漏记字段;负向造幽灵字段能命中报错;npm run check 全绿",
"status": "done",
"notes": "自补:门禁增强;说明性条目(...children、带括号注记)按非标识符跳过,避免误报",
"domain": "tooling"
},
{
"id": "g66",
"title": "verify 反向校验入参接口字段均已登记文档",
"motivation": "上一轮只挡了「文档有、实现无」的幽灵字段;反方向(types.ts 加了字段但文档没写)仍会漏,Agent 读文档就会少一个可用参数",
"acceptance": "ua verify 解析 src/types.ts 的 <组件>Props 字段并与文档 props 表双向比对;清单支持 propsType 覆盖接口名、false 表示无入参;补齐 form.onReset 漏记;负向注入 ghostField 能命中报错;npm run check 全绿",
"status": "done",
"notes": "自补:门禁增强;接口名默认取 PascalCase(组件 id)+Props,switchButton/dataTable 用 propsType 覆盖",
"domain": "tooling"
},
{
"id": "g67",
"title": "tag 支持 checkable 筛选片",
"motivation": "筛选场景只能用 closable 标签表达「已选」,表达不了「未选可点」;且删掉再重建节点会丢焦点",
"acceptance": "TagProps 增加 checkable/checked/onChange;渲染为 button + aria-pressed,effect 同步 class 不整块重渲染;选中态低视觉比重(accent 10% 底 + 55% 边框),hover 上浮、按压回缩、focus-visible 光环并带 reduced-motion 兜底;文档与演示补齐;npm run check 全绿",
"status": "done",
"notes": "自补:组件增强;checkable 与 closable 互斥(同时传以 checkable 为准),checked 不传则内部自持",
"domain": "tune"
},
{
"id": "g68",
"title": "alert 增加语义图标与通栏形态",
"motivation": "提示条只有文字,语义强度全靠颜色(色弱不可辨);顶部通知也做不出通栏效果,得靠调用方套壳改样式",
"acceptance": "AlertProps 增加 banner/showIcon/icon;图标纯装饰 aria-hidden 不干扰读屏播报;is-banner 去圆角、只留下边框、通栏 padding;文档 props 与手把手补齐;npm run check 全绿",
"status": "done",
"notes": "自补:组件增强;图标用字符而非外部图标集,避免引入依赖,颜色走 currentColor 跟随语义档",
"domain": "tune"
},
{
"id": "g69",
"title": "修正单击排序的升降序循环与 getPageInfo 陈旧快照",
"motivation": "补冒烟时发现两处真 bug:非 multi 的 toggleSort 每轮先清空规则,导致同一列永远停在 asc(点了变不回去);getPageInfo 只在 derive 里赋值,没先取可见行时返回陈旧/默认快照",
"acceptance": "toggleSort 保留本列既有方向再循环 asc → desc → 取消(multi 保留全部列);getPageInfo 内部先跑一次带 memo 的派生;冒烟补三条不变量(快照即时性、selection 跨筛选保留、三次点击回无序);npm run check 全绿",
"status": "done",
"notes": "自补:核心正确性;state.pagination.page 保留调用方意图、仅视图钳制,这条既有语义未改",
"domain": "tune"
},
{
"id": "g70",
"title": "冒烟补多列排序、页大小与 reset 重置断言",
"motivation": "冒烟只覆盖单列排序与越界钳制;multi 排序的规则优先级、setPageSize 回第 1 页、reset 回默认态都没有回归网,改动 patchState 时容易静默破功",
"acceptance": "smoke 新增三条不变量:multi 双列规则生成且按规则序生效(city 升序 + 同城 age 升序)、setPageSize 回第 1 页、reset 清空排序筛选并把分页回 1/20;npm run check 全绿",
"status": "done",
"notes": "自补:回归网;断言数 9 → 16",
"domain": "tune"
},
{
"id": "g71",
"title": "数据表表头补 aria-sort 三态播报",
"motivation": "排序态只有 ↑ / ↓ / ↕ 视觉箭头,读屏用户听不出当前按哪列排、什么方向;这是可排序表最基本的一条 a11y 约束",
"acceptance": "可排序 th 用 effect 同步 aria-sort(ascending / descending / none),与箭头同源;文档手把手补一条;npm run check 全绿",
"status": "done",
"notes": "自补:无障碍;非 sortable 列不写 aria-sort,避免读屏误报",
"domain": "tune"
},
{
"id": "g72",
"title": "文档站搜索框与结果区补无障碍语义",
"motivation": "生成的文档站搜索框没有可访问名(读屏只报「编辑框」),搜索后也没有结果数播报,键盘/读屏用户拿不到反馈",
"acceptance": "input 改 type=search + aria-label;nav 加 aria-label;新增 sr-only 的 role=status 区域,render 时播报「匹配 N 个包」与「无匹配结果」;npm run check 全绿",
"status": "done",
"notes": "自补:无障碍(文档站产物本身)",
"domain": "tooling"
},
{
"id": "g73",
"title": "表格分页区补导航语义与页码播报",
"motivation": "底栏只是一排按钮,读屏不知道那是分页;页码/行数变化也没有播报,筛选后用户不知道结果变了",
"acceptance": "分页容器加 role=navigation + aria-label;页码行数文本改 role=status aria-live=polite;npm run check 全绿",
"status": "done",
"notes": "自补:无障碍;live region 只在文本变化时播报,翻页/筛选都会触发",
"domain": "tune"
},
{
"id": "g74",
"title": "弹窗与抽屉补 aria-labelledby 标题关联",
"motivation": "role=dialog 但没挂可访问名,读屏只会念「对话框」,用户不知道是哪个弹窗;标题元素本身也没有 id,无法被引用",
"acceptance": "modal/drawer 用模块级序号生成唯一标题 id 并挂 aria-labelledby,多实例不撞号;npm run check 全绿",
"status": "done",
"notes": "自补:无障碍;drawer 的 aria-hidden 与 open 同步逻辑未动",
"domain": "tune"
},
{
"id": "g75",
"title": "评分组件键盘可达性与焦点保持",
"motivation": "role=slider 却不可聚焦(div 无 tabindex),keydown 只挂在首帧节点上且整体重渲染会替换节点——方向键实际按一下就失效,读屏也听不到键盘操作",
"acceptance": "整组 tabindex=0 可聚焦,keydown 每轮渲染重挂,重渲染后焦点归还并继续连按;补 focus-visible 光环;npm run check 全绿",
"status": "done",
"notes": "自补:无障碍;重渲染策略保持不变(星标少,开销可忽略)",
"domain": "tune"
},
{
"id": "g76",
"title": "文字头像补 role=img 使名称可播报",
"motivation": "无 src 的文字头像只是带 aria-label 的 span,普通元素上的 aria-label 不保证被读屏当名称读出来,等于没有可访问名",
"acceptance": "文字头像容器补 role=img,与有 src 时的 img alt 语义对齐;npm run check 全绿",
"status": "done",
"notes": "自补:无障碍;title 仍保留供鼠标悬浮提示",
"domain": "tune"
},
{
"id": "g77",
"title": "刷新 CHANGELOG 至最新提交",
"motivation": "g64 之后又落了 12 次提交,changelog:check 已经报漂移;CHANGELOG 停在旧版本等于给读者过期信息",
"acceptance": "npm run changelog 重新生成(226 提交),changelog:check 不再报漂移;npm run check 全绿",
"status": "done",
"notes": "自补:发版前刷新步骤,按 g64 约定的方式执行",
"domain": "tune"
},
{
"id": "g78",
"title": "新增 space 间距容器组件",
"motivation": "库里 56 个组件都在各自实现里写 margin/gap,「一排按钮统一间距」「工具栏两端撑开」这类布局诉求没有承载物,演示页与业务方只能重复拼 style",
"acceptance": "新增 space(props, ...children):direction / size(sm|md|lg 走令牌,数字按像素)/ align / justify / wrap / block;manifest 参数表 + 手把手步骤 + api.exports 登记;demo 有 sec-space 区块真实调用;README 计数与分组同步;npm run check 全绿",
"status": "done",
"notes": "自补:布局基础件缺失;间距一律走 --ua-space-* 令牌,几何用内联 style 不进皮肤样式表",
"domain": "new"
},
{
"id": "g79",
"title": "数据表可排序表头补键盘可达与列作用域",
"motivation": "表头只绑了 onClick,键盘用户聚焦不到、也按不出来——README 声称「关键交互都有键盘等价」,表格排序是唯一漏网的;th 还缺 scope=col,读屏无法把列标题与数据格关联",
"acceptance": "可排序 th 进 Tab 序(tabindex=0,不可排序的不抢焦点),Enter/空格 触发与点击相同的 toggleSort(空格不滚页),补 scope=col;悬停/聚焦视觉沿用既有 ua-sortable 与全局 focus-visible;dataTable 仍 ≤ 200 行;npm run check 全绿",
"notes": "自补:无障碍;排序入口收敛为单个 toggle 闭包,鼠标与键盘同源,不改核心签名",
"status": "done",
"domain": "tune"
},
{
"id": "g80",
"title": "通知悬停/聚焦暂停自动关闭计时",
"motivation": "duration 一设定就是定时器到点即退场,鼠标悬停正在读的卡片照样消失(WCAG 2.2.1「计时可调」);通知恰恰是长篇描述,读到一半被收走比不自动关更烦",
"acceptance": "pointerenter / focusin 暂停并扣减已流逝时间,pointerleave / focusout 按剩余毫秒续计;duration 为 0 时行为不变;关闭仍先清计时器;文档与演示描述同步;npm run check 全绿",
"notes": "自补:无障碍;不改签名,remaining 时间在组件内折算",
"status": "done",
"domain": "tune"
},
{
"id": "g81",
"title": "树形组件改用 roving tabindex",
"motivation": "每行都写死 tabindex=0,键盘用户从树里穿过去要按 N 次 Tab(20 个节点就是 20 次),违反 WAI-ARIA 树模式「整棵树一个 Tab 停点、行间用方向键」的要求",
"acceptance": "只有当前行 tabindex=0 其余 -1;焦点落到哪行停点就跟到哪行(focusin 同步),下次 Tab 直接回到该行;↑↓/Home/End 移动后停点随焦点迁移;初始停点为首个未禁用根节点;tree ≤ 200 行;npm run check 全绿",
"notes": "自补:无障碍;keyOf 抽出复用,去掉 keydown 里的内联查找",
"status": "done",
"domain": "tune"
},
{
"id": "g82",
"title": "文件选择补类型/体积/数量校验与拒收提示",
"motivation": "accept 只设到原生 input 上,拖拽进来的文件完全绕过校验;也没有体积与数量上限,超限只能靠调用方在 onChange 里事后过滤,用户看不到为什么文件没进清单(静默丢弃)",
"acceptance": "新增 maxSize(字节)/ maxCount(多选上限)两个可选参数;accept 支持 .ext / type/subtype / type/* 且对拖拽同样生效;被拒文件不进清单并在 role=status 提示行写明原因(类型不符 / 超上限 / 数量已满);不改onChange 语义;文档与演示同步;npm run check 全绿",
"notes": "自补:可用性;校验入口收敛到 add(),点选与拖拽共用同一条路径",
"status": "done",
"domain": "tooling"
},
{
"id": "g83",
"title": "开关改用 switch 语义并补可访问名兜底",
"motivation": "开关底层是 input type=checkbox,读屏一律播报成「复选框」,与视觉上的开关语义不符;不留 label 时更是完全没有可访问名(只有一个匿名复选框)",
"acceptance": "input 补 role=switch(选中态沿用原生 checked 映射);新增可选 ariaLabel,缺省取 label,纯图标开关也有可访问名;文档 props 与演示(无文案开关实例)同步;npm run check 全绿",
"notes": "自补:无障碍;沿用原生 checkbox 的 Space 切换行为,不重造键盘交互",
"status": "done",
"domain": "tune"
},
{
"id": "g84",
"title": "数据表定宽列支持拖拽调宽",
"motivation": "列宽只能在定义时写死,长内容把列撑开就再也调不回去;表格库缺这个基本操作,用户只能改代码重发版",
"acceptance": "width 为数字的列在表头右缘出现手柄:拖动实时改对应 <col> 宽度(下限 56px,拖出表头仍跟手),聚焦手柄后 ← → 每次 ±8px,双击复位到声明宽度;拖拽/双击不触发排序;拖拽期间全局禁选文本;样式走令牌且有 reduced-motion 兜底;dataTable ≤ 200 行;npm run check 全绿",
"notes": "自补:可用性;拖拽与键盘共用 apply 单一写宽入口,逻辑拆到 columnResize.ts 免撑大 dataTable",
"status": "done",
"domain": "tune"
},
{
"id": "g85",
"title": "进度条补可访问名与语义色",
"motivation": "定量态只有 aria-valuenow,没有名称——读屏只念「50%」,不知道是什么在 50%;且除 accent/success 外没有失败告警色,同步失败也只能显示成一片蓝",
"acceptance": "新增 label(可访问名,不确定态缺省「进行中」兜底)与 variant(default/success/warning/danger);语义色规则置于 .ua-done 之后同特异性胜出,满格不回退 success;样式只消费 --ua-* 令牌;文档 props/steps 与演示同步;npm run check 全绿",
"notes": "自补:无障碍 + 视觉语义;CSS 层用特异性解决 variant 与满格 success 的冲突,不加 JS 分支",
"status": "done",
"domain": "tune"
},
{
"id": "g86",
"title": "弹层外部点击监听改为按需挂载",
"motivation": "8 个弹层(select / multiSelect / cascader / datePicker / colorPicker / autoComplete / menu / popover)在构造时就在 document 上挂一个永不移除的 click 监听:组件从页面移除后监听仍常驻(SPA 里实例越多越积压),且各写一份重复逻辑;popover 里还残留一个空的 keydown 监听",
"acceptance": "overlay 新增 bindOutside(root, isOpen, close):仅展开期间挂全局 click、收起即摘,内部点击直接返回;8 个弹层全部改用它并删掉各自的重复实现;popover 删掉空 keydown,点弹层内部不再误关;npm run check 全绿",
"notes": "自补:稳健性 + 去重;展开动作本身也是 click,靠 root.contains 卫语句避免刚开就被关",
"status": "done",
"domain": "tune"
},
{
"id": "g87",
"title": "轮播补焦点暂停与轮播语义",
"motivation": "自动播放只在 pointerenter 暂停——键盘用户没有 hover,Tab 进轮播后幻灯片照样自己换页,正在读的内容被抽走(WCAG 2.2.2);区域与幻灯片也只有笼统的 aria-label,读屏不知道这是轮播的第几张",
"acceptance": "焦点进入(focusin)与移出(focusout)同样暂停自动播放,两者都不在才前进;区域补 aria-roledescription=轮播,每张补 aria-roledescription=幻灯片 且标签为「第 x 张,共 n 张」;演示描述同步;npm run check 全绿",
"notes": "自补:无障碍;hover 与 focus 分开计数,避免一个 pointerleave 就误解除暂停",
"status": "done",
"domain": "tune"
},
{
"id": "g88",
"title": "数据表支持导出 CSV",
"motivation": "表格能筛能排却导不出来,用户只能截图或手抄;导出还必须导「筛选+排序后的全量行」而不是当前页,但内核只有 getVisibleRows()(已分页裁剪),UI 层拿不到全量派生行",
"acceptance": "内核 getVisibleRows(paged=false) 跳过 pagination 阶段并新增 getAllRows(),派生上下文带 paged 标记(缺省分页),分页快照不受影响;dataTable 新增 exportable / exportName,工具栏按钮导出筛选+排序后的全量行、单元格走 format;CSV 带 BOM + CRLF、逗号引号换行正确转义;冒烟补 4 条不变量;npm run check 全绿",
"notes": "自补:功能缺口;核心只加可选参数与一个实例方法,不动既有签名",
"status": "done",
"domain": "tune"
},
{
"id": "g89",
"title": "演示产物启用保守压缩",
"motivation": "demo.html 单文件 278 KB,其中 JS 近 190 KB 全是未压缩的空白与冗余括号;演示页是给别人点开看的产物,体积直接影响加载体验,而全量 minify 会把标识符改短、不利于现场排查",
"acceptance": "esbuild 开启 minifyWhitespace + minifySyntax(保留标识符),产物仍可读;demo.html 由 278 KB 降到 205 KB(JS 190 KB → 120 KB);产物语法可被 node --check 通过,sec-space / ua-col-grip 等新增内容仍在;npm run check 全绿",
"notes": "自补:构建改进;刻意不用全量 minify,平衡体积与可读性",
"status": "done",
"domain": "tune"
},
{
"id": "g90",
"title": "刷新 CHANGELOG 至最新提交",
"motivation": "g89 之后又累积了十余次迭代提交,changelog:check 已报漂移;CHANGELOG 停在旧版本等于给读者过期信息",
"acceptance": "npm run changelog 重新生成(253 提交),changelog:check 不再报漂移(提交本条后按既有约定会再差一条,属正常);npm run check 全绿",
"notes": "自补:发版前刷新步骤,按 g64/g77 的既定方式执行",
"status": "done",
"domain": "tune"
},
{
"id": "g91",
"title": "表单字段补辅助说明 hint",
"motivation": "formItem 只有 label 与错误位,没有「格式提示」的位置:调用方只能把说明塞进 placeholder 或标签里,一报错提示就被替换得无处可看,用户不知道正确格式是什么",
"acceptance": "FormItemProps 新增可选 hint,渲染在控件下方;校验失败时 hint 让位(hidden)只留错误文案,校验通过自动恢复;样式走令牌;文档参数表(formItem 前缀)与演示同步;npm run check 全绿",
"notes": "自补:表单可用性;不改 formItem 返回句柄与 form 聚合逻辑",
"status": "done",
"domain": "tune"
},
{
"id": "g92",
"title": "徽标支持数字角标与纯红点",
"motivation": "badge 只能塞一段文字,做「未读 5 条」「99+」「有更新的小红点」时得自己拼 span + 手写样式,红色点还会硬编码颜色绕过令牌",
"acceptance": "BadgeProps 新增 count / max / dot,label 改为可选;count 超过 max(缺省 99)显示 N+,负数归零;dot 渲染 8px 圆点并以 role=img + label 作可访问名;新增样式只消费 --ua-* 令牌;文档与演示(三种形态)同步;npm run check 全绿",
"notes": "自补:组件能力补齐;保持纯展示、无内部状态",
"status": "done",
"domain": "tune"
},
{
"id": "g93",
"title": "树形组件支持按标签搜索",
"motivation": "节点一多(如目录树、组织架构)就只能靠展开逐层找;tree 只有展开/收起与方向键,没有任何过滤入口,几十个节点等于不可用",
"acceptance": "TreeProps 新增 searchable:顶部搜索框按标签包含匹配(大小写不敏感),命中节点连同父链保留、命中枝临时强制展开,不命中整枝隐藏且不参与 ↑↓ 遍历(visibleRows 改判 [hidden]);搜索框放在 role=tree 之外避免语义混淆;文档与演示同步;tree ≤ 200 行;npm run check 全绿",
"notes": "自补:可用性;搜索只改视图显隐,不回写 expanded 信号,退出搜索即恢复用户原有展开态",
"status": "done",
"domain": "tune"
},
{
"id": "g94",
"title": "补充数据表联动导出配方",
"motivation": "本轮新增的受控 page 联动、列宽拖拽、CSV 导出三个能力只有单组件参数表,没有「怎么拼在一起」的指导;配方文件停在第 4 条,Agent 与新人拼表格联动时仍要自己试",
"acceptance": "recipes.md 新增「配方 5:数据表 + 独立分页器 + 导出」,覆盖同一 page 信号双绑、exportable 导全量行与当页的区别、拖拽只对数字列宽生效、rowKey 记账;npm run docs 后 llms-full.txt 出现该配方;npm run check 全绿",
"notes": "自补:文档完善;只写用法不碰实现,配方随 docs 注入 llms-full",
"status": "done",
"domain": "tooling"
},
{
"id": "g95",
"title": "统计数值支持倒计时模式",
"motivation": "活动剩余时间这类需求只能调用方自己 setInterval 改信号再塞进 statistic,还得自己管停表;组件本身只认一个数值信号,秒级刷新没有落脚点",
"acceptance": "StatisticProps 新增 countdown(目标时间戳毫秒),传了即每秒显示 DD 天 HH:MM:SS(不足一天省天数),value 转为可选;到点或组件被移除后自动 clearInterval,不留常驻定时器;倒计时不走滚动动画;文档与演示同步;npm run check 全绿",
"notes": "自补:功能补齐;停表条件放在 tick 内自检 isConnected,无需额外卸载钩子",
"status": "done",
"domain": "tune"
},
{
"id": "g96",
"title": "抽屉支持上下滑出方位",
"motivation": "drawer 的 placement 只有 left/right,移动端最常见的「底部弹层」只能拿 modal 或者自己写 fixed 容器凑,既丢了焦点陷阱与 Esc 栈,样式也不统一",
"acceptance": "placement 扩到 'left' | 'right' | 'top' | 'bottom';上下滑出时 width 语义变为高度、横向自动铺满(max-width 放开),入场位移走 translateY,四条方位共用同一套遮罩/焦点陷阱/Esc 栈;文档与演示(底部抽屉实例)同步;npm run check 全绿",
"notes": "自补:组件能力补齐;只加 CSS 分支与一个三元表达尺寸,逻辑零改动",
"status": "done",
"domain": "tune"
},
{
"id": "g97",
"title": "本轮收尾刷新变更日志",
"motivation": "g90 之后又落了 19 次迭代提交(space 组件、列宽拖拽、CSV 导出、树搜索、抽屉四向等),CHANGELOG 已明显落后",
"acceptance": "npm run changelog 重新生成(267 提交),changelog:check 一致(提交本条后按约定再差一条);npm run check 全绿",
"notes": "自补:发版前刷新步骤,收尾统一执行",
"status": "done",
"domain": "tune"
},
{
"id": "g98",
"title": "弹窗与抽屉补遮罩关闭开关",
"motivation": "modal/drawer 点遮罩必关,表单类弹窗填到一半误触就丢内容;调用方没有任何开关能保留 × 与 Esc 的同时禁掉遮罩关闭",
"acceptance": "ModalProps / DrawerProps 新增 closeOnOverlay(缺省 true);传 false 时点遮罩不关闭,× 与 Esc 行为不变;文档参数表与演示(防误关弹窗实例)同步;npm run check 全绿",
"notes": "自补:可用性;遮罩点击处理改成显式卫语句,语义比一长串 && 更清楚",
"status": "done",
"domain": "tune"
},
{
"id": "g99",
"title": "加载指示器补可定制播报文案",
"motivation": "spinner 的 aria-label 写死「加载中」,页面里同时有三处加载时读屏念三次一样的话,用户不知道在等什么;组件没有出口让调用方说明加载对象",
"acceptance": "SpinnerProps 新增可选 label,缺省仍是「加载中」(不传即旧行为);role=status 保持不变;文档参数与手把手步骤、演示(带 label 的实例)同步;npm run check 全绿",
"notes": "自补:无障碍;纯透传一个可选参数,行为向后兼容",
"status": "done",
"domain": "tune"
},
{
"id": "g100",
"title": "收尾刷新变更日志",
"motivation": "g97 之后又落了 6 次提交(抽屉四向、遮罩关闭开关、加载播报等),changelog:check 已报漂移",
"acceptance": "npm run changelog 重新生成(273 提交),changelog:check 一致;npm run check 全绿",
"notes": "自补:发版前刷新步骤;本条为本次唤起第 22 轮收尾",
"status": "done",
"domain": "tune"
},
{
"id": "g101",
"title": "新增锚点导航组件 anchor",
"motivation": "文档页、设置页、长表单都需要「侧边跳转 + 滚动高亮」,现在库里没有任何可复用件:demo 的侧栏目录 (buildToc) 是手写 IntersectionObserver + 类名切换,偏移量、hash 回写、禁用项、减少动效各写各的,调用方要复制一遍才用得上",
"acceptance": "新增 anchor(props)(packages/skin/ui/src/anchor.ts,< 200 行):items/href/title/disabled、offset 偏移量、container 内滚容器、onChange 回调;滚动按目标位置高亮并同步 aria-current,点击平滑定位 + replaceState 回写 hash,prefers-reduced-motion 下瞬移;types/manifest/index/ui.css/demo/README/AGENTS/ARCHITECTURE 同步,npm run check 全绿",
"notes": "自补:组件补充;样式全走令牌,选出态维持低视觉比重(accent 10% 底色 + 左侧 2px 指示条)",
"status": "done",
"domain": "new"
},
{
"id": "g102",
"title": "焦点陷阱下沉为共享助手,图片预览接上",
"motivation": "modal 与 drawer 各自复制了一份 25 行的 Tab 焦点陷阱(focusables + keydown 环形回绕),改一处漏一处;image 的全屏预览层是 role=dialog aria-modal,却完全没有焦点陷阱也没把焦点移进去,键盘用户 Tab 一下就穿到背后的页面",
"acceptance": "overlay.ts 新增 trapFocus(box, isOpen):Tab / Shift+Tab 首尾环形,有无可用焦点与焦点在容器外的两种情况都拦回;modal / drawer 删掉本地实现改为调用(行为不变);image 预览层补 tabindex=-1 + 打开即聚焦 + 全程锁焦点;npm run check 全绿",
"notes": "自补:一致性与无障碍;陷阱实现单一来源,后续新增模态层直接复用",
"status": "done",
"domain": "tune"
},
{
"id": "g103",
"title": "表格空状态区分「没数据」与「被筛掉」",
"motivation": "dataTable 不论有没有筛选词,0 行时一律显示「暂无匹配数据」:数据本身就空的时候提示「匹配」让人以为筛选还在生效,而筛选筛空的时候又不给任何出口,用户只能手动删掉输入框里的字才发现原来有数据",
"acceptance": "有筛选词时显示「没有符合「关键词」的行」并给「清除筛选」按钮(一次性清空关键字信号与内核筛选、回第 1 页);无筛选词时显示「暂无数据」且不出现按钮;关键字用受控信号持有,与筛选框同源;每行文件仍 ≤ 200 行;npm run check 全绿",
"notes": "自补:可用性;空状态行抽到 tableEmpty.ts,dataTable 顺手把 api 类型别名下沉到 types.ts 才没破 200 行",
"status": "done",
"domain": "tune"
},
{
"id": "g104",
"title": "留痕校验回查提交是否真实存在",
"motivation": "iterations.log 目前只校验字段格式:commit 随便写个哈希、或复制粘贴时抄错一位,门禁照样全绿。留痕是「这条迭代真的落过」的唯一凭证,凭证本身不可验证等于没校验",
"acceptance": "ua lint-iterations 额外做双向核对:每行 commit 必须能在 git 历史里按前缀命中,且该提交主题以 iter(<id>) 开头(兼容早期无 scope 的 iter: <id>);伪造 / 抄错的哈希报违规并退出码 1;非 git 环境(打包分发)自动跳过该维度;ITERATION.md 留痕章节写明该校验;npm run check 全绿",
"notes": "自补:门禁工具链;一次 git log 建索引,150 行留痕不额外起进程",
"status": "done",
"domain": "tooling"
},
{
"id": "g105",
"title": "标签页支持可关闭页签",
"motivation": "多开详情页 / 编辑器类界面需要「关掉这个页签」,tabs 只有 label + disabled,调用方得自己在页签头外面挂按钮,关闭按钮的 hover 显现、指示条宽度、键盘等价全得重写一遍",
"acceptance": "TabItem 新增 closable(缺省不可关),渲染 × 关闭按钮(aria-label=关闭 <label>),点击或页签聚焦后按 Delete 触发 onClose(value);指示条改为量外壳宽度,可关闭页签的高亮能盖住 ×;触摸设备(hover: none)常显 ×;表单化入参不变(closable 在项上),文档参数表/手把手/示例与演示(三张可关订单页签,关完显示空态)同步;npm run check 全绿",
"notes": "自补:组件可用性;关闭只回调不改数据,符合受控约定",
"status": "done",
"domain": "tune"
},
{
"id": "g106",
"title": "滚动监听改为卸载自清理",
"motivation": "affix / backTop / anchor / tour 四个组件都是「挂 window 或容器的 scroll+resize 监听,从不摘」:单页应用里每次进页面重建组件就多一根监听,旧闭包连同旧 DOM 一起被 window 拽住,切十次页面就十份空转的 sync 在滚动时跑",
"acceptance": "新增 watch.ts 的 watchViewport(root, host, handler):滚动 / 缩放时若 root 已脱离文档就一次性摘掉两个监听并停止回调,否则转交 handler 并首帧同步一次;四个组件改用该助手,行为与原来一致(首帧同步保留);npm run check 全绿",
"notes": "自补:一致性与泄漏治理;一处实现四处复用,后续新增长期监听组件照抄",
"status": "done",
"domain": "tune"
},
{
"id": "g107",
"title": "新增二次确认气泡 popconfirm",
"motivation": "删除 / 发布这类危险操作只有两条路:直接执行(误点就没了)或弹 modal(打断视线、还要再找一次按钮)。中间档位「就地确认」缺失,而 popover 的 open 状态是组件私有的,调用方连编程收起都做不到,没法自己拼",
"acceptance": "PopoverProps 新增受控 open 信号(不传行为不变);新增 popconfirm(props, trigger):title/description/confirmText/cancelText/variant/placement/onConfirm/onCancel/open,确定先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」;组件 58→59,manifest/docs/README/AGENTS/ARCHITECTURE/demo 同步;npm run check 全绿",
"notes": "自补:组件补充;复用 popover + button,不重复实现定位与 Esc 栈",
"status": "done",
"domain": "new"
},
{
"id": "g108",
"title": "表单提交失败给汇总条与回调",
"motivation": "提交失败时只把焦点扔到第一处错误:视觉上用户得自己数还有几处红框,读屏用户更是只听到某个输入框的 aria-invalid,完全不知道总共有几项要改;调用方也拿不到「这次失败了几项」做埋点或顶部提示",
"acceptance": "form 提交时统计不通字段数,顶部渲染 role=alert 的汇总条「有 N 项需要修改,已定位到第一处」(通过则隐藏);FormProps 新增 onError(count) 与 errorSummary(缺省 true,传 false 不插节点);文档参数表与演示说明同步;npm run check 全绿",
"notes": "自补:无障碍与可用性;汇总条与逐项红框同源,均由一次提交的校验结果推导",
"status": "done",
"domain": "tune"
},
{
"id": "g109",
"title": "提示气泡支持触摸屏点按",
"motivation": "tooltip 只挂 pointerenter / pointerleave:触摸屏上按下会触发 enter(延迟 150ms 出),抬手立刻触发 leave 收起 —— 等于气泡刚出来就被手指带走,用户根本没机会读;移动端等于该组件不可用",
"acceptance": "触摸指针(pointerType=touch)不走 hover 分支,改为按下即出场、抬手后延迟 1.2s 收起;鼠标与键盘路径行为不变;re-enter 时清掉上一轮的延迟收起计时;文档手把手与 api 说明补触摸语义;npm run check 全绿",
"notes": "自补:可用性;只在原文件加两个分支与一个计时器,未改签名",
"status": "done",
"domain": "tune"
},
{
"id": "g110",
"title": "下拉选择支持清空",
"motivation": "筛选项一旦选中就回不到「未选」:城市、负责人这类可选筛选的下拉没有清空出口,用户只能刷新页面或换一个值。textField 早有 clearable,select 一直没有对应能力",
"acceptance": "SelectProps 新增 clearable(缺省 false):有值时触发器右缘出现 ×,点它把值写成空串并回调 onChange('');收起态按 Delete / Backspace 等价清空;按钮不进 Tab 序、无值自动隐藏;文档参数表 / 手把手 / api 说明与演示(城市下拉开启清空)同步;npm run check 全绿",
"notes": "自补:可用性;复用既有 value 信号与 onChange,不新增状态",
"status": "done",
"domain": "tune"
},
{
"id": "g111",
"title": "面包屑支持超长路径折叠",
"motivation": "七层路径(首页/控制台/订单管理/华东区/2026 Q4/待发货/订单 #1024)在窄容器里会把末项当前页挤到换行甚至省略号之后,而当前页恰恰是最该看清的;breadcrumb 没有任何收窄手段",
"acceptance": "BreadcrumbProps 新增 maxItems:超出时渲染「首项 + … + 末 Math.max(2, maxItems-1) 项」,折叠按钮 aria-label 报出被折叠条数、点一下原地展开全部;展开后不再折叠,末项始终是当前页;文档参数表 / 手把手 / 演示(七层路径 maxItems=4)同步;npm run check 全绿",
"notes": "自补:可用性;渲染走 effect + replaceChildren,不重建整个 nav",
"status": "done",
"domain": "tune"
},
{
"id": "g112",
"title": "消息提示支持撤销类操作按钮",
"motivation": "「已删除 3 条记录」这种提示最常见的下一步就是撤销,现在只能干看着它 3 秒后消失,要撤销得自己去列表里找回来;toast 只有 closable 一个交互出口",
"acceptance": "ToastProps 新增 actionLabel 与 onAction:传了文案才渲染操作按钮(accent 色、hover 下划线、focus-visible 有环),点后先收起再回调;带操作按钮的 toast 接收指针事件(原来只有 closable 才接收),悬停暂停逻辑不变;文档参数表 / 手把手 / api 说明与演示(「带撤销」按钮,5s)同步;npm run check 全绿",
"notes": "自补:可用性;沿用现有 dismiss,不新增公开 API",
"status": "done",
"domain": "tune"
},
{
"id": "g113",
"title": "吸顶容器支持贴底固定",
"motivation": "底部操作条(保存 / 取消 / 合计金额)是长表单与详情页的刚需,affix 只会贴顶:调用方得自己监听滚动改 position,或者干脆让操作条跟着内容滚走,用户翻到表单底部才发现按钮在屏幕外",
"acceptance": "AffixProps 新增 position(top 缺省 / bottom):贴底时占位容器底边越过「视口底 - offset」才固定,内联边距写 bottom 并清空 top,取消固定时两边都还原;贴顶行为不变;文档参数表 / 手把手 / api 说明同步;npm run check 全绿",
"notes": "自补:组件可用性;只在 sync 里加一条判定与一个样式分支",
"status": "done",
"domain": "tune"
},
{
"id": "g114",
"title": "收尾刷新变更日志",
"motivation": "g100 之后又落了 26 次提交(锚点导航、焦点陷阱下沉、留痕真实性校验、可关闭页签、二次确认气泡、表单汇总条、下拉清空、面包屑折叠、toast 撤销、贴底固钉等),changelog:check 已报漂移",
"acceptance": "npm run changelog 重新生成(301 提交),changelog:check 与提交史一致;npm run check 全绿",
"notes": "自补:发版前刷新步骤;本条为本次唤起收尾轮",
"status": "done",
"domain": "tune"
},
{
"id": "g115",
"title": "新增月历组件 calendar",
"motivation": "排期表、值班表、日程页需要「整月网格一直看得见」,datePicker 只能弹层选一天且收起来就没了;调用方要自己算月初留白、当月天数、今天与选中的标记,还要补键盘漫游,一写就是一百多行重复逻辑",
"acceptance": "新增 calendar(props)(< 200 行):value/month 信号、marker 每日标记(文案同时进 aria-label)、onSelect / onMonthChange;月初留白与天数计算抽到 dateKit.ts,datePicker 改为共用同一份(消除两处日期工具);组件 59→60,types/index/css/manifest/docs/README/AGENTS/ARCHITECTURE/demo 同步;npm run check 全绿",
"notes": "自补:组件补充 + 派生语义单一来源(日期工具下沉 dateKit)",
"status": "done",
"domain": "new"
},
{
"id": "g116",
"title": "抽屉支持非模态模式",
"motivation": "「一边看列表一边改右侧详情」是后台最常见的交互,但 drawer 只有模态一档:打开就盖一层遮罩、锁住背景滚动、把焦点困在面板里,主视图彻底不可用;调用方只能拿 fixed 的 div 自己拼一个假抽屉",
"acceptance": "DrawerProps 新增 modal(缺省 true):false 时遮罩透明且 pointer-events: none、面板自身可交互并带阴影、不锁背景滚动、不启用 Tab 焦点陷阱、aria-modal=false、点外部不算关闭(× 与 Esc 仍可用);文档参数表 / 手把手与演示(非模态检查器)同步;npm run check 全绿",
"notes": "自补:组件可用性;沿用现有遮罩节点加一个 is-plain 分支,不新增 DOM 结构",
"status": "done",
"domain": "tune"
},
{
"id": "g117",
"title": "指示条与滑块跟随尺寸重算",
"motivation": "tabs 的滑动指示条与 segmented 的滑块都只在「激活项变化」和挂载后一帧各量一次:窗口缩放、侧栏展开收起、字体调大之后,指示条宽度就停在旧值上,明显错位;这类「布局变了但没重算」的问题用户只会觉得组件坏了一半",
"acceptance": "watch.ts 新增 trackSize(root, handler):用 ResizeObserver 观察根节点尺寸,变化即回调,节点脱离文档时自查并断开(无 ResizeObserver 的环境回落成 window resize);tabs / segmented 接入,指示条与滑块在容器变宽变窄后保持对齐;npm run check 全绿",
"notes": "自补:正确性;与 watchViewport 同一处收口,后续布局敏感组件照抄",
"status": "done",
"domain": "tune"
},
{
"id": "g118",
"title": "收尾再刷变更日志",
"motivation": "g114 之后又落了 4 次提交(新增 calendar 月历、抽屉非模态、trackSize 尺寸重算等),changelog:check 再次报漂移",
"acceptance": "npm run changelog 重新生成(309 提交),与提交史一致;npm run check 全绿",
"notes": "自补:发版前刷新步骤;本次唤起收尾轮",
"status": "done",
"domain": "tune"
},
{
"id": "g119",
"title": "空状态支持自定义图标",
"motivation": "empty 的图标位写死成 ⌀,业务方几乎都会换成自己的插图(搜索无结果用放大镜、空订单用纸箱);现在只能在外面再包一层插画 div,既影响居中节奏又和组件内置图标同屏",
"acceptance": "EmptyProps 新增 icon(unknown):字符串或图标 DOM 直接进图标位,缺省仍是 ⌀;图标位保持 aria-hidden,语义由 title / description 承担;文档参数表 / 手把手与演示(搜索无结果换成 🔍)同步;npm run check 全绿",
"notes": "自补:可用性;一个可选参数透传,行为向后兼容",
"status": "done",
"domain": "tune"
},
{
"id": "g120",
"title": "AGENTS.md 补新增组件落点清单",
"motivation": "本轮 20 次迭代里,每加一个组件都要现推一遍「要改哪些文件」:types / index / css / manifest 两处 / demo / README 三处 / AGENTS / ARCHITECTURE / docs,漏一处就靠 verify 报错来提醒,返工成本高;接手的 Agent 也没有一份照单打勾的清单",
"acceptance": "AGENTS.md「贡献新组件」章节补一张 10 行的落点表(每个落点写清具体动作与门禁原因),并说明改既有组件入参同样要走 manifest / docs 三步;npm run check 全绿",
"notes": "自补:Agent 文档完善;把本轮踩到的同步点固化成清单",
"status": "done",
"domain": "new"
},
{
"id": "g121",
"title": "textField 补 maxCount 字数统计",
"motivation": "textarea 早有 maxCount(原生上限 + 实时 n/limit),textField 却没有:昵称、标题、单行备注这类短字段同样需要限长,调用方只能自己拼计数节点贴在输入框外,样式与位置各写各的",
"acceptance": "TextFieldProps 新增 maxCount(缺省不渲染):传了即设原生 maxlength 拦截超限输入,输入框内侧附加组显示实时 n/上限(数字等宽不抖);与 clearable 共存时同组并排;文档参数表 / api 说明 / 手把手与 llms-full.txt 同步;npm run check 全绿",
"notes": "自补:输入类一致性;计数与清空共用后置附加组,零新增布局容器;首轮 verify 曾报 llms-full 漂移,重跑 npm run docs 后恢复(docs 需在 index 之后跑)",
"status": "done",
"domain": "tune"
},
{
"id": "g122",
"title": "评分组件支持 allowHalf 半星",
"motivation": "评分只有整数档:商品 4.5 星、服务 3.5 分这类半档表达不了,调用方只能取整或自己换别的组件;点星也只能选整颗,左半区白白浪费",
"acceptance": "RateProps 新增 allowHalf(缺省整数):点击星标左半区记 0.5 分、右半区整颗,键盘步进同为 0.5(值按 0.5 量化夹取),悬停位置实时预览半格填充(左半亮右半灭走渐变裁字),value 信号可写入 x.5;文档参数表 / api 说明 / 手把手与 llms-full.txt 同步;npm run check 全绿",
"notes": "自补:组件能力补齐;命中值与键盘步进共用 0.5 量化,不改信号语义",
"status": "done",
"domain": "tune"
},
{
"id": "g123",
"title": "收尾刷新变更日志",
"motivation": "g118 之后又落了 10 次提交(textField 字数统计、rate 半星等),changelog:check 已报漂移",
"acceptance": "npm run changelog 重新生成(319 提交),changelog:check 一致(提交本条后按约定再差一条,属正常);npm run check 全绿",
"notes": "自补:发版前刷新步骤;本次唤起收尾轮",
"status": "done",
"domain": "tune"
},
{
"id": "g124",
"title": "checkbox 补 indeterminate 半选态",
"motivation": "全选/子项联动只能表达「全勾」与「全不勾」:部分选中时全选框没有任何视觉区分,dataTable 内部的全选三态也是包私有实现,独立 checkbox 场景(树形勾选、批量列选择)用不上",
"acceptance": "CheckboxProps 新增 indeterminate(布尔或信号):同步到原生 input.indeterminate 属性,accent-color 自动渲染横杠,读屏播报 mixed;不改变 checked 值与点击语义(点一下即回全选);文档参数表 / api 说明 / 手把手与 llms-full.txt 同步;npm run check 全绿",
"notes": "自补:组件能力补齐;零新增 CSS(accent-color 原生覆盖 :indeterminate)",
"status": "done",
"domain": "tune"
},
{
"id": "g125",
"title": "开关补 loading 加载态",
"motivation": "开关切换常伴随后端写入(改配置、提交设置):请求在途时用户可以连续拨动,最后一次点击覆盖前面的状态,业务方只能自己在 onChange 里加标志位手动锁",
"acceptance": "SwitchProps 新增 loading(布尔或信号):加载中锁定交互(disabled 同值语义)、滑块换成转圈(reduced-motion 静态化为实心环)、input 挂 aria-busy;与 disabled 同时生效且不双写竞争;文档参数表 / api 说明 / 手把手与 llms-full.txt 同步;npm run check 全绿",
"notes": "自补:组件能力补齐;loading 接管时 disabled 不再走 h() 绑定,单一 effect 写入避免竞争",
"status": "done",
"domain": "tune"
},
{
"id": "g126",
"title": "数字输入补 precision 小数位",
"motivation": "金额、比例、权重这类小数场景:step 0.1 步进会累积出 0.30000000000000004 这样的浮点尾差直接写进信号,显示位数也无法约束,调用方只能自己在 onChange 里 toFixed 再写回",
"acceptance": "InputNumberProps 新增 precision(缺省不处理):写回与显示都按 toFixed(p) 归一(写回量化 + 输入框定长显示),步进浮点尾差消除;min/max 夹取语义不变;文档参数表 / api 说明 / 手把手与 llms-full.txt 同步;npm run check 全绿",
"notes": "自补:组件能力补齐;量化收敛在 emit/sync 单点,不改夹取与连发逻辑",
"status": "done",
"domain": "tune"
},
{
"id": "g127",
"title": "补充长表单编辑页组合配方",
"motivation": "affix 贴底、form 汇总条、hint、maxCount、saving 三处锁定这些能力都是本轮及此前几轮陆续落地的,但「长表单编辑页怎么拼」没有一篇端到端指导,Agent 与新人仍要自己试错组合",
"acceptance": "recipes.md 新增「配方 6:长表单编辑页(吸底操作条 + 提交锁定)」:affix position bottom 固定操作条、form 聚合校验与汇总条、同一 saving 信号锁按钮/开关/请求出口、maxCount 与 rules max 同源约束;npm run docs 后 llms-full.txt 出现该配方;npm run check 全绿",
"notes": "自补:文档完善;只写用法不碰实现,配方随 docs 注入 llms-full",
"status": "done",
"domain": "tooling"
},
{
"id": "g128",
"title": "收尾刷新变更日志",
"motivation": "g123 之后又落了 10 次提交(rate 半星、checkbox 半选、开关 loading、precision、配方 6 等),changelog:check 已报漂移",
"acceptance": "npm run changelog 重新生成(329 提交),changelog:check 一致(提交本条后按约定再差一条,属正常);npm run check 全绿",
"notes": "自补:发版前刷新步骤;本次唤起收尾轮,顺带提交 g127 漏带的 llms-full.txt",
"status": "done",
"domain": "tune"
},
{
"id": "g129",
"title": "新增排版文本组件 typography",
"motivation": "文档类页面(详情、说明、条款)目前只能手搓 h/p + 内联样式,标题层级、语义色、多行省略、复制都没有统一入口",
"acceptance": "typography 支持 variant(title/text/paragraph) + level + type + 装饰位(strong/underline/delete/mark/code) + align + ellipsis 多行截断 + copyable;标题出语义 h1-h5;样式只消费 --ua-*;manifest/docs/README/demo/llms-full 同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充方向",
"domain": "new"
},
{
"id": "g130",
"title": "新增栅格容器 grid",
"motivation": "页面骨架只能靠 space 与手写 flex,24 栅格、列间距、响应式断点缺失,示例页布局各写一套",
"acceptance": "grid 支持 columns 固定列数与 min 走 auto-fit 自适应列宽、gutter 统一列间距、align 交叉轴对齐、dense 紧凑填充;窄屏自动降为单列;样式只消费 --ua-*;manifest/docs/README/demo/llms-full 同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充方向(原设想 row/col 24 栅格,实施改为单容器 grid —— auto-fit 已覆盖响应式,跨列需求交给子项自身宽度,少一层抽象)",
"domain": "new"
},
{
"id": "g131",
"title": "新增悬浮操作按钮 floatButton",
"motivation": "backTop 只能回顶,页面里「新建 / 帮助 / 客服」这类常驻悬浮入口没有统一件,各处自己 fixed 定位",
"acceptance": "floatButton 固定右下角,支持 icon/label/tooltip/badge/点击回调与 position 偏移,键盘可达;样式只消费 --ua-*;文档与演示同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充方向",
"domain": "new"
},
{
"id": "g132",
"title": "@提及输入补 mentions 组件",
"motivation": "评论 / 协作场景要 @ 选人,目前只能 textarea + 自己接 autoComplete,光标定位与插入逻辑重复实现",
"acceptance": "mentions 在 @ 触发后按光标位置弹候选,↑↓/Enter/Tab 选择、Esc 关闭,选中插入文本并回调 onSelect;受控 value 信号;样式只消费 --ua-*;文档与演示同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充方向",
"domain": "tune"
},
{
"id": "g133",
"title": "树形选择补 treeSelect 组件",
"motivation": "tree 与 select 都在,但「可搜索的层级下拉选择」缺位,级联选择 cascader 不适配任意深度懒加载场景",
"acceptance": "treeSelect 点击开弹层、支持搜索过滤、单选/多选、父子联动、清空,弹层沿用 overlay 的 Esc 栈与外部点击收起;样式只消费 --ua-*;文档与演示同步;npm run check 全绿",
"status": "done",
"notes": "自补:组件补充方向",
"domain": "tune"
},
{
"id": "g134",
"title": "组件计数自动同步 ua sync-counts",
"motivation": "新增组件要手改 README 标题 / README 包说明 / AGENTS / ARCHITECTURE 四处数字,本轮四次新增每次都靠人工替换;且 README 标题「共 N 个」此前不在 verify 正则里,漏改不报错",
"acceptance": "新增 ua sync-counts(幂等,无变化不写盘)把三份文档的「N 个组件 / N 个 DOM 组件 / 共 N 个」统一改写为清单实际数量;verify 的计数正则覆盖 README 标题形态并提示修法;npm run check 全绿",
"notes": "自补:构建/门禁工具链改进(实测把 README 标题改成 61 后 verify 能报行号,sync-counts 一键修回)",
"status": "done",
"domain": "tune"
},
{
"id": "g135",
"title": "表单支持多列布局 columns",
"motivation": "字段多的编辑页只能一列往下堆,长表单要滚很久;页面里已有 grid,form 却没用上,调用方只能自己拆成多个 form(校验被割裂)",
"acceptance": "form 新增 columns(1/2/3),>1 时字段走 grid 铺列、窄屏自动单列、按钮区始终整行;单向依赖(form 复用 grid);manifest props/steps 与 demo 同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节",
"status": "done",
"domain": "tune"
},
{
"id": "g136",
"title": "新增链接组件 link",
"motivation": "页面里的 a 标签各写各的样式:站外链接普遍漏 rel=noopener、没有 ↗ 提示,读屏也不知道开了新窗口;禁用态要么当纯文本要么还能点",
"acceptance": "link 支持 href/label/target/external/muted/disabled/onClick,站外自动补 ↗ 与 rel 并播报新窗口,禁用去 href 保留 aria-disabled;样式只消费 --ua-*;文档与演示同步;npm run check 全绿",
"notes": "自补:组件补充方向",
"status": "done",
"domain": "new"
},
{
"id": "g137",
"title": "演示页补暗色主题开关",
"motivation": "tokens 里有 [data-theme=dark] 整套令牌,但演示页从未切换过:暗色令牌长期无人验证,页面自有 --page-* 变量也只跟系统偏好、不跟显式主题",
"acceptance": "演示页头部提供暗色主题开关(写 html[data-theme],localStorage 记忆,初值跟随系统);--page-* 变量响应 data-theme,媒体查询只在未显式指定时生效;npm run check 全绿",
"notes": "自补:演示页完善",
"status": "done",
"domain": "tune"
},
{
"id": "g138",
"title": "日期选择支持区间模式",
"motivation": "筛选条件几乎都要「起止日期」,datePicker 只有单日:调用方只能并排放两个选择器,用户看不出选了什么区间,也拿不到排序保证",
"acceptance": "datePicker 新增 range/end/onRangeChange:首点定起点不收弹层、次点定终点并排序,区间内部有底色;文档 props/steps 与演示同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节",
"status": "done",
"domain": "tune"
},
{
"id": "g139",
"title": "提示气泡支持左右方位",
"motivation": "tooltip 只有上下两向:竖排工具栏、贴右边的内容只能硬翻,left/right 全靠调用方自己补样式;position.ts 的 flip 也只认上下",
"acceptance": "position 新增 flipSide(四向判定,水平按宽度算左右空间),tooltip placement 支持 left/right 并复用自动翻转;CSS 四向定位 + 出场位移分离;文档 props/steps 与演示同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节",
"status": "done",
"domain": "tune"
},
{
"id": "g140",
"title": "表格支持整行点击",
"motivation": "「点行进详情」是表格最常用交互,dataTable 只能把跳转塞进某一列的按钮:点击区域小、键盘不可达,行本身对读屏也只是文本",
"acceptance": "dataTable 新增 onRowClick:传了则该行 tabindex=0 并响应点击 / Enter / 空格,未传时行为与现在完全一致(不进 Tab 序列);文档 props/steps 与演示同步;单文件仍 ≤200 行;npm run check 全绿",
"notes": "自补:既有组件可用性细节(行数恰好压到 200 行上限,靠合并注释行达标)",
"status": "done",
"domain": "tune"
},
{
"id": "g141",
"title": "通知支持四角停靠",
"motivation": "notification 只能堆在右下角,与抽屉 / 悬浮按钮抢位置;页面里已有 toast 占右上,通知没有别处可去",
"acceptance": "notification 新增 placement(四个角),每角独立容器与 3 条上限,顶部角落新通知贴边出现、旧的往下让;文档 props/steps 与演示同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节",
"status": "done",
"domain": "tune"
},
{
"id": "g142",
"title": "间距容器支持分隔符 split",
"motivation": "「A · B · C」这类一行多段信息(筛选摘要、面包屑式路径)只能手写 span 分隔符再逐个调 margin,gap 与分隔符混用后间距不均,读屏还会念出「·」",
"acceptance": "space 新增 split:仅在 2 个以上子元素间插入分隔符,分隔符 aria-hidden、弱化色、不可选中;文档与演示同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节",
"status": "done",
"domain": "tune"
},
{
"id": "g143",
"title": "页签栏支持右侧操作区 extra",
"motivation": "「新建 / 设置」这类页签级操作只能塞进页签数组或浮在页签栏外,垂直不对齐、分隔线断掉;调用方还得自己包一层 flex",
"acceptance": "tabs 新增 extra:与页签同一行、共用一条分隔线,不传时 DOM 结构不变(纯增量);文档 props/steps 与演示同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节",
"status": "done",
"domain": "tune"
},
{
"id": "g144",
"title": "新增头像组组件 avatarGroup",
"motivation": "协作列表 / 评论头部要展示参与人,并排 avatar 占横向空间且没有「还有 N 人」的表达;调用方各自用 margin 叠放,切口与层级都不一致",
"acceptance": "avatarGroup 支持 items(name/src/variant)、max 折叠为 +N、size 与 avatar 对齐;整组 title 列全部姓名、+N 有可访问名;样式只消费 --ua-*;文档与演示同步;npm run check 全绿",
"notes": "自补:组件补充方向",
"status": "done",
"domain": "new"
},
{
"id": "g145",
"title": "滑块支持刻度标注 marks",
"motivation": "0-100 的滑块看不出档位含义,用户只能看右侧数字猜;调用方要在轨道下标 0/50/100 得自己量宽度算百分比",
"acceptance": "slider 新增 marks(value/label),按 min-max 换算百分比绝对定位、随宽度自适应;轨道包一层不影响右侧数值;刻度 aria-hidden;文档与演示同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节",
"status": "done",
"domain": "tune"
},
{
"id": "g146",
"title": "进度条支持环形形态",
"motivation": "看板与上传卡片普遍要环形进度,progress 只有线性条:调用方只能自带 SVG 并对齐语义色与百分比文字",
"acceptance": "progress 新增 type=line|circle,环形走 createElementNS 的 SVG 双圆 + dashoffset,中间显示百分比、aria-label 拼名称与数值,variant 换弧色;linear 行为不变;文档与演示同步;npm run check 全绿",
"notes": "自补:既有组件可用性细节(坑:h() 用 createElement 造不出可渲染 SVG,必须 createElementNS)",
"status": "done",
"domain": "tune"
},
{
"id": "g147",
"title": "补三条组合配方(筛选栏/评论/详情页)",
"motivation": "本轮新增的 treeSelect、datePicker range、mentions、avatarGroup、typography、grid 只有参数表,Agent 拿到组件却不知道该怎么拼;recipes.md 还停在 6 条",
"acceptance": "recipes.md 补配方 7-9(筛选栏、评论输入、详情页排版),只消费对外 API,随 npm run docs 进入 llms-full.txt;npm run check 全绿",
"notes": "自补:Agent 文档完善",
"status": "done",
"domain": "tooling"
},
{
"id": "g148",
"title": "MCP 暴露组合配方检索(list_recipes / get_recipe)",
"motivation": "recipes.md 已有 9 条跨组件配方,但只写在文档里:Agent 通过 MCP 只能查单组件参数,拿不到「怎么拼」,容易各自造轮子或漏掉约定。",
"acceptance": "ua mcp 新增 list_recipes(id/title/components)与 get_recipe(骨架代码 + 要点 + 用到的组件)两个工具;配方解析复用 recipes.md 单一来源;verify 增加配方结构校验(编号连续、每条有代码块与要点、有通用约定段);AGENTS/README/CLI 清单文档同步。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g149",
"title": "CLI 补 ua search(关键词找组件 + 拼错给相近 id)",
"motivation": "组件已达 67 个,只有 ua list 全量平铺与 ua info 精确查询:不记得 id 时只能肉眼扫列表,拼错则只回一句 not found。",
"acceptance": "ua search <关键词> 按 id/标题/props 名/步骤匹配并排序输出,支持 --json;ua info 与 search 未命中时给出编辑距离 ≤3 的相近 id;usage 与 CLI 清单 steps、README、AGENTS 同步。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g150",
"title": "ua info 反查组件出现在哪些配方",
"motivation": "配方与组件是两套入口:查完 props 往往还要自己判断「这组件平时和谁一起用」,而 recipes.md 里已有答案却没接上。",
"acceptance": "ua info <组件id> 末尾列出包含该组件的配方 id 与标题(无命中不追加空行);--json 输出增加 recipes 字段;MCP get_component 组件级返回同步带 recipes。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g151",
"title": "verify 校验 MCP 工具清单三处一致",
"motivation": "MCP 工具从 4 个扩到 6 个时,README/AGENTS/CLI 清单靠手改;漏一处就会让 Agent 读到过期的工具名,而 verify 当时不管这块。",
"acceptance": "MCP_TOOL_NAMES 为单一来源,cmdMcp 启动时自证注册表与之一致;verify 检查 README/AGENTS/CLI 清单逐个写出工具名,缺失即报错并指出漏了哪个(已负向实测)。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g152",
"title": "ua list 增加组件全览模式",
"motivation": "ua list 只列 5 个包,67 个组件的 id 只能靠 ua search 猜关键词去找;没关键词时缺少「一次扫全量」的入口。",
"acceptance": "ua list 每行带上组件数;ua list --components 按包分组列出 id / 标题 / 演示锚点并给出总数与后续命令提示,原有无参输出保持可用。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g153",
"title": "ua search 支持多关键词与脚本退出码",
"motivation": "单一关键词只能粗筛:想找「日期区间」时搜日期会连 calendar/timeline 一起返回;且未命中仍退 0,脚本里判断不了。",
"acceptance": "ua search a b 取各词命中的最弱档做 AND 过滤仍保持 id>标题>props>步骤 的排序;未命中输出相近建议并 exit 1;无参数打印用法。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g154",
"title": "MCP search_components 补组件级命中",
"motivation": "原实现只在包级字段(name/description/tags/api.exports)里搜,命中后只返回包:Agent 搜「日期」得到 @ui-arch/ui,还得再猜组件 id。",
"acceptance": "返回 { packages, components } 两级结构,components 按 id/标题/props 名命中并带 package 字段;空 query 与无命中都抛明确错误;工具描述同步。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g155",
"title": "llms.txt 补配方目录 + 修正 MCP 工具清单描述",
"motivation": "Agent 常只读 llms.txt 速览,但速览里完全没有配方入口;同时 cli 包导出描述里只写了 3 个 MCP 工具,与实际的 6 个不符。",
"acceptance": "ua index 在 llms.txt 末尾输出配方目录(id / 标题 / 要点数)并指明三种取法;cli 清单的 mcp 导出描述与 MCP_TOOL_NAMES 一致。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g156",
"title": "ua tokens 支持按名字过滤",
"motivation": "21 个令牌全表打印后再肉眼找某一个,写样式时反复查多行;缺令牌名拼错也不报错。",
"acceptance": "ua tokens <子串> 只列匹配项并在末尾标注匹配词,--json 同步过滤;无匹配时给出总数并以 1 退出。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g157",
"title": "补配方 10:暗色主题切换",
"motivation": "令牌层早就支持 [data-theme=dark],但配方里没有任何一条说清「怎么切、初值怎么定、原生控件为何仍是白底」,实践只能靠翻令牌表猜。",
"acceptance": "recipes.md 新增配方 10(切 data-theme + 同步 color-scheme + 初值先读 localStorage),verify 的配方结构校验通过,llms-full 与 MCP get_recipe 10 可取到骨架。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g158",
"title": "日期选择支持快捷区间预设",
"motivation": "仪表盘类界面里「最近 7 天 / 最近 30 天」是高频刚需,现在调用方只能在弹层外自己摆按钮再算日期区间,算完还得手写 value/end 两个信号,重复且易错。",
"acceptance": "datePicker 新增 presets 入参(仅区间模式渲染):每项支持 days 相对今天或 from/to 绝对日期,点击即写入 value/end、按 min/max 钳制并收起;演示页区间示例带三档预设;manifest props 表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g159",
"title": "多选下拉支持标签折叠",
"motivation": "multiSelect 的 max 放开后标签会把输入区挤到换行,窄栏(筛选栏 / 表单侧栏)里选中五六个就撑破布局,调用方只能自己截断数组、却因此拿不到真实值。",
"acceptance": "multiSelect 新增 maxTagCount:前 N 枚标签照常渲染与删除,其余折成虚线「+N」(title 给全量、点击展开弹层,含 aria-label);演示页加一枚预置 3 项的折叠实例;manifest props 表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g160",
"title": "滑块支持双滑块区间",
"motivation": "价格 / 数值区间筛选是电商与报表的基本控件,此前只能拼两个单值 slider,视觉上两条独立轨道、还得自己保证下限不超过上限,调用方到处写同样的 clamp。",
"acceptance": "slider 新增 range / end / onRangeChange:两条原生 range 叠轨、只有拇指可命中,强调色画出已选段,越界自动压平保证 low ≤ high,右侧显示「低 ~ 高」;单值路径不变;manifest props 表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g161",
"title": "通知支持同 key 原地更新",
"motivation": "异步任务(上传/导出/同步)要把一条通知从 0% 刷到 100%,此前只能再调一次 notification,结果右上角堆出三条互不相干的卡,MAX_STACK=3 还会把别的通知挤掉。",
"acceptance": "notification 新增 key 入参:同 key 二次调用原地改标题 / 描述 / 语义色并返回同一句柄;句柄新增 update(partial),close 时释放 key 登记;演示页加三拍进度演示;manifest props 表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g162",
"title": "门禁补暗色令牌齐全校验",
"motivation": "暗色主题此前只靠人眼看:新增一个 --ua-color-* 忘了在 [data-theme='dark'] 里给值,暗色下就静默沿用亮色(如白底深字条突然发白),没有任何机制拦。",
"acceptance": "verify 新增两项校验:每个 --ua-color-* 必须在暗色块有覆盖值;暗色块里的变量必须先在 :root 定义过(防改名漏改);ua tokens 对缺暗色的颜色令牌追加「缺暗色」标注;AGENTS.md 门禁说明同步;npm run check 全绿,且临时抽掉一条暗色值时 verify 确实报错。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g163",
"title": "留痕校验补编号与时间线约束",
"motivation": "上一实例曾把新条目误插到旧条目后面,把一条 done 写成两条,还出现过手写时间戳——这些都是「结构错但内容看起来对」的漂移,verify 当时完全看不出来。",
"acceptance": "verify 新增:backlog id 不重复、g 编号严格递增、同一 id 只能有一条 done 留痕、留痕 ts 必须带 +08:00、时间线不回退、不落在未来(5 分钟容差);AGENTS.md 与 verify 成功文案同步;负向注入(复制条目 + 手写无偏移时间戳)四类错误全部报出,复原后全绿。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g164",
"title": "描述列表支持竖排与标题操作区",
"motivation": "详情页右侧窄栏里,字段名横排会把值挤成两三行断字;标题行想放个「刷新 / 更新时间」也没位置——只能在外面另拼一个 flex 头。",
"acceptance": "descriptions 新增 layout(vertical 把字段名挪到值上方,bordered 时自动去掉标签分隔线与底纹)与 extra(标题行右侧内容);演示页加竖排 + 操作区实例;manifest props 表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g165",
"title": "步骤条支持竖向排列",
"motivation": "审批流 / 物流轨迹在侧栏里只能横排,三步以上就被挤成两行、连接线错位;调用方只能退回自己拼时间线,丢了 done/current 三态语义。",
"acceptance": "stepper 新增 direction:vertical 时圆点成一列、连接线走圆点下方、末步不画线、已完成段用主色;演示页加独立信号的竖向实例;manifest props 表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g166",
"title": "下拉选项支持分组标题",
"motivation": "技术栈 / 部门 / 城市这类长候选集只能平铺,用户得一行行扫;分组要么拆成多个 select,要么在 label 里塞「【前端】Vue」这类假标题。",
"acceptance": "OptionItem 与 CheckOption 新增 group:select 在组首插灰色小标题(段间分隔线)、整组过滤无命中时标题隐藏;multiSelect 候选同步支持且小标题不占键盘下标;演示页加分组下拉实例;manifest 的 options 描述与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g167",
"title": "弹窗支持内置页脚与异步确认",
"motivation": "「取消 + 确定」这两个按钮几乎每个确认框都要手拼一遍,异步提交时还得自己管「处理中」禁用与成功后收窗,各处写法不一。",
"acceptance": "modal 新增 confirmText / cancelText / onConfirm:不传 footer 时渲染内置按钮组(footer 仍优先),onConfirm 返回 Promise 时按钮禁用并显示「处理中…」,成功自动收窗、失败保持打开;演示页把确认弹窗改成异步确认;manifest props 表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g168",
"title": "补配方 11:数据看板筛选区",
"motivation": "本轮补的区间滑块、日期预设、分组下拉都是筛选区零件,但配方里没有一条把它们拼起来;Agent 拿到零件也不知道该用 computed 派生查询而不是各存一份状态。",
"acceptance": "recipes.md 新增配方 11(grid 三列放 slider/datePicker/select + dataTable),要点写清「信号单一来源」「区间控件自带 clamp」「group/presets 的取舍」;verify 配方结构校验通过;llms-full.txt 与 MCP get_recipe 可取到。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g169",
"title": "表单字段支持跨列",
"motivation": "columns: 2 的表单里,多行备注 / 地址这类字段只能各占一格,看上去被压成半宽;想整宽只能把 form 拆成两个再手拼,校验聚合也就断了。",
"acceptance": "formItem 新增 span:字段在栅格里占 N 列;grid 窄屏单列时用 !important 忽略跨列,避免撑出横向滚动;演示页两列表单加整宽备注;manifest 的 formItem 参数表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g170",
"title": "分页器支持极简模式与单页隐藏",
"motivation": "表格工具栏里放完整页码会占掉半行;只有一页时又留一个灰掉的「1」占位,两种场景都只能靠调用方自己条件渲染。",
"acceptance": "pagination 新增 simple(只留前后箭头 + 「当前 / 总页」文字,带 aria-live)与 hideOnSinglePage(单页整块 hidden);演示页各加一例;manifest props 表与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g171",
"title": "刷新 CHANGELOG 与配方目录",
"motivation": "CHANGELOG 由提交史生成、不进 check(它包含每个提交自身,提交后必然差一条),只能作为发版前刷新;本轮 14 次迭代后已落后 30 余条,llms.txt 的配方目录也漏了配方 11。",
"acceptance": "npm run changelog 重生成到当前提交史(400 提交);llms.txt 补配方 11 目录行并在此后随 docs 归档;CHANGELOG 内容与 git log 一致(npm run changelog:check 通过)。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g172",
"title": "列表命令支持 JSON 输出",
"motivation": "ua info 早就有 --json,但 ua list 只能打印文本,脚本与 Agent 想拿「包 + 组件 id」清单只能抓 stdout 再正则,容易随文案变化失效。",
"acceptance": "ua list --json 输出 {name, version, type, description, components[]} 数组,--components 仍逐组件展开;CLI 清单 exports 与 AGENTS.md 的用法行同步;npm run check 全绿(含 lint-iterations)。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g173",
"title": "上传图片显示缩略图",
"motivation": "选完图片只看到文件名,用户没法确认选的是哪张——上传头像 / 凭证照这类场景必须点开本地文件核对,体验很别扭。",
"acceptance": "upload 对 image/* 文件渲染 28px 缩略图(object-fit: cover),对象 URL 按 File 缓存、文件离场即 revoke;非图片保持文件名 + 体积;manifest 步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g174",
"title": "时间线支持双侧交替",
"motivation": "里程碑 / 项目历程这类内容用单侧时间线时,长正文会把右侧挤成一条窄柱;做左右交替得自己包两层栅格,pending 虚节点还容易错位。",
"acceptance": "timeline 的 mode 增加 alternate:轴线居中、条目按奇偶分列左右、圆点贴中线、窄屏自动退回单侧(pending 虚节点参与交替计数);演示页加里程碑实例;manifest 参数类型与步骤、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g175",
"title": "确认气泡支持异步确认",
"motivation": "删除 / 归档的行内操作基本都要打后端,但 popconfirm 的 onConfirm 是同步签名:点确定立刻收起,请求失败时气泡已经没了,用户只能看到一条报错、得重新点开。",
"acceptance": "popconfirm 的 onConfirm 返回 Promise 时:气泡保持展开、确认按钮变「处理中…」并禁用,resolve 后收起、reject 后复原可重试;类型放宽为 () => unknown(普通回调仍先收起再执行),modal 的 onConfirm 同步放宽;演示页加异步归档实例;manifest、llms-full 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g176",
"title": "ua search 覆盖参数说明并标出命中字段",
"motivation": "Agent 找组件时给的往往是需求词(如「处理中」「省略号」「归档」),这些词只出现在参数说明或步骤里;原实现只拼参数名,说明文字搜不到,明明有组件却回 no match,Agent 只能改用 list 全量翻。",
"acceptance": "search 按 id / 标题 / 参数名 / 参数说明 / 步骤五层打分排序,命中非 id 标题时行尾标出命中字段(如「← 参数说明含『处理中』」),--json 附带 matched;MCP search_components 复用同一字段表,工具描述与 AGENTS.md、tools manifest 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tune"
},
{
"id": "g177",
"title": "补 ua recipes 子命令,配方不再只能靠 MCP 取",
"motivation": "组合配方(跨组件拼装骨架)原本只有 MCP 的 list_recipes / get_recipe 能读,没注册 MCP 或纯 CLI 场景下 Agent 只能翻 recipes.md 原文;另外 CLI 与 MCP 各写了一份「配方里用到哪些组件」的抽取逻辑,口径容易漂移。",
"acceptance": "ua recipes 列出编号/标题/用到的组件,ua recipes <编号> 打印骨架代码与要点,--json 给脚本用;MCP 复用同一抽取函数;AGENTS.md 与 packages/tools/cli/ua.manifest.json 同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g178",
"title": "verify 兜住「认不出组件」的配方",
"motivation": "配方骨架里的组件名如果拼错(如 cascader 写成 cascaderSelect),结构校验全都过——代码块在、要点在、编号连续,但 ua recipes 会把组件列成空表,Agent 拿到的是一份「没有依赖」的骨架,排查成本高。",
"acceptance": "ua verify 对每条配方检查代码块+正文是否至少认出一个已注册组件 id,认不出直接报错并指名配方编号与标题;负向自检(临时插一条用未注册组件名的配方)确认会拦;AGENTS.md 门禁说明同步;npm run check 全绿。",
"notes": "",
"status": "done",
"domain": "tooling"
},
{
"id": "g179",
"title": "弹层阴影语义化:剩余字面阴影归入 elevation 四档",
"motivation": "阴影已收敛出四档令牌,但仍有主题色投影(color-mix text/accent)与状态色环以字面值散落在弹层、气泡、选中态里,同名层级在不同组件用不同数值,观感不齐且改不动。",
"acceptance": "ui.css 中 box-shadow 的 px 字面值全部归入 --ua-shadow-1..4 或语义色环(无主题色投影字面值);`ua audit:visual --json` 的 shadow-literal 计数相对 .ua/visual-baseline.json 至少减少 8 处并写入新基线;在 packages/skin/ui/ua.manifest.json 的 docs.intro 写明「下拉=shadow-2 / 浮层=shadow-3 / 弹窗=shadow-4 / 通知=shadow-4」对照表;npm run check 全绿。",
"status": "done",
"notes": "主题色投影新增 shadow-accent / shadow-accent-strong 两档令牌(含暗色),文本系投影按「下拉=2 / 浮层=3 / 最高层=4」归档;shadow-literal 17→9(减 8 处),新基线 27 处已写入;manifest intro 补阴影对照表",
"domain": "visual"
},
{
"id": "g180",
"title": "内距令牌化:padding / gap 字面值降到基线以下",
"motivation": "间距只有 4/8/12/16 四档令牌,实际却大量写 6px/10px 这类中间值,同一类容器内距不统一,改密度只能全仓替换。",
"acceptance": "packages/design/tokens/tokens.css 补齐内距档位(新增 6px/10px 两档或把它们归并到既有档并在注释说明),tokens.json 同步;visual-rules 增加 space-literal 类(padding/gap/margin 的 px 字面值),迁移脚本同步规则并幂等;该类计数写入新基线且比首次统计少 30 处以上;npm run check 全绿。",
"status": "done",
"notes": "新增半步内距档 space-05/15/25/35(2/6/10/14px,tokens.css+json 同步);visual-rules 新增 space-literal 第六类(迁移脚本与审计共用同一张表、幂等);首次统计 160 处,迁移 121 处后余 13 处,新基线 40 处",
"domain": "visual"
},
{
"id": "g181",
"title": "表单控件高度统一接入 control-h 三档",
"motivation": "控件高度令牌 --ua-control-h-sm/md/lg 已就位,但 button/textField/select/inputNumber 仍各自写死高度,同一行并排时高度不一致,也挡住「略放大提高可点面积」的落地。",
"acceptance": "ui.css 中 .ua-btn / .ua-input / .ua-select / .ua-input-number 的高度声明全部引用 var(--ua-control-h-*);visual-rules 增加 control-h-literal 类且计数为 0;apps/demo/main.ts 中存在一行并排的三档高度演示(sec 锚点内真实调用四个组件);npm run check 全绿。",
"status": "done",
"notes": "四个控件载体(ua-btn/ua-input/ua-select-trigger/ua-input-number-field)统一 height 走 control-h 三档;button/textField/select/inputNumber 新增 size 入参(sm/md/lg,is-sm/is-lg 类);步进器按钮 height:auto 豁免;control-h-literal 第七类规则(28/34/40px 保留给控件档位)计数 0;28px×5 与 40px×2 字面值归档位令牌;demo sec-inputnumber 加四组件三档并排行",
"domain": "visual"
},
{
"id": "g182",
"title": "暗色主题阴影与焦点环适配",
"motivation": "暗色块只覆盖颜色,弹层阴影沿用亮色主题的低透明度黑投影,暗底上几乎看不出层级;焦点环在暗色下对比也不足。",
"acceptance": "[data-theme='dark'] 覆盖 --ua-shadow-1..4(更高黑度)与 --ua-focus-ring 两个槽位;ua verify 增加一条校验:暗色块缺 shadow / focus 覆盖即报错;ua tokens 输出的暗色列能看到这 6 个令牌的暗色值;npm run check 全绿。",
"status": "done",
"notes": "暗色块补 --ua-focus-ring / --ua-focus-ring-inset(透明度上调补对比),shadow-1..4 与 accent 两档人类上游已备;verify 暗色齐全校验扩展到 --ua-shadow-* / --ua-focus-* 前缀(负向自检删除 shadow-1 暗色覆盖已被拦);ua tokens 暗色列可见全部 6 个令牌的暗色值;tokens.json focus 组补 light/dark 结构",
"domain": "visual"
},
{
"id": "g183",
"title": "字重与行高统一到三档令牌",
"motivation": "标题、正文、辅助文字的字重与行高全靠各组件手写,同一层级的标题在不同弹窗里字重不同,多行文本行距忽松忽紧。",
"acceptance": "visual-rules 增加 weight-literal 与 leading-literal 两类(font-weight / line-height 的字面值),迁移脚本把字面值改为 --ua-weight-* / --ua-leading-*;两类计数归零并写入新基线;标题/正文/辅助三档在注释里写明归属;npm run check 全绿。",
"status": "done",
"notes": "visual-rules 新增 weight-literal / leading-literal 两类(第八、九类);字重映射 400/500/600/700 → normal/medium/semibold/bold(补 bold 档),行高 unitless 就近收敛到 none/tight/normal(补 none 档,px 行高按图形对齐豁免);两类 36+36 处全部归零并写新基线 40 处;tokens.css 注释标明标题/正文/辅助三档归属",
"domain": "visual"
},
{
"id": "g184",
"title": "层级令牌落地:z-index 字面值归零",
"motivation": "--ua-z-dropdown/sticky/overlay/modal/toast 五档已定义但无人使用,实际仍是各组件写 100/1000/9999,弹层互相盖住的问题只能靠试数值解决。",
"acceptance": "visual-rules 增加 z-literal 类;迁移脚本把 ui.css 中全部 z-index 字面值替换为 var(--ua-z-*);该类计数为 0 并写入新基线;ua tokens 能看到五档层级;npm run check 全绿。",
"status": "done",
"notes": "z-literal 第十类规则(组件内 z:1/2 微排序豁免,同 1px 边框惯例);按选择器把 20 处全局浮层字面值归入五档:下拉/气泡类 20/30/40→dropdown,fab→dropdown,toast/notice 宿主→toast,affix/backtop→sticky,tour/模态抽屉遮罩→overlay,image 遮罩→modal;迁移脚本对下拉类 20/30/40 提供幂等自动迁移;计数归零并写新基线 40 处",
"domain": "visual"
},
{
"id": "g185",
"title": "边框细节统一到 subtle 与基础色",
"motivation": "分割线、输入框、卡片边框混用基础边框色与更深的黑边,1px 线在不同组件上粗细观感不一,密集表格里分割线过重。",
"acceptance": "分割线改用 --ua-border-subtle、输入框与卡片边框用 --ua-color-border,ui.css 中不再出现硬编码黑边(rgba(0,0,0,…) 作为边框值);visual-rules 增加 border-literal 类且计数为 0;表格与输入框在 demo 中肉眼无粗细跳变;npm run check 全绿。",
"status": "done",
"notes": "border-literal 第十一类规则(border/border-color 色值字面量,纯黑 rgba 边可幂等迁移到基础边框色);19 处 1px 分割线统一改走 --ua-border-subtle(表格行线、卡片页脚、弹窗/抽屉页头页脚、列表项、选项卡底边、折叠面板与步骤条连接线、分隔线组件),组件轮廓仍用 --ua-color-border;border-literal 计数 0 并写新基线 40 处;顺带修正 manifest 里「五类/十组」陈旧表述",
"domain": "visual"
},
{
"id": "g186",
"title": "视觉回归抽查清单:六个代表组件的可勾选验收项",
"motivation": "视觉类改动只能靠人眼抽查,每次凭记忆看一遍,漏看是常态;把六个高频组件的观感要点固化成清单,后续视觉条目可直接复用。",
"acceptance": "在 ITERATION.md 或 packages/skin/ui/ua.manifest.json 增加《视觉回归抽查清单》,button / textField / select / modal / dataTable / toast 六个组件各 ≥3 条可勾选要点(合计 ≥18 条);清单条目必须覆盖边框、阴影、过渡、焦点环、暗色五项;ua verify 校验清单存在且 18 条要点齐全;npm run check 全绿。",
"status": "done",
"notes": "清单落在 ITERATION.md《视觉回归抽查清单》:六个代表组件共 26 条可勾选要点(button 5 / textField 4 / select 4 / modal 5 / dataTable 4 / toast 4),每条带【边框/阴影/过渡/焦点环/暗色】维度标签;ua verify 新增 checkVisualChecklist(章节存在 + 六段齐全 + 每段 ≥3 + 合计 ≥18 + 五维度全覆盖 + 维度标签合法性),负向自检确认删段/降标签/条目不足都会报错;AGENTS.md 与 cli manifest 同步该职责",
"domain": "visual"
},
{
"id": "g187",
"title": "新增虚拟列表组件 virtualList",
"motivation": "长列表(万级流水、日志、消息流)用 list 全量渲染必然卡死,DOM 数量随数据线性增长是性能悬崖;组件库有 list 但没有虚拟化,调用方只能各写各的滚动窗口逻辑。(自补选题;g179–g186 已被人工视觉域条目占用,本条顺延取号)",
"acceptance": "新增 virtualList(固定行高、只渲染可视窗口 + overscan 缓冲条、rAF 合帧滚动重建、scrollTo 句柄越界钳制),10 处落点齐全(实现/类型/导出/样式/manifest/demo/README/计数/文档),万行 demo 常驻 DOM 只有窗口行数;npm run check 全绿。",
"notes": "自补:组件补充;rebase 到人工新推的视觉令牌/审计门禁上游后门禁全绿",
"status": "done"
},
{
"id": "g188",
"title": "新增命令面板组件 commandPalette(⌘K 唤起)",
"motivation": "68 个组件里缺「全局命令入口」这类高频交互:菜单要逐级点、autoComplete 只能选值不能执行动作,Agent 与键盘用户都没有一个「输入即缩小、回车就执行」的入口。",
"acceptance": "新增 commandPalette:open 信号驱动显隐、items 支持 group/hint/keywords 三层过滤、↑↓ 环形选择 + Home/End + 回车执行、Esc 走弹层栈只关最上层、开关均归还焦点、⌘K/Ctrl+K 可开关;样式只消费 --ua-* 令牌,禁止新增硬编码阴影/时长/字号;demo 新增可交互区块;npm run check 全绿且 audit:visual 计数不涨。",
"status": "done",
"notes": "新增 packages/skin/ui/src/commandPalette.ts(148 行)+ types 三接口 + ui.css 13 条规则(全令牌,含 prefers-reduced-motion 兜底);过滤看 label/hint/keywords 三层,高亮只切 class 不重建列表(aria-activedescendant 供读屏);Esc 复用 overlay 栈、滚动锁复用 lockScroll;demo 新增 sec-cmdk(按钮 + ⌘K 双入口);README 清单与「浮层」分组、ARCHITECTURE/AGENTS 组件数随 sync-counts 同步到 69;audit:visual 仍 40 处"
},
{
"id": "g189",
"title": "补配方 12:命令面板(⌘K 全局操作入口)",
"motivation": "commandPalette 刚入库,但「id 分发 / keywords 兜拼音 / onSelect 里开二次确认 / 多实例 hotkey 冲突」这些坑只有实现者知道;配方是 Agent 与新人复用组件的入口,缺一条就会各写一遍。",
"acceptance": "packages/tools/cli/recipes.md 增加配方 12(编号连续、含代码块与要点),代码块至少认出一个已注册组件 id;ua recipes 12 能取到骨架与要点;npm run check 全绿。",
"status": "done",
"notes": "配方 12「命令面板(⌘K 全局操作入口)」:id 做分发键、label 只显示;keywords 塞拼音别名;同组连续才合并;二次确认交给 modal/popconfirm 而非面板内叠层;一页只留一个 hotkey;items 快照需重建。ua recipes 12 识别 commandPalette / modal / popconfirm / toast 四组件"
},
{
"id": "g190",
"title": "字号字面值归零:补展示档与相对缩放档",
"motivation": "五档主轴只覆盖到 18px,剩下 5 处 22/26/34px 与大号字符图标、代码片段 0.92em、行内图标 0.85em 无令牌可用,只能写字面值;字号门禁因此长期停在 5 处,谁也不敢动。",
"acceptance": "tokens 增加 --ua-font-size-3xl/4xl/5xl(22/26/34px 展示档)与 --ua-font-size-scale-md/sm(0.92em/0.85em 相对缩放档),visual-rules 映射表同步;迁移后 font-size-literal 计数为 0 并写入新基线;ua tokens 能看到五个新令牌;npm run check 全绿。",
"status": "done",
"notes": "字号令牌从 6 档扩到 11 档:3xl 22px(评分星等字符图形)/ 4xl 26px(统计主数值)/ 5xl 34px(空状态大图标)/ scale-md 0.92em(代码片段)/ scale-sm 0.85em(行内小图标,随文字缩放不建绝对档);迁移脚本按表幂等替换 5 处,font-size-literal 5→0,总计数 40→35 已写基线"
},
{
"id": "g191",
"title": "焦点环变体归零:补语义色环 / 光晕 / 选中环四档",
"motivation": "焦令牌只有强调色两档,评分星(警告色)、校验失败(错误色外扩环)、时间线脉冲(5px 4% 光晕)、色板选中(实色 2px)四处只能自绘,audit 的 focus-ring-variant 长期停在 4 处,也意味着语义色焦点在暗底上没有统一的可调点。",
"acceptance": "tokens 增加 --ua-focus-ring-warning / -danger / -pulse / -selected 四档(亮暗各一套,暗色块必须有覆盖),tokens.json 同步;ui.css 四处自绘环改走令牌;focus-ring-variant 计数为 0,总计数 35→27 并写入新基线;npm run check 全绿。",
"status": "done",
"notes": "四档语义环:warning 25%(评分星)/ danger 18%(校验失败外扩)/ pulse 5px 4%(时间线呼吸光晕,暗色加倍 8%)/ selected 实色 2px(色板选中);暗色块四个都给了覆盖,verify 暗色齐全校验通过;替换后 focus-ring-variant 4→0,同时这 4 条不再计入 shadow-literal,总计数 35→27"
},
{
"id": "g192",
"title": "动效时长字面值归零:补更慢档与循环四档",
"motivation": "三档交互时长上限 0.25s,转圈 0.7/0.8s、扫光 1.4s、脉冲 1.6s、条纹 0.6s、进度条 0.35s 全部无处可归,只能写字面值;duration-literal 一直卡在 9 处,加载动画的节奏也没法统一调。",
"acceptance": "tokens 增加 --ua-duration-slower(0.35s 交互)与 --ua-duration-loop-fast/mid/slow/long(0.6/0.8/1.2/1.6s 循环),tokens.json 同步;visual-rules 增加循环档映射(仅在 animation + infinite 时启用)并扩展交互档到四档;duration-literal 计数为 0,总计数 27→18 并写新基线;npm run check 全绿。",
"status": "done",
"notes": "交互时长三档→四档(新增 slower 0.35s,收 0.3/0.32/0.35 三处大跨度形变);循环四档 0.6/0.8/1.2/1.6s,0.7 与 0.8 并到中档、1.2 与 1.4 并到慢档;迁移脚本按「animation + infinite」判定走循环表,避免把交互过渡误归循环档;9 处全部归零,总计数 27→18"
},
{
"id": "g193",
"title": "阴影字面值归零:补描边族与断口令牌",
"motivation": "shadow-literal 剩下 5 处都不是投影,而是「用 box-shadow 画线」:粘性表头底部细线、日历今天的内框、色板/头像组的底色断口。它们没有令牌可用,只能写字面值,也让投影类目混进了非投影值。",
"acceptance": "tokens 增加描边族 --ua-shadow-hairline / inset-border / inset-accent 与断口 --ua-shadow-gap(亮暗各一套,暗色块必须有覆盖),tokens.json 同步;ui.css 五处改走令牌;shadow-literal 计数为 0,总计数 18→13 并写新基线;npm run check 全绿。",
"status": "done",
"notes": "四档描边族:hairline(inset 0 -1px 0 border-subtle,用阴影画线不占高度)/ inset-border(1px 细内框)/ inset-accent(强调内框标今天)/ gap(0 0 0 2px bg 底色断口,头像组与角标各一处);暗色四档全部覆盖;shadow-literal 5→0,总计数 18→13,剩空间类 13 处"
},
{
"id": "g194",
"title": "审计 json 输出每类命中明细(行号 / 属性 / 值 / 提示)",
"motivation": "ua audit:visual --json 只给计数,排查「哪一行踩了规则」必须临时手写 node -e 调 scanSource —— 本轮迭代里这件事就重复了三次;Agent 与 CI 都拿不到可定位信息,棘轮报红时仍要人工全表搜索。",
"acceptance": "audit:visual --json 在 counts / baseline 之外增加 hits:每个类目一组 { line, prop, value, hint },空类目给空数组;人读模式与退出码不变;cli manifest 的 api 描述同步;npm run check 全绿。",
"status": "done",
"notes": "--json 增加 hits(11 个类目齐全,空类目为空数组),每项含 line / prop / value / hint;实测 space-literal 13 条可定位到具体行与值;人读输出与棘轮退出码未改动,manifest 描述同步后 docs 已重生成"
},
{
"id": "g195",
"title": "内距字面值归零:视觉基线清零(11 类全 0)",
"motivation": "space-literal 剩 13 处,全部是「梯子里没有的零碎值」:3/5/7px 微调、13px 连接线对齐、22px 树缩进、28px 空态留白。它们让基线永远停在 13,也让「视觉零漂移」这个目标永远达不成。",
"acceptance": "tokens 补 --ua-space-6(24px 大面积留白)与 --ua-space-indent(22px 层级缩进);13 处按就近收敛或换用新令牌;space-literal 归零、总计数 13→0 并写入零基线;audit:visual 在零基线下能拦住任何新增字面值(负向自检);npm run check 全绿。",
"status": "done",
"notes": "13 处全部归位:3px 间隙→space-1(4)、5px 微距→space-15(6)、7px 页签→space-15(6)、13px 连接线→space-35(14,与圆点中心对齐)、28px 空态留白→新增 space-6(24)、22px 树缩进→新增 space-indent(22);合计 0 处,基线写为全 0(11 类),负向自检插一条 rgba 投影即报红并指出工作区未提交改动"
},
{
"id": "g196",
"title": "中性色阶地基:表面/文字/遮罩补中间档令牌",
"motivation": "令牌表只有 bg/surface 两层表面、text/muted 两级文字,层次全靠 ui.css 里 86 处 color-mix 临时派生;遮罩更是 4 处 rgba 各自为政(0.36/0.4/0.45/0.6,行 1917/3728/4278/4345),行 4778 虚拟列表底部渐变还在私写一条 border-subtle 的复制值。暗色适配每次都要逐处重调。",
"acceptance": "tokens.css 新增 --ua-color-surface-2/-3/-inset(表面三阶,color-mix 从 text 派生)与 --ua-color-text-secondary/-faint(文字四阶补齐)与 --ua-color-overlay(遮罩统一档),亮暗各一套;4 处 rgba 遮罩改走 --ua-color-overlay,行 4778 改走令牌;verify 暗色覆盖校验通过;npm run check 全绿。",
"status": "done",
"notes": "中性色阶地基:tokens 新增 surface-2/3/inset、text-secondary/faint、overlay/strong 9 令牌(亮暗双向),ui.css 遮罩 5 处 rgba 归档 + 虚拟列表私写分割线回归 border-subtle + 凹陷散点 3 处归档;commit 9ef8637,check 全绿(QA 验证见后续);QA O1 备案:drawer/modal/cmdk 遮罩 0.36/0.40/0.40 统一至 0.45 属有意归档(整体略变重),image/tour 等值迁移"
},
{
"id": "g197",
"title": "语义淡底配方化:accent 淡底五档收敛为两档令牌",
"motivation": "「选中态低视觉比重」的约定靠 54 处 background: color-mix 手写实现,透明度散点 5/6/8/10/12/14% 五档并存(13 处 10%、11 处 8%、6 处 12%、4 处 5%、2 处 14%),同一个「淡色选中底」在全库深浅不一,调一次主题要改 54 处。",
"acceptance": "tokens.css 新增 --ua-color-tint-accent(10% 标准选中底)与 --ua-color-tint-accent-weak(5% 悬停底),亮暗各一套;54 处 accent 淡底中:5/6/8% 悬停档归入 tint-accent-weak,10/12/14% 选中档归入 tint-accent;收敛后 ui.css 不得再出现 background: color-mix(in srgb, var(--ua-color-accent) N%, transparent) 字面配方;npm run check 全绿。",
"status": "done",
"notes": "accent 淡底配方化:tint-accent(10%)/tint-accent-weak(5%) 亮暗各套,37 处 transparent 变体收敛(16 弱 + 21 标准),混 bg 与高浓档按规则保留;commit 8148317,check 全绿(QA 验证见后续)"
},
{
"id": "g198",
"title": "全控件按下态补齐:从「能点」到「有手感」",
"motivation": "76 处 hover 对 7 处 :active——按下态只覆盖 button/checkbox/radio/tag/fab 五家;select-trigger 连 hover 都没有(行 548),input 无 hover,menu-item/option/page-num/tab/acc-head/calendar-day/slider 全部只有 hover 一档。点下去没有任何反馈,是「塑料感」的最大来源。",
"acceptance": "input/textarea 补 hover 边框过渡;select-trigger 补 hover 与 active;menu-item/option/page-num/tab/acc-head/calendar-day/button-like 列表行补 :active(底色加深一档,走 surface-active 或 tint 令牌);scale 类回缩与位移类下沉不混用、同族保持一致;全部带 prefers-reduced-motion 兜底;npm run check 全绿。",
"status": "done",
"notes": "全控件按下态补齐:input/textarea 补 hover 边框过渡,select-trigger/menu-item/option/tab/acc-head/page-btn/calendar-day 补 :active(surface-2 深一档),9 规则 + 2 transition 补齐,:not(:disabled) 守卫;commit 2996b87,check 全绿(QA 验证见后续)"
},
{
"id": "g199",
"title": "质感层:主按钮内高光、卡片边框减负、标题字距",
"motivation": "渐变全库只有 3 处(进度条纹×2 + 开关激活),主按钮纯平色块无光感;13/15 个卡片容器「1px 边框 + 阴影」叠加导致层次靠线不靠面;letter-spacing 仅 3 处(行 1237/1809/1811),表头与分组标题没有字距节奏。",
"acceptance": "主按钮(.ua-btn-primary)加顶部内高光(inset 0 1px 0 白系令牌)与 hover 光泽阴影(走 shadow-accent 系);卡片容器 hover 时阴影升一档、边框同步减淡(配合而非叠加);tokens 新增 --ua-tracking-wide(0.04em)并应用到表头/分组标题/大写小标题;质感手法只走令牌,不新增硬编码;npm run check 全绿。",
"status": "done",
"notes": "质感层:新增 highlight 内高光/shadow-highlight/tracking-wide/tight 令牌(亮暗覆盖),主按钮内高光+hover 主题色投影,卡片贴地影+hover 升档边框减淡,字距 4 处全走令牌;幽灵按钮显式退出质感层(透明底白高光露线);commit bfb9178,check 全绿"
},
{
"id": "g200",
"title": "动效编排:浮层分层入场与列表交错",
"motivation": "animation-delay 全库 0 处——modal/drawer/menu/dropdown 的入场全部齐步走,元素同时出现,没有先后叙事;行 1228/2572/4390 还有 3 处私写 cubic-bezier 绕开了 --ua-ease 体系。",
"acceptance": "modal/drawer 内容分层入场(面板先动、内容 60ms 错峰跟随,animation-delay 走 --ua-duration 系派生);菜单/下拉项 nth-child 交错入场(1-8 项、20ms 步进、超出即正常出现);3 处私写缓动归入令牌(必要时新增 --ua-ease-spring 回弹档);全部带 prefers-reduced-motion 兜底;npm run check 全绿。",
"status": "done",
"notes": "动效编排:私写缓动 3 处归零(ease-out/spring 归档),modal/drawer 内容分层入场(fast 档错峰),select/menu 弹层子项 nth-child 2..8 交错(--ua-duration-step 0.02s 派生),专块 reduced-motion 兜底;commit 91aa304,check 全绿;QA 复核定稿:分层入场 delay 由 fast(150ms) 收到微步 3 档(60ms),reduced-motion 兜底并入文件末尾 !important 清单(原局部 RM 块被源序反超失效),交错第 8 项起锁定同档保证单调,fix(ui) 0821286"
},
{
"id": "g201",
"title": "焦点可达性::focus-visible 区分键盘与鼠标",
"motivation": "焦点环统一挂在 :focus 上——鼠标点击按钮/页签/菜单项也会出环,键盘用户才需要的视觉信号变成了鼠标用户的视觉噪音,这是「看起来不专业」的隐性来源。",
"acceptance": "按钮/页签/菜单项/分页等点击型控件焦点环改挂 :focus-visible(键盘导航才出环);输入类控件维持 :focus(输入态需要常驻环);触屏/老浏览器降级到现状不劣化;npm run check 全绿。",
"status": "done",
"notes": "焦点可达性:清点确认点击型控件已无裸 :focus 残留(38 处 focus-visible/7 处输入类保留);修「吞环」3 处——tab/option/menu-item 键焦改内嵌描边(下拉面板滚动会裁外环;菜单面板不裁切,密集列表用内嵌描边更整齐);commit e64f72b,check 全绿;fix(ui) 0821286 同步更正归因注释"
},
{
"id": "g202",
"title": "按下态可见性修正:surface 系改以 surface 为基底",
"motivation": "QA 数值实测:surface-2(#f6f6f6) 以 bg 为基底,落在 .ua-select-pop/.ua-menu-pop 的 surface(#f6f7f9) 底上对比仅 1.008/1.009,肉眼不可见;且 :active 特异性覆盖 :hover 的 tint-accent-weak(对比 1.077/1.102),按下反而丢失淡蓝高亮,反馈方向是反的。.ua-option:active 覆盖 .ua-option-selected、.ua-tab:active 覆盖 .ua-tab-active 同理(按下选中项淡底短暂丢失)。",
"acceptance": "方案(择一或组合):surface-2/3/inset 改为相对 var(--ua-color-surface) 混色(需重验所有消费点与视觉基线);或 option/menu-item/tab 的 :active 改用 surface-3 或 tint-accent 保持可见;option-selected/tab-active 的按下态保底不丢选中语义;npm run check 全绿 + QA 数值复核按下对比 ≥1.05。",
"status": "done",
"notes": "QA R2 证据:亮暗各测;附带收尾项——三参 bg 变体 4 处(ui.css 804/842/1056/1490,accent 8~10% 混 bg)与 tint-accent 视觉等价可并入;menu-item 等 transition 的字面 ease 改 var(--ua-ease)(O3 风格债);落地:option/menu-item/tab 按下态改 tint-accent(对比 1.154/1.189 ≥1.05,选中项按下不丢语义);tag-info/tag.is-checked/step-done/toast-info 四处 accent 混 bg 并入 tint-accent;transition/animation 字面 ease 全量换令牌(ease-in-out 保留:无限循环动画无对应令牌,改曲线属视觉变更超范围)"
},
{
"id": "g203",
"title": "文档站亮暗主题切换,demo 支持 ?theme= 参数",
"motivation": "文档站 index.html 只有系统级暗色(prefers-color-scheme),无法手动切换;人工审阅 qa/stable 时想对照暗色观感只能改系统设置。demo 页已有 data-theme 全套暗色,但文档站 iframe 引用 demo.html 时无法联动主题。",
"acceptance": "1) 文档站页头新增主题切换按钮:亮/暗/跟随系统三态持久到 localStorage,CSS 用 :root[data-theme=dark] 显式块 + 媒体查询 :not([data-theme=light]) 兜底;2) demo 页初始化优先读 URL 参数 ?theme=dark|light(文档站 iframe 联动入口),其次 localStorage,最后系统;3) 切换按钮时所有 iframe.demo 带参重载实现联动;4) npm run docs 重新生成产物,npm run check 全绿。",
"domain": "tooling",
"status": "done",
"notes": "落地:docs 模板加显式暗色块(:root[data-theme=dark] + 媒体查询 :not([data-theme=light]))与页头切换按钮(localStorage 持久);demo 主题初始化优先读 ?theme= 参数;切换时 iframe.demo 带参重载联动。产物(apps/docs/* 与 demo dist)不入库由 npm run docs 本地生成;apps/docs/demo.html 须先 npm run demo 再 npm run docs,顺序反了拷到旧产物。"
},
{
"id": "g204",
"title": "textarea 支持 autosize 自适应内容高度",
"motivation": "多行输入在评论/描述场景下固定行数体验差:内容一行时下方大片空白,内容超长时内部滚动突兀。主流组件库标配 autosize,本库 textarea 缺失。",
"acceptance": "1) TextareaProps 新增 autosize?: boolean | { minRows?: number; maxRows?: number },true 跟随内容涨落,对象态钳最小/最大行高,超 maxRows 内部滚动;2) 输入与外部写回信号都触发重算;3) manifest 文档 props 表补 autosize 行,npm run docs 同步 llms-full.txt;4) 样式零新增硬编码(高度走 JS 内联,行高从 computed style 推导),npm run check 全绿。",
"domain": "tune",
"status": "done",
"notes": "落地:autosize 布尔/对象双态,高度与 min/max 行钳制在统一 frame 里由 computed style 推导(line-height 为 normal 按 1.5 倍字号退化),scrollHeight 盒模型补偿含边框;输入与外部写回信号都重算;resize 置 none。"
},
{
"id": "g205",
"title": "textField 支持密码框(password 显隐切换)",
"motivation": "登录/改密场景需要密码输入,当前只能裸 input type=password,没有显隐切换,用户无法核对输入内容。",
"acceptance": "1) TextFieldProps 新增 password?: boolean,开启后原生 type=password + 内置「显示/隐藏」切换按钮(aria-label 同步);2) 显隐按钮与 clearable/字数统计共用后置附加组不互斥;3) manifest 文档 props 表补 password 行,npm run docs 同步;4) 复用既有 clear 按钮样式零新增 CSS,npm run check 全绿。",
"domain": "tune",
"status": "done",
"notes": "落地:el.type 原生打点,显隐按钮复用 ua-input-clear 样式零新增 CSS,文案即状态(按钮永远写下一步动作),aria-label 同步;与 clearable/字数统计共用后置附加组不互斥。"
},
{
"id": "g206",
"title": "rate 支持 allowClear 清除评分",
"motivation": "评错了想撤销只能逐星点回 1 星,无法清零;主流组件库 rate 均有 allowClear。",
"acceptance": "1) RateProps 新增 allowClear?: boolean,再次点击当前分值(含半星精确相等)清零,键盘路径不受影响;2) aria-valuemin 随 allowClear 变 0/1;3) manifest props 表补行并 npm run docs 同步;4) npm run check 全绿。",
"domain": "tune",
"status": "done",
"notes": "落地:emit 加 allowZero 放行口,点击命中值与当前值精确相等即清零;aria-valuemin 随 allowClear 0/1 切换,读屏语义一致。"
},
{
"id": "g207",
"title": "progress 支持 format 自定义文本",
"motivation": "上传场景要显示「12.4 MB / 30 MB」而非百分比,当前进度文本写死取整百分号,线/环形都不能定制。",
"acceptance": "1) ProgressProps 新增 format?: (v: number) => string,传入则线/环形文本均用其返回值替代「N%」;2) 缺省行为不变;3) manifest props 表补行并 npm run docs 同步;4) npm run check 全绿。",
"domain": "tune",
"status": "done",
"notes": "落地:textOf 统一文本出口(format 优先,缺省取整百分号),线性与环形两条路径共用,环形 aria-label 同步走 textOf;ring 签名追加 textOf 参数零破坏。"
},
{
"id": "g208",
"title": "multiSelect 支持 clearable 一键清空",
"motivation": "select 已有 clearable,多选选了一堆标签要逐个点 × 撤销;datePicker/treeSelect 亦缺,本轮先补 multiSelect(改动面最小)。",
"acceptance": "1) MultiSelectProps 新增 clearable?: boolean,有值时根右缘显示清空钮(复用 .ua-select-clear 样式,右缘用 --ua-space-2 内收);2) 清空后写信号+回调并保持焦点;3) manifest props 表补行并 npm run docs 同步;4) 样式零新增字面量(audit:visual 基线仍 0),npm run check 全绿。",
"domain": "tune",
"status": "done",
"notes": "落地:复用 .ua-select-clear 样式(仅 right 用 --ua-space-2 内收,零新增字面量),clearable 未开不挂载、有值才显示,清空 stopPropagation 防误展开并保持输入焦点。"
},
{
"id": "g209",
"title": "演示页补齐本轮新参数的现场演示",
"motivation": "g205-g208 新增 password / autosize / allowClear / clearable 均无演示,人工审阅 qa/stable 时在 demo 看不到新能力。",
"acceptance": "1) sec-bind 加密码输入演示(password + clearable);2) sec-form 加 autosize 多行演示(1-4 行钳制);3) sec-rate 加 allowClear 可清除演示;4) sec-multiselect 既有实例开 clearable;5) 各 section 描述同步提及;npm run check 全绿。",
"domain": "tooling",
"status": "done",
"notes": "落地:sec-bind 密码框、sec-form autosize 钳制、sec-rate allowClear 行、sec-multiselect 开 clearable,各 section 描述同步。"
},
{
"id": "g210",
"title": "滚动条令牌化与暗色适配",
"motivation": "13 个滚动容器(数据表、下拉弹层、抽屉、穿梭框、级联、命令面板等)全靠浏览器原生滚动条,Windows 下是十余 px 的灰白粗条,暗色主题里成了一条亮带;滚动条颜色也没有令牌可循,属于视觉地基里最后一块没人管的地带。",
"acceptance": "tokens.css 与 tokens.json 新增 --ua-color-scrollbar / --ua-color-scrollbar-hover 并带 [data-theme='dark'] 覆盖;ui.css 为全部滚动容器统一 scrollbar-width: thin + scrollbar-color,WebKit 伪元素做胶囊拇指与悬停加深、轨道透明;样式只消费 --ua-* 令牌,npm run audit:visual 各类计数不上升;暗色下拇指可见、右下角不留白块;npm run check 全绿。",
"status": "done",
"notes": "tokens 新增 --ua-color-scrollbar(0.22/0.24) 与 --ua-color-scrollbar-hover(0.38/0.42),tokens.css 与 tokens.json 同步;ui.css 末尾新增滚动条段:13 个滚动容器统一 scrollbar-width:thin + scrollbar-color 走令牌,WebKit 伪元素做 8px 胶囊拇指、轨道与右下角透明、悬停加深带 --ua-duration-fast 过渡;仅用 inline-size/block-size 定粗细以避开 height 字面量口径,audit:visual 十一类保持 0 处;llms-full.txt 经 npm run docs 重生成",
"domain": "visual"
},
{
"id": "g211",
"title": "verify 增加令牌双源一致性门禁",
"motivation": "颜色令牌要在两处登记:tokens.css 定义真实 CSS 变量、tokens.json 供文档站色板与 Agent 文档使用;两处靠人手同步,只改一处时文档色板会凭空多出或漏掉一个色块,没有任何门禁能发现(g210 新增滚动条令牌时就靠人工两处对齐)。",
"acceptance": "ua verify 新增 checkTokenParity:tokens.json 的 color 键按 camelCase → kebab 折算成 --ua-color-* 后,与 tokens.css :root 集合双向查漏(JSON 多出 / CSS 多出都报错),并逐项比对亮色值与 [data-theme='dark'] 暗色值;负向自检(改坏一处值、删掉一个键)必须报错并指出变量名与两处取值;AGENTS.md 的 verify 职责描述同步;npm run check 全绿。",
"status": "done",
"notes": "cmdVerify 接入 checkTokenParity:camelCase → kebab(含数字段 surface2 → surface-2)折算后双向查名,命中则比对 root 亮值与 dark 暗值,报错直接点名变量与两侧取值;接在 checkDarkTokens 之后、llms-full 之前;verify OK 汇总文案补「tokens.json ↔ tokens.css 名字与值双源一致」,AGENTS.md 同步该职责与「只改一处会凭空多/少色块」的动机;负向自检两种破坏(改值 0.22→0.99、删 scrollbarHover 键)各自被拦下并给出精确变量名",
"domain": "tooling"
},
{
"id": "g212",
"title": "tabs 溢出时把激活页签拉回可视区",
"motivation": "页签条是 overflow-x: auto 的滚动容器,但组件从不滚动它:页签多到溢出后,程序改 active 信号(或编辑器的「打开下一个文件」这类场景)会把激活页签留在视口外,用户看不到当前在哪一页,指示条也跑到看不见的地方。",
"acceptance": "tabs 在同步激活态时检测激活页签是否被裁出页签条视口,被裁才滚(就近对齐、两侧留白),已完整可见时不动手;只滚 .ua-tabs-head 本身,不带动页面纵向滚动;系统开启 prefers-reduced-motion 时用 auto 直接跳位;demo 增加八个页签的窄容器现场演示;manifest 补一条手把手说明;npm run check 全绿。",
"status": "done",
"notes": "tabs.ts 新增 revealActive:先判 scrollWidth > clientWidth(无溢出直接返回),再判激活外壳是否完整落在 [scrollLeft, scrollLeft+clientWidth] 内,被裁才滚且就近对齐(左裁补 left-8、右裁补 right-clientWidth+8),目标钳在 [0, max];只调 head.scrollTo,不走 scrollIntoView 以免带动页面;behavior 依 prefers-reduced-motion 选 smooth/auto;sync 里指示条摆位后调用,effect 与 trackSize 两条路径都覆盖;manifest 补一条手把手(含「已可见不动手」与降动效说明),demo 加 320px 窄容器 + 八页签 + 当前值回显",
"domain": "tune"
},
{
"id": "g213",
"title": "新增 timePicker 时间选择组件",
"motivation": "datePicker 只覆盖日期维度,一天内的时刻(预约、值班、定时任务)没有可用组件;原生 input[type=time] 在各浏览器外观不一、步长与暗色都不可控,与自绘弹层的既有约定也不一致。",
"acceptance": "新增 timePicker(props):时 / 分两列自绘弹层,value 信号双向绑定 HH:mm,minuteStep 可配(缺省 5),当前值所在格高亮并滚到列中部,↑↓/Home/End 列内导航、Esc / 点外部收起且归还焦点;走 overlay 的 Esc 栈与 bindOutside、flip 做空间翻转;样式只消费 --ua-* 令牌,禁止新增硬编码阴影/时长/字号,npm run audit:visual 各类计数不增;10 处落点(实现 / types / index / css / manifest exports / manifest docs / demo / README 清单与分组 / sync-counts / docs)齐全;npm run check 全绿。",
"status": "done",
"notes": "timePicker.ts 113 行:pad 提为模块级纯函数(避免 const TDZ),commit 只在值变化时派发 onChange;列内导航 navigate 复用于时/分两列(↑↓ 环形、Home/End 直达);reveal 用 offsetTop 算 scrollTop 让激活格落到列中部,不走 scrollIntoView 以免带动页面;复用 overlay 的 bindOutside / pushOverlay 与 position 的 flip;ui.css 新增 .ua-timepicker-* 段全部消费令牌(control-h-md / radius-md / shadow-2 / duration-fast / z-dropdown / tint-accent),带 prefers-reduced-motion 兜底,audit:visual 十一类仍为 0;manifest exports 72 条 + docs.components 70 项(props 与 types 双向过 verify),demo 增 sec-timepicker(受控 15 分钟步长 + 禁用态 + 当前值回显),README 清单与分组补名,sync-counts 三处计数 69→70,docs 重生成",
"domain": "new"
},
{
"id": "g214",
"title": "浮层入场动效收敛到单一 keyframes",
"motivation": "七个弹层(级联 / 自动补全 / 日期 / 颜色 / 多选 / @提及 / 树选择)各复制了一份「淡入 + 上移 4px」的 keyframes,名字各异、缓动一半写成裸 ease;更糟的是弹层翻到触发器上方时动画仍自上而下,方向与位置相反,能明显看出「从错的那边滑进来」。",
"acceptance": "删除 7 份重复 keyframes,统一为 ua-pop-drop(自上而下 4px)与 ua-pop-rise(自下而上,供翻转态);引用处时长与缓动全部走 --ua-duration-fast / --ua-ease-out;.is-flip 的五个弹层补 animation-name 覆盖;ui.css 内不再出现 *.ua-*-in 自造入场动画;npm run audit:visual 各类计数不增;manifest intro 补一条「新弹层怎么写入场」的约定并修正组件数;npm run check 全绿。",
"status": "done",
"notes": "ui.css 删除 ua-cascader-in / ua-ac-in / ua-dp-in / ua-cp-in / ua-ms-in / ua-mentions-in / ua-treeselect-in 七份 keyframes(它们内容完全相同:淡入 + translateY(-4px)),新增 @keyframes ua-pop-drop(-4px 淡入)与 ua-pop-rise(+4px,供翻转态),7 处引用统一为 animation: ua-pop-drop var(--ua-duration-fast) var(--ua-ease-out) both(原先 5 处裸 ease、2 处 --ua-ease 且缺 both);末尾补 5 条 .is-flip 的 animation-name 覆盖(cascader / autocomplete / datepicker / colorpicker / multiselect,特异性 0,3,0 稳压 0,2,0);reduced-motion 各块原样保留;manifest intro 修正「28 个 DOM 组件」陈旧计数并补「新弹层怎么写入场、为什么不要自造 keyframes」;audit:visual 十一类维持 0",
"domain": "visual"
},
{
"id": "g215",
"title": "配方 13:日期 + 时间组合预约时点",
"motivation": "timePicker 落地后,「日期 + 时刻」是最常见的组合场景,但两个组件各写一段、拼装与校验放在哪一层没有范例;Agent 拿到 datePicker 与 timePicker 两个独立组件时容易写出「一个信号装完整时点」的错配。",
"acceptance": "recipes.md 新增配方 13(编号连续、含代码块与要点),代码里真实用上 datePicker / timePicker / form / formItem / toast,并说明两段各管一段、拼装交给 computed、约束优先于校验、半截状态由 required 兜底;ua recipes 13 能读出来且识别到组件;npm run check 全绿。",
"status": "done",
"notes": "recipes.md 新增配方 13(共 13 条):datePicker(min 限制今天) + timePicker(minuteStep 15) 各绑一个信号,at 由 computed 拼装,form 的 required 拦半截状态,onSubmit 走 toast;要点五条覆盖「两段各管一段 / 时间给初值日期留空 / 约束优先于校验 / 半截状态交 required / 区间怎么拼」;ua recipes 13 能识别 datePicker、form、timePicker、toast 四个组件;verify 的配方结构校验(编号连续 + 代码块 + 要点 + 至少一个组件 id)通过",
"domain": "tooling"
},
{
"id": "g216",
"title": "timePicker 支持 clearable 清空",
"motivation": "时间一旦选中就无法回到「未选」态:用户想撤销时只能重选一个假值;select 与 multiSelect 早已有 clearable 与 Delete/Backspace 等价键,timePicker 缺这一环,同一个表单里交互不一致。",
"acceptance": "timePicker 新增 clearable 入参:有值时触发器右侧出现 ×(不进 Tab 序、不与 ▾ 重叠),聚焦触发器按 Delete / Backspace 也能清空,清空写回空串并触发 onChange,同时收起弹层;样式只消费 --ua-* 令牌(含行高走 --ua-leading-none),npm run audit:visual 计数维持 0;types 与 manifest props / steps 同步;demo 演示清空;npm run check 全绿。",
"status": "done",
"notes": "timePicker.ts 138 行:clear() 只在有值时派发(避免空回调噪音),clearBtn 走 tabindex -1 不进 Tab 序、hidden 由 effect 跟 value 走;root 加 has-clear 类给触发器补右内距,× 用 right: --ua-space-6 让到 ▾ 左侧不重叠;root keydown 接管收起态的 Backspace/Delete(展开时按键归列内导航);types/manifest props(6 项)与 steps(7 条)同步,demo 首个实例开 clearable 并演示清空提示;踩坑:line-height: 1 被 audit:visual 的 leading-literal 拦下(基线 0),改走 --ua-leading-none 后十一类仍为 0",
"domain": "tune"
},
{
"id": "g217",
"title": "frame 内核冒烟 + effect 注销与重追踪修复",
"motivation": "70 个组件全部跑在 @ui-arch/frame 上,但 smoke 只覆盖 table-core:信号 / effect / computed 的语义改动没有任何自动化拦网。首次补测即抓到两个真缺陷——effect 返回的注销函数是空实现(卸载后仍被信号唤醒),且重跑不清理上一轮依赖(条件分支换走后旧信号继续唤它)。",
"acceptance": "新增 packages/core/frame/scripts/smoke.mjs(14 条断言:读写与同值短路 / 注销后不再重跑 / 条件分支重追踪 / computed 派生与链式 / 多订阅者扇出),跑在 dist 上不依赖 DOM;修 signal.ts 的 effect:注销真的摘订阅、重跑前清旧订阅;npm run smoke 串起 frame + table-core 两段;AGENTS.md 说明 smoke 覆盖范围;npm run check 全绿。",
"status": "done",
"notes": "signal.ts 引入 activeDeps(当前 effect 订阅到的信号订阅集集合):信号读值在收集 active 的同时登记自己那本 subs,effect 每次重跑先遍历 deps 摘掉旧订阅再 clear(条件分支换走后不再被旧信号唤),注销函数置 stopped 标志并逐个 subs.delete(run)(原先只留空注释);computed 未改,靠 effect 自动受益;新增 packages/core/frame/scripts/smoke.mjs 14 条断言(含负向:注销后不重跑、切分支后旧依赖静默、扇出时注销一个不影响另一个),首跑即 5 红(其中 1 红是我把 3×2 写成 30 的断言错误,已改 6),修完 14/14 绿;npm run smoke 改为 frame + table-core 两段串跑,AGENTS 门禁段补两段断言数量与用途",
"domain": "tooling"
},
{
"id": "g218",
"title": "timePicker 未选值时两列停在此刻",
"motivation": "没选过值时打开弹层,时列与分列都停在 00:00,用户要从头往下翻到当前时刻;而「选一个今天稍后的时间」正是最常见的用法,默认落点应该就近。",
"acceptance": "reveal 增加 fallback 参数:有激活格仍滚激活格,无值时滚到当前小时 / 当前分钟档(分钟按 minuteStep 折算下标并钳在列长内);manifest 步骤文案同步;npm run check 全绿。",
"status": "done",
"notes": "reveal(col, cells, fallback) 三参:findIndex 到 is-active 就用它,否则退到 clamp(fallback, 0, len-1);打开时 fallback 取 new Date() 的 getHours() 与 floor(getMinutes()/step)(步长大于 1 时分列下标与分钟数不同,必须折算);manifest 步骤 2 同步「还没选过时停在此刻」;时间源用系统时钟不引额外状态,选中后仍以 value 为准",
"domain": "tune"
},
{
"id": "g219",
"title": "verify 增加重复 @keyframes 检测",
"motivation": "刚才把 7 份复制的浮层入场动画合并掉了,但没有门禁阻止下一个人再复制一份:帧体完全相同、只改名字的动画会让方向与时长各写各的(翻转态朝反方向入场就是这么来的),而 CSS 层面完全合法,review 也容易漏。",
"acceptance": "ua verify 新增 checkDuplicateKeyframes:对皮肤包样式表里每个 @keyframes 取帧体(剥注释、空白归一)比对,出现相同帧体即报错并指出两个动画名;负向自检(临时追加一份同体动画)必须报错;顺带清掉现存重复(ua-switch-spin 与 ua-spin 帧体相同,开关转圈改为复用 ua-spin);AGENTS.md 同步该职责;npm run check 全绿。",
"status": "done",
"notes": "checkDuplicateKeyframes 接在 checkSkinCss 之后(同走 skin 包样式表遍历):@keyframes 正则抠出「名字 + 帧体」,帧体剥 /* */ 注释、空白压成单空格后进 Map,撞车即报「A 与 B 帧体完全相同」;负向自检临时追加 ua-dup-test(同 rotate(360deg) 帧体)被拦下并点名 ua-spin,验证后移除(踩坑:/tmp 备份在跨命令时不稳,改用正则精确剥离测试块,git diff 确认只剩预期的 2 增 4 删);现存 ua-switch-spin 与 ua-spin 同体,删掉前者、开关 loading 复用 ua-spin;verify OK 汇总文案与 AGENTS 职责描述同步",
"domain": "tooling"
},
{
"id": "g220",
"title": "滚动条统一规则补齐新增滚动容器",
"motivation": "g210 把 13 个滚动容器纳入细条 + 令牌拇指的统一规则,但 g213 新加的 timePicker 两列(overflow-y: auto)没进名单:同屏一个表单里的时 / 分列又回到 Windows 原生灰白粗条,暗色下尤其刺眼。手工维护的选择器清单本身也容易漏。",
"acceptance": "用脚本枚举 ui.css 里所有 overflow: auto|scroll 的 .ua-* 容器,逐一核对是否在滚动条统一规则内,漏项补齐(本次为 .ua-timepicker-col);补完覆盖数为 0 漏项;样式仍全部消费令牌,npm run audit:visual 十一类计数维持 0;npm run check 全绿。",
"status": "done",
"notes": "g220 的代码提交(1ac8bae)与本次留痕分两次落地:因上一轮提交后直接进入下一轮,漏了置 done 与留痕,收尾时按 backlog ↔ 留痕交叉校验才暴露(该条目在 backlog 仍 open)。修复内容:用括号配平脚本枚举 ui.css 中全部 overflow: auto|scroll 的 .ua-* 容器(14 个),逐条与 scrollbar-width 统一规则的选择器清单比对,唯一漏项 .ua-timepicker-col 补入——它正是 g213 新增时被忘掉的;audit:visual 十一类维持 0。教训:每轮「代码提交 → 置 done → 留痕 → 推送」四步要连着做完再开下一轮",
"domain": "visual"
},
{
"id": "g221",
"title": "verify 校验滚动容器是否都进细条规则",
"motivation": "滚动条统一规则是一串手写选择器,新增滚动容器(这次是 timePicker 两列)必然被忘掉,而症状只在暗色下才明显;靠人眼比对 14 个容器与一串选择器不现实。",
"acceptance": "ua verify 新增 checkScrollCoverage:括号配平扫出所有含 overflow: auto|scroll 的 .ua-* 单类规则,逐个核对是否出现在带 scrollbar-width 的统一规则里,漏项报出类名与实际后果;负向自检(从规则里摘掉 .ua-timepicker-col)必须报错;AGENTS.md 与 verify 汇总文案同步;npm run check 全绿。",
"status": "done",
"notes": "checkScrollCoverage:先剥注释再做括号配平扫描(正则切不开 @media 里的嵌套花括号),逐条记录选择器与规则体;含 overflow(-x/-y): auto|scroll 的单类 .ua-* 入容器集,再取体内含 scrollbar-width 的统一规则反查,漏项报「暗色下会露出原生亮条」;当前 14 个容器 14 个覆盖、0 漏;负向自检摘掉 .ua-timepicker-col 被精确点名(还原时踩坑:同名字符串 replace 命中了 .ua-cmdk-list 的另一条规则,把 .ua-timepicker-col 误并进它的 flex 规则,已用 Edit 精确改回并靠 git diff 确认 ui.css 与 HEAD 完全一致);verify 汇总文案与 AGENTS 职责同步",
"domain": "tooling"
},
{
"id": "g222",
"title": "timePicker 支持 size 高度档位",
"motivation": "textField / select / inputNumber 都有 sm·md·lg 三档,timePicker 缺这一环:与小号输入框并排时它仍占 34px,一排控件高低不齐,只能靠外层包一层 hack。",
"acceptance": "timePicker 新增 size?: 'sm' | 'md' | 'lg',触发器加 is-<size> 类,高度并入文件顶部「控件高度三档」统一规则(--ua-control-h-*),组件段不再重复声明高度;types 与 manifest props / steps 同步;demo 演示小号;npm run check 全绿。",
"status": "done",
"notes": "ui.css 顶部三处档位选择器各并进 .ua-timepicker-trigger(md / is-sm / is-lg),组件段原本写死的 height: var(--ua-control-h-md) 删掉,改为注释说明「高度由顶部三档统一给」——同一件事只留一个来源;TS 侧 class 按 select 的写法拼 is-${size}(缺省 md 不挂类);types 增 size 字段、manifest props 7 项 + steps 8 条,demo 加 320px 内的「小号」实例;check 全绿,audit 十一类仍 0",
"domain": "tune"
},
{
"id": "g223",
"title": "timePicker 非档值按最近档对齐",
"motivation": "外部传进来的值不一定落在 minuteStep 档上(09:07 配 15 分钟档很常见,历史数据或后端下发都可能):此时分列没有任何高亮格,滚动锚点还错跑到「此刻」,用户看到的是一个「有值却什么都没选中」的弹层。",
"acceptance": "新增 nearestMinute 助手:值不在档上时取最近档,作为分列高亮与滚动锚点;打开时锚点优先跟随值(有值滚值、无值滚此刻);点最近档即写回对齐后的值,触发器在用户重新选之前照常显示原值;manifest 步骤说明该行为;npm run check 全绿。",
"status": "done",
"notes": "nearestMinute(mm) 取 Math.round(mm/step) 下标并钳到列尾,空值/NaN 直接返回空串;column 内引入 activeText():时列精确比、分列走最近档,is-active 与 aria-selected 同源(原先两处各写一遍比较);打开时锚点改为「有值滚值(时取 Number(hh)、分取 nearest 的下标)、无值滚此刻」;点最近档 commit 时按当前 hh + 档值写回,触发器在用户重选前仍显示原值;manifest steps 补一条说明该行为;check 全绿、audit 十一类 0(踩坑:bash -e 内联脚本里的反引号被命令替换吃掉,改用 Edit 工具精改模板字符串)",
"domain": "tune"
},
{
"id": "g224",
"title": "timePicker 收起态 ↑↓ 微调时间",
"motivation": "表单里最常做的是「往后挪 15 分钟」这种微调,现在必须开弹层、找到分列、点格子、再收起;inputNumber 早就有 ↑↓ 步进,时间选择却没有对应的键盘手感。",
"acceptance": "收起态聚焦触发器时 ↑↓ 按 minuteStep 加减、Shift + ↑↓ 按小时加减,跨 00:00 / 23:59 环形回绕,写回与收起态清空共用同一套 keydown;展开态按键仍归列内导航;manifest 步骤说明;npm run check 全绿。",
"status": "done",
"notes": "root 的 keydown 收成三段:展开态直接让位给列内导航 → 收起态 clearable 时接管 Backspace/Delete → 有值时接管 ↑↓(delta = ±(shiftKey ? 60 : step),总分钟数 +1440 取模实现环形),写回走同一个 commit(值没变不派发);timePicker.ts 159 行仍在 200 行门禁内;manifest steps 补第 9 条说明步长与 Shift 语义",
"domain": "tune"
},
{
"id": "g225",
"title": "时间选择两列之间补分界线与统一列宽",
"motivation": "时列与分列之间只有一点 gap,两列数字挨在一起像一列 24 行;列宽又随内容浮动,触发器 116px 而弹层宽窄不定,展开时弹层与触发器边缘对不齐。",
"acceptance": "弹层补 min-width(与触发器最小宽度同档)、单元格补 min-width 与居中(两位数字纵向对齐)、第二列加 1px --ua-border-subtle 分界线与左侧内距;只用令牌,npm run audit:visual 十一类计数维持 0;npm run check 全绿。",
"status": "done",
"notes": ".ua-timepicker-pop 补 min-width: 132px(触发器 min-width 116 + 两侧内距,展开时左右边缘对齐);.ua-timepicker-cell 补 min-width: 44px 与 text-align: center(24 与 05 两位数纵向对齐);第二列用 .ua-timepicker-col + .ua-timepicker-col 加 margin/padding 与 border-left: 1px var(--ua-border-subtle) 分界;新声明全部并回原有选择器块(不新起重复块),audit:visual 十一类仍 0,verify 的滚动容器覆盖校验通过",
"domain": "visual"
},
{
"id": "g226",
"title": "配方通用约定补三条单一来源规则",
"motivation": "本轮踩到的三类重复(同一段动效各写一份、新增滚动容器漏进滚动条名单、颜色令牌只改 tokens.css)都已有 verify 拦网,但写组件的人不知道这回事;把结论写进「通用约定」,比藏在门禁报错里更容易被读到。",
"acceptance": "recipes.md 的《通用约定》补三条:动效统一 ua-pop-drop / ua-pop-rise 与令牌、新增滚动容器要进 scrollbar-width 名单、新增颜色令牌要 tokens.css 与 tokens.json 双写;仍然保持「先查令牌再写样式」等既有条目不变;npm run check 全绿。",
"status": "done",
"notes": "recipes.md 末尾三条新约定各自点出「违反时哪道门禁会报」:g214 收敛的 ua-pop-drop / ua-pop-rise(对应 checkDuplicateKeyframes)、滚动容器进 scrollbar-width 名单(对应 checkScrollCoverage)、颜色令牌 tokens.css + tokens.json 双写(对应 checkTokenParity);llms-full 重生成,配方结构校验(13 条编号连续 + 通用约定原样保留)通过",
"domain": "tooling"
},
{
"id": "g227",
"title": "timePicker 打开后焦点进入时列",
"motivation": "弹层打开后焦点还留在触发器上:键盘用户按 Tab 会跳到页面下一个元素(弹层被跳过),列内的 ↑↓ 也用不上——能看见却进不去,是弹层类组件最常见的无障碍漏项。",
"acceptance": "打开弹层时把焦点送到时列的当前格(无值则首个),用 preventScroll 保住刚算好的列内滚动位置;Esc 收起后焦点回到触发器(既有行为不变);manifest 步骤说明;npm run check 全绿。",
"status": "done",
"notes": "打开 effect 的 rAF 里在 reveal 之后补焦点:优先时列 is-active 格、无值退到首格,focus({ preventScroll: true }) 避免浏览器把刚算好的列内居中位置重新滚掉;收起路径未改(Esc 与选完分都 trigger.focus());manifest steps 补第 10 条;check 全绿",
"domain": "tune"
},
{
"id": "g228",
"title": "ua doctor 环境自检命令",
"motivation": "AGENTS.md 要求新 Agent 先跑通环境再干活,但「环境是否就绪」目前只能靠跑一次完整 npm run check 反推(几分钟)。缺一条秒级的自检命令,把 node 版本、依赖安装、git 钩子、.ua 元数据可解析性一次性检完。",
"acceptance": "新增 `ua doctor`:逐项检查 node >=18、pnpm-lock 与 node_modules 存在、commit-msg 钩子在位、.ua/backlog.json 与 .ua/index.json 可解析、iterations.log 每行是合法 JSON;全部通过打印 pass 清单退出 0,任一失败打印该条原因退出 1;AGENTS.md 环境准备补一行;manifest docs 说明;npm run docs 重生成;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "新增 cmdDoctor:七项检查(node>=18、pnpm-lock、node_modules、commit-msg 钩子、backlog/index 可解析、留痕逐行 JSON),core.hooksPath 优先于默认 .git/hooks;负向自检确认污染留痕即 FAIL 退出 1;AGENTS.md 环境准备改为「先 doctor 再 check」;manifest usage/steps 同步;check 全绿"
},
{
"id": "g229",
"title": "混色浓度令牌化:color-mix 百分比收敛为 --ua-alpha-* 六档",
"motivation": "ui.css 里 44 处 color-mix 的混入浓度散着 13 档百分比(10/12/16/18/20/24/30/40/45/55/65/70/80),同一「淡底」「半透描边」各组件各写各的,观感不齐也没法一眼对齐;且是继字号/时长/阴影/圆角之后最后一块没进棘轮的视觉字面量。",
"acceptance": "tokens 新增 --ua-alpha-soft/tint/line/mid/strong/heavy 六档(10/18/30/45/55/70%),tokens.json 同步 alpha 组;ui.css 全部 color-mix 百分比换令牌(就近收敛,Δ≤6),最后一处 rgba 白色条纹并进 accent-contrast + tint;audit:visual 新增 mix-literal 类目并棘轮到 0,负向自检确认新增字面量即红;门禁全绿。",
"status": "done",
"domain": "visual",
"notes": "tokens 新增 --ua-alpha-* 六档(10/18/30/45/55/70%),13 档字面值就近收敛(12→10、16/20/24→18、40→45、65/80→70,Δ≤6),44 处 color-mix 全量令牌化,最后一处 rgba 白纹并入 accent-contrast+tint;visual-rules 新增 mix-literal 类目(extractMixes 配平括号提取、渐变停靠位与几何百分比豁免、嵌套逐层计)并接入迁移脚本同一张表;baseline 补键 0;负向自检确认 33% 字面量即红;tint-accent 亮色定义改从 --ua-alpha-soft 派生;check 全绿(视觉回归抽查清单六组件段落留待人工过目——淡底档 Δ≤2 对比度方向不变)"
},
{
"id": "g230",
"title": "demo 页级命令面板:Ctrl+K 跳转任意演示区块",
"motivation": "演示页 63 个区块,侧边目录要滚到侧栏再过滤;组件库自己的 commandPalette 就是为这种高频跳转生的——演示页应当自己吃狗粮,也让「多实例只有一个注册快捷键」的约定有现场示范。",
"acceptance": "demo 挂全局 commandPalette(hotkey 默认注册 Ctrl+K),items 从 section.sec 反推(与侧边目录同源),onSelect 平滑滚动到区块;原 commandPalette 演示卡片实例改 hotkey:false(按钮可开,卡片文案补一句多实例约定说明);npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "demoTour 后挂全局 commandPalette:items 从 section.sec 反推(与目录同源),keywords 带去前缀锚点 id,onSelect scrollIntoView 平滑跳转;原演示卡实例 hotkey:false 并补多实例约定文案;check 全绿"
},
{
"id": "g231",
"title": "ua search 覆盖配方域(标题/组件/要点三层)",
"motivation": "Agent 找「空状态怎么拼」「长表单怎么锁」这类跨组件诉求时,组件搜索只能靠参数说明间接命中;配方标题与要点才是这类知识的直接载体,搜不到等于 recipes 白写一半。",
"acceptance": "cmdSearch 同时搜组件与配方(配方标题 3 / 组件 2 / 要点 1 权重,多词最弱档口径与组件一致),命中行给出 `ua recipes <编号>` 指路;--json 增加 recipes 数组带 matched 字段;MCP search_components 返回体同步加 recipes;AGENTS/manifest 的覆盖面描述补「配方」;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "cmdSearch 增加配方域(recipeMatch:标题 3/组件 2/要点 1,多词最弱档口径与组件一致),命中行给 ua recipes <编号> 指路;--json 改为 {components, recipes} 两数组各带 matched;MCP search_components 返回体同步加 recipes 且描述更新;AGENTS/manifest 覆盖面描述补配方;实测「吸底」组件零命中时配方仍可达;check 全绿"
},
{
"id": "g232",
"title": "ua doctor 补 git 身份检查",
"motivation": "全新环境 git 未配 user.name/user.email 时 commit 直接被拒;doctor 的职责就是把「能干活」一次检完,漏了这项等于七项全过还是提交不了。",
"acceptance": "doctor 新增第 8 项:git config user.name 与 user.email 均非空才 pass(spawnSync 实测,任一为空 FAIL 并提示配置命令);AGENTS/manifest 步骤文案的「七项」表述如出现则同步;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "doctor 第 5 项起插入 git 身份检查(spawnSync 实测 user.name/user.email,空则 FAIL 并给配置提示),8 项全过;AGENTS/manifest 无「七项」硬表述无需改;check 全绿"
},
{
"id": "g233",
"title": "位移量令牌化:transform 位移字面量收敛为 --ua-translate-* 档位并纳入审计口径",
"motivation": "ui.css 还有 23 处 transform 的 px 位移字面量(hover 上浮 1/2px、弹层入场 4/6/8/10px、滑入 12px、箭头偏移 3px),是最后一个未令牌化的视觉维度;audit:visual 十二类口径都不扫 transform,新写组件的位移值仍会各自为政,与颜色/间距的治理水平脱节。",
"acceptance": "ui.css 内 transform 的 px 位移全部改写为 --ua-translate-* 档位(负号用 calc(-1 * var(...)) 表达,10px 就近收敛到 8 档,百分比居中位移豁免);tokens.css/tokens.json 双源登记七档;audit:visual 新增 translate-literal 类别并 --baseline 归零;npm run check 全绿。",
"status": "done",
"domain": "visual",
"notes": "audit:visual 新增 translate-literal 第十三类(KIND_META + SCAN_PROPS 加 transform + detect/migrate 双口径),TRANSLATE_TOKENS 八档映射(10px 收敛到 8 档);迁移脚本自动改写 ui.css 33 处位移(负号 calc(-1 * var(...)),百分比与 0 豁免);tokens.css/tokens.json 双源登记 --ua-translate-1..12;AGENTS「五类」与 cli manifest「十二组」表述同步为十三,npm run docs 重建 llms-full 与文档站;基线重写后全 0,npm run check 全绿"
},
{
"id": "g234",
"title": "新增 pinInput 验证码输入组件(组件 70→71)",
"motivation": "表单域缺验证码 / 短信码输入这类 N 格单字符控件,业务只能用普通 textField 凑合,无进格、无粘贴分发、无 one-time-code 自动填充。",
"acceptance": "N 格单字符数字输入:输入自动进格、退格回删(空格回删上一格)、←/→ 移格、聚焦全选覆盖、粘贴自动分发(整段从首格、片段从当前格);value 信号双向绑定,mask 密文 + one-time-code,disabled 布尔/信号;样式只消费 --ua-* 令牌,禁止新增硬编码阴影/时长/字号;十处落点齐全,npm run check 全绿。",
"status": "done",
"domain": "new",
"notes": "组件 70→71;格子建一次不重渲染(焦点不丢),外部同步走单 effect(同值不写防打断输入法);样式复用 .ua-input 基类 + .ua-pin-cell 覆盖,零新增视觉字面量(audit 全 0);demo 锚点须全小写(verify 锚点正则只认 sec-[a-z0-9-],sec-pinInput 首跑踩坑改 sec-pininput);manifest/docs/README/llms-full 十处落点齐,check 全绿"
},
{
"id": "g235",
"title": "新增 copyButton 复制按钮组件(组件 71→72)",
"motivation": "文档站 / 演示页 / 业务表单都高频需要「一键复制」,此前只能各写一份 navigator.clipboard 样板并各自处理降级。",
"acceptance": "点击写入剪贴板并短暂显示已复制文案(2s 还原);text 支持 string 或函数(点击瞬间取实时值);clipboard API 不可用时降级 execCommand;variant/size/disabled 沿用按钮口径;样式复用 .ua-btn 零新增字面量;十处落点齐全,npm run check 全绿。",
"status": "done",
"domain": "new",
"notes": "组件 71→72;复用 .ua-btn 类零新增 CSS;label 用 signal + h() 函数型子节点实现文案切换(ButtonProps.label 收窄为 string,不走组合改走直接构造避免动既有类型口径);execCommand 降级用隐藏 textarea 临时选区;check 全绿"
},
{
"id": "g236",
"title": "新增 countdown 倒计时组件(组件 72→73)",
"motivation": "验证码重发 / 秒杀 / 会话过期这类倒计时高频出现,此前只能业务自写 setInterval;与 pinInput 组合即成完整的验证码收发场景。",
"acceptance": "秒级递减到 0 自动停表并触发 onFinish;缺省 mm:ss 两位补零,format 可自定义;返回句柄 start/stop/reset(已归零 start 重开一轮,reset 复位停表);running 缺省 true;样式只消费 --ua-* 令牌;十处落点齐全,npm run check 全绿。",
"status": "done",
"domain": "new",
"notes": "组件 72→73;remain 信号 + h() 函数型子节点响应式刷新文本(role=timer);句柄 stop 幂等(timer 句柄 0 判停);CSS 只加 .ua-countdown 数字等宽一行;demo 与 pinInput 同页组成验证码场景;check 全绿"
},
{
"id": "g237",
"title": "新增 kbd 键盘按键标记组件(组件 73→74)",
"motivation": "文档 / 空状态 / 提示文案里高频出现快捷键说明,此前只能裸文本,没有键帽视觉;demo 命令面板(Ctrl+K)等场景正缺这个配套展示件。",
"acceptance": "单键传字符串、组合键传数组以 + 连接(+ 对读屏 aria-hidden);键帽样式(底边加厚一线立体感)只消费 --ua-* 令牌;十处落点齐全,npm run check 全绿。",
"status": "done",
"domain": "new",
"notes": "组件 73→74;实现 10 行(h() 纯声明式拼接,无状态);键帽高度走 --ua-control-h-sm 档、字号 sm 档,零新增视觉字面量;demo 与命令面板快捷键说明混排;check 全绿"
},
{
"id": "g238",
"title": "新增 highlight 关键词高亮组件(组件 74→75)",
"motivation": "搜索结果 / 补全列表 / 帮助文档都高频需要「命中片段高亮」,此前业务各自 indexOf 切串包 span,样式与刷新逻辑散落各处。",
"acceptance": "命中片段包进 mark(大小写不敏感、全部命中、首尾空白忽略);text / keyword 均接受信号响应式重画;空关键词原样展示;高亮淡底只消费 --ua-* 令牌;十处落点齐全,npm run check 全绿。",
"status": "done",
"domain": "new",
"notes": "组件 74→75;单 effect 追踪两个信号(render 内统一取值,依赖自动收集);淡底走警示黄 --ua-alpha-tint 混色 + mark 语义标签;demo 与 textField 联动演示实时高亮;check 全绿"
},
{
"id": "g239",
"title": "焦点描边令牌化:outline 43 处收敛为三档复合令牌并纳入审计口径",
"motivation": "ui.css 有 20 处 outline 焦点描边字面量(2px 实色外描边 / 1.5px 柔和内嵌 / alpha-mid 柔和外描边三种形态反复手写),是 box-shadow 焦点环之外的第二套焦点视觉,未入审计口径就还会漂移。",
"acceptance": "tokens.css/tokens.json 新增 --ua-focus-outline / -inset / -soft 三档(含暗色覆盖);audit:visual 新增 outline-literal 第十四类(outline: none 豁免);20 处迁移后基线归零;outline-offset 为逐控件几何微调不入表;npm run check 全绿。",
"status": "done",
"domain": "visual",
"notes": "迁移走 OUTLINE_TOKENS 整值精确匹配(squash 归一空白);2 处 alpha-mid 柔和描边归 --ua-focus-outline-soft;verify 拦下暗色覆盖缺失(三个新令牌补 [data-theme=dark] 同口径覆盖);AGENTS 审计类别数同步为十四;check 全绿"
},
{
"id": "g240",
"title": "button 增加 loading 态(既有组件打磨)",
"motivation": "异步提交按钮此前要业务自己拼「转圈 + 禁点 + 还原」三件事,switchButton 有 is-loading 而主按钮反而没有,口径不齐。",
"acceptance": "loading?: boolean | Signal<boolean>:加载中转圈(复用 ua-spin 帧体)并自动禁点,aria-busy 播报忙碌;文案位置不漂移(转圈非加载态不占位);prefers-reduced-motion 降级为静态环;manifest/docs/demo 同步;npm run check 全绿。",
"status": "done",
"domain": "tune",
"notes": "loading 布尔归一为信号走同一响应式路径;disabled 口径合并为「显式禁用或加载中」(bindProp 函数式属性);类名切换而非整棵重渲染(保焦点);CSS 复用 .ua-spinner 类 + .ua-btn-spin 尺寸覆盖,零新增字面量;check 全绿"
},
{
"id": "g241",
"title": "verify 增加演示锚点大写防护",
"motivation": "锚点集合用 /sec-[a-z0-9-]+/ 提取,大写锚点(如 sec-pinInput)会被静默吞掉,manifests 登记后误报「缺 section」,g234 实踩浪费一轮排查。",
"acceptance": "verify 校验 main.ts 中 /sec-[A-Za-z0-9-]+/ 命中含大写的锚点即报错并给出小写口径提示;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "在 cmdVerify 锚点集合构建处加大写扫描(正向提示改小写);当前工作区无违规锚点,check 全绿"
},
{
"id": "g242",
"title": "令牌对比度门禁 audit:contrast",
"motivation": "audit:visual 只管「没有硬编码字面量」,令牌本身的对比度无人把关:改一个颜色值就能让全站辅助文字掉到 4.4,亮底上人眼察觉不到,暗色更难发现(实测暗色主按钮反白仅 3.92、暗色强调文字压淡底 4.44 均不达标)。",
"acceptance": "新增 ua audit:contrast:解析 tokens.css 亮暗两套变量(含 var() 与 color-mix 求值、半透明底先压到页面底),16 组文字×底色配对按 WCAG 公式算比值,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6)退出码 1,--json 出逐对明细;不达标的三条配对通过修令牌达标;npm run check 全绿。",
"status": "done",
"domain": "visual",
"notes": "亮色 text-muted #6b7280→#676d78(凹陷底 4.39→4.72)、暗色 accent #378add→#4a9aeb(反白 3.92→4.77、强调文字压淡底 4.44→5.26),16 对全绿最低 4.58;接入 check 尾步 + AGENTS/README/ITERATION/CLI 清单同步"
},
{
"id": "g243",
"title": "文档站与演示页外壳配色从令牌派生",
"motivation": "两处外壳 chrome 各自手写十六进制(文档站 --bg/--accent、演示页 --page-*),改暗色 accent 后页面外壳仍留着旧蓝,与组件色各显示一种;这类漂移手写清单永远追不上令牌。",
"acceptance": "新增 packages/tools/cli/lib/shell-palette.mjs 作为唯一派生点(缺键退回内置默认,tokens.json 缺失不炸),文档站 renderDocs 与 apps/demo/build.mjs 都改为调用它;构建产物里的 --accent/--muted/--page-* 必须等于 tokens.json 对应值,源码不再出现外壳手写色值;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "产物核对:docs --accent 亮 #185fa5 / 暗 #4a9aeb,demo --page-muted 亮 #676d78 / 暗 #888780,两处产物 378add 残留归零(tokens.css 注释里的旧值描述一并洗掉,避免 grep 误判)"
},
{
"id": "g244",
"title": "新增 inputTag 标签输入(组件 76)",
"motivation": "筛选条件、收件人、技能标签这类「输一串值」的输入只能靠 callers 自己拿 textField + tag 手拼,成标签 / 去重 / 退格删末个 / 满额禁输四件事每处各写一遍。",
"acceptance": "新增 inputTag:回车或逗号成标签、chip 可删、空框退格删末个、失焦自动入列,value 信号双向绑定,max 满额禁输、maxLength 截断、allowDuplicate 可开;样式只复用 .ua-input-group / .ua-tag,禁止新增硬编码阴影 / 时长 / 字号(audit:visual 保持 0);manifest + demo + README + 计数 + llms-full 全同步,npm run check 全绿。",
"status": "done",
"domain": "new",
"notes": "组件 75→76;样式仅 4 条排布规则(flex 换行 / 内距 / 收缩 / 溢出换行),全部走 --ua-space-* 与 --ua-control-h-md;单文件 78 行"
},
{
"id": "g245",
"title": "高对比偏好令牌 + 对比度门禁升到四套主题",
"motivation": "系统「提高对比度」偏好下组件毫无变化:边框仍是 0.15 透明黑、辅助文字仍是浅灰,低视力用户开了开关等于没开;而对比度门禁只验常规两套主题,覆盖不到这条路径。",
"acceptance": "tokens.css 末尾(媒体查询需后置胜出)加 @media (prefers-contrast: more):亮/暗两态分别加深 border / 滚动条、把 text-muted 升到次级档、text-faint 升到原辅助档,只改已有变量不新增名字(verify 双源核对保持一一对应);audit:contrast 解析该媒体块,把配对表在亮/暗 × 常规/高对比四套主题下各算一遍,附 --json 明细;npm run check 全绿。",
"status": "done",
"domain": "visual",
"notes": "高对比下辅助文字 4.85→7.49(亮)/4.72→8.71(暗)、三级文字 2.64→4.83,四套主题 16 对全绿;负向自检(把 text-faint 调亮)确认新主题会被判不达标"
},
{
"id": "g246",
"title": "ua info 支持 --md 输出 Markdown 片段",
"motivation": "Agent 或人要把单个组件的参数表贴进文档 / issue / 回复时,只能从文本版参数表里手抄对齐空格,抄完还要重新排表格;清单数据本来就在手里。",
"acceptance": "ua info <组件id> --md 输出标题 + 参数表 + 用法步骤 + 示例代码块,类型里的竖线自动转义(联合类型不截断表格),尾部保留「出现在配方」指路;文本版与 --json 输出不变,用法串与 AGENTS/清单文档同步;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "与文本版共用 formatComponent 的数据源,只多一个渲染函数;select 的 'a'|'b' 联合类型已实测转义为 \\| 不破表"
},
{
"id": "g247",
"title": "表格支持整体换数据源(setRows + 信号数据)",
"motivation": "表内核在创建时把 data 定死,筛选条一改就得整表重建:用户已排的序、已翻的页、已勾的行全丢。真实业务里「条件变了换结果集」是常态,此前只能靠调用方自己 effect + replaceChildren 绕。",
"acceptance": "table-core 新增 setRows(引用未变不重算;按 rowKey 剔除新数据里已消失的选择项;排序 / 筛选 / 页码保持),dataTable 的 data 接受 T[] | Signal<T[]>(信号变化自动换源),新增冒烟断言覆盖换源与失效行剔除;dataTable 单文件仍 ≤200 行(数据源逻辑抽到 tableDataSource.ts);manifest / demo / AGENTS 冒烟条数同步;npm run check 全绿。",
"status": "done",
"domain": "tune",
"notes": "冒烟 21→26 条;dataTable 曾到 205 行触发 style 门禁,抽表数据源助手 + 合并 RowKey 类型导入 + 压掉 banner 注释后 199 行;demo 在 sec-inputtag 里联动演示(删标签即换源,页码排序不重置)"
},
{
"id": "g248",
"title": "配方 14:标签筛选条 → 表格联动",
"motivation": "多条件筛选 + 表格是数据类页面最常见的一屏,此前只有「表格自带单列关键字筛选」的说明,没人讲清多条件该在信号层派生、以及换源为什么不能重建表格。",
"acceptance": "recipes.md 追加配方 14(编号连续、含可运行骨架与要点),骨架只用已注册组件(inputTag / select / button / space / dataTable),要点覆盖「筛选在信号层做」「传信号不传数组」「且/或语义」「空状态复用」;ua recipes 14 能解析出组件清单;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "顺手清掉骨架里未使用的 toast 导入与 tag 变量名(避免配方组件清单误报 tag);与 g244/g247 两轮新能力形成闭环"
},
{
"id": "g249",
"title": "doctor 增加命令面与门禁链自洽自检",
"motivation": "今天新增 audit:contrast 与 ua info --md 时都要手工记得同步 usage 串、CLI 清单、check 链三处,漏一处没人报错——跑 ua recipes 时才发现 usage 里根本没有 recipes 这一项。这类「文档承诺 ≠ 实现」只能靠机器查。",
"acceptance": "ua doctor 从 8 项升到 11 项:①usage 串 ↔ dispatch case 双向覆盖(选项组里的竖线不能被当命令分隔)②package.json 的 check 链覆盖全部门禁步骤 ③CLI 清单 api.exports 声明的命令都有 case;负向自检(删 check 链里的 audit:contrast)确认会 FAIL;doctor 全过,npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "新检查当场抓到真问题:usage 串漏了 recipes;解析先剔除 [--json|--baseline] 选项组再切 |,否则选项竖线被当分隔符;顺带把 usage 的 info 补上 [--json|--md]"
},
{
"id": "g250",
"title": "动效兜底门禁 audit:motion + 补两处漏网无限动画",
"motivation": "audit:visual 只管字面量、verify 只管时长令牌,没人管「无限循环动画有没有 reduced-motion 降级」。实测转圈(.ua-spinner)与不确定态进度条两处根本没有降级——开了「减少动效」的用户满屏仍在转,属于前庭敏感的硬伤。",
"acceptance": "新增 ua audit:motion:自写 CSS 扫描器按大括号层级切规则并标记是否处于 prefers-reduced-motion 媒体块内,把所有带 infinite 的动画逐个核对是否被 animation: none 关掉,缺兜底退出码 1、--json 出逐条明细;补掉两处漏网;接入 check 链与 doctor 的必需步骤表;npm run check 全绿。",
"status": "done",
"domain": "visual",
"notes": "扫描器两版写错才收敛:①前导注释混进选择器 ②子块字符归属父块导致选择器为空;最终用「尾缓冲在 ; / } 处清空」方案,936 条规则、49 条降级规则全部正确识别;负向自检(删 spinner 兜底)确认报「缺兜底 1 处」"
},
{
"id": "g251",
"title": "ua tokens --unused 反向收口令牌面",
"motivation": "令牌只增不减:112 个变量里没人知道哪几个其实没组件在用,加令牌时也不敢删旧的。正向查询(按名找)解决不了「谁没用」,需要一个反向视图。",
"acceptance": "ua tokens --unused 列出皮肤样式表与 tokens.css 自身都没引用的令牌(自身派生引用算消费,否则 --ua-color-highlight 会被误报),--json 出 total + unused 数组;用法串与清单文档同步;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "首版只扫 ui.css 把 highlight 误报为闲置(实际被 --ua-shadow-highlight 引用),补上 tokens.css 作为消费面后剩 3 个真闲置:surface-3 / text-secondary / font-size-xl;text-secondary 仍被 audit:contrast 配对表引用,故只报不删"
},
{
"id": "g252",
"title": "MCP get_component 支持 format: md",
"motivation": "g246 给 CLI 加了 --md,但 Agent 侧主力入口是 MCP:get_component 仍只回一大坨 JSON,Agent 拿到要自己拼参数表才能贴给人看。",
"acceptance": "MCP get_component 入参增加 format 枚举 json|md(缺省 json 行为不变);md 时返回与 ua info --md 同款 Markdown 片段(标题 + 参数表 + 用法 + 示例 + 配方指路);字符串结果在 tools/call 层原样作为 text 返回(不再 JSON 转义);npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "实测 stdio 往返:text 字段直接是可读 Markdown;json 分支未变(老客户端无感)"
},
{
"id": "g253",
"title": "文档站组件卡加「复制参数表」按钮",
"motivation": "人类读文档站想引用某个组件的参数表时只能手抄或截图;CLI/MCP 侧已经有 Markdown 口径(g246/g252),文档站却还用不上。",
"acceptance": "文档站每个组件卡标题右侧加「复制参数表」按钮:点击生成与 ua info --md 同款 Markdown(标题 + 参数表 + 用法 + 示例,竖线转义),成功文案短暂变「已复制」;无 clipboard 权限时退回 textarea + execCommand;npm run check 全绿。",
"status": "done",
"domain": "tooling",
"notes": "生成器本身是模板串,MD 里的三反引号围栏改用 String.fromCharCode(96) 拼,避免把模板串提前截断(第一版直接写裸反引号导致 ua.js 语法错误,已回滚重写);顺手用 node 取生成产物里的 mdOf 做了一次实际输出核对(含联合类型竖线转义)"
},
{
"id": "g254",
"title": "换源后越界页码回写内核状态(settle 钩子)",
"motivation": "g247 引入 setRows 换源后暴露:数据从 3 页缩到 1 页时,getPageInfo() 钳制成 1、state.pagination.page 仍是 3——视图和状态各说各话。更糟的是数据恢复后表格会「记得」失效的旧页码直接跳回第 3 页,用户会以为数据被吞了。",
"acceptance": "TablePlugin 增加可选 settle 钩子(派生后收敛自身状态切片,无需修正须原样返回同一引用);core 在派生末尾跑一轮 settle 并只走 onStateChange、不广播订阅(避免读取期重入);pagination 用新鲜快照回写生效页,快照过期不写;smoke 补 5 条断言覆盖「缩水→回写 / 恢复→不跳页 / 跳越界→回写末页 / 快照语义不变」;npm run check 全绿。",
"status": "done",
"domain": "core",
"notes": "只回写不改派生:pageInfo 仍是纯派生量,越界钳制口径不变(dataTable 的受控页码桥接因此不再需要额外钳制);快照新鲜度用「本次 derive 是否按当前 state 完整跑过」判断,paged=false(getAllRows 导出)会把 fresh 置假,避免拿旧快照写错页码"
},
{
"id": "g255",
"title": "减少动效下归零全站悬停位移",
"motivation": "audit:motion 只盯 infinite 循环动画,没人管「一动就位移」的那一类。实测皮肤样式表里 14 处悬停 / 按压位移(btn 上浮、page-btn 回缩、image 放大、backtop 抬升、colorpicker 格子浮起…)在 prefers-reduced-motion 下一处降级都没有——开了「减少动效」的用户每次划鼠标都被推一下,比循环动画更常见也更难躲。",
"acceptance": "ui.css 末尾新增一条 ua- 类通配的减少动效规则(:hover / :active 下 transform 与 translate 归零,颜色 / 描边 / 阴影过渡保留);audit:motion 扩为两类必查,悬停位移逐条核对是否有对应归零规则(或全局通配兜底),缺一条退出码 1、--json 增加 shift 段;负向自检删掉规则确认报「未归零 8 处」;ui.css 仍零硬编码字面量、对比度与其余门禁全绿。",
"status": "done",
"domain": "visual",
"notes": "兜底选全局通配而不是逐组件补:14 处分散在 10 多个组件,逐个补降级等于给每个新组件留一道必踩的坑;用 !important 压源码序更早的 :hover transform,同时只归零 transform/translate,颜色与阴影过渡照旧,避免把状态反馈一起砍掉。顺带把 AGENTS 里 smoke 条数(26 条)与 g254 的实际 31 条对齐"
},
{
"id": "g256",
"title": "数据表格分页条加每页条数切换(抽出 tablePager)",
"motivation": "分页条只有「上一页 / 下一页」,用户想看更多行只能去改代码里的 pageSize。而 pageSize 是建表时定死的,最需要它的「先粗看全量、再细看几行」恰恰是运行期行为。另外上一页在第 1 页仍可点、下一页在末页仍可点,点了只是派发一次无效页码靠内核钳制兜底。",
"acceptance": "新增 tablePager 模块(上一页 / 页码播报 / 下一页 / 每页条数下拉),dataTable 改为调用它并把行数压回 200 行以内;首末页对应按钮置灰;下拉用 select 自绘弹层、走 --ua-* 令牌零硬编码;调用方传入的 pageSize 不在候选里时自动补进候选(避免下拉显示空白);manifest 文档补一条步骤;npm run check 全绿。",
"status": "done",
"domain": "tune",
"notes": "抽出而非就地加:dataTable 当时 199 行正卡在 CODING 的单文件上限,再加十行就破线;分页条本来只依赖 info/total/loading 三个读取器,独立成模块后 dataTable 掉到 199 行以内且不再 import button。页大小变更走内核 setPageSize(自带回第 1 页语义),下拉值由 effect 从内核快照同步,双向不打架"
},
{
"id": "g257",
"title": "分页条窄屏自动换行",
"motivation": "g256 给分页条加了每页条数下拉后,「上一页 / 页码 / 下一页 / 5 条/页」四个控件挤在一行且不换行:侧栏或窄窗口下直接横向溢出,末尾的下拉被裁掉一半——恰恰是新加的那个控件最先看不见。",
"acceptance": "分页条允许换行且换行后行距紧一档(列距不变);页码信息与页大小下拉用 auto 外边距把控件分组靠右,换行到第二行也不贴边;只改布局不加字面量,npm run audit:visual 计数不涨、npm run check 全绿。",
"status": "done",
"domain": "visual",
"notes": "选择 flex-wrap 而不是媒体查询:分页条容器宽度由表格宽度而非视口决定,媒体查询猜不准,让 flex 自己按可用空间折行更稳;行距用 gap 的两值写法,不引入新的间距令牌"
},
{
"id": "g258",
"title": "select 增加 ariaLabel 可访问名",
"motivation": "下拉触发器只显示当前值文案(「5 条/页」「全部」),没有可见 label 时读屏念出来只是一句没主语的量词,用户不知道自己在选什么。表格分页条正是这种场景:新加的页大小下拉既无 label 也无名字。",
"acceptance": "SelectProps 增加 ariaLabel(缺省不落属性),触发器写入 aria-label;表格分页条传「每页条数」;manifest 的 select 参数表与步骤同步(verify 会核对类型字段与文档一一对应);npm run check 全绿。",
"status": "done",
"domain": "tune",
"notes": "加在 select 而非只给分页条写死属性:凡是「工具栏 / 分页条 / 筛选器」这类无可见 label 的场合都需要同一个入口;用 ?? null 让 h() 在未传时整条属性不落地,避免输出空 aria-label(空名字比没名字更糟,读屏会念成空白)"
},
{
"id": "g259",
"title": "数据表格暴露 pageSizes 候选",
"motivation": "g256 的每页条数候选写死在分页条里(5/10/20),调用方想换成 10/25/50 只能改库源码——而每页条数恰恰是业务侧最常按数据密度调的参数。",
"acceptance": "DataTableProps 增加 pageSizes 并透传给分页条(缺省 [5, 10, 20],当前 pageSize 不在候选里自动补);manifest 参数表同步(verify 核对类型字段 ↔ 文档);dataTable 仍 ≤200 行;npm run check 全绿。",
"status": "done",
"domain": "tune",
"notes": "加参数后 dataTable 一度涨到 201 行被 style 门禁拦下(CODING 单文件上限):顺手合并了一处空行,把表上层桥接注释与 busy 读取器贴紧,语义分组没变"
},
{
"id": "g260",
"title": "配方 15:表格分页与独立分页器联动",
"motivation": "g254/g256/g259 把分页能力补齐(越界收敛、每页条数、条数候选),但配方库里没有一条讲「页码提到页面外、多个表格共用一个分页器」该怎么接——这正是受控 page 信号存在的理由,缺配方等于让使用者自己猜两边写回的顺序。",
"acceptance": "recipes.md 新增配方 15(编号连续、含可编译的 ts 代码块、代码块里认出已注册组件 id),讲清「一个页码只有单一来源」「改页大小别手动回第 1 页」「越界页码由内核收敛」「无可见 label 要传 ariaLabel」四条要点;npm run check(含 verify 的配方结构校验)全绿。",
"status": "done",
"domain": "tooling",
"notes": "配方放在「通用约定」之前、紧接配方 14,保证编号递增;要点里特意写了两条踩坑结论:业务层再手动 page(1) 会出现「先跳页再被推回」的闪动;外部联动不需要自己钳制页码,因为内核已把生效页回写进 state"
},
{
"id": "g261",
"title": "inputTag 删除 chip 后焦点归还输入框",
"motivation": "g244 的 inputTag 里,点 chip 上的 × 会连带把那个按钮本身删掉——焦点随之掉回 body。键盘用户删完一个标签想接着输入,得从头 Tab 回输入框;连续删多个标签时每删一个就丢一次焦点。",
"acceptance": "删除 chip 后把焦点交回输入框;manifest 的 inputTag 步骤同步这句行为(verify 核对文档与实现一致);npm run check 全绿。",
"status": "done",
"domain": "tune",
"notes": "焦点归还写在删除回调里而不是 render 末尾:render 是「按信号重画」,外部改信号(如重置筛选条)时不该抢焦点,只有用户亲手点 × 这一个入口需要接住"
},
{
"id": "g262",
"title": "对比度门禁补非文本配对(焦点描边 3:1)",
"motivation": "audit:contrast 只算文字配对,可键盘用户「焦点在哪」全靠焦点描边——它糊进背景时文字对比度再漂亮也没用。WCAG 1.4.11 对这类界面部件只要求 3:1,但之前没有任何门禁盯着它:把 accent 调浅一点,文字配对也许还过,焦点环已经没了。",
"acceptance": "配对表增加「焦点描边 / 页面底」「焦点描边 / 表面」两对,阈值 3(非文本档),亮/暗 × 常规/高对比四套各算;README、AGENTS、CLI 清单里 16 组的表述同步成 18 组并说明非文本档来源;npm run check 全绿。",
"status": "done",
"domain": "visual",
"notes": "配对表本来就是「设计契约」的写法(写死在代码里、加一对等于承认一种新组合),所以非文本档沿用同一张表、只给不同的 min,不另开一套配置;实测 light 6.52/6.09、dark 6.35/5.77,说明现役 accent 作焦点描边是够的——这条门禁的价值在「以后调浅时会被拦住」"
},
{
"id": "g263",
"title": "标签输入支持多值粘贴",
"motivation": "从表格一列、聊天记录或需求列表里粘「上海,北京,深圳」到标签框,此前只会整串变成一个 chip,用户还得逐个删掉重敲。而整段粘贴恰恰是标签类输入最常见的入口。",
"acceptance": "粘贴文本里出现半角/全角逗号、换行或制表符时自动拆分并逐个入列(各自过截断 / 去重 / 上限),单值粘贴保持浏览器默认行为;manifest 步骤同步;inputTag 仍 ≤200 行且 npm run check 全绿。",
"status": "done",
"domain": "tune",
"notes": "第一版把分隔符正则直接内联在监听器里,两次粘贴各写一份;改成顶部一个 SEP 常量后两处共用(test 与 split 同一语义),也顺手避开「正则带 g 标志导致 test 推进 lastIndex」的坑(不写 g)。写文件时用 Python 转义,\n 被吞成真换行导致 TS 语法错误,改用编辑器直接写入修好"
},
{
"id": "g264",
"title": "数据表格可自定义空状态文案",
"motivation": "空状态的文案被写死成「暂无数据」,可业务侧常要说清「为什么空」——「本月还没有工单」「筛选条件太窄,试试放宽时间」。有筛选词时的文案本身是好的(带关键词 + 一键清除),缺的只是「真没数据」那一句。",
"acceptance": "DataTableProps 增加 emptyText(缺省「暂无数据」),透传到空状态行;有筛选词时仍走「没有符合「x」的行」+ 清除按钮,两种语义不混用;manifest 参数表登记(verify 双向核对类型字段);dataTable 仍 ≤200 行、npm run check 全绿。",
"status": "done",
"domain": "tune",
"notes": "通过给 emptyRow 加第 4 个可选参数实现,调用处只多一个实参不新增行——dataTable 正卡在 199 行上限;参数默认值放函数签名而不是调用方,避免其它调用点(目前只有 dataTable)被动改"
},
{
"id": "g265",
"title": "新增布局骨架组件 layout",
"motivation": "76 个组件里没有「页面骨架」:想搭一个带页头 + 侧栏 + 内容 + 页脚的控制台界面,只能自己写 div 拼 flex,页头吸顶要重写 z-index、侧栏折叠要自己接信号、内容区滚动要自己补滚动条令牌——每个用库的人都要把同一段骨架重抄一遍。菜单(menu)、竖排锚点(anchor)、卡片(card)都在,缺的是把它们装起来的容器。",
"acceptance": "新增 layout(props, ...children):children 即内容区,header / sider / footer 三入参按需渲染(缺省不套空容器);siderWidth 缺省 220;collapsible 时页头左侧出开关、collapsed 信号双向绑定(外部给则不自管);sticky 让页头吸顶走 --ua-z-sticky;样式只消费 --ua-* 令牌,禁止新增硬编码阴影 / 时长 / 字号,内容区滚动条纳入统一细条规则;十处落点全同步(src / types / index / ui.css / manifest 双处 / demo / README / 计数 / docs),npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "骨架刻意不带圆角与投影——它是页面容器不是卡片,浮起感留给内容里的 card / modal;折叠态宽度用 .is-collapsed 的 width:0 + overflow hidden 压过内联展开宽度(宽度只有一个来源),aria-hidden 随折叠翻转,读屏不再进菜单;verify 的滚动条规则当场抓出漏网的内容区:新增滚动容器必须并入 scrollbar-width 统一规则,否则暗色下露原生亮条"
},
{
"id": "g266",
"title": "新增 JSON 树查看组件 jsonViewer",
"motivation": "接口联调、配置排查、日志排查都绕不开「看一段原始 JSON」:此前只能 JSON.stringify 后塞进 pre,一行到底、层级看不出来,长数据把页面撑穿。tree 是业务树的形状(勾选 / 异步加载),塞 JSON 会逼着给每个节点造一份包装对象,语义也对不上——需要的是纯只读的类型着色查看器。",
"acceptance": "新增 jsonViewer(props):value 接收任意值,对象 / 数组可展开收起、分支收起时显示「N 项 / N 个键」,叶子按 string/number/boolean/null 四类着色;depth 控制默认展开层数(缺省 2)、maxHeight 限高后在框内滚动并纳入统一细条规则、rootLabel / ariaLabel 可配;样式只消费 --ua-* 令牌,禁止新增硬编码阴影 / 时长 / 字号;十处落点全同步(src / types / index / ui.css / manifest 双处 / demo / README / 计数 / docs),npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "用 role=tree/treeitem + aria-expanded 表达折叠态,开关的 aria-label 带完整路径(response.data.items.0)——读屏能说出「展开的是哪一支」;收起用 hidden 而不是重建节点,折叠态天然保住;踩坑:h() 返回 HTMLElement,给变量标 HTMLDivElement 类型会 TS2741(缺 align),改成 as 断言;留痕 ts 必须 %:z(+08:00),%z 的 +0800 会被 lint-iterations 判违规"
},
{
"id": "g267",
"title": "补 Windows 强制色(forced-colors)高对比兜底",
"motivation": "库一直按 prefers-contrast 做高对比,但 Windows 上真正会被大量用户打开的是「强制颜色」(高对比主题):它用系统色板强制替换颜色、剥掉 box-shadow、把自绘底色抹平。结果恰恰是库最依赖的两件事失效——浮层靠阴影当边框、选中态靠淡色底表达。开高对比的用户看到的是:弹层与页面融成一片、下拉里分不清选了哪一项、骨架屏整块消失。",
"acceptance": "ui.css 末尾新增 @media (forced-colors: active) 兜底块:10 类靠阴影当边框的容器(card/modal/drawer/pop/menu/select-pop/json/tip/toast/layout-sider)补 1px CanvasText 真边框并清掉阴影;9 类控件(button/input/textarea/select-trigger/input-group/checkbox/switch/tag/badge)补描边;选中态(option/menu-item/tab/step)改用 Highlight 底 + HighlightText 字并 forced-color-adjust: none;骨架与占位走 GrayText。不写死任何色值(避免撞 border-literal / outline-literal 门禁),audit:visual 仍为 0;README 可访问性约定补一条;npm run check 全绿",
"status": "done",
"domain": "visual",
"notes": "焦点描边刻意不重写:全局 outline 的色值在强制色下会被系统自动强制,自己补一条 2px solid Highlight 反而会撞 outline-literal —— 门禁在这里正好挡住了「好心写死」。先做了焦点环覆盖率统计(78 个组件里 24 个有专属规则),发现 [class^=ua-]:focus-visible 已是全局兜底,于是放弃那条选题改做强制色:选题前先量缺口,比凭直觉补更重要。forced-color-adjust: none 只给选中态用——不关掉调整的话系统会把 Highlight 也归一到窗口底色"
},
{
"id": "g268",
"title": "verify 增加孤儿演示区块反向校验",
"motivation": "verify 一直只查正向:清单里的 demo 锚点必须在 demo 里存在。反过来没查——demo 里新加的 section 如果没人登记进清单,就成了一段「谁都指不到、删了也没人发现」的孤儿演示。实测真有:sec-alert 是 alert 的专属演示(四档语义色 + 可关闭 + banner),但清单里 alert.demo 还指着旧的聚合区块 sec-feedback,于是这段更完整的演示从文档站与 MCP 查询里彻底消失,谁也不知道它存在。",
"acceptance": "verify 新增反向校验:把 demo 里 section() 定义处的锚点(只认定义处,不认 href: '#sec-x' 这类引用)与全部组件 demo 字段求差,差集非空即报「孤儿演示」;修掉现存唯一一条(alert.demo → sec-alert,专属演示重新回到文档站与 MCP);AGENTS.md 的 verify 覆盖描述补这一条;npm run check 全绿",
"status": "done",
"domain": "tooling",
"notes": "口径踩坑:第一版直接用 verify 既有的全量锚点集合(match(/sec-[a-z0-9-]+/g)),结果把 anchor 演示里故意写的 href: '#sec-missing-zone'(演示「找不到区块自动跳过」)判成孤儿——正则必须只认 section( 定义处。另一坑:改 ua.js 时用 bash 双引号内联 node 写正则,反斜杠被吞成 /section(s*'.../ 直接语法错,整个 CLI 无法加载(docs 也静默失败),改用编辑器直接写正则"
},
{
"id": "g269",
"title": "新增文本差异对比组件 diffView",
"motivation": "审阅场景(配置改动、变更记录、纠错前后对比)需要「哪几行变了」的视图,此前只能把两段文本并排贴进 pre 让人眼比对。库里有 jsonViewer 看结构、highlight 标关键词,却没有行级差异:这是审阅链路里唯一缺的一环,而它恰恰最依赖「对齐」——人眼对不齐就等于没看。",
"acceptance": "新增 diffView(props):before / after 两份文本按行比对,剪公共前后缀后对中间段做 LCS,输出 same/add/del 三类行;增删行同时有底色与行首 + / − 符号两条通道(不只靠颜色),双行号列 user-select:none 不进复制内容;showStats 给 +N / −N,aria-label 自动播报同一组数字;中间段行数乘积 > 40 万时退化为整段替换(语义正确、不卡主线程);样式只消费 --ua-* 令牌(增删底色走 color-mix + --ua-alpha-tint),禁止新增硬编码阴影 / 时长 / 字号;十处落点全同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "对齐分三步:先剪公共前缀、再剪公共后缀、最后才 DP(dp[i][j] 从后往前填,回溯时按「删还是加」比较下一格长度),日常改两行只算中间几行,不会 2000×2000 全表;退化阈值用中间段乘积而不是总行数——总行数大但改动集中时仍然能逐行对齐。踩坑复现:用 bash 内联 node 生成 demo 里的 join(\n) 与嵌套引号,反斜杠被吞成真实换行,esbuild 报 Unterminated string literal,改用编辑器直接写入 + 双引号内层字符串"
},
{
"id": "g270",
"title": "新增代码块组件 codeBlock",
"motivation": "演示页与文档站里到处是「一段代码 + 一个复制按钮」:此前靠 h(pre) 手拼,行号、标题、复制各写一遍,行号还容易被一起选进剪贴板。库里有 copyButton(复制)与 diffView(对比),缺的正是最常用的「单向展示」那一块——它该把行号与复制都收进同一个组件,顺手保证复制出来的是纯原文。",
"acceptance": "新增 codeBlock(props):code 原文渲染为等宽多行,行号列缺省显示(单行自动省略)、user-select:none 不进复制;标题行 title 优先于 lang,两者与 copyable 全关时整条头部省掉;maxHeight 限高后在框内滚动并纳入统一细条规则;复制走 copyButton 且直接读 props.code(不从 DOM 取文本,杜绝行号混入);onCopy 回调透传;样式只消费 --ua-* 令牌,禁止新增硬编码阴影 / 时长 / 字号;十处落点全同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "行号用 aria-hidden 而不是 role=presentation:读屏读代码时不该被「1 2 3」打断;空行渲染成一个空格撑住行高,否则连续空行会把代码块压扁。踩坑:Write 工具写文件时把行尾注释的 ***/ 后面粘了个 </n> 残片,tsc 直接报语法错——新建组件后必须先 build 再过其余门禁;demo 里的嵌套引号改用 .mjs 脚本写(Write 工具落盘不转义),比 bash 内联 heredoc 稳定"
},
{
"id": "g271",
"title": "新增手写签名板组件 signaturePad",
"motivation": "审批 / 签收类表单最缺的一件控件是「手写签名」:没有它,这类场景只能整块绕开组件库,自己接 canvas。而自己接往往踩同一串坑——高分屏线条发虚(忘了按 DPR 放位图)、移动端一按就滚页(没关 touch-action)、窗口一变签名被清空(重设位图会清画布)。这些都该由库一次解决。",
"acceptance": "新增 signaturePad(props) 并返回 handle(el / clear / toDataURL / isEmpty):指针绘制 + setPointerCapture 跟手、位图按 DPR 放大、笔色读 canvas 的 color 走令牌、容器宽度变化经 trackSize 重算并在重设位图后按快照贴回笔迹、touch-action: none 独占手势;收笔写回 value 信号与 onChange,清空写空串并复位按钮态;disabled 支持布尔与信号;样式只消费 --ua-* 令牌,禁止新增硬编码阴影 / 时长 / 字号;十处落点全同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "返回句柄是本库既定模式(formItem / virtualList 同款),因为「有画布就有外部要的操作」:取图、清空、判空。三处状态(位图 / signed 信号 / value 信号)必须一次改完——clear() 里少写一处就会出现「画布清空但信号还是旧图」。踩坑:Write 工具连续三次只写进文件头部(把注释当全文提交),build 直接暴露;demo 里 IIFE 少一个右括号,esbuild 报 Expected ) but found ; —— 新建组件后先 build 再谈其余门禁,能省一轮全量 check"
},
{
"id": "g272",
"title": "视觉回归抽查清单扩到十一个组件并让下限自推导",
"motivation": "清单固定 six 组件是 10-23 定的,那时库只有 34 个组件、新增的多是表单小件。这两轮一口气加了 layout / jsonViewer / diffView / codeBlock / signaturePad 五类新形态(容器、数据展示、录入画布),它们恰恰是「令牌用对了但观感可能错」的高风险区——侧栏折叠抖动、JSON 竖线叠重、diff 行底色渗到行号列、代码块悬停残影、签名笔迹发灰,这些都没有抽查位。清单不覆盖,就等于默认它们不用人眼验收。",
"acceptance": "ITERATION.md 清单新增 layout / jsonViewer / diffView / codeBlock / signaturePad 五段,各 3-4 条、维度标签仍限五类;ua verify 的 REQUIRED 扩到十一段,MIN_TOTAL 改为 REQUIRED.length × MIN_PER_GROUP(33)由段数推导——以后加抽查位只需加段、不用手改总数;verify 成功语与 AGENTS.md、ITERATION.md 的「六段 / ≥18 条」表述同步为「十一段 / ≥33 条」;npm run check 全绿",
"status": "done",
"domain": "visual",
"notes": "下限从写死的 18 改成 段数 × 每条下限:这类「N 个组件」的计数一旦写死在门禁里就必然会漂——库自己已经在 verify 里靠 sync-counts 治过一次同样的病,这次直接把口径做成派生量。选题前先查了「焦点环覆盖」与「outline:none 是否配环」两处,均已被全局规则与逐组件 :focus-visible 覆盖,于是转向验收纪律本身:视觉门禁的分工是 audit:visual 管字面量、清单管观感,清单漏段等于放行"
},
{
"id": "g273",
"title": "新增区域加载遮罩组件 loading",
"motivation": "刷新一块内容(表格重取、卡片换页)时的等待反馈,库里只有两个原子:spinner(转圈)与 skeleton(骨架)。真实页面要的是「盖住这一块」——内容原地留位、操作被挡住、明确在等什么。此前只能各自写一个 position:absolute 遮罩,于是各处的半透明浓度、是否保留内容、快请求要不要闪各不相同,同一产品里三种等待观感。",
"acceptance": "新增 loading(props, ...children):children 为被遮挡内容,active 支持布尔与信号;默认 delay 120ms 出场、关闭立即收起(快请求不闪、慢请求必有反馈);内容只降透明度不隐藏(布局不跳),遮罩走 --ua-alpha-strong 混色、aria-busy 同步;text 同时作可见文案与读屏播报,spinnerSize / minHeight 可调;样式只消费 --ua-* 令牌,禁止新增硬编码阴影 / 时长 / 字号;十处落点全同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "出场延迟与关闭立刻收是一对:只做延迟会出现「关得也慢」,遮罩在数据回来后又挂 120ms 更像卡住;所以延迟只作用于「显示」这一个方向,paint(false) 直接执行。内容降透明度到 0.5 而不是隐藏:留位才能让遮罩收起时不跳动。踩坑:ButtonProps.label 已收窄为 string(g240 遗留),demo 里想传函数型文案会直接类型报错,改成固定文案 + 状态切换"
},
{
"id": "g274",
"title": "补三条新组件的组合配方(骨架 / 刷新遮罩 / 审阅面板)",
"motivation": "配方文件停在配方 15,而那 15 条写的都是老组件(表单、弹窗、表格分页)。本轮新加的 layout / loading / jsonViewer / diffView / codeBlock / signaturePad 在配方里一个字都没有——Agent 拿到单组件参数表能知道「怎么调」,却不知道「怎么拼」,于是很可能把 layout 当卡片用、把 diffView 当全文展示用。配方是唯一能表达「职责边界」的载体。",
"acceptance": "recipes.md 新增配方 16(layout + breadcrumb + menu + card 后台页骨架)、17(loading 包住 dataTable 的刷新遮罩)、18(codeBlock + diffView + copyButton + tag 的变更审阅面板),编号自 15 连续递增、每条都有代码块与 3 条要点、代码块内至少含一个已注册组件 id;要点写清职责边界(骨架不塞交互、遮罩不换空数据、diff 不代替全文);llms-full.txt 与文档站随 npm run docs 重生成;npm run check 全绿",
"status": "done",
"domain": "tooling",
"notes": "配方 16 里特意写了「不要再套一层 overflow:auto」——layout 内容区自己就是滚动容器且已在细条名单内,两层滚动会让吸顶页头与滚动条重复出现,这类「两个都对、拼起来错」的坑只能靠配方说明。配方 17 强调 finally 收遮罩:只写 try 分支的示例会诱导出「异常时遮罩永远挂着」的实现"
},
{
"id": "g275",
"title": "新增相对时间组件 relativeTime",
"motivation": "动态 / 评论 / 操作日志里最常出现的一列是「3 分钟前」。此前只能自己算差值再手拼文案,于是每个页面各写一套:有的不刷新(页面开着十分钟还显示「刚刚」)、有的把所有时间都渲染成「N 天前」(半年前的记录也这么写,读者还得自己换算)、有的遇到脏数据直接抛错让整列空白。这三件事都该由一个组件定死。",
"acceptance": "新增 relativeTime(props):value 接受 ISO 串 / 10 位秒戳 / 13 位毫秒戳 / Date;按 refresh(缺省 30s)自动刷新文案,超 threshold(缺省 7 天)改显示绝对日期;title 恒给精确到分钟的时间,datetime 属性给 ISO;非法时间显示 fallback(缺省 —)不抛错;计时器在节点脱离文档后自断;样式只消费 --ua-* 令牌(等宽数字 + nowrap),禁止新增硬编码阴影 / 时长 / 字号;十处落点全同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "10 位 / 13 位时间戳按数量级判秒与毫秒(<1e12 视为秒)——真按位数数会踩「2033 年以后的秒级戳突破 1e10」这类边界,用数量级更稳。计时器自断用的是 watch.ts 同款「isConnected 为假就 clearInterval」写法,列表反复挂载也不会累积。等宽数字(tabular-nums)是时间列不抖动的关键,肉眼很难描述但一排「3 分钟前 / 11 小时前」混排时立刻能看出来"
},
{
"id": "g276",
"title": "新增迷你趋势线组件 sparkline",
"motivation": "仪表盘卡片里「一个数 + 一条走向」是最常见的信息单元,库里有 statistic 装主数值,却没有表达走向的图形——于是各页面要么塞一张完整图表(坐标轴、图例、交互一整套,跟卡片格子完全不匹配),要么干脆不显示趋势。迷你趋势线该只有折线本身:无坐标轴、无交互、尺寸听调用方。",
"acceptance": "新增 sparkline(props):values 序列经 max/min 归一后按 viewBox 等比缩放,首末点贴边、内缩 2px 防裁切;area 缺省填充(低透明度)、marker 缺省标记末点;全等值时走中线不除零;非有限数先剔除;少于两点返回空图不画假横线;颜色全走 currentColor(线 / 面积 / 圆点),父级改 color 即换色;样式只消费 --ua-* 令牌,禁止新增硬编码阴影 / 时长 / 字号;十处落点全同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "svg 元素必须 createElementNS,h() 只能造 HTML——写了个 svgEl 小工具统一 setAttribute,避免每处重复三行。全等值除零保护写成 max - min || span 兜底 1:所有点落中线才是正确观感,直接除会得到 Infinity 把折线扔出画布。踩坑:statistic.value 是 Signal<number> 而非数字,demo 里直接传 1284 会类型报错——演示代码也算源码,得按真实 API 写。踩坑:写 backlog 备注时把表达式放进反引号、整串又用 bash 双引号包着,反引号被当作命令替换执行掉了(备注里凭空少一句),改用脚本落盘。"
},
{
"id": "g277",
"title": "新增日历热力图组件 heatmap",
"motivation": "活跃度看板(提交、工单、登录)最直观的表达是「一年一张格子图」:一眼看出哪几天密集、哪几周空窗。此前库里没有任何日历型图形——calendar 是可选日期的交互组件,timeline 是一维事件流,都表达不了「二维时间密度」。于是这类页面只能引第三方图表库,为一张格子图背上整套坐标轴体系。",
"acceptance": "新增 heatmap(props):values 只给有数据的日期(缺失按 0 渲染),同日多条自动求和;末列对齐到本周(周一起算的完整七行),按最大值归一成四档浓度 + 零档;格子尺寸与网格行高同源(cellSize),悬停 title 给日期与数量、formatValue 可自定义;图例「少 → 多」五格与色阶同源;样式只消费 --ua-* 令牌(色阶走 color-mix + --ua-alpha-*),禁止新增硬编码阴影 / 时长 / 字号;十处落点全同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "格子的浓度是「相对」而不是绝对:按当批数据的最大值归一,所以它回答「哪天更密」,不回答「数值多高」——这点必须写进文档,否则会有人拿它当绝对值比较两张图。末列对齐用「今天 + 到本周日的天数」再回退 12 周,保证最后一列永远完整,不会因为今天是周三就少三格。踩坑复现:写 backlog 备注时把表达式放进反引号、整串又被 bash 双引号包住,反引号被当成命令替换执行、备注少一句(g276 已踩一次),这轮改成先把条目写成 JSON 文件再读,彻底绕开 shell 解释。"
},
{
"id": "g278",
"title": "热力图图例与网格尺寸同源,并把两个图表件纳入视觉抽查",
"motivation": "上一轮交付 heatmap 时图例色块走的是样式表里的 --ua-space-3(12px),网格格子走的是调用方内联的 cellSize(缺省 11px)——同一个小部件里两个尺寸来源,差 1px 在 11px 的格子上就是 9% 的体量差,图例看起来「比格子胖一圈」。同时 heatmap 与 sparkline 都是纯视觉件(颜色深浅 / 折线走向就是全部信息量),却没有视觉抽查位,改一个 alpha 档位没有任何人眼验收环节盯着。",
"acceptance": "heatmap 的图例色块改用与网格同一个 cell 值内联设置尺寸,样式表里删掉 .ua-heat-cell 的写死宽高(尺寸只留一个来源);ITERATION.md 抽查清单新增 heatmap / sparkline 两段(各 3 条、维度标签仍限五类),ua verify 的 REQUIRED 扩到十三段、下限仍由段数 × 3 推导(39);AGENTS.md 与 verify 成功语同步为十三段 / ≥39 条;npm run audit:visual 仍为 0 处、npm run check 全绿",
"status": "done",
"domain": "visual",
"notes": "这类「同一部件两处写尺寸」的问题靠肉眼很难定位(只会觉得图例有点不对),是靠读代码时发现「样式表写 12px、内联写 11px」才抓住的——派生量必须只有一个来源,与 CODING.md 的派生语义一致原则是同一件事。清单扩容依旧不手改总数:MIN_TOTAL 上一轮已改成段数 × 3,这轮只加两段就自动变 39。"
},
{
"id": "g279",
"title": "diffView 增加「忽略行内空白」模式",
"motivation": "真实审阅里最常见的「假差异」不是内容改了,而是格式化:换了个格式化器、把 4 空格改成 2 空格、行尾多了空格——diffView 会把这些全标成增删行,真正的逻辑改动被埋在几十行空白色块里。人眼在噪音里找信号必然漏,于是审阅退化成「大概看了下」。",
"acceptance": "DiffViewProps 增加 ignoreWhitespace(缺省 false):开启后比对键把行内连续空白压成一个空格并 trim,显示仍用原文(缩进不丢);LCS、公共前后缀、DP 全部走比对键,同一套口径不出现两处判定;统计条显示「已忽略行内空白差异」把口径写在脸上;manifest 参数表与步骤同步(verify 双向校验);演示加一组「只差缩进」的对照实例;npm run check 全绿",
"status": "done",
"domain": "tune",
"notes": "关键是把「显示文本」与「比对键」拆成两个字段(Row.text / Row.key),而不是在比较时临时归一:临时归一会出现「前缀比较用原文、中间段用归一」的两套口径,同一份数据两种结论。归一只影响比对,不影响展示——用户看到的仍是真实缩进,这决定了它是「过滤器」而不是「美化器」。提示文案必须出现在统计条里:静默忽略差异比不忽略更危险。"
},
{
"id": "g280",
"title": "formItem 支持条件显示(visible 信号)",
"motivation": "「选了『其他』才出现输入框」「选了企业才要税号」这类联动字段在任何真实表单里都有,此前调用方只能自己重建 items 数组:一旦重建,字段的校验结果、错误文案、焦点全部丢失,用户刚看到的红框会在切换选项时凭空消失。而如果只是把节点藏起来(不管校验),被藏起来的必填项又会永远拦住提交。两条路都错,缺的是「隐藏即退出校验」这一个语义。",
"acceptance": "FormItemProps 增加 visible(Signal<boolean>):为假时 el.hidden 隐藏并立即清掉错误态;validate 与 validateAsync 首行统一「隐藏即返回 null」,同步 / 异步两条路同一口径;manifest 的 form 参数表补(formItem)visible 与一条步骤(verify 双向校验 FormItemProps 字段);npm run check 全绿",
"status": "done",
"domain": "tune",
"notes": "隐藏时主动 paint(null) 而不是等下一次校验:否则用户切换选项的瞬间会留下一个「看不见的红框」,而它仍存在于 DOM 的错误区里。校验的早退放在函数首行而不是规则循环里,是为了让「隐藏 = 不参与」成为唯一判定点——两处各写一份条件迟早会漂。form 的参数表用(formItem)前缀登记子接口字段,是这套清单既有的约定,新增字段必须跟着加。"
},
{
"id": "g281",
"title": "抽出 diffKit 纯逻辑并给皮肤包加冒烟脚本",
"motivation": "g269 把行级差异算法直接写在 diffView 的渲染函数里,于是它有「三处前缀 / 后缀 / 中间段」这种一眼看不出对错的逻辑,却没有任何断言——改一个比较符肉眼完全看不出来,只有等某次审阅漏掉一行才可能发现。皮肤包此前冒烟覆盖为 0:frame 14 条、table-core 31 条,皮肤层全靠演示页人工看。",
"acceptance": "新增 packages/skin/ui/src/diffKit.ts(normalizeLine / toRows / alignMiddle / diffLines 四个纯函数 + DiffOp / DiffRow 类型 + DP_LIMIT 常量),diffView 只保留渲染;新增 packages/skin/ui/scripts/smoke.mjs 八条断言(相同文本全 same、纯增、纯删、尾部换行不算差异、忽略空白生效、不忽略时报差异、行内空白归一、超限退化后内容不丢),npm run smoke 串上第三段,SKIN SMOKE OK 是门禁的一部分;AGENTS.md 的 smoke 描述同步为「三段 / skin 8 条」;npm run check 全绿",
"status": "done",
"domain": "tooling",
"notes": "第 6 条「不忽略空白时同一对文本必须报差异」是对照组:只测「忽略后无差异」会被「函数永远返回空」的实现骗过——每个开关类特性都该配一条反向断言。冒烟直接 import dist/diffKit.js(构建产物),因此 npm run check 里 smoke 必须排在 build 之后,这一点由 check 链顺序保证。踩坑:style 门禁要求导出类型也带 /*** ***/ 注释,抽出模块时把注释写成 // 会直接红。"
},
{
"id": "g282",
"title": "codeBlock 增加长行折行开关",
"motivation": "代码块默认横向滚动是为了保住行号对齐,但这条缺省在两种场景里是错的:窄屏(手机 / 抽屉里)横向拖动读代码极其难用;长日志、SQL、base64 这类「一行几百字符」的内容横向滚动等于没法读。缺少折行开关,调用方只能自己在外面覆写 white-space,而这恰好会破坏行号列的表现。",
"acceptance": "CodeBlockProps 增加 wrap(缺省 false):为真时容器加 is-wrap,行按容器宽度折行(pre-wrap + break-word),行号列仍只占首行左侧;缺省行为(横向滚动)不变;manifest 参数表与一条步骤同步(verify 双向校验 CodeBlockProps 字段);演示里的代码块改为 wrap: true 展示折行;npm run check 全绿",
"status": "done",
"domain": "tune",
"notes": "折行与行号对齐是互斥的:折行后一个逻辑行占多行,行号列只会贴在最左一列上——所以这条不能做成缺省,必须让调用方明确选择「读得下」还是「对得齐」。CSS 用 pre-wrap 而不是 pre-line:缩进是代码语义的一部分,折行不能顺手把行首缩进吃掉。"
},
{
"id": "g283",
"title": "减少动效补齐第三类:旋转 / 缩放过渡",
"motivation": "audit:motion 只守两类——无限循环动画与悬停位移。可空间动效其实有三类:折叠箭头的 90° 旋转、按压缩放(scale)、抽屉滑入的 transform 过渡都属于第三类,它们在减少动效下仍会「动」,而前两类门禁完全看不见。g273 之前的 jsonViewer 折叠箭头就是典型:transition: rotate 一条都没被任何门禁盯住。",
"acceptance": "ui.css 末尾补一条通配降级:prefers-reduced-motion 下把 ua- 类的 transition-property 收窄成颜色类属性(color / background-color / border-color / box-shadow / opacity / fill / stroke),空间变换即时到位;audit:motion 增加第三段审计(识别 transition 里提到 rotate / scale / transform 的规则,要求存在通配 transition-property 收窄兜底),缺失即退出码 1 并列出缺口;报告与 --json 输出同步;AGENTS.md 的动效门禁说明补第三类;npm run check 全绿(当前 35 处全部覆盖)",
"status": "done",
"domain": "visual",
"notes": "关键取舍是「收窄过渡属性」而不是「关掉全部过渡」:颜色 / 阴影 / 透明度变化不产生空间位移,砍掉它们会把状态反馈一起砍没(用户看不到 hover 变色的反馈,反而更困惑)。审计的兜底形态也随之改成「存在一条通配 transition-property 规则即视为全站覆盖」,避免要求 35 处各自补一条——通配一条的好处是新增组件不必记得补。踩坑:给 formatMotionReport 加第三段时只改了函数体没改解构签名,运行时报 spatial is not defined——函数签名和返回值是一对,加字段两处都要动。"
},
{
"id": "g284",
"title": "新增环形进度组件 progressRing",
"motivation": "反馈类里只有线形 progress 与 spinner:仪表盘、完成度徽章这类场景通常要「一个圆环 + 中心百分比」,现有组件只能拿 SVG 手搓,结果是每个页面各搓一份、弧长算法与旋转原点还各不相同(默认从 3 点起步是最常见的错)。环形进度属于高频小件,应该有统一定义。",
"acceptance": "新增 progressRing(props):SVG 双圆(底环 + 进度弧)靠 stroke-dasharray 画弧,0 点固定在 12 点方向顺时针;value 支持数字与信号(信号变化自动重绘),越界自动夹到 0-100;showText=false 可只留环;role=progressbar + aria-valuenow 随值同步;样式只消费 --ua-* 令牌(弧色走 currentColor、底环走描边色),audit:visual 不新增字面量、audit:motion 的旋转过渡有减少动效兜底;manifest 双处 + demo sec-ring + README 清单与分组同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": "弧长靠 dasharray(画 c*pct/100,剩下留空)而不是 path 弧线命令:一条属性就能表达任意进度,省掉大弧标志位与起止角的三角计算。旋转原点必须给 rotate(-90 cx cy),否则默认从 3 点方向起步——这是手搓环最常翻的车。第一版把两个 circle 塞进了中间层 div 再用 querySelector 搬进 svg,DOM 上等于挂了两次;改成先 svg.append(track, arc) 再组容器。另外 transition 一开始写成 0.3s 字面量,被 audit:visual 的 duration-literal 抓住,改用 --ua-duration-slower。"
},
{
"id": "g285",
"title": "分页总量走信号:越界页码自动钳回父级",
"motivation": "pagination 的 total 是静态数字,于是「筛选后总量从 137 掉到 25」这类场景只能靠调用方自己算新页数:不改 props.page 就停在空页(第 14 页里一条数据都没有),改了又要自己复制一份 Math.ceil 公式。渲染层的 renderList 虽然把显示页码夹住了,但信号里仍是旧值,父级 state 与界面不一致——这正是「派生量有两个来源」的典型。",
"acceptance": "PaginationProps.total 支持 number | Signal<number>;组件内新增钳制 effect:页码越界(>最大页或 <1)时把夹取后的值写回 page 信号并回调 onChange,合法时不动手(不产生无意义回调);总量恢复后不记忆已失效的旧页码;manifest 的 total 类型与说明同步;demo sec-pagination 增加「总量 137 条 / 筛到 25 条」实例,能看到页码从 14 自动落到 3;npm run check 全绿",
"status": "done",
"domain": "tune",
"notes": ""
},
{
"id": "g286",
"title": "新增单行通知条组件 noticeBar",
"motivation": "播报类目前只有 alert(可多行、带标题、占块)与 toast(浮层、会自动消失)。页顶常驻的那种「一行通告 + 关闭」没有对应件:用 alert 会浪费一整块垂直空间,用 toast 又会被自动收走。真实场景(维护公告、试用期倒计时、离线重试提示)需要的是一行高、可关闭、超长能滚的横条。",
"acceptance": "新增 noticeBar(props):语义色四档 + 左侧符号(色彩之外的第二线索)+ 可选关闭;scroll=true 时正文走跑马灯(--ua-duration-loop-long),悬停暂停,减少动效偏好下停成静态且完整原文仍在 title;关闭隐藏整条并回调 onClose;样式只消费 --ua-* 令牌(audit:visual 不新增任何字面量,首次实现因 line-height:1 被拦下并改掉);audit:motion 的无限动画兜底从 6 处增至 7 处;manifest 双处 + demo sec-notice + README 清单与分组同步,npm run check 全绿",
"status": "done",
"domain": "new",
"notes": ""
},
{
"id": "g287",
"title": "通知条关闭走淡出退场,窄屏改为换行",
"motivation": "g286 的 noticeBar 关闭是直接 hidden:整条瞬间消失,和 alert 的淡出退场手感不一致(同类播报件两种消失方式)。另一处是硬 nowrap——不传 scroll 时窄屏上长文案被裁掉且无法读到,信息直接丢失。",
"acceptance": "关闭时先挂 is-out 类做淡出 + 上移(走 --ua-duration-base 与 --ua-translate-4,减少动效下只淡出不位移),200ms 后再 hidden,onClose 立即触发;≤640px 时正文允许换行并停掉跑马灯;audit:motion 的旋转/缩放过渡兜底从 35 处增至 36 处;demo 公告文案改回真实能力(原文宣称的「列宽记忆」并不存在);npm run check 全绿",
"status": "done",
"domain": "visual",
"notes": ""
},
{
"id": "g288",
"title": "文档步骤下限提到 4 条并补齐四个薄条目",
"motivation": "verify 只要求 steps ≥3,三条恰好是「怎么传 / 能做什么 / 一个变体」的标准配方——写得出来但没有边界:不说什么情况下别用、出错行为是什么。Agent 读文档时缺的正是这两块,于是把「不该这么用」的坑留给用户踩。",
"acceptance": "verify 的 steps 下限从 3 提到 4 并在报错里点明四步各写什么;badge / divider / kbd / highlight 四个三条目组件补第四条真实内容(交互语义归属、读屏分组、平台键名映射、空关键词行为),不是凑数;AGENTS.md 两处说明同步(组件入库表与 verify 覆盖清单);npm run check 全绿",
"status": "done",
"domain": "tooling",
"notes": ""
},
{
"id": "g289",
"title": "tooltip 出场期间跟随滚动重算翻转",
"motivation": "气泡是 wrap 内的绝对定位,滚动时跟着触发器走没问题;问题在翻转判定——flipSide 拿的是视口几何,只在出场那一瞬算一次。用户滚一下页面,原本「朝上放不下所以朝下」的判断可能已经反过来,气泡就压在视口边上;反过来也一样。这是只在滚动后才显形的错位,代码评审很容易漏过。",
"acceptance": "出场期间挂 scroll(捕获阶段,容器滚动不冒泡)+ resize 监听,rAF 节流重算 flipSide;收起时立即摘监听(关闭态不空转),重复出场不重复绑定;tooltip 文档 steps 补该行为;npm run check 全绿",
"status": "done",
"domain": "tune",
"notes": ""
},
{
"id": "g290",
"title": "抽出 trackShift 并接到下拉弹层",
"motivation": "g289 把滚动跟随写在了 tooltip 里,可下拉族(select / autoComplete / cascader / datePicker / timePicker / colorPicker / menu / multiSelect)用的是同一个 flip,同样只在打开那一瞬算一次——同一个缺陷被复制在九处,修一处等于只修了一份。先把监听逻辑抽成公共件,tooltip 用法不变(还顺手删掉了刚写的一整段重复代码),再给用量最大的 select 接上。",
"acceptance": "position.ts 新增 trackShift(onShift):捕获阶段监听 scroll + resize、rAF 节流、返回摘除函数并撤销待执行帧;tooltip 改用它(删除内联的 bound/raf/bind/unbind 五个变量,文件反而短了);select 展开期间接上 trackShift,收起即摘;两个组件的文档 steps 同步口径;npm run check 全绿",
"status": "done",
"domain": "tooling",
"notes": ""
},
{
"id": "g291",
"title": "下拉族七处弹层接上滚动跟随",
"motivation": "g290 抽出了 trackShift 但只接到了 select:autoComplete / cascader / colorPicker / datePicker / timePicker / menu / multiSelect 七处仍是「打开时算一次」的旧写法——同一个 flip、同一个缺陷,用户滚一下页面弹层就压到视口边上。欠账按组件分散在七个文件里,正是那种「每轮修一个,永远修不完」的活。",
"acceptance": "七处 effect 统一改成:收起摘监听 / 展开先算一次 / 展开期间 trackShift 重算(模式与 select 一致);七个文件的 position 导入补 trackShift;改动为机械替换,不改各处触发元素(input / trigger / box);npm run check 全绿",
"status": "done",
"domain": "tune",
"notes": ""
},
{
"id": "g292",
"title": "toast 溢出挤掉最早一条改走退场",
"motivation": "同屏满 4 条后第 5 条进来,最早那条是直接 remove():凭空消失。而手动关闭 / 自动超时都是淡出后移除——同一类播报件在同一屏上有两种消失方式,g287 刚为 noticeBar 统一过这个口径,toast 这里是漏网的一处。",
"acceptance": "溢出时改调 dismiss()(摘 ua-open 触发过渡 + transitionend 与 320ms 兜底二选一),与手动关闭走同一条路径;toast 文档 steps 补堆叠上限与 duration: 0 常驻的用法;npm run check 全绿",
"status": "done",
"domain": "visual",
"notes": ""
},
{
"id": "g293",
"title": "把浮层定位口径写进 Agent 约定",
"motivation": "g289–g291 连着三轮在修同一个缺陷:浮层只在打开时算一次翻转,滚动后就错位。修完了却没沉淀成规则——下一个新组件(或新 Agent)照样会写出「只在 open 时算一次」的版本,等于再把欠账复制一份。这类「已经用三轮纠正过的写法」必须变成入库门槛,否则只是把坑往后推。",
"acceptance": "AGENTS.md《贡献新组件的强制约定》新增第 7 条:打开时 flip / flipSide 算一次、展开期间 trackShift 重算、收起调释放函数摘监听,并点明「只在打开时算一次」的后果;npm run check 全绿",
"status": "done",
"domain": "tooling",
"notes": ""
},
{
"id": "g294",
"domain": "tune",
"status": "done",
"title": "dataTable 列显隐:工具栏「列」面板 + 受控 hiddenColumns",
"motivation": "表格列多时用户只想看关心的几列,目前只能靠调用方自己改 columns 重渲染;且 dataTable.ts 已顶到 199 行上限,受控桥接与工具栏构件该分出去。",
"acceptance": "① 开 columnToggle 后工具栏出现「列」按钮,弹层勾选即时增删列(表头/单元格/colspan/骨架行/空状态/导出全部跟随);② 至少保留一列可见,最后一列的勾选框置灰;③ 传 hiddenColumns 信号可受控并写回;④ 样式只消费 --ua-* 令牌,npm run audit:visual 计数不涨;⑤ 受控页码与选中桥接抽到 tableBridge.ts,dataTable.ts 仍 ≤200 行;⑥ npm run check 全绿。",
"notes": "列显隐落地:新 tableColumns.ts(触发器 + 勾选弹层,末列锁死)+ dataTable 派生 shown()/span() 统一供表头/单元格/骨架/空状态/colgroup 使用;受控页码与选中桥接抽到 tableBridge.ts(dataTable.ts 199→186 行);导出改惰性取列跟随可见列;ui.css 加 .ua-col-list/.ua-col-item(纯令牌,audit:visual 仍 0 处)"
},
{
"id": "g295",
"domain": "visual",
"status": "done",
"title": "表面阶梯审计:新增 ua audit:surface 并修正悬停底看见不了",
"motivation": "对比度门禁只管「文字 vs 底色」,管不到「底色 vs 底色」;实测表面→二阶表面亮度比只有 1.008(暗色 1.006),即悬停底与表面几乎同色,6 处 surface-2 与新建的列面板悬停都白改。",
"acceptance": "① ua audit:surface 按四套主题逐档验阶梯(页面底→表面→悬停底→按下底 ≥1.04,凹陷底 ≥1.02),不达标退出码 1,负向自检确认会红;② surface-2/3 改为从 surface 派生并拉开档距;③ audit:surface 进 check 链、package.json 脚本、usage 串与 doctor 的 check 链检查;④ 列面板悬停改用 surface-2(原先撞 .ua-pop 同底色);⑤ audit:contrast 与 audit:visual 仍全绿。",
"notes": "新 lib/surface-audit.mjs:四套主题 × 四段阶梯算亮度比(1.04 / 凹陷 1.02),--json/--list 双出口,进 check 链与 doctor;tokens surface-2/3 改为从 surface 派生(亮 6%/14%,暗 7%/17%),阶梯由 1.008→1.123(暗 1.006→1.193);列面板悬停改用 surface-2(原与 .ua-pop 同底色等于没反馈);contrast-audit 导出 resolve/ratio 供复用"
},
{
"id": "g296",
"domain": "tune",
"status": "done",
"title": "修 form 校验失败仍提交:自绘控件字段没有原生 input 时守卫失效",
"motivation": "提交守卫写成「找到焦点目标才中止」,而焦点目标只查 input/textarea/select;非搜索式下拉、评分、开关容器里没有原生 input,firstBad 为 null 时校验有错也照样调 onSubmit(demo 的「城市」必填下拉即可复现)。",
"acceptance": "① 中止判据改为错误条数 bad > 0;② 焦点落点按 控件 → 触发器(button/[tabindex]) → 字段容器(tabIndex=-1) 三级退让;③ manifest 的 form 步骤与 demo 描述同步说明;④ npm run check 全绿。",
"notes": "form 提交守卫改判错误条数;焦点按 控件 → 触发器 → 字段容器(tabIndex=-1) 三级退让,自绘控件字段也能被定位并拦住提交"
},
{
"id": "g297",
"domain": "tooling",
"status": "done",
"title": "新增 ua loop:循环锁 / 留痕 / 待办与选题建议一次给全",
"motivation": "接力实例判断「该不该抢锁、上一条做完没、下一条选谁」过去要翻 .ua/loop.lock 的 mtime、tail 留痕、再数 backlog,或去查调度器 sqlite;口径散在 ITERATION.md 里靠人记。",
"acceptance": "① ua loop 输出锁心跳(<15 分钟判定与协议同源)、上次留痕、backlog open 与视觉欠账数、下一条选题建议;② --json 给脚本;③ 进 usage 串与 CLI 清单 exports(doctor 的 ghost 检查通过);④ ITERATION.md 写明诊断入口、AGENTS 的 CLI 列表补一行;⑤ npm run check 与 doctor 全绿。",
"notes": "ua loop 只读诊断:锁心跳(<15min 同协议口径)/ 上次留痕 / backlog open 与视觉欠账 / 下一条选题建议,--json 可机读;进 usage 与 CLI 清单,doctor 11 项全过,ITERATION 与 AGENTS 同步"
},
{
"id": "g298",
"domain": "visual",
"status": "done",
"title": "死令牌归位:按下态改用 surface-3,结果页大标题用上 font-size-xl",
"motivation": "tokens --unused 报出 2 个令牌无人引用:surface-3(注释写「按下底」,但四处 :active 仍用与悬停同档的 surface-2,于是按下与悬停同色)与 font-size-xl(字号最大档没有任何组件用)。",
"acceptance": "① 下拉触发器 / 折叠头 / 分页按钮 / 日历格四处按下态改 surface-3,注释「加深一档」与实现对齐;② .ua-result-title 升到 xl,与弹窗标题 lg 拉开层级;③ ua tokens --unused 归零;④ audit:surface / audit:visual / audit:contrast 全绿。",
"notes": "四处 :active 改 surface-3(按下比悬停更实,与注释一致),.ua-result-title 用上 xl;tokens --unused 由 2 个降为 0,三项视觉门禁全绿"
},
{
"id": "g299",
"domain": "tooling",
"status": "done",
"title": "视觉抽查清单补三段:commandPalette / noticeBar / progressRing",
"motivation": "清单只覆盖到 sparkline,后期新增的命令面板、通知条、环形进度没有抽查位——视觉类条目收尾时「照单过一遍」会直接跳过它们,等于裸奔。",
"acceptance": "① ITERATION.md 新增三段共 11 条,维度落在五类内;② ua.js 的 REQUIRED 从 13 扩到 16、下限自动变 48 条、OK 文案同步;③ AGENTS 的清单描述同步为十六段 ≥48 条;④ npm run verify / check 全绿。",
"notes": "ITERATION.md 抽查清单加 commandPalette(4) / noticeBar(3) / progressRing(4);REQUIRED 13→16、条目下限自动 39→48、verify OK 文案与 AGENTS 描述同步"
},
{
"id": "g300",
"domain": "tune",
"status": "done",
"title": "toast 悬停暂停从未生效:宿主穿透把指针拦在门外",
"motivation": "toast.ts 里写了 pointerenter 暂停倒计时,但样式只在 .ua-toast.is-closable / .is-action 时才给 pointer-events: auto,默认 toast 全程穿透——最常见的「toast.success(msg)」根本收不到 hover,暂停逻辑成了死代码;键盘用户 Tab 进关闭按钮时也照样被倒计时收走。",
"acceptance": "① .ua-toast 自身接收指针,删掉 is-closable / is-action 两处补丁式 pointer-events(宿主容器仍穿透,只有卡片本体可命中);② 暂停 / 恢复抽成 pause/resume,剩余时长不低于 400ms 且重复进入不二次扣减;③ focusin / focusout 与悬停同口径暂停,焦点在卡片内部移动不恢复;④ manifest 的 closable 说明与手把手步骤同步,npm run check 全绿。",
"notes": "pointer-events: auto 落到 .ua-toast 本体,删掉 is-closable / is-action 两处补丁规则;暂停恢复抽成 pause/resume(剩余不低于 400ms、重复进入不二次扣减),focusin/focusout 与悬停同口径、焦点在卡片内部移动不恢复;manifest 的 closable 说明与手把手步骤同步"
},
{
"id": "g301",
"domain": "new",
"status": "done",
"title": "新增拖拽排序列表 sortableList(88 个组件)",
"motivation": "库里有 list 展示列表、virtualList 长列表,但缺「可调整顺序」的交互件:任务排优先级、字段顺序、看板列这类场景每次都得自己写一遍拖放,dragstart/dragover 的边界(不拦 dragover 就没 drop、拖拽中重建节点会中断手势)反复踩。",
"acceptance": "① 整行可拖,指针越过邻行中线即换位,dragend 落定;② 键盘等价 Alt+↑↓ 上下搬一格、Alt+Home/End 搬首尾,裸 ↑↓ 留给页面滚动;③ 顺序写回 value 信号并触发 onChange,拖拽期间跳过外部重排避免中断手势;④ 句柄 { el, move, order },下标越界与禁用时静默忽略;⑤ 样式只消费 --ua-* 令牌,禁止新增硬编码阴影/时长/字号;⑥ manifest/docs/README/demo/llms-full 同步,npm run check 全绿。",
"notes": "新增 src/sortableList.ts(118 行)+ types 两项 + index 导出 + ui.css 六条(只消费令牌);整行可拖、越过邻行中线换位、dragend 落定,Alt+↑↓/Home/End 键盘搬位;顺序写回 value 信号并触发 onChange,拖拽中跳过外部重排;句柄 { el, move, order } 越界静默忽略;组件 87→88,README/AGENTS/ARCHITECTURE 计数与 docs 同步"
},
{
"id": "g302",
"domain": "tune",
"status": "done",
"title": "dataTable 接上内核的多列排序:Shift+点击叠加第二列",
"motivation": "table-core 的 toggleSort 早就支持 action.multi(按 rules 顺序逐列比较),但皮肤层 dispatch 时从不传 multi,用户按住 Shift 点第二个表头毫无反应,多列排序只能靠手写 setSorting;表头标记也只反映单列方向,看不出比较优先级。",
"acceptance": "① 表头点击与键盘 Enter / 空格都读 shiftKey 并透传 multi;② 参与排序的列 ≥ 2 时标记追加优先级序号(↑1 / ↓2),单列时保持原箭头;③ manifest 手把手与 demo 描述同步;④ npm run check 全绿。",
"notes": "th 的 toggle 收 multi 参数,onClick / onKeydown 都读 shiftKey;sortMark 在 rules ≥ 2 时追加优先级序号(↑1 / ↓2),单列保持原箭头;manifest 步骤与 demo 描述同步"
},
{
"id": "g303",
"domain": "visual",
"status": "done",
"title": "拖拽行别用整行透明度:正文对比被压到 4.86",
"motivation": "sortableList 的拖拽态原本整行 opacity: .7,实测把拖拽行正文对比从 12.03 压到 4.86(亮色)——低于项目正文 7:1 的口径,而「正在被拿起」完全可以用虚线边 + 抬升投影表达,不必牺牲可读性。",
"acceptance": "① 拖拽态去掉整行 opacity,改用 --ua-shadow-2 抬升 + 虚线 accent 边 + tint-accent 底;② 静态态对比回到 12+(亮)/ 11+(暗);③ 把手悬停转 accent 色提示可抓,拖拽中 cursor 为 grabbing;④ audit:visual / audit:contrast / audit:motion 全绿,manifest 步骤同步。",
"notes": ".is-dragging 去 opacity 改 --ua-shadow-2 抬升 + 虚线 accent 边 + tint-accent 底(对比 4.86 → 12.03 亮 / 11.32 暗);把手悬停转 accent、拖拽中 grabbing;item 过渡补 box-shadow,manifest 步骤同步"
},
{
"id": "g304",
"domain": "tooling",
"status": "done",
"title": "配方补一条可排序检查单:句柄式组件的拼装样例",
"motivation": "sortableList 是句柄式组件(返回 { el, move, order }),与 form / virtualList 同类,但 18 条配方里没有一条演示句柄式拼装——Agent 照配方抄会把返回值直接当节点 append,页面一片空白;「顺序只存一份」「move 与拖拽同一条提交路径」这些约定也没人写下来。",
"acceptance": "① recipes.md 新增配方 19(编号连续、带代码块与要点),代码块里认出 sortableList / card / button / toast;② 要点覆盖句柄要 append(.el)、顺序唯一真相、move 与拖拽同路径、边缘不自动滚动、条目文案唯一;③ ua recipes 19 能取到该条,llms-full.txt 与 llms.txt 重生成;④ npm run verify / check 全绿。",
"notes": "recipes.md 新增配方 19:sortableList + card + button + toast 的可排序检查单,要点写清句柄要 append(.el)、顺序唯一真相、move 与拖拽同路径、边缘不自动滚动、文案唯一;ua recipes 19 可取出,llms.txt / llms-full.txt 重生成"
},
{
"id": "g305",
"domain": "new",
"status": "done",
"title": "新增瀑布流布局 masonry(89 个组件)",
"motivation": "库里有 grid(等宽等高的规则栅格)与 space(线性间距),但图片墙、评论流、看板这类「列高不等」的布局没有落点,业务侧只能自己写 column-width 与 break-inside,还要记住多列容器不能是 flex/grid 这个坑。",
"acceptance": "① masonry({ items, columns?, minColumnWidth?, gap? }) 走 CSS 多列:不传 columns 按 minColumnWidth 自适应,传了即固定列数;② 每项包 .ua-masonry-item 并 break-inside: avoid,卡片不在列边界拆断;③ gap 同时管列间距与行间距;④ 样式只消费 --ua-* 令牌,禁止新增硬编码阴影/时长/字号;⑤ manifest/docs/README/demo/llms-full 同步,组件 88→89,npm run check 全绿。",
"notes": "新增 src/masonry.ts(22 行)+ types + index + ui.css 两条;CSS 多列实现:不传 columns 走 column-width 自适应,传了即 column-count 固定列数,gap 一处同时给 column-gap 与 item margin-bottom,item 带 break-inside: avoid;组件 88→89,README/AGENTS/ARCHITECTURE 计数与 docs 同步"
},
{
"id": "g306",
"domain": "tooling",
"status": "done",
"title": "淡底也进审计:选中底 / 悬停底的浓度不能再被悄悄调没",
"motivation": "audit:surface 只验表面阶梯(surface 各档),但全站 52 处选中底与悬停底用的是半透明 accent 淡底(tint-accent / -weak)——浓度手写在令牌里,暗色弱档降到 2% 时悬停与静止只差 1.026,界面「看起来没反应」,而现有门禁一声不响。",
"acceptance": "① LADDER 增加两档「表面 → 淡底标准档 / 弱档」,阈值 1.04,用 ratio() 先把半透明淡底压到表面上再算;② 四套主题全绿(亮 1.156 / 1.074,暗 1.232 / 1.118);③ 负向自检:暗色弱档改 2% 时 audit:surface 退出码 1 并点出 dark / contrastDark 两处;④ AGENTS 对该门禁的描述同步。",
"notes": "LADDER 增加「表面 → 淡底标准档 / 弱档」两档(阈值 1.04),复用 contrast-audit 的 ratio():半透明淡底先 over 到表面上再算;四套主题全绿(亮 1.156/1.074,暗 1.232/1.118);负向自检把暗色弱档改 2% 后 1.026 < 1.04,audit:surface 退出码 1 并点出 dark/contrastDark 两处,改回即恢复;AGENTS 描述同步"
},
{
"id": "g307",
"domain": "visual",
"status": "done",
"title": "拖拽行缺少层内抬升:投影被后面的行盖掉",
"motivation": "g303 给拖拽行加了抬升投影,但行仍是静态定位、没有层级——同一容器里后续兄弟元素绘制在后,被拖那一行的投影直接被下一行的底色切掉,「拿起来」看起来像贴纸被压住。层级令牌只有 dropdown 起的五档(100 起),拿它抬一行会污染语义。",
"acceptance": "① 新增 --ua-z-raised: 10 令牌(层内抬升,够压过同容器兄弟即可);② .ua-sortable-item.is-dragging 补 position: relative + z-index: var(--ua-z-raised);③ ua tokens --unused 不报新令牌、audit:visual 的 z-literal 仍为 0;④ npm run check 全绿。",
"notes": "tokens.css 新增 --ua-z-raised: 10(层内抬升档,与 dropdown 100 起的那套分层语义区分);.is-dragging 补 position: relative + z-index: var(--ua-z-raised),投影不再被后续行盖住;tokens --unused 0 个、audit:visual 合计仍 0 处,docs 重生成"
},
{
"id": "g308",
"domain": "tune",
"status": "done",
"title": "sortableList 支持拖到边缘自动滚,摘掉「长列表拖不动」的限制",
"motivation": "文档里写明「拖到边缘不会自动滚动,条目超过一屏就别用」,这等于把最常见的场景(几十条检查单)挡在门外:拖到容器下沿不动,用户只能反复滚动重来。",
"acceptance": "① dragstart 里一次算好最近的纵向滚动祖先(overflow-y auto/scroll 且确已溢出),dragover 每帧只在贴边 48px 感应带内按帧滚 12px;② 离开感应带或 dragend 立即取消帧并清状态,不留后台 rAF;③ 找不到滚动祖先(整页滚动)时不接管;④ manifest 步骤与配方 19 要点同步改写,npm run check 全绿。",
"notes": "dragstart 用 scrollHost() 一次算最近纵向滚动祖先(overflow-y auto/scroll 且已溢出),dragover 在上下 48px 感应带内按帧滚 12px(EDGE_BAND/EDGE_STEP 常量),离开感应带或 dragend 取消 rAF 并清 scroller/edgeDir;找不到祖先不接管;manifest 步骤与配方 19 要点同步"
},
{
"id": "g309",
"domain": "tooling",
"status": "done",
"title": "把每轮迭代的手工留痕脚本收成 ua iter done / add",
"motivation": "每轮收尾都要临时写 node 脚本改 backlog、手拼 iterations.log 的 JSON——历史踩过 ts 手写缺时区、字段顺序自创、状态忘记改、哈希抄错,lint-iterations 事后才拦。这条流程本该由 CLI 保证,不该每轮靠人记。",
"acceptance": "① 新增 ua iter done <id> <commit> <summary> [notes]:backlog 置 done、追加 {ts,id,result,commit,summary} 固定字段行,ts 由本机时钟生成带 +08:00 偏移,commit 校验 7-40 位哈希,条目不存在直接报错;② ua iter add <json>:五字段齐全 + id 唯一校验后追加;③ usage 串、dispatch、CLI 清单三处同步(doctor 通过);④ 临时脚本 .ua/log-one.mjs 与 .ua/add-entry.mjs 删除;⑤ npm run check 全绿。",
"notes": ""
},
{
"id": "g310",
"domain": "tune",
"status": "done",
"title": "multiSelect 补「全选 / 半选」:8 个候选逐个点太慢",
"motivation": "多选下拉只能逐项点或按退格删标签,选项一多就是体力活;transfer 早就有三态全选框,multiSelect 这个更常用的组件反而没有,用户只能「先搜再一个个点」。",
"acceptance": "① 新增 selectAll 入参:弹层顶部一行「全选(已选/可选)」,全选 ✓、部分选中显 –、再点即清空这批;② 只作用于当前过滤结果,先搜再全选=按条件批量选;③ 到 max 时补满即停,不静默超选;④ 键盘可达:全选行占 active -1 号位,↑↓ 能走到、Enter 触发;⑤ manifest 参数表与手把手、demo 同步,npm run check 全绿。",
"notes": ""
},
{
"id": "g311",
"domain": "visual",
"status": "done",
"title": "骨架文本行高对齐正文:加载完成时表格不再窜一下",
"motivation": ".ua-skeleton-text 写死 height: 12px,而表格单元格里的正文行盒是「13px 字号 × 1.55 行高 ≈ 20px」——骨架行比真实行矮 8px,五行就差 40px,dataTable 从骨架切到数据时整表高度突跳,比没有加载态更晃眼。",
"acceptance": "① .ua-skeleton-text 的高度改成 calc(正文字号 × 正文档行高),与真实文字行盒等高;② 同时消掉这处 12px 字面值;③ skeleton 手把手补一条说明为什么按行盒算;④ audit:visual / audit:motion 全绿,npm run check 通过。",
"notes": ""
},
{
"id": "g312",
"domain": "tune",
"status": "done",
"title": "表格空状态补操作出口:真没数据时只能干看着",
"motivation": "emptyRow 只区分「筛掉了」与「真没数据」,但两种情况都只给文案——列表本来就空时没有「新建」这类下一步,用户只能自己找工具栏;而筛选没结果时又该引导清条件,两种语义不该共用一个按钮位。",
"acceptance": "① DataTableProps 新增 emptyAction 插槽,只在「真没数据」时渲染在占位文案右侧;② 有筛选词时该位置仍是「清除筛选」,两者互斥;③ manifest 参数表与手把手、类型注释同步;④ npm run check 全绿。",
"notes": ""
},
{
"id": "g313",
"domain": "tune",
"status": "done",
"title": "sortableList 禁用态没有任何视觉线索",
"motivation": "disabled 只拦住了 dragstart 与键盘搬位,界面上一模一样:光标还是 grab、悬停照样变色,用户拖三次没反应只会以为组件坏了。",
"acceptance": "① 根节点按 disabled 切换 is-disabled 类与 aria-disabled(布尔与信号两种读法都响应);② 禁用时行光标回默认、文字退次级色、把手退到边框色、悬停与按下都不再给底色反馈;③ manifest 的 disabled 说明与手把手补一条,demo 增一个禁用实例;④ audit:visual / audit:contrast 全绿,npm run check 通过。",
"notes": ""
},
{
"id": "g314",
"domain": "tooling",
"status": "done",
"title": "视觉抽查清单补两段:sortableList / masonry",
"motivation": "清单停在十六段(g299 扩过一轮),之后新增的拖拽排序与瀑布流没有抽查位——这两个组件恰好是「靠观感判断做没做对」的类型(拖拽虚影、列边界断卡),照着清单过一遍时会被直接跳过。",
"acceptance": "① ITERATION.md 新增两段共 8 条,维度标签落在五类内;② ua.js 的 REQUIRED 从 16 扩到 18(条目下限由段数自动推到 54)、verify OK 文案同步;③ AGENTS 的清单描述同步为十八段 ≥54 条;④ npm run verify / check 全绿。",
"notes": ""
},
{
"id": "g315",
"domain": "tune",
"status": "done",
"title": "表格补行展开:长内容只能挤在单元格里或跳页",
"motivation": "订单备注、用户标签、二级操作这类内容塞进单元格会把列撑爆,跳详情页又太重;dataTable 只有 onRowClick 一条路,等于逼调用方在「挤」和「跳走」之间二选一。",
"acceptance": "① 新增 renderDetail 入参,传了才多一列展开钮(▸ 旋转 90° 表示展开);② 详情行整行跨列、凹陷底、允许折行,展开态按 rowKey 记,筛选与翻页不丢;③ 展开钮拦冒泡,整行可点时不会顺带触发 onRowClick;④ 空状态与骨架行的 colspan 计入展开列;⑤ manifest 与 demo 同步,npm run check 全绿。",
"notes": ""
},
{
"id": "g316",
"domain": "tooling",
"status": "done",
"title": "ua loop 直接给下一个可用编号,自补条目不再撞号",
"motivation": "backlog 清空时每轮都要自己翻最大 g 号再自补,历史上撞过号、靠 rebase 收拾;诊断命令明明已经把 backlog 读进内存,顺带算一个 nextId 是举手之劳。",
"acceptance": "① ua loop 文本输出在「需自补」时带「下一条可用编号 gNNN」;② --json 增 nextId 字段供脚本直接用;③ CLI 清单与 ITERATION.md 的诊断入口描述同步;④ npm run doctor / check 全绿。",
"notes": ""
},
{
"id": "g317",
"domain": "visual",
"status": "done",
"title": "文字选区跟随主题:默认蓝底选区在暗色下突兀",
"motivation": "全站没有任何 ::selection 规则,用户选中文本时走浏览器默认选区——亮色下是系统蓝、暗色下是亮蓝底黑字,与 --ua-* 主题色系完全不搭,代码块 / diffView 这类大量复制的场景尤其刺眼;caret-color 已跟随 accent,选区却没有,两者不一致。",
"acceptance": "① ui.css 新增全局 ::selection 规则,选区底色由既有 --ua-color-accent 经 color-mix 派生(低透明度,与焦点环同族),不新增任何硬编码颜色;② 亮 / 暗 / 高对比主题下选区均可辨认,audit:visual / audit:contrast / audit:surface 全绿;③ 文档(llms-full.txt 经 npm run docs 同步)令牌说明处补一句选区口径;④ npm run check 通过。",
"notes": ""
},
{
"id": "g318",
"domain": "tooling",
"status": "done",
"title": "配方 20:分步表单向导(步进条 + 按步校验)",
"motivation": "多步向导是后台最高频的表单形态之一,现有 19 条配方没有覆盖「步进条 + 分步字段 + 步间校验 + 回跳」的组合:stepper 的 active 双向绑定与 formItem 句柄的 validate() 恰好能拼出这个场景,缺一条示范怎么把校验拦在步与步之间,而不是最后一步集体报错。",
"acceptance": "① recipes.md 新增配方 20(编号连续、含代码块与要点,代码块能被 ua recipes 认出 stepper / formItem / button / card / toast 组件);② 配方代码与真实签名一致(FormRule.message、button disabled 收 Signal、formItem 句柄 validate);③ ua recipes 20 与 ua search 均能命中;④ npm run check 通过。",
"notes": ""
},
{
"id": "g319",
"domain": "tooling",
"status": "done",
"title": "ua info 支持逗号分隔批量查询多个组件",
"motivation": "Agent 拼一个界面通常要 3–5 个组件的参数,现在只能 `ua info button`、`ua info select` 一条条查(MCP get_component 同样单查),每轮多花好几次往返;逗号分隔批量一次拿全,文本 / --md / --json 三种模式都覆盖。",
"acceptance": "① `ua info button,select --md` 一次输出两个组件的 Markdown 片段(片段间以分隔线隔开);② `--json` 多值时输出数组、单值保持对象(不破坏现有脚本消费);③ 不存在的 id 报错并指明是第几个;④ manifest usage 串同步更新;⑤ npm run check 通过。",
"notes": ""
},
{
"id": "g320",
"domain": "tooling",
"status": "done",
"title": "配方 21:消息中心(铃铛角标 + 抽屉通知流)",
"motivation": "badge 的手把手里明确说「点角标进消息中心应当把 badge 塞进 button 里」,但 20 条配方没有一条给出消息中心的全貌:未读数怎么单一来源、静态快照组件(list / badge)怎么接信号、已读回写怎么只写数据源,都缺示范。",
"acceptance": "① recipes.md 新增配方 21(编号连续、代码块与要点齐全,ua recipes 能认出 badge / drawer / list / relativeTime / button / card 组件);② 配方与真实签名一致(badge count、drawer open 收信号、list items 静态数组、relativeTime value 收时间戳);③ npm run check 通过。",
"notes": ""
},
{
"id": "g321",
"domain": "visual",
"status": "done",
"title": "纯交互控件防误选 + 移动端点按灰闪清理",
"motivation": "按钮 / 徽标 / 页签 / 分页 / 步骤条这类控件的文案是「动作」不是「内容」:双击或拖选会把按钮文字选中变蓝,观感割裂且打断连点(现全库只有 8 处零散 user-select,交互控件基本没盖住);移动端浏览器默认点按灰闪(tap highlight)与令牌皮肤同层突兀,按下反馈应由 :active 态承担。",
"acceptance": "① ui.css 全局区新增一条控件清单式 user-select: none(button/tag/badge/fab/backtop/page-btn/tab/seg-btn/step/rate-star/checkbox/radio/switch/menu-item/crumb),内容型组件(代码块 / 表格 / 描述列表 / 链接)不入选、照常可复制;② 全站 ua- 类 -webkit-tap-highlight-color: transparent,不引入任何颜色字面量;③ audit:visual / audit:contrast / audit:surface / audit:motion 全绿,npm run check 通过。",
"notes": ""
},
{
"id": "g322",
"domain": "tooling",
"status": "done",
"title": "MCP get_component 支持逗号批量,与 ua info 对齐",
"motivation": "g319 给 `ua info` 加了逗号批量,但 Agent 查组件的主通道是 MCP(get_component 一次仍只能查一个);拼一个界面要 3–5 次工具往返,MCP 侧同样需要批量,并抽出 CLI / MCP 共用的名字解析函数避免两处口径漂移。",
"acceptance": "① get_component 的 name 支持逗号分隔多值:json 返回数组、md 返回以水平线分隔的多段片段,单值输出结构不变;② 不存在的 id 报错并标明序号;③ 名字解析抽成 CLI / MCP 共用函数;④ 工具 description 同步更新;⑤ npm run check 通过。",
"notes": ""
},
{
"id": "g323",
"domain": "tooling",
"status": "done",
"title": "文档站收录组合配方区(骨架 + 要点展开即看)",
"motivation": "21 条配方此前只在 CLI / MCP / llms-full.txt 三条 Agent 通道可查,人类版文档站完全没有入口——浏览器里翻配方只能回 recipes.md 源文件;文档站已有组件/令牌/搜索/亮暗主题,配方是最后一块缺口。",
"acceptance": "① apps/docs/index.html 生成「组合配方」卡:每条配方一个 details 折叠块(标题 + 组件 chips + 代码块 + 要点),导航栏补「配方」锚点;② 搜索态不渲染配方卡(不属于任何包,避免稀释过滤结果);③ 配方数据由 parseRecipes 生成而非手写,recipes.md 改动后 npm run docs 即同步;④ npm run check 通过。",
"notes": ""
},
{
"id": "g324",
"domain": "tune",
"status": "done",
"title": "table-core 补 setColumns:动态换列后失效排序 / 筛选剔除",
"motivation": "setRows 已支持整体换源并按 rowKey 剔除失效选择,但列定义仍是构造时写死:动态列表(按角色 / 视图切换列集)只能重建整个实例,排序与筛选状态全丢;setColumns 与 setRows 对称——换列后指向不存在列的 sorting / filters 失去语义,应剔除而不是静默残留。",
"acceptance": "① core 新增 setColumns(next):引用未变不动;换列后剔除 sorting / filters 里 key 不在新列集的条目,其余状态(分页 / 选择)保持;② getColumns 返回最新列集,getCellValue 按新列 accessor 取值;③ smoke 冒烟补断言(失效剔除 + 未变短路 + accessor 跟随);④ npm run check 通过。",
"notes": ""
},
{
"id": "g325",
"domain": "visual",
"status": "done",
"title": "avatar 破图回退文字头像:src 加载失败不再露碎图标",
"motivation": "avatar 有 src 时直接渲染 img,加载失败(404 / 网络断开 / 权限过期)会露出浏览器默认碎图标,圆形容器里一块灰裂图比没有头像更难看;image 组件已有 error 兜底,avatar 没跟,同类组件口径不一致。",
"acceptance": "① avatar img 注册 error 监听:加载失败就地替换为与无 src 时同款的文字头像(名称首字 + role=img + aria-label),尺寸与语义底色不变;② 正常加载路径零变化(不加多余请求);③ manifest 文档 avatar props/步骤同步补一条兜底说明,verify 双向校验过;④ npm run check 通过。",
"notes": ""
},
{
"id": "g326",
"domain": "tooling",
"status": "done",
"title": "ua search 同义词回退:中文口语词不再搜不到组件",
"motivation": "库内 id 全是英文(modal / select / spinner),而人和 Agent 张口就是「走马灯」「悬浮按钮」「分格输入」。这类词在 id / 标题 / 参数名 / 参数说明 / 步骤五层与配方三层里都没有原文,search 只剩 editDistance ≤ 3 的英文近似——对中文完全无效,于是「没有这个组件」的错误结论被反复得出,实际 carousel / floatButton / pinInput 都在库里。",
"acceptance": "① 新增 lib/synonyms.mjs:中文口语词 → 组件 id 的一组映射,一词只许归一组;② ua search 在五层 + 配方三层全空时按表回退(多词取交集,交集空退并集),命中行标「← 同义词「走马灯」」、页脚注明「按同义词表回退」,正常命中路径零变化不掺水;③ MCP search_components 共用同一份 fallbackBySynonym,返回项 matched.field 为「同义词」,CLI 与 MCP 口径不漂移;④ ua verify 校验表内每个 id 真实存在、同一个词不归两组,组件改名后残留即门禁红;⑤ npm run check 全绿。",
"notes": ""
},
{
"id": "g327",
"domain": "tune",
"status": "done",
"title": "stepper 补失败态:提交没过的那一步不再无处表达",
"motivation": "步骤条只有 done/current/upcoming 三态,某步校验失败时只能把 active 退回去,于是「失败」和「还没走到」长得一模一样——用户看不出是卡住了还是失败了;另外 clickable 时只有圆点能点,标题文字点不动,手指落在文字上是空操作。",
"acceptance": "① StepperItem 增可选 status: 'error':该步红点 + 感叹号 + 标题标红,优先级高于进度态、不进 done 也不占 current、不参与回跳;② 失败符号带 role=img + aria-label「第 N 步失败:xx」,读屏不读孤立感叹号;③ clickable 时整个步骤条可点(圆点按钮 + 标题文字),点按钮与点文字各只触发一次 onChange;④ 样式只消费 --ua-*(danger 色 / alpha-soft 淡底 / weight-semibold),audit:visual 计数不涨;⑤ manifest props/步骤与演示同步,npm run check 全绿。",
"notes": ""
},
{
"id": "g328",
"domain": "visual",
"status": "open",
"title": "语义三色淡底补令牌:同一组件里 info 与其余三色不再两套口径",
"motivation": "accent 有 --ua-color-tint-accent(亮 10% / 暗 14%),语义三色却没有对应令牌,15 处淡底全靠手写 color-mix:同一组 toast / tag 里 info 走令牌(透明叠加)、success / warning / danger 走「与页面底混色」(不透明),四种语义档的底其实两套口径;手写浓度也无法被门禁守住——accent 那档被 audit:surface 抓到过暗色几乎看不见,语义三色同样没有栅栏。",
"acceptance": "① tokens.css 新增 --ua-color-tint-danger / -success / -warning(亮色 var(--ua-alpha-soft),暗色 14%,与 accent 分档同构),tokens.json 同步三键(verify 的名字与值双源校验要过);② ui.css 15 处手写语义淡底(含与页面底混色的 6 处)统一换成令牌,四种语义档只剩一套口径;③ audit:surface 阶梯新增「表面 → 危险/成功/警告淡底」三档(阈值 1.04),四套主题全部实测达标;④ audit:visual 混色浓度计数不涨;⑤ npm run check 全绿。",
"notes": ""
}
]
}