agentcss 0.25.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,68 @@
16
16
 
17
17
  (无)
18
18
 
19
+ ## [0.25.1] - 2026-11-03
20
+
21
+ **修订版:修 `agentcss pdf` 在**长文档**上的一处误判 —— 50 页的文档会被报
22
+ 「无法判定预算」(退出码 4),哪怕预算够、文件也没坏。不改类名、不改令牌、不改 API。**
23
+
24
+ ### 修复:长文档的页树根 `Count` 被读成了叶节点的数
25
+
26
+ `agentcss pdf <page> --max-pages=N` 会交叉核对两个页数:`/Type /Page` 对象数
27
+ 与页树根的 `/Count`。两者不一致时**拒绝判定预算**(这是对的 —— 预算判错的代价是
28
+ "多印 40 页纸,而工具说通过")。
29
+
30
+ 问题出在"页树根"的读法:旧实现取的是**文件里第一个** `/Count`。
31
+ 而 Chromium 的页树是**分层**的,实测一份 50 页产物里有 8 个 `/Count`:
32
+
33
+ ```
34
+ 8, 8, 8, 8, 8, 8, 2, 50 ← 只有最后一个是根
35
+ ```
36
+
37
+ 第一个是叶节点的 8。于是"页对象 50"和"页树 8"打架,工具据此报
38
+ `unreadable` —— **一份完全正常的 50 页文档被读法错误挡在门外**,
39
+ 使用者会以为是自己的 PDF 坏了。
40
+
41
+ 现在顺引用走:`/Type /Catalog` → 它的 `/Pages N 0 R` → 对象 N 的 `/Count`。
42
+ 对象内键序不保证,所以两侧各开一个窗口找;解析不到时返回 `null`
43
+ (= 不做交叉核对),**不退回"第一个匹配"** —— 宁可少一道核对,
44
+ 也不能拿一个错数字去否决用户的文件。
45
+
46
+ 反向控制:把旧读法放回去,同一份样本立刻读成 8,两条端到端断言同时转红。
47
+
48
+ ### 新增:打印门禁的六个对抗夹具(12 条断言)
49
+
50
+ 此前的反向控制喂的是**合成数字**,证明不了"判据拿到真实极端页面还认得出来"。
51
+ 新增 `05-audit/fixtures/print-adversarial/`(生成器 `_gen.py`,改规模只改常量):
52
+
53
+ | 面 | 断言 |
54
+ |---|---|
55
+ | 50 页 | 页数正好 50(多一页 = 尾部空页);文本流与页数**逐个**对上 |
56
+ | 一行表 | 整表落在 1 页(`thead` 被重复到第二页会变 2 页) |
57
+ | 200 列 | 判出横向溢出;配一份压进纸面的对照,证明不是"见大表就红" |
58
+ | 嵌套浮层 | 打印态下两层遮罩都落回正常流、不铺底色,正文与两层弹窗的字都在 |
59
+ | 近空白页 | 一个字符也算字;首屏无字、**三页文档中间一页无字**都必须报出来 |
60
+ | 单块超一页 | 与"同内容可拆分"的对照组比 run 数,少了就是静默丢字 |
61
+
62
+ 12 条都做过变异验证:拆掉 `overlay.css` 的 `@media print` ⇒ ④ 转红;
63
+ 夹具从 50 页改成 51 页 ⇒ ① 转红;夹具目录改名 ⇒ 报"缺 10/10 个"并退出非 0
64
+ (缺测不许算过)。
65
+
66
+ ### 修复:`pagesWithoutText` 的计数会被二进制噪声抵掉
67
+
68
+ ⑫ 算的是 `无字页 = 页数 − 文本流数`,而"文本流"只要求流里出现 `BT` 两个字节 ——
69
+ 字体 / cmap / ICC 的二进制里会**偶然**出现它。实测同一批夹具 10 个里有 6 个多计
70
+ (50 页数出 51 个流、1 页数出 2 个)。而差是**没有位置信息**的:
71
+ 多出来的那一个流,正好抵掉一张真没字的纸 ⇒ ⑫ 报 0,安静地通过。
72
+
73
+ 现在要求同一流里同时有 `BT`(开始文本)+ `Tf`(选字体)+ `Tj`/`TJ`(真的写出来)。
74
+ 实测 10 个夹具逐个精确。副作用是真实页面的 run 数各少了 1–3 个
75
+ (`recipe-report` 1071→1070、`type-wide-table` 1526→1523、`type-form` 578→576、
76
+ `type-grid-media` 517→516,页数一个没变)—— 那些 run 是二进制里的假算子,
77
+ 基线按新口径重录。
78
+
79
+ ---
80
+
19
81
  ## [0.25.0] - 2026-11-03
20
82
 
21
83
  **次版本:新增一个子命令(`agentcss pdf`)+ 新增契约字段(`printPolicy` / `pageBudget`)
package/CONTRIBUTING.md CHANGED
@@ -29,7 +29,7 @@ npm run gate # 全量:含浏览器类
29
29
  npm run gate:fast # 只跑静态层,不用装浏览器
