agentcss 0.11.3 → 0.11.4
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 +2 -2
- package/01-tokens/focus-ring.css +1 -1
- package/01-tokens/tokens.css +1 -1
- package/03-patterns/nav/README.md +1 -1
- package/03-patterns/pagination/pagination.css +2 -2
- package/03-patterns/reveal/README.md +3 -3
- package/04-recipes/analysis-report/demo.html +2 -2
- package/04-recipes/longform/README.md +1 -1
- package/04-recipes/longform/longform.html +6 -10
- package/04-recipes/report/demo.html +1 -1
- package/04-recipes/table/demo.html +1 -1
- package/04-recipes/table/table.css +1 -1
- package/04-recipes/tierA-tokens.css +1 -1
- package/09-assets/README.md +1 -1
- package/09-assets/bar/README.md +1 -3
- package/09-assets/bar/bar.css +2 -2
- package/09-assets/bar/bar.js +6 -6
- package/09-assets/bar/demo.html +2 -2
- package/09-assets/echarts-adapter/echarts-adapter.js +8 -8
- package/09-assets/model-viewer/model-viewer.css +2 -2
- package/09-assets/scientific-plot/scientific-plot.js +1 -1
- package/CHANGELOG.md +84 -48
- package/CONTRIBUTING.md +1 -1
- package/README.md +158 -369
- package/README.zh-CN.md +508 -0
- package/SUPPORT.md +2 -2
- package/ai/START-HERE.md +1 -1
- package/ai/components.json +2 -2
- package/ai/components.meta.json +1 -1
- package/ai/tokens.json +1 -1
- package/dist/09-assets/bar/bar.js +1 -1
- package/dist/09-assets/echarts-adapter/echarts-adapter.js +1 -1
- package/dist/09-assets/scientific-plot/scientific-plot.js +1 -1
- package/dist/COSTS.md +9 -9
- package/docs/INVARIANT.md +1 -1
- package/index.html +77 -62
- package/package.json +3 -3
- package/ERRORS.md +0 -1658
- package/docs/BACKLOG.md +0 -127
- package/docs/BENCHMARK.md +0 -252
- package/docs/GUIDANCE.md +0 -154
- package/docs/RELEASING.md +0 -311
- package/docs/RFC-THEME-SCOPE.md +0 -141
- package/docs/ROADMAP.md +0 -182
package/docs/RELEASING.md
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
# 发布流程
|
|
2
|
-
|
|
3
|
-
> 目标只有一句:**发出去的包,就是门禁验过的那棵树。**
|
|
4
|
-
> 任何一步让这两者产生偏差(版本号不一致、dist 没重建、tag 指错地方),
|
|
5
|
-
> 这次发布的"可追溯性"就已经破了 —— 而追溯性是本库对外承诺的一部分。
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 一、版本号出现在四处(不要手改)
|
|
10
|
-
|
|
11
|
-
| 位置 | 说明 |
|
|
12
|
-
|---|---|
|
|
13
|
-
| `VERSION` | 最短、最不会被格式化影响 ⇒ 以它为准 |
|
|
14
|
-
| `package.json` → `version` | npm 实际用的那个 |
|
|
15
|
-
| `package-lock.json` | **两处**:根包 + `packages[""]`(手改最常漏这里) |
|
|
16
|
-
| `CHANGELOG.md` | 最新一个版本条目的标题 |
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
python3 05-audit/bump-version.py 0.9.0 # 一次性改四处 + 自检读回
|
|
20
|
-
python3 05-audit/bump-version.py --check # 只查一致(已接进门禁 version-sync)
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 二、发布前的六步
|
|
26
|
-
|
|
27
|
-
### 第 1 步 · 决定版本号(人判断)
|
|
28
|
-
|
|
29
|
-
看 `CHANGELOG.md` 的 `## [Unreleased]` 段:
|
|
30
|
-
|
|
31
|
-
| 段里有什么 | 版本 |
|
|
32
|
-
|---|---|
|
|
33
|
-
| 只有 Fixed / Security(改 bug、改文案) | 修订号 +1(`0.8.9` → `0.8.10`) |
|
|
34
|
-
| 有 Added(新组件 / 新能力,下游不改代码也能升) | 次版本 +1(`0.8.x` → `0.9.0`) |
|
|
35
|
-
| 有 Changed 且**不兼容**(令牌或定义改了语义) | 主版本 +1 |
|
|
36
|
-
|
|
37
|
-
### 第 2 步 · 把 `[Unreleased]` 变成正式条目
|
|
38
|
-
|
|
39
|
-
在 `CHANGELOG.md` 里:
|
|
40
|
-
|
|
41
|
-
1. 把 `## [Unreleased]` 改成 `## [0.8.9] - 2026-10-17`(日期用发布当天)
|
|
42
|
-
2. 在 `# Changelog` 之后、`## 版本号约定` 之前**新加一个空的**
|
|
43
|
-
|
|
44
|
-
```markdown
|
|
45
|
-
## [Unreleased]
|
|
46
|
-
|
|
47
|
-
### Added
|
|
48
|
-
|
|
49
|
-
### Changed
|
|
50
|
-
|
|
51
|
-
### Fixed
|
|
52
|
-
|
|
53
|
-
### Security
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
> 空小节可以留,也可以只留标题 —— 但**标题 `## [Unreleased]` 必须在**
|
|
57
|
-
> (`release-gate` 的 `[11] CHANGELOG 结构` 会查)。
|
|
58
|
-
|
|
59
|
-
### 第 3 步 · 同步版本号
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
python3 05-audit/bump-version.py 0.8.9
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
⚠️ 如果 CHANGELOG 里已经有 `## [0.8.9] - ` 这个标题(第 2 步做得对),
|
|
66
|
-
脚本**不会**再插一个 TODO 骨架;否则它会插一个,插完记得写。
|
|
67
|
-
|
|
68
|
-
### 第 4 步 · 全量门禁
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
npm run gate # MODE=full,约 12 分钟
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
**必须 0 FAIL。** 不是"我改的那几道绿了"就行 —— 见 `CONTRIBUTING.md`。
|
|
75
|
-
顺带看清它末尾那行「本轮:执行 N 道 · 按层跳过 M 道」:
|
|
76
|
-
`MODE=full` 下跳过数必须是 **0**。
|
|
77
|
-
|
|
78
|
-
### 第 5 步 · 重建产物并提交
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
python3 05-audit/build-dist.py # 源码 → dist
|
|
82
|
-
python3 05-audit/build-dist.py --check # 必须绿(sha256 对得上)
|
|
83
|
-
python3 05-audit/dist-cost-gate.py --update
|
|
84
|
-
git add -A && git commit -m "release: 0.8.9 —— <一句话>"
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### 第 6 步 · 打 tag(annotated)
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
git tag -a v0.8.9 -m "0.8.9 — 2026-10-17
|
|
91
|
-
|
|
92
|
-
<从 CHANGELOG 抄两三条要点>"
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
⚠️ **必须 annotated**(`-a`),不能是 lightweight:lightweight tag 没有 message,
|
|
96
|
-
半年后回溯时看不到"这一版是什么"。
|
|
97
|
-
`release-gate` 的 `[10] git tag 锚点` 会核对:
|
|
98
|
-
tag 存在 + 是 annotated + **tag 指向的提交里 `VERSION` 就是这个版本**。
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 三、发布(这一步只能人做)
|
|
103
|
-
|
|
104
|
-
### 一条命令
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
bash 05-audit/publish.sh
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
它做四件事:查认证 → 查 registry 不是镜像源 → 查版本五处一致 → `npm publish`。
|
|
111
|
-
最后那一步会先跑 `prepublishOnly`,门禁红就什么都不发出去。
|
|
112
|
-
|
|
113
|
-
**认证有两条路**,脚本都支持:
|
|
114
|
-
|
|
115
|
-
| 路 | 怎么做 | 适用 |
|
|
116
|
-
|---|---|---|
|
|
117
|
-
| a | 先 `npm login`(浏览器授权 / 一次性验证码) | 常用 |
|
|
118
|
-
| b | 把 npm 的 **Automation Token** 存进仓库根的 `.npm-token` | `npm login` 打不开浏览器时 |
|
|
119
|
-
|
|
120
|
-
`.npm-token` 已被 `.gitignore` 挡住(`git check-ignore .npm-token` 可验证),
|
|
121
|
-
**发布完请删掉它**。凭据不进仓库、也不该贴进任何聊天窗口。
|
|
122
|
-
|
|
123
|
-
### 关于 provenance(现在**没有** —— 这是决策,不是遗漏)
|
|
124
|
-
|
|
125
|
-
npm 支持 `npm publish --provenance`:包页面会显示「Built and signed on GitHub Actions」,
|
|
126
|
-
使用者能验证"这个 tarball 由这个仓库、这次提交、这条流水线产出"。
|
|
127
|
-
|
|
128
|
-
我们**现在不发 provenance**,三条原因,每条都可以推翻:
|
|
129
|
-
|
|
130
|
-
| # | 原因 | 推翻条件 |
|
|
131
|
-
|---|---|---|
|
|
132
|
-
| 1 | 人工在本机发布(`publish.sh`)拿不到 OIDC 身份 —— provenance 要由 CI 的 `id-token: write` 签发 | 把发布搬进 GitHub Actions |
|
|
133
|
-
| 2 | 仓库里没有 release workflow(只有 `gate.yml`) | 同上 |
|
|
134
|
-
| 3 | 需要仓库 secret `NPM_TOKEN` | 在 GitHub 设置里加一次 |
|
|
135
|
-
|
|
136
|
-
**要加的时候**动作是:新增 `.github/workflows/release.yml`(`on: workflow_dispatch`),
|
|
137
|
-
job 加 `permissions: id-token: write` 与 `contents: write`,跑
|
|
138
|
-
`npm publish --provenance --access public`,并把 tag / CHANGELOG / VERSION 三者核对
|
|
139
|
-
**放在 publish 之前**。
|
|
140
|
-
|
|
141
|
-
⚠️ provenance 证明的是**来源**,不是**内容正确**。内容正确性仍然由 160+ 道门禁与
|
|
142
|
-
`prepublishOnly` 守着 —— 两件事不能互相替代,别拿一个顶另一个。
|
|
143
|
-
|
|
144
|
-
### ⚠️ 先确认 registry 不是镜像源
|
|
145
|
-
|
|
146
|
-
作者这台机器上 `npm config get registry` 返回 `https://registry.npmmirror.com/`
|
|
147
|
-
(淘宝镜像)。**镜像源是只读的**,`npm publish` 会得到:
|
|
148
|
-
|
|
149
|
-
```
|
|
150
|
-
npm ERR! 403 Forbidden - PUT https://registry.npmmirror.com/agentcss
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
这不是权限问题、也不是网络问题 —— 改密码、重登都没用。
|
|
154
|
-
仓库根目录的 `.npmrc` 已经把 registry 钉在官方源,**在仓库目录里执行**就不会踩到;
|
|
155
|
-
换个目录执行则仍会用镜像。
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
npm config get registry # 必须是 https://registry.npmjs.org/
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
npm login # 需要账号 + 2FA
|
|
163
|
-
npm run publish:check # 可选:手工再跑一次发布前自检(prepublishOnly 也会跑)
|
|
164
|
-
npm publish # 会先跑 prepublishOnly ⇒ 门禁不过就发不出去
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### 为什么 `publish` 必须由人执行
|
|
168
|
-
|
|
169
|
-
- 需要 npm 账号的密码 / 2FA(一次性验证码)
|
|
170
|
-
- 发布是**对外不可逆动作**:npm 对"撤回"有严格限制
|
|
171
|
-
(72 小时内可 `npm unpublish <包>@<版本>`,之后基本只能发 patch;
|
|
172
|
-
政策会变,以 npm 官方文档为准)
|
|
173
|
-
- 发错版本号的代价通常不是"改一下",而是"多一个永久存在的难看的版本"
|
|
174
|
-
|
|
175
|
-
### 发布前自检挡住的是什么(`prepublishOnly`)
|
|
176
|
-
|
|
177
|
-
`package.json` 里有:
|
|
178
|
-
|
|
179
|
-
```json
|
|
180
|
-
"prepublishOnly": "npm run publish:check"
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
它跑「`MODE=fast` 全量静态门禁(约 100 秒)+ `pack-smoke`(真打 tarball 再装一次)」。
|
|
184
|
-
⇒ **门禁红的时候 `npm publish` 会直接失败**,而不是把一颗雷发出去。
|
|
185
|
-
⚠️ 不要把它改成 `prepack`:`pack-smoke` 内部会调 `npm pack`,会递归。
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## 四、发布后必须核对的五件事
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
# ① registry 上真的有这个版本
|
|
193
|
-
npm view agentcss version dist.tarball
|
|
194
|
-
|
|
195
|
-
# ② 装进空项目,exports 每条都能解析(这是使用者唯一关心的事)
|
|
196
|
-
mkdir -p /tmp/pkgcheck && cd /tmp/pkgcheck
|
|
197
|
-
npm init -y >/dev/null && npm i agentcss
|
|
198
|
-
node -e "console.log(require.resolve('agentcss/package.json'))"
|
|
199
|
-
ls node_modules/agentcss/dist/01-tokens/tokens.css
|
|
200
|
-
|
|
201
|
-
# ③ 包内没有内部产物(fixtures / baseline / manifest)
|
|
202
|
-
npm pack agentcss --dry-run --json | grep -E "fixtures|baseline|manifest" || echo "干净"
|
|
203
|
-
|
|
204
|
-
# ④ 包名没有被"近似名"混淆(搜一下会不会装错)
|
|
205
|
-
npm search agentcss --json | head -40
|
|
206
|
-
|
|
207
|
-
# ⑤ 远端 tag 也推了(npm 与 git 的锚点要成对)
|
|
208
|
-
git push origin v0.8.9
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
然后**把 README 的安装段改成已发布**:
|
|
212
|
-
|
|
213
|
-
```markdown
|
|
214
|
-
## 安装
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
npm install agentcss
|
|
218
|
-
```
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
这是发布流程的**最后一步,也是最容易忘的一步** ——
|
|
222
|
-
忘了它,README 会一直说"尚未发布",而包其实已经在 registry 上了。
|
|
223
|
-
|
|
224
|
-
---
|
|
225
|
-
|
|
226
|
-
## 五、发布出问题怎么办
|
|
227
|
-
|
|
228
|
-
| 情况 | 处置 |
|
|
229
|
-
|---|---|
|
|
230
|
-
| 版本号写错 / 忘了改某处 | 还没 `npm publish` ⇒ 直接改;已发 ⇒ 发 patch,**不要**强行覆盖 |
|
|
231
|
-
| 包里混进不该有的文件 | 已发 ⇒ 立刻发 patch(`files` 改好再发);不要靠 `unpublish` 兜底 |
|
|
232
|
-
| 发出去发现功能坏了 | 优先发 patch 修;`unpublish` 只在前 72 小时内可靠 |
|
|
233
|
-
| tag 指错提交 | `git tag -d` + 重打 + `git push --force origin <tag>`(tag 是唯一的例外,可以强推) |
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## 六、三种典型错法(都真实发生过)
|
|
238
|
-
|
|
239
|
-
1. **改了版本号但没重建 `dist`** ⇒ 发出去的包里是旧产物。
|
|
240
|
-
`dist-fresh` 判据守着(源码 sha256 变了而 dist 没重建 ⇒ 红)。
|
|
241
|
-
2. **手改了 `package-lock.json` 的一处**(根包改了、`packages[""]` 没改)⇒
|
|
242
|
-
`version-sync` 红。这是 `bump-version.py` 存在的主要理由。
|
|
243
|
-
3. **README 的安装命令与包名不一致** ⇒ 照做的人装错东西。
|
|
244
|
-
本库真踩过:README 当时写 `npm install frontend-lib`,而那个名字是别人 2017 年的包。
|
|
245
|
-
`release-gate` 的 `[12] 发布前自检` 现在会核对**README 里的裸包名必须等于
|
|
246
|
-
`package.json.name`**。
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
## 七、发布受阻时:GitHub Release 降级分发
|
|
251
|
-
|
|
252
|
-
**2026-10-28 的实况**:账号 2FA 是 `auth-and-writes` ⇒ `npm publish` 需要一次性
|
|
253
|
-
验证码(`EOTP`)。历史上能发是因为本机存着 token(token 免 2FA),而那两个 token
|
|
254
|
-
在 **10-25 全部过期**(granular token 有效期 7 天);**新建 token 本身也要过一次 2FA**
|
|
255
|
-
⇒ 闭环,只能由账号持有人给一次因子。
|
|
256
|
-
|
|
257
|
-
在"暂时发不上去"和"交付不能停"之间,用 GitHub Release 附件分发。装法与 npm 包
|
|
258
|
-
**完全一致**(同样是 `npm pack` 产出的 tgz,`package.json` 里的 `name` 仍是 `agentcss`):
|
|
259
|
-
|
|
260
|
-
```bash
|
|
261
|
-
npm pack # 产 agentcss-<ver>.tgz
|
|
262
|
-
gh release create v<ver> agentcss-<ver>.tgz --notes-file <notes.md>
|
|
263
|
-
# 使用者:
|
|
264
|
-
npm install https://github.com/owain323/frontend-lib/releases/download/v<ver>/agentcss-<ver>.tgz
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
三条纪律:
|
|
268
|
-
|
|
269
|
-
1. **tarball 必须来自 `npm pack`**,不要手工打 zip —— 否则发到 registry 的那一份和
|
|
270
|
-
Release 那一份不是同一个东西,"验证过"就失去意义。
|
|
271
|
-
2. **上传后独立核一次**:`gh api repos/.../releases/tags/v<ver>` 看 asset 的 `size`
|
|
272
|
-
与本地一致;再**从另一台机器**下载算 sha256。本机网络会把它拖成半截
|
|
273
|
-
(实测 739,543 字节只下到 48,418 就断了,而 `curl` 退出码仍是 0)。
|
|
274
|
-
3. **README 必须写真实状态**(两个方向都算):没能发上去就不能写成已发布;
|
|
275
|
-
发上去了也不能继续挂着"registry 上没有"。本次就是后半句踩过的坑 ——
|
|
276
|
-
0.11.2 已经上了 registry,包内 README 却还写着"没有可安装版本",
|
|
277
|
-
装下来的人第一眼读到一句自我否定的话(0.11.3 修)。
|
|
278
|
-
|
|
279
|
-
### 本节状态:**已解除**(0.11.2 起)
|
|
280
|
-
|
|
281
|
-
这一节保留是因为降级路径本身仍然有效(GitHub 被墙时 npm 也可能挂),
|
|
282
|
-
但它不再是**唯一**通路。
|
|
283
|
-
|
|
284
|
-
**真根因(此前判断错了两次)**
|
|
285
|
-
|
|
286
|
-
| 我当时的判断 | 实际 |
|
|
287
|
-
|---|---|
|
|
288
|
-
| "npm 登录失效(401)" | 登录没问题;本机 token 在 **10-25 到期**(granular 默认 7 天) |
|
|
289
|
-
| "账号开了 TOTP,找用户要 6 位码" | 2FA 页只登记了**一把 security key**(`agentc`),**没有 TOTP 设备** —— 手机上任何验证器都不会有 npm 条目,"等推送"永远等不到 |
|
|
290
|
-
| "新建 token 也要 2FA ⇒ 闭环" | 网页端生成 **bypass-2FA 的 granular token** 只需要在网页里勾选,CLI 侧不需要任何一次性码 |
|
|
291
|
-
|
|
292
|
-
**现在怎么做**(可重复,不依赖任何人给验证码)
|
|
293
|
-
|
|
294
|
-
1. 网页 `Settings → Access Tokens → Generate New Token`
|
|
295
|
-
(直达:`/settings/<user>/tokens/granular-access-tokens/new`)
|
|
296
|
-
2. 勾 **Bypass two-factor authentication (2FA)**
|
|
297
|
-
3. Packages and scopes → 权限选 **Read and write (publish and stage)**
|
|
298
|
-
→ Select packages 选 **All packages**
|
|
299
|
-
4. Generate token → 把 `npm_…` 写进 `~/.npmrc`:
|
|
300
|
-
`//registry.npmjs.org/:_authToken=npm_…`
|
|
301
|
-
5. `npm whoami` 必须回账号名,再 `npm publish <tarball> --ignore-scripts`
|
|
302
|
-
6. **轮询 registry 直到目标版本出现**(本次实测:第 7 次、约 50 秒后出现;
|
|
303
|
-
前 6 次都只有旧版本)。不要在第一次查询就下结论。
|
|
304
|
-
|
|
305
|
-
**当前机器上的凭据**(0.11.2 / 0.11.3 就是用它在发):
|
|
306
|
-
`~/.npmrc` 里的 `agentcss-publish`,创建于 2026-10-28,**到期 2026-11-04**(npm 对带写权限的 token 默认只给 7 天,上限 90 天)。
|
|
307
|
-
到期前重新生成一枚(同样勾 bypass + Read and write + All packages),
|
|
308
|
-
否则 CICD 会以 `401` 的形式失败 —— 而 `401` 很容易被误读成"没登录"。
|
|
309
|
-
|
|
310
|
-
⚠️ npm 公告:**带 bypass-2FA 的 token 直接发布能力将在 2027 年 1 月移除**
|
|
311
|
-
(之后要改走 stage + 2FA 审批)。届时本节要重写,不能照抄。
|
package/docs/RFC-THEME-SCOPE.md
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# RFC · 令牌主题的容器级作用域(P1)
|
|
2
|
-
|
|
3
|
-
状态:**已实施**(0.11.0)。本文件记录决策与**不做什么**,实施细节见
|
|
4
|
-
`01-tokens/tokens.css` 的生成段与 `05-audit/theme-scope-gate.js`。
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 一、问题
|
|
9
|
-
|
|
10
|
-
现在暗色只有两条入口,都在**根元素**上:
|
|
11
|
-
|
|
12
|
-
```css
|
|
13
|
-
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { … } }
|
|
14
|
-
:root[data-theme="dark"] { … } /* 由 theme-sync.py 生成 */
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
后果是:**一个页面只能有一种主题**。做不了这些真实需求 ——
|
|
18
|
-
|
|
19
|
-
- 文档站里把"反色示例"放在一块区域(同一页同时展示明暗两版)
|
|
20
|
-
- 营销页的深色 hero + 浅色正文
|
|
21
|
-
- 嵌入第三方内容的容器强行用某一套配色(对方假设了底色)
|
|
22
|
-
|
|
23
|
-
对标对象:Primer 的 `[data-color-mode]`、Radix Themes 的 `<Theme>` 组件、
|
|
24
|
-
Material 3 的局部 surface —— 都是**容器级**的。
|
|
25
|
-
|
|
26
|
-
## 二、决定
|
|
27
|
-
|
|
28
|
-
### D1 · 选择器去掉 `:root` 限定
|
|
29
|
-
|
|
30
|
-
```css
|
|
31
|
-
:root[data-theme="dark"],
|
|
32
|
-
[data-theme="dark"] { … }
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
保留 `:root[...]` 一项是为了根元素上的特异性不降级(与旧行为逐字等价),
|
|
36
|
-
`[data-theme="dark"]` 这一项让**任何容器**也能用。
|
|
37
|
-
|
|
38
|
-
### D2 · 必须显式生成 `[data-theme="light"]` 块
|
|
39
|
-
|
|
40
|
-
CSS 的 `var()` 是**继承**的。容器里声明了 `--raw-*`,后代全部继承过去 ——
|
|
41
|
-
没有任何"取消声明、回到上一个作用域"的语法:
|
|
42
|
-
|
|
43
|
-
- `revert` 回滚到 UA/用户来源,自定义属性在 UA 里不存在 ⇒ 变成"未定义",**不是**回到父级浅色值
|
|
44
|
-
- `unset` 对自定义属性等同 `inherit` ⇒ 继承的还是暗色值
|
|
45
|
-
|
|
46
|
-
所以"暗容器里再嵌一块浅色"只能靠**把浅色 L1 全部重写一遍**。
|
|
47
|
-
|
|
48
|
-
⚠️ 这等于造出**第三份真值**(浅色 L1 现在有两处)。按 `docs/INVARIANT.md` I-7,
|
|
49
|
-
第二份手写真值必然漂移 ⇒ 所以这块**由 `theme-sync.py` 生成**,并逐条校验(判据 ②)。
|
|
50
|
-
不手写。
|
|
51
|
-
|
|
52
|
-
### D3 · 容器不支持"未标注即跟随系统"
|
|
53
|
-
|
|
54
|
-
自动跟随仍然**只在根上**生效。理由不是偷懒:
|
|
55
|
-
|
|
56
|
-
要让容器"没写 data-theme 时跟着系统走",选择器只能是
|
|
57
|
-
`*:not([data-theme])` 之类 —— 那会把 30 个令牌**写到每一个元素上**。
|
|
58
|
-
后果有两个,都是硬伤:
|
|
59
|
-
|
|
60
|
-
1. 每个元素各存一份令牌 ⇒ 使用者写在 `body` 上的 `--paper` 覆盖不了任何后代
|
|
61
|
-
2. 元素数 × 30 个自定义属性,长页面上是实打实的内存与样式重算开销
|
|
62
|
-
|
|
63
|
-
⇒ 约定:**容器要局部主题,就必须显式写 `data-theme="dark" | "light"`**。
|
|
64
|
-
|
|
65
|
-
### D4 · 本次**不加** `color-scheme`
|
|
66
|
-
|
|
67
|
-
`color-scheme` 能让原生滚动条、日期选择器、复选框跟随主题(`date-range.css`
|
|
68
|
-
已经在局部这么做了)。但把 `color-scheme: dark` 加到根上会**改原生控件的渲染**:
|
|
69
|
-
|
|
70
|
-
- 40 页视觉基线会全部变化
|
|
71
|
-
- 而"零视觉影响"正是本库用来证明改动安全的手段 —— 一旦基线全变,这次改动就失去了可比对的证据
|
|
72
|
-
|
|
73
|
-
⇒ 单独一轮做,并且要为它**专门**重建基线、写清"这次基线变化是预期的"。
|
|
74
|
-
不是 forgotten,是 deferred,见本文末"未决"。
|
|
75
|
-
|
|
76
|
-
### D5 · 组件里不许再按 `prefers-color-scheme` 分支
|
|
77
|
-
|
|
78
|
-
一旦主题可以来自容器,`@media (prefers-color-scheme)` 就**只反映系统**,
|
|
79
|
-
不反映"当前作用域实际是什么"。这类覆盖在三种情况下会失效:
|
|
80
|
-
|
|
81
|
-
| 场景 | 组件内的 media 查询 | 结果 |
|
|
82
|
-
|---|---|---|
|
|
83
|
-
| 系统浅色 + `data-theme="dark"` | 不命中 | 组件留在浅色样式 ⇒ **与周围不自洽** |
|
|
84
|
-
| 系统深色 + `data-theme="light"` | 命中 | 组件用了暗色样式 ⇒ **与周围不自洽** |
|
|
85
|
-
| 容器级 `data-theme` | 完全不反映 | 同上 |
|
|
86
|
-
|
|
87
|
-
⇒ 组件的明暗差异必须**全部由令牌表达**。本次清理出两处:
|
|
88
|
-
|
|
89
|
-
- `badge.css`:那段 media 块与基础规则**逐字相同**(`--text-on-solid` 在两个
|
|
90
|
-
主题下都已正确翻转)⇒ 纯死代码,删除
|
|
91
|
-
- `tooltip.css`:这段是真分支(暗色下把气泡从"反色"改成 `--surface-raised`)。
|
|
92
|
-
改成两个新语义令牌 `--tooltip-bg` / `--tooltip-fg`,由 L1 按作用域翻转
|
|
93
|
-
|
|
94
|
-
## 三、影响面
|
|
95
|
-
|
|
96
|
-
| 文件 | 变化 | 是否影响既有页面渲染 |
|
|
97
|
-
|---|---|---|
|
|
98
|
-
| `01-tokens/tokens.css` | 生成段变成两个(dark 加选择器、新增 light) | **否**(`:root` 下的解析值不变) |
|
|
99
|
-
| `01-tokens/tierA-tokens.css` | 重新生成 | 否 |
|
|
100
|
-
| `01-tokens/focus-ring.css` | 4 个选择器去掉 `:root` 限定 | 否(根上特异性不变,仍是 `[data-theme]` 0,1,0 vs 原 0,2,0 —— 见下注) |
|
|
101
|
-
| `02-primitives/badge/badge.css` | 删一段等价 media 块 | 否 |
|
|
102
|
-
| `03-patterns/tooltip/tooltip.css` | media 块 → 令牌 | **否**(新令牌在两种主题下取的就是原来那两组颜色) |
|
|
103
|
-
| 其余 33 个组件 | 不动 | 否 |
|
|
104
|
-
|
|
105
|
-
> 焦点环那 4 个选择器:原本 `:root[data-theme="dark"] :focus-visible` 是 0,2,0;
|
|
106
|
-
> 改成 `[data-theme="dark"] :focus-visible` 是 0,1,0。它的对手是
|
|
107
|
-
> `:focus-visible { outline-color: var(--accent) }`(0,1,0,位置更靠前)——
|
|
108
|
-
> 仍然后者被覆盖,因为同类特异性下**后出现者赢**。已由 `focus-ring-check.js` 与
|
|
109
|
-
> 新增的容器用例共同守住。
|
|
110
|
-
|
|
111
|
-
## 四、验收判据(全部机械可跑)
|
|
112
|
-
|
|
113
|
-
| 编号 | 判据 | 门禁 |
|
|
114
|
-
|---|---|---|
|
|
115
|
-
| ① | 暗色生成段与 `@media` 段**逐条一致** | `theme-sync --check` |
|
|
116
|
-
| ② | 浅色生成段与 `:root` 的 L1 **逐条一致** | `theme-sync --check` |
|
|
117
|
-
| ③ | 根上四种组合(系统 × data-theme)判色仍正确 | `theme-css-gate` |
|
|
118
|
-
| ④ | 容器内组件随容器变色、容器外不变 | `theme-scope-gate` |
|
|
119
|
-
| ⑤ | 嵌套回退:暗容器里的浅容器真的是浅色 | `theme-scope-gate` |
|
|
120
|
-
| ⑥ | 组件在容器作用域下对比度仍达标 | `theme-scope-gate`(实测对比度,不读源码) |
|
|
121
|
-
| ⑦ | 全库不再有"组件按系统偏好分支" | `theme-scope-gate` 静态判据 |
|
|
122
|
-
|
|
123
|
-
反向控制(必须能红):把 `[data-theme="dark"]` 改回 `:root[data-theme="dark"]` ⇒
|
|
124
|
-
④⑤ 立刻红;删掉 `[data-theme="light"]` 段 ⇒ ⑤ 红;把 tooltip 的 media 块加回去 ⇒ ⑦ 红。
|
|
125
|
-
|
|
126
|
-
## 五、回滚
|
|
127
|
-
|
|
128
|
-
改动按三次提交落地(RFC / 核心作用域 / 组件去 media),每一提交都可单独
|
|
129
|
-
`git revert` 而不影响另两步。回滚"核心作用域"后,组件侧的新令牌仍能工作
|
|
130
|
-
(只是不再支持容器级),不会留下悬空 `var()`。
|
|
131
|
-
|
|
132
|
-
## 六、未决(下次拍板)
|
|
133
|
-
|
|
134
|
-
1. **`color-scheme`**(D4):加不加、加到哪一层、基线怎么重录 —— 需要单独一轮。
|
|
135
|
-
建议做法:只在**显式** `data-theme` 作用域上设 `color-scheme`,根上不改 ⇒
|
|
136
|
-
既有页面基线不变,容器内的原生控件才跟随。
|
|
137
|
-
2. **容器级"跟随系统"**(D3):如果确实需要,正确做法不是 `*:not([data-theme])`,
|
|
138
|
-
而是让使用者显式写 `data-theme="auto"`,由 CSS 无法表达 ⇒ 必须 JS 落地。
|
|
139
|
-
这会破坏"纯 CSS 明暗"(I-4 的教训),所以**默认不做**。
|
|
140
|
-
3. **主题内的阴影**:`--shadow-1/2` 是固定 `rgba(31,29,25,…)`,暗色下几乎不可见。
|
|
141
|
-
这是既有问题,与作用域无关,留到令牌色阶工程(D5b)一起处理。
|
package/docs/ROADMAP.md
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# 0.4.x → 1.0 路线图
|
|
2
|
-
|
|
3
|
-
> 依据:外部评审意见 + 自查核实。
|
|
4
|
-
> 评审的每一条断言都已逐条核实,**结论记在「核实」列** —— 不照单全收。
|
|
5
|
-
|
|
6
|
-
## 一、评审断言的核实结果
|
|
7
|
-
|
|
8
|
-
| # | 评审的说法 | 核实 | 处置 |
|
|
9
|
-
|---|---|---|---|
|
|
10
|
-
| 1 | Select.value 应为标量 | ✅ 属实 | 已修 |
|
|
11
|
-
| 2 | ComponentInstance.destroy() 是假的 | ✅ 属实(三个组件都没有) | 已修 |
|
|
12
|
-
| 3 | Combobox.tags 应是 getter | ✅ 属实 | 已修 |
|
|
13
|
-
| 4 | DateRange 实例没有静态方法 | ✅ 属实 | 已修 |
|
|
14
|
-
| 5 | PresetName 列的值全不存在 | ✅ 属实(真实只有 5 个) | 已修 |
|
|
15
|
-
| 6 | DateRange.onChange 签名错 | ✅ 属实 | 已修 |
|
|
16
|
-
| 7 | API 门禁抓不到上面这些 | ✅ 属实(只比对方法名) | 已完成(W3) |
|
|
17
|
-
| 8 | README 的 script src 会 404 | ✅ 属实 | 已修 |
|
|
18
|
-
| 9 | overlay 嵌套 inert 有归属权 bug | ✅ 属实 | 已修 |
|
|
19
|
-
| 10 | prefix-build 只改 CSS,不安全 | ✅ 属实 | 已完成(W9) |
|
|
20
|
-
| 11 | CI 没有 npm ci | ✅ 属实 | 已完成(W7) |
|
|
21
|
-
| 12 | lockfile 指向 npmmirror | ✅ 属实 | 已完成(W7) |
|
|
22
|
-
| 13 | START-HERE 事实过时 | ✅ 属实 | 已修 |
|
|
23
|
-
| 14 | CHANGELOG 日期空、数字过时 | ✅ 属实 | 已完成(W8) |
|
|
24
|
-
| 15 | API.md 自身 create/attach 不一致 | ✅ 属实 | 已完成(W8) |
|
|
25
|
-
| 16 | **npm 泄漏内部文档** | ❌ **不属实** | 已用 tarball 实装证伪 |
|
|
26
|
-
|
|
27
|
-
> ⚠️ **处置列在 0.8.0 收口时核对过一次**:第 7 / 10 / 11 / 12 / 14 / 15 条
|
|
28
|
-
> 第一版写的是「待办 W3 / W9 / W7 / W8」—— 那是**当时的决定**,
|
|
29
|
-
> 而这些编号在第三节「待办事项」里都已标 ✅ 完成。
|
|
30
|
-
> 两处打架比漏写一项更误导人 ⇒ 统一改成「已完成(Wx)」。
|
|
31
|
-
|
|
32
|
-
### 关于第 16 条的更正
|
|
33
|
-
|
|
34
|
-
评审未提,但自查时一度以为 `npm install` 会把仓库里未发布的内部目录一起装进去。
|
|
35
|
-
**实测证明是假象**:`npm install file:../repo` 走本地目录软链,npm 会照搬整个仓库;
|
|
36
|
-
真实的 `npm pack` + `npm install *.tgz` 内容完全干净(`files` 白名单生效)。
|
|
37
|
-
|
|
38
|
-
⇒ 这条已转成 W2(打包实装冒烟),**确保结论持续成立**,而不是改一个不存在的 bug。
|
|
39
|
-
|
|
40
|
-
## 二、本轮已完成
|
|
41
|
-
|
|
42
|
-
| 事项 | 内容 | 验证方式 |
|
|
43
|
-
|---|---|---|
|
|
44
|
-
| W1 | 类型契约校正(6 组幻觉 API) | 正例编译通过 + 10 条反例全部被拒 |
|
|
45
|
-
| W1b | overlay inert 归属权修复 | 契约测试 + **反向控制**(退回旧实现必然红) |
|
|
46
|
-
| W2 | 打包实装冒烟(7 项) | 注入语法错误 / 改错文档路径,均被抓到 |
|
|
47
|
-
| W3 | TypeScript 编译门禁 | 把类型改松后门禁精确指出被改的那一处 |
|
|
48
|
-
| W8 | README script 路径 | 门禁化,路径不存在即红 |
|
|
49
|
-
|
|
50
|
-
### 顺带修掉的门禁自身假绿
|
|
51
|
-
|
|
52
|
-
这两条比评审列的更严重 —— **门禁在骗人**:
|
|
53
|
-
|
|
54
|
-
1. **`es5-gate` 传 `--dir` 时扫 0 个文件,却报 PASS**。
|
|
55
|
-
它自己源码第 98-103 行就写了这个警告,但 `check-all.sh` 一直在传 `--dir`。
|
|
56
|
-
⇒ 已改回位置参数,现在真扫 53 个文件。
|
|
57
|
-
2. **16 个门禁用 `--dir .`,会扫进 `node_modules`**。
|
|
58
|
-
装 TypeScript 后立刻暴露:a11y 报「index.html 缺 lang」,那是第三方文件。
|
|
59
|
-
⇒ 改为显式列举目录 + 按门禁能力分组。
|
|
60
|
-
|
|
61
|
-
> ⭐ 这两条印证了评审的核心判断:
|
|
62
|
-
> **最危险的不是代码有bug,而是治理系统给出错误的绿灯。**
|
|
63
|
-
|
|
64
|
-
## 二·五、0.4.0 已完成(对标五强后的拔高)
|
|
65
|
-
|
|
66
|
-
对标 Pico CSS / Radix / Open Props / Web Awesome / Primer,
|
|
67
|
-
每一条的「学什么、不学什么、为什么不学」都写在 `BENCHMARK.md`。
|
|
68
|
-
|
|
69
|
-
| 事项 | 来源 | 判据(怎么算做完) |
|
|
70
|
-
|---|---|---|
|
|
71
|
-
| `ai/` 机器可读契约 + 由源码生成 + 同步门禁 | Open Props / Web Awesome | 改源码不重新生成 ⇒ 红;注入假组件 ⇒ 拒 |
|
|
72
|
-
| Patch API(对语义 ID 的五种操作) | 结构要求 | 非法操作 ⇒ 文档字节级不变 |
|
|
73
|
-
| 独立 CLI `node ai/cli.js` | 结构要求 | 不加载任何模型也能跑完八个子命令 |
|
|
74
|
-
| Profiles + 组件成熟度 | Primer | strict 下报错数 > creative 下报错数 |
|
|
75
|
-
| 能力协商(不按模型名分支) | 结构要求 | 声明不同能力 ⇒ 得到不同可用操作集 |
|
|
76
|
-
| 前向兼容 `extensions` | 结构要求 | 旧校验器遇未知扩展只忽略,不判非法 |
|
|
77
|
-
| 呈现形态语义移出核心 | 结构要求 | 核心检索呈现类关键词 ⇒ 0 命中 |
|
|
78
|
-
| 规范分三层(Invariant / Contract / Guidance) | 架构要求 | 三层职责不重叠;建议层改动不影响门禁 |
|
|
79
|
-
| **强制暗色改纯 CSS**(去掉 30 条 inline) | Pico | 不加载 JS 时四种组合在真浏览器全成立 |
|
|
80
|
-
| **`is-*` 收敛为 data 属性** | Radix | 全库归零;成熟度按四条判据重判 —— **不是一律 stable**(27 个全 stable 的阶梯没有鉴别力,对标 Primer 六档每档都要有硬判据;现状 20 stable / 7 beta) |
|
|
81
|
-
| 级联分层**就绪门控**(不切换) | Web Awesome | 每处 `!important` 都能归类;加第一条 `@layer` 就要基线档 |
|
|
82
|
-
|
|
83
|
-
⚠️ 级联分层**故意不切**:目标环境是移动 WebView,基线未确认,
|
|
84
|
-
切换等于拿使用者的线上页面赌。前提与判据见 `BENCHMARK.md`
|
|
85
|
-
「级联分层的决定」一节 —— **写了就算闭环,不是遗留缺口**。
|
|
86
|
-
|
|
87
|
-
## 三、待办事项(按优先级)
|
|
88
|
-
|
|
89
|
-
### 🔴 P0 —— 契约类,不做完不能发1.0
|
|
90
|
-
|
|
91
|
-
| # | 事项 | 判据 |
|
|
92
|
-
|---|---|---|
|
|
93
|
-
| W3 | ✅ **API 契约门禁已重写** —— 真浏览器取`getOwnPropertyDescriptors`,双向比对 | 已验:注入 `Select.destroy` + `getValue` 双双被抓 |
|
|
94
|
-
| W4 | ✅ **文档事实门禁已建** —— 组件清单/门禁数/版本日期 | 已验:改门禁数字必报错 |
|
|
95
|
-
| W5 | ✅ **API.md 已形式化** —— 实测出create 7 / attach 2 / construct 2 / direct 8 四类| 已验:把 Drawer 写成 create 必报错 |
|
|
96
|
-
|
|
97
|
-
### 🟠 P1 —— 发布工程类
|
|
98
|
-
|
|
99
|
-
| # | 事项 | 判据 |
|
|
100
|
-
|---|---|---|
|
|
101
|
-
| W6 | ✅ **已完成(并入 W3)** —— 真实浏览器 API 快照:从运行时提取,与类型、API.md 三方对照 | 已随 W3 的 `api-contract-gate.py` 落地:真浏览器里取 `getOwnPropertyDescriptor`,与 `.d.ts` 声明、`API.md` 三方对齐。单独立项会与 W3 重复同一份判据(E31)。 |
|
|
102
|
-
| W7 | ✅ **CI 已补 npm ci + WebKit** —— 拆三层 PR/契约/nightly | CI 每个 job 都装依赖 |
|
|
103
|
-
| W8 | ✅ **lockfile 已换官方源** —— 91 处 npmmirror → npmjs | 已实测 npm ci 正常 |
|
|
104
|
-
| W9 | ✅ **prefix 已做成三层联动** —— CSS/JS/HTML 同步改写 | 已验:只换 CSS 时 `display:block`失效,全换后 `flex` 生效 |
|
|
105
|
-
|
|
106
|
-
### 🟡 P2 —— 长期
|
|
107
|
-
|
|
108
|
-
| # | 事项 | 判据 |
|
|
109
|
-
|---|---|---|
|
|
110
|
-
| W10 | 真机 WebView 验证(iOS WKWebView + Android WebView 各一个) | 🟡 **已采集 2 个真实入口**:iOS Safari(15/15)、Android 微信内置 WebView / XWEB(14/15)。通道是探针页直接 POST 回传,不需要人工抄数字。**仍缺**:Android 系统 WebView(Chrome)、iOS 微信、其它 App 内核 |
|
|
111
|
-
| ~~W11~~ | ~~删掉失效的 api-snapshot(被 W3 取代后)~~ | ✅ **已完成**(0.8.9 核对):`api-snapshot.py` 已不在仓库里,只剩三处**历史说明**(`api-contract-gate.py` / `api-contract.js` / `tsc-gate.py` 的文件头,讲的是它为什么被取代 —— 那些要留着)。⚠️ 这一项在表里躺了好几轮没人划掉,正是 `ERRORS.md` E13「文档里的待办没人回来划」的又一次 |
|
|
112
|
-
| W12 | **`@layer` 的 WebView 基线实测** | 🔴 **结论:不能无条件切层**(5 行数据,1 行阻塞):iOS Safari 15/15 · iOS 微信 15/15 · iOS 企微 15/15 · Android 微信 XWEB 14/15(缺 `scrollbar-width`,有私有回退)· **Android 百度浏览器 11/15,缺 `@layer`(Chromium 97)**。`@layer` 不支持时**整层规则被丢弃**(不是层序错,是样式没了)⇒ 只能 `@supports` 双写或不切。⚠️ 决策还差一步:百度浏览器在目标受众里的占比 —— 基线表只回答"支不支持",不替产品下"值不值得"的结论。另:判据被数据纠正过一次(原来把"缺任何库在用的能力"都算成不能切层,而 `scrollbar-width` 与层叠无关),已拆成"阻塞(只认 `@layer`)/ 不阻塞但需逐条判定回退"。方法论:**探针说某项不支持 ≠ 该能力在本库没效果**,判读必须回源码看有没有第二条路 |
|
|
113
|
-
| W13 | Compatibility Benchmark 的**真模型实测** | ✅ 工具链已封闭:记录 v2 格式 / 八条判据 / `--check-records` / 参考记录必须满分(管线自洽)。❌ **真模型跑批仍是 0 条** —— 缺的是 API 访问权,不是代码。状态是 `UNVERIFIED`,不是 `PASS` |
|
|
114
|
-
|
|
115
|
-
### 🟡 P2·决策项 —— 已论证,**不要擅自实施**
|
|
116
|
-
|
|
117
|
-
这四项看起来像"机会",实际是**取舍**。记在这里是为了避免下次重开同一个争论。
|
|
118
|
-
|
|
119
|
-
| # | 事项 | 为什么不做(实测依据) | 什么时候能重开 |
|
|
120
|
-
|---|---|---|---|
|
|
121
|
-
| D1 | ✅ **已完成(Unreleased)** —— 包内同一批 CSS 不再出现三次 | 做法:`dist/x.css` **内容改成压缩版**、不再产出 `x.min.css`。路径不变 ⇒ 使用者的 `import` 一行都不用改;写死 `.min.css` 的少数人去掉 `.min`。⚠️ **口径更正(重要)**:删掉的那 39 个 `*.min.css` 让**解包**从 632 → 427 KB(−205 KB,−32%),但 **tarball 只从 650.3 → 641.8 KB(−8.5 KB,−1.3%)** —— 因为 tar.gz 是单一压缩流,两份近乎相同的 CSS 会被高效压掉。⇒ 此前"占解包 19.3%"的说法没错,但它**不等于**下载体积能省 19%;两者差 24 倍。 | 已完成。验收:`dist-parity` 39 个产物 CSSOM 一致 / 明暗两套视觉回归 34-34 全一致 / `pack-smoke` 全绿 / 暗色对比度 1237 个文字 0 不足 |
|
|
122
|
-
| D2 | ❌ **判定不做(用户决定)** —— dist 的 JS 只去注释不压缩(esbuild 实测 raw −33.7% / gzip −13.6%) | 撞 `build-dist.py` 约束 ②「零依赖、零构建链:不引入任何 npm 包」。为绕开它而**手写** minifier,就回到约束 ③ 明确拒绝的风险上。(已实测:`--target=es5` 与 `--target=es2017` 输出逐字节相同 ⇒ 源码本就是纯 ES5,风险比预想低,但未归零) | **已决定:不破零依赖。** 剩余体积大头在 CSS(D1 已经吃掉解包 205 KB),JS 那点 gzip 收益不值得为它引入构建链;而手写 minifier 会重新踩上约束③ 拒绝过的风险 —— 压缩器 bug 的表现是**某个组件在某个分支下静默失效**,比省几十 KB 重要得多。⇒ 保持 `dist/**/*.js` 只去注释。若将来改做"发布前构建一次、产物入库",再重开。 |
|
|
123
|
-
| D3 | ❌ **试做后判定:不做** —— 拆 `table-core` + `table-variants` | **试过了**:按规则块切分,`core` 11.7 KB + `variants` 4.5 KB,规则集合与原文 47 条完全等价(切分脚本能通过等价校验)。但**净账不成立**:`build-dist` 扫 `04-recipes/*.css`,新增的两个文件会一起进 dist ⇒ dist 多 16.2 KB(gzip 约 +3.5 KB),而"只用核心"的人只省 4.5 KB,且他必须主动改引入路径。⇒ **不关心的人多付、关心的人小幅收益**,对"默认交付"的库是负和。真正的按需方案是 `purge-css.js`(W14)—— 按实际 DOM 生成,实测 analysis-report 页删掉 8,797 B 未命中规则,且不需要使用者改任何路径。 | 若将来改做"按组件单独发包"(每个组件独立 entry),再重开这条 —— 那时切分才有意义,因为粒度是包而不是文件 |
|
|
124
|
-
| D4 | ✅ **已完成** —— 全仓行尾统一到 LF,并有 `eol-gate` 防复发(改造前实测 300 个文件 CRLF / 80 个 LF) | `dist/manifest.json` 存了每源文件的 `sourceSha256`,`05-audit/size-baseline.json` 存了每源文件的 raw/gzip 字节数 —— 这两份基线**按当前混合行尾录的**。直接加 `.gitattributes eol=lf` 会让 clone 出来的树全变 LF ⇒ 那 300 个文件的两份基线**全部对不上** ⇒ `dist-fresh` 与 `size-budget` 在**新克隆里假红**(这正是"clone 能不能跑绿"被搞坏的方式)。⇒ **两步必须一起做**:① 先让判据不再依赖行尾(读文本时把 CRLF 归一成 LF 再算哈希/量字节,且只写一份读取器 —— E31)② 再做全仓统一 + 重录两份基线 | 已完成。① 归一化读取器收敛成 `05-audit/_bytes.py`(`read_norm` / `sha256` / `size` / `gzip_size`),`dist-fresh` 与 `size-budget` 都改用它 —— E31:只写一份;② 全仓转 LF 后重录两份基线。
|
|
125
|
-
|
|
126
|
-
⚠️ **但"搬运 ≠ 约束"**:转完之后**三个生成器**写文件没指定 `newline`,每跑一次就把产物翻回 CRLF —— 实测复发三次(`theme-sync` 写 tokens.css / `make-tiersnippet` 写 tierA / `shot-baseline` 写 `_stamp.json`)。现由 `eol-gate.py` 兜住:`git ls-files --eol` 的**索引侧与工作区侧**都不得是 `crlf` / `mixed`;反向控制用**伪造的 `--eol` 输出**,不依赖"仓库里恰好有个坏文件"(那种前提会随仓库状态漂移,见 E50)。现状:480 个文本文件全 LF,70 个二进制不计。 |
|
|
127
|
-
| D5 | ✅ **已完成(Unreleased)** —— 颜色令牌分两层:L1 `--raw-*`(取值)+ L2 语义别名(`var()`) | 做法:`:root` 里 30 个颜色字面量搬进 L1 区块,语义行改成 `var(--raw-x)`;**暗色段只覆盖 L1** —— 语义层是 `var()` 引用会自然跟随 ⇒「暗色段漏写某个语义令牌」在结构上**不可能发生**(这正是原来只能靠人眼/判据③ 兜、而③ 只查"声明了的"、抓不到漏写的那一类)。L1 **不进对外契约**:`ai/tokens.json` / `ai/tokens.tree.json` / `tierA-tokens.css` 全部在导出前解析成字面值。
|
|
128
|
-
|
|
129
|
-
⚠️ **纠正两处此前的错误判断(都实测证伪)**:① 原写"`theme-sync` 只同步语义令牌 ⇒ 原始值层会被漏掉"——**错**,`collect()` 并不按前缀过滤,实测插一条 `--raw-probe` 能正常同步;② 原写"把 `tokens.tree.json` 改成 alias 是契约变更"—— 只要在**导出前 resolve**,实测两种对外产物**与分层前逐字节相同**,零契约变更。
|
|
130
|
-
|
|
131
|
-
🔴 **真正撞到的坑(此前完全没预见到)**:`getComputedStyle` 会解析 `var()`,但 **CSSOM 规则文本不会**。`token-tree-gate` 的 ⑤b 原来读规则文本 ⇒ 分层后读到 `var(--raw-paper)`,与导出的 `#f6f7f8` 永远对不上 ⇒ 探针新增 `resolvedLight` / `resolvedDark`(读 computed),⑤b 改用它。另一个真 bug:`theme-sync` 按正则找 `@media`,**注释里举的例子会先命中** —— 实测让 `[data-theme="dark"]` 段生成成 **0 个令牌**,而 `--check` 还报"一致"(0 == 0)。已改为先 `mask_comments()` 再定位。 | 验收:分层**前后**浏览器解析值逐条比对 —— 107 个原有令牌 **0 处值变化 / 0 处消失**(60 条"基线未覆盖"全部是 30 个新 L1 × 明暗两套);`ai/tokens.json` 与 `ai/tokens.tree.json` **与 HEAD 逐字节相同**;明暗两套视觉回归各 34/34 一致;`layer-tokens.py --check` 五条结构判据(含反向控制)全过 |
|
|
132
|
-
|
|
133
|
-
### 🟢 P2·已完成(0.8.9)
|
|
134
|
-
|
|
135
|
-
| # | 事项 | 结果 |
|
|
136
|
-
|---|---|---|
|
|
137
|
-
| W14 | **「按用到生成」出口** | `05-audit/purge-css.js` + 自检 `purge-css-selftest.js`(已接进 check-all)。实测 analysis-report 页删掉 8,797 B 未命中规则。⚠️ 保留「命中 + 动态 + @规则」是**有意的保守策略**:`:hover`/`:focus` 这类判不了、但交互时必须留着 |
|
|
138
|
-
| W15 | **09-assets 进了构建** | `SRC_DIRS` 原来漏了 `09-assets` ⇒ 四支资产 CSS **从来没进过 dist**。补上后 min/stripped 各 39 支(原 35),`exports` 的 `./assets/*` 也随之改指 dist |
|
|
139
|
-
| W16 | **`visual` 实跑了** | 此前一直 SKIP(缺 Pillow)。实测:**34/34 页观感一致**,RC=0 |
|
|
140
|
-
| W17 | **`09-assets/model-viewer/vendor` 与 `.glb` 不进包** | 与 README 自述「three.js 由主项目负责」对齐。包体 **899.1 → 631.3 kB(−29.8%)** |
|
|
141
|
-
| W18 | **注释占比不再硬编码** | `dist-cost-gate.py` 里两处字面量 `64%` 改为实算,并标注**字节口径**(字符口径会低报 12 个百分点) |
|
|
142
|
-
|
|
143
|
-
### 🟢 P2·已完成(Unreleased —— 尚未发版,见 CHANGELOG `[Unreleased]`)
|
|
144
|
-
|
|
145
|
-
| # | 事项 | 结果 |
|
|
146
|
-
|---|---|---|
|
|
147
|
-
| W19 | **暗色回归终于有了门禁** | `token-resolved-gate.js`:**137** 个令牌 × 4 种明暗组合的**浏览器解析值**(D5 分层后从 107 增至 137,新增的是 L1)。五条判据里最要紧的是 ③「声明即生效」——「声明合法、两条文本一致,但浏览器没让它生效」这一类此前**没有任何门禁能抓**(`--lift-*` 被注释吞掉、裸声明被丢弃,都是这类)。`theme-css` 只探 2 个令牌,恰好是没被吞的那两条。这替代了原来"只能人眼看暗色"的那一步 |
|
|
148
|
-
| W20 | **`selftest-all` 此前只扫 `.py`** | JS 门禁的自检**从来没被这个清单发现过**。改成按扩展名认解释器,且只用**调用点**判定(`argv.indexOf('--selftest')`),不是文档里出现过这个词就算。发现的 26 个自检变成 37 个(36 跑通 + 1 条按理由登记跳过) |
|
|
149
|
-
| W21 | **Benchmark 记录侧的三处假绿** | `--record` 指向不存在的文件只 WARN、RC=0;`runs \|\| []` 让空记录算出"0/0 通过";扁平的 `runs` 让"≥20 次分布"这件事**工具根本表达不了**。现已:记录 v2 + 八条判据 + `--check-records` + 参考记录必须 10/10(管线自洽)。实测还抓到一个真缺陷:给 T03 喂空 `ops` 时 `expect()` 抛 `TypeError` 把整份跑批打断 |
|
|
150
|
-
| W22 | **真机探针与基线表** | 自包含探针页(15 项,4 项行为实测)+ 静态门禁(清单与源码**双向**一致)+ 浏览器门禁(探针在 Chromium 里真跑)。⚠️ 数据仍是 **UNVERIFIED**:机器能做的都做了,剩下的必须有人在手机上按一次 |
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
## 四、执行纪律(本轮血泪)
|
|
154
|
-
|
|
155
|
-
1. **每条门禁都要反向控制** —— 注入坏样本,确认它会红。
|
|
156
|
-
本轮有三道门禁因此暴露自身失效(fixture 写错、假绿、路径解析失败)。
|
|
157
|
-
2. **区分「真 bug」与「看起来像 bug」** —— npm 泄漏那条就是误判,
|
|
158
|
-
先实测再动手,否则会改一个不存在的问题。
|
|
159
|
-
3. **批量改动后立刻做结构校验** —— JS 语法、CSS 花括号、Python `ast.parse`,
|
|
160
|
-
再逐文件与改前对照,确认没破坏原有结构。
|
|
161
|
-
4. **清理项目用脚本不入库** —— 一次性清理脚本用完即删。
|
|
162
|
-
5. **提交前跑门禁,推送后验证远端** —— 且要用陌生人视角 `git clone` 一遍。
|
|
163
|
-
|
|
164
|
-
## 五、当前评级
|
|
165
|
-
|
|
166
|
-
> ⚠️ 这张表以前在"说明"列里写"待做",但其中两项**早已完成**:
|
|
167
|
-
> `api-form-gate` 已经在比对运行时形态、`doc-facts-gate` 已经在跑。
|
|
168
|
-
> 过时的"待做"会让下一个人从头再做一遍 —— 这正是本库反复登记的
|
|
169
|
-
> 「文档说有、实现没有」的反向形态。
|
|
170
|
-
> ⇒ 现在每一行都带**复核命令**,分数不靠记忆。
|
|
171
|
-
|
|
172
|
-
| 维度 | 上一轮 | 现在 | 说明 | 复核 |
|
|
173
|
-
|---|---:|---:|---|---|
|
|
174
|
-
| TypeScript / Runtime 一致性 | 5 | **9** | 编译器双向验证 | 门禁里的 `tsc` |
|
|
175
|
-
| API 契约门禁有效性 | 5 | **8.5** | 运行时形态由 `api-form-gate` 比对:真浏览器探针取形态,再与 `API.md` 双向比(形态 / 成员 / 漏掉的全局) | `python 05-audit/api-form-gate.py` |
|
|
176
|
-
| npm / Package 工程 | 7 | **9** | tarball 实装 + 包内闭包 + 消费者侧 `require`/`import` 真解析 | `release-gate.py` · `pack-closure.js` · `pkg-resolve-gate.py` |
|
|
177
|
-
| WebView 真实性证据 | 4.5 | **4.5(环境边界)** | 7 行真机全部 `VERIFIED`;再扩需要拿不到的 Android 系统浏览器 —— 见 `docs/BACKLOG.md` §4.1 | `python 05-audit/webview-probe-gate.py` |
|
|
178
|
-
| CI / Release | 6 | **6.5(待远端验证)** | `npm ci` 每个 job 都跑、nightly 含 WebKit 矩阵、浅克隆丢 tag 已修(`fetch-depth: 0` + `fetch-tags`)—— 但**修复后还没在远端跑过一次** | 推送后看 Actions 结果,不要本地推断 |
|
|
179
|
-
| 文档产品化 | 6.5 | **8.5** | `doc-facts` 核对组件数 / 门禁数 / stable·beta;`autoinit` 把"哪些组件会自动初始化"写成契约 | `doc-facts-gate.py` · `autoinit-gate.py` |
|
|
180
|
-
|
|
181
|
-
**评级纪律**:表中任何一行,只要能被门禁复核,就以门禁输出为准;
|
|
182
|
-
不能用"我记得改过"当依据。
|