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/ai/scaffold.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* scaffold.js — 三个「最小壳」:正确的骨架,不含内容
|
|
3
|
+
*
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* 🔴 为什么给壳,而不是给模板引擎
|
|
6
|
+
* ----------------------------------------------------------------------------
|
|
7
|
+
* 这一层最容易做歪的方向是"喂一段内容,吐一页报告" —— 那需要解析输入、
|
|
8
|
+
* 决定结构、再选版式,最后产出的东西**没法被断言**(对不对取决于读的人)。
|
|
9
|
+
*
|
|
10
|
+
* 这里只做一件事:把**前置条件**给对。漏一条 `<link>`、少一层 `.rep__grid`、
|
|
11
|
+
* 把文章标题写成 h3,页面不会报错,只会静默地长得不对 —— 而这些都是
|
|
12
|
+
* "知道就不会错、不知道就一定会错"的东西。
|
|
13
|
+
*
|
|
14
|
+
* ⇒ 壳只保证三件事:
|
|
15
|
+
* ① 引对了样式表(少一个 = 那部分完全没样式)
|
|
16
|
+
* ② 骨架层级对(.rep > header > .rep__grid > .rep__body/.rep__aside)
|
|
17
|
+
* ③ 每一版**必有件**的位置留出来了(ai/recipes.json 里那几条)
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ 三个壳都必须能通过 `ai check --html --recipe=<自己>`。
|
|
20
|
+
* 这不是口号:05-audit/html-check-gate.js 每次都会把三个壳跑一遍,
|
|
21
|
+
* 壳和判据一旦对不上就红。壳里少写一个必有件,门禁当场抓到。
|
|
22
|
+
*/
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
var fs = require('fs');
|
|
26
|
+
var path = require('path');
|
|
27
|
+
|
|
28
|
+
var AI = __dirname;
|
|
29
|
+
|
|
30
|
+
/** 壳里出现的 CSS 清单(相对库根)。只列真正用到的,不多引。 */
|
|
31
|
+
var SHEETS = [
|
|
32
|
+
'01-tokens/tokens.css',
|
|
33
|
+
'01-tokens/typography.css',
|
|
34
|
+
'01-tokens/page.css',
|
|
35
|
+
'04-recipes/table/table.css',
|
|
36
|
+
'04-recipes/report/report.css'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function head(title, cssPrefix) {
|
|
40
|
+
var p = String(cssPrefix || 'node_modules/agentcss').replace(/\/+$/, '');
|
|
41
|
+
var links = SHEETS.map(function (s) {
|
|
42
|
+
return '<link rel="stylesheet" href="' + p + '/' + s + '">';
|
|
43
|
+
}).join('\n');
|
|
44
|
+
return '<!doctype html>\n'
|
|
45
|
+
+ '<html lang="zh-CN">\n'
|
|
46
|
+
+ '<head>\n'
|
|
47
|
+
+ '<meta charset="utf-8">\n'
|
|
48
|
+
+ '<meta name="viewport" content="width=device-width, initial-scale=1">\n'
|
|
49
|
+
+ '<title>' + title + '</title>\n'
|
|
50
|
+
+ links + '\n'
|
|
51
|
+
+ '</head>\n';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* 表格:三个壳共用。caption + th[scope] 是硬要求,壳里就写对,
|
|
55
|
+
免得使用者照着抄的时候把这两条一起漏掉。 */
|
|
56
|
+
function table() {
|
|
57
|
+
return ' <div class="table">\n'
|
|
58
|
+
+ ' <table>\n'
|
|
59
|
+
+ ' <caption>表 1 · 三组对比(写"这张表比的是什么",不是"这是数据")</caption>\n'
|
|
60
|
+
+ ' <thead>\n'
|
|
61
|
+
+ ' <tr><th scope="col">项目</th><th scope="col">A 组</th>'
|
|
62
|
+
+ '<th scope="col">B 组</th><th scope="col">C 组</th></tr>\n'
|
|
63
|
+
+ ' </thead>\n'
|
|
64
|
+
+ ' <tbody>\n'
|
|
65
|
+
+ ' <tr><th scope="row">指标一</th><td>—</td><td>—</td><td>—</td></tr>\n'
|
|
66
|
+
+ ' <tr><th scope="row">指标二</th><td>—</td><td>—</td><td>—</td></tr>\n'
|
|
67
|
+
+ ' </tbody>\n'
|
|
68
|
+
+ ' </table>\n'
|
|
69
|
+
+ ' </div>\n';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function figure(wide) {
|
|
73
|
+
return ' <figure class="rep__fig' + (wide ? ' rep__fig--wide' : '') + '">\n'
|
|
74
|
+
+ ' <!-- 放图:09-assets/scientific-plot 的容器 id,或直接 <img src alt> -->\n'
|
|
75
|
+
+ ' <div id="chart"></div>\n'
|
|
76
|
+
+ ' <figcaption class="rep__cap">图 1 · 图说(说清"看什么",不是"这是什么")</figcaption>\n'
|
|
77
|
+
+ ' </figure>\n';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function section(title, body) {
|
|
81
|
+
return ' <section class="rep__sec">\n'
|
|
82
|
+
+ ' <h3>' + title + '</h3>\n'
|
|
83
|
+
+ body
|
|
84
|
+
+ ' </section>\n';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* 三种版式各自的那一段。顺序刻意与 README 的取舍表一致。 */
|
|
88
|
+
var VARIANTS = {
|
|
89
|
+
'report/finance': {
|
|
90
|
+
label: 'report · 金融研究版',
|
|
91
|
+
rootClass: 'rep rep--finance',
|
|
92
|
+
title: '标题:把结论写在标题里',
|
|
93
|
+
kicker: '行业跟踪 · 金融研究版',
|
|
94
|
+
standfirst: '导语:先说结论与它成立的条件,再说依据。读者读完这一段就该知道要不要往下看。',
|
|
95
|
+
before: ' <!-- 关键数字条:这一版的读者先看数、后看论证(ai/recipes.json: rep__keyline) -->\n'
|
|
96
|
+
+ ' <div class="rep__keyline">\n'
|
|
97
|
+
+ ' <div><b>读数一</b><span>—</span></div>\n'
|
|
98
|
+
+ ' <div><b>读数二</b><span>—</span></div>\n'
|
|
99
|
+
+ ' <div><b>读数三</b><span>—</span></div>\n'
|
|
100
|
+
+ ' </div>\n',
|
|
101
|
+
body: '',
|
|
102
|
+
after: ' <!-- 风险披露:这一版必须有(ai/recipes.json: rep__risk) -->\n'
|
|
103
|
+
+ ' <p class="rep__risk">风险披露:把结论的适用条件、数据口径与不确定性写在这里。'
|
|
104
|
+
+ '没有这一段的金融结论读起来像建议,而不是分析。</p>\n',
|
|
105
|
+
aside: '次要读数放这里。放不下的大图不要塞边注 —— 边注只有 14em。'
|
|
106
|
+
},
|
|
107
|
+
'report/technical': {
|
|
108
|
+
label: 'report · 科研技术版',
|
|
109
|
+
rootClass: 'rep rep--technical',
|
|
110
|
+
title: '标题:说清对象与方法',
|
|
111
|
+
kicker: '实验报告 · 科研技术版',
|
|
112
|
+
standfirst: '导语:一句话说清做了什么、得到什么、结论的置信边界在哪。',
|
|
113
|
+
before: '',
|
|
114
|
+
body: '',
|
|
115
|
+
after: ' <!-- 来源脚注:这一版必须有(ai/recipes.json: rep__source) -->\n'
|
|
116
|
+
+ ' <p class="rep__source">来源:数据出处、采集时间与口径。逐条核对的人会顺着这里回查原始材料。</p>\n',
|
|
117
|
+
aside: '术语与口径写这里。这一版的读者会逐条核对,边注用来放"不影响主线但需要说明"的东西。'
|
|
118
|
+
},
|
|
119
|
+
'report/magazine': {
|
|
120
|
+
label: 'report · 杂志编辑版',
|
|
121
|
+
rootClass: 'rep rep--magazine',
|
|
122
|
+
title: '标题:用一个具体的画面开头',
|
|
123
|
+
kicker: '长报道 · 杂志编辑版',
|
|
124
|
+
standfirst: '导语:把读者带进场景,而不是先给结论。这一版是通读的,不是查数的。',
|
|
125
|
+
before: ' <!-- 引语块:这一版靠停顿制造节奏(ai/recipes.json: rep__pull) -->\n'
|
|
126
|
+
+ ' <blockquote class="rep__pull">一句能被单独引用的话。它应该是全文的转折点,不是摘要。</blockquote>\n',
|
|
127
|
+
body: '',
|
|
128
|
+
after: '',
|
|
129
|
+
aside: '短提醒放这里。这一版没有编号,边注是唯一的"旁白"通道。'
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {string} id 版式名(见 ai/recipes.json)
|
|
135
|
+
* @param {object} [opts] `{cssPrefix}` —— 样式表前缀,默认 `node_modules/agentcss`
|
|
136
|
+
* @returns {{ok:true, html:string, id:string, requires:string[]}|{ok:false, message:string}}
|
|
137
|
+
*/
|
|
138
|
+
function render(id, opts) {
|
|
139
|
+
opts = opts || {};
|
|
140
|
+
var v = VARIANTS[id];
|
|
141
|
+
if (!v) {
|
|
142
|
+
return { ok: false, message: '未知版式:' + id + '(可用:' + Object.keys(VARIANTS).join(' / ') + ')' };
|
|
143
|
+
}
|
|
144
|
+
var wide = id === 'report/magazine';
|
|
145
|
+
|
|
146
|
+
var body = ' <section class="rep__sec">\n'
|
|
147
|
+
+ ' <h3>第一节:先给判断,再给依据</h3>\n'
|
|
148
|
+
+ ' <p>正文。这一段的每句话都应该能被上一段的数字或下一段的图支撑;'
|
|
149
|
+
+ '撑不住的删掉。</p>\n'
|
|
150
|
+
+ figure(wide)
|
|
151
|
+
+ table()
|
|
152
|
+
+ ' </section>\n'
|
|
153
|
+
+ section('第二节:把限制写清楚', ' <p>适用的边界、数据口径、以及"这一版有意不做什么"。</p>\n');
|
|
154
|
+
|
|
155
|
+
var html = head(v.title, opts.cssPrefix)
|
|
156
|
+
+ '<body class="page">\n'
|
|
157
|
+
+ '<main class="wrap">\n'
|
|
158
|
+
+ ' <!-- 由 `agentcss ai scaffold` 生成:只给壳,不给内容。\n'
|
|
159
|
+
+ ' 写完跑:agentcss ai check 本文件 --recipe=' + id + ' -->\n'
|
|
160
|
+
+ ' <article class="' + v.rootClass + '">\n'
|
|
161
|
+
+ ' <header>\n'
|
|
162
|
+
+ ' <p class="rep__kicker">' + v.kicker + '</p>\n'
|
|
163
|
+
+ ' <h2 class="rep__title">' + v.title + '</h2>\n'
|
|
164
|
+
+ ' <p class="rep__standfirst">' + v.standfirst + '</p>\n'
|
|
165
|
+
+ ' <ul class="rep__meta"><li>作者</li><li>日期</li><li>版本 v1</li></ul>\n'
|
|
166
|
+
+ ' </header>\n'
|
|
167
|
+
+ v.before
|
|
168
|
+
+ ' <div class="rep__grid">\n'
|
|
169
|
+
+ ' <div class="rep__body">\n'
|
|
170
|
+
+ body
|
|
171
|
+
+ ' </div>\n'
|
|
172
|
+
+ ' <aside class="rep__aside">' + v.aside + '</aside>\n'
|
|
173
|
+
+ ' </div>\n'
|
|
174
|
+
+ v.after
|
|
175
|
+
+ ' </article>\n'
|
|
176
|
+
+ '</main>\n'
|
|
177
|
+
+ '</body>\n'
|
|
178
|
+
+ '</html>\n';
|
|
179
|
+
|
|
180
|
+
var requires = [];
|
|
181
|
+
try {
|
|
182
|
+
var all = JSON.parse(fs.readFileSync(path.join(AI, 'recipes.json'), 'utf8'));
|
|
183
|
+
requires = ((all.recipes[id] || {}).requires || []).map(function (r) { return r.selector; });
|
|
184
|
+
} catch (e) { /* 读不到就不报;门禁会盯 recipes.json */ }
|
|
185
|
+
|
|
186
|
+
return { ok: true, html: html, id: id, requires: requires };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function list() {
|
|
190
|
+
return Object.keys(VARIANTS).map(function (k) {
|
|
191
|
+
return { id: k, label: VARIANTS[k].label };
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = { render: render, list: list, VARIANTS: VARIANTS, SHEETS: SHEETS };
|
package/ai/shot.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* shot.js — 把页面截成 PNG(给定宽度、可选整页)
|
|
3
|
+
*
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* 🔴 为什么不用 `chrome --screenshot --window-size=390,844`
|
|
6
|
+
* ----------------------------------------------------------------------------
|
|
7
|
+
* 因为它**会撒谎**。Windows 上窗口有最小宽度,`--window-size=390` 拿到的
|
|
8
|
+
* 实际视口是 **500px**(实测 innerWidth=500),而 `--screenshot` 只按 390 裁一刀。
|
|
9
|
+
* 结果是一张"看起来是 390 宽"的图:文字在右边缘被切掉、媒体查询按 500 生效 ——
|
|
10
|
+
* 你据此判断"窄屏没问题",其实从没看过 390。
|
|
11
|
+
*
|
|
12
|
+
* 这类失败最贵的地方在于**它不报错**:图是好的、能打开、只是内容不对。
|
|
13
|
+
* 所以这里走 Chrome 的调试管道(`--remote-debugging-pipe`,零依赖、不开端口),
|
|
14
|
+
* 用 `Emulation.setDeviceMetricsOverride` 把视口**设成**要的那个数,再截。
|
|
15
|
+
*
|
|
16
|
+
* ============================================================================
|
|
17
|
+
* 为什么是管道而不是 `--remote-debugging-port`
|
|
18
|
+
* ----------------------------------------------------------------------------
|
|
19
|
+
* 端口版要处理:端口占用、防火墙弹窗(Windows 首次监听会弹)、启动竞态
|
|
20
|
+
* (等调试端口文件出现的时机)、以及同一个 profile 被复用时的锁。
|
|
21
|
+
* 管道版把这些全绕开:读写走子进程的 fd 3/4,消息以 `\0` 分隔的 JSON。
|
|
22
|
+
* 代价是自己拼十几行帧处理 —— 比处理上面四件事便宜得多。
|
|
23
|
+
*
|
|
24
|
+
* ============================================================================
|
|
25
|
+
* 它**不是**视觉基线
|
|
26
|
+
* ----------------------------------------------------------------------------
|
|
27
|
+
* 基线(逐字节比对)由 `05-audit/shot-baseline.py` 管,那套要考虑字体、
|
|
28
|
+
* 双平台产物与指纹。这里的产物是给人/给 Agent **看一眼**的:
|
|
29
|
+
* 同一台机器、同样参数,两次逐字节一致(门禁验的就是这条);
|
|
30
|
+
* 换一台机器不保证 —— 字体光栅化不一样,别拿它当跨机器判据。
|
|
31
|
+
*/
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
var fs = require('fs');
|
|
35
|
+
var os = require('os');
|
|
36
|
+
var path = require('path');
|
|
37
|
+
var { pathToFileURL } = require('url');
|
|
38
|
+
var pdf = require('./pdf.js');
|
|
39
|
+
var connect = require('./cdp.js').connect;
|
|
40
|
+
|
|
41
|
+
var DEFAULT_WIDTHS = [1440];
|
|
42
|
+
var DEFAULT_HEIGHT = 900;
|
|
43
|
+
var DEFAULT_WAIT = 800;
|
|
44
|
+
/* 整页截图的高度上限。PNG 面积是 W×H×scale²,超了内存与耗时都会失控;
|
|
45
|
+
截断**不静默**:结果里带 truncatedAt,人/Agent 能看见。 */
|
|
46
|
+
var MAX_FULL_HEIGHT = 10000;
|
|
47
|
+
|
|
48
|
+
/** 页面在某个宽度下的量:溢出多少、谁溢出、有多高。 */
|
|
49
|
+
var PROBE_JS = [
|
|
50
|
+
'(function(){',
|
|
51
|
+
' var d = document.documentElement;',
|
|
52
|
+
' var vw = d.clientWidth;',
|
|
53
|
+
' var over = [];',
|
|
54
|
+
' var all = document.querySelectorAll("body *");',
|
|
55
|
+
' for (var i = 0; i < all.length && over.length < 40; i++) {',
|
|
56
|
+
' var e = all[i], r = e.getBoundingClientRect();',
|
|
57
|
+
' if (r.width === 0 && r.height === 0) continue;',
|
|
58
|
+
' if (r.right > vw + 1 || r.left < -1) {',
|
|
59
|
+
' var cls = typeof e.className === "string" ? e.className : "";',
|
|
60
|
+
' over.push(e.tagName.toLowerCase() + (cls ? "." + cls.trim().split(/\\s+/).join(".") : "")',
|
|
61
|
+
' + " @" + Math.round(r.right));',
|
|
62
|
+
' }',
|
|
63
|
+
' }',
|
|
64
|
+
' return JSON.stringify({',
|
|
65
|
+
' viewport: vw, scrollWidth: d.scrollWidth, scrollHeight: d.scrollHeight,',
|
|
66
|
+
' overflow: over.slice(0, 6), overflowCount: over.length',
|
|
67
|
+
' });',
|
|
68
|
+
'})()'
|
|
69
|
+
].join('\n');
|
|
70
|
+
|
|
71
|
+
function slugWidth(w) { return String(w); }
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {object} o
|
|
75
|
+
* @param {string} o.src 本地 HTML 路径或 http(s) URL
|
|
76
|
+
* @param {string} o.out 输出文件;多宽度时须含 `{width}`
|
|
77
|
+
* @param {number[]} [o.widths] 视口宽度,默认 [1440]
|
|
78
|
+
* @param {number} [o.height] 视口高度,默认 900
|
|
79
|
+
* @param {boolean} [o.full] 整页(否则只截首屏)
|
|
80
|
+
* @param {number} [o.scale] 设备像素比,默认 1
|
|
81
|
+
* @param {number} [o.wait] 加载后额外等待(毫秒),默认 800
|
|
82
|
+
* @param {string} [o.chrome]
|
|
83
|
+
* @param {number} [o.timeoutMs]
|
|
84
|
+
* @returns {Promise<object>}
|
|
85
|
+
*/
|
|
86
|
+
async function shoot(o) {
|
|
87
|
+
o = o || {};
|
|
88
|
+
var t0 = Date.now();
|
|
89
|
+
if (!o.src) return { ok: false, code: 'usage', message: '缺少 src' };
|
|
90
|
+
if (!o.out) return { ok: false, code: 'usage', message: '缺少 out' };
|
|
91
|
+
|
|
92
|
+
var widths = o.widths && o.widths.length ? o.widths : DEFAULT_WIDTHS;
|
|
93
|
+
for (var i = 0; i < widths.length; i++) {
|
|
94
|
+
if (!Number.isInteger(widths[i]) || widths[i] < 200 || widths[i] > 4000) {
|
|
95
|
+
return { ok: false, code: 'usage',
|
|
96
|
+
message: '宽度必须是 200–4000 的整数,收到 ' + JSON.stringify(widths[i]) };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
var height = o.height || DEFAULT_HEIGHT;
|
|
100
|
+
if (!Number.isInteger(height) || height < 200 || height > 4000) {
|
|
101
|
+
return { ok: false, code: 'usage',
|
|
102
|
+
message: '高度必须是 200–4000 的整数,收到 ' + JSON.stringify(o.height) };
|
|
103
|
+
}
|
|
104
|
+
var scale = o.scale === undefined ? 1 : Number(o.scale);
|
|
105
|
+
if (!(scale > 0 && scale <= 3)) {
|
|
106
|
+
return { ok: false, code: 'usage', message: '--scale 必须在 (0, 3],收到 ' + JSON.stringify(o.scale) };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
var remote = /^https?:\/\//i.test(o.src);
|
|
110
|
+
if (!remote) {
|
|
111
|
+
var srcAbs = path.resolve(o.src);
|
|
112
|
+
if (!fs.existsSync(srcAbs)) return { ok: false, code: 'usage', message: '找不到输入文件:' + srcAbs };
|
|
113
|
+
o.src = srcAbs;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var outTpl = String(o.out);
|
|
117
|
+
if (widths.length > 1 && outTpl.indexOf('{width}') < 0) {
|
|
118
|
+
return { ok: false, code: 'usage',
|
|
119
|
+
message: '截多个宽度时 --out 必须含 {width}(例:shot-{width}.png),'
|
|
120
|
+
+ '否则 ' + widths.length + ' 张图会写进同一个文件,最后只剩一张' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
var chrome = pdf.resolveChrome(o.chrome);
|
|
124
|
+
if (chrome.error) return { ok: false, code: 'no-chrome', message: chrome.error };
|
|
125
|
+
|
|
126
|
+
var profile = fs.mkdtempSync(path.join(os.tmpdir(), 'agentcss-shot-'));
|
|
127
|
+
var cdp = connect(chrome.path, profile);
|
|
128
|
+
var shots = [];
|
|
129
|
+
var consoleErrors = [];
|
|
130
|
+
var budget = o.timeoutMs || 90000;
|
|
131
|
+
var deadline = Date.now() + budget;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
var target = await cdp.send('Target.createTarget', { url: 'about:blank' });
|
|
135
|
+
var attached = await cdp.send('Target.attachToTarget', { targetId: target.targetId, flatten: true });
|
|
136
|
+
var sid = attached.sessionId;
|
|
137
|
+
|
|
138
|
+
await cdp.send('Page.enable', {}, sid);
|
|
139
|
+
await cdp.send('Runtime.enable', {}, sid);
|
|
140
|
+
/* JS 异常要收集:截出一张"布局正确但内容没渲染"的图是最坏的产物,
|
|
141
|
+
而它通常就是一行 TypeError 造成的。 */
|
|
142
|
+
cdp.on('Runtime.exceptionThrown', function (p) {
|
|
143
|
+
if (consoleErrors.length >= 20) return;
|
|
144
|
+
var d = (p && p.exceptionDetails) || {};
|
|
145
|
+
var txt = (d.exception && d.exception.description) || d.text || '未捕获异常';
|
|
146
|
+
consoleErrors.push(String(txt).split('\n')[0]);
|
|
147
|
+
});
|
|
148
|
+
cdp.on('Runtime.consoleAPICalled', function (p) {
|
|
149
|
+
if (consoleErrors.length >= 20) return;
|
|
150
|
+
if (!p || p.type !== 'error') return;
|
|
151
|
+
var parts = (p.args || []).map(function (a) {
|
|
152
|
+
return a.value === undefined ? (a.description || a.type) : String(a.value);
|
|
153
|
+
});
|
|
154
|
+
consoleErrors.push('console.error: ' + parts.join(' '));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
for (var w = 0; w < widths.length; w++) {
|
|
158
|
+
if (Date.now() > deadline) {
|
|
159
|
+
return { ok: false, code: 'timeout', message: '超出时间预算 ' + budget + 'ms', chrome: chrome.path, shots: shots };
|
|
160
|
+
}
|
|
161
|
+
var width = widths[w];
|
|
162
|
+
var w0 = Date.now();
|
|
163
|
+
|
|
164
|
+
await cdp.send('Emulation.setDeviceMetricsOverride', {
|
|
165
|
+
width: width, height: height, deviceScaleFactor: scale, mobile: false
|
|
166
|
+
}, sid);
|
|
167
|
+
|
|
168
|
+
var loaded = cdp.waitEvent('Page.loadEventFired', sid, 30000);
|
|
169
|
+
var nav = await cdp.send('Page.navigate', { url: remote ? o.src : pathToFileURL(o.src).href }, sid);
|
|
170
|
+
if (nav && nav.errorText) {
|
|
171
|
+
return { ok: false, code: 'render',
|
|
172
|
+
message: '页面加载失败(' + nav.errorText + '):' + o.src, chrome: chrome.path, shots: shots };
|
|
173
|
+
}
|
|
174
|
+
await loaded;
|
|
175
|
+
if (o.wait) await new Promise(function (r) { setTimeout(r, o.wait); });
|
|
176
|
+
|
|
177
|
+
var probe = await cdp.send('Runtime.evaluate', {
|
|
178
|
+
expression: PROBE_JS, returnByValue: true
|
|
179
|
+
}, sid);
|
|
180
|
+
var info = {};
|
|
181
|
+
try { info = JSON.parse(probe.result.value); } catch (e) { info = {}; }
|
|
182
|
+
|
|
183
|
+
var clipH = height;
|
|
184
|
+
var truncated = null;
|
|
185
|
+
if (o.full) {
|
|
186
|
+
var want = info.scrollHeight || height;
|
|
187
|
+
clipH = Math.min(want, MAX_FULL_HEIGHT);
|
|
188
|
+
if (want > MAX_FULL_HEIGHT) truncated = want;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
var shot = await cdp.send('Page.captureScreenshot', {
|
|
192
|
+
format: 'png',
|
|
193
|
+
captureBeyondViewport: !!o.full,
|
|
194
|
+
clip: { x: 0, y: 0, width: width, height: clipH, scale: 1 }
|
|
195
|
+
}, sid);
|
|
196
|
+
var png = Buffer.from(shot.data, 'base64');
|
|
197
|
+
if (png.length < 8 || png.readUInt32BE(0) !== 0x89504e47) {
|
|
198
|
+
return { ok: false, code: 'render', message: '产出的不是 PNG(' + png.length + ' 字节)',
|
|
199
|
+
chrome: chrome.path, shots: shots };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
var outFile = path.resolve(outTpl.replace(/\{width\}/g, slugWidth(width)));
|
|
203
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
204
|
+
fs.writeFileSync(outFile, png);
|
|
205
|
+
|
|
206
|
+
shots.push({
|
|
207
|
+
width: width, out: outFile, bytes: png.length, ms: Date.now() - w0,
|
|
208
|
+
png: { width: png.readUInt32BE(16), height: png.readUInt32BE(20) },
|
|
209
|
+
full: !!o.full,
|
|
210
|
+
truncatedAt: truncated,
|
|
211
|
+
viewport: info.viewport === undefined ? null : info.viewport,
|
|
212
|
+
scrollWidth: info.scrollWidth === undefined ? null : info.scrollWidth,
|
|
213
|
+
scrollHeight: info.scrollHeight === undefined ? null : info.scrollHeight,
|
|
214
|
+
overflow: info.overflow || [],
|
|
215
|
+
overflowCount: info.overflowCount || 0
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
ok: true, code: null, message: null, chrome: chrome.path,
|
|
221
|
+
scale: scale, wait: o.wait === undefined ? DEFAULT_WAIT : o.wait,
|
|
222
|
+
shots: shots, consoleErrors: consoleErrors, ms: Date.now() - t0
|
|
223
|
+
};
|
|
224
|
+
} catch (e) {
|
|
225
|
+
return { ok: false, code: 'render', message: String(e && e.message || e), chrome: chrome.path, shots: shots };
|
|
226
|
+
} finally {
|
|
227
|
+
cdp.close();
|
|
228
|
+
/* 清理必须**在进程退出前**做完:CLI 随后就 exit,挂 setTimeout 等于每截一张图
|
|
229
|
+
就在系统临时目录里留一个 Chrome profile(pdf.js 那边实测漏过 6 个)。
|
|
230
|
+
先等一下再删:Windows 上子进程刚 kill 时还握着文件句柄。 */
|
|
231
|
+
await new Promise(function (r) { setTimeout(r, 120); });
|
|
232
|
+
try { fs.rmSync(profile, { recursive: true, force: true, maxRetries: 3 }); }
|
|
233
|
+
catch (e) { /* 偶发 EBUSY,交给系统清理 */ }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = { shoot: shoot, DEFAULT_WIDTHS: DEFAULT_WIDTHS, DEFAULT_HEIGHT: DEFAULT_HEIGHT, DEFAULT_WAIT: DEFAULT_WAIT };
|
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', '
|
|
64
|
+
'items', 'additionalProperties', 'allOf', 'anyOf',
|
|
61
65
|
'oneOf', 'not', 'propertyNames', 'contains', 'if', 'then', 'else'
|
|
62
66
|
];
|
|
63
|
-
|
|
64
|
-
|
|
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 =
|
|
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/01-tokens/page.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.page {margin:0;min-height:100vh;min-height:100dvh;background:var(--paper);color:var(--text-primary);font-family:var(--sans);font-size:var(--fs-base);line-height:var(--lh-base);-webkit-text-size-adjust:100%;padding-block:calc(env(safe-area-inset-top, 0px) + var(--sp-6)) calc(env(safe-area-inset-bottom, 0px) + var(--sp-6));padding-inline:max(env(safe-area-inset-right, 0px), var(--sp-4)) max(env(safe-area-inset-left, 0px), var(--sp-4));}.page *, .page *::before, .page *::after {box-sizing:border-box;}.page .wrap {margin-inline:auto;max-width:min(var(--measure-page), 100%);}.page .wrap--prose {max-width:min(var(--measure-prose), 100%);}.page h1 {font-family:var(--sans);font-size:var(--fs-2xl);font-weight:500;letter-spacing:var(--tracking-title);line-height:var(--lh-tight);margin:0 0 var(--sp-3);max-width:var(--measure-title);}.page h2 {font-size:var(--fs-lg);margin:var(--sp-7) 0 var(--sp-3);}.page .rule-top {border-top:1px solid var(--border-decor);padding-top:var(--sp-4);}.page .rule-bottom {border-bottom:1px solid var(--border-decor);padding-bottom:var(--sp-2);margin-bottom:var(--sp-4);}.page code {font-family:var(--mono);font-size:0.9em;background:var(--surface-sunken);padding:1px 4px;border-radius:var(--r-xs);}:where(.page) pre {overflow-x:auto;}.page .box {background:var(--surface);border:1px solid var(--border-decor);border-radius:var(--r-md);padding:var(--sp-5);}.page .box.bad {background:var(--surface-sunken);}.page .cmp {display:grid;grid-template-columns:minmax(0, 1fr) minmax(0, 1fr);gap:var(--sp-4);}.page .cmp--wide-first {grid-template-columns:minmax(0, 1.55fr) minmax(0, 1fr);}@media (max-width:640px) {.page .cmp, .page .cmp--wide-first, .page .cmp--clip {grid-template-columns:minmax(0, 1fr);}}.page .cmp__pane {padding:var(--sp-4);border:1px solid var(--border-decor);border-radius:var(--r-md);background:var(--surface-sunken);min-width:0;}.page .cmp--clip .cmp__pane {overflow:hidden;}.page .cmp__pane > h3 {margin:0 0 var(--sp-3);font-size:var(--fs-sm);font-weight:500;}.page .cmp__bad > h3 {color:var(--danger);}.page .cmp__good > h3 {color:var(--success);}.page .cmp__why {margin:var(--sp-3) 0 0;font-size:var(--fs-xs);line-height:1.6;color:var(--text-secondary);}
|
|
1
|
+
.page {margin:0;min-height:100vh;min-height:100dvh;background:var(--paper);color:var(--text-primary);font-family:var(--sans);font-size:var(--fs-base);line-height:var(--lh-base);-webkit-text-size-adjust:100%;padding-block:calc(env(safe-area-inset-top, 0px) + var(--sp-6)) calc(env(safe-area-inset-bottom, 0px) + var(--sp-6));padding-inline:max(env(safe-area-inset-right, 0px), var(--sp-4)) max(env(safe-area-inset-left, 0px), var(--sp-4));}@media print {.page {min-height:0;padding-block-end:0;}}.page *, .page *::before, .page *::after {box-sizing:border-box;}.page .wrap {margin-inline:auto;max-width:min(var(--measure-page), 100%);}.page .wrap--prose {max-width:min(var(--measure-prose), 100%);}.page h1 {font-family:var(--sans);font-size:var(--fs-2xl);font-weight:500;letter-spacing:var(--tracking-title);line-height:var(--lh-tight);margin:0 0 var(--sp-3);max-width:var(--measure-title);}.page h2 {font-size:var(--fs-lg);margin:var(--sp-7) 0 var(--sp-3);}.page .rule-top {border-top:1px solid var(--border-decor);padding-top:var(--sp-4);}.page .rule-bottom {border-bottom:1px solid var(--border-decor);padding-bottom:var(--sp-2);margin-bottom:var(--sp-4);}.page code {font-family:var(--mono);font-size:0.9em;background:var(--surface-sunken);padding:1px 4px;border-radius:var(--r-xs);}:where(.page) pre {overflow-x:auto;}.page .box {background:var(--surface);border:1px solid var(--border-decor);border-radius:var(--r-md);padding:var(--sp-5);}.page .box.bad {background:var(--surface-sunken);}.page .cmp {display:grid;grid-template-columns:minmax(0, 1fr) minmax(0, 1fr);gap:var(--sp-4);}.page .cmp--wide-first {grid-template-columns:minmax(0, 1.55fr) minmax(0, 1fr);}@media (max-width:640px) {.page .cmp, .page .cmp--wide-first, .page .cmp--clip {grid-template-columns:minmax(0, 1fr);}}.page .cmp__pane {padding:var(--sp-4);border:1px solid var(--border-decor);border-radius:var(--r-md);background:var(--surface-sunken);min-width:0;}.page .cmp--clip .cmp__pane {overflow:hidden;}.page .cmp__pane > h3 {margin:0 0 var(--sp-3);font-size:var(--fs-sm);font-weight:500;}.page .cmp__bad > h3 {color:var(--danger);}.page .cmp__good > h3 {color:var(--success);}.page .cmp__why {margin:var(--sp-3) 0 0;font-size:var(--fs-xs);line-height:1.6;color:var(--text-secondary);}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.tabs {display:flex;flex-direction:column;gap:var(--sp-4);}.tabs--manual .tabs__tab[aria-selected="true"] {color:var(--text-secondary);font-weight:400;}@media (hover:hover) and (pointer:fine) {.tabs--manual .tabs__tab[aria-selected="true"]:hover {color:var(--text-primary);}}.tabs__list {display:flex;gap:var(--sp-1);border-bottom:1px solid var(--border-decor);position:relative;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;}.tabs__list::-webkit-scrollbar {display:none;}.tabs--vertical {flex-direction:row;gap:var(--sp-4);}.tabs--vertical .tabs__list {flex-direction:column;border-bottom:0;border-inline-end:1px solid var(--border-decor);}.tabs__tab {appearance:none;background:none;border:0;margin-bottom:-1px;padding:var(--sp-3) var(--sp-4);font:inherit;font-size:var(--fs-base);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;transition:color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);white-space:nowrap;flex:0 0 auto;}.tabs--vertical .tabs__tab {margin-bottom:0;margin-inline-end:-1px;border-bottom:0;border-inline-end:2px solid transparent;text-align:start;}.tabs__tab[aria-selected="true"] {color:var(--text-primary);font-weight:600;border-bottom-color:var(--accent);}.tabs--vertical .tabs__tab[aria-selected="true"] {border-inline-end-color:var(--accent);}@media (hover:hover) and (pointer:fine) {.tabs__tab:not([aria-disabled="true"]):hover {color:var(--text-primary);background:var(--surface-sunken);}}.tabs__tab[aria-disabled="true"] {color:var(--text-tertiary);cursor:not-allowed;}.tabs__tab:focus-visible {outline:2px solid var(--accent);outline-offset:-2px;border-radius:var(--r-sm) var(--r-sm) 0 0;}.tabs__panel {padding:var(--sp-4) 0;}.tabs__panel[hidden] {display:none;}@media (prefers-reduced-motion:no-preference) {.tabs__panel--enter {animation:tabs-fade var(--dur-base) var(--ease-out);}}@keyframes tabs-fade {from {opacity:0;}to {opacity:1;}}
|
|
1
|
+
.tabs {display:flex;flex-direction:column;gap:var(--sp-4);}.tabs--manual .tabs__tab[aria-selected="true"] {color:var(--text-secondary);font-weight:400;}@media (hover:hover) and (pointer:fine) {.tabs--manual .tabs__tab[aria-selected="true"]:hover {color:var(--text-primary);}}.tabs__list {display:flex;gap:var(--sp-1);border-bottom:1px solid var(--border-decor);position:relative;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;}.tabs__list::-webkit-scrollbar {display:none;}.tabs--vertical {flex-direction:row;gap:var(--sp-4);}.tabs--vertical .tabs__list {flex-direction:column;border-bottom:0;border-inline-end:1px solid var(--border-decor);}.tabs__tab {appearance:none;background:none;border:0;margin-bottom:-1px;padding:var(--sp-3) var(--sp-4);font:inherit;font-size:var(--fs-base);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;transition:color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);white-space:nowrap;flex:0 0 auto;}.tabs--vertical .tabs__tab {margin-bottom:0;margin-inline-end:-1px;border-bottom:0;border-inline-end:2px solid transparent;text-align:start;}.tabs__tab[aria-selected="true"] {color:var(--text-primary);font-weight:600;border-bottom-color:var(--accent);}.tabs--vertical .tabs__tab[aria-selected="true"] {border-inline-end-color:var(--accent);}@media (hover:hover) and (pointer:fine) {.tabs__tab:not([aria-disabled="true"]):hover {color:var(--text-primary);background:var(--surface-sunken);}}.tabs__tab[aria-disabled="true"] {color:var(--text-tertiary);cursor:not-allowed;}.tabs__tab:focus-visible {outline:2px solid var(--accent);outline-offset:-2px;border-radius:var(--r-sm) var(--r-sm) 0 0;}.tabs__panel {padding:var(--sp-4) 0;}.tabs__panel[hidden] {display:none;}@media (prefers-reduced-motion:no-preference) {.tabs__panel--enter {animation:tabs-fade var(--dur-base) var(--ease-out);}}@keyframes tabs-fade {from {opacity:0;}to {opacity:1;}}@media print {.tabs__panel--enter {animation:none;transition:none;opacity:1;}}
|
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.
|
|
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.
|
|
27
|
-
| primitive | `delta` | 2.9 KB | 0.3 KB | — | **3.2 KB** | 24.
|
|
28
|
-
| primitive | `input` | 2.9 KB | 1.0 KB | — | **4.0 KB** | 25.8 KB | −21.
|
|
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.
|
|
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.
|
|
36
|
-
| primitive | `timeline` | 2.9 KB | 0.5 KB | — | **3.4 KB** | 25.
|
|
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,18 +42,18 @@
|
|
|
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.
|
|
46
|
-
| pattern | `pagination` | 2.9 KB | 0.8 KB | 2.8 KB | **6.5 KB** | 32.
|
|
47
|
-
| pattern | `reveal` | 2.9 KB | 0.3 KB | 1.2 KB | **4.4 KB** | 26.
|
|
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
|
-
| pattern | `tabs` | 2.9 KB | 0.7 KB | 2.0 KB | **5.
|
|
49
|
+
| pattern | `tabs` | 2.9 KB | 0.7 KB | 2.0 KB | **5.7 KB** | 29.2 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 |
|
|
51
51
|
| pattern | `tree` | 2.9 KB | 0.6 KB | 3.1 KB | **6.6 KB** | 30.3 KB | −23.7 KB |
|
|
52
52
|
|
|
53
53
|
## 三、全库
|
|
54
54
|
|
|
55
55
|
- 全部组件都用上(min,含 tokens 一份):**72.6 KB**
|
|
56
|
-
- 源码版(带注释)全库 gzip:386.
|
|
56
|
+
- 源码版(带注释)全库 gzip:386.7 KB
|
|
57
57
|
|
|
58
58
|
> ⚠️ 源码版那个数字**不是**要付的钱:它包含我们的注释(全库 CSS 有 69% 是注释,字节口径)。
|
|
59
59
|
> 它的用途只有一个 —— 让人看清"dist 到底省了多少"。
|
package/docs/ACCESSIBILITY.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
| 项目 | 值 |
|
|
7
7
|
|---|---|
|
|
8
|
-
| 声明适用版本 | agentcss `0.
|
|
9
|
-
| 声明日期 | 2026-11-
|
|
8
|
+
| 声明适用版本 | agentcss `0.27.1` |
|
|
9
|
+
| 声明日期 | 2026-11-06 |
|
|
10
10
|
| 复核节奏 | 每次发版前重跑(`npm run gate` 内含全部证据) |
|
|
11
11
|
| 范围 | 本库交付的 CSS / JS、全部 `demo.html`(组件页 + 图表页 + 组合页)、以及文档站生成的页面 |
|
|
12
12
|
| 不在范围 | 使用者拿这些组件拼出来的业务页面(那是你的无障碍责任,不是库的) |
|