Skip to content

Commit 44de78d

Browse files
committed
docs(ui): 订正 Loading 首创断言与 Probe 靶子口径
1 parent bd8e61f commit 44de78d

3 files changed

Lines changed: 35 additions & 16 deletions

File tree

‎app/src/ui/primitives/Loading.css‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
*
44
* Why(为什么存在):全站加载提示现状是 **83 行/31 文件的一行灰字**,且**全站 0 骨架屏**
55
* (recon §2);规格 §5.1 把加载收敛为三形态(骨架 = 已知结构 / 探针 = 时长未知)。
6-
* 本文件同时是**全仓第一批 `@keyframes` 的产地**(实测改造前 0 个,recon §8.1):循环环境动效
7-
* 按规格 §8.2 判据走 CSS keyframes —— **不占 JS 主线程**,且 reduced-motion 一条媒体查询即静态。
6+
* 本文件自带**两条环境层 `@keyframes`**(骨架微光 + 探针摆动):循环环境动效按规格 §8.2 判据走
7+
* CSS keyframes —— **不占 JS 主线程**,且 reduced-motion 一条媒体查询即静态。
8+
* **事实订正(本文件不宣称任何"首个/第一")**:本仓第一条 `@keyframes` 是 `EmptyState.css` 的
9+
* `ed-empty-in`(一次性入场,`0e5e78ab` @ 00:06:10,**先于**本文件所在的 `174b1893` @ 00:06:41;
10+
* `git log -S "@keyframes ed-empty-in"` 可复核);本文件的两条是**循环(`infinite`)**动画。
811
*
912
* 边界:① 零颜色字面量(色值只在 ui/tokens.css 与生成器里);② 不写 `z-index`(层级是
1013
* `ui/zIndex.ts` 标尺的职责);③ 位移一律 ≤ 8px(规格 §8.4 末句,由 `style-seams.test.ts` 扫
@@ -14,6 +17,10 @@
1417
* 第二条媒体查询**(§8.2 的「一条媒体查询即静态」);⑥ 本文件只出三个**基类**
1518
* (`ed-loading` / `ed-skeleton` / `ed-probe`)+ 一个修饰类(`ed-loading--inline`):多行骨架的
1619
* 条距走相邻兄弟规则而**不新增容器类**,免得 T14 的「基类 ⊆ reduced-motion 名单」守卫为第三处开口子。
20+
* ⑦ **已知缺口(T11 评审 C-1;控制方裁决由 T14 在 `motion.css` 侧修,本文件不补)**:reduced-motion
21+
* 块按「基类名单」覆盖 `.ed-skeleton`,但真正带 `animation` 的是它的**伪元素 `::after`** ——
22+
* `animation-*` **不可继承**、伪元素也不在选择器匹配链上 ⇒ 计算样式层面 `::after` 仍是
23+
* `infinite 1200ms`(`.ed-probe` 无此问题:动画就挂在元素本身)。
1724
*/
1825

1926
.ed-loading {
@@ -47,7 +54,7 @@
4754
animation: ed-skeleton-shimmer var(--ed-dur-skeleton, 1200ms) var(--ed-ease, cubic-bezier(0.2, 0, 0, 1)) infinite;
4855
}
4956

50-
/* 全仓第一个 @keyframes:只动 `transform`(合成层,不逐帧触发布局) */
57+
/* 骨架微光:只动 `transform`(合成层,不逐帧触发布局) */
5158
@keyframes ed-skeleton-shimmer {
5259
from { transform: translateX(-100%); }
5360
to { transform: translateX(100%); }

‎app/src/ui/primitives/Loading.test.tsx‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@
66
* 无文字的最小单点 → `Probe`)。批 4 要把 83 行/31 文件的「加载中…」按这张表迁移,边界一旦漂移,
77
* 三个词就会退化成今天那种逐处手写的灰字。故本文件钉的是「谁渲染什么 + 类名 + 无障碍角色」。
88
*
9-
* ② 后半段(`describe("Loading.css …")`)是 **CSS 文本契约**,不是样式复述:本任务是全仓第一批
10-
* `@keyframes` 的产地(recon §8.1 实测改造前 0 个),而**周期与 `infinite` 在 jsdom 里无从观测**
11-
* (jsdom 既不跑动画也不解析样式表)⇒ 只能直接读 `Loading.css` 文本断言(计划 Step 1 明写
12-
* 「node 与 jsdom 都能跑」)。该段只读盘、不依赖 DOM。
9+
* ② 后半段(`describe("Loading.css …")`)是 **CSS 文本契约**,不是样式复述:本原语自带两条环境层
10+
* `@keyframes`,而**周期与 `infinite` 在 jsdom 里无从观测**(jsdom 既不跑动画也不解析样式表)
11+
* ⇒ 只能直接读 `Loading.css` 文本断言(计划 Step 1 明写「node 与 jsdom 都能跑」)。该段只读盘、不依赖 DOM。
12+
*
13+
* ③ **本文件的 reduced-motion 断言是「文本包含式」,不是「计算样式式」** —— 它只能证明
14+
* `motion.css` 的名单里**写了**三个基类,**证明不了真正带动画的那个选择器被覆盖**:`.ed-skeleton`
15+
* 的动画挂在伪元素 `::after` 上,而 `animation-*` 不可继承、伪元素也不在选择器匹配链上
16+
* (T11 评审 C-1 实测:计算样式里 `::after` 仍是 `infinite 1200ms`)。修法与机器守卫在 `motion.css`
17+
* 侧(控制方裁决交 T14),本文件**故意不加**该断言 —— 加了就会在 T14 修好之前常红。
1318
*
1419
* 副作用:无(纯展示组件;不读 store、不发请求、不写磁盘)。
1520
* 边界:本仓未装 `@testing-library/jest-dom` / `user-event`(硬约束:不新增依赖)⇒ 断言一律用
@@ -165,8 +170,8 @@ describe("Probe —— 不需要文字的最小单元(判定顺序第 3 问)
165170
});
166171
});
167172

