agentcss 0.24.1 → 0.25.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 (77) hide show
  1. package/01-tokens/README.md +1 -1
  2. package/01-tokens/tokens.css +67 -1
  3. package/01-tokens/typography.css +1 -1
  4. package/02-primitives/button/demo.html +8 -0
  5. package/02-primitives/card/README.md +15 -0
  6. package/02-primitives/choice/README.md +30 -0
  7. package/02-primitives/choice/choice.css +1 -1
  8. package/02-primitives/input/README.md +1 -0
  9. package/02-primitives/select/README.md +42 -2
  10. package/02-primitives/select/demo.html +11 -6
  11. package/02-primitives/select/select.js +184 -94
  12. package/02-primitives/timeline/demo.html +2 -2
  13. package/02-primitives/timeline/timeline.css +1 -1
  14. package/03-patterns/accordion/README.md +15 -0
  15. package/03-patterns/content/README.md +11 -0
  16. package/03-patterns/drawer/README.md +1 -1
  17. package/03-patterns/dropdown/demo.html +1 -1
  18. package/03-patterns/form-validation/README.md +18 -0
  19. package/03-patterns/form-validation/form-validation.css +12 -1
  20. package/03-patterns/list/README.md +11 -0
  21. package/03-patterns/overlay/README.md +26 -6
  22. package/03-patterns/overlay/overlay.css +22 -3
  23. package/03-patterns/overlay/overlay.js +63 -17
  24. package/03-patterns/states/README.md +15 -0
  25. package/03-patterns/states/states.css +18 -1
  26. package/03-patterns/tooltip/README.md +12 -0
  27. package/04-recipes/longform/longform.html +3 -3
  28. package/04-recipes/report/README.md +9 -0
  29. package/04-recipes/report/report.css +5 -0
  30. package/04-recipes/table/README.md +16 -0
  31. package/04-recipes/tierA-tokens.css +3 -3
  32. package/09-assets/README.md +18 -0
  33. package/09-assets/bar/bar.css +25 -10
  34. package/09-assets/echarts-adapter/demo.html +3 -1
  35. package/09-assets/echarts-adapter/echarts-adapter.js +107 -34
  36. package/09-assets/scientific-plot/README.md +2 -1
  37. package/09-assets/scientific-plot/scientific-plot.js +0 -7
  38. package/AGENTS.md +2 -1
  39. package/CHANGELOG.md +370 -18
  40. package/CONTRIBUTING.md +6 -5
  41. package/README.md +9 -2
  42. package/README.zh-CN.md +25 -10
  43. package/SECURITY.md +5 -2
  44. package/START-HERE.md +7 -7
  45. package/SUPPORT.md +86 -24
  46. package/ai/START-HERE.md +3 -2
  47. package/ai/cli.js +126 -6
  48. package/ai/components.json +258 -2
  49. package/ai/components.meta.json +257 -40
  50. package/ai/components.schema.json +96 -0
  51. package/ai/contract.schema.json +11 -0
  52. package/ai/pdf-pages.js +79 -0
  53. package/ai/pdf.js +354 -0
  54. package/ai/tokens.json +2 -2
  55. package/ai/validate.js +48 -1
  56. package/dist/01-tokens/tokens.css +1 -1
  57. package/dist/02-primitives/select/select.js +158 -71
  58. package/dist/03-patterns/form-validation/form-validation.css +1 -1
  59. package/dist/03-patterns/overlay/overlay.css +1 -1
  60. package/dist/03-patterns/overlay/overlay.js +40 -11
  61. package/dist/03-patterns/states/states.css +1 -1
  62. package/dist/04-recipes/report/report.css +1 -1
  63. package/dist/04-recipes/tierA-tokens.css +1 -1
  64. package/dist/09-assets/bar/bar.css +1 -1
  65. package/dist/09-assets/echarts-adapter/echarts-adapter.js +75 -33
  66. package/dist/09-assets/scientific-plot/scientific-plot.js +0 -7
  67. package/dist/COSTS.md +37 -37
  68. package/docs/ACCESSIBILITY.md +3 -3
  69. package/docs/FACTS.md +144 -0
  70. package/docs/LAYER-BASELINE.md +28 -3
  71. package/docs/PRINT.md +450 -0
  72. package/docs/RELEASE-PROCESS.md +286 -0
  73. package/docs/VERIFY-RELEASE.md +167 -0
  74. package/index.html +1 -1
  75. package/llms-full.txt +8 -8
  76. package/llms.txt +5 -0
  77. package/package.json +6 -1
package/README.zh-CN.md CHANGED
@@ -64,7 +64,7 @@ npm install agentcss
64
64
 