30
30
  ```
31
31
 
32
- 全量是 **232 道门禁**(默认 `min(核数, 6)` 路并发,实测 583 秒 / 退出码 0;
32
+ 全量是 **233 道门禁**(默认 `min(核数, 6)` 路并发,实测 583 秒 / 退出码 0;
33
33
  单道耗时累加 1658 秒,并发效率约 2.8×。组件层要起浏览器,还要逐张比 **109 张**视觉截图 ——
34
34
  47 浅色 + 47 暗色 + 15 RTL,按平台分集,只比本平台那套)。
35
35
  只想确认没改坏静态契约的话用 `gate:fast`,几秒到几分钟。
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
3
  "generatedBy": "05-audit/gen-ai-contract.py",
4
- "libraryVersion": "0.25.0",
4
+ "libraryVersion": "0.25.1",
5
5
  "$comment": "机器可读的组件契约。slots = BEM 元素名(在本库里元素名就是插槽名),states.enum = 组件会在 DOM 上写出的 data-* 取值,states.legacyClass = 仍未收敛到 data 属性的历史状态类(INVARIANT I-8 未达标)。printPolicy = 这个组件在纸上会发生什么(空数组 = 原样保留);pagination = 内容是否被分页裁过(server 不许与 expand 同时出现,否则展开的只是当前一页)。",
6
6
  "maturityCriteria": {
7
7
  "alpha": "缺 demo.html 或缺 05-audit 下的专属门禁 ⇒ 还没被机器验证过,不要用",
package/ai/pdf-pages.js CHANGED
@@ -34,10 +34,38 @@ function countPageObjects(raw) {
34
34
  return (raw.match(/\/Type\s*\/Page[^s]/g) || []).length;
35
35
  }
36
36
 
37
- /** 页树根声明的 `/Count`(取第一个;没有则 null)。 */
37
+ /**
38
+ * 页树**根节点**声明的 `/Count`(拿不到则 null)。
39
+ *
40
+ * 🔴 不能取"文件里第一个 `/Count`" —— 那是**叶节点**的数。
41
+ * 实测一份 50 页的 Chromium 产物:文件里 8 个 `/Count`,依次是
42
+ * `8,8,8,8,8,8,2,50` —— 只有最后一个(根节点)是 50。
43
+ * 按"第一个"取会读成 8,于是 `pages=50` 与 `countFromTree=8` 打架,
44
+ * 导出器据此报"两套说法不一致,无法判定预算" —— **用户拿着一份 50 页的
45
+ * 正常文档,被一个读法错误挡在门外**。
46
+ *
47
+ * 正确路径:catalog `/Type /Catalog` → 它的 `/Pages N 0 R` → 对象 N 的 `/Count`。
48
+ * 对象内的键序不保证,所以两侧各开一个窗口找;找不到就返回 null
49
+ * (null 表示"没有交叉核对",`agree` 为真 —— 宁可少一道核对,
50
+ * 也不能拿一个错数字去否决用户的文件)。
51
+ */
38
52
  function countFromTree(raw) {
39
- const m = /\/Count\s+(\d+)/.exec(raw);
40
- return m ? +m[1] : null;
53
+ const s = Buffer.isBuffer(raw) ? raw.toString('latin1') : String(raw);
54
+ const cat = s.search(/\/Type\s*\/Catalog/);
55
+ if (cat < 0) return null;
56
+
57
+ /* 键序不固定:在 catalog 前后各取一段找 /Pages 引用 */
58
+ const win = s.slice(Math.max(0, cat - 2000), cat + 2000);
59
+ const ref = /\/Pages\s+(\d+)\s+0\s+R/.exec(win);
60
+ if (!ref) return null;
61
+
62
+ /* 找 `N 0 obj` 本身,再在它后面找 /Count。
63
+ ⚠️ 不用 `[\s\S]*?endobj` 截取:流数据里可能**恰好**出现 endobj 的字节。 */
64
+ const at = s.search(new RegExp('(^|[^0-9])' + ref[1] + '\\s+0\\s+obj'));
65
+ if (at < 0) return null;
66
+ const body = s.slice(at, at + 2000);
67
+ const c = /\/Count\s+(\d+)/.exec(body);
68
+ return c ? +c[1] : null;
41
69
  }
42
70
 
43
71
  /** 第一张纸的 `/MediaBox [x0 y0 x1 y1]` → `{width, height}`(pt,1/72 英寸)。 */
@@ -5,8 +5,8 @@
5
5
 
6
6
  | 项目 | 值 |
7
7
  |---|---|
8
- | 声明适用版本 | agentcss `0.25.0` |
9
- | 声明日期 | 2026-11-02 |
8
+ | 声明适用版本 | agentcss `0.25.1` |
9
+ | 声明日期 | 2026-11-03 |
10
10
  | 复核节奏 | 每次发版前重跑(`npm run gate` 内含全部证据) |
11
11
  | 范围 | 本库交付的 CSS / JS、全部 `demo.html`(组件页 + 图表页 + 组合页)、以及文档站生成的页面 |
12
12
  | 不在范围 | 使用者拿这些组件拼出来的业务页面(那是你的无障碍责任,不是库的) |
package/index.html CHANGED
@@ -97,7 +97,7 @@ li a:active { background: var(--surface-sunken); }
97
97
  </p>
98
98
  </main>
99
99
  <footer class="wrap">
100
- <p class="note">0.25.0 · 零第三方运行时依赖</p>
100
+ <p class="note">0.25.1 · 零第三方运行时依赖</p>
101
101
  </footer>
102
102
  </body>
103
103
  </html>
package/llms-full.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # agentcss — full reference (one file)
2
2
 
3
- > Generated from `ai/components.json` + `ai/pitfalls*.json` + `ai/behaviors.json` at version **0.25.0**.
3
+ > Generated from `ai/components.json` + `ai/pitfalls*.json` + `ai/behaviors.json` at version **0.25.1**.
4
4
  > Do not edit by hand: run `python 05-audit/gen-llms.py` (freshness is gated).
5
5
  > Short version: `llms.txt`. Machine contract: `ai/*.json`.
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcss",
3
- "version": "0.25.0",
3
+ "version": "0.25.1",
4
4
  "description": "移动 Web 组件库:CSS + 可选行为脚本,零第三方运行时依赖。样式用 bundler 引入,交互行为用 script 标签引入。",
5
5
  "keywords": [
6
6
  "css",