agentcss 0.24.2 → 0.25.1
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.
- package/01-tokens/README.md +1 -1
- package/01-tokens/tokens.css +67 -1
- package/01-tokens/typography.css +1 -1
- package/02-primitives/button/demo.html +8 -0
- package/02-primitives/card/README.md +15 -0
- package/02-primitives/choice/README.md +30 -0
- package/02-primitives/choice/choice.css +1 -1
- package/02-primitives/input/README.md +1 -0
- package/02-primitives/select/README.md +42 -2
- package/02-primitives/select/demo.html +11 -6
- package/02-primitives/select/select.js +184 -94
- package/02-primitives/timeline/demo.html +2 -2
- package/02-primitives/timeline/timeline.css +1 -1
- package/03-patterns/accordion/README.md +15 -0
- package/03-patterns/content/README.md +11 -0
- package/03-patterns/drawer/README.md +1 -1
- package/03-patterns/dropdown/demo.html +1 -1
- package/03-patterns/form-validation/README.md +18 -0
- package/03-patterns/form-validation/form-validation.css +12 -1
- package/03-patterns/list/README.md +11 -0
- package/03-patterns/overlay/README.md +26 -6
- package/03-patterns/overlay/overlay.css +22 -3
- package/03-patterns/overlay/overlay.js +63 -17
- package/03-patterns/states/README.md +15 -0
- package/03-patterns/states/states.css +18 -1
- package/03-patterns/tooltip/README.md +12 -0
- package/04-recipes/longform/longform.html +3 -3
- package/04-recipes/report/README.md +9 -0
- package/04-recipes/report/report.css +5 -0
- package/04-recipes/table/README.md +16 -0
- package/04-recipes/tierA-tokens.css +3 -3
- package/09-assets/README.md +18 -0
- package/09-assets/bar/bar.css +25 -10
- package/09-assets/echarts-adapter/demo.html +3 -1
- package/09-assets/echarts-adapter/echarts-adapter.js +107 -34
- package/09-assets/scientific-plot/README.md +2 -1
- package/09-assets/scientific-plot/scientific-plot.js +0 -7
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +379 -18
- package/CONTRIBUTING.md +6 -5
- package/README.md +6 -2
- package/README.zh-CN.md +19 -7
- package/SECURITY.md +5 -2
- package/START-HERE.md +7 -7
- package/SUPPORT.md +86 -24
- package/ai/START-HERE.md +3 -2
- package/ai/cli.js +126 -6
- package/ai/components.json +258 -2
- package/ai/components.meta.json +257 -40
- package/ai/components.schema.json +96 -0
- package/ai/contract.schema.json +11 -0
- package/ai/pdf-pages.js +107 -0
- package/ai/pdf.js +354 -0
- package/ai/tokens.json +2 -2
- package/ai/validate.js +48 -1
- package/dist/01-tokens/tokens.css +1 -1
- package/dist/02-primitives/select/select.js +158 -71
- package/dist/03-patterns/form-validation/form-validation.css +1 -1
- package/dist/03-patterns/overlay/overlay.css +1 -1
- package/dist/03-patterns/overlay/overlay.js +40 -11
- package/dist/03-patterns/states/states.css +1 -1
- package/dist/04-recipes/report/report.css +1 -1
- package/dist/04-recipes/tierA-tokens.css +1 -1
- package/dist/09-assets/bar/bar.css +1 -1
- package/dist/09-assets/echarts-adapter/echarts-adapter.js +75 -33
- package/dist/09-assets/scientific-plot/scientific-plot.js +0 -7
- package/dist/COSTS.md +37 -37
- package/docs/ACCESSIBILITY.md +3 -3
- package/docs/FACTS.md +1 -1
- package/docs/LAYER-BASELINE.md +28 -3
- package/docs/PRINT.md +450 -0
- package/docs/RELEASE-PROCESS.md +78 -3
- package/index.html +1 -1
- package/llms-full.txt +7 -7
- package/llms.txt +5 -0
- package/package.json +6 -1
package/START-HERE.md
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
| 标签 / 徽章 | `02-primitives/badge/badge.css` | 141 | — | 有状态标记就要("已披露""停牌") |
|
|
40
40
|
| 卡片 | `02-primitives/card/card.css` | 283 | — | 有分组内容就要 |
|
|
41
41
|
| 单选 / 多选 | `02-primitives/choice/choice.css` | 424 | — | 单选 / 多选就要 |
|
|
42
|
-
| 下拉选择 | `02-primitives/select/select.css` | 151 | **必需
|
|
42
|
+
| 下拉选择 | `02-primitives/select/select.css` | 151 | **必需 631** (`select.js`) | 原生 select 的外观或交互不够用就要 |
|
|
43
43
|
| 可搜索选择 | `02-primitives/combobox/combobox.css` | 223 | **必需 477** (`combobox.js`) | 选项多到要搜索,或要选多个并留成标签就要 |
|
|
44
44
|
| 开关 | `02-primitives/switch/switch.css` | 429 | — | 有"立即生效的设置"就要 |
|
|
45
45
|
| 分隔线 | `02-primitives/separator/separator.css` | 73 | — | 要分开内容块就要 |
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
| 分页 | `03-patterns/pagination/pagination.css` | 195 | **必需 401** (`pagination.js`) | 数据多到要翻页就要 |
|
|
55
55
|
| 树形 | `03-patterns/tree/tree.css` | 122 | **必需 464** (`tree.js`) | 层级数据(文件 / 组织 / 分类)要展开就要 |
|
|
56
56
|
| 侧边抽屉 | `03-patterns/drawer/drawer.css` | 129 | **必需 361** (`drawer.js`) | 要从侧边滑出面板就要 |
|
|
57
|
-
| 弹窗 / 吐司 | `03-patterns/overlay/overlay.css` |
|
|
58
|
-
| 状态(空 / 加载 / 错 / 成功) | `03-patterns/states/states.css` |
|
|
59
|
-
| 表单校验 | `03-patterns/form-validation/form-validation.css` |
|
|
57
|
+
| 弹窗 / 吐司 | `03-patterns/overlay/overlay.css` | 338 | **必需 684** (`overlay.js`) | 需要弹层(吐司 / 模态)就要 |
|
|
58
|
+
| 状态(空 / 加载 / 错 / 成功) | `03-patterns/states/states.css` | 269 | — | **凡是会异步取数就要** |
|
|
59
|
+
| 表单校验 | `03-patterns/form-validation/form-validation.css` | 274 | — | 提交前要校验就要 |
|
|
60
60
|
| 列表增删 | `03-patterns/list/list.css` | 162 | 可选 201 (`flip.js`,FLIP 增删动画,纯增强) | 列表会动态增删就要 |
|
|
61
61
|
| 导航 / 目录 | `03-patterns/nav/nav.css` | 314 | **必需 425** (`nav.js`) · 可选 111 (`toc.js`,自动生成目录,纯增强) | 顶部要导航,或长文要目录就要 |
|
|
62
62
|
| 长文排版 | `03-patterns/content/content.css` | 286 | — | 写文档 / 长文就要 |
|
|
@@ -68,11 +68,11 @@
|
|
|
68
68
|
| analysis-report | `04-recipes/analysis-report/analysis-report.css` | 226 | — | |
|
|
69
69
|
| app-shell | `04-recipes/app-shell/app-shell.css` | 588 | **必需 288** (`shell-nav.js`) | |
|
|
70
70
|
| detail-page | `04-recipes/detail-page/detail-page.css` | 190 | — | |
|
|
71
|
-
| report | `04-recipes/report/report.css` |
|
|
71
|
+
| report | `04-recipes/report/report.css` | 369 | — | |
|
|
72
72
|
| table | `04-recipes/table/table.css` | 409 | — | |
|
|
73
|
-
| bar | `09-assets/bar/bar.css` |
|
|
73
|
+
| bar | `09-assets/bar/bar.css` | 181 | **必需 320** (`bar.js`) | |
|
|
74
74
|
| model-viewer | `09-assets/model-viewer/model-viewer.css` | 143 | **必需 423** (`model-viewer.js`) | |
|
|
75
|
-
| scientific-plot | `09-assets/scientific-plot/scientific-plot.css` | 141 | **必需
|
|
75
|
+
| scientific-plot | `09-assets/scientific-plot/scientific-plot.css` | 141 | **必需 603** (`scientific-plot.js`) | |
|
|
76
76
|
| sparkline | `09-assets/sparkline/sparkline.css` | 158 | **必需 508** (`sparkline.js`) | |
|
|
77
77
|
|
|
78
78
|
### 🔴 这些组件的 JS 是**必需的**(不是增强)
|
package/SUPPORT.md
CHANGED
|
@@ -17,14 +17,32 @@
|
|
|
17
17
|
> **那句推测是错的**:CI 的 `浏览器矩阵(firefox)` 作业一直在跑,而且是绿的
|
|
18
18
|
> (UA 自报 `X11; Linux x86_64; rv:155.0 Gecko/20100101 Firefox/155.0`)。
|
|
19
19
|
> ⚠️ 与 ❌ 都会低估覆盖面,和 ✅ 高估一样是失真。
|
|
20
|
+
>
|
|
21
|
+
> 🔴 现在有**四档**,不是三档:
|
|
22
|
+
> 原来的三档里没有「人工实测过、但没有自动化守着」这一档,
|
|
23
|
+
> 于是 7 行真机数据只能落进 `❌ 未验证`(「没有在真实环境跑过」)——
|
|
24
|
+
> 而那句话对它们是**假的**。新增 `🔍` 之后,`❌` 才重新只表示它字面上的意思。
|
|
25
|
+
> ⇒ 加这一档**不是**为了把 ❌ 说成 ✅,`✅` 仍然只给自动化(定义见下)。
|
|
20
26
|
|
|
21
27
|
## 状态定义
|
|
22
28
|
|
|
23
|
-
| 标记 | 含义 |
|
|
24
|
-
|
|
25
|
-
| ✅ 已验证 |
|
|
26
|
-
|
|
|
27
|
-
|
|
|
29
|
+
| 标记 | 含义 | 进不进入对外承诺 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| ✅ 已验证 | 有**自动化检查**覆盖,且**跑的就是这个引擎**(作业自报 UA 为证) | 进入 |
|
|
32
|
+
| 🔍 实测(人工一次) | 在**真实设备**上人工跑过能力探针,有完整 UA 与逐项结果;**没有自动化回归保护** | **不进入** |
|
|
33
|
+
| ⚠️ 部分验证 | 只覆盖了部分功能或部分视口 | 部分 |
|
|
34
|
+
| ❌ 未验证 | **没有在真实环境跑过** | 不进入 |
|
|
35
|
+
|
|
36
|
+
🔴 **`🔍` 与 `✅` 不能互相代替,也不能相加。**
|
|
37
|
+
|
|
38
|
+
| | 说的是什么 | 浏览器升级时谁会发现 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| ✅ | 「有自动化检查在这个引擎里跑,有人替我们守着」 | 门禁会红 |
|
|
41
|
+
| 🔍 | 「我们确实在这台设备上试过一次,之后没人再看」 | **没人** |
|
|
42
|
+
|
|
43
|
+
⇒ 所以 `🔍` 永远**不升级**为发布承诺。它只回答一个问题:
|
|
44
|
+
**"这个环境我们到底试没试过"** —— 而不是"能不能保证它一直能用"。
|
|
45
|
+
把 `🔍` 读成 `✅`,就是这一页历史上记过的那种失真(见顶部两条更正)。
|
|
28
46
|
|
|
29
47
|
---
|
|
30
48
|
|
|
@@ -72,32 +90,72 @@ grep 一条都数不到),还有 `skip "…"`(这台机器跑不了的门
|
|
|
72
90
|
|
|
73
91
|
---
|
|
74
92
|
|
|
75
|
-
##
|
|
93
|
+
## 移动环境:试过的 与 没试过的
|
|
94
|
+
|
|
95
|
+
上面那张表是「有人守着」的自动化覆盖。移动端目前**没有**这种覆盖,
|
|
96
|
+
但有另一类证据:**在真实设备上人工跑过能力探针**。两者必须分开看。
|
|
97
|
+
|
|
98
|
+
### 试过一次的(🔍 人工实测,无回归保护)
|
|
99
|
+
|
|
100
|
+
7 行数据来自在真实设备上打开 [`capability-probe.html`](10-review/webview/capability-probe.html)。
|
|
101
|
+
完整 UA 与逐项结论在 [`docs/LAYER-BASELINE.md`](docs/LAYER-BASELINE.md);
|
|
102
|
+
15 项能力分别是什么、怎么自己复跑一遍,在 [`10-review/webview/RESULTS.md`](10-review/webview/RESULTS.md)。
|
|
103
|
+
本节 7 行**全部是 `🔍`** —— 没有一行有自动化守着。
|
|
76
104
|
|
|
77
|
-
|
|
|
105
|
+
| 入口 | UA 自述的环境 | 实测 | 缺什么 |
|
|
78
106
|
|---|---|---|---|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
107
|
+
| Safari(系统浏览器) | iPhone · iOS 18_7 | **15 / 15** | 无 |
|
|
108
|
+
| 微信 8.0.47 | Android 14 · XWEB(Chromium 146) | **14 / 15** | `scrollbar-width` —— 有 `::-webkit-scrollbar` 私有回退,观感可接受 |
|
|
109
|
+
| 微信 8.0.79 | iPhone · iOS 26_6_2 ⚠️ 用户口述设备为平板,但 UA 与视口均为 iPhone ⇒ 在 iPad 上以兼容模式运行 | **15 / 15** | 无 |
|
|
110
|
+
| 企业微信 5.0.12 | iPhone · iOS 18_7 ⚠️ 同上,兼容模式 | **15 / 15** | 无 |
|
|
111
|
+
| 百度浏览器 13.51 | Android 14 · Chromium 97 | **11 / 15** | 🔴 `@layer`(阻塞切层);`dvh`、`scrollbar-width` 有降级;原生 Popover 本库未使用 |
|
|
112
|
+
| Safari 26.6.1 | iPad · 桌面版 UA | **15 / 15** | 无 |
|
|
113
|
+
| QQ 浏览器 20.6.6 | iPhone · iOS 26_6_2 | **15 / 15** | 无 |
|
|
114
|
+
|
|
115
|
+
### 没有真机数据的(❌ / ⚠️)
|
|
116
|
+
|
|
117
|
+
| 平台 | 入口 / 宿主 | 状态 | 说明 |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| Android | **系统 WebView** | ❌ | **没有任何真机数据** —— 移动端最大的一块空白,也是用户量最大的入口 |
|
|
120
|
+
| Android | Chrome(移动版) | ⚠️ | 只用**桌面**引擎模拟了移动视口,**不是真机引擎** |
|
|
121
|
+
| iOS | 其它 WKWebView 宿主 App | ❌ | 微信 / 企微**这两个宿主**已测(见上表);自建 App、抖音 / 小红书 / 支付宝等内置浏览器**都没有数据** |
|
|
122
|
+
|
|
123
|
+
🔴 **「内核相同」不等于「宿主相同」。** 同一个 WKWebView,宿主 App 可以关掉部分能力
|
|
124
|
+
(内联播放、脚本桥、缓存策略)。⇒ 上表按**入口**分行,不按内核归类 ——
|
|
125
|
+
这正是探针页那句警告的意思:入口写不清楚,数据就没法用。
|
|
126
|
+
|
|
127
|
+
### ⚠️ 就算是 🔍 的那 7 行,也只覆盖了「能力有没有」
|
|
128
|
+
|
|
129
|
+
探针只回答「这个引擎认不认这 15 项语法」。它**不回答**:
|
|
130
|
+
|
|
131
|
+
- 软键盘弹起时 `100vh` 塌不塌、输入框会不会被键盘盖住
|
|
132
|
+
- 安全区(刘海 / 底部横条)有没有正确留白
|
|
133
|
+
- 页面回弹、滚动惯性、`position: sticky` 在真机上顺不顺
|
|
134
|
+
- VoiceOver / TalkBack 念出来对不对
|
|
135
|
+
- 200% 字号下会不会裁字、320px 宽会不会横滚
|
|
83
136
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
都没有被上面任何一项覆盖。
|
|
137
|
+
这些没有任何一项被上面 7 行覆盖,**也没有被桌面 WebKit 的自动化覆盖**。
|
|
138
|
+
⇒ 「能力 15/15」和「页面在真机上能用」是两个不同的问题,别把前者当成后者。
|
|
87
139
|
|
|
88
140
|
---
|
|
89
141
|
|
|
90
|
-
##
|
|
142
|
+
## 已知风险与未覆盖项
|
|
91
143
|
|
|
92
|
-
1. **`:has()`** ——
|
|
144
|
+
1. **`:has()`** —— `theme-toggle` 里已避免使用;其余用法在**5 个 WebKit 系入口**上实测通过
|
|
145
|
+
(iPhone Safari / iOS 微信 / iOS 企微 / iPad Safari / iPhone QQ,见上表),
|
|
146
|
+
但那是人工一次、**没有自动化守着** ⇒ 后续改动仍需真机复跑
|
|
93
147
|
2. **`backdrop-filter`** —— Firefox 与旧 WebKit 行为不一致,用了 `@supports` 兜底
|
|
94
|
-
3. **`conic-gradient`**(环形进度)——
|
|
148
|
+
3. **`conic-gradient`**(环形进度)—— **探针没有覆盖这一项,也没有真机数据**。
|
|
149
|
+
原来这里写的是「Safari 15.4+ 才支持」,那是**版本传闻**:按 `docs/LAYER-BASELINE.md`
|
|
150
|
+
的纪律,未经真机验证的版本结论不当事实引用 ⇒ 要结论就先把它加进探针、跑一次再说
|
|
95
151
|
4. **滚动条样式** —— `::-webkit-scrollbar` 与 Firefox 的 `scrollbar-width` 是两套语法,组件里都写了
|
|
96
152
|
5. **iOS 输入框的 `font-size < 16px` 会触发缩放** —— 输入类组件已避免
|
|
97
153
|
6. **`@container`**(0.16.0 起使用,`[data-cq]` 标记的槽位)—— 与 `@layer` 同类:
|
|
98
154
|
不支持时**整块丢弃**。区别在于**基础样式不在块里**,所以降级后与标记前完全一致;
|
|
99
155
|
`container-query-check.js` 每次运行都会把 `@container` 剥掉再跑一遍,证明布局不塌。
|
|
100
|
-
|
|
156
|
+
⚠️ 这一条的依据是**剥离实验**(人为去掉 `@container` 看布局),
|
|
157
|
+
不是"在 Chromium 97 真机上跑过" —— 降级路径对,但旧内核上究竟怎么排版,
|
|
158
|
+
探针没有覆盖这一项,所以**没有真机结论**。
|
|
101
159
|
|
|
102
160
|
## 已知限制(已验证存在,不是"待验证")
|
|
103
161
|
|
|
@@ -111,13 +169,17 @@ grep 一条都数不到),还有 `skip "…"`(这台机器跑不了的门
|
|
|
111
169
|
|
|
112
170
|
## 打算怎么补
|
|
113
171
|
|
|
114
|
-
|
|
172
|
+
按「缺的到底是什么」排序,不是按好做程度:
|
|
115
173
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
174
|
+
| 优先级 | 缺什么 | 为什么排这个位置 |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| 1 | **Android 系统 WebView 真机** | 唯一 **0 数据**的入口,且是用户量最大的一块 |
|
|
177
|
+
| 2 | **已有 7 行的复跑** | 让「一次性实测」变成「跟得上浏览器升级」—— 不复跑,`🔍` 只会越来越旧 |
|
|
178
|
+
| 3 | **真机整页验收**(软键盘 / 安全区 / 回弹 / 200% 字号 / 读屏) | 探针永远测不了这几项,只能拿设备点一遍 |
|
|
179
|
+
| 4 | popover 的碰撞翻转(上面「已知限制」第 1 条) | 这是功能取舍,不是验证缺口 |
|
|
119
180
|
|
|
120
181
|
⚠️ 在补齐之前,请勿把「移动 WebView 支持」当作已验证的能力对外承诺。
|
|
182
|
+
`🔍` 的作用是让读者看见「试过什么、还差什么」,它本身**不是**承诺。
|
|
121
183
|
|
|
122
|
-
>
|
|
123
|
-
>
|
|
184
|
+
> 记:前两项**卡在设备上** —— 手上没有可用的安卓机,也没有 iOS 设备,
|
|
185
|
+
> 云真机需要账号。这条不是「忘了做」,是「缺外部条件」,所以不写进版本承诺。
|
package/ai/START-HERE.md
CHANGED
|
@@ -74,9 +74,10 @@
|
|
|
74
74
|
| 成熟度 | `ai/components.json` 的 `maturity` 是**算出来的**(判据在 `ai/components.meta.json`),不是人写的 |
|
|
75
75
|
| 有没有 demo | 组件目录下有 `demo.html` 就能直接跑起来对照 |
|
|
76
76
|
| 有没有门禁守 | `05-audit/check-all.sh`(`npm run gate`)一次跑全,逐项打印 `PASS`/`FAIL`/`SKIP` |
|
|
77
|
-
| 浏览器支持 | README「浏览器支持」表:桌面 Chrome/Edge/Firefox
|
|
77
|
+
| 浏览器支持 | README「浏览器支持」表:桌面 Chrome/Edge/Firefox 已验证(自动化);iOS Safari / 微信 / 企微 / QQ 等 7 个入口**人工实测过能力探针**(`🔍`,无回归保护);**Android 系统 WebView 无数据** |
|
|
78
78
|
|
|
79
|
-
⚠️
|
|
79
|
+
⚠️ **`🔍` 只表示「人工试过一次」,不是「已验证」**。它没有自动化守着,
|
|
80
|
+
也不代表页面在真机上能用 —— 别把两者混着说,更不要替它打包票。
|
|
80
81
|
|
|
81
82
|
## 5 · 什么时候应当沿用宿主项目现有技术
|
|
82
83
|
|
package/ai/cli.js
CHANGED
|
@@ -31,6 +31,9 @@ var fs = require('fs');
|
|
|
31
31
|
var path = require('path');
|
|
32
32
|
var validate = require('./validate.js');
|
|
33
33
|
var patch = require('./patch.js');
|
|
34
|
+
/* 打印导出。⚠️ 只在 `pdf` 子命令里用 —— 主路径不许因为它多加载任何东西
|
|
35
|
+
(`require` 是同步的,放在文件顶部是为了不把 CLI 拆成两截,代价是 ~2 KB 源码)。 */
|
|
36
|
+
var pdfExport = require('./pdf.js');
|
|
34
37
|
|
|
35
38
|
var AI = __dirname;
|
|
36
39
|
|
|
@@ -107,17 +110,22 @@ function flag(name, def) {
|
|
|
107
110
|
// ---------------------------------------------------------------- check
|
|
108
111
|
function cmdCheck(args) {
|
|
109
112
|
var file = args[0];
|
|
110
|
-
if (!file) die('用法:ai check <doc.json> [--profile=creative|standard|strict]');
|
|
113
|
+
if (!file) die('用法:ai check <doc.json> [--profile=creative|standard|strict] [--medium=screen|paper]');
|
|
111
114
|
var doc = readJSON(file);
|
|
112
115
|
var profile = flag('profile', doc.profile || 'standard');
|
|
113
|
-
|
|
116
|
+
/* 介质是**与档位正交**的第二根轴:
|
|
117
|
+
strict+paper 与 creative+paper 都必须合法,做成第四个 profile 会把两维压成一维。
|
|
118
|
+
默认 screen ⇒ 今天所有调用零成本;paper 才追加打印义务。 */
|
|
119
|
+
var medium = flag('medium', 'screen');
|
|
120
|
+
var r = validate.validateDoc(doc, { profile: profile, medium: medium });
|
|
114
121
|
var json = isJson();
|
|
115
122
|
|
|
116
123
|
if (json) {
|
|
117
124
|
out(r);
|
|
118
125
|
process.exit(r.ok ? 0 : 1);
|
|
119
126
|
}
|
|
120
|
-
process.stdout.write(' === 契约校验(档位:' + r.profile
|
|
127
|
+
process.stdout.write(' === 契约校验(档位:' + r.profile
|
|
128
|
+
+ ',介质:' + r.medium + ')===\n\n');
|
|
121
129
|
if (r.errors.length === 0) {
|
|
122
130
|
process.stdout.write(' OK ' + Object.keys(doc.nodes || {}).length +
|
|
123
131
|
' 个节点,无违规\n');
|
|
@@ -629,6 +637,97 @@ function cmdProfile() {
|
|
|
629
637
|
process.exit(0);
|
|
630
638
|
}
|
|
631
639
|
|
|
640
|
+
// ---------------------------------------------------------------- pdf
|
|
641
|
+
/* 退出码与其它子命令分开:调用方(CI / 编辑器)要能分清
|
|
642
|
+
① 参数写错 ② 这台机器上没有浏览器 ③ 渲染失败 ④ 超预算。
|
|
643
|
+
合成一个 1 的话,"没有 Chrome"会被当成"页面有错"去查,方向就错了。 */
|
|
644
|
+
var PDF_EXIT = {
|
|
645
|
+
usage: 2, 'no-chrome': 3, render: 4, timeout: 4, unreadable: 4, 'over-budget': 5
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
function cmdPdf(args) {
|
|
649
|
+
/* ⚠️ pdf 的参数错误一律退出 2(与 PDF_EXIT.usage 一致),**不能走 die()** ——
|
|
650
|
+
die() 退出 1,而 1 在这套约定里表示"进程/未分类失败"。
|
|
651
|
+
两种用法错误给两个退出码,调用方就没法按码分支了。 */
|
|
652
|
+
function bad(msg) {
|
|
653
|
+
if (isJson()) out({ ok: false, command: 'pdf', code: 'usage', message: msg });
|
|
654
|
+
else process.stderr.write(' [FAIL] ' + msg + '\n');
|
|
655
|
+
process.exit(PDF_EXIT.usage);
|
|
656
|
+
}
|
|
657
|
+
var src = args[0];
|
|
658
|
+
if (!src) {
|
|
659
|
+
bad('用法:agentcss pdf <页面.html|URL> --out <file.pdf> '
|
|
660
|
+
+ '[--paper=a4|letter|a5|a3|legal] [--margin="18mm 15mm"] '
|
|
661
|
+
+ '[--max-pages=N] [--chrome=<path>] [--json]');
|
|
662
|
+
}
|
|
663
|
+
var remote = /^https?:\/\//i.test(src);
|
|
664
|
+
var outFile = flag('out', null);
|
|
665
|
+
if (outFile === null || outFile === true) {
|
|
666
|
+
/* 远程地址推不出本地文件名,必须显式给 —— 猜一个出来只会让人找不到文件在哪 */
|
|
667
|
+
if (remote) bad('远程地址必须显式给 --out');
|
|
668
|
+
outFile = src.replace(/\.(html?|xhtml)$/i, '') + '.pdf';
|
|
669
|
+
}
|
|
670
|
+
var budget = flag('max-pages', null);
|
|
671
|
+
if (budget === true) bad('--max-pages 后面要跟数字(不设 = 无界)');
|
|
672
|
+
/* 先按字符串校验再转数字:让报错里出现**使用者打进去的那串字符**。
|
|
673
|
+
直接 Number() 再报错时,`abc` 已经变成 NaN,报「收到 NaN/null」是在念实现细节(E264)。 */
|
|
674
|
+
if (budget !== null && !/^\d+$/.test(String(budget))) {
|
|
675
|
+
bad('--max-pages 必须是 ≥1 的整数,收到 ' + JSON.stringify(String(budget)));
|
|
676
|
+
}
|
|
677
|
+
var chrome = flag('chrome', null);
|
|
678
|
+
if (chrome === true) bad('--chrome 后面要跟可执行文件路径');
|
|
679
|
+
|
|
680
|
+
var r = pdfExport.exportPdf({
|
|
681
|
+
src: src,
|
|
682
|
+
out: outFile,
|
|
683
|
+
paper: flag('paper', pdfExport.DEFAULT_PAPER),
|
|
684
|
+
margin: flag('margin', pdfExport.DEFAULT_MARGIN),
|
|
685
|
+
budget: budget === null ? null : Number(budget),
|
|
686
|
+
chrome: chrome
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
var box = r.mediaBox
|
|
690
|
+
? Math.round(r.mediaBox.width) + '×' + Math.round(r.mediaBox.height) + 'pt' : '?';
|
|
691
|
+
var report = {
|
|
692
|
+
ok: r.ok,
|
|
693
|
+
command: 'pdf',
|
|
694
|
+
code: r.ok ? null : r.code,
|
|
695
|
+
message: r.ok ? null : r.message,
|
|
696
|
+
out: r.out || null,
|
|
697
|
+
pages: r.pages === undefined ? null : r.pages,
|
|
698
|
+
budget: r.budget === undefined ? null : r.budget,
|
|
699
|
+
paper: r.paper || null,
|
|
700
|
+
paperSize: box,
|
|
701
|
+
margin: r.margin || null,
|
|
702
|
+
bytes: r.bytes || null,
|
|
703
|
+
injected: r.injected === undefined ? null : r.injected,
|
|
704
|
+
chrome: r.chrome || null,
|
|
705
|
+
ms: r.ms || null
|
|
706
|
+
};
|
|
707
|
+
if (isJson()) {
|
|
708
|
+
out(report);
|
|
709
|
+
process.exit(r.ok ? 0 : (PDF_EXIT[r.code] || 4));
|
|
710
|
+
}
|
|
711
|
+
if (!r.ok) {
|
|
712
|
+
process.stderr.write(' [FAIL] ' + r.message + '\n');
|
|
713
|
+
if (r.code === 'over-budget') {
|
|
714
|
+
process.stderr.write(' (退出码 ' + (PDF_EXIT[r.code] || 4)
|
|
715
|
+
+ ':预算超了不是渲染出错,产物是完整的)\n');
|
|
716
|
+
}
|
|
717
|
+
process.exit(PDF_EXIT[r.code] || 4);
|
|
718
|
+
}
|
|
719
|
+
process.stdout.write(' OK ' + r.out + '\n');
|
|
720
|
+
process.stdout.write(' ' + r.pages + ' 页 · ' + report.paperSize + ' · '
|
|
721
|
+
+ (r.margin ? '边距 ' + r.margin : '边距由页面决定')
|
|
722
|
+
+ ' · ' + r.bytes + ' 字节 · ' + r.ms + ' ms\n');
|
|
723
|
+
process.stdout.write(' 预算 ' + (r.budget === null ? '未设(无界)' : r.budget + ' 页')
|
|
724
|
+
+ ' · ' + (r.injected
|
|
725
|
+
? '纸幅 ' + r.paper
|
|
726
|
+
: '纸幅由页面自己的 CSS 决定(远程页面:未注入 @page,--paper/--margin 不生效)')
|
|
727
|
+
+ '\n');
|
|
728
|
+
process.exit(0);
|
|
729
|
+
}
|
|
730
|
+
|
|
632
731
|
function cmdCapabilities() {
|
|
633
732
|
var doc = readJSON(path.join(AI, 'capabilities.json'));
|
|
634
733
|
if (isJson()) { out(doc); process.exit(0); }
|
|
@@ -656,7 +755,8 @@ var CMDS = {
|
|
|
656
755
|
tree: cmdTree,
|
|
657
756
|
profile: cmdProfile,
|
|
658
757
|
capabilities: cmdCapabilities,
|
|
659
|
-
pitfalls: cmdPitfalls
|
|
758
|
+
pitfalls: cmdPitfalls,
|
|
759
|
+
pdf: cmdPdf
|
|
660
760
|
};
|
|
661
761
|
|
|
662
762
|
function main() {
|
|
@@ -664,7 +764,7 @@ function main() {
|
|
|
664
764
|
if (!argv.length || argv[0] === 'help' || argv[0] === '--help') {
|
|
665
765
|
process.stdout.write(
|
|
666
766
|
'agentcss ai —— 独立校验入口(零依赖、不联网、不依赖任何模型)\n\n' +
|
|
667
|
-
' ai check <doc.json> [--profile=creative|standard|strict] [--json]\n' +
|
|
767
|
+
' ai check <doc.json> [--profile=creative|standard|strict] [--medium=screen|paper] [--json]\n' +
|
|
668
768
|
' ai validate <file.json> --schema=contract|components|tokens|tokensAudience|tokensTree|deprecations|patch [--json]\n' +
|
|
669
769
|
' ai diff <改动前.json> <改动后.json> [--json] 按语义 id 比对\n' +
|
|
670
770
|
' ai patch <doc.json> <patch.json> [-o out.json] [--json]\n' +
|
|
@@ -675,11 +775,31 @@ function main() {
|
|
|
675
775
|
' ai pitfalls [--component=<id>] [--scope=component|layout|process|writing] [--tag=<t>] [--json]\n' +
|
|
676
776
|
' ai profile [--json] 列出三档及其差异\n' +
|
|
677
777
|
' ai capabilities [--json] 列出可协商的能力\n' +
|
|
778
|
+
' ai pdf <页面.html|URL> --out <file.pdf> [--paper=a4|letter|a5|a3|legal]\n' +
|
|
779
|
+
' [--margin="18mm 15mm"] [--max-pages=N] [--chrome=<path>] [--json]\n' +
|
|
780
|
+
' HTML → PDF。零依赖(用你机器上已装的 Chrome),@page 由导出方给。\n' +
|
|
781
|
+
'\n' +
|
|
782
|
+
' 打印:`--medium=paper` 才会校验 printPolicy 与 pageBudget;默认 screen 零成本。\n' +
|
|
783
|
+
' 每个组件的纸面行为:ai/components.json 的 printPolicy。\n' +
|
|
678
784
|
'\n' +
|
|
679
|
-
' 每个子命令都支持 --json;失败时输出 {ok:false, command, error} 并以非 0 退出。\n'
|
|
785
|
+
' 每个子命令都支持 --json;失败时输出 {ok:false, command, error} 并以非 0 退出。\n' +
|
|
786
|
+
' 前缀 `ai` 可写可不写:`agentcss ai components` 与 `agentcss components` 等价。\n');
|
|
680
787
|
process.exit(0);
|
|
681
788
|
}
|
|
682
789
|
var name = argv.shift();
|
|
790
|
+
/* 🔴 `ai` 是可选的**前缀别名**(2026-11-02 补):
|
|
791
|
+
帮助里的命令一直写成 `ai components`,AGENTS.md / llms.txt / STABILITY.md /
|
|
792
|
+
VERIFY-RELEASE.md 也都教 `npx agentcss ai components --json` ——
|
|
793
|
+
而分发器只认 `components`,于是「照着文档敲第一条命令」直接得到
|
|
794
|
+
「未知子命令:ai」。消费者视角的硬伤:文档里最靠前的那条命令不能用,
|
|
795
|
+
后面写得再准也读不到。
|
|
796
|
+
两种写法都认,免得已经抄下去的人再改一遍。 */
|
|
797
|
+
if (name === 'ai') {
|
|
798
|
+
name = argv.length ? argv.shift() : '';
|
|
799
|
+
if (!name) {
|
|
800
|
+
die('`agentcss ai` 后面要跟子命令(可用:' + Object.keys(CMDS).join(' / ') + ')');
|
|
801
|
+
}
|
|
802
|
+
}
|
|
683
803
|
CURRENT_CMD = name;
|
|
684
804
|
var fn = CMDS[name];
|
|
685
805
|
if (!fn) die('未知子命令:' + name + '(可用:' + Object.keys(CMDS).join(' / ') + ')');
|