agentcss 0.16.0 → 0.24.0
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/02-primitives/choice/choice.css +31 -2
- package/02-primitives/input/input.css +14 -0
- package/02-primitives/progress/progress.css +56 -0
- package/02-primitives/skeleton/skeleton.css +18 -0
- package/02-primitives/switch/switch.css +61 -2
- package/02-primitives/timeline/README.md +70 -0
- package/02-primitives/timeline/demo.html +161 -0
- package/02-primitives/timeline/timeline.css +152 -0
- package/02-primitives/upload/README.md +142 -0
- package/02-primitives/upload/demo.html +318 -0
- package/02-primitives/upload/upload.css +490 -0
- package/02-primitives/upload/upload.js +676 -0
- package/03-patterns/drawer/drawer.js +30 -5
- package/04-recipes/app-shell/README.md +272 -0
- package/04-recipes/app-shell/app-shell.css +588 -0
- package/04-recipes/app-shell/demo.html +322 -0
- package/04-recipes/app-shell/shell-nav.js +288 -0
- package/04-recipes/table/table.css +22 -0
- package/AGENTS.md +8 -0
- package/API.md +4 -0
- package/CHANGELOG.md +763 -0
- package/CONTRIBUTING.md +22 -1
- package/README.md +17 -5
- package/README.zh-CN.md +16 -6
- package/START-HERE.md +12 -7
- package/ai/START-HERE.md +37 -3
- package/ai/behaviors.json +32 -0
- package/ai/cli.js +183 -25
- package/ai/components.json +185 -7
- package/ai/components.meta.json +13 -0
- package/ai/pitfalls-writing.json +119 -0
- package/ai/pitfalls.json +193 -0
- package/dist/02-primitives/choice/choice.css +1 -1
- package/dist/02-primitives/input/input.css +1 -1
- package/dist/02-primitives/progress/progress.css +1 -1
- package/dist/02-primitives/skeleton/skeleton.css +1 -1
- package/dist/02-primitives/switch/switch.css +1 -1
- package/dist/02-primitives/timeline/timeline.css +1 -0
- package/dist/02-primitives/upload/upload.css +1 -0
- package/dist/02-primitives/upload/upload.js +515 -0
- package/dist/03-patterns/drawer/drawer.js +14 -4
- package/dist/04-recipes/app-shell/app-shell.css +1 -0
- package/dist/04-recipes/app-shell/shell-nav.js +173 -0
- package/dist/04-recipes/table/table.css +1 -1
- package/dist/COSTS.md +12 -10
- package/docs/ACCESSIBILITY.md +20 -7
- package/docs/AI-CONTRACT.md +2 -0
- package/docs/BUDGET.md +179 -0
- package/docs/CONVENTIONS.md +3 -0
- package/docs/STABILITY.md +126 -0
- package/esm/index.mjs +2 -0
- package/esm/upload.mjs +7 -0
- package/index.html +5 -2
- package/llms-full.txt +153 -0
- package/llms.txt +55 -0
- package/package.json +4 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
(function (global) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var flEmit = function (el, name, detail) {
|
|
5
|
+
if (!el || !el.dispatchEvent) return null;
|
|
6
|
+
var type = name.indexOf('fl-') === 0 ? name : 'fl-' + name;
|
|
7
|
+
var ev = null;
|
|
8
|
+
|
|
9
|
+
if (typeof window.CustomEvent === 'function') {
|
|
10
|
+
try {
|
|
11
|
+
ev = new window.CustomEvent(type, {
|
|
12
|
+
detail: detail || null, bubbles: true, cancelable: false
|
|
13
|
+
});
|
|
14
|
+
} catch (e) { ev = null; }
|
|
15
|
+
}
|
|
16
|
+
if (!ev) {
|
|
17
|
+
try {
|
|
18
|
+
ev = document.createEvent('CustomEvent');
|
|
19
|
+
ev.initCustomEvent(type, true, false, detail || null);
|
|
20
|
+
} catch (e2) { return null; }
|
|
21
|
+
}
|
|
22
|
+
el.dispatchEvent(ev);
|
|
23
|
+
return ev;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
var ICON =
|
|
27
|
+
'<svg viewBox="0 0 16 16" width="18" height="18" fill="none" '+
|
|
28
|
+
'stroke="currentColor" stroke-width="1.6" stroke-linecap="round" '+
|
|
29
|
+
'aria-hidden="true" focusable="false"><path d="M2.5 4h11M2.5 8h11M2.5 12h11"/></svg>';
|
|
30
|
+
|
|
31
|
+
function warn(msg) {
|
|
32
|
+
if (global.console && global.console.warn) global.console.warn(msg);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function stripIds(node) {
|
|
36
|
+
if (node.removeAttribute) node.removeAttribute('id');
|
|
37
|
+
var all = node.querySelectorAll ? node.querySelectorAll('[id]') : [];
|
|
38
|
+
for (var i = 0; i < all.length; i++) all[i].removeAttribute('id');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ShellNav(root, opts) {
|
|
42
|
+
if (!root) throw new Error('ShellNav: 缺少根元素');
|
|
43
|
+
var o = this.opts = opts || {};
|
|
44
|
+
var self = this;
|
|
45
|
+
|
|
46
|
+
this.root = root;
|
|
47
|
+
this.btn = null;
|
|
48
|
+
this.handle = null;
|
|
49
|
+
this.disabled = false;
|
|
50
|
+
|
|
51
|
+
var side = root.querySelector('.shell__side');
|
|
52
|
+
var topbar = root.querySelector('.shell__topbar');
|
|
53
|
+
|
|
54
|
+
if (!side || !topbar) {
|
|
55
|
+
this.disabled = true;
|
|
56
|
+
warn('shell-nav: 缺少 ' + (!side ? '.shell__side' : '.shell__topbar') +
|
|
57
|
+
',已跳过该元素(不改别人的布局)。');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!global.Drawer || typeof global.Drawer.open !== 'function') {
|
|
62
|
+
this.disabled = true;
|
|
63
|
+
warn('shell-nav: 需要先加载 03-patterns/drawer/drawer.js。' +
|
|
64
|
+
'本层只做接线,抽屉的 Esc / 焦点陷阱 / 焦点归位 / 锁滚动都由它提供。'+
|
|
65
|
+
'已跳过,不注入菜单按钮。');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.side = side;
|
|
70
|
+
this.topbar = topbar;
|
|
71
|
+
|
|
72
|
+
var title = root.getAttribute('data-shell-nav') || '导航';
|
|
73
|
+
var btn = document.createElement('button');
|
|
74
|
+
btn.type = 'button';
|
|
75
|
+
btn.className = 'shell__menu';
|
|
76
|
+
btn.setAttribute('aria-label', o.label || ('打开' + title));
|
|
77
|
+
btn.setAttribute('aria-haspopup', 'dialog');
|
|
78
|
+
btn.innerHTML = ICON;
|
|
79
|
+
btn.addEventListener('click', function () { self.open(); });
|
|
80
|
+
topbar.insertBefore(btn, topbar.firstChild);
|
|
81
|
+
this.btn = btn;
|
|
82
|
+
|
|
83
|
+
this.onResize = function () {
|
|
84
|
+
if (!self.handle) return;
|
|
85
|
+
|
|
86
|
+
if (self.btn && self.btn.offsetParent === null) self.close();
|
|
87
|
+
};
|
|
88
|
+
global.addEventListener('resize', this.onResize);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
ShellNav.prototype = {
|
|
92
|
+
constructor: ShellNav,
|
|
93
|
+
|
|
94
|
+
open: function () {
|
|
95
|
+
if (this.disabled || this.handle) return this.handle;
|
|
96
|
+
var self = this;
|
|
97
|
+
var title = this.root.getAttribute('data-shell-nav') || '导航';
|
|
98
|
+
|
|
99
|
+
var content = this.side.cloneNode(true);
|
|
100
|
+
stripIds(content);
|
|
101
|
+
|
|
102
|
+
content.className += ' shell__side--drawer';
|
|
103
|
+
|
|
104
|
+
var handle = this.handle = global.Drawer.open({
|
|
105
|
+
place: this.opts.place || 'left',
|
|
106
|
+
title: title,
|
|
107
|
+
content: content,
|
|
108
|
+
|
|
109
|
+
initialFocus: '[aria-current="page"]',
|
|
110
|
+
onClose: function () {
|
|
111
|
+
self.handle = null;
|
|
112
|
+
flEmit(self.root, 'fl-shell-nav', { open: false });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
var el = handle.el;
|
|
117
|
+
el.addEventListener('click', function (e) {
|
|
118
|
+
var n = e.target;
|
|
119
|
+
while (n && n !== el) {
|
|
120
|
+
if (n.tagName === 'A') { self.close(); return; }
|
|
121
|
+
n = n.parentNode;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
flEmit(this.root, 'fl-shell-nav', { open: true });
|
|
126
|
+
return handle;
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
close: function () {
|
|
130
|
+
var h = this.handle;
|
|
131
|
+
if (!h) return false;
|
|
132
|
+
|
|
133
|
+
this.handle = null;
|
|
134
|
+
h.close();
|
|
135
|
+
return true;
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
destroy: function () {
|
|
139
|
+
this.close();
|
|
140
|
+
if (this.onResize) {
|
|
141
|
+
global.removeEventListener('resize', this.onResize);
|
|
142
|
+
this.onResize = null;
|
|
143
|
+
}
|
|
144
|
+
if (this.btn && this.btn.parentNode) this.btn.parentNode.removeChild(this.btn);
|
|
145
|
+
this.btn = null;
|
|
146
|
+
if (this.root && this.root.shellNavInstance === this) this.root.shellNavInstance = null;
|
|
147
|
+
this.disabled = true;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
global.ShellNav = ShellNav;
|
|
152
|
+
|
|
153
|
+
function init() {
|
|
154
|
+
var nodes = document.querySelectorAll('[data-shell-nav]');
|
|
155
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
156
|
+
|
|
157
|
+
if (nodes[i].shellNavInstance) continue;
|
|
158
|
+
try {
|
|
159
|
+
nodes[i].shellNavInstance = new ShellNav(nodes[i]);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
|
|
162
|
+
warn('shell-nav: 初始化失败,已跳过该元素 —— ' + (e && e.message));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (typeof document === 'undefined') return;
|
|
168
|
+
if (document.readyState === 'loading') {
|
|
169
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
170
|
+
} else {
|
|
171
|
+
init();
|
|
172
|
+
}
|
|
173
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.table {position:relative;overflow:auto;max-height:var(--table-max-h, 70vh);background:var(--surface);border:1px solid var(--border-decor);border-radius:var(--r-md);}.table table {width:100%;border-collapse:separate;border-spacing:0;font-size:var(--fs-sm);}.table thead th {position:sticky;top:0;z-index:var(--z-sticky);background:var(--surface-raised);color:var(--text-secondary);font-weight:600;text-align:start;white-space:nowrap;padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--border-decor);}.table__sort {display:inline-flex;align-items:center;gap:var(--sp-1);min-height:32px;padding:0 var(--sp-1);margin:0 calc(-1 * var(--sp-1));background:none;border:0;border-radius:var(--r-xs, 3px);color:inherit;font:inherit;cursor:pointer;}.table__sort:focus-visible {outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset);}.table__sort::after {content:"";inline-size:.85em;block-size:.85em;flex:none;background-color:currentColor;opacity:.45;-webkit-mask:var(--sort-glyph) center / contain no-repeat;mask:var(--sort-glyph) center / contain no-repeat;--sort-glyph:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.4 5.2 4.6h1.7v2.6h2.2V4.6h1.7z'/%3E%3Cpath d='M8 14.6 10.8 11.4h-1.7V8.8H6.9v2.6H5.2z'/%3E%3C/svg%3E");}.table th[aria-sort="ascending"] .table__sort::after, .table__sort[aria-sort="ascending"]::after {--sort-glyph:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6 4.6 6h2.1v8h2.6V6h2.1z'/%3E%3C/svg%3E");opacity:1;}.table th[aria-sort="descending"] .table__sort::after, .table__sort[aria-sort="descending"]::after {--sort-glyph:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 14.4 11.4 10h-2.1V2H6.7v8H4.6z'/%3E%3C/svg%3E");opacity:1;}.table tbody td, .table tbody th {padding:var(--sp-2) var(--sp-3);text-align:start;border-bottom:1px solid var(--border-decor);vertical-align:top;}.table tbody tr:last-child td {border-bottom:0;}@media (hover:hover) and (pointer:fine) {.table tbody tr:hover {background:var(--surface-sunken);}}.table .num {text-align:end;font-variant-numeric:tabular-nums;font-family:var(--mono);white-space:nowrap;}.table .num--pos {color:var(--success);}.table .num--neg {color:var(--danger);}.table .num--pos::before {content:"+";}.table .num--neg::before {content:"−";}.table__empty, .table__error, .table__loading {padding:var(--sp-7) var(--sp-4);text-align:center;color:var(--text-secondary);font-size:var(--fs-sm);}.table__empty {background:var(--surface-sunken);}.table__error {background:var(--surface-sunken);color:var(--danger);}.table__loading {color:var(--text-tertiary);}.table__bar {height:8px;margin:var(--sp-2) auto;max-width:220px;background:var(--surface-sunken);border-radius:999px;overflow:hidden;}@media (prefers-reduced-motion:no-preference) {.table__bar::after {content:"";display:block;height:100%;width:40%;background:var(--accent-soft);border-radius:999px;animation:table-shimmer 1.2s var(--ease-in-out, ease-in-out) infinite;}@keyframes table-shimmer {0% {transform:translateX(-100%);}100% {transform:translateX(250%);}}}.table__empty .btn, .table__error .btn {margin-top:var(--sp-3);}.table--compact {font-size:var(--fs-xs);}.table--compact thead th, .table--compact tbody td {padding:var(--sp-1) var(--sp-2);}.table__cap {caption-side:top;text-align:start;padding:var(--sp-2) var(--sp-3);font-size:var(--fs-xs);color:var(--text-tertiary);}.table__foot {margin:var(--sp-2) 0 0;padding:0 var(--sp-1);font-size:var(--fs-xs);color:var(--text-tertiary);line-height:1.6;}.table__foot ol {margin:0;padding-inline-start:var(--sp-4);}.table thead tr + tr th {top:2.4em;font-weight:500;font-size:var(--fs-xs);color:var(--text-tertiary);border-bottom:1px solid var(--border-decor);}.table thead th[colspan] {text-align:center;border-inline-start:1px solid var(--border-decor);}.table thead th[colspan]:first-child {border-inline-start:0;}.table--financial thead th {white-space:nowrap;}.table--financial tbody th {white-space:nowrap;}.table--financial thead th:not(:first-child) {text-align:end;}.table__unit {font-weight:400;font-size:inherit;color:var(--text-secondary);margin-inline-start:0.35em;white-space:nowrap;}.table .num--na, .table .cell--na {color:var(--text-tertiary);text-align:center;}.table--academic {border:0;border-radius:0;background:transparent;max-height:none;}.table--academic table {background:transparent;}.table--academic thead th {background:transparent;color:var(--text-primary);font-weight:600;border-top:2px solid var(--border-decor-str);border-bottom:1px solid var(--border-decor);position:static;}.table--academic thead tr + tr th {font-weight:400;border-top:0;border-bottom:1px solid var(--border-decor);}.table--academic tbody td, .table--academic tbody th {border-bottom:0;}.table--academic tbody tr:last-child td, .table--academic tbody tr:last-child th {border-bottom:1px solid var(--border-decor);}@media (hover:hover) and (pointer:fine) {.table--academic tbody tr:hover {background:transparent;}}.table--stats .stat__se {color:var(--text-tertiary);font-size:var(--fs-xs);}.table--stats .stat__p {font-family:var(--mono);font-variant-numeric:tabular-nums;}.table--stats .stat__p--sig {font-weight:700;color:var(--text-primary);}.table--stats .stat__ci {font-family:var(--mono);font-variant-numeric:tabular-nums;white-space:nowrap;}@media print {.table {max-height:none;overflow:visible;}.table thead th {position:static;}.table--academic thead th {border-top:2px solid var(--text-primary);}}
|
|
1
|
+
.table {position:relative;overflow:auto;max-height:var(--table-max-h, 70vh);background:var(--surface);border:1px solid var(--border-decor);border-radius:var(--r-md);}.table table {width:100%;border-collapse:separate;border-spacing:0;font-size:var(--fs-sm);}.table thead th {position:sticky;top:0;z-index:var(--z-sticky);background:var(--surface-raised);color:var(--text-secondary);font-weight:600;text-align:start;white-space:nowrap;padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--border-decor);}.table__sort {display:inline-flex;align-items:center;gap:var(--sp-1);min-height:32px;padding:0 var(--sp-1);margin:0 calc(-1 * var(--sp-1));background:none;border:0;border-radius:var(--r-xs, 3px);color:inherit;font:inherit;cursor:pointer;}.table__sort:focus-visible {outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset);}.table__sort::after {content:"";inline-size:.85em;block-size:.85em;flex:none;background-color:currentColor;opacity:.45;-webkit-mask:var(--sort-glyph) center / contain no-repeat;mask:var(--sort-glyph) center / contain no-repeat;--sort-glyph:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.4 5.2 4.6h1.7v2.6h2.2V4.6h1.7z'/%3E%3Cpath d='M8 14.6 10.8 11.4h-1.7V8.8H6.9v2.6H5.2z'/%3E%3C/svg%3E");}@media (forced-colors:active) {.table__sort::after {background-color:CanvasText;opacity:1;}}.table th[aria-sort="ascending"] .table__sort::after, .table__sort[aria-sort="ascending"]::after {--sort-glyph:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6 4.6 6h2.1v8h2.6V6h2.1z'/%3E%3C/svg%3E");opacity:1;}.table th[aria-sort="descending"] .table__sort::after, .table__sort[aria-sort="descending"]::after {--sort-glyph:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 14.4 11.4 10h-2.1V2H6.7v8H4.6z'/%3E%3C/svg%3E");opacity:1;}.table tbody td, .table tbody th {padding:var(--sp-2) var(--sp-3);text-align:start;border-bottom:1px solid var(--border-decor);vertical-align:top;}.table tbody tr:last-child td {border-bottom:0;}@media (hover:hover) and (pointer:fine) {.table tbody tr:hover {background:var(--surface-sunken);}}.table .num {text-align:end;font-variant-numeric:tabular-nums;font-family:var(--mono);white-space:nowrap;}.table .num--pos {color:var(--success);}.table .num--neg {color:var(--danger);}.table .num--pos::before {content:"+";}.table .num--neg::before {content:"−";}.table__empty, .table__error, .table__loading {padding:var(--sp-7) var(--sp-4);text-align:center;color:var(--text-secondary);font-size:var(--fs-sm);}.table__empty {background:var(--surface-sunken);}.table__error {background:var(--surface-sunken);color:var(--danger);}.table__loading {color:var(--text-tertiary);}.table__bar {height:8px;margin:var(--sp-2) auto;max-width:220px;background:var(--surface-sunken);border-radius:999px;overflow:hidden;}@media (prefers-reduced-motion:no-preference) {.table__bar::after {content:"";display:block;height:100%;width:40%;background:var(--accent-soft);border-radius:999px;animation:table-shimmer 1.2s var(--ease-in-out, ease-in-out) infinite;}@keyframes table-shimmer {0% {transform:translateX(-100%);}100% {transform:translateX(250%);}}}.table__empty .btn, .table__error .btn {margin-top:var(--sp-3);}.table--compact {font-size:var(--fs-xs);}.table--compact thead th, .table--compact tbody td {padding:var(--sp-1) var(--sp-2);}.table__cap {caption-side:top;text-align:start;padding:var(--sp-2) var(--sp-3);font-size:var(--fs-xs);color:var(--text-tertiary);}.table__foot {margin:var(--sp-2) 0 0;padding:0 var(--sp-1);font-size:var(--fs-xs);color:var(--text-tertiary);line-height:1.6;}.table__foot ol {margin:0;padding-inline-start:var(--sp-4);}.table thead tr + tr th {top:2.4em;font-weight:500;font-size:var(--fs-xs);color:var(--text-tertiary);border-bottom:1px solid var(--border-decor);}.table thead th[colspan] {text-align:center;border-inline-start:1px solid var(--border-decor);}.table thead th[colspan]:first-child {border-inline-start:0;}.table--financial thead th {white-space:nowrap;}.table--financial tbody th {white-space:nowrap;}.table--financial thead th:not(:first-child) {text-align:end;}.table__unit {font-weight:400;font-size:inherit;color:var(--text-secondary);margin-inline-start:0.35em;white-space:nowrap;}.table .num--na, .table .cell--na {color:var(--text-tertiary);text-align:center;}.table--academic {border:0;border-radius:0;background:transparent;max-height:none;}.table--academic table {background:transparent;}.table--academic thead th {background:transparent;color:var(--text-primary);font-weight:600;border-top:2px solid var(--border-decor-str);border-bottom:1px solid var(--border-decor);position:static;}.table--academic thead tr + tr th {font-weight:400;border-top:0;border-bottom:1px solid var(--border-decor);}.table--academic tbody td, .table--academic tbody th {border-bottom:0;}.table--academic tbody tr:last-child td, .table--academic tbody tr:last-child th {border-bottom:1px solid var(--border-decor);}@media (hover:hover) and (pointer:fine) {.table--academic tbody tr:hover {background:transparent;}}.table--stats .stat__se {color:var(--text-tertiary);font-size:var(--fs-xs);}.table--stats .stat__p {font-family:var(--mono);font-variant-numeric:tabular-nums;}.table--stats .stat__p--sig {font-weight:700;color:var(--text-primary);}.table--stats .stat__ci {font-family:var(--mono);font-variant-numeric:tabular-nums;white-space:nowrap;}@media print {.table {max-height:none;overflow:visible;}.table thead th {position:static;}.table--academic thead th {border-top:2px solid var(--text-primary);}}
|
package/dist/COSTS.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
实测两笔账,一笔不省:
|
|
11
11
|
|
|
12
12
|
- `tokens` 源码版 gzip **20.4 KB** → min 版 **2.7 KB**(省 87%)——
|
|
13
|
-
源码多的那部分**全是注释**(全库 CSS 有
|
|
13
|
+
源码多的那部分**全是注释**(全库 CSS 有 68% 是注释,字节口径),而注释是给维护者的,不是给浏览器的。
|
|
14
14
|
- tokens 的 min 版 2.7 KB **比任何一个组件自己都贵** ⇒
|
|
15
15
|
"只用一个组件"的真实起步价就是它。不公开这张表,等于让人盲买。
|
|
16
16
|
|
|
@@ -21,21 +21,23 @@
|
|
|
21
21
|
| primitive | `badge` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 23.1 KB | −19.9 KB |
|
|
22
22
|
| primitive | `button` | 2.7 KB | 1.0 KB | — | **3.7 KB** | 26.8 KB | −23.1 KB |
|
|
23
23
|
| primitive | `card` | 2.7 KB | 0.9 KB | — | **3.7 KB** | 24.5 KB | −20.8 KB |
|
|
24
|
-
| primitive | `choice` | 2.7 KB | 1.1 KB | — | **3.8 KB** |
|
|
24
|
+
| primitive | `choice` | 2.7 KB | 1.1 KB | — | **3.8 KB** | 26.3 KB | −22.5 KB |
|
|
25
25
|
| primitive | `combobox` | 2.7 KB | 1.0 KB | 5.4 KB | **9.1 KB** | 29.9 KB | −20.9 KB |
|
|
26
26
|
| primitive | `date-range` | 2.7 KB | 0.8 KB | 2.5 KB | **6.1 KB** | 26.9 KB | −20.8 KB |
|
|
27
27
|
| primitive | `delta` | 2.7 KB | 0.3 KB | — | **3.0 KB** | 23.1 KB | −20.2 KB |
|
|
28
|
-
| primitive | `input` | 2.7 KB | 1.0 KB | — | **3.
|
|
28
|
+
| primitive | `input` | 2.7 KB | 1.0 KB | — | **3.8 KB** | 24.3 KB | −20.6 KB |
|
|
29
29
|
| primitive | `metric` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 23.6 KB | −20.4 KB |
|
|
30
30
|
| primitive | `popover` | 2.7 KB | 0.8 KB | 2.2 KB | **5.7 KB** | 27.7 KB | −22.0 KB |
|
|
31
|
-
| primitive | `progress` | 2.7 KB | 0.
|
|
31
|
+
| primitive | `progress` | 2.7 KB | 0.8 KB | — | **3.5 KB** | 24.7 KB | −21.2 KB |
|
|
32
32
|
| primitive | `select` | 2.7 KB | 0.8 KB | 3.5 KB | **7.1 KB** | 30.0 KB | −22.9 KB |
|
|
33
33
|
| primitive | `separator` | 2.7 KB | 0.2 KB | — | **3.0 KB** | 21.7 KB | −18.7 KB |
|
|
34
|
-
| primitive | `skeleton` | 2.7 KB | 0.5 KB | — | **3.2 KB** |
|
|
35
|
-
| primitive | `switch` | 2.7 KB | 0.8 KB | — | **3.5 KB** |
|
|
34
|
+
| primitive | `skeleton` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 22.3 KB | −19.1 KB |
|
|
35
|
+
| primitive | `switch` | 2.7 KB | 0.8 KB | — | **3.5 KB** | 28.7 KB | −25.2 KB |
|
|
36
|
+
| primitive | `timeline` | 2.7 KB | 0.5 KB | — | **3.2 KB** | 23.7 KB | −20.4 KB |
|
|
37
|
+
| primitive | `upload` | 2.7 KB | 1.4 KB | 4.2 KB | **8.3 KB** | 37.8 KB | −29.5 KB |
|
|
36
38
|
| pattern | `accordion` | 2.7 KB | 1.3 KB | 1.8 KB | **5.8 KB** | 34.9 KB | −29.1 KB |
|
|
37
39
|
| pattern | `content` | 2.7 KB | 1.0 KB | — | **3.7 KB** | 25.0 KB | −21.2 KB |
|
|
38
|
-
| pattern | `drawer` | 2.7 KB | 0.8 KB | 2.
|
|
40
|
+
| pattern | `drawer` | 2.7 KB | 0.8 KB | 2.5 KB | **6.0 KB** | 27.5 KB | −21.5 KB |
|
|
39
41
|
| pattern | `dropdown` | 2.7 KB | 0.8 KB | 3.4 KB | **6.9 KB** | 29.8 KB | −22.9 KB |
|
|
40
42
|
| pattern | `form-validation` | 2.7 KB | 0.8 KB | — | **3.5 KB** | 24.9 KB | −21.4 KB |
|
|
41
43
|
| pattern | `list` | 2.7 KB | 0.6 KB | — | **3.3 KB** | 22.8 KB | −19.5 KB |
|
|
@@ -50,9 +52,9 @@
|
|
|
50
52
|
|
|
51
53
|
## 三、全库
|
|
52
54
|
|
|
53
|
-
- 全部组件都用上(min,含 tokens 一份):**
|
|
54
|
-
- 源码版(带注释)全库 gzip:
|
|
55
|
+
- 全部组件都用上(min,含 tokens 一份):**69.0 KB**
|
|
56
|
+
- 源码版(带注释)全库 gzip:348.5 KB
|
|
55
57
|
|
|
56
|
-
> ⚠️ 源码版那个数字**不是**要付的钱:它包含我们的注释(全库 CSS 有
|
|
58
|
+
> ⚠️ 源码版那个数字**不是**要付的钱:它包含我们的注释(全库 CSS 有 68% 是注释,字节口径)。
|
|
57
59
|
> 它的用途只有一个 —— 让人看清"dist 到底省了多少"。
|
|
58
60
|
|
package/docs/ACCESSIBILITY.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
| 项目 | 值 |
|
|
7
7
|
|---|---|
|
|
8
|
-
| 声明适用版本 | agentcss `0.
|
|
9
|
-
| 声明日期 | 2026-10-
|
|
8
|
+
| 声明适用版本 | agentcss `0.24.0` |
|
|
9
|
+
| 声明日期 | 2026-10-31 |
|
|
10
10
|
| 复核节奏 | 每次发版前重跑(`npm run gate` 内含全部证据) |
|
|
11
11
|
| 范围 | 本库交付的 CSS / JS、全部 `demo.html`(组件页 + 图表页 + 组合页)、以及文档站生成的页面 |
|
|
12
12
|
| 不在范围 | 使用者拿这些组件拼出来的业务页面(那是你的无障碍责任,不是库的) |
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
| 3.3.2 | 标签或说明 | ✅ | 必填项有可见标记 + `required` |
|
|
68
68
|
| 3.3.7 | 冗余输入(2.2 A) | — | 由业务表单决定,库不承载 |
|
|
69
69
|
| 3.3.8 | 可访问的认证(2.2 AA) | — | 库不含登录流程 |
|
|
70
|
-
| 4.1.2 | 名称、角色、值 | ✅ | `a11y-ax.js`:**全部
|
|
70
|
+
| 4.1.2 | 名称、角色、值 | ✅ | `a11y-ax.js`:**全部 43 个 demo 页**的可访问性树逐节点判,每个可交互控件有非空名称;朗读序列逐页存快照防回归。图标态(`.shell--rail`)的名字来自**视觉隐藏但仍可访问**的 `.shell__link-label`,不是 `aria-label` —— `app-shell-check` F12 / G4 两头守(改成 `display:none` 名字会消失) |
|
|
71
71
|
| 4.1.3 | 状态消息 | ◐ | toast / 表单错误用 `role="status"` / `alert`;**未**在真实读屏里验证播报时机 |
|
|
72
72
|
|
|
73
73
|
**统计:✅ 27 条 · ◐ 7 条 · ✗ 0 条 · — 12 条。**
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
| 手段 | 覆盖 | 最近结果 |
|
|
82
82
|
|---|---|---|
|
|
83
|
-
| `axe-core`(`a11y-scan.js`) |
|
|
83
|
+
| `axe-core`(`a11y-scan.js`) | 41 页(39 组件 demo + 2 个非组件页) | **0 类违规**,991 条规则通过 |
|
|
84
84
|
| 反向控制(注入 5 类典型违规) | 证明上一条不是"没查" | 5 类全部抓到 |
|
|
85
85
|
| `heading-outline.js`(人工工具) | **全部 HTML:96 页 / 887 个标题** | 0 处跳级 · 每页恰好 1 个 `h1` · DOM 标题数 == 无障碍树 heading 数 |
|
|
86
86
|
| `a11y-ax.js` 可访问性树 | **全部 `demo.html`(现 40 个)**;清单与磁盘不一致即红 | 0 个无名控件;40 份朗读序列快照在 `10-review/a11y-tree/` |
|
|
@@ -121,10 +121,23 @@
|
|
|
121
121
|
这是与 Carbon / Spectrum 那类一致性报告差距最大的一处。
|
|
122
122
|
2. **没有 VPAT / ACR 正式报告**。采购流程要的那种文档本库没有出;
|
|
123
123
|
上面那张 SC 表可以当它的替代品,但它不是法律意义上的 ACR。
|
|
124
|
-
3. **强制色板(forced-colors
|
|
124
|
+
3. **强制色板(forced-colors)已逐组件验到"信息不丢",但没上过真机**:
|
|
125
|
+
2026-10-31 用 CDP 的 `Emulation.setEmulatedMedia` 把 `forced-colors` 打开,
|
|
126
|
+
扫了契约里全部有 demo 的组件页 + 7 个 fixture,修掉 7 类真实缺陷(进度条整块空白、开关滑块消失、
|
|
127
|
+
排序箭头与错误图标变白、骨架屏伪装成"加载完"、单选圆点消失、
|
|
128
|
+
侧栏"当前项"变成五项全都带竖线),
|
|
129
|
+
并由 `05-audit/forced-colors-check.js` 守着(含"剥掉修复必须变红"的反向控制)。
|
|
130
|
+
第 7 类(0.22.1)的教训是**透明边框不是免费预留**:`border-inline-start:
|
|
131
|
+
3px solid transparent` 在强制色里会被解析成实线(链接上是 LinkText,当前项是
|
|
132
|
+
CanvasText),于是每一项都长出一条竖线,当前项只剩色相差 —— 现在改用
|
|
133
|
+
「字重 + 系统色圆点」两条非颜色线索,并且圆点用 `content: ""` 画,
|
|
134
|
+
避免 `content: "•"` 把可访问名污染成「圆点 总览」。
|
|
135
|
+
**仍然缺的是**:真实 Windows 高对比度主题下的截图与人眼确认 ——
|
|
136
|
+
模拟色板与真机不是同一套(`GrayText` 在模拟里是深红,真机是灰),
|
|
137
|
+
所以本文档所有强制色结论都只到"语义可辨"这一层,不到"配色好看"。
|
|
125
138
|
4. **RTL 已进渲染判据与视觉基线,但没进真机**:
|
|
126
|
-
`rtl-render.js`
|
|
127
|
-
方向性字形镜像),`shot-baseline.py --dir rtl`
|
|
139
|
+
`rtl-render.js` 判全部 demo 页(RTL 不横滚 / 阿·希文字膨胀 1.3× 不破相 /
|
|
140
|
+
方向性字形镜像),`shot-baseline.py --dir rtl` 另有整套 RTL 视觉基线
|
|
128
141
|
(覆盖"镜像错了但不溢出不改字形"那一类)。
|
|
129
142
|
**仍然缺的是**:母语 RTL 使用者的实际阅读体验、iOS/Android 真机 RTL 截图,
|
|
130
143
|
以及「页面加载后再切 `dir`」时组件内部状态是否需要重算。
|
package/docs/AI-CONTRACT.md
CHANGED
|
@@ -176,6 +176,8 @@ h1 { font-size: 31px } /* 改一个,14 页全变 */
|
|
|
176
176
|
- `ai/validate.js` + `node ai/cli.js check <文件>` —— 零依赖校验
|
|
177
177
|
- `ai/profiles.json` —— 三档严格程度;`strict` 只允许 `stable` 组件
|
|
178
178
|
- `ai/capabilities.json` —— 按**能力**协商可用操作,不按模型名分支
|
|
179
|
+
- `ai/pitfalls.json`(组件 / 版式 / 交付)+ `ai/pitfalls-writing.json`(写文案、做演示、发页面)
|
|
180
|
+
—— 已知翻车点;查:`node ai/cli.js pitfalls --json [--component=<id>] [--scope=writing]`
|
|
179
181
|
- `ai/behaviors.json` —— **行为覆盖矩阵**:哪些组件必须有哪种微行为
|
|
180
182
|
(状态:`core` 已收编 / `manual` 手写未收编 / `gap` 应做未做 / `n/a` 不适用)
|
|
181
183
|
⇒ **不登记就红**,不许靠省略跳过;`manual` 与 `gap` 的数量有棘轮,只能降不能升
|
package/docs/BUDGET.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# 体积预算
|
|
2
|
+
|
|
3
|
+
> **一句话**:全库生效 CSS 的天花板是 **172 KB**,冻结。常规新增组件(每个 1~4 KB)
|
|
4
|
+
> **不需要动任何数字**;吃光储备要给一次带理由的账,最多三次;之后只能删。
|
|
5
|
+
> 数据在 `05-audit/budget.json`,判定在 `05-audit/perf-gate.py`,
|
|
6
|
+
> 判据自己的判据在 `05-audit/budget-selftest.py`。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1 · 为什么要有这份文件
|
|
11
|
+
|
|
12
|
+
因为**旧的治理方式已经坏过一次,而且坏得很安静**。
|
|
13
|
+
|
|
14
|
+
`perf-gate.py` 里原本有一个常量 `MAX_ALL_CSS`。它在半年里被改过六次:
|
|
15
|
+
|
|
16
|
+
| 版本 | 从 | 到 | 理由 |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| 0.10 | 120 KB | 124 KB | 容器级主题要在每个作用域重声明 L2 别名(规定动作) |
|
|
19
|
+
| 0.10 | 124 KB | 125 KB | `page.css` 收了 `.cmp` 对错对照家族 |
|
|
20
|
+
| 0.12 | 125 KB | 132 KB | `delta` + `metric` + `market.css` |
|
|
21
|
+
| 0.18 | 132 KB | 139 KB | `upload` |
|
|
22
|
+
| 0.18 | 139 KB | 142 KB | `app-shell` |
|
|
23
|
+
| 0.19 | 142 KB | 143 KB | `shell-nav` 接线 |
|
|
24
|
+
|
|
25
|
+
每一次都写了理由,每一次理由都成立。**问题不在任何一次改动,而在结构**:
|
|
26
|
+
|
|
27
|
+
> 被约束的人手里握着那个数字。
|
|
28
|
+
|
|
29
|
+
于是"加组件"和"改上限"变成了同一个动作的两步,而下一次必然重演。
|
|
30
|
+
更加隐蔽的是:上限一旦抬到"随手加什么都装得下",这道预算就**再也不会失败**了 ——
|
|
31
|
+
它还在跑,还在打 `OK`,但已经不再约束任何东西。
|
|
32
|
+
|
|
33
|
+
所以 0.22.2 换的不是数字,是**谁在什么时候能改它**。
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2 · 五层,各拦一种不同的失效
|
|
38
|
+
|
|
39
|
+
| 层 | 判据 | 拦的是什么 | 触发时怎么办 |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| L1 | 单文件 ≤ 24 KB | 一个文件变成怪物 | 拆文件 |
|
|
42
|
+
| L2 | 单组件 ≤ 10 KB | 一个组件变成子框架 | 拆组件 / 承认它是框架并移出 `04-recipes` |
|
|
43
|
+
| L3 | 总量 ≤ 授予 + 储备(10 KB) | 常规增长被卡住 | **不用管**,储备就是给这个的 |
|
|
44
|
+
| L4 | 总量 ≤ 天花板(172 KB) | 产品承诺被越过 | 删,或走 ADR |
|
|
45
|
+
| L5 | 总量 ≥ 授予 − 死区(2 KB) | 降下来之后没人收线 | `--ratchet`(安全,无需理由) |
|
|
46
|
+
|
|
47
|
+
L3 和 L5 是一对:**L3 让正常更新免费,L5 让免费额度不会自己长大**。
|
|
48
|
+
只有这两条同时在场,"不用动数字"才不等于"没有预算"。
|
|
49
|
+
|
|
50
|
+
L1 / L2 管形状,L3 / L4 / L5 管总量 —— 它们不互相替代。
|
|
51
|
+
一个 20 KB 的 `report.css` 不会因为总量还有余量就变得合理。
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 3 · 172 KB 是怎么来的
|
|
56
|
+
|
|
57
|
+
不是审美,是三道约束夹出来的:
|
|
58
|
+
|
|
59
|
+
**① 路线图要装得下。** 还剩 8~12 个候选组件,实测每个组件的生效 CSS 中位数
|
|
60
|
+
**2.4 KB**(`delta` 1.5 / `metric` 1.5 / `app-shell` 2.4 / `shell-nav` 0.9 / `upload` 4.2)。
|
|
61
|
+
当前 144.45 KB(2026-10-31 实测)⇒ 172 还差 **28.2 KB ≈ 11 个中等组件**。
|
|
62
|
+
**这个数就是按"把剩下的组件做完也不用再开会"定的。**
|
|
63
|
+
|
|
64
|
+
**② 不能低于 160 KB。** 低于它,做完路线图就会撞线 ——
|
|
65
|
+
那就退回到"每加两个组件改一次数字",等于没改。
|
|
66
|
+
|
|
67
|
+
**③ 传输成本要站得住。** 46 个文件合并后 gzip 实测 **23.6 KB**
|
|
68
|
+
(生效 144.45 KB,压缩比 6.13:1)。172 KB 对应约 **28 KB gzip**,
|
|
69
|
+
仍在一套基础样式表该有的量级里;再往上就是"又一个 Bootstrap",
|
|
70
|
+
与本库"小而全、可整包引入"的定位冲突。
|
|
71
|
+
|
|
72
|
+
⚠️ 量的是**剥掉注释后的 UTF-8 字节**(浏览器不解析注释),
|
|
73
|
+
且经 `_bytes.read_norm` 归一化(行尾不影响结果)。
|
|
74
|
+
用磁盘体积判会严重高估:全库磁盘体积里约七成是注释,真正生效的不到一半。
|
|
75
|
+
口径与 `doc-facts-gate.py` 的 `real_css_size()` 一致。
|
|
76
|
+
(这里**不写绝对值** —— 上一版写过「467.2 KB 里 318.4 KB」,
|
|
77
|
+
加一个组件就漂,而漂了的数字没有任何门禁会叫。要具体数,看 `dist/COSTS.md`,
|
|
78
|
+
那份是 `dist-cost-gate.py` 每次现算并当场比对的。)
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 4 · 三种动作,三种手续
|
|
83
|
+
|
|
84
|
+
### 4.1 加组件(最常见)—— 零手续
|
|
85
|
+
|
|
86
|
+
只要总量还在 `授予 + 储备` 以内,门禁直接放行。**不用改 `budget.json`,不用改代码。**
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python3 05-audit/perf-gate.py
|
|
90
|
+
# OK 合计 144.4 KB / 152.7 KB | 天花板还剩 27.6 KB ≈ 11 个中等组件
|
|
91
|
+
# 本轮已用储备 1.8 KB / 10.0 KB;棘轮死区 2.0 KB
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4.2 吃光储备 —— 一次带理由的账
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
python3 05-audit/perf-gate.py --grant --reason "为什么这 3.1 KB 是必要的"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`--reason` 至少 12 个字,会被写进 `budget.json` 的 `grants[]`(含日期与当次体积)。
|
|
101
|
+
它会出现在 `git diff` 里 —— **这就是目的**:让"我又多要了 3 KB"这件事
|
|
102
|
+
在代码评审里被看见,而不是消失在一次没有 diff 的常量修改里。
|
|
103
|
+
|
|
104
|
+
**最多三次。** 储备 × 3 ≈ 天花板余量,所以三次之后天然撞天花板 ——
|
|
105
|
+
那时候唯一的路是删。
|
|
106
|
+
|
|
107
|
+
### 4.3 体积降了 —— 收线
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python3 05-audit/perf-gate.py --ratchet
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
删掉 6 KB 死代码之后如果不收线,储备会从 10 KB 变成 16 KB,**预算悄悄放松**。
|
|
114
|
+
所以门禁在反方向也判:比授予低 2 KB 以上就要求收线。2 KB 是死区,
|
|
115
|
+
让"合并一条规则省 300 B"这种噪声不触发。
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 5 · 什么时候才允许动天花板
|
|
120
|
+
|
|
121
|
+
**只有一种情况**:储备用尽(三次授予之后仍然不够),并且能同时给出这三样:
|
|
122
|
+
|
|
123
|
+
1. 一段实测:新增的 X KB 具体是什么规则、为什么无法合并或删除;
|
|
124
|
+
2. 一次代价换算:新的天花板对应多少 gzip(`perf-gate.py` 每次都会打当前总量);
|
|
125
|
+
3. 一段 ADR,写进本文件下面的表格 —— 不是写进 `perf-gate.py` 的注释。
|
|
126
|
+
|
|
127
|
+
| 日期 | 从 | 到 | 谁批准 | 换来的是什么 | 代价(gzip) |
|
|
128
|
+
|---|---|---|---|---|---|
|
|
129
|
+
| 2026-10-31 | — | 172 KB | 初始冻结 | 路线图剩余 8~12 个组件的空间 | ≈28 KB |
|
|
130
|
+
|
|
131
|
+
**不允许**的理由(历史上出现过,现在一律拒绝):
|
|
132
|
+
|
|
133
|
+
- "这次是真实增长" —— 每次都是真实增长,这不是理由,是现象;
|
|
134
|
+
- "拆起来麻烦" —— L2 的存在就是为了让"麻烦"有一个具体的数字;
|
|
135
|
+
- "先抬上去,回头再删" —— 回头不会来。
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 6 · 这份预算不管什么
|
|
140
|
+
|
|
141
|
+
- **不管 gzip 总量**。压缩比会随内容变化(目前 6.1:1),把它做成硬门禁会得到
|
|
142
|
+
一条"复制粘贴反而更容易过"的判据 —— gzip 恰好能压掉重复内容。
|
|
143
|
+
重复内容由 `size-baseline.py` 的 `raw` 口径抓(那边同时看 raw 与 gzip)。
|
|
144
|
+
- **不管第三方代码**(`vendor/`)。那些不是我们的字节。
|
|
145
|
+
- **不管 demo 的内联 `<style>`** —— 那条单独卡在 8 KB,
|
|
146
|
+
因为内联样式会绕过 tokens 门禁,是完全不同的失效方式。
|
|
147
|
+
- **不管注释**。本库的注释是刻意投入(取舍理由、踩过的坑),
|
|
148
|
+
剥掉注释后的字节才是浏览器要解析的东西。想看注释占比:
|
|
149
|
+
`python3 05-audit/doc-facts-gate.py` 会核 README 里那个数字。
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 7 · 判据自己怎么被验证
|
|
154
|
+
|
|
155
|
+
体积门禁换模型时引入了一个新风险:**五个分支里只有一个会被日常触发**。
|
|
156
|
+
天花板永远不会红(除非已经晚了),储备永远不会红(除非已经用光),
|
|
157
|
+
棘轮只在删代码后才该红 —— 于是"写反不等号"可以绿着跑一百次。
|
|
158
|
+
|
|
159
|
+
所以有两道互相独立的验证:
|
|
160
|
+
|
|
161
|
+
| 文件 | 验什么 | 怎么验 |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `perf-gate.py --selftest` | `verdict()` 的五个分支顺序 + `main()` 真的调它 | 8 条合成数字 + 一条字节码接线检查 |
|
|
164
|
+
| `budget-selftest.py` | **端到端**行为:退出码、失败文案、`--grant`/`--ratchet` 写盘、账本缺失 | 15 条,用 `FL_BUDGET` 指向临时账本跑真实的 `perf-gate.py` |
|
|
165
|
+
|
|
166
|
+
变异探针(一次性的,结论记在这里):把五个分支逐一改成恒假、把 `main()` 改成
|
|
167
|
+
绕过 `verdict`、去掉授予次数上限 —— **5/5 全部被 `budget-selftest.py` 判红**,
|
|
168
|
+
且每次源码都逐字节还原(sha 核对)。
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 8 · 冻结声明
|
|
173
|
+
|
|
174
|
+
自 `0.22.2` 起,`05-audit/budget.json` 的四个数字(天花板 / 储备 / 死区 / 单组件)
|
|
175
|
+
**进入冻结**:改动它们需要一次显式的 ADR(第 5 节),
|
|
176
|
+
而不是一次"顺手调大"。
|
|
177
|
+
|
|
178
|
+
冻结不等于永远不会变 —— 它等于**变的时候必须留下痕迹**。
|
|
179
|
+
账本里的 `grants[]` 就是那个痕迹。
|
package/docs/CONVENTIONS.md
CHANGED
|
@@ -260,6 +260,9 @@
|
|
|
260
260
|
| 表格里的方向语义(涨跌嵌 `delta`、零值不带符号、口径只翻市场列) | `table-check.js` ⑫⑬⑭ |
|
|
261
261
|
| 指标缺数是显式状态、数字等宽对齐 | `metric-check.js` |
|
|
262
262
|
| 文档里的数字与实际一致(组件数/门禁数/精简档令牌数) | `doc-facts-gate.py` |
|
|
263
|
+
| 体积不越过账面预算(天花板 / 储备 / 棘轮 / 单组件) | `perf-gate.py`(账本 `05-audit/budget.json`,规则 [BUDGET.md](BUDGET.md)) |
|
|
264
|
+
| 预算判据自己的五个分支与接线 | `budget-selftest.py`(用临时账本端到端跑真实门禁) |
|
|
265
|
+
| 公开名字不消失、d.ts 与运行时一致 | `deprecation-gate.py` + `api-contract-gate.py`(承诺见 [STABILITY.md](STABILITY.md)) |
|
|
263
266
|
|
|
264
267
|
想加一条新规则?先在门禁里加判据、确认它会红,再写进本文 ——
|
|
265
268
|
**顺序反了就会得到一条"写在文档里、没人执行"的规则。**
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# 稳定性承诺(冻结声明)
|
|
2
|
+
|
|
3
|
+
> **一句话**:从 `0.23.0` 起,这个库进入**只增不减**的冻结期。
|
|
4
|
+
> 公开名字(令牌、类名、JS 成员、CLI 子命令、契约字段、文件路径)
|
|
5
|
+
> 不消失、不改名、不改语义;渲染细节、注释、文档、门禁随时可改。
|
|
6
|
+
> 冻结**不是**"停止维护",是"改动必须对下游可见"。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1 · 为什么写这份文件
|
|
11
|
+
|
|
12
|
+
"别动了"这句话有两种翻译,而它们的后果完全相反:
|
|
13
|
+
|
|
14
|
+
| 翻译 | 后果 |
|
|
15
|
+
|---|---|
|
|
16
|
+
| 不再发布、不再修 | 下游遇到 bug 只能自己打补丁,一年后没人敢升级 |
|
|
17
|
+
| 不再**破坏** | 下游可以放心把版本号写死,而修复继续到货 |
|
|
18
|
+
|
|
19
|
+
这份文件选第二种。它把"冻结"定义成一组**可被机器检查**的承诺,
|
|
20
|
+
每条承诺都指着一个正在跑的门禁 —— 没有判据的承诺不写在这里。
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2 · 冻的是什么(承诺 ↔ 判据)
|
|
25
|
+
|
|
26
|
+
| 承诺 | 具体到 | 谁在守 |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| 令牌名不消失 | `--text-primary`、`--sp-2` 这类名字 | `deprecation-gate.py` ⑦(`ai/surface.baseline.json` 只增不减) |
|
|
29
|
+
| 类名不消失 | `.btn`、`.field`、`.shell__link` 这类名字 | 同上 |
|
|
30
|
+
| JS 成员不消失 | `Select.open()`、`AccordionOptions.duration` 这类名字 | `deprecation-gate.py` ⑦ + `api-contract-gate.py`(d.ts ↔ 运行时**实测**比对) |
|
|
31
|
+
| 声明的名字真的存在 | 不许出现"文档里有、代码里没有"的幻觉 API | `gen-ai-contract.py --check`、`dead-class-gate.py` |
|
|
32
|
+
| 行为事件名不消失 | `fl:select` 这类事件名 | 只记在基线里,**没有运行时探针**(见 §6) |
|
|
33
|
+
| CLI 子命令与 `--json` 形状 | `agentcss ai <cmd> --json` 的 11 个子命令、失败也是 JSON | `ai-layer-gate.js` ⑥′⑥″ |
|
|
34
|
+
| 契约文件字段 | `ai/components.json` / `pitfalls*.json` / `behaviors.json` | `gen-ai-contract --check`、`ai-layer-gate` ⑦ |
|
|
35
|
+
| 文件路径 | `02-primitives/button/button.css` 这类 `<link>` 直接指向的路径 | `dist-parity-check`、`pack-smoke.sh`、`assets-catalog-gate.py` |
|
|
36
|
+
| 体积承诺 | 全库生效 CSS ≤ 172 KB,常规增长不涨价 | `perf-gate.py` + `budget-selftest.py`(见 [BUDGET.md](BUDGET.md)) |
|
|
37
|
+
|
|
38
|
+
逐条清单与总数**不写在这份文件里** —— 那是 `ai/surface.baseline.json` 的职责
|
|
39
|
+
(`deprecation-gate.py` 每次运行都会打印总数与「无未登记的移除」)。写在文档里的
|
|
40
|
+
计数一定会漂 —— 这份文件的第一版就写死过「337 个类名」,后来基线涨到 379,
|
|
41
|
+
没有任何门禁会因为那句话过时而变红。**数字只有一个来源,才不会有第二个版本。**
|
|
42
|
+
|
|
43
|
+
⚠️ "不消失"包含**不改语义**。把 `.btn` 从 44px 高改成 36px 不会让门禁变红,
|
|
44
|
+
但它一样是破坏性变更 —— 判据守不住的部分,靠这一行字约束。
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3 · 没冻的(随时可以改,不需要任何流程)
|
|
49
|
+
|
|
50
|
+
| 面 | 为什么可以随便改 |
|
|
51
|
+
|---|---|
|
|
52
|
+
| CSS 选择器的**写法** | 只要渲染结果等价。合并规则、换 `:is()`、调顺序都行 |
|
|
53
|
+
| 注释 | 本库注释是给人读的取舍记录,跟着代码一起演进 |
|
|
54
|
+
| demo 页 | 它们是文档,不是 API |
|
|
55
|
+
| 文档、guide、README | 同上。数字由 `doc-facts-gate.py` 对账 |
|
|
56
|
+
| 门禁脚本本身 | 门禁只约束产品,不约束自己 —— 但它自己要被 `selftest-all.py` 约束 |
|
|
57
|
+
| 新增组件、新增令牌、新增类名 | 纯增量,不破坏任何下游 |
|
|
58
|
+
| 内部实现(JS 私有函数、`data-*` 内部状态) | 没有承诺过,也从未写进 d.ts |
|
|
59
|
+
|
|
60
|
+
⚠️ 有一条**反直觉**的:`data-state` 这类属性值是**公开的**
|
|
61
|
+
(`.btn[data-state="loading"]` 是文档里教人写的选择器),
|
|
62
|
+
所以它们属于 §2 而不是 §3,即使它们长得像内部实现。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 4 · 版本语义(0.x 期间)
|
|
67
|
+
|
|
68
|
+
| 变更类型 | 版本位 | 例子 |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| 修复(渲染错、对比度不达标、门禁误报) | patch | `0.23.0 → 0.23.1` |
|
|
71
|
+
| 新增(组件、令牌、CLI 子命令、文档) | minor | `0.23.0 → 0.24.0` |
|
|
72
|
+
| 删除 / 改名 / 改语义 | **必须走废弃流程** | 见 `docs/DEPRECATIONS.md` |
|
|
73
|
+
|
|
74
|
+
废弃流程的硬要求(`deprecation-gate.py` ③):**迁移窗口 ≥ 一个次版本**。
|
|
75
|
+
也就是说,`0.24.0` 宣布废弃,最早 `0.26.0` 才能真的删 ——
|
|
76
|
+
中间那个版本是给下游的。
|
|
77
|
+
|
|
78
|
+
1.0 之后这条不变,只是"次版本"变成"主版本"。
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 5 · 什么情况下解冻
|
|
83
|
+
|
|
84
|
+
解冻指"允许一次破坏性变更"。三个条件**全部**满足才成立:
|
|
85
|
+
|
|
86
|
+
1. **有下游证据**:至少一个真实使用方(含本库自己的 `guide/` 与 demo)
|
|
87
|
+
明确表示旧名字挡路,且无法通过新增绕开;
|
|
88
|
+
2. **有迁移路径**:新名字先发布、旧名字标 deprecated、`replacement` 字段非空
|
|
89
|
+
(`deprecation-gate.py` ⑥ 会验证替代品真的存在);
|
|
90
|
+
3. **有账**:写进 `docs/DEPRECATIONS.md`,并让 `deprecation-gate.py` 的
|
|
91
|
+
`retroactive` 计数**不增加** —— 那条是棘轮,只许降。
|
|
92
|
+
|
|
93
|
+
不满足就只有一个选项:**新增,不动旧的**。
|
|
94
|
+
本库已经这样做过多次(`metric` 与 `delta` 并存、`page.css` 里 `.cmp` 家族
|
|
95
|
+
替代 22 份页内副本),代价是多几十行 CSS,收益是没人被迫改代码。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 6 · 诚实边界:这份承诺覆盖不到的地方
|
|
100
|
+
|
|
101
|
+
写出来,是因为"不说清就等于假绿":
|
|
102
|
+
|
|
103
|
+
- **事件名**(`fl:select` 等 6 个)只在基线里,**没有运行时探针**。
|
|
104
|
+
删了它,`deprecation-gate` 会红,但没有东西能证明它还会触发。
|
|
105
|
+
- **`data-*` 属性值**(`data-state="loading"`、`data-shell-nav`)不在
|
|
106
|
+
`surface.baseline.json` 的自动采集里 —— 它们在 §2 表里是**人写的承诺**,
|
|
107
|
+
不是机器守的。
|
|
108
|
+
- **视觉像素**。`10-review/shots/` 的 61 张基线只防"我们自己拍到的这些页面",
|
|
109
|
+
下游自己组装的页面会怎么变,量不到。
|
|
110
|
+
- **CSS 变量在 `@media` 里的重定义**(如强制色分支)。选择器结构和
|
|
111
|
+
`forced-colors-check.js` 覆盖了 7 类缺陷,但"用户自己的强制色主题"没有。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 7 · 怎么确认冻结没被破坏
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python3 05-audit/deprecation-gate.py # 公开名字有没有无流程消失
|
|
119
|
+
python3 05-audit/api-contract-gate.py # d.ts 与运行时是否仍然一致
|
|
120
|
+
node 05-audit/ai-layer-gate.js # CLI 与契约文件是否仍可用
|
|
121
|
+
python3 05-audit/perf-gate.py # 体积有没有越线
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
四道全绿 ⇒ §2 的每一条都还成立。任何一道红,说明"冻结"已经被破坏,
|
|
125
|
+
而破坏它的那次提交就在 `git log` 里 —— 这就是这份文件想要的效果:
|
|
126
|
+
**让"我们改了什么"从记忆问题变成一个查询问题。**
|
package/esm/index.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import '../02-primitives/combobox/combobox.js';
|
|
|
5
5
|
import '../02-primitives/date-range/date-range.js';
|
|
6
6
|
import '../02-primitives/popover/popover.js';
|
|
7
7
|
import '../02-primitives/select/select.js';
|
|
8
|
+
import '../02-primitives/upload/upload.js';
|
|
8
9
|
import '../03-patterns/accordion/accordion.js';
|
|
9
10
|
import '../03-patterns/drawer/drawer.js';
|
|
10
11
|
import '../03-patterns/dropdown/dropdown.js';
|
|
@@ -24,6 +25,7 @@ export const Combobox = g.Combobox;
|
|
|
24
25
|
export const DateRange = g.DateRange;
|
|
25
26
|
export const Popover = g.Popover;
|
|
26
27
|
export const Select = g.Select;
|
|
28
|
+
export const Upload = g.Upload;
|
|
27
29
|
export const Accordion = g.Accordion;
|
|
28
30
|
export const Drawer = g.Drawer;
|
|
29
31
|
export const Dropdown = g.Dropdown;
|
package/esm/upload.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* ⚠️ 生成物,勿手改 —— 由 05-audit/gen-esm.py 从源码的 `global.X = …` 生成。
|
|
2
|
+
改源码后跑 `python3 05-audit/gen-esm.py` 重新生成,`--check` 会拦住漂移。 */
|
|
3
|
+
import '../02-primitives/upload/upload.js';
|
|
4
|
+
|
|
5
|
+
const g = typeof window !== 'undefined' ? window : globalThis;
|
|
6
|
+
export const Upload = g.Upload;
|
|
7
|
+
export default Upload;
|