168-
describe("Loading.css —— 全仓第一批 @keyframes(周期与 infinite 只能从 CSS 文本钉)", () => {
169-
it("恰好两个 @keyframes 各定义一次(骨架微光 + 探针摆动;本文件就是全仓产地)", () => {
173+
describe("Loading.css —— 两条环境层 @keyframes(周期与 infinite 只能从 CSS 文本钉)", () => {
174+
it("恰好两个 @keyframes 各定义一次(骨架微光 + 探针摆动;均为循环环境动效)", () => {
170175
expect(CSS.match(/@keyframes ed-skeleton-shimmer/g)).toHaveLength(1);
171176
expect(CSS.match(/@keyframes ed-probe-swing/g)).toHaveLength(1);
172177
expect(CSS.match(/@keyframes/g)).toHaveLength(2);
@@ -196,11 +201,15 @@ describe("Loading.css —— 全仓第一批 @keyframes(周期与 infinite 只
196201
});
197202

198203
describe("reduced-motion:静态由 motion.css 的既有块承担(§8.2「一条媒体查询即静态」)", () => {
199-
it("本文件不得自带第二条媒体查询(全仓唯一一条 reduced-motion 块在 motion.css)", () => {
204+
it("本文件不得自带第二条媒体查询(app/src 内唯一的 reduced-motion 规则块在 motion.css)", () => {
200205
expect(CSS).not.toContain("@media");
201206
});
202207

203-
it("motion.css 的块覆盖本文件三个基类,且 iteration-count: 1 让循环动画停住", () => {
208+
it("motion.css 的块**列了**本文件三个基类,且 iteration-count: 1 让循环动画停住", () => {
209+
// ⚠️ 本条只证「名单里有这三个基类」,**不证**「真正带动画的选择器被覆盖」:
210+
// `.ed-skeleton` 的 animation 挂在伪元素 `::after` 上(`animation-*` 不可继承、伪元素不在
211+
// 选择器匹配链上)⇒ 实际被停住的只有 `.ed-probe`。这是 T11 评审 C-1,修在 `motion.css` 侧
212+
// (控制方裁决交 T14);见文件头 ③ 与 `Loading.css` 边界⑦。
204213
const at = MOTION_CSS.indexOf("@media (prefers-reduced-motion");
205214
expect(at, "motion.css 缺少 reduced-motion 块").toBeGreaterThanOrEqual(0);
206215
const block = MOTION_CSS.slice(at);

‎app/src/ui/primitives/Loading.tsx‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
*
88
* | 组件 | 一句话规则 | 现状靶子(批 4 迁移) |
99
* |---|---|---|
10-
* | `Skeleton` | **知道要出现什么形状**(几行/几块)⇒ 用骨架微光占位那个形状 | `GoalDetail.tsx` 的「加载中…」一行灰字 → 3 行骨架 |
11-
* | `Loading` | **不知道形状,但需要一句文字说明在等什么**(时长未知)⇒ 文案 + 探针 | `ClassroomCapturePanel.tsx`「⏳ 正在下载模型(~650MB)…」 |
12-
* | `Probe` | **不需要文字的最小单元**:单点脉冲,可嵌按钮/行内,并被 `Loading` 内部复用 | `GroupDeleteConfirm.tsx` 的 `data-testid="group-delete-loading"` 单行灰字 |
10+
* | `Skeleton` | **知道要出现什么形状**(几行/几块)⇒ 用骨架微光占位那个形状 | `GoalDetail.tsx:83` 的「加载中…」一行灰字 → 3 行骨架 |
11+
* | `Loading` | **不知道形状,但需要一句文字说明在等什么**(时长未知)⇒ 文案 + 探针 | `ClassroomCapturePanel.tsx:125-127`「⏳ 正在下载模型(~650MB)…」· **`GroupDeleteConfirm.tsx:96`** 的 `data-testid="group-delete-loading"` |
12+
* | `Probe` | **不需要文字的最小单元**:单点脉冲,可嵌按钮/行内,并被 `Loading` 内部复用 | 仓内**暂无**纯"无文字最小单点"的靶子 —— 它的形态今天由 `Loading` 内部复用(`.ed-loading` 里的圆点)承载;批 4 遇到按钮内/行内加载点再用 |
1313
* ⇒ 判定顺序:**先问"形状已知吗"**(是 → `Skeleton`)→ **再问"要文字吗"**(要 → `Loading`)→ 都不要 → `Probe`。
14+
* ⚠️ **靶子口径订正(T11 评审 I-2,控制方采纳)**:`GroupDeleteConfirm.tsx:96` 渲染的是**文字**
15+
* 「正在统计影响面…」且被替换的 impact 形状**已知** ⇒ 按判定表落 **`Loading`**,不落 `Probe`
16+
* (该点原被列在 `Probe` 名下,是错的)。⇒ 三形态的靶子**2/3 成立**,`Probe` 暂无真实靶子。
1417
*
15-
* 副作用:`import "./Loading.css"` —— 首个引入本原语的模块会带上样式表与**全仓第一批 `@keyframes`**
16-
* (循环环境动效走 CSS,不占 JS 主线程)。组件本身不读 store、不发请求、不写磁盘、不注册计时器。
18+
* 副作用:`import "./Loading.css"` —— 首个引入本原语的模块会带上样式表与本原语自带的
19+
* **两条环境层 `@keyframes`**(循环动效走 CSS,不占 JS 主线程)。组件本身不读 store、不发请求、不写磁盘、不注册计时器。
1720
*
1821
* 边界:① 排版(字号/墨度)**一律交给 `Text`**,本文件不写字号与颜色(`.ed-loading--inline .ed-text`
1922
* 是"跟随宿主字阶"的唯一例外,且写在 CSS 里);② 三个组件都是**纯展示**:`Loading`/`Skeleton` 的

0 commit comments

Comments
 (0)