65
65
  ```bash
66
66
  # ① 钉住某一版、且拿到与我们验证过的那一份**逐字节一致**的 tarball
67
- npm install https://github.com/owain323/frontend-lib/releases/download/v0.11.3/agentcss-0.11.3.tgz
67
+ npm install https://github.com/owain323/frontend-lib/releases/download/v<版本>/agentcss-<版本>.tgz
68
68
 
69
69
  # ② 想跟着 master 走(未发布的最新提交)
70
70
  npm install github:owain323/frontend-lib
@@ -85,9 +85,9 @@ npm install github:owain323/frontend-lib
85
85
 
86
86
  ## 组件一览
87
87
 
88
- 共 **37** 个(stable 37 · 全部通过四条判据)。每个组件目录下有 `README.md`(为什么这么做)和 `demo.html`(能直接抄的结构)。
88
+ 共 **40** 个(stable 40 · 全部通过四条判据)。每个组件目录下有 `README.md`(为什么这么做)和 `demo.html`(能直接抄的结构)。
89
89
 
90
- ### primitives · 基础件(15)
90
+ ### primitives · 基础件(17)
91
91
 
92
92
  | 组件 | 根类名 | 用途 |
93
93
  |---|---|---|
@@ -106,6 +106,8 @@ npm install github:owain323/frontend-lib
106
106
  | `separator` | `separator` | 分隔线,可带中间文字 |
107
107
  | `skeleton` | `skeleton` | 加载占位骨架 |
108
108
  | `switch` | `switch` | 开关切换 |
109
+ | `timeline` | `timeline` | 时间轴:按时间排的事件序列(公告/大事记/版本记录);顺序即信息,用 `ol`,状态交给 `badge`,颜色在这里会失效 |
110
+ | `upload` | `upload` | 文件上传:零 JS 的原生形态 + 拖放/多文件/校验的增强形态 |
109
111
 
110
112
  ### patterns · 组合模式(14)
111
113
 
@@ -126,11 +128,12 @@ npm install github:owain323/frontend-lib
126
128
  | `tooltip` | `tooltip` | 悬停/聚焦提示 |
127
129
  | `tree` | `tree` | 可展开树形结构 |
128
130
 
129
- ### recipes · 整页配方(4)
131
+ ### recipes · 整页配方(5)
130
132
 
131
133
  | 组件 | 根类名 | 用途 |
132
134
  |---|---|---|
133
135
  | `analysis-report` | `report` | 组合型研究报告页面:正文与辅助栏并排,窄屏自然退回单栏 |
136
+ | `app-shell` | `shell` | 应用外壳:侧栏 + 顶栏 + 主区三段式;sticky 侧栏自滚、`min-width` 防撑破、窄屏收走侧栏 |
134
137
  | `detail-page` | `detail-page` | 实体详情页骨架:字段齐全 / 部分缺失 / 一条数据都没有,三态共用同一套 DOM |
135
138
  | `report` | `rep` | 报告版式系统 v1:共享基础 + 三套版式(杂志/科研技术/金融研究),只换一个 … |
136
139
  | `table` | `table` | 数据表格:排序/加载/空/错误四态;三种用途预设(财务/学术/统计) |
@@ -409,7 +412,8 @@ bash 05-audit/check-all.sh # 全量门禁约 130 项,本机约 8 分钟
409
412
 
410
413
  ### 明确不承诺的
411
414
 
412
- - **未在 iOS / Android 真机 WebView 上验证**(见下一节)。
415
+ - **不承诺「移动 WebView 支持」**:只在 7 个具体入口上人工跑过能力探针,
416
+ 没有自动化守着,也没做过真机整页验收(见下一节)。Android 系统 WebView **没有任何数据**。
413
417
  - 不做虚拟滚动:大表格的行数上限交给调用方判断。
414
418
  - 不提供 React / Vue 组件,只提供 CSS 类与可选的行为脚本。
415
419
  - `scientific-plot` 不做插值、回归或任何统计计算,也不宣称显著性。
@@ -419,13 +423,24 @@ bash 05-audit/check-all.sh # 全量门禁约 130 项,本机约 8 分钟
419
423
 
420
424
  | 环境 | 状态 |
421
425
  |---|---|
422
- | Chrome / Edge(桌面) | 已验证 |
423
- | Firefox(桌面) | 已验证 |
424
- | iOS Safari / WKWebView | **未在真机验证** |
425
- | Android WebView | **未在真机验证** |
426
+ | Chrome / Edge(桌面) | ✅ 已验证(自动化) |
427
+ | Firefox(桌面) | ✅ 已验证(自动化) |
428
+ | iOS Safari / 微信 / 企微 / QQ 浏览器 | 🔍 人工实测 15/15(无自动化) |
429
+ | Android 微信 | 🔍 人工实测 14/15(缺 `scrollbar-width`,有私有回退) |
430
+ | Android 百度浏览器 | 🔍 人工实测 11/15(缺 `@layer`,阻塞切层) |
431
+ | Android 系统 WebView | ❌ 无数据 |
432
+ | Android Chrome(移动版) | ⚠️ 仅桌面引擎模拟移动视口 |
433
+
434
+ **`✅` 与 `🔍` 不能相加。** `✅` 是「有自动化检查在这个引擎里跑,有人守着」;
435
+ `🔍` 是「在真实设备上人工试过一次,之后没人再看」——
436
+ 浏览器升级只会被前者发现。逐项结果、完整 UA 与复核方法见
437
+ [`docs/LAYER-BASELINE.md`](docs/LAYER-BASELINE.md) 与
438
+ [`10-review/webview/RESULTS.md`](10-review/webview/RESULTS.md)。
426
439
 
427
440
  ⚠️ 定位里写「移动 Web」指的是**设计取向**(触控优先、命中区不小于 44px),
428
- 不代表已在真机 WebView 上跑过完整测试。生产前请在自己的目标环境验证。
441
+ 不代表已被自动化覆盖,更不代表页面在真机上就能用:
442
+ 软键盘、安全区、回弹、200% 字号、读屏这些**一项都没验过**。
443
+ 生产前请在自己的目标环境验证 —— 尤其是 Android 系统 WebView,那是目前唯一 0 数据的入口。
429
444
 
430
445
  ## 主题定制
431
446
 
package/SECURITY.md CHANGED
@@ -48,8 +48,11 @@ combobox 那处 `innerHTML` 当前是安全的(已转义),但缺一道门
48
48
  | 打过的 tag(`v0.8.8` 起) | ✅ 修安全相关缺陷并回移植 |
49
49
  | `v0.8.8` 之前 | ❌ 无 tag、无锚点,无法可靠回溯 |
50
50
 
51
- **本库尚未发布到 npm**(`agentcss` 这个名字已实测未被占用,但还没占住)。
52
- 安装方式见 `README.md` 的「安装」一节 —— 里面写的是当前**真实可用**的路径。
51
+ **本库已发布到 npm**:包名 `agentcss`,首次发布 2026-10-18(`0.0.0-stage`),
52
+ `dist-tags.latest` 指向最新稳定版;从 `0.16.0` 起的版本带 **SLSA provenance**
53
+ (`npm view agentcss@<版本> dist.attestations` 可查,证明产物由本仓库的
54
+ release 工作流构建,不是本地 `npm publish` 上传的)。
55
+ 安装方式见 `README.md` 的「安装」一节;装哪个版本可以 `npm view agentcss version` 查。
53
56
 
54
57
  ### 版本与提交点的锚点
55
58
 
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 | **必需 541** (`select.js`) | 原生 select 的外观或交互不够用就要 |
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` | 319 | **必需 638** (`overlay.js`) | 需要弹层(吐司 / 模态)就要 |
58
- | 状态(空 / 加载 / 错 / 成功) | `03-patterns/states/states.css` | 252 | — | **凡是会异步取数就要** |
59
- | 表单校验 | `03-patterns/form-validation/form-validation.css` | 263 | — | 提交前要校验就要 |
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` | 364 | — | |
71
+ | report | `04-recipes/report/report.css` | 369 | — | |
72
72
  | table | `04-recipes/table/table.css` | 409 | — | |
73
- | bar | `09-assets/bar/bar.css` | 166 | **必需 320** (`bar.js`) | |
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 | **必需 610** (`scientific-plot.js`) | |
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
- | ✅ 已验证 | 有自动化检查覆盖,且**跑的就是这个引擎**(有 UA 证据) |
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
- | iOS | WKWebView | ❌ | **未在真机或模拟器验证** |
80
- | iOS | Safari | ❌ | 未在真机验证 |
81
- | Android | Android WebView | ❌ | **未在真机验证** |
82
- | Android | Chrome(移动版)| ⚠️ | 仅用**桌面**引擎模拟了移动视口,**非真机引擎** |
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
- ⚠️ 桌面 WebKit 自动化(上面那个 ✅)**不等于** iOS WKWebView:
85
- 真机上的软键盘、可视视口变化、安全区、`100vh`、页面回弹、VoiceOver
86
- 都没有被上面任何一项覆盖。
137
+ 这些没有任何一项被上面 7 行覆盖,**也没有被桌面 WebKit 的自动化覆盖**。
138
+ ⇒ 「能力 15/15」和「页面在真机上能用」是两个不同的问题,别把前者当成后者。
87
139
 
88
140
  ---
89
141
 
90
- ## 已知风险(未验证带来的)
142
+ ## 已知风险与未覆盖项
91
143
 
92
- 1. **`:has()`** —— 已在 `theme-toggle` 里避免使用;其余用法需在 WebKit 上实测
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`**(环形进度)—— Safari 15.4+ 才支持
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
- Chromium 97 不会白屏,只会"按视口断点"(即旧行为)。
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
- 1. **Android WebView 真机**(用户量最大)
117
- 2. **iOS WKWebView**(`100vh` / 滚动 / 输入框是重灾区)
118
- 3. popover 的碰撞翻转(上面"已知限制"第 1 条)
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
- > 2026-10-31 记:前两项**卡在设备上** —— 手上没有可用的安卓机,也没有 iOS 设备,
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 已验证;**iOS/Android WebView 未在真机验证** |
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
- var r = validate.validateDoc(doc, { profile: profile });
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 + ')===\n\n');
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(' / ') + ')');