agentcss 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/01-tokens/README.md +1 -1
  2. package/01-tokens/responsive-demo.html +1 -1
  3. package/01-tokens/tokens.core.css +1 -1
  4. package/01-tokens/tokens.css +2 -2
  5. package/02-primitives/date-range/date-range.js +1 -1
  6. package/02-primitives/timeline/demo.html +1 -1
  7. package/03-patterns/drawer/demo.html +1 -1
  8. package/03-patterns/overlay/overlay.css +1 -1
  9. package/03-patterns/states/states.css +1 -1
  10. package/04-recipes/app-shell/README.md +1 -1
  11. package/04-recipes/app-shell/demo.html +1 -1
  12. package/04-recipes/longform/content.html +6 -6
  13. package/04-recipes/report/demo.html +2 -2
  14. package/04-recipes/table/README.md +1 -1
  15. package/04-recipes/table/demo.html +1 -1
  16. package/09-assets/bar/bar.css +1 -1
  17. package/09-assets/model-viewer/model-viewer.js +1 -1
  18. package/AGENTS.md +3 -1
  19. package/CHANGELOG.md +103 -5
  20. package/CONTRIBUTING.md +44 -3
  21. package/README.md +2 -0
  22. package/README.zh-CN.md +2 -0
  23. package/START-HERE.md +12 -5
  24. package/ai/START-HERE.md +40 -6
  25. package/ai/check-html.js +658 -0
  26. package/ai/cli.js +557 -67
  27. package/ai/components.json +1 -1
  28. package/ai/pdf.js +2 -2
  29. package/ai/recipes.json +86 -0
  30. package/ai/scaffold.js +195 -0
  31. package/ai/shot.js +345 -0
  32. package/ai/validate.js +65 -6
  33. package/dist/COSTS.md +10 -10
  34. package/docs/ACCESSIBILITY.md +2 -2
  35. package/docs/ARCHITECTURE.md +116 -0
  36. package/docs/LAYER-BASELINE.md +2 -2
  37. package/docs/PRINT.md +3 -3
  38. package/docs/RELEASE-PROCESS.md +52 -2
  39. package/docs/STABILITY.md +1 -1
  40. package/docs/VERIFY-RELEASE.md +17 -0
  41. package/index.html +3 -3
  42. package/llms-full.txt +17 -3
  43. package/llms.txt +26 -5
  44. package/package.json +2 -1
package/ai/validate.js CHANGED
@@ -32,7 +32,11 @@ var path = require('path');
32
32
  var AI_DIR = __dirname;
33
33
 
34
34
  // ---------------------------------------------------------------- 能力白名单
