agentcss 0.26.0 → 0.27.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/page.css +11 -0
- package/01-tokens/responsive-demo.html +1 -1
- package/01-tokens/tokens.core.css +1 -1
- package/01-tokens/tokens.css +2 -2
- package/02-primitives/date-range/date-range.js +1 -1
- package/02-primitives/timeline/demo.html +1 -1
- package/03-patterns/drawer/demo.html +1 -1
- package/03-patterns/overlay/overlay.css +1 -1
- package/03-patterns/states/states.css +1 -1
- package/03-patterns/tabs/tabs.css +9 -0
- package/04-recipes/app-shell/README.md +1 -1
- package/04-recipes/app-shell/demo.html +1 -1
- package/04-recipes/longform/content.html +6 -6
- package/04-recipes/report/demo.html +2 -2
- package/04-recipes/table/README.md +1 -1
- package/04-recipes/table/demo.html +1 -1
- package/09-assets/bar/bar.css +1 -1
- package/09-assets/model-viewer/model-viewer.js +1 -1
- package/AGENTS.md +4 -2
- package/CHANGELOG.md +126 -5
- package/CONTRIBUTING.md +44 -3
- package/README.md +7 -4
- package/README.zh-CN.md +18 -12
- package/START-HERE.md +12 -5
- package/ai/START-HERE.md +41 -7
- package/ai/cdp.js +152 -0
- package/ai/check-html.js +658 -0
- package/ai/cli.js +578 -70
- package/ai/components.json +1 -1
- package/ai/pdf-renderer.js +177 -0
- package/ai/pdf.js +125 -80
- package/ai/recipes.json +86 -0
- package/ai/scaffold.js +195 -0
- package/ai/shot.js +237 -0
- package/ai/validate.js +65 -6
- package/dist/01-tokens/page.css +1 -1
- package/dist/03-patterns/tabs/tabs.css +1 -1
- package/dist/COSTS.md +12 -12
- package/docs/ACCESSIBILITY.md +2 -2
- package/docs/ARCHITECTURE.md +116 -0
- package/docs/LAYER-BASELINE.md +2 -2
- package/docs/PRINT.md +41 -13
- package/docs/RELEASE-PROCESS.md +52 -2
- package/docs/STABILITY.md +1 -1
- package/docs/VERIFY-RELEASE.md +17 -0
- package/index.html +3 -3
- package/llms-full.txt +19 -4
- package/llms.txt +29 -6
- package/package.json +2 -1
package/START-HERE.md
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
| 进度条 / 环 | `02-primitives/progress/progress.css` | 228 | — | 有耗时过程要给进度就要 |
|
|
48
48
|
| 浮层气泡 | `02-primitives/popover/popover.css` | 187 | **必需 345** (`popover.js`) | 要一段信息贴着触发元素浮出来就要 |
|
|
49
49
|
| 日期区间 | `02-primitives/date-range/date-range.css` | 206 | **必需 332** (`date-range.js`) | 要选起止日期就要 |
|
|
50
|
-
| 标签页 | `03-patterns/tabs/tabs.css` |
|
|
50
|
+
| 标签页 | `03-patterns/tabs/tabs.css` | 181 | **必需 316** (`tabs.js`) | 同层级内容切换就要 |
|
|
51
51
|
| 折叠面板 | `03-patterns/accordion/accordion.css` | 453 | **必需 389** (`accordion.js`) | 长表单分段就要 |
|
|
52
52
|
| 动作菜单 | `03-patterns/dropdown/dropdown.css` | 135 | **必需 575** (`dropdown.js`) | 要一组动作(新建 / 导出 / 删除)就要 |
|
|
53
53
|
| 文字提示 | `03-patterns/tooltip/tooltip.css` | 89 | **必需 299** (`tooltip.js`) | 图标按钮或缩写需要一句话解释就要 |
|
|
@@ -122,11 +122,15 @@
|
|
|
122
122
|
| `report/` | `04-recipes/report/` | 报告版式系统 v1:共享基础 + 三种版式取舍(杂志 / 科研技术 / 金融研究) |
|
|
123
123
|
| `table/` | `04-recipes/table/` | 专业数据表格:列级格式与单位,三种用途预设 |
|
|
124
124
|
|
|
125
|
-
##
|
|
125
|
+
## 抄完先做这几件事
|
|
126
126
|
|
|
127
|
-
1.
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
1. **查一遍结构** —— `node ai/cli.js check 你的页面.html`
|
|
128
|
+
查的是产物:类名在库里存不存在、引没引到定义它的样式表、图有没有 alt、
|
|
129
|
+
表有没有名字、标题有没有跳级。**它不联网、不开浏览器,一秒出结果。**
|
|
130
|
+
2. **看一眼** —— `node ai/cli.js shot 你的页面.html --out shot-{width}.png --width=390,1440 --full`
|
|
131
|
+
390 那张是按真 390 宽度排的(不是把宽图裁一刀),顺手报横向溢出与 JS 报错。
|
|
132
|
+
3. **跑一遍无障碍** —— 浏览器控制台里(或用 axe)确认对比度达标
|
|
133
|
+
4. **别改类名** —— 类名是 API(`.btn` `.btn--primary` `.field__input`),
|
|
130
134
|
改名等于换组件。**要改样式就改令牌**,全库会一起变
|
|
131
135
|
|
|
132
136
|
---
|
|
@@ -146,6 +150,9 @@ CSS 类跨框架通用,React 组件会绑死框架。
|
|
|
146
150
|
|
|
147
151
|
> **抄 `tierA-tokens.css`(A 档)或 `tokens.css`(B/C 档)→ 再抄你要的那几个 CSS → 保留类名。**
|
|
148
152
|
|
|
153
|
+
**让模型来写页面时**,另有一份按「它需要知道什么」组织的清单:`ai/START-HERE.md`
|
|
154
|
+
(`ai components` / `ai pitfalls` / `ai check` / `ai shot` / `ai scaffold` 都在那里)。
|
|
155
|
+
|
|
149
156
|
---
|
|
150
157
|
|
|
151
158
|
## 你可能还想找的(有 / 没有,一次说清)
|
package/ai/START-HERE.md
CHANGED
|
@@ -58,10 +58,12 @@
|
|
|
58
58
|
| 组件清单与成熟度 | `node ai/cli.js components --maturity=stable` |
|
|
59
59
|
| 某个组件踩过的坑 | `node ai/cli.js pitfalls --component=<id>`(数据在 `ai/pitfalls.json`) |
|
|
60
60
|
| 写文案 / 做演示 / 发页面时的坑 | `node ai/cli.js pitfalls --scope=writing`(数据在 `ai/pitfalls-writing.json`,不挂组件) |
|
|
61
|
-
| 令牌名与取值 | `node ai/cli.js tokens --
|
|
61
|
+
| 令牌名与取值 | `node ai/cli.js tokens --set=dark`、`node ai/cli.js tree`(分层视图) |
|
|
62
62
|
| 组件真实长什么样 | 该组件目录下的 `demo.html` |
|
|
63
63
|
| 英文一页速览 | `llms.txt` / `llms-full.txt`(发现层;本文件是中文详版) |
|
|
64
|
-
| 页面怎么搭 | `04-recipes/`
|
|
64
|
+
| 页面怎么搭 | `04-recipes/` 下的示例;要写报告/长文就先 `node ai/cli.js scaffold list` |
|
|
65
|
+
| 写完了对不对 | `node ai/cli.js check <页面.html>`(类名/结构/版式必有件) |
|
|
66
|
+
| 写完好不看 | `node ai/cli.js shot <页面.html> --out shot-{width}.png --width=390,1440 --full` |
|
|
65
67
|
| 令牌怎么改 | README「主题定制」(覆盖 CSS 变量,不需重新编译) |
|
|
66
68
|
|
|
67
69
|
**类名即 API**(`.btn`、`.btn--primary`)。改样式改**令牌**,不要改类名,
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
|
|
72
74
|
| 信号 | 怎么看 |
|
|
73
75
|
|---|---|
|
|
74
|
-
| 成熟度 | `ai/components.json` 的 `maturity` 是**算出来的**(判据在 `ai/components.meta.json
|
|
76
|
+
| 成熟度 | `ai/components.json` 的 `maturity` 是**算出来的**(判据在 `ai/components.meta.json`),不是人写的。它判的是**机制完整**:有 demo、有专属门禁、没有旧 `.is-*` 状态类、没有行为债。**它不判**视觉质量、真实模型生成效果、真机表现 —— `40/40 stable` 不等于 `40/40 好看`,更不等于有人在生产里用过 |
|
|
75
77
|
| 有没有 demo | 组件目录下有 `demo.html` 就能直接跑起来对照 |
|
|
76
78
|
| 有没有门禁守 | `05-audit/check-all.sh`(`npm run gate`)一次跑全,逐项打印 `PASS`/`FAIL`/`SKIP` |
|
|
77
79
|
| 浏览器支持 | README「浏览器支持」表:桌面 Chrome/Edge/Firefox 已验证(自动化);iOS Safari / 微信 / 企微 / QQ 等 7 个入口**人工实测过能力探针**(`🔍`,无回归保护);**Android 系统 WebView 无数据** |
|
|
@@ -92,11 +94,42 @@
|
|
|
92
94
|
npm ci # 按锁文件安装(首次)
|
|
93
95
|
npm test # 单元测试
|
|
94
96
|
npm run test:types # 类型契约:正例必过 + 反例必挂
|
|
95
|
-
npm run gate:
|
|
96
|
-
npm run gate
|
|
97
|
-
|
|
97
|
+
npm run gate:pre-commit # 提交前档(约 7 秒:契约 / 页面 / 文案 / 行为核心 / 类型)
|
|
98
|
+
npm run gate:fast # 静态层全部(约 4 分钟,不含浏览器类判据)
|
|
99
|
+
npm run gate # 全量(含浏览器与视觉基线;**发版前**跑,不是提交前)
|
|
100
|
+
node ai/cli.js check <doc.json> # 校验契约文档(计划:节点类型 / 组件档位)
|
|
101
|
+
node ai/cli.js check <页面.html> # 校验产物(类名 / 结构 / 版式必有件)
|
|
102
|
+
node ai/cli.js shot <页面.html> --out shot-{width}.png --width=390,1440 --full
|
|
103
|
+
# 截图(真实视口),顺带报横向溢出与 JS 报错
|
|
104
|
+
node ai/cli.js scaffold report/finance --out report.html
|
|
105
|
+
# 生成最小壳(正确骨架 + 必有件位置),不含内容
|
|
98
106
|
```
|
|
99
107
|
|
|
108
|
+
**退出码可以直接分支**(全部子命令同一套):
|
|
109
|
+
|
|
110
|
+
| 码 | 含义 | 你该做什么 |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| 0 | 通过 | — |
|
|
113
|
+
| 1 | **判定不通过**(页面/文档确实有错) | 读 `findings` / `errors`,改产物 |
|
|
114
|
+
| 2 | 用法错误(开关名、取值、重复、空值) | 改调用 |
|
|
115
|
+
| 3 | 环境不具备(没有浏览器 / 没有 node) | 换机器或跳过,别改代码 |
|
|
116
|
+
| 4 | 渲染失败 | 看 message |
|
|
117
|
+
| 5 | 超出预算(产物是完整的) | 拆文档或放宽预算 |
|
|
118
|
+
| 6 | 工具内部错误 | 报 bug,别改自己的参数 |
|
|
119
|
+
|
|
120
|
+
> `1` 和 `2` 分开是有原因的:把「页面有 3 个错」和「`--profile` 拼错了」都返回 1,
|
|
121
|
+
> 自动修复循环会去改**错的那一边**。
|
|
122
|
+
|
|
123
|
+
**计划与产物是两件事,判据不重叠**:
|
|
124
|
+
|
|
125
|
+
| 查什么 | 命令 | 能发现 | 发现不了 |
|
|
126
|
+
|---|---|---|---|
|
|
127
|
+
| 计划(doc.json) | `check <doc.json>` | 节点类型、组件是否在档位内、介质义务 | 写出来的 HTML 对不对 |
|
|
128
|
+
| 产物(.html) | `check <页面.html>` | 类名未定义 / 引错样式表 / 缺 alt、caption、scope / 标题跳级 / 版式缺件 | 页面**好看不好看**(那要 `shot` 看一眼) |
|
|
129
|
+
|
|
130
|
+
> 两个命令都**不联网、不启动浏览器**;`shot` 用你机器上已装的 Chrome/Edge,
|
|
131
|
+
> 不打包浏览器(理由同 `ai pdf`)。
|
|
132
|
+
|
|
100
133
|
改动本库**自身**时的分层(决定该动哪一层):
|
|
101
134
|
|
|
102
135
|
| 层 | 文件 | 性质 |
|
|
@@ -112,7 +145,8 @@ node ai/cli.js check <doc.json> # 校验契约文档(读 JSON,不解析原
|
|
|
112
145
|
|
|
113
146
|
有人问过要不要把 `ai/*.json` 包成一个 MCP server。结论是**不做**:
|
|
114
147
|
|
|
115
|
-
1.
|
|
148
|
+
1. 十四个子命令**全是本机命令**(读契约 / 校验文件 / 调本机浏览器)——
|
|
149
|
+
MCP 版本只是换一层传输,一个新能力都没有。
|
|
116
150
|
2. 它会是**第二份真值**:命令怎么调、参数叫什么、返回什么形状,都得再写一遍。
|
|
117
151
|
CLI 改了 server 没改,就多一处只有用户能发现的错(`--json` 那次已经踩过同类)。
|
|
118
152
|
3. 用户要多信任一个常驻进程(安装、权限、升级),而 CLI 离线、可审计、跑完即退。
|
package/ai/cdp.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* cdp.js — 零依赖 Chrome DevTools Protocol 管道客户端。
|
|
3
|
+
*
|
|
4
|
+
* shot 与 pdf 共用这一份实现。调试走 fd 3/4,以 NUL 分隔 JSON;不开放端口,
|
|
5
|
+
* 不连接使用者的浏览器,也不引入 Puppeteer 运行时依赖。
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var { spawn } = require('child_process');
|
|
10
|
+
|
|
11
|
+
/** 启动一个隔离的 headless Chrome,并返回有界的 CDP 请求接口。 */
|
|
12
|
+
function connect(chromePath, profile) {
|
|
13
|
+
var args = [
|
|
14
|
+
'--headless=new',
|
|
15
|
+
'--disable-gpu',
|
|
16
|
+
'--disable-extensions',
|
|
17
|
+
'--no-first-run',
|
|
18
|
+
'--no-default-browser-check',
|
|
19
|
+
'--hide-scrollbars',
|
|
20
|
+
'--remote-debugging-pipe',
|
|
21
|
+
'--user-data-dir=' + profile,
|
|
22
|
+
'about:blank'
|
|
23
|
+
];
|
|
24
|
+
/* 只有明确以 root 在容器中运行时才允许调用方关闭沙箱。 */
|
|
25
|
+
if (process.env.AGENTCSS_NO_SANDBOX === '1') args.unshift('--no-sandbox');
|
|
26
|
+
var child = spawn(chromePath, args, {
|
|
27
|
+
stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'],
|
|
28
|
+
windowsHide: true
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
var buf = Buffer.alloc(0);
|
|
32
|
+
var pending = Object.create(null);
|
|
33
|
+
var waiters = [];
|
|
34
|
+
var listeners = [];
|
|
35
|
+
var nextId = 0;
|
|
36
|
+
var dead = null;
|
|
37
|
+
var stderr = '';
|
|
38
|
+
|
|
39
|
+
function failAll(err) {
|
|
40
|
+
dead = dead || err;
|
|
41
|
+
Object.keys(pending).forEach(function (k) {
|
|
42
|
+
clearTimeout(pending[k].timer);
|
|
43
|
+
pending[k].reject(dead);
|
|
44
|
+
});
|
|
45
|
+
pending = Object.create(null);
|
|
46
|
+
waiters.splice(0).forEach(function (w) {
|
|
47
|
+
clearTimeout(w.timer);
|
|
48
|
+
w.reject(dead);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
var commandPipe = child.stdio[3];
|
|
53
|
+
var eventPipe = child.stdio[4];
|
|
54
|
+
if (!commandPipe || !eventPipe) {
|
|
55
|
+
failAll(new Error('无法建立 CDP 管道(Chrome 没能启动)'));
|
|
56
|
+
}
|
|
57
|
+
/* 🔴 必须有 error 监听。浏览器起不来时管道会以 EPIPE/ECONNRESET 结束,
|
|
58
|
+
而 Node 的流在没有 error 监听时**直接抛出** —— 那就成了"本工具崩了",
|
|
59
|
+
和"这份 PDF 没导出来"是两件事,也让调用方看不到结构化结论。 */
|
|
60
|
+
if (commandPipe) commandPipe.on('error', function (e) { failAll(e); });
|
|
61
|
+
if (eventPipe) eventPipe.on('error', function (e) { failAll(e); });
|
|
62
|
+
|
|
63
|
+
function dispatch(msg) {
|
|
64
|
+
if (msg.id !== undefined && pending[msg.id]) {
|
|
65
|
+
var p = pending[msg.id];
|
|
66
|
+
delete pending[msg.id];
|
|
67
|
+
clearTimeout(p.timer);
|
|
68
|
+
if (msg.error) p.reject(new Error(p.method + ':' + (msg.error.message || JSON.stringify(msg.error))));
|
|
69
|
+
else p.resolve(msg.result);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!msg.method) return;
|
|
73
|
+
for (var L = 0; L < listeners.length; L++) {
|
|
74
|
+
if (listeners[L].method === msg.method) listeners[L].fn(msg.params, msg.sessionId);
|
|
75
|
+
}
|
|
76
|
+
for (var i = waiters.length - 1; i >= 0; i--) {
|
|
77
|
+
var w = waiters[i];
|
|
78
|
+
if (w.method === msg.method && (!w.sessionId || w.sessionId === msg.sessionId)) {
|
|
79
|
+
waiters.splice(i, 1);
|
|
80
|
+
clearTimeout(w.timer);
|
|
81
|
+
w.resolve(msg.params);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (eventPipe) eventPipe.on('data', function (chunk) {
|
|
87
|
+
buf = Buffer.concat([buf, chunk]);
|
|
88
|
+
var i;
|
|
89
|
+
while ((i = buf.indexOf(0)) >= 0) {
|
|
90
|
+
var raw = buf.subarray(0, i).toString('utf8');
|
|
91
|
+
buf = buf.subarray(i + 1);
|
|
92
|
+
try { dispatch(JSON.parse(raw)); } catch (e) { /* 忽略不完整的非 JSON 浏览器输出 */ }
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
child.stdio[1].on('data', function () { /* Chrome 常规 stdout 不属于协议 */ });
|
|
96
|
+
child.stdio[2].on('data', function (chunk) {
|
|
97
|
+
stderr = (stderr + chunk.toString('utf8')).slice(-4000);
|
|
98
|
+
});
|
|
99
|
+
child.on('error', function (e) { failAll(e); });
|
|
100
|
+
child.on('exit', function (code) {
|
|
101
|
+
failAll(new Error('Chrome 提前退出(退出码 ' + code + ')'));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function send(method, params, sessionId, timeoutMs) {
|
|
105
|
+
return new Promise(function (resolve, reject) {
|
|
106
|
+
if (dead) return reject(dead);
|
|
107
|
+
var id = ++nextId;
|
|
108
|
+
var limit = timeoutMs || 15000;
|
|
109
|
+
var timer = setTimeout(function () {
|
|
110
|
+
delete pending[id];
|
|
111
|
+
reject(new Error(method + ' 超时(' + limit + 'ms 没有回应)'));
|
|
112
|
+
}, limit);
|
|
113
|
+
pending[id] = { resolve: resolve, reject: reject, timer: timer, method: method };
|
|
114
|
+
try {
|
|
115
|
+
var message = { id: id, method: method, params: params || {} };
|
|
116
|
+
/* 顶层命令不能带 sessionId:null;Chrome 只接受“字段不存在”或字符串。 */
|
|
117
|
+
if (sessionId !== undefined && sessionId !== null) message.sessionId = sessionId;
|
|
118
|
+
commandPipe.write(JSON.stringify(message) + '\0');
|
|
119
|
+
} catch (e) {
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
delete pending[id];
|
|
122
|
+
reject(e);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function waitEvent(method, sessionId, timeoutMs) {
|
|
128
|
+
return new Promise(function (resolve, reject) {
|
|
129
|
+
var limit = timeoutMs || 15000;
|
|
130
|
+
var timer = setTimeout(function () {
|
|
131
|
+
var at = waiters.findIndex(function (w) {
|
|
132
|
+
return w.method === method && w.sessionId === sessionId;
|
|
133
|
+
});
|
|
134
|
+
if (at >= 0) waiters.splice(at, 1);
|
|
135
|
+
reject(new Error('等 ' + method + ' 超时(' + limit + 'ms)'));
|
|
136
|
+
}, limit);
|
|
137
|
+
waiters.push({ method: method, sessionId: sessionId,
|
|
138
|
+
resolve: resolve, reject: reject, timer: timer });
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
send: send,
|
|
144
|
+
waitEvent: waitEvent,
|
|
145
|
+
on: function (method, fn) { listeners.push({ method: method, fn: fn }); },
|
|
146
|
+
close: function () { try { child.kill(); } catch (e) { /* 已退出 */ } },
|
|
147
|
+
stderr: function () { return stderr; },
|
|
148
|
+
child: child
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { connect: connect };
|