Review changes batch 6 of 6

This commit is contained in:
2026-07-20 06:52:33 +08:00
parent db97d3da27
commit 5a31a75da0
160 changed files with 9206 additions and 572 deletions
@@ -0,0 +1,160 @@
# AppLoading 必要加载动效实施计划
> **执行要求:** 使用 `superpowers:executing-plans` 在当前会话内逐步执行。用户明确禁止多代理、worktree、`git add`、commit、push、reset 和 checkout。
**目标:** 修复 G01 在 `prefers-reduced-motion: reduce` 下加载态完全静止的问题,使印牌和如意结保留清晰的透明度呼吸,同时不产生缩放、旋转或位移。
**架构:** 继续由 `components/AppLoading.vue` 单一维护加载动效,不在 G01 复制样式。正常模式保持现有动画;减少动态效果模式改用独立的透明度关键帧,并强制 `transform: none`。测试先锁定旧行为必须失败,再验证静态契约和当前 Chrome 运行时证据。
**技术栈:** Vue 3、uni-app、SCSS、PowerShell 契约测试、Chrome DevTools Protocol、Node.js。
## 全局约束
- 只修改 G01 加载态直接依赖的公共 `AppLoading`、聚焦测试和当轮内部证据。
- 不改变 `variant``text``description` 组件契约。
- 不改变 G01 状态逻辑、布局、背景、文案、标题栏或底部导航。
- 不新增图片、CSS 图形、JavaScript 定时器或业务接口。
- `prefers-reduced-motion: reduce` 下只允许透明度变化,`transform` 必须为 `none`
- H5 截图只是内部候选;Android/HBuilderX 仍未验证。
- 不执行任何 Git 写操作。
---
### Task 1:修复并验证减少动态效果模式的必要加载反馈
**文件:**
- 修改:`tests/app-loading-contract.ps1`
- 修改:`components/AppLoading.vue`
- 验证:`tests/g01-loading-state-contract.ps1`
- 验证:`tests/g-series-app-loading-contract.ps1`
- 验证:`tests/module-app-loading-contract.ps1`
- 内部证据:`docs/design/screens/runtime/2026-07-19/g01-approval/03-loading-essential-motion-412x915.png`
**接口:**
- 消费:`AppLoading` 现有 `variant``text``description` 属性。
- 产出:正常模式继续使用 `app-loading-seal-breathe``app-loading-knot-breathe`;减少动态效果模式使用 `app-loading-seal-essential-pulse``app-loading-knot-essential-pulse`
- 不增加新的组件属性、事件或页面状态。
- [ ] **Step 1:先补充会失败的公共契约**
`tests/app-loading-contract.ps1` 的现有动画断言之后加入:
```powershell
Assert-Match -Content $component -Pattern '@keyframes\s+app-loading-seal-essential-pulse' -Message 'Reduced-motion AppLoading must retain an essential seal opacity pulse'
Assert-Match -Content $component -Pattern '@keyframes\s+app-loading-knot-essential-pulse' -Message 'Reduced-motion AppLoading must retain an essential knot opacity pulse'
Assert-Match -Content $component -Pattern '(?s)@media\s*\(prefers-reduced-motion:\s*reduce\).*?\.app-loading__seal\s*\{[^}]*animation:\s*app-loading-seal-essential-pulse\s+1\.2s\s+ease-in-out\s+infinite;[^}]*transform:\s*none;' -Message 'Reduced-motion seal must pulse opacity without transform motion'
Assert-Match -Content $component -Pattern '(?s)@media\s*\(prefers-reduced-motion:\s*reduce\).*?\.app-loading__knot\s*\{[^}]*animation:\s*app-loading-knot-essential-pulse\s+1\.2s\s+ease-in-out\s+\.2s\s+infinite;[^}]*transform:\s*none;' -Message 'Reduced-motion knot must pulse opacity without transform motion'
if ($component -match '(?s)@media\s*\(prefers-reduced-motion:\s*reduce\).*?animation:\s*none') {
throw 'Reduced-motion AppLoading must not become completely static'
}
```
- [ ] **Step 2:运行聚焦契约并确认 RED**
运行:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/app-loading-contract.ps1
```
预期:退出码非 0,错误首先指向缺少 `app-loading-seal-essential-pulse`,证明测试捕获的是当前完全静止行为,而不是语法或路径错误。
- [ ] **Step 3:实现最小降动效透明度脉冲**
`components/AppLoading.vue` 中保留现有正常模式关键帧,并把当前媒体查询替换为:
```scss
@keyframes app-loading-seal-essential-pulse {
0%, 100% { opacity: .58; }
50% { opacity: 1; }
}
@keyframes app-loading-knot-essential-pulse {
0%, 100% { opacity: .28; }
50% { opacity: .86; }
}
@media (prefers-reduced-motion: reduce) {
.app-loading__seal {
animation: app-loading-seal-essential-pulse 1.2s ease-in-out infinite;
transform: none;
}
.app-loading__knot {
animation: app-loading-knot-essential-pulse 1.2s ease-in-out .2s infinite;
transform: none;
}
}
```
不得修改模板、属性、尺寸、文案或正常模式关键帧。
- [ ] **Step 4:运行聚焦契约并确认 GREEN**
运行:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/app-loading-contract.ps1
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/g01-loading-state-contract.ps1
```
预期:分别输出 `APP-LOADING-CONTRACT PASS``G01-LOADING-STATE-CONTRACT PASS`,退出码均为 0。
- [ ] **Step 5:在当前唯一 Chrome 标签页验证真实动画**
保持 G01 `state=loading` 和 412×915 视口。通过 9222 CDP 在同一次检查中读取:
```js
const seal = document.querySelector('.app-loading__seal')
const knot = document.querySelector('.app-loading__knot')
const sealStyle = getComputedStyle(seal)
const knotStyle = getComputedStyle(knot)
({
reduced: matchMedia('(prefers-reduced-motion: reduce)').matches,
sealAnimation: sealStyle.animationName,
sealTransform: sealStyle.transform,
sealOpacity: sealStyle.opacity,
sealAnimations: seal.getAnimations().length,
knotAnimation: knotStyle.animationName,
knotTransform: knotStyle.transform,
knotOpacity: knotStyle.opacity,
knotAnimations: knot.getAnimations().length
})
```
等待约 420ms 后再次读取。预期:
- `reduced``true`
- 两个 `animationName` 分别以新的 essential pulse 名称开头;Vue scoped CSS 可在运行时追加哈希后缀;
- 两个 `getAnimations().length` 均大于 0
- 两次读取的透明度不同;
- 两个 `transform` 均为 `none`
- 页面仍为 G01 加载态。
- [ ] **Step 6:捕获并人工检查当前候选帧**
使用当前唯一标签页保存:
```text
docs/design/screens/runtime/2026-07-19/g01-approval/03-loading-essential-motion-412x915.png
```
人工检查:标题栏、背景、印牌、如意结、两行文案和底部导航均完整;没有列表、失败态或原生 Loading 串入。静态截图不作为动画运行证明,动画结论以 Step 5 的多时点数据为准。
- [ ] **Step 7:运行受影响合同与空白检查**
运行:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/g-series-app-loading-contract.ps1
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tests/module-app-loading-contract.ps1
git diff --check -- components/AppLoading.vue tests/app-loading-contract.ps1 docs/superpowers/specs/2026-07-19-app-loading-essential-motion-design.md docs/superpowers/plans/2026-07-19-app-loading-essential-motion.md
```
预期:两个合同均输出各自的 `PASS``git diff --check` 退出码为 0。不得把 Android/HBuilderX 写成已验证。
- [ ] **Step 8:回到同一加载态等待用户复核**
保持浏览器停留在修正后的 G01 加载态,不自动切换到失败态。只有用户明确说加载态“通过”后,才继续 G01 下一状态;在此之前 G01 保持 `[!]`