35
- // ⚠️ 改这个表 = 改工具的能力边界。加关键字 ⇒ 必须同时实现它,否则别加。
35
+ // ⚠️ 改这个表 = 改工具的能力边界。
36
+ // 🔴 表里有的关键字**必须真的实现**:白名单只负责"允许出现",
37
+ // 不负责"校验它"。两者不一致 ⇒ schema 里写了、校验器当没看见 ⇒ **静默假绿**。
38
+ // (本轮审计 F4:`uniqueItems` / `not` 在表里挂了很久但没实现,
39
+ // `validate({type:'array',uniqueItems:true},[1,1])` 返回 ok:true。)
36
40
  var SUPPORTED_KEYWORDS = [
37
41
  '$schema', '$id', '$comment', '$ref', 'definitions', 'title',
38
42
  'type', 'required', 'properties', 'additionalProperties', 'patternProperties',
@@ -57,15 +61,30 @@ function isPlainObject(v) {
57
61
  */
58
62
  // 哪些键的"值"本身还是 schema
59
63
  var SUBSCHEMA_KEYS = [
60
- 'items', 'additionalProperties', 'patternProperties', 'allOf', 'anyOf',
64
+ 'items', 'additionalProperties', 'allOf', 'anyOf',
61
65
  'oneOf', 'not', 'propertyNames', 'contains', 'if', 'then', 'else'
62
66
  ];
63
- // 哪些键是"名字 → schema"的容器(它们的**键**是字段名,不是关键字)
64
- var CONTAINER_KEYS = ['properties', 'definitions', '$defs', 'dependentSchemas'];
67
+ /* 哪些键是"名字 → schema"的容器:它们的**键**不是关键字。
68
+ 🔴 `patternProperties` 必须在这里,不能当普通子 schema ——
69
+ 它的键是**正则**(`^--[a-z][a-z0-9-]*$`),
70
+ 当成关键字检查就会报"用了没实现的关键字",而 schema 本身完全合法。
71
+ 这个错 0.26.0 就有,只是预扫当时只覆盖 4 份文件、碰巧都没用 patternProperties
72
+ ⇒ 直到本轮把预扫扩到全部 8 份才暴露(`tokens.audience` 第一个撞上)。 */
73
+ var CONTAINER_KEYS = ['properties', 'definitions', '$defs', 'dependentSchemas',
74
+ 'patternProperties'];
75
+
76
+ /* 扫哪些文件:**目录里现找**,不手写清单。
77
+ 手写清单必漂 —— 0.26.0 时 `ai/` 下有 8 份 schema,这里只列了 4 份
78
+ (behaviors / deprecations / tokensAudience / tokensTree 从来没被扫过),
79
+ 于是那四份里出现未知关键字也不会有人知道。 */
80
+ function schemaFiles() {
81
+ return fs.readdirSync(AI_DIR).filter(function (f) {
82
+ return /\.schema\.json$/.test(f);
83
+ }).sort();
84
+ }
65
85
 
66
86
  function assertSchemasUseOnlySupported() {
67
- var files = ['contract.schema.json', 'components.schema.json',
68
- 'tokens.schema.json', 'patch.schema.json'];
87
+ var files = schemaFiles();
69
88
  var unknown = [];
70
89
 
71
90
  function checkNode(schema, f, ptr) {
@@ -118,6 +137,27 @@ function typeOk(expected, value) {
118
137
  return true;
119
138
  }
120
139
 
140
+ /* JSON Schema 的"相等"是**深比较**:`{a:1}` 与 `{a:1}` 是两个对象但相等。
141
+ 用 `===` 判重会把重复项全放过,而且是静默的。 */
142
+ function deepEqual(a, b) {
143
+ if (a === b) return true;
144
+ if (Array.isArray(a) || Array.isArray(b)) {
145
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
146
+ for (var i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
147
+ return true;
148
+ }
149
+ if (isPlainObject(a) && isPlainObject(b)) {
150
+ var ka = Object.keys(a), kb = Object.keys(b);
151
+ if (ka.length !== kb.length) return false;
152
+ for (var j = 0; j < ka.length; j++) {
153
+ if (!Object.prototype.hasOwnProperty.call(b, ka[j])) return false;
154
+ if (!deepEqual(a[ka[j]], b[ka[j]])) return false;
155
+ }
156
+ return true;
157
+ }
158
+ return false;
159
+ }
160
+
121
161
  function resolveRef(root, ref) {
122
162
  if (ref.charAt(0) !== '#' || ref.indexOf('#/') !== 0) {
123
163
  throw new Error('只支持文档内 $ref:' + ref);
@@ -178,6 +218,18 @@ function validate(schema, data, root, ptr, errors) {
178
218
  if (schema.maxItems !== undefined && data.length > schema.maxItems) {
179
219
  errors.push({ path: ptr, msg: '元素多于 ' + schema.maxItems + ' 个' });
180
220
  }
221
+ /* `uniqueItems: true` = 元素两两不相等。O(n²) 深比较 —— 这里的数据都是
222
+ 几十个以内,可读性比复杂度重要;真要上千个再换哈希。 */
223
+ if (schema.uniqueItems === true) {
224
+ for (var ui = 0; ui < data.length; ui++) {
225
+ for (var uj = ui + 1; uj < data.length; uj++) {
226
+ if (deepEqual(data[ui], data[uj])) {
227
+ errors.push({ path: ptr + '/' + uj,
228
+ msg: '第 ' + uj + ' 个元素与第 ' + ui + ' 个重复(uniqueItems)' });
229
+ }
230
+ }
231
+ }
232
+ }
181
233
  if (schema.items) {
182
234
  data.forEach(function (v, i) {
183
235
  validate(schema.items, v, root, ptr + '/' + i, errors);
@@ -228,6 +280,13 @@ function validate(schema, data, root, ptr, errors) {
228
280
  errors.push({ path: ptr, msg: '必须恰好满足 oneOf 中的一条,实际满足 ' + n + ' 条' });
229
281
  }
230
282
  }
283
+ /* `not`:**必须不**满足里面的约束。子校验用独立的 errors 数组 ——
284
+ 借用外层数组的话,"不满足 not" 会被记成"满足了"的错,方向正好反了。 */
285
+ if (schema.not !== undefined) {
286
+ if (validate(schema.not, data, root, ptr, []).length === 0) {
287
+ errors.push({ path: ptr, msg: '不应满足 not 里的约束(实际满足了)' });
288
+ }
289
+ }
231
290
  return errors;
232
291
  }
233
292
 
package/dist/COSTS.md CHANGED
@@ -18,22 +18,22 @@
18
18
 
19
19
  | 档 | 组件 | tokens(必付) | CSS | JS | **合计** | 源码版合计 | 差 |
20
20
  |---|---|---:|---:|---:|---:|---:|---:|
21
- | primitive | `badge` | 2.9 KB | 0.5 KB | — | **3.4 KB** | 24.7 KB | −21.2 KB |
21
+ | primitive | `badge` | 2.9 KB | 0.5 KB | — | **3.4 KB** | 24.6 KB | −21.2 KB |
22
22
  | primitive | `button` | 2.9 KB | 1.0 KB | — | **3.9 KB** | 28.3 KB | −24.4 KB |
23
23
  | primitive | `card` | 2.9 KB | 0.9 KB | — | **3.9 KB** | 26.0 KB | −22.1 KB |
24
24
  | primitive | `choice` | 2.9 KB | 1.1 KB | — | **4.1 KB** | 28.1 KB | −24.0 KB |
25
25
  | primitive | `combobox` | 2.9 KB | 1.0 KB | 5.7 KB | **9.6 KB** | 31.8 KB | −22.2 KB |
26
- | primitive | `date-range` | 2.9 KB | 0.8 KB | 2.8 KB | **6.6 KB** | 28.9 KB | −22.4 KB |
27
- | primitive | `delta` | 2.9 KB | 0.3 KB | — | **3.2 KB** | 24.7 KB | −21.5 KB |
28
- | primitive | `input` | 2.9 KB | 1.0 KB | — | **4.0 KB** | 25.8 KB | −21.9 KB |
26
+ | primitive | `date-range` | 2.9 KB | 0.8 KB | 2.8 KB | **6.6 KB** | 28.9 KB | −22.3 KB |
27
+ | primitive | `delta` | 2.9 KB | 0.3 KB | — | **3.2 KB** | 24.6 KB | −21.4 KB |
28
+ | primitive | `input` | 2.9 KB | 1.0 KB | — | **4.0 KB** | 25.8 KB | −21.8 KB |
29
29
  | primitive | `metric` | 2.9 KB | 0.5 KB | — | **3.4 KB** | 25.1 KB | −21.7 KB |
30
- | primitive | `popover` | 2.9 KB | 0.8 KB | 2.3 KB | **6.1 KB** | 29.7 KB | −23.7 KB |
30
+ | primitive | `popover` | 2.9 KB | 0.8 KB | 2.3 KB | **6.1 KB** | 29.7 KB | −23.6 KB |
31
31
  | primitive | `progress` | 2.9 KB | 0.8 KB | — | **3.7 KB** | 26.2 KB | −22.5 KB |
32
32
  | primitive | `select` | 2.9 KB | 0.8 KB | 4.3 KB | **8.1 KB** | 33.1 KB | −25.0 KB |
33
33
  | primitive | `separator` | 2.9 KB | 0.2 KB | — | **3.2 KB** | 23.2 KB | −20.0 KB |
34
34
  | primitive | `skeleton` | 2.9 KB | 0.5 KB | — | **3.4 KB** | 23.8 KB | −20.4 KB |
35
- | primitive | `switch` | 2.9 KB | 0.8 KB | — | **3.7 KB** | 30.3 KB | −26.5 KB |
36
- | primitive | `timeline` | 2.9 KB | 0.5 KB | — | **3.4 KB** | 25.2 KB | −21.7 KB |
35
+ | primitive | `switch` | 2.9 KB | 0.8 KB | — | **3.7 KB** | 30.2 KB | −26.5 KB |
36
+ | primitive | `timeline` | 2.9 KB | 0.5 KB | — | **3.4 KB** | 25.1 KB | −21.7 KB |
37
37
  | primitive | `upload` | 2.9 KB | 1.4 KB | 4.5 KB | **8.8 KB** | 39.7 KB | −30.9 KB |
38
38
  | pattern | `accordion` | 2.9 KB | 1.3 KB | 1.8 KB | **6.0 KB** | 36.4 KB | −30.4 KB |
39
39
  | pattern | `content` | 2.9 KB | 1.0 KB | — | **4.0 KB** | 26.8 KB | −22.9 KB |
@@ -42,9 +42,9 @@
42
42
  | pattern | `form-validation` | 2.9 KB | 0.8 KB | — | **3.7 KB** | 26.8 KB | −23.1 KB |
43
43
  | pattern | `list` | 2.9 KB | 0.6 KB | — | **3.5 KB** | 24.3 KB | −20.8 KB |
44
44
  | pattern | `nav` | 2.9 KB | 1.2 KB | 2.6 KB | **6.8 KB** | 33.0 KB | −26.2 KB |
45
- | pattern | `overlay` | 2.9 KB | 1.2 KB | 3.8 KB | **7.9 KB** | 39.3 KB | −31.3 KB |
46
- | pattern | `pagination` | 2.9 KB | 0.8 KB | 2.8 KB | **6.5 KB** | 32.8 KB | −26.3 KB |
47
- | pattern | `reveal` | 2.9 KB | 0.3 KB | 1.2 KB | **4.4 KB** | 26.5 KB | −22.0 KB |
45
+ | pattern | `overlay` | 2.9 KB | 1.2 KB | 3.8 KB | **7.9 KB** | 39.2 KB | −31.3 KB |
46
+ | pattern | `pagination` | 2.9 KB | 0.8 KB | 2.8 KB | **6.5 KB** | 32.7 KB | −26.3 KB |
47
+ | pattern | `reveal` | 2.9 KB | 0.3 KB | 1.2 KB | **4.4 KB** | 26.4 KB | −22.0 KB |
48
48
  | pattern | `states` | 2.9 KB | 0.9 KB | — | **3.8 KB** | 26.3 KB | −22.5 KB |
49
49
  | pattern | `tabs` | 2.9 KB | 0.7 KB | 2.0 KB | **5.6 KB** | 29.1 KB | −23.5 KB |
50
50
  | pattern | `tooltip` | 2.9 KB | 0.5 KB | 2.0 KB | **5.4 KB** | 28.7 KB | −23.3 KB |
@@ -5,8 +5,8 @@
5
5
 
6
6
  | 项目 | 值 |
7
7
  |---|---|
8
- | 声明适用版本 | agentcss `0.26.0` |
9
- | 声明日期 | 2026-11-04 |
8
+ | 声明适用版本 | agentcss `0.27.0` |
9
+ | 声明日期 | 2026-11-06 |
10
10
  | 复核节奏 | 每次发版前重跑(`npm run gate` 内含全部证据) |
11
11
  | 范围 | 本库交付的 CSS / JS、全部 `demo.html`(组件页 + 图表页 + 组合页)、以及文档站生成的页面 |
12
12
  | 不在范围 | 使用者拿这些组件拼出来的业务页面(那是你的无障碍责任,不是库的) |
@@ -0,0 +1,116 @@
1
+ # agentcss 是怎么组成的
2
+
3
+ > 写给第一次接触这个项目的人。不需要先懂前端工程,读完能回答三个问题:
4
+ > 这是什么、东西怎么组织、靠什么保证不出乱子。
5
+
6
+ **一句话**:这是一个给手机网页用的**现成零件库**——按钮、表格、抽屉、整页版式都有,
7
+ 拿来就能用。它有三个少见的特点:
8
+
9
+ - **不装任何依赖**。把这个库装进项目,你的 `node_modules` 不会多出任何别的东西。
10
+ - **类名就是说明书**。样式全靠类名控制,不要求写初始化脚本、不绑定任何框架。
11
+ - **专门照顾 AI 编码助手**。每个组件都有一份机器可读的"说明书",AI 先读它再写代码,
12
+ 写完还能用配套命令自己检查。
13
+
14
+ 规模:40 个组件(全部稳定可用)、约 1 MB 的 npm 包、全库样式总量锁死在 172 KB 以内。
15
+
16
+ ## 一张图看懂
17
+
18
+ ```mermaid
19
+ flowchart TD
20
+ subgraph lib["组件本体 —— 依赖自上而下"]
21
+ direction TB
22
+ R["整页配方 recipes · 5 个<br/>拿来就是一页完整版式"]
23
+ P["组合模式 patterns · 14 个<br/>抽屉、弹层、时间线…"]
24
+ B["基础件 primitives · 17 个<br/>按钮、表格、下拉框…"]
25
+ T["设计令牌 tokens + 行为核 · 6 个<br/>颜色字号间距 + 少量交互脚本"]
26
+ R --> P --> B --> T
27
+ end
28
+
29
+ subgraph use["两种用法"]
30
+ H["给人:复制 demo 改内容"]
31
+ A["给 AI:先读机器说明书,写完自检"]
32
+ end
33
+
34
+ subgraph out["三个出口"]
35
+ NPM["npm 包"]
36
+ WEB["文档站(40 个组件页)"]
37
+ REPO["GitHub 仓库(检查系统全部公开)"]
38
+ end
39
+
40
+ H --> NPM
41
+ A --> NPM
42
+ NPM -.-> WEB
43
+ NPM -.-> REPO
44
+ ```
45
+
46
+ 读法:想用一个小按钮,拿 `02-primitives` 里的就行;想要一整页报表,直接拿
47
+ `04-recipes`。上面的层会用到下面的层,反过来不会——所以拆哪儿都拆得动。
48
+
49
+ ## 四层组件,像盖房子
50
+
51
+ | 层 | 是什么 | 打个比方 | 数量 |
52
+ |---|---|---|---|
53
+ | `01-tokens` | 颜色、字号、间距这些"数值表",浅色/深色两套主题;附 6 个小交互脚本(弹窗关闭、焦点归位这类) | 调色盘和刻度尺 | 1 套 |
54
+ | `02-primitives` | 最小的独立组件:按钮、卡片、表格、下拉框 | 砖和预制板 | 17 |
55
+ | `03-patterns` | 需要多个组件配合的场景:抽屉、弹层、标签页、时间线 | 预制房间 | 14 |
56
+ | `04-recipes` | 一整页的成品版式:报表页、应用外壳、财务页 | 样板房 | 5 |
57
+
58
+ 另有 `09-assets`(4 个独立小资产,比如图表底座),不属于这套分层,按需取用。
59
+
60
+ ## 目录地图
61
+
62
+ | 目录 | 放什么 |
63
+ |---|---|
64
+ | `01-tokens/` … `04-recipes/`、`09-assets/` | 上面说的组件本体,每个组件自带可打开的 demo 页和说明 |
65
+ | `ai/` | 给 AI 看的机器说明书(4 份 JSON)和一个命令行工具 |
66
+ | `05-audit/` | 自动检查系统(下节讲),全部公开、任何人可以运行 |
67
+ | `docs/` | 设计与工程文档(本文在这里) |
68
+ | `dist/`、`esm/`、`types/` | 构建产物,由源码自动生成,不许手改 |
69
+
70
+ ## 修改一处,处处自动跟上
71
+
72
+ 这个项目最讲究的一件事:**同一份数据只存一个地方,别的都由它自动生成。**
73
+
74
+ - 40 个组件的清单(类名、状态、无障碍属性)只写在 `ai/components.json` 一处。
75
+ 文档站的 46 个页面、给 AI 的摘要文本,全部由这份清单自动生成——
76
+ 有一个自动检查专门盯着"生成物和清单是否一致",改了清单忘了重新生成就会红。
77
+ - 关键数字同理:文档里写的组件数、检查数,都有自动检查对着源头核。
78
+
79
+ 好处很直接:文档不会和实现"各说各话",因为大部分文档本来就是实现生成的。
80
+
81
+ ## 怎么保证不出乱子:一个自动验收系统
82
+
83
+ 仓库里有一套自动检查(这个项目里叫"门禁"),共 **251 项**,像三道验收:
84
+
85
+ | 什么时候跑 | 跑多少 | 多快 | 查什么 |
86
+ |---|---|---|---|
87
+ | 每次提交前 | 5 项 | 约 7 秒 | 说明书、页面结构、文案、类型有没有被改坏 |
88
+ | 每次推送时 | 128 项 | 约 3.5 分钟 | 以上全部,外加所有不需要浏览器的检查 |
89
+ | 发布之前 | 251 项 | 约 10 分钟 | 再加真浏览器实测、逐像素视觉比对、安装包体检 |
90
+
91
+ 三条规矩让它长期可信:
92
+
93
+ 1. **每个检查先证明自己能抓到问题**——故意弄坏一个组件,检查必须变红;
94
+ 把检查关掉后问题必须重新出现。做不到这一点的检查不算数。
95
+ 2. **"跳过"不等于"通过"**——环境缺浏览器就明确标"本次没查",绝不悄悄变绿。
96
+ 3. **没测过就说没测**——真机只在 5 类环境验证过,其余内核明说"未验证",不写"应该没问题"。
97
+
98
+ ## 给 AI 编码助手怎么用
99
+
100
+ 机器说明书在 `ai/` 目录,四份 JSON 覆盖:每个组件的类名与状态、已知的坑、
101
+ 设计数值、交互脚本的行为。配套命令行工具(装完这个包就能用):
102
+
103
+ ```bash
104
+ npx agentcss ai components --json # 列出全部 40 个组件
105
+ npx agentcss ai check page.html --json # 检查你写出来的页面合不合规范
106
+ npx agentcss ai shot page.html --width=390,1440 # 按真实手机宽度截图
107
+ npx agentcss ai scaffold report/finance # 生成一页报表的最小骨架
108
+ ```
109
+
110
+ 流程就是:**读说明书 → 出骨架 → 填内容 → 自己检查**。最后一步不是可选项——
111
+ "写完了"和"写对了"是两件事。
112
+
113
+ ---
114
+
115
+ *本文数字的出处:组件数来自 `ai/components.json`;检查项数以 `05-audit/check-all.sh`
116
+ 实际注册为准;样式预算见 `docs/BUDGET.md`。均为 2026-11-05 的实测值。*
@@ -34,7 +34,7 @@
34
34
  不是手抄的,也不是推测的:探针页把结果 **POST 回采集端**,落成 JSON 后原样贴进表格。
35
35
  手机访问 `http://<电脑内网地址>:端口` 属于**非安全上下文**,
36
36
  `navigator.clipboard` 在那里根本不存在 —— 所以页面提供的是"发送到电脑"(同源 POST)作为主路径,
37
- 剪贴板只作备用(详见 `ERRORS.md` E47)。
37
+ 剪贴板只作备用。
38
38
 
39
39
  ---
40
40
 
@@ -163,7 +163,7 @@ Android 上 Chrome ≠ 系统 WebView ≠ 各家 App 内置 WebView。**入口
163
163
 
164
164
  「iOS 16.4 才支持某个特性」这类说法在本文件里没有位置,原因很直接:
165
165
  我没有在真机上验证过它,写下来就会被下一个人当成事实引用
166
- (`ERRORS.md` E13 记过一类同源事故:文档里的结论没人回来复核,最后整份文档都在骗人)。
166
+ (一类同源事故:文档里的结论没人回来复核,最后整份文档都在骗人)。
167
167
 
168
168
  ⇒ 需要版本结论时,**跑一次探针页**,用那一次的 UA 与结果说话。
169
169
  UA 是证据,版本传闻不是。
package/docs/PRINT.md CHANGED
@@ -291,8 +291,8 @@ L3 声明→生效 在真浏览器的 print 媒体下量标本页的计算值
291
291
  ```
292
292
 
293
293
  反向控制 7 条(覆盖四条腿),其中两条是历史事故的**逐字复刻**:
294
- E254(去掉 `.tabs ` 前缀,让组件 CSS 同特异性盖掉它)与
295
- E255(只删 `visibility: visible`,display 与高度全对、一个字不印)。
294
+ 去掉 `.tabs ` 前缀,让组件 CSS 同特异性盖掉它;以及
295
+ 只删 `visibility: visible`,display 与高度全对、一个字不印。
296
296
 
297
297
  ### 内容完整性是**硬判据**(2026-11-02 起)
298
298
 
@@ -356,7 +356,7 @@ npx agentcss pdf report.html --out report.pdf --max-pages=8
356
356
 
357
357
  `--paper` 支持 `a4` / `letter` / `a5` / `a3` / `legal`。边距是 CSS 简写
358
358
  (`18mm`、`"18mm 15mm"`、四段都认)。这两个参数靠**注入一条 `@page` 规则**实现,
359
- 注入位置在使用者样式之后 —— 否则同特异性会被盖掉(E254 的同一个病)。
359
+ 注入位置在使用者样式之后 —— 否则同特异性会被盖掉(同一个病)。
360
360
 
361
361
  零运行时依赖:用使用者机器上**已装好的** Chrome / Chromium / Edge。
362
362
  找不到就明确报错(退出码 3),不静默降级成空文件。指定位置:
@@ -132,7 +132,7 @@ pack-smoke.sh # npm pack → 装进空项目 → 解析入口
132
132
 
133
133
  工作流按这个顺序落地:
134
134
 
135
- 1. 校验 tag 名 == `package.json.version`,检查 `NPM_TOKEN`;
135
+ 1. 校验 tag 名 == `package.json.version`,并确认 npm 足够新(OIDC 需 ≥ 11.5.1);
136
136
  2. `npm ci`;
137
137
  3. **probe**(只读):输出 `npm=… release=… provenance=…`;
138
138
  4. `publish:check`;
@@ -156,7 +156,7 @@ pack-smoke.sh # npm pack → 装进空项目 → 解析入口
156
156
 
157
157
  ⚠️ 第 8 步的回执必须**等 registry 索引**。`npm publish` 返回 0 之后,版本端点还要几十秒才可见;
158
158
  回执在发布后立刻跑,会拿到 `npm=absent` 并把一次成功的发布报成「未完成」。
159
- `release-run.sh verify` 现在对 `absent` 重试(默认 18 次 × 10 秒),
159
+ `release-run.sh verify` 现在对 `absent` 重试(默认 42 次 × 10 秒 = 420 秒),
160
160
  对 `unreadable` **不重试**(那是接口/网络问题,等下去也不会变)。
161
161
 
162
162
  这条不是推演,是 `v0.24.2` 第三次运行的现场:npm 与 Release 都建好了,
@@ -164,6 +164,56 @@ pack-smoke.sh # npm pack → 装进空项目 → 解析入口
164
164
  registry `integrity` 与 attestation `subject` 全部一致——产物是对的,
165
165
  错的是**回执比索引快**。
166
166
 
167
+ ⚠️ 预算必须**明显大于**实测最大值,不能贴着它设。`v0.26.0`(2026-11-05)是第二次:
168
+ `npm publish` 在 `08:15:34` 返回成功,registry 到 `08:18:40` 才索引到——**186 秒**,
169
+ 而当时的上界是 `17 × 10 = 170` 秒,回执在 `08:18:38` 放弃,比真相早 2 秒。
170
+ 后果:包已经发出去了,回执却写 `npm=absent`,要再跑一遍 14 分钟的门禁才能拿到绿。
171
+ ⇒ 现在的 420 秒是实测量级的 6 倍:慢只是多等,错判是回执说谎。
172
+
173
+ ### 5.1.1 事后改包(dist-tag 等):走 trusted publishing,不要走令牌
174
+
175
+ 发布完成后要改**包管理类**的状态(例如删一个 dist-tag),**不要**想着用发布令牌去做:
176
+
177
+ - 勾了「绕过两步验证」的细粒度令牌(GAT)自 2026-07 起只能发布写,做包管理删除会被
178
+ `403 Granular access tokens that bypass two-factor authentication may not perform this action` 挡住;
179
+ - npm 网页里**没有** dist-tag 管理入口(`/package/<pkg>/dist-tags` 是 404,
180
+ Versions 的 Current Tags 只有只读表格);
181
+ - 剩下唯一合法的口子是 **trusted publishing**:它的 Allowed actions 里明确有 `npm dist-tag`。
182
+
183
+ 本仓的落地方式(一次性运维,不影响发版):
184
+
185
+ 1. npm 包设置 → Trusted Publisher → GitHub Actions,
186
+ 仓库标识(`owner/repo`,见 `package.json` 的 `repository`)与 `release.yml`,
187
+ 勾 `npm publish` 与 `npm dist-tag`;
188
+ 2. `release.yml` 里的 `dist-tag-cleanup` job:只在 `workflow_dispatch` 且填了
189
+ `dist_tag_rm` 时跑;`id-token: write` 换 OIDC 凭证;先 `npm i -g npm@latest`
190
+ (OIDC 需 ≥ 11.5.1);**不要**给 `setup-node` 传 `registry-url`
191
+ (它会写一条空的 `_authToken` 行,值指向 `NODE_AUTH_TOKEN`,先撞 ENEEDAUTH,轮不到 OIDC);
192
+ 3. 触发:
193
+
194
+ ```bash
195
+ gh workflow run release.yml -f dist_tag_rm=<tag 名>
196
+ ```
197
+
198
+ 2026-11-05 实测:run 41997717147 success,`probe-2fa` 删除后只剩 `latest`。
199
+ 这条作业**不会**被 tag 推送触发(条件是 `github.event_name == 'workflow_dispatch'` 且输入非空)。
200
+
201
+ ### 5.1.2 发版本身的凭据:OIDC,没有任何长期令牌
202
+
203
+ 发版与上面的包管理动作共用同一套信任关系:npm 侧把 `release.yml` 登记为
204
+ trusted publisher(`Status: Valid`),工作流用 `id-token: write` 现换一次短期凭证。
205
+ **仓库里不再有任何 npm 令牌 secret**,也没有 7 天/90 天到期的东西会悄悄断掉发布。
206
+
207
+ 两条硬约束(都实测踩过,`release-gate.py` 的 `workflow_problems` 逐条守着):
208
+
209
+ 1. `setup-node` **不传** `registry-url` —— 它会写一行空的 `_authToken`(值指向
210
+ `NODE_AUTH_TOKEN`),npm 先撞 `ENEEDAUTH`,根本轮不到 OIDC;
211
+ 2. 必须 `npm install -g npm@latest` —— Node 22 自带 npm 10.x,OIDC 需要 ≥ 11.5.1。
212
+
213
+ `workflow_problems` 同时**反向**断言:谁把 `NPM_TOKEN` / `NODE_AUTH_TOKEN` /
214
+ `registry-url` 加回来,门禁立刻红。这不是风格检查——长期令牌模型在 2027-01
215
+ 会连直接发布权一起失效,留着它只是把断点推迟到下一次发版。
216
+
167
217
  ### 5.2 站点链(主分支触发)
168
218
 
169
219
  | 态 | 含义 | 探测方式 |
package/docs/STABILITY.md CHANGED
@@ -30,7 +30,7 @@
30
30
  | JS 成员不消失 | `Select.open()`、`AccordionOptions.duration` 这类名字 | `deprecation-gate.py` ⑦ + `api-contract-gate.py`(d.ts ↔ 运行时**实测**比对) |
31
31
  | 声明的名字真的存在 | 不许出现"文档里有、代码里没有"的幻觉 API | `gen-ai-contract.py --check`、`dead-class-gate.py` |
32
32
  | 行为事件名不消失 | `fl:select` 这类事件名 | 只记在基线里,**没有运行时探针**(见 §6) |
33
- | CLI 子命令与 `--json` 形状 | `agentcss ai <cmd> --json` 的 11 个子命令、失败也是 JSON | `ai-layer-gate.js` ⑥′⑥″ |
33
+ | CLI 子命令与 `--json` 形状 | `agentcss ai <cmd> --json` 的 14 个子命令、失败也是 JSON | `ai-layer-gate.js` ⑥′⑥″、`facts-gate.py`(计数从 `ai/cli.js` 现算) |
34
34
  | 契约文件字段 | `ai/components.json` / `pitfalls*.json` / `behaviors.json` | `gen-ai-contract --check`、`ai-layer-gate` ⑦ |
35
35
  | 文件路径 | `02-primitives/button/button.css` 这类 `<link>` 直接指向的路径 | `dist-parity-check`、`pack-smoke.sh`、`assets-catalog-gate.py` |
36
36
  | 体积承诺 | 全库生效 CSS ≤ 172 KB,常规增长不涨价 | `perf-gate.py` + `budget-selftest.py`(见 [BUDGET.md](BUDGET.md)) |
@@ -170,3 +170,20 @@ a3d36f35ee748632ba05705e669ce950260dd81a3d32b5f3f46fadae0bd084ef # npm 包内
170
170
  ## 6. 更早的版本
171
171
 
172
172
  provenance 不是从第一个版本就有的。实测 `0.10.0` 的 attestation 接口返回 404 —— 这类版本只能核对 `gitHead`(L0),不要把它当作签名证据。
173
+
174
+ ---
175
+
176
+ ## 7. 已知偏差:2026-11-05 的历史重写
177
+
178
+ | 项 | 内容 |
179
+ |---|---|
180
+ | 发生了什么 | 仓库提交历史被重写一次,用于移除早期版本里误留的真实项目代号与本机用户名 |
181
+ | 受影响范围 | **`0.26.0` 及更早**的 provenance 里记录的 `gitCommit` |
182
+ | 具体表现 | 那个 SHA 在重写后的仓库里**不再是任何分支的祖先**;`git cat-file -p <sha>` 会报 `Not a valid object` |
183
+ | 仍然成立的部分 | npm 上的 attestation 本身**没有被改动**(sigstore 签名是不可变的)。它仍然能证明:这个 tarball 由本仓库的 `release.yml` 在某个提交上构建 |
184
+ | 从什么时候恢复正常 | `0.27.0` 起,provenance 的 `gitCommit` 与仓库历史一致 |
185
+ | GitHub 侧 | 旧提交会以**不可达对象**状态保留一段时间,知道 SHA 仍能按地址访问;之后由平台 GC。仓库已无任何 ref 指向它们 |
186
+
187
+ 核验旧版本时正确的读法:**签名证明"构建来源与工作流",不证明"那个提交现在还在仓库里"**。
188
+ 这两件事在重写前是重合的,重写后必须分开看 —— 本文件不打算把这一点藏起来。
189
+
package/index.html CHANGED
@@ -39,7 +39,7 @@ li a:active { background: var(--surface-sunken); }
39
39
  <li><a href="02-primitives/choice/demo.html">checkbox / radio / select</a><span class="r__p">单选/多选/原生 select 外观的统一控件</span></li>
40
40
  <li><a href="02-primitives/combobox/demo.html">combobox · 组合框 / 标签输入</a><span class="r__p">可搜索 + 可多选标签的输入控件</span></li>
41
41
  <li><a href="02-primitives/date-range/demo.html">date-range · 日期区间</a><span class="r__p">日期区间选择,含预设快捷范围</span></li>
42
- <li><a href="02-primitives/delta/demo.html">delta · 变化量</a><span class="r__p">变化量:涨跌方向 + 符号 + 颜色。只描述往哪边变,不判断好坏(等于好恶是 badge 的事,会计正负是 ta</span></li>
42
+ <li><a href="02-primitives/delta/demo.html">delta · 变化量</a><span class="r__p">变化量:涨跌方向 + 符号 + 颜色。</span></li>
43
43
  <li><a href="02-primitives/input/demo.html">input / field</a><span class="r__p">文本输入及其标签/帮助/错误三件套</span></li>
44
44
  <li><a href="02-primitives/metric/demo.html">metric · 指标块</a><span class="r__p">指标块:一个标签 + 一个值 + 一句上下文。数字等宽、单位分离、缺数是一等状态</span></li>
45
45
  <li><a href="02-primitives/popover/demo.html">popover · 气泡卡片</a><span class="r__p">锚定到触发元素的浮层</span></li>
@@ -48,7 +48,7 @@ li a:active { background: var(--surface-sunken); }
48
48
  <li><a href="02-primitives/separator/demo.html">separator · 分隔线</a><span class="r__p">分隔线,可带中间文字</span></li>
49
49
  <li><a href="02-primitives/skeleton/demo.html">skeleton · 骨架屏</a><span class="r__p">加载占位骨架</span></li>
50
50
  <li><a href="02-primitives/switch/demo.html">switch · 开关</a><span class="r__p">开关切换</span></li>
51
- <li><a href="02-primitives/timeline/demo.html">timeline · 时间轴</a><span class="r__p">时间轴:按时间排的事件序列(公告 / 大事记 / 版本记录)。顺序即信息,用 ol;状态交给 badge,颜色在</span></li>
51
+ <li><a href="02-primitives/timeline/demo.html">timeline · 时间轴</a><span class="r__p">时间轴:按时间排的事件序列(公告 / 大事记 / 版本记录)。</span></li>
52
52
  <li><a href="02-primitives/upload/demo.html">upload · 文件上传</a><span class="r__p">文件上传:零 JS 的原生形态 + 拖放/多文件/校验的增强形态</span></li>
53
53
  </ul>
54
54
 
@@ -97,7 +97,7 @@ li a:active { background: var(--surface-sunken); }
97
97
  </p>
98
98
  </main>
99
99
  <footer class="wrap">
100
- <p class="note">0.26.0 · 零第三方运行时依赖</p>
100
+ <p class="note">0.27.0 · 零第三方运行时依赖</p>
101
101
  </footer>
102
102
  </body>
103
103
  </html>
package/llms-full.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # agentcss — full reference (one file)
2
2
 
3
- > Generated from `ai/components.json` + `ai/pitfalls*.json` + `ai/behaviors.json` at version **0.26.0**.
3
+ > Generated from `ai/components.json` + `ai/pitfalls*.json` + `ai/behaviors.json` at version **0.27.0**.
4
4
  > Do not edit by hand: run `python 05-audit/gen-llms.py` (freshness is gated).
5
5
  > Short version: `llms.txt`. Machine contract: `ai/*.json`.
6
6
 
@@ -16,10 +16,24 @@ npx agentcss ai pitfalls --json --component=table
16
16
  npx agentcss ai tokens --json --set=dark
17
17
  npx agentcss ai tree --json --set=dark --cat=color
18
18
  npx agentcss ai validate <file> --schema=components --json
19
- npx agentcss ai check <doc.json> --profile=strict --json
19
+ npx agentcss ai check <doc.json> --profile=strict --json # the plan
20
+ npx agentcss ai check <page.html> --json # the artifact
21
+ npx agentcss ai scaffold report/finance --out report.html # skeleton only
22
+ npx agentcss ai shot <page.html> --out shot-{width}.png --width=390,1440 --full
23
+ npx agentcss ai pdf <page.html> --out page.pdf # zero deps: your Chrome
20
24
  ```
21
25
 
22
- Every subcommand accepts `--json`; failures print `{"ok":false,...}` and exit non-zero.
26
+ Every subcommand accepts `--json`. On failure it prints *one* JSON document —
27
+ `{"ok":false,"command":"…","error":{"kind":"…","message":"…"}}` — so a pipeline can
28
+ never mistake an error for data. Exit codes are one table for the whole CLI:
29
+ `0` pass · `1` the thing you checked is bad (a verdict, not a tool failure) ·
30
+ `2` bad flag or value (unknown flag, unknown enum value, empty value, repeated flag) ·
31
+ `3` environment missing · `4` render failed · `5` over budget · `6` tool bug.
32
+ `check <page.html>` is static (no network, no browser) and reports undefined class names,
33
+ stylesheets that were never linked, missing `img alt` / table name / `th scope`, skipped
34
+ heading levels and the per-layout obligations in `ai/recipes.json`. `shot` drives your
35
+ installed Chrome over a debugging pipe: the viewport is exactly the width you asked for,
36
+ and it reports horizontal overflow plus JS errors.
23
37
 
24
38
  ## 2 · Components (40)
25
39
 
package/llms.txt CHANGED
@@ -21,22 +21,43 @@ npx agentcss ai tokens --json --set=dark
21
21
  npx agentcss ai pitfalls --json --component=table
22
22
  npx agentcss ai validate ai/components.json --schema=components --json
23
23
  npx agentcss ai check page.json --profile=strict --json
24
+ npx agentcss ai check page.html --json # check the page you actually wrote
25
+ npx agentcss ai scaffold report/finance --out report.html # minimal shell (skeleton, no content)
26
+ npx agentcss ai shot page.html --out shot-{width}.png --width=390,1440 --full
27
+ # PNG at exact viewport widths
24
28
  npx agentcss ai pdf page.html --out page.pdf --max-pages=8 # HTML → PDF (zero deps: uses your Chrome)
25
29
  ```
26
30
 
31
+ `check <doc.json>` and `check <page.html>` answer **different** questions: the JSON one
32
+ validates the *plan* (node kinds, component maturity, paper obligations), the HTML one
33
+ validates the *artifact* — class names that the library does not define, stylesheets that
34
+ were never linked, missing `img alt` / table name / `th scope`, skipped heading levels,
35
+ and the per-layout obligations in `ai/recipes.json`. Both are static: no network, no browser.
36
+
37
+ `shot` drives your installed Chrome over a debugging pipe (no port, no bundled browser) so
38
+ the viewport is **exactly** the width you asked for, and reports horizontal overflow plus
39
+ JS errors. Same machine + same flags ⇒ byte-identical PNG (that is what the gate checks).
40
+
27
41
  Print / PDF: `--medium=paper` is the obligation switch — only then are `printPolicy`
28
42
  and `pageBudget` enforced. Per-component paper behaviour lives in
29
43
  `ai/components.json` → `printPolicy`. `@page` is owned by the exporter, not the library.
30
44
 
31
- Every subcommand accepts `--json`. On failure it prints
32
- `{"ok":false,"command":"…","error":{"kind":"…","message":"…"}}` and exits non-zero,
33
- so a pipeline can never mistake an error for data.
45
+ Every subcommand accepts `--json`. On failure it prints *one* JSON document —
46
+ `{"ok":false,"command":"…","error":{"kind":"…","message":"…"}}` — and exits non-zero,
47
+ so a pipeline can never mistake an error for data. There is exactly one failure shape;
48
+ the `kind` values map to exit codes: `1` verdict-negative (`findings`), `2` bad usage,
49
+ `3` environment missing, `4` render failed, `5` over budget, `6` tool bug.
50
+
51
+ Enum flags (`--set`, `--cat`, `--maturity`, `--profile`, `--medium`, `--recipe`) reject
52
+ an unknown value, an empty value, and a repeated occurrence with exit `2`. Flags a
53
+ subcommand does not understand are rejected too — they are never silently ignored.
34
54
 
35
55
  `pitfalls` needs `agentcss@0.21.0+`: `npx` resolves the *published* version, which can lag
36
56
  this repository. If a subcommand is missing, check `npx agentcss --version` first.
37
57
 
38
- There is **no MCP server**: the CLI is the whole interface (read-only queries, no daemon,
39
- no second copy of the contract to keep in sync). See `ai/START-HERE.md` §7.
58
+ There is **no MCP server**: the CLI is the whole interface (no daemon, no network,
59
+ no second copy of the contract to keep in sync). `scaffold` and `shot` write only the file
60
+ you name; every other subcommand is a read-only query. See `ai/START-HERE.md` §7.
40
61
 
41
62
  ## Use it in a page
42
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcss",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "移动 Web 组件库:CSS + 可选行为脚本,零第三方运行时依赖。样式用 bundler 引入,交互行为用 script 标签引入。",
5
5
  "keywords": [
6
6
  "css",
@@ -94,6 +94,7 @@
94
94
  "scripts": {
95
95
  "test": "node --test 05-audit/unit.test.mjs 05-audit/behavior-cores.test.mjs",
96
96
  "gate": "bash 05-audit/check-all.sh",
97
+ "gate:pre-commit": "bash 05-audit/check-all.sh --pre-commit",
97
98
  "gate:fast": "bash 05-audit/check-all.sh --fast",
98
99
  "gate:timing": "bash 05-audit/check-all.sh --timing",
99
100
  "ci:observe": "python3 05-audit/nightly-observe.py",