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/01-tokens/README.md
CHANGED
|
@@ -257,7 +257,7 @@ node ai/cli.js tree [--set=light|dark] [--cat=color] # 第二种
|
|
|
257
257
|
(`contrast` / `gen-ai-contract` / `gen-tokens-tree` / `token-audience` /
|
|
258
258
|
`theme-sync`)。真实事故:一条 `@layer` 让六个工具同时读错,且都没有报错。
|
|
259
259
|
3. 选择器是 `:root[data-market="intl"]` (0,2,0),**不是** `:where(...)` (0) ——
|
|
260
|
-
`:where()` 抢不过 `:root` 的 (0,1,0)
|
|
260
|
+
`:where()` 抢不过 `:root` 的 (0,1,0),写了等于没写。
|
|
261
261
|
|
|
262
262
|
> 精简档(`tierA-tokens.css`)**不含**这段交换:它是一份 `<style>` 内的
|
|
263
263
|
> 令牌快照,没有配套文件可引。要国际口径就自己补一条规则 ——
|
package/01-tokens/page.css
CHANGED
|
@@ -86,6 +86,17 @@
|
|
|
86
86
|
max(env(safe-area-inset-left, 0px), var(--sp-4));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/* 纸面视口高度不等于一屏,屏幕安全区与末尾留白也不属于正文。
|
|
90
|
+
实测长金融报告:仅去掉 padding-block-end,8 页(末页全空白)→ 7 页,
|
|
91
|
+
3689 个非空白字符保持不变;只去掉首端内边距或 min-height 都不能修它。
|
|
92
|
+
保留首端留白与左右内边距,避免顺手改变整份文档的分页位置。 */
|
|
93
|
+
@media print {
|
|
94
|
+
.page {
|
|
95
|
+
min-height: 0;
|
|
96
|
+
padding-block-end: 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
/* ============================================================================
|
|
90
101
|
1b. 盒模型(`border-box`)—— 只在 .page 范围内生效
|
|
91
102
|
----------------------------------------------------------------------------
|
|
@@ -76,7 +76,7 @@ p { margin: 0 0 var(--sp-4); }
|
|
|
76
76
|
</div>
|
|
77
77
|
<p style="margin-top:var(--sp-4)">
|
|
78
78
|
判据很简单:<strong>这个值变了会不会导致"控件大小跟着变"?会 → 不要流体。</strong>
|
|
79
|
-
|
|
79
|
+
<code>--fs-xs</code> / <code>--fs-sm</code>(UI 文本)和 <code>--sp-1</code>~<code>--sp-3</code>(小间距)都属这一类。
|
|
80
80
|
</p>
|
|
81
81
|
|
|
82
82
|
<h2>三、该用断点的:布局结构</h2>
|
|
@@ -884,7 +884,7 @@
|
|
|
884
884
|
—— 遍历所有声明、后者覆盖前者、**不看选择器**。
|
|
885
885
|
把一段**条件性**的交换规则放在这里,它们会把它当成默认值叠上去,
|
|
886
886
|
于是报告里「暗色 涨」显示成绿色,而浏览器里其实是对的 —— 门禁冤枉代码,
|
|
887
|
-
|
|
887
|
+
比漏判更难查(实测)。
|
|
888
888
|
⇒ 本文件只放「无条件声明」与「暗色媒体段」,条件规则一律外置。
|
|
889
889
|
========================================================================= */
|
|
890
890
|
|
package/01-tokens/tokens.css
CHANGED
|
@@ -926,7 +926,7 @@
|
|
|
926
926
|
—— 遍历所有声明、后者覆盖前者、**不看选择器**。
|
|
927
927
|
把一段**条件性**的交换规则放在这里,它们会把它当成默认值叠上去,
|
|
928
928
|
于是报告里「暗色 涨」显示成绿色,而浏览器里其实是对的 —— 门禁冤枉代码,
|
|
929
|
-
|
|
929
|
+
比漏判更难查(实测)。
|
|
930
930
|
⇒ 本文件只放「无条件声明」与「暗色媒体段」,条件规则一律外置。
|
|
931
931
|
========================================================================= */
|
|
932
932
|
|
|
@@ -1117,7 +1117,7 @@ input[type="submit"],
|
|
|
1117
1117
|
/* ⚠️ 模态的遮罩规则**不在这里**,在 03-patterns/overlay/overlay.css。
|
|
1118
1118
|
原因不是洁癖:`.dialog-backdrop { position: static }` 写在这里会被
|
|
1119
1119
|
overlay.css 里同特异性的 `position: fixed` 盖掉 —— 规则写了、不生效、
|
|
1120
|
-
|
|
1120
|
+
不报错(同一个形状,2026-11-02 由 print-policy-gate 的 L3 抓到)。 */
|
|
1121
1121
|
|
|
1122
1122
|
/* 不跨页切断。代价:高于半页的卡片会被整体推到下一页 ⇒ 页数可能变多。 */
|
|
1123
1123
|
tr, img, figure, svg,
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
function paintSummary(s) {
|
|
197
|
-
/* 🔴 不拼 innerHTML
|
|
197
|
+
/* 🔴 不拼 innerHTML:`s` 来自两个 <input> 的 value,
|
|
198
198
|
而 `type=date` 在 Chromium 97 旧内核会回退成文本输入 ⇒ value 可为任意串。 */
|
|
199
199
|
if (!summary) return;
|
|
200
200
|
summary.textContent = '';
|
|
@@ -74,7 +74,7 @@ ul.demo-list li { margin: var(--sp-1) 0; }
|
|
|
74
74
|
<span class="dr-bar__title">筛选</span>
|
|
75
75
|
<button class="dr-tiny" type="button" tabindex="-1" aria-label="关闭">×</button>
|
|
76
76
|
</div>
|
|
77
|
-
<p class="cmp__why"
|
|
77
|
+
<p class="cmp__why">关闭是<strong>独立目标</strong>:抽屉滑进来之后,用户第一件事就是找它。24px 的叉在触屏上要瞄准,点偏了就点到了背后的内容。</p>
|
|
78
78
|
</div>
|
|
79
79
|
<div class="cmp__pane cmp__good" data-good="button.drawer__close|h|>=44">
|
|
80
80
|
<h3>✅ 本库:<code>drawer__close</code> 44px</h3>
|
|
@@ -289,7 +289,7 @@ body[data-scroll-locked] { overflow: hidden; }
|
|
|
289
289
|
隐藏遮罩会把弹窗里的字一起吞掉(静默截断)。实测:屏幕开着 dialog 时
|
|
290
290
|
打印,遮罩是 `position: fixed; inset: 0; rgba(21,24,28,.4)`,整页 40% 黑。
|
|
291
291
|
⚠️ 必须写在这个文件里:写进 tokens.css 会被这里同特异性的 `position: fixed`
|
|
292
|
-
盖掉 ——
|
|
292
|
+
盖掉 —— 规则写了、不生效、不报错(由 print-policy-gate L3 抓到)。
|
|
293
293
|
========================================================================= */
|
|
294
294
|
@media print {
|
|
295
295
|
.dialog-backdrop {
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
⇒ 减少动态效果的用户照样看到闪烁的微光;
|
|
138
138
|
· 与 `02-primitives/skeleton/skeleton.css` 同特异性 ⇒ 谁后加载谁定生死。
|
|
139
139
|
|
|
140
|
-
即"要不要尊重减少动态效果"取决于 CSS
|
|
140
|
+
即"要不要尊重减少动态效果"取决于 CSS 顺序。与同类。
|
|
141
141
|
⚠️ 这里与 skeleton.css 是**两份实现**(已登记在 css-ownership.json),
|
|
142
142
|
修的时候两边都要看。 */
|
|
143
143
|
@media (prefers-reduced-motion: no-preference) {
|
|
@@ -243,7 +243,7 @@ rail 下硬塞一个 `.shell__side-label` 会被 CSS 藏掉(兜底,防止它
|
|
|
243
243
|
|
|
244
244
|
- **❌ 侧栏 `position: fixed` + 主区 `margin-inline-start`** —— 看起来能用,但侧栏被摘出
|
|
245
245
|
grid 流,主区宽度靠**手抄的数字**维持(改侧栏宽就要改两处);`inset-inline-start`
|
|
246
|
-
与 `margin-inline-start` 只要有一处漏改,RTL
|
|
246
|
+
与 `margin-inline-start` 只要有一处漏改,RTL 下侧栏跑到右边、主区仍在右边留白。
|
|
247
247
|
|
|
248
248
|
正确的做法见上面「侧栏为什么是 `sticky` 而不是 `fixed`」。
|
|
249
249
|
|
|
@@ -934,7 +934,7 @@
|
|
|
934
934
|
—— 遍历所有声明、后者覆盖前者、**不看选择器**。
|
|
935
935
|
把一段**条件性**的交换规则放在这里,它们会把它当成默认值叠上去,
|
|
936
936
|
于是报告里「暗色 涨」显示成绿色,而浏览器里其实是对的 —— 门禁冤枉代码,
|
|
937
|
-
|
|
937
|
+
比漏判更难查(实测)。
|
|
938
938
|
⇒ 本文件只放「无条件声明」与「暗色媒体段」,条件规则一律外置。
|
|
939
939
|
========================================================================= */
|
|
940
940
|
|
|
@@ -1125,7 +1125,7 @@ input[type="submit"],
|
|
|
1125
1125
|
/* ⚠️ 模态的遮罩规则**不在这里**,在 03-patterns/overlay/overlay.css。
|
|
1126
1126
|
原因不是洁癖:`.dialog-backdrop { position: static }` 写在这里会被
|
|
1127
1127
|
overlay.css 里同特异性的 `position: fixed` 盖掉 —— 规则写了、不生效、
|
|
1128
|
-
|
|
1128
|
+
不报错(同一个形状,2026-11-02 由 print-policy-gate 的 L3 抓到)。 */
|
|
1129
1129
|
|
|
1130
1130
|
/* 不跨页切断。代价:高于半页的卡片会被整体推到下一页 ⇒ 页数可能变多。 */
|
|
1131
1131
|
tr, img, figure, svg,
|
|
@@ -1460,12 +1460,12 @@ body {
|
|
|
1460
1460
|
指令与数据混在一起,地址按字计。</p>
|
|
1461
1461
|
<h2>存储器构成</h2>
|
|
1462
1462
|
<p>不同 Block II 的分段数有差异,引用时应以具体的手册为准。</p>
|
|
1463
|
-
<table>
|
|
1463
|
+
<table aria-label="存储器构成">
|
|
1464
1464
|
<thead>
|
|
1465
1465
|
<tr>
|
|
1466
|
-
<th>区域</th>
|
|
1467
|
-
<th>容量</th>
|
|
1468
|
-
<th>用途</th>
|
|
1466
|
+
<th scope="col">区域</th>
|
|
1467
|
+
<th scope="col">容量</th>
|
|
1468
|
+
<th scope="col">用途</th>
|
|
1469
1469
|
</tr>
|
|
1470
1470
|
</thead>
|
|
1471
1471
|
<tbody>
|
|
@@ -289,8 +289,8 @@
|
|
|
289
289
|
<!-- ======================================================================== -->
|
|
290
290
|
<h2 class="rule-top">4 · 🔴 本轮明确不做(不是"还没做",是"这一版不管")</h2>
|
|
291
291
|
<ol>
|
|
292
|
-
<li><strong>分页 PDF / 印刷分页</strong
|
|
293
|
-
孤行寡行控制 —— 全部不在本版范围内,需要
|
|
292
|
+
<li><strong>分页 PDF / 印刷分页</strong>:<code>@page</code> 页边距、页眉页脚、页码、跨页不断行、
|
|
293
|
+
孤行寡行控制 —— 全部不在本版范围内,需要 <code>@page</code>、实物打样与 PDF 引擎验证。
|
|
294
294
|
CSS 里只保留了"打印时不更糟"的最小保障(隐藏边注、图表不跨撕裂)。</li>
|
|
295
295
|
<li><strong>自动目录、尾注/脚注、交叉引用、参考文献样式</strong>:图/表编号用 CSS counter
|
|
296
296
|
做到"写第几就显示第几",再往上的引用图(「详见图 3」这种自动维护)不支持。</li>
|
|
@@ -185,7 +185,7 @@ python -m http.server 8000
|
|
|
185
185
|
会让同一格同时受两套颜色支配,谁赢取决于选择器权重 —— 那是最难查的一类 bug。
|
|
186
186
|
`table-check` 的判据 ⑬ 会挡住给 `table.css` 添 `num--up` / `num--down` 的做法。
|
|
187
187
|
|
|
188
|
-
### 零值:两边都不许有符号(0.12.1
|
|
188
|
+
### 零值:两边都不许有符号(0.12.1 修)
|
|
189
189
|
|
|
190
190
|
`format: 'signed-percent'` 的正号由类名触发,而旧实现判的是 `v < 0` ⇒
|
|
191
191
|
`v === 0` 落进 else 分支得到 `num--pos`,渲染成「**+0.0%**」——
|
|
@@ -260,7 +260,7 @@
|
|
|
260
260
|
/* 符号:负数总是有 −(数值语义 + 无障碍第二线索);
|
|
261
261
|
正数只有 signed-* 才带 + —— 这就是"按列语义处理"。
|
|
262
262
|
|
|
263
|
-
🔴 但符号必须**跟打印出来的数字同号**(0.12.1
|
|
263
|
+
🔴 但符号必须**跟打印出来的数字同号**(0.12.1 修):
|
|
264
264
|
旧实现判的是 `v < 0`,于是 `v === 0` 掉进 else 分支拿到 `num--pos`,
|
|
265
265
|
渲染成「+0.0%」—— 一个没变化的数被念成"涨了"。
|
|
266
266
|
同一条错还会在**四舍五入**处复现:0.0004 在 1 位精度下打印成
|
package/09-assets/bar/bar.css
CHANGED
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
这里原来有一段针对 `prefers-color-scheme: dark` 的媒体查询块,
|
|
168
168
|
里面只有两条:`.bar` 的 background 与 `.bar__label` 的 stroke。
|
|
169
169
|
⚠️ 那段文字在这里**故意不照抄**:多个门禁拿这个字符串当锚点,
|
|
170
|
-
|
|
170
|
+
注释里出现字面量会把它们带偏(踩过一次)。
|
|
171
171
|
它做的只有一件事:把 `var()` 的**兜底值**从 #fff 换成暗色值。
|
|
172
172
|
令牌在的时候(本库的正式用法)两条声明解析结果完全相同 ⇒ 纯冗余。
|
|
173
173
|
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
this.ui.appendChild(this.btn);
|
|
117
117
|
|
|
118
118
|
this.hint = el('p', 'mv__hint');
|
|
119
|
-
/* 🔴 文件名来自 `src`,可能是调用方拼出来的 URL
|
|
119
|
+
/* 🔴 文件名来自 `src`,可能是调用方拼出来的 URL。
|
|
120
120
|
原来用 innerHTML 直接拼 ⇒ `<img onerror>` 这类值会被解析成节点。
|
|
121
121
|
改成 textContent 组装:`<code>` 与 `<b>` 仍然存在,但文本永远只是文本。 */
|
|
122
122
|
var fname = el('code', null, this.src.split('/').pop() || '—');
|
package/AGENTS.md
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
- **Contract (machine-readable):** `ai/components.json` — 40 components with root class, slots, variants, states, ARIA, file paths, and **`printPolicy`** (what happens to this component on paper: `omit` / `expand` / `reveal` / `keep-together` / `allow-break`, plus a `pagination` flag). Query it; never guess class names — and never assume a component prints as it looks on screen.
|
|
4
4
|
- **Known failure modes:** `ai/pitfalls.json`, or `npx agentcss ai pitfalls --json --component=<id>`.
|
|
5
|
-
- **Print / PDF:** `npx agentcss ai pdf <page.html> --out page.pdf [--paper=a4|letter|
|
|
5
|
+
- **Print / PDF:** `npx agentcss ai pdf <page.html> --out page.pdf [--paper=a3|a4|a5|letter|legal] [--margin="18mm 15mm"] [--max-pages=N] [--wait-ms=800] [--ready-selector="#report-ready"]`. Wait for load + fonts; use a visible ready selector for asynchronous content. A timeout or browser failure never replaces an existing PDF. `@page` belongs to the exporter, never to the library. For paper validation use `ai check <doc.json> --medium=paper` — it requires `pageBudget` and refuses `pagination:"server"` components.
|
|
6
6
|
- **Query:** `npx agentcss ai components --json [--maturity=stable]` · `ai tokens --json [--set=dark]` · `ai tree --json`.
|
|
7
|
-
- **Validate:** `npx agentcss ai validate <file> --schema=components --json`. Every subcommand accepts `--json`; failures print `{"ok":false
|
|
7
|
+
- **Validate, then branch on the exit code:** `npx agentcss ai validate <file> --schema=components --json`. Every subcommand accepts `--json`; failures print exactly one document `{"ok":false,"command","error":{"kind","message"}}`. Codes are one table for the whole CLI: `0` pass · `1` what you checked is bad (a verdict) · `2` bad flag or value — unknown flag, unknown enum value, empty value and a repeated flag all land here · `3` environment missing (no browser / no `node`) · `4` render failed · `5` over budget · `6` tool bug. A typo can never degrade into "empty result, exit 0".
|
|
8
|
+
- **Check the page you actually wrote:** `npx agentcss ai check <page.html> [--recipe=report/finance]`. Static — no network, no browser. It reports class names the library does not define, stylesheets that were never linked (naming the file that defines the class), missing `img alt` / table name / `th scope`, skipped heading levels, and the per-layout obligations in `ai/recipes.json`. `npx agentcss ai scaffold <recipe> --out <page.html>` gives a minimal shell that already passes.
|
|
9
|
+
- **See it before shipping:** `npx agentcss ai shot <page.html> --out shot-{width}.png --width=390,1440 --full`. The viewport is exactly the width you ask for (a cropped window is not the same thing), and it reports horizontal-overflow culprits plus JS errors.
|
|
8
10
|
- **Markup ground truth:** each component's `demo.html`. Restyle through tokens (CSS variables); never rewrite component selectors.
|
|
9
11
|
- **Docs are Chinese:** `ai/START-HERE.md` (navigation). English entry points: `README.md`, `llms.txt`, `llms-full.txt`.
|
package/CHANGELOG.md
CHANGED
|
@@ -12,9 +12,130 @@
|
|
|
12
12
|
- **次版本**:新增组件 / 新增能力(下游不改代码也能升级)
|
|
13
13
|
- **修订号**:修 bug、修文案(下游无需任何改动)
|
|
14
14
|
|
|
15
|
+
> **关于历史条目里的 `E123` 这类编号**:那是 0.26.0 及更早版本沿用的
|
|
16
|
+
> 内部失误台账索引,台账本身不随包分发。从 0.27.0 起,本文件只描述
|
|
17
|
+
> **改了什么、影响谁**,不再引用内部编号 —— 读者不需要一份查不到的索引。
|
|
18
|
+
|
|
15
19
|
## [Unreleased]
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
## [0.27.1] - 2026-11-06
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **`ai pdf` 有了就绪契约:`--wait-ms`(0–60000,默认 800)与
|
|
26
|
+
`--ready-selector=<CSS>`。** 导出顺序固定为:页面 `load` → `document.fonts.ready`
|
|
27
|
+
→ 可选可见元素 → 额外等待 → 固定动画终态 → `Page.printToPDF`。
|
|
28
|
+
选择器在打印媒体下必须可见且有尺寸 —— 只存在但 `display:none` 不算就绪。
|
|
29
|
+
整个导出默认 60 秒总预算,超时是明确失败(退出码 4),不是"等一下大概就好了"。
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **PDF 导出不再把旧文件当成本次成功。** 导出改走共享零依赖 CDP 管道
|
|
34
|
+
(`ai/cdp.js`,截图与 PDF 共用)。浏览器只写暂存产物,先验 `%PDF-` 文件头与页树,
|
|
35
|
+
再原子替换最终文件;浏览器失败、超时、非法参数或暂存产物无效时,已有 PDF
|
|
36
|
+
逐字节保留,结论明确为失败。未捕获 JS 异常随 `jsErrors` 返回。
|
|
37
|
+
超页数预算仍是另一种结果:保留完整的新 PDF 并返回 `over-budget`,绝不静默截断。
|
|
38
|
+
- **纸张不再吞下淡入面板或单独多出空白尾页。** Tabs 在 print 媒体下取消动画/过渡
|
|
39
|
+
并让 `opacity` 落到 1;`.page` 在 print 媒体下去掉屏幕最小高度与末尾内边距。
|
|
40
|
+
真实金融长文由 8 页(两页空白)修为 7 页,且每页都有文字。新增延迟内容、旧文件哈希
|
|
41
|
+
保护、有界超时与真实长文逐页内容、崩溃结论形状与临时文件清理判据(E14–E20)。
|
|
42
|
+
- **修正 AI 入口文档漂移。** 暗色查询统一为 `--set=dark`;区分 `check` 的 HTML/JSON
|
|
43
|
+
输入与 `diff` 的 JSON 输入;零模型 Benchmark 记录明确为 `3 / UNVERIFIED`。
|
|
44
|
+
|
|
45
|
+
## [0.27.0] - 2026-11-06
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **`ai check <page.html>` —— 校验产物,而不只是计划。**
|
|
50
|
+
此前 `ai check` 只吃 `doc.json`(节点类型、组件档位),管不到"写出来的那一页"。
|
|
51
|
+
新增 16 条判据,查的都是**不会报错、只会静默失效**的东西:
|
|
52
|
+
类名库里没有 / 类名库里有但这页没引到定义它的样式表(指名到文件)/
|
|
53
|
+
引的样式表一个都读不到 / `<img>` 缺 `alt` / 表没有可访问名称 /
|
|
54
|
+
`th` 缺 `scope` / id 重复 / 标题跳级 / 表单控件没有可读名称 /
|
|
55
|
+
`target="_blank"` 缺 `rel="noopener"` / 缺 `lang`、`charset`、`viewport`。
|
|
56
|
+
纯静态:不联网、不启动浏览器。按扩展名分派,`check x.json` 的行为不变。
|
|
57
|
+
- **`ai shot <page.html> --out shot-{width}.png` —— 出 PNG。**
|
|
58
|
+
用 Chrome 的调试管道(不开端口、不打包浏览器)把视口**设成**要的宽度,
|
|
59
|
+
再截图。为什么不用 `--window-size`:Windows 上窗口有最小宽度,
|
|
60
|
+
`--window-size=390` 实际按 500px 排版、只按 390 裁一刀 —— 图是好的,
|
|
61
|
+
内容不对,且不报错。输出里同时给出横向溢出(指名元素)、内容高度与 JS 报错。
|
|
62
|
+
同一台机器、同样参数两次逐字节一致(门禁验的就是这条)。
|
|
63
|
+
- **`ai scaffold <版式> --out page.html` —— 三个最小壳**(`report/finance`、
|
|
64
|
+
`report/technical`、`report/magazine`):只给正确的骨架与必有件的位置,
|
|
65
|
+
不含内容。三个壳都由门禁跑一遍 `ai check --recipe=<自己>`。
|
|
66
|
+
- **`ai/recipes.json`** —— 版式"必有件"的机器可读事实:能从标记判定的写进
|
|
67
|
+
`requires`(少一件就报错),判不了的写进 `unchecked`(只打印,不判过)。
|
|
68
|
+
- **`prose-gate`** —— 检查页面的**可见文本**:未渲染的 `**加粗**` / 反引号 / `](链接)`、
|
|
69
|
+
整页括号不配平(通常是把 `(…)` 切断),以及生成器是否切在语义边界上。
|
|
70
|
+
自带反向控制:四类注入必须全部抓到,注释与代码块里的同样字符不许误报。
|
|
71
|
+
- **诊断可分辨**:`npm pack` 失败时 `pack-closure` 会打印 npm/node 版本、cwd、
|
|
72
|
+
stdout/stderr 尾,并区分"产品失败(package.json 坏了)"与"清单没拿到(无法判定)";
|
|
73
|
+
`check-all.sh` 在 `node` 不在 PATH 时一次性说明并整体退出 `3`,
|
|
74
|
+
而不是让 5 道 python 门禁各抛一个 `WinError 2` 堆栈。
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- **退出码统一成一套,并且可以按码分支。** 以前同一个 `--profile` 写错,
|
|
79
|
+
`pdf` 给 2、其它子命令给 1;而 `1` 同时表示"页面有错"和"工具崩了"。
|
|
80
|
+
现在全 CLI 一张表:
|
|
81
|
+
|
|
82
|
+
| 码 | 含义 |
|
|
83
|
+
|---|---|
|
|
84
|
+
| 0 | 通过 |
|
|
85
|
+
| **1** | **判定不通过**(页面/文档确实有错 —— 是结论,不是工具坏了) |
|
|
86
|
+
| **2** | 用法错误(开关名、取值、重复、空值) |
|
|
87
|
+
| 3 | 环境不具备(没有浏览器 / 没有 node) |
|
|
88
|
+
| 4 | 渲染失败 |
|
|
89
|
+
| 5 | 超出预算(产物完整) |
|
|
90
|
+
| 6 | 工具内部错误 |
|
|
91
|
+
|
|
92
|
+
失败的 JSON 也统一成**一种形状**:`{"ok":false,"command","error":{"kind","message"}}`
|
|
93
|
+
(`pdf` 以前发的是顶层 `code`/`message`,调用方得写两个分支)。
|
|
94
|
+
- **枚举开关的非法值一律报错,不再退化成"空结果"。** 以前:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
ai tokens --set=not-a-theme --json → stdout 是字面量 undefined,退出码 0
|
|
98
|
+
ai tree --set=not-a-theme --json → 同上
|
|
99
|
+
ai components --maturity=not-a-level → [],退出码 0
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
对 Agent 这不是"没输出",而是**错误的控制流**:它会把拼错的参数读成
|
|
103
|
+
"这个库没有 beta 组件",然后据此写结论。现在非法值 / 空值 / 同一个开关给两次
|
|
104
|
+
/ 开关后面吞掉另一个开关,全部 `2` 退出,并在 message 里列出合法取值。
|
|
105
|
+
合法取值**从数据现算**(`profiles.json` 的 `allowedMaturity`、`tokens.json` 的
|
|
106
|
+
`sets`、`tokens.tree.json` 的 `meta.categories`、`recipes.json` 的版式),
|
|
107
|
+
不在这里另抄一份。
|
|
108
|
+
- **与本命令无关的开关不再被静默忽略。** `ai components --set=dark` 以前退出码 0、
|
|
109
|
+
什么都不说 —— 调用方会以为"按 dark 过滤过了"。现在每个子命令声明自己认识的开关
|
|
110
|
+
(`ai/cli.js` 的 `FLAGS` 表),不认识的给 `2`。
|
|
111
|
+
- **`ai check <page.html> --profile` / `--medium` 明确报错**:这两个开关只对
|
|
112
|
+
`doc.json` 有效。以前静默忽略 ⇒ 调用方以为"按 strict 档查过了"。
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
- **Schema 校验器实现了 `uniqueItems` 与 `not`。** 这两个关键字一直挂在
|
|
117
|
+
"已实现"白名单里却没有实现:`validate({type:'array',uniqueItems:true},[1,1])`
|
|
118
|
+
返回 `ok:true`,`validate({type:'number',not:{const:1}},1)` 同样。
|
|
119
|
+
白名单只保证"允许出现",不保证"真的校验" —— 现在逐个关键字都有正/负契约用例,
|
|
120
|
+
新加关键字却不写用例,门禁直接红。
|
|
121
|
+
- **能力预扫从 4 份 schema 扩到全部 8 份。** 以前只扫 contract/components/tokens/patch,
|
|
122
|
+
`behaviors` / `deprecations` / `tokensAudience` / `tokensTree` 从来没被扫过 ——
|
|
123
|
+
它们里面出现未知关键字也不会有人知道。扩上去当场撞出一个真 bug:
|
|
124
|
+
`patternProperties` 的**键是正则**,被当成关键字检查,于是
|
|
125
|
+
`tokensAudience` 报"用了没实现的关键字"(schema 本身完全合法)。
|
|
126
|
+
- **生成器不再按字符硬切句子。** 首页摘要以前是 `purpose[:56]`,于是
|
|
127
|
+
`delta` 结尾是「会计正负是 ta」、`timeline` 是「颜色在」,首页还因此括号不配平。
|
|
128
|
+
现在按语义边界切(句末标点 > 分句边界 > 硬切 + 省略号),并且括号必须配平。
|
|
129
|
+
`aria-label` 里的小节名走同一条规则 —— 读屏会把它逐字念出来。
|
|
130
|
+
- **可见文本里不再有未渲染的 Markdown。** 修了 5 处:
|
|
131
|
+
`timeline` / `drawer` / `app-shell` 的 `**加粗**`,`responsive-demo` 与
|
|
132
|
+
`report` 页里的反引号代码;另有 `guide/app-shell.html` 的一个孤立 `**`
|
|
133
|
+
(源头是 `04-recipes/app-shell/README.md` 里一处没闭合的加粗)。
|
|
134
|
+
- **`md-render.py` 产出的表格补 `scope="col"` 与可访问名称**
|
|
135
|
+
(取最近一节的小节标题作 `aria-label`)。此前每一份从 Markdown 出来的页面
|
|
136
|
+
都缺这两件 —— GFM 的表格语法里没有它们可写,而缺了不会报错,
|
|
137
|
+
只会让读屏用户听到一串没有表头的数字。
|
|
138
|
+
- `04-recipes/longform/content.html` 随之重生成(差 4 行,纯属性)。
|
|
18
139
|
|
|
19
140
|
## [0.26.0] - 2026-11-04
|
|
20
141
|
|
|
@@ -141,7 +262,7 @@ F8(demo 对比度)、F10(model-viewer 小溢出)、F12(缺 SemVer/废
|
|
|
141
262
|
按真值更正,历史段落只加注记、不改写。
|
|
142
263
|
|
|
143
264
|
上述每一项都跑了正向、反向控制与真实发布包验证;发布前全量门禁 0 FAIL。
|
|
144
|
-
|
|
265
|
+
新增的失误与纠正规则登记在内部失误台账(不随包分发)。
|
|
145
266
|
|
|
146
267
|
---
|
|
147
268
|
|
|
@@ -335,7 +456,7 @@ README 里「想知道我们踩过哪些坑」的指向,从内部文件改到
|
|
|
335
456
|
|
|
336
457
|
### 修复:9 处指向不存在文件的引用
|
|
337
458
|
|
|
338
|
-
|
|
459
|
+
内部失误台账(不入库)、`docs/ROADMAP.md`、`docs/GUIDANCE.md` 被
|
|
339
460
|
README / CONTRIBUTING / 05-audit/README 引用,但文件不存在。
|
|
340
461
|
现在全部改指向真实文件,或去掉编号(内部条号对外部读者没有意义)。
|
|
341
462
|
|
|
@@ -1339,7 +1460,7 @@ G0d 前提:G4 的替换真的改到了文件
|
|
|
1339
1460
|
现在:
|
|
1340
1461
|
|
|
1341
1462
|
- 词表统一走 `terms`(原来 outsider-audit 自己抄了一份,与 leak-scan 口径不同)
|
|
1342
|
-
-
|
|
1463
|
+
- 真实代号与用户名模式移出公开树
|
|
1343
1464
|
(`.gitignore` 里,也不在 npm `files` 里 ⇒ 不进包)
|
|
1344
1465
|
- 读不到时**不假装扫过**:`terms.private_status()` 由三道扫描器打印
|
|
1345
1466
|
「私有词表未加载 ⇒ 本次未覆盖」
|
|
@@ -2084,7 +2205,7 @@ Actions → release → Run workflow → 勾 `shoot_baseline`,
|
|
|
2084
2205
|
### 文档对齐
|
|
2085
2206
|
|
|
2086
2207
|
- `CONTRIBUTING.md`:补「全量为什么**不**在 CI 上跑」的完整理由与责任边界;
|
|
2087
|
-
补 `rc=3`
|
|
2208
|
+
补 `rc=3` 三态表;补「失败时先读本地门禁日志」。
|
|
2088
2209
|
- `docs/A11Y-MANUAL-PROTOCOL.md`:结果表补 `accordion` / `drawer` / `dropdown`
|
|
2089
2210
|
三行 —— 三个 beta 组件此前一个都不在表内,而「beta 升 stable」的判据之一
|
|
2090
2211
|
就是行为零欠账,人工读屏是唯一的用户侧证据。**状态仍写「未执行」**:
|
package/CONTRIBUTING.md
CHANGED
|
@@ -27,23 +27,45 @@
|
|
|
27
27
|
npm ci # 按锁文件装(验证 lockfile 真能装)
|
|
28
28
|
npm run gate # 全量:含浏览器类
|
|
29
29
|
npm run gate:fast # 只跑静态层,不用装浏览器
|
|
30
|
+
npm run gate:pre-commit # 提交前:五道秒级判据(实测约 7s)
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
三档不是"快慢"的区别,是**判断边界**的区别:
|
|
34
|
+
|
|
35
|
+
| 档 | 什么时候跑 | 能证明什么 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `gate:pre-commit` | 每次 `git commit` 前 | 契约 / 页面结构 / 文案 / 行为核心 / 类型没被改坏 |
|
|
38
|
+
| `gate:fast` | push 前 | 加上静态层的全部判据(不含浏览器) |
|
|
39
|
+
| `gate` | 收尾、发版前 | 全部,含浏览器、视觉基线、打包闭包 |
|
|
40
|
+
|
|
41
|
+
`gate:pre-commit` 走绿只说明"可以提交",**不说明可以发版**。想让它自动跑,
|
|
42
|
+
把 `bash 05-audit/check-all.sh --pre-commit` 写进 `.git/hooks/pre-commit` 即可;
|
|
43
|
+
本仓不替使用者装 hook —— 提交节奏是个人选择。
|
|
44
|
+
|
|
32
45
|
全量的门禁条数由 `05-audit/check-all.sh` 实算,**不在文档里写死** —— 新增一道门禁
|
|
33
46
|
就会变,写下来的第二天就是第二个会漂的真值(I-7)。默认 `min(核数, 6)` 路并发;
|
|
34
47
|
组件层要起浏览器,还要逐张比浅色 / 暗色 / RTL 三套视觉基线(按平台分集,
|
|
35
48
|
只比本平台那套)。条数、耗时、并发效率一律以**本轮门禁打印的结果**为准。
|
|
36
49
|
只想确认没改坏静态契约的话用 `gate:fast`,几秒到几分钟。
|
|
37
50
|
|
|
38
|
-
###
|
|
51
|
+
### CI 跑什么、不跑什么(边界,不是缺口)
|
|
39
52
|
|
|
40
53
|
`.github/workflows/gate.yml` 分三层:`quick`(`MODE=fast`,静态 + 视觉)、
|
|
41
|
-
`contract`(API 契约,需 Chromium)、`browsers
|
|
54
|
+
`contract`(API 契约,需 Chromium)、`browsers`(三引擎矩阵)。
|
|
42
55
|
**没有任何一处是 `MODE=full`**,而且不该有 —— 实测在 Linux runner 上跑全量会
|
|
43
56
|
FAIL 10+ 道(字体渲染不同 ⇒ 视觉 diff 必然不一致;路径/编码不同 ⇒ 体积类误报;
|
|
44
57
|
shallow clone ⇒ 依赖 git 状态的判据语义不同;浏览器版本不同 ⇒ 截图基线不匹配)。
|
|
45
58
|
|
|
46
59
|
**一个长期红的 CI 比没有 CI 更糟**:大家会习惯性忽略它,真正的新问题也一起被忽略。
|
|
60
|
+
|
|
61
|
+
触发面(2026-11-05 调整):
|
|
62
|
+
|
|
63
|
+
| 事件 | 跑哪些 | 为什么 |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| 直推 `master` | 只 `quick` | 这个仓库一直是直推,之前**完全没有 CI 保护**;quick 约 3–4 分钟,能挡住"本地没跑就推" |
|
|
66
|
+
| PR | `quick` + `contract` | 合并前要契约 |
|
|
67
|
+
| nightly / 手动 | 全部三个 job | 三引擎矩阵成本高,不该每次 push 都烧 |
|
|
68
|
+
|
|
47
69
|
所以边界是:
|
|
48
70
|
|
|
49
71
|
- **CI 负责**:跨平台可靠的那部分(静态契约、类型、API 形态、单引擎行为)
|
|
@@ -52,10 +74,29 @@ shallow clone ⇒ 依赖 git 状态的判据语义不同;浏览器版本不同
|
|
|
52
74
|
`browsers` job 已覆盖三引擎的组件行为契约,但它**不替代**本机全量:
|
|
53
75
|
它不跑视觉基线(基线按平台分集)、不跑 RTL/响应式/无障碍全量。
|
|
54
76
|
|
|
77
|
+
### 私有词表(维护者;缺失时 leak 判据会变弱)
|
|
78
|
+
|
|
79
|
+
`leak-scan` 的「真实代号 / 用户名」两类要靠一份**不随包分发**的词表才能生效。
|
|
80
|
+
它放在 `internal/private-terms.txt`(已被 `.gitignore` 挡住,也不在 `package.json`
|
|
81
|
+
的 `files` 里)。文件不在时,脚本会打印:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
⚠️ 私有词表未加载(0 条)⇒ 真实代号 / 用户名判据**本次未覆盖**
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**这不是通过**。本地开发把它放回去即可;CI 上因为拿不到这个文件,需要:
|
|
88
|
+
|
|
89
|
+
1. GitHub 仓库 → Settings → Secrets and variables → Actions → 新建
|
|
90
|
+
`FL_PRIVATE_TERMS`,值就是词表全文(多行);
|
|
91
|
+
2. `gate.yml` 的 `quick` job 会把它写进 `internal/private-terms.txt` 再跑门禁;
|
|
92
|
+
3. 没配时日志里会有一条 `::notice`,明确写着"这一类在 CI 上不覆盖"。
|
|
93
|
+
|
|
94
|
+
⚠️ 不要把词表直接提交进仓库 —— 那等于把要防的东西本身公开了。
|
|
95
|
+
|
|
55
96
|
### 失败时先读哪里
|
|
56
97
|
|
|
57
98
|
`selftest-all` 报红时,除屏幕上那 4 行摘要,**完整子输出会落盘**到
|
|
58
|
-
`internal/tmp/selftest-fail-<脚本名>.log`(`internal/`
|
|
99
|
+
`internal/tmp/selftest-fail-<脚本名>.log`(`internal/` 是本地忽略目录,不进仓库)。
|
|
59
100
|
不可复现的红最需要这个 —— 摘要往往不够定位。
|
|
60
101
|
|
|
61
102
|
只想验一条反向控制时,用 `SKIP_SELFTESTS="a.py,b.py" python3 05-audit/selftest-all.py`
|
package/README.md
CHANGED
|
@@ -31,6 +31,8 @@ The JS entry points (`esm/*.mjs`) are **ESM only** — `exports` has no `require
|
|
|
31
31
|
|
|
32
32
|
- **Every demo page, one index** — https://owain323.github.io/frontend-lib/
|
|
33
33
|
- **Component documentation with search** — https://owain323.github.io/frontend-lib/guide/
|
|
34
|
+
- **How this project is organized** — [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
|
35
|
+
(what each layer is, how the automated checks work — in plain language)
|
|
34
36
|
|
|
35
37
|
Both are generated from `ai/components.json` and the per-component `README.md`
|
|
36
38
|
files, so a page cannot quietly go missing from the index when a component is
|
|
@@ -331,10 +333,11 @@ Every component carries a contract covering focus order, keyboard operation, ARI
|
|
|
331
333
|
|
|
332
334
|
```bash
|
|
333
335
|
npm ci
|
|
334
|
-
npm
|
|
335
|
-
npm run gate:
|
|
336
|
-
npm
|
|
337
|
-
npm run
|
|
336
|
+
npm test # unit and behaviour-core tests
|
|
337
|
+
npm run gate:pre-commit # ~7s: the five checks a one-line change can break
|
|
338
|
+
npm run gate:fast # static layer, no browser needed
|
|
339
|
+
npm run gate # full run: browsers, visual baselines, pack closure
|
|
340
|
+
npm run dist # rebuild dist/
|
|
338
341
|
```
|
|
339
342
|
|
|
340
343
|
## License
|
package/README.zh-CN.md
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
- **全部示例页,一个索引** —— https://owain323.github.io/frontend-lib/
|
|
17
17
|
- **带搜索的组件文档** —— https://owain323.github.io/frontend-lib/guide/
|
|
18
|
+
- **这个项目是怎么组成的** —— [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
|
19
|
+
(四层组件、自动检查系统,用平话讲)
|
|
18
20
|
|
|
19
21
|
两者都由 `ai/components.json` 与各组件的 `README.md` **生成**:
|
|
20
22
|
新增组件时索引不会悄悄漏掉它,文档页里的每条链接都指向真实存在的文件。
|
|
@@ -419,7 +421,7 @@ python -m http.server 8000 # 在仓库根起静态服务
|
|
|
419
421
|
|
|
420
422
|
```bash
|
|
421
423
|
python -m http.server 8000 & # 🔴 必须先起静态服务:一部分门禁要真浏览器渲染
|
|
422
|
-
bash 05-audit/check-all.sh #
|
|
424
|
+
bash 05-audit/check-all.sh # 全量门禁;条数以本轮输出为准,本机约 10 分钟
|
|
423
425
|
```
|
|
424
426
|
|
|
425
427
|
每一项的输出是 `PASS` / `FAIL` / `SKIP`,末尾给出失败条数与退出码(全绿为 0)。
|
|
@@ -516,14 +518,15 @@ bash 05-audit/check-all.sh # 全量门禁约 130 项,本机约 8 分钟
|
|
|
516
518
|
|
|
517
519
|
```bash
|
|
518
520
|
node ai/cli.js components --maturity=stable # 列出达到 stable 的组件
|
|
519
|
-
node ai/cli.js tokens --
|
|
520
|
-
node ai/cli.js check doc.json #
|
|
521
|
+
node ai/cli.js tokens --set=dark # 列出暗色令牌
|
|
522
|
+
node ai/cli.js check doc.json # 校验契约计划(读 JSON)
|
|
523
|
+
node ai/cli.js check page.html # 静态检查真实页面(读 HTML)
|
|
521
524
|
node ai/cli.js diff a.json b.json # 两份契约文档按语义 id 比对
|
|
522
525
|
```
|
|
523
526
|
|
|
524
|
-
>
|
|
525
|
-
>
|
|
526
|
-
>
|
|
527
|
+
> `check` 按计划与产物分流:JSON 查节点、组件档位和打印义务;HTML 查类名、
|
|
528
|
+
> 样式表链接与页面结构。两者都是静态检查,不启动浏览器。`diff` 只吃 JSON;
|
|
529
|
+
> 真浏览器的渲染回归由 `npm run gate` 负责。
|
|
527
530
|
|
|
528
531
|
- `ai/components.json` / `ai/tokens.json` 由源码**生成**(`--check` 已接门禁)
|
|
529
532
|
⇒ 改了源码忘记重新生成就红。真值只有一份,不靠"记得同步"。
|
|
@@ -554,16 +557,19 @@ node ai/cli.js diff a.json b.json # 两份契约文档按语义 id
|
|
|
554
557
|
## 开发
|
|
555
558
|
|
|
556
559
|
```bash
|
|
557
|
-
npm ci
|
|
558
|
-
npm test
|
|
559
|
-
npm run test:types
|
|
560
|
-
npm run gate:
|
|
561
|
-
npm run gate
|
|
560
|
+
npm ci # 按锁文件安装
|
|
561
|
+
npm test # 单元测试(约 1 秒)
|
|
562
|
+
npm run test:types # 类型契约:正例必过 + 反例必挂
|
|
563
|
+
npm run gate:pre-commit # 提交前档:5 道最容易被一行改动打破的判据(约 7 秒)
|
|
564
|
+
npm run gate:fast # 静态层:不装浏览器也能跑(约 3.5 分钟)
|
|
565
|
+
npm run gate # 全量:含浏览器、视觉基线与打包闭包(约 10 分钟)
|
|
562
566
|
npm run ai -- check path/to/doc.json # 机器可读契约的命令行(读 JSON 文档)
|
|
563
567
|
npm run benchmark # 兼容性基准
|
|
564
568
|
```
|
|
565
569
|
|
|
566
|
-
|
|
570
|
+
三档是**递进**关系,不是三选一:改代码时 `gate:pre-commit` 随手跑,
|
|
571
|
+
推送前 `gate:fast`,发版前 `gate`。**提交前档绿 ≠ 可发版** ——
|
|
572
|
+
它只覆盖"改一行就可能破"的那几道。
|
|
567
573
|
|
|
568
574
|
类型声明在 `types/index.d.ts`。改动它之前先看 `API.md` 的「稳定程度约定」。
|
|
569
575
|
|