From 622a6f98a8bd8ddee9f1bdbf9a45cc977b129087 Mon Sep 17 00:00:00 2001 From: zyh Date: Tue, 21 Apr 2026 06:43:36 +0000 Subject: [PATCH] =?UTF-8?q?sync:=20auto-fix=20=E8=A7=84=E5=88=99=E6=8C=89?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E6=8B=86=E5=88=86=20=E2=80=94=20uni-app/i18n?= =?UTF-8?q?=20=E4=B8=93=E5=B1=9E=E6=A8=A1=E6=9D=BF=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 platforms/uni-app.md: uni-app 专属修复模板(#17/#18/#19/#21/#23) - 新增 platforms/nextjs-i18n.md: i18n 专属修复模板(#25-#28) - engine.md 新增 §0 平台检测与条件加载逻辑 - classification.md 新增 #25-#30 条目(i18n/数据同步/资源404) - iron-laws.md 新增 i18n 检测规则 - visual-verification.md 新增 i18n 语言一致性检测 - journey-definition.md 新增 i18n 断言模板 - journey-execution.md 新增 i18n 校验子步骤 --- .../modes/journey/journey-definition.md | 10 + .../modes/journey/journey-execution.md | 30 +- .../d8d-dev/reference/platform-uni-app.md | 4 +- .../d8d-dev/rules/auto-fix/classification.md | 92 +++-- .../skills/d8d-dev/rules/auto-fix/engine.md | 25 +- .../rules/auto-fix/platforms/nextjs-i18n.md | 148 ++++++++ .../rules/auto-fix/platforms/uni-app.md | 232 +++++++++++++ .../d8d-dev/rules/auto-fix/workflows.md | 319 +++++------------- .claude/skills/d8d-dev/rules/iron-laws.md | 15 +- .../d8d-dev/rules/visual-verification.md | 24 ++ 10 files changed, 631 insertions(+), 268 deletions(-) create mode 100644 .claude/skills/d8d-dev/rules/auto-fix/platforms/nextjs-i18n.md create mode 100644 .claude/skills/d8d-dev/rules/auto-fix/platforms/uni-app.md diff --git a/.claude/skills/d8d-dev/modes/journey/journey-definition.md b/.claude/skills/d8d-dev/modes/journey/journey-definition.md index 34b8d7b..304e445 100644 --- a/.claude/skills/d8d-dev/modes/journey/journey-definition.md +++ b/.claude/skills/d8d-dev/modes/journey/journey-definition.md @@ -199,6 +199,16 @@ - [ ] {具体可量化的断言1} - [ ] {具体可量化的断言2} - [ ] {全程使用自然导航路径} + +### i18n 断言(可选 — 仅 i18n 项目填写) + +> 如果项目使用了 i18n(如 next-intl、next-i18next),每个旅程步骤需额外定义以下断言。 +> 非 i18n 项目留空或删除此区域即可。 + +| 步骤 | 预期 URL(含 locale) | 预期 html lang | 预期内容语言 | 预期 locale cookie | +|------|---------------------|---------------|-------------|-------------------| +| 1 | `/en/login` | `en` | English | `NEXT_LOCALE=en` | +| 2 | `/zh/dashboard` | `zh` | 中文 | `NEXT_LOCALE=zh` | ``` ### 旅程来源优先级 diff --git a/.claude/skills/d8d-dev/modes/journey/journey-execution.md b/.claude/skills/d8d-dev/modes/journey/journey-execution.md index 2d68abb..9a280d4 100644 --- a/.claude/skills/d8d-dev/modes/journey/journey-execution.md +++ b/.claude/skills/d8d-dev/modes/journey/journey-execution.md @@ -411,6 +411,33 @@ curl 只用于补充数据验证(如确认数据库状态),**永远不能 6. 修复失败 → 标记 auto_fix_result: "FAIL",记录原因 6. ⛔ **源码 SVG data URI 扫描**(视觉验证关卡内 — 每次 snapshot 后强制执行): + 6.5. ⛔ **i18n 校验子步骤**(⛔ 条件激活 — 仅 i18n 项目,每次 snapshot 后强制执行): + 前置检测:`glob src/i18n/**/*` 或 `next.config.*` 中含 i18n 配置。 + 项目无 i18n 配置 → 跳过以下所有步骤(单语言项目零影响)。 + + a. URL locale 检查: + `browser_evaluate` → `window.location.pathname` + → 验证匹配 `/^\/(en|zh)\//`(或项目配置的 locale 列表) + → FAIL(#25):URL 无 locale 前缀 + + b. html lang 检查: + `browser_evaluate` → `document.documentElement.lang` + → 验证与 URL locale 一致 + → FAIL(#26):lang 属性不匹配 + + c. 内容语言检查: + `browser_evaluate` → `document.body.innerText.substring(0, 2000)` + → URL `/zh/`:检查文本包含中文(`\u4e00-\u9fff`),无英文整句 + → URL `/en/`:检查文本无中文字符 + → FAIL(#27):内容语言不匹配 + + d. Cookie 检查: + `browser_evaluate` → `document.cookie` + → 验证包含 NEXT_LOCALE(或项目配置的 cookie 名) + → 验证 cookie 值与 URL locale 一致 + → FAIL(#28):cookie 缺失或值不匹配 + + e. 记录结果到 verification_detail.i18n_check(格式见 ../../rules/visual-verification.md) g. ⛔ **约束一致性检查**(每个用户步骤首次执行时 — R7 执行时强制检测): 触发条件(操作被拦截 + 声明方说"可选"): - 操作后 Toast 出现且含"请上传/请填写/请选择/不能为空/不能为空" @@ -430,7 +457,8 @@ curl 只用于补充数据验证(如确认数据库状态),**永远不能 - ⛔ **资源加载状态检查**(新增 — 每条旅程必须执行): - `browser_network_requests(static=true, filter="\\.(jpg|jpeg|png|gif|webp|svg|mp4|webm|mov)$")` - 过滤图片/视频资源请求,检查 HTTP 状态码 - - 任何 403/404 → 标记 `resource_status_check.found = FOUND` + 触发修复 + - 任何 403/404 → 标记 `resource_status_check.found = FOUND` + 触发 AUTO-FIX #30(见 ../../rules/auto-fix/workflows.md) + - ⛔ **禁止行为**: 将 404/403 标记为"非阻断性"跳过修复。资源 404 是 Bug(#30),必须修复。 - 常见 403 原因:MinIO 私有 bucket 未用 presigned URL、防盗链、CORS - 常见 404 原因:资源路径错误、文件未上传 - 记录到 pipeline-state.yaml 的 `verification_detail.resource_status_check` diff --git a/.claude/skills/d8d-dev/reference/platform-uni-app.md b/.claude/skills/d8d-dev/reference/platform-uni-app.md index 25849cf..1caa46c 100644 --- a/.claude/skills/d8d-dev/reference/platform-uni-app.md +++ b/.claude/skills/d8d-dev/reference/platform-uni-app.md @@ -522,6 +522,6 @@ grep -rn '`/``/``/``/`` 等)| Bug(平台不兼容) | AUTO-FIX | H5 环境下 `` 不渲染,`` 无法点击。⛔ 必须替换为纯 view + CSS 自定义组件,参见 ../reference/platform-uni-app.md §8 | -| **99** | **以上都不匹配的任何 FAIL** | **默认: Bug** | **AUTO-FIX** | **兜底规则,确保无遗漏** | -| **V1** | 页面主题/配色与原型不一致(深色→浅色、主色调偏差) | 视觉差异 | VISUAL-FIX | 原型深色主题但实际浅色;按钮颜色不一致 | -| **V2** | 页面布局结构与原型不同(顶部导航↔侧边栏、网格↔列表、卡片↔表格) | 视觉差异 | VISUAL-FIX | 原型顶部水平导航但实际左侧边栏;原型卡片网格但实际列表 | -| **V3** | 品牌标识与原型不同(Logo 图标、标题文字、副标题) | 视觉差异 | VISUAL-FIX | 原型闪电图标+"VividSpark"但实际 "V" 字母+"Welcome back" | -| **V4** | 原型存在的 UI 组件在实际页面中缺失(Remember me、FAQ、过滤器、标签页等) | 视觉差异 | VISUAL-FIX | 原型有 FAQ 区域但实际缺失;原型有 Remember me 但实际无 | -| **V5** | 按钮/输入框/卡片样式与原型不一致(padding、圆角、边框、字号等) | 视觉差异 | VISUAL-FIX | 原型圆角 16px 但实际 8px;原型按钮 padding 16/32 但实际 12/24 | +> **平台专属条目**: 标记 `[uni-app]` 的见 `./platforms/uni-app.md`,标记 `[i18n]` 的见 `./platforms/nextjs-i18n.md`。 + +| # | 平台 | FAIL 特征 | 分类 | 修复类型 | 示例 | +|---|------|-----------|------|---------|------| +| 1 | 通用 | API 返回 404 且路由文件不存在 | 未实现 | AUTO-IMPLEMENT | 路由缺失 | +| 2 | 通用 | API 返回 500 / 错误状态码 | Bug | AUTO-FIX | 空 body 返回 500 | +| 3 | 通用 | 前端组件/按钮未渲染 | 未实现 | AUTO-IMPLEMENT | 登录入口按钮缺失 | +| 4 | 通用 | 页面跳转到错误位置 | Bug | AUTO-FIX | 导航逻辑错误 | +| 5 | 通用 | 页面停留在非功能态("加载中"/空白/错误态/有内容但零交互元素) | Bug | AUTO-FIX | hydration 失败 / 渲染 Bug / fetchProfile 空 catch / 响应结构不匹配 | +| 6 | 通用 | 可交互元素无法通过 Playwright 操作 | Bug | AUTO-FIX | uni-input 编译后 browser_type 失败 | +| 7 | 通用 | 功能提示"暂未开放"/"开发中"/"暂未实现" | 未实现 | AUTO-IMPLEMENT | 手机号登录只有占位符 | +| 8 | 通用 | 测试数据过期/状态不对 | 数据问题 | AUTO-DATA-FIX | 收益冻结期不可提现 | +| 9 | 通用 | 已有数据阻止测试(重复评价等) | 数据问题 | AUTO-DATA-FIX | 删除旧评价或换测试用户 | +| 10 | 通用 | 正常业务规则正确拒绝无效操作 | 可接受 | 仅记录 | 最低提现金额限制 | +| 11 | 通用 | 旅程定义或源码中发现 Mock 登录且手机号登录未实现 | 未实现 | AUTO-IMPLEMENT | Mock 登录不是真实用户流程,必须实现手机号+验证码登录 | +| 12 | 通用 | 操作被前端表单校验拦截(Toast/提示阻止提交)| 环境限制/校验 | ENV-ADAPT | 按 Toast 内容分流:缺少必填→补充填写;文件上传→browser_file_upload;环境限制→代码添加 dev 替代 | +| 13 | 通用 | 表单校验失败但页面无任何提示(无 Toast/无错误文字)| Bug(UX 缺陷)| AUTO-FIX | 用户不知道为什么操作无效 → 添加 Toast 或内联错误提示 | +| 14 | 通用 | 即时使用数据(验证码等)仅通过 Toast 显示,无 console.log 或自动填充,下一步骤无法获取 | Bug(数据丢失风险)| AUTO-FIX | sendCode() 用 Toast 显示验证码但无 console.log,流程卡死 | +| 15 | 通用 | UI 直接输出英文枚举值(如 APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)而非本地化中文标签 | Bug(UI 本地化缺失)| AUTO-FIX | 任何前端页面(管理后台/小程序/H5)状态字段显示英文枚举而非中文 | +| 16 | 通用 | 约束声明与代码行为不一致(Constraint Mismatch): 旅程描述/UI 文案说"可选/可跳过/选填"但代码实际必填,或 UI 标记"选填"但接口必填 | 约束不一致 | JOURNEY-FIX / AUTO-FIX | 旅程说"可跳过上传证件"但代码 `if (!idCardFront)` Toast "请上传所有证件照片" | +| 17 | uni-app | 前端代码 CSS/style 含 SVG data URI(充当图标/背景/装饰) | Bug(平台不兼容) | → 见 platforms/uni-app.md | 微信小程序不支持 SVG data URI | +| 18 | uni-app | 页面同时有系统导航栏和自定义 fixed header,导致双层标题栏 | Bug(双重导航栏) | → 见 platforms/uni-app.md | 截图中顶部出现两层标题 | +| 19 | uni-app | Tab 页面下拉刷新方式不一致,或使用列表级 scroll-view refresher | Bug(UX 不一致) | → 见 platforms/uni-app.md | 首页用 scroll-view refresher,其他 Tab 无刷新 | +| 20 | 通用 | API route handler 无 try-catch 包裹,导致未处理异常直接返回原始 500 且无日志 | Bug(健壮性) | AUTO-FIX | Admin 提现审批 PUT 返回 500 但状态仍更新(异常被吞) | +| 21 | uni-app | uni-app 页面使用 `onMounted` 获取数据,但该页面是 `uni.navigateBack()` 的返回目标,导致回退时数据不刷新 | Bug(数据陈旧) | → 见 platforms/uni-app.md | 提现后返回收益页余额未刷新 | +| 22 | 通用 | Playwright MCP 浏览器崩溃/超时(browser_snapshot/browser_click 等操作返回错误或无响应)| 环境问题 | AUTO-RETRY | 浏览器进程崩溃、MCP 连接超时、页面 unresponsive | +| 23 | uni-app | uni-app 原生组件在 H5 环境下不渲染/不可交互 | Bug(平台不兼容) | → 见 platforms/uni-app.md | H5 环境下 `` 不渲染 | +| 25 | i18n | i18n 项目页面 URL 缺少 locale 前缀 | Bug(i18n) | → 见 platforms/nextjs-i18n.md | middleware locale 检测逻辑错误 | +| 26 | i18n | html lang 属性与 URL locale 不匹配 | Bug(i18n) | → 见 platforms/nextjs-i18n.md | layout.tsx 的 lang 属性未从 URL params 读取 | +| 27 | i18n | 页面内容语言与 URL locale 不匹配 | Bug(i18n) | → 见 platforms/nextjs-i18n.md | i18n 翻译文件缺失或加载错误 | +| 28 | i18n | NEXT_LOCALE Cookie 缺失或值与 URL locale 不匹配 | Bug(i18n) | → 见 platforms/nextjs-i18n.md | middleware 或 layout 未正确同步 cookie | +| 29 | 通用 | API 返回正确数据但前端显示值不匹配(数据映射/提取逻辑错误)| Bug(数据同步) | AUTO-FIX | API 返回 credits=3000 但 UI 显示 0 | +| 30 | 通用 | 静态资源 404(图片/图标/CSS/JS 加载失败,`browser_network_requests` 确认)| Bug(资源缺失) | AUTO-FIX | sparklogo.png 路径错误或文件缺失 | +| **99** | 通用 | **以上都不匹配的任何 FAIL** | **默认: Bug** | **AUTO-FIX** | **兜底规则,确保无遗漏** | +| **V1** | 通用 | 页面主题/配色与原型不一致(深色→浅色、主色调偏差) | 视觉差异 | VISUAL-FIX | 原型深色主题但实际浅色;按钮颜色不一致 | +| **V2** | 通用 | 页面布局结构与原型不同(顶部导航↔侧边栏、网格↔列表、卡片↔表格) | 视觉差异 | VISUAL-FIX | 原型顶部水平导航但实际左侧边栏;原型卡片网格但实际列表 | +| **V3** | 通用 | 品牌标识与原型不同(Logo 图标、标题文字、副标题) | 视觉差异 | VISUAL-FIX | 原型闪电图标+"VividSpark"但实际 "V" 字母+"Welcome back" | +| **V4** | 通用 | 原型存在的 UI 组件在实际页面中缺失(Remember me、FAQ、过滤器、标签页等) | 视觉差异 | VISUAL-FIX | 原型有 FAQ 区域但实际缺失;原型有 Remember me 但实际无 | +| **V5** | 通用 | 按钮/输入框/卡片样式与原型不一致(padding、圆角、边框、字号等) | 视觉差异 | VISUAL-FIX | 原型圆角 16px 但实际 8px;原型按钮 padding 16/32 但实际 12/24 | ### "可接受"严格限定(⛔ 仅以下 2 种,其余一律必须修复) @@ -104,9 +112,23 @@ - ⛔ 无任何提示 → Bug(UX 缺陷:校验失败但用户看不到原因)→ AUTO-FIX(添加 Toast/错误提示) - 即时使用数据(验证码)仅通过 Toast 显示且源码无 console.log → Bug(#14) → AUTO-FIX(添加 console.log + 自动填充) - UI 显示英文枚举值(APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)→ Bug(#15) → AUTO-FIX(添加中文映射函数,适用于所有前端:管理后台 statusLabel / 小程序 computed 映射) -- 前端代码含 `data:image/svg+xml`(SVG data URI)→ `grep -rn "data:image/svg+xml" app/mini-program/src/` 扫描所有 `.vue` 文件 → 发现 → 平台不兼容(#17) → AUTO-IMPLEMENT(⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + 替换所有 SVG data URI,**禁止用另一种 SVG data URI 替代原来的**) -- Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用)→ Bug(#19) → AUTO-FIX(参见 ../reference/platform-uni-app.md §7 统一为 onPullDownRefresh) -- uni-app 原生组件 H5 不渲染 → `browser_snapshot` 显示目标区域空白/缺失,`Read` 源码确认使用了 ``/``/``/``/`` 等 uni-app 原生组件 → Bug(#23) → AUTO-FIX(⛔ 替换为纯 view + CSS 自定义组件,参见 ../reference/platform-uni-app.md §8。**禁止标记为"环境限制"跳过修复**) - 无法归类的 FAIL → 默认 Bug(#99) → AUTO-FIX - API 返回 500 + `Read` 源码发现 handler 无 try-catch → Bug(#20) → AUTO-FIX(添加 try-catch 包裹 + console.error + JSON 500 响应) -- `uni.navigateBack()` 返回后 `browser_snapshot` 数据未刷新 + 源码用 `onMounted` 非 `onShow` → Bug(#21) → AUTO-FIX(替换 `onMounted` 为 `onShow`) +- `browser_evaluate` 抓取 API 响应与 UI 显示值不一致(API 200 且数据正确但 UI 显示错误)→ Bug(#29) → AUTO-FIX(见 workflows.md AUTO-FIX #29) +- `browser_network_requests` 检测到静态资源 404 → Bug(#30) → AUTO-FIX(见 workflows.md AUTO-FIX #30) + +**平台专属判断流程**(仅对应平台项目时执行): +- uni-app 项目 → 见 `./platforms/uni-app.md` 关键判断流程(#17/#18/#19/#21/#23) +- i18n 项目 → 见 `./platforms/nextjs-i18n.md` 关键判断流程(#25/#26/#27/#28) + +### ⛔ #99 兜底规则强化(⛔ 强制) + +**核心原则**: 任何由执行模板中定义的校验子步骤(含 i18n 校验 6.5 节的 FAIL)产生的检测结果,如果在分类表中找不到精确匹配的编号,**必须**走 #99 兜底触发 AUTO-FIX。 + +**⛔ 禁止行为**: +- ❌ 将检测到的 FAIL 标记为"非阻断性"跳过修复 +- ❌ 以"分类表中无对应编号"为由不触发修复 +- ❌ 以"不影响核心流程"为由记录但不修复 +- ❌ 将任何 FAIL 结果降级为 WARN/建议 + +**唯一例外**: 满足"可接受"两项严格限定之一(系统按设计正确拒绝 / 业务规则正确阻止),且不影响后续步骤。 diff --git a/.claude/skills/d8d-dev/rules/auto-fix/engine.md b/.claude/skills/d8d-dev/rules/auto-fix/engine.md index 1e99b21..63a32e9 100644 --- a/.claude/skills/d8d-dev/rules/auto-fix/engine.md +++ b/.claude/skills/d8d-dev/rules/auto-fix/engine.md @@ -4,6 +4,23 @@ --- +## 0. 平台检测与条件加载(⛔ 启动时执行一次) + +修复引擎启动时,检测项目结构以决定加载哪些平台专属修复模板: + +1. **检测 uni-app/小程序项目**: + - `[ -d "app/mini-program/src" ]` 或 `grep -rl "pages.json" . --include="*.json" -l | head -1` + - 检测到 → `PLATFORM_UNIAPP=true` → 额外 Read: `./platforms/uni-app.md` + +2. **检测 i18n 项目**: + - `grep -rl "next-intl\|next-i18n\|\[locale\]" src/app/ src/middleware.ts 2>/dev/null | head -1` + - 或检查 `src/app/[locale]/` 目录是否存在 + - 检测到 → `PLATFORM_I18N=true` → 额外 Read: `./platforms/nextjs-i18n.md` + +3. **未检测到任何平台**: 仅加载通用修复模板(classification.md + diagnostics.md + workflows.md) + +--- + ## 1. 步骤完成判定标准(⛔ 每个 step 执行后强制判定) **PASS**: `browser_snapshot` 显示的内容 **完全包含** 旅程/验证定义中该步骤"用户看到 X"的预期状态。 @@ -27,7 +44,8 @@ ## 2. FAIL 分类标准 -> ⛔ **Read fully and follow: ./classification.md** — 包含 23 种 FAIL 分类(#1-#23, #99, V1-V5)、"可接受"严格限定、"已知问题不豁免"规则、关键判断流程。 +> ⛔ **Read fully and follow: ./classification.md** — 包含所有 FAIL 分类(#1-#30, #99, V1-V5)、"可接受"严格限定、"已知问题不豁免"规则、关键判断流程。 +> 平台专属条目(#17/#18/#19/#21/#23 标记 uni-app,#25-#28 标记 i18n)在分类表中保留编号但指向平台文件。 --- @@ -39,7 +57,8 @@ ## 4. 修复流程 -> ⛔ **Read fully and follow: ./workflows.md** — 包含 AUTO-FIX、AUTO-IMPLEMENT、AUTO-RETRY、AUTO-DATA-FIX、ENV-ADAPT 全部修复模板。 +> ⛔ **Read fully and follow: ./workflows.md** — 包含 AUTO-FIX、AUTO-IMPLEMENT、AUTO-RETRY、AUTO-DATA-FIX、ENV-ADAPT 通用修复模板。 +> ⛔ 平台专属修复模板条件加载: uni-app 项目 → `./platforms/uni-app.md`,i18n 项目 → `./platforms/nextjs-i18n.md`。 --- @@ -140,3 +159,5 @@ AUTO-FIX/AUTO-IMPLEMENT 执行后: | `visual-diff-mode.md` | Phase VD-3 逐页修复 → VISUAL-FIX (V1-V5) 分类引用 classification.md | | `e2e-testing.md` | 步骤 9.5 判定规则 → 引用 classification.md 第 2 节分类标准 | | `iron-laws.md` | 检测即阻断规则 → 引用 classification.md 第 2 节分类标准 + engine.md 第 5 节 GATE-CHECK | +| `auto-fix/platforms/uni-app.md` | uni-app 专属修复模板(#17/#18/#19/#21/#23)→ 由 engine.md 第 0 节条件加载 | +| `auto-fix/platforms/nextjs-i18n.md` | i18n 专属修复模板(#25-#28)→ 由 engine.md 第 0 节条件加载 | diff --git a/.claude/skills/d8d-dev/rules/auto-fix/platforms/nextjs-i18n.md b/.claude/skills/d8d-dev/rules/auto-fix/platforms/nextjs-i18n.md new file mode 100644 index 0000000..99740aa --- /dev/null +++ b/.claude/skills/d8d-dev/rules/auto-fix/platforms/nextjs-i18n.md @@ -0,0 +1,148 @@ +# Next.js i18n 专属 FAIL 分类与修复模板 + +> **⛔ 仅 i18n 项目加载** — 当 `engine.md` 平台检测确认项目使用 Next.js i18n 路由时读取此文件。 +> 此文件包含 FAIL 分类表中 #25-#28 的完整定义和修复模板。 + +--- + +## 1. FAIL 分类(i18n 专属) + +| # | FAIL 特征 | 分类 | 修复类型 | 示例 | +|---|-----------|------|---------|------| +| 25 | i18n 项目页面 URL 缺少 locale 前缀(如应为 /en/xxx 或 /zh/xxx 但实际为 /xxx)| Bug(i18n) | AUTO-FIX | middleware locale 检测逻辑错误 | +| 26 | html lang 属性与 URL locale 不匹配(如 URL 为 /zh/ 但 ``)| Bug(i18n) | AUTO-FIX | layout.tsx 的 lang 属性未从 URL params 读取 | +| 27 | 页面内容语言与 URL locale 不匹配(/zh/ 页面含英文整句,/en/ 页面含中文)| Bug(i18n) | AUTO-FIX | i18n 翻译文件缺失或加载错误 | +| 28 | NEXT_LOCALE Cookie 缺失或值与 URL locale 不匹配 | Bug(i18n) | AUTO-FIX | middleware 或 layout 未正确同步 cookie | + +--- + +## 2. 关键判断流程(i18n 专属) + +- i18n 项目 `browser_evaluate` 检测到 html lang 与 URL locale 不匹配 → Bug(#26) → AUTO-FIX(见下方 AUTO-FIX #26) +- i18n 项目 `browser_evaluate` 检测到 NEXT_LOCALE cookie 缺失或值不匹配 → Bug(#28) → AUTO-FIX(见下方 AUTO-FIX #28) +- i18n 项目 URL 缺少 locale 前缀 → Bug(#25) → AUTO-FIX(检查 middleware locale 检测逻辑) +- i18n 项目页面内容语言与 URL locale 不匹配 → Bug(#27) → AUTO-FIX(检查翻译文件和加载逻辑) + +--- + +## 3. 修复模板 + +### AUTO-FIX #25: URL locale 前缀缺失(max 2 次) + +**适用**: FAIL #25 — i18n 项目页面 URL 缺少 locale 前缀 + +**为什么这是 Bug**: 缺少 locale 前缀会导致 middleware 无法正确检测语言、服务端渲染语言回退、SEO 无法正确索引多语言页面。 + +**检测触发条件**(journey-execution.md 6.5a 子步骤): +- `browser_evaluate` → `window.location.pathname` 不以 `/en/` 或 `/zh/` 等语言前缀开头 +- 例: 应该是 `/zh/dashboard` 但实际是 `/dashboard` + +**流程**: +1. `Read` middleware 文件(通常 `src/middleware.ts`): + - 检查 locale 检测逻辑是否正确 + - 检查重定向规则是否将无前缀 URL 重定向到带 locale 前缀的 URL +2. 修复路径(根据根因选择): + - **middleware 未检测 locale**: 添加 locale 检测逻辑(从 cookie/accept-language/header 读取) + - **middleware 重定向规则缺失**: 添加无前缀 URL → 带前缀 URL 的重定向 + - **默认 locale 未设置**: 在 middleware 中设置默认 locale +3. `Edit` 修复 middleware +4. `Bash` 重启服务 +5. 重测: `browser_navigate` → 确认 URL 自动添加 locale 前缀 + +### AUTO-FIX #26: html lang 属性同步(max 2 次) + +**适用**: FAIL #26 — html lang 属性与 URL locale 不匹配(如 URL 为 /zh/ 但 ``) + +**为什么这是 Bug**: html lang 属性影响屏幕阅读器发音、浏览器翻译提示、SEO 语言信号。lang 属性不匹配会导致辅助功能错误和 SEO 降级。 + +**检测触发条件**(journey-execution.md 6.5b 子步骤): +- `browser_evaluate` → `document.documentElement.lang` 返回值与 URL 中的 locale 不一致 +- 例: URL `/zh/dashboard` 但 `document.documentElement.lang` = "en" + +**流程**: +1. 从 URL 提取当前 locale: `browser_evaluate` → `window.location.pathname.match(/^\/(en|zh)\//)?.[1]` +2. `grep` 搜索 ` **⛔ 仅 uni-app/小程序项目加载** — 当 `engine.md` 平台检测确认项目包含 uni-app 时读取此文件。 +> 此文件包含 FAIL 分类表中 #17/#18/#19/#21/#23 的完整定义和修复模板。 + +--- + +## 1. FAIL 分类(uni-app 专属) + +| # | FAIL 特征 | 分类 | 修复类型 | 示例 | +|---|-----------|------|---------|------| +| 17 | 前端代码 CSS/style 含 `url("data:image/svg+xml")` 或 `url('data:image/svg+xml')`(SVG data URI 充当图标/背景/装饰) | Bug(平台不兼容) | AUTO-IMPLEMENT | 微信小程序不支持 SVG data URI。⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss,将所有 SVG data URI 替换为 Iconify 类名。**禁止用另一种 SVG data URI 替代原来的** | +| 18 | 页面同时有系统导航栏(pages.json navigationBarTitleText 非 custom)和自定义 fixed header,导致双层标题栏 | Bug(双重导航栏) | AUTO-FIX | 截图中顶部出现两层标题(系统导航+自定义头部),参见 ../reference/platform-uni-app.md §3 | +| 19 | Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用),或使用列表级 scroll-view refresher | Bug(UX 不一致) | AUTO-FIX | 首页用 scroll-view refresher,其他 Tab 无刷新,参见 ../reference/platform-uni-app.md §7 | +| 21 | uni-app 页面使用 `onMounted` 获取数据,但该页面是 `uni.navigateBack()` 的返回目标,导致回退时数据不刷新 | Bug(数据陈旧) | AUTO-FIX | 提现后返回收益页余额未刷新(onMounted 不再触发) | +| 23 | uni-app 原生组件在 H5 环境下不渲染/不可交互(``/``/``/``/`` 等)| Bug(平台不兼容) | AUTO-FIX | H5 环境下 `` 不渲染,`` 无法点击。⛔ 必须替换为纯 view + CSS 自定义组件,参见 ../reference/platform-uni-app.md §8 | + +--- + +## 2. 关键判断流程(uni-app 专属) + +- 前端代码含 `data:image/svg+xml`(SVG data URI)→ `grep -rn "data:image/svg+xml" app/mini-program/src/` 扫描所有 `.vue` 文件 → 发现 → 平台不兼容(#17) → AUTO-IMPLEMENT(⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + 替换所有 SVG data URI,**禁止用另一种 SVG data URI 替代原来的**) +- Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用)→ Bug(#19) → AUTO-FIX(参见 ../reference/platform-uni-app.md §7 统一为 onPullDownRefresh) +- uni-app 原生组件 H5 不渲染 → `browser_snapshot` 显示目标区域空白/缺失,`Read` 源码确认使用了 ``/``/``/``/`` 等 uni-app 原生组件 → Bug(#23) → AUTO-FIX(⛔ 替换为纯 view + CSS 自定义组件,参见 ../reference/platform-uni-app.md §8。**禁止标记为"环境限制"跳过修复**) +- `uni.navigateBack()` 返回后 `browser_snapshot` 数据未刷新 + 源码用 `onMounted` 非 `onShow` → Bug(#21) → AUTO-FIX(替换 `onMounted` 为 `onShow`) + +--- + +## 3. 修复模板 + +### AUTO-FIX #17: Emoji → Iconify 替换方案(检测到 emoji 充当功能图标时使用 ⛔ 强制) + +⛔ **完整安装配置、使用方式、禁止方案列表见 ../../reference/platform-uni-app.md 第 1 节。禁止自行发明替代方案。** + +适用条件: icon/btn 元素内含 emoji 字符(如 🔍🔔💬🪪💳📷🏠📱) + +执行步骤: +1. **Read fully and follow: ../../reference/platform-uni-app.md 第 1 节**(⛔ 不可跳过 — 含 Admin/Mini-program 两条路径完整安装步骤) +2. **替换源码**: 将所有 emoji 字符替换为 Iconify 类名(如 🔍 → `class="i-lucide-search"`) +3. **Bash** 安装依赖 + 重新构建 +4. **重测** 验证图标渲染正常 + +⛔ **禁止的替代方案**(完整列表见 ../../reference/platform-uni-app.md 第 1 节"禁止的替代方案") + +### AUTO-FIX #18: 双重导航栏修复(max 1 次) + +**适用**: FAIL #18 — 页面在 pages.json 有 `navigationBarTitleText`(非 custom)且页面源码有 `position:fixed` 的自定义 header + +**为什么这是 Bug**: 系统导航栏与自定义头部同时显示,用户看到两层标题(如"首页"+"NEON PULSE"),浪费屏幕空间且视觉混乱。uni-app 小程序项目应统一使用 `navigationStyle: "custom"`。 + +**流程**: +1. `Read` pages.json → 找到该页面的 style 配置 +2. `Edit` pages.json → 将该页面 style 改为 `{ "navigationStyle": "custom" }`(移除或保留 navigationBarTitleText 均可,custom 模式下不显示) +3. `Read` 页面源码 → 找到自定义 header 的 CSS(通常为 `position: fixed; top: 0` 的元素) +4. `Edit` 页面源码 CSS: + - header 添加 `padding-top: env(safe-area-inset-top, 20px)` + - header height 改为 `calc(原高度 + env(safe-area-inset-top, 20px))` + - content margin-top 与新 header height 一致(含 safe-area) +5. 重测 → 截图确认只有一层标题 + +**修复示例**: +```css +/* 修复前(首页 header 与系统导航栏重叠): */ +.header { position: fixed; top: 0; left: 0; right: 0; height: 64px; } +.content { margin-top: 64px; } + +/* 修复后(统一使用自定义导航栏): */ +.header { + position: fixed; top: 0; left: 0; right: 0; + height: calc(64px + env(safe-area-inset-top, 20px)); + padding-top: env(safe-area-inset-top, 20px); +} +.content { + margin-top: calc(64px + env(safe-area-inset-top, 20px)); +} +``` + +```jsonc +// pages.json 修复: +// 修复前: +{ "path": "pages/home/index", "style": { "navigationBarTitleText": "首页" } } +// 修复后: +{ "path": "pages/home/index", "style": { "navigationStyle": "custom" } } +``` + +### AUTO-FIX #19: 下拉刷新方式统一(max 1 次) + +**适用**: FAIL #19 — Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用) + +**⛔ 详细修复步骤见 ../../reference/platform-uni-app.md §7** + +**流程**: +1. `Edit` pages.json → globalStyle 添加 `"enablePullDownRefresh": true` +2. 对 LIST_LEVEL 页面(scroll-view refresher): + - 移除 scroll-view 的 `refresher-enabled` / `:refresher-triggered` / `@refresherrefresh` + - 移除 `refreshing` ref 和 `onRefresh` 函数 + - 添加 `onPullDownRefresh` 生命周期(from `@dcloudio/uni-app`) + - 数据加载 `finally` 中调用 `uni.stopPullDownRefresh()` +3. 对 NONE 页面:添加 `onPullDownRefresh` + 对应数据刷新逻辑 + `uni.stopPullDownRefresh()` +4. 重测 → 所有 Tab 页面整页下拉刷新效果一致 + +### AUTO-FIX #21: uni-app 页面 onMounted→onShow 替换(max 1 次) + +**适用**: 旅程中 `uni.navigateBack()` 返回目标页面后,页面数据未刷新 + +**为什么这是 Bug**: `onMounted` 仅在组件首次创建时触发。当用户从子页面 `navigateBack()` 返回时,父页面组件仍在导航栈中未被销毁,`onMounted` 不会再次触发。uni-app 的 `onShow` 在每次页面显示时都会触发,是正确的数据刷新时机。 + +**触发条件**(三条件全部满足): +- 条件 A: 旅程中使用 `uni.navigateBack()` 返回到该页面 +- 条件 B: 返回后 `browser_snapshot` 显示数据未更新(如余额、列表数量未变化) +- 条件 C: `Read` 页面源码确认数据获取在 `onMounted` 而非 `onShow` 中 + +**修复流程**: +1. `Read` 页面 `.vue` 文件 +2. 修改 import:将 `import { ref, onMounted } from "vue"` 中 `onMounted` 移除(若 vue 导入不再需要 onMounted) +3. 添加 `import { onShow } from "@dcloudio/uni-app"`(新 import 行) +4. 将 `onMounted(() => { fetchXxx() })` 替换为 `onShow(() => { fetchXxx() })` +5. 参考模式:项目中已有正确使用 `onShow` 的页面(如 profile/index.vue) + +**修复模板**: +```typescript +// 修复前: +import { ref, onMounted } from "vue"; +// ... +onMounted(() => { fetchEarnings(); }); + +// 修复后: +import { ref } from "vue"; +import { onShow } from "@dcloudio/uni-app"; +// ... +onShow(() => { fetchEarnings(); }); +``` + +### AUTO-FIX #23: uni-app 原生组件 H5 跨平台替换(max 1 次) + +**适用**: FAIL #23 — uni-app 原生组件在 H5 环境下不渲染或不可交互 + +**为什么这是 Bug(而非"环境限制")**: uni-app 项目需要同时运行在微信小程序和 H5(测试环境)。任何平台下组件不可用都意味着**跨平台兼容性缺陷**。正确的做法是在代码层面做跨平台适配,而非标记"环境限制"跳过。 + +**检测触发条件**(三条件任一满足): +- 条件 A:`browser_snapshot` 中目标交互区域空白/缺失,但 `Read` 源码确认该区域有 uni-app 原生组件(``/``/``/``/``) +- 条件 B:`browser_click` 在 ``/`` 等元素上失败,`browser_evaluate` 确认 DOM 中该元素存在但渲染尺寸异常(width/height=0 或 display=none) +- 条件 C:`grep` 源码扫描发现使用了已知 H5 不兼容组件,且该组件对用户流程必需 + +**⛔ 禁止行为**: +- ❌ 标记为"环境限制"/"已知问题"跳过修复 +- ❌ 仅在源码添加注释说明"H5 不可用" +- ❌ 用 `browser_evaluate` 绕过不可交互组件后声称"操作完成" +- ❌ 用 `browser_run_code` 直接操作 DOM 状态 + +**流程**: +1. `Read` 页面源码 → 定位原生组件的使用位置和绑定逻辑 +2. 根据 `../../reference/platform-uni-app.md §8` 的替换方案,用纯 view + CSS 自定义组件替换 +3. 保持原有的 `v-model`/`@tap` 逻辑不变(仅替换模板和样式) +4. 重测失败步骤 → PASS 继续 + +**替换模板(checkbox)**: +```vue + + + + + + + +``` +```css +.custom-checkbox { + width: 18px; height: 18px; min-width: 18px; + border-radius: 4px; + border: 1.5px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.05); + display: flex; align-items: center; justify-content: center; + transition: all 0.2s ease; +} +.custom-checkbox.checked { + background: linear-gradient(135deg, #ffb3b5, #ff5167); + border-color: #ff5167; +} +.check-icon { width: 12px; height: 12px; color: #000; } +``` + +**替换模板(radio)**: +```vue + + + + + + + +``` +```css +.custom-radio { + width: 18px; height: 18px; min-width: 18px; + border-radius: 50%; + border: 1.5px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.05); + display: flex; align-items: center; justify-content: center; + transition: all 0.2s ease; +} +.custom-radio.checked { border-color: #ff5167; } +.radio-dot { + width: 10px; height: 10px; border-radius: 50%; + background: linear-gradient(135deg, #ffb3b5, #ff5167); +} +``` + +**替换模板(switch)**: +```vue + + + + + + + +``` +```css +.custom-switch { + width: 44px; height: 24px; border-radius: 12px; + background: rgba(255, 255, 255, 0.1); + position: relative; transition: background 0.2s; +} +.custom-switch.on { background: linear-gradient(135deg, #ffb3b5, #ff5167); } +.switch-thumb { + width: 20px; height: 20px; border-radius: 50%; + background: #fff; position: absolute; top: 2px; left: 2px; + transition: transform 0.2s; +} +.custom-switch.on .switch-thumb { transform: translateX(20px); } +``` diff --git a/.claude/skills/d8d-dev/rules/auto-fix/workflows.md b/.claude/skills/d8d-dev/rules/auto-fix/workflows.md index adc6959..e4f72bb 100644 --- a/.claude/skills/d8d-dev/rules/auto-fix/workflows.md +++ b/.claude/skills/d8d-dev/rules/auto-fix/workflows.md @@ -72,19 +72,12 @@ Playwright 操作返回错误/超时: - ❌ 不重试直接标记 crash 并继续 - ❌ 将业务逻辑错误(Toast/400/500)误分类为 Playwright 崩溃 -**Emoji → Iconify 替换方案(检测到 emoji 充当功能图标时使用 ⛔ 强制)**: +### 平台专属修复模板(⛔ 条件加载) -⛔ **完整安装配置、使用方式、禁止方案列表见 ../reference/platform-uni-app.md 第 1 节。禁止自行发明替代方案。** +以下修复模板已迁移至平台专属文件,仅当 engine.md 平台检测确认对应平台时加载: -适用条件: icon/btn 元素内含 emoji 字符(如 🔍🔔💬🪪💳📷🏠📱) - -执行步骤: -1. **Read fully and follow: ../reference/platform-uni-app.md 第 1 节**(⛔ 不可跳过 — 含 Admin/Mini-program 两条路径完整安装步骤) -2. **替换源码**: 将所有 emoji 字符替换为 Iconify 类名(如 🔍 → `class="i-lucide-search"`) -3. **Bash** 安装依赖 + 重新构建 -4. **重测** 验证图标渲染正常 - -⛔ **禁止的替代方案**(完整列表见 ../reference/platform-uni-app.md 第 1 节"禁止的替代方案") +- **uni-app 项目** → Read fully: `./platforms/uni-app.md`(含 #17 Emoji→Iconify、#18 双重导航栏、#19 下拉刷新、#21 onShow、#23 原生组件) +- **i18n 项目** → Read fully: `./platforms/nextjs-i18n.md`(含 #25 locale 前缀、#26 html lang、#27 内容语言、#28 Cookie 同步) **手机号+验证码登录实现方案(#11 触发时使用)**: @@ -130,63 +123,45 @@ if (res.code) { code.value = res.code; } uni.showToast({ title: "验证码已发送", icon: "success" }); ``` -### AUTO-FIX #18: 双重导航栏修复(max 1 次) +### AUTO-FIX #16: 约束不一致修复(max 1 次) -**适用**: FAIL #18 — 页面在 pages.json 有 `navigationBarTitleText`(非 custom)且页面源码有 `position:fixed` 的自定义 header +**适用**: FAIL #16 — 旅程描述/UI 文案说"可选"但代码实际必填,或 UI 标记"选填"但接口必填 -**为什么这是 Bug**: 系统导航栏与自定义头部同时显示,用户看到两层标题(如"首页"+"NEON PULSE"),浪费屏幕空间且视觉混乱。uni-app 小程序项目应统一使用 `navigationStyle: "custom"`。 +**检测触发条件**(双条件同时满足): +- 条件 1:声明方说"可选"(旅程描述含"可跳过/可选/选填/skip/optional/使用占位图" 或 页面文本含"(选填)/(可选)") +- 条件 2:代码实际"必填"(Toast 含"请上传/请填写/请选择/不能为空" 或 源码有 `if (!field)` 验证) + +**为什么这是问题**: 用户被"可选"承诺误导,但操作时被强制校验拦截。真实用户体验受损。 **流程**: -1. `Read` pages.json → 找到该页面的 style 配置 -2. `Edit` pages.json → 将该页面 style 改为 `{ "navigationStyle": "custom" }`(移除或保留 navigationBarTitleText 均可,custom 模式下不显示) -3. `Read` 页面源码 → 找到自定义 header 的 CSS(通常为 `position: fixed; top: 0` 的元素) -4. `Edit` 页面源码 CSS: - - header 添加 `padding-top: env(safe-area-inset-top, 20px)` - - header height 改为 `calc(原高度 + env(safe-area-inset-top, 20px))` - - content margin-top 与新 header height 一致(含 safe-area) -5. 重测 → 截图确认只有一层标题 +1. `Read` 对应页面源码 → 定位必填验证逻辑(`if (!field)` / `required` / `validate`) +2. 判断正确设计意图: + - PRD/UX 规范说可选 → AUTO-FIX 代码(移除必填验证或改为可选提交) + - PRD/UX 规范说必填 → JOURNEY-FIX 旅程定义(更新描述为必填)+ AUTO-FIX UI 文案(移除"选填"标记) + - 无明确 PRD 依据 → JOURNEY-FIX(旅程定义改为反映代码行为)+ 记录 `constraint_mismatch` 到 pending_fixes +3. 修复后重测该步骤 -**修复示例**: -```css -/* 修复前(首页 header 与系统导航栏重叠): */ -.header { position: fixed; top: 0; left: 0; right: 0; height: 64px; } -.content { margin-top: 64px; } +**代码修复示例**(设计意图=可选时): +```javascript +// 修复前(强制必填): +if (!form.idCardFront || !form.idCardBack || !form.idCardHolding) { + uni.showToast({ title: "请上传所有证件照片", icon: "none" }); + return; +} -/* 修复后(统一使用自定义导航栏): */ -.header { - position: fixed; top: 0; left: 0; right: 0; - height: calc(64px + env(safe-area-inset-top, 20px)); - padding-top: env(safe-area-inset-top, 20px); -} -.content { - margin-top: calc(64px + env(safe-area-inset-top, 20px)); -} +// 修复后(改为可选): +// 移除强制校验,允许不上传照片直接提交 ``` -```jsonc -// pages.json 修复: +**旅程修复示例**(设计意图=必填时): +```markdown // 修复前: -{ "path": "pages/home/index", "style": { "navigationBarTitleText": "首页" } } +9. 用户第二步上传身份证照片(可跳过或使用占位图)→ 点击"下一步" + // 修复后: -{ "path": "pages/home/index", "style": { "navigationStyle": "custom" } } +9. 用户第二步上传身份证照片(必填:身份证正面、反面、手持照片)→ 点击"下一步" ``` -### AUTO-FIX #19: 下拉刷新方式统一(max 1 次) - -**适用**: FAIL #19 — Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用) - -**⛔ 详细修复步骤见 ../reference/platform-uni-app.md §7** - -**流程**: -1. `Edit` pages.json → globalStyle 添加 `"enablePullDownRefresh": true` -2. 对 LIST_LEVEL 页面(scroll-view refresher): - - 移除 scroll-view 的 `refresher-enabled` / `:refresher-triggered` / `@refresherrefresh` - - 移除 `refreshing` ref 和 `onRefresh` 函数 - - 添加 `onPullDownRefresh` 生命周期(from `@dcloudio/uni-app`) - - 数据加载 `finally` 中调用 `uni.stopPullDownRefresh()` -3. 对 NONE 页面:添加 `onPullDownRefresh` + 对应数据刷新逻辑 + `uni.stopPullDownRefresh()` -4. 重测 → 所有 Tab 页面整页下拉刷新效果一致 - ### AUTO-FIX #20: API route try-catch 包裹(max 1 次) **适用**: API endpoint 返回 500,`Read` 源码发现 handler 函数无 try-catch 包裹 @@ -226,177 +201,6 @@ export async function PUT(req, { params }) { } ``` -### AUTO-FIX #21: uni-app 页面 onMounted→onShow 替换(max 1 次) - -**适用**: 旅程中 `uni.navigateBack()` 返回目标页面后,页面数据未刷新 - -**为什么这是 Bug**: `onMounted` 仅在组件首次创建时触发。当用户从子页面 `navigateBack()` 返回时,父页面组件仍在导航栈中未被销毁,`onMounted` 不会再次触发。uni-app 的 `onShow` 在每次页面显示时都会触发,是正确的数据刷新时机。 - -**触发条件**(三条件全部满足): -- 条件 A: 旅程中使用 `uni.navigateBack()` 返回到该页面 -- 条件 B: 返回后 `browser_snapshot` 显示数据未更新(如余额、列表数量未变化) -- 条件 C: `Read` 页面源码确认数据获取在 `onMounted` 而非 `onShow` 中 - -**修复流程**: -1. `Read` 页面 `.vue` 文件 -2. 修改 import:将 `import { ref, onMounted } from "vue"` 中 `onMounted` 移除(若 vue 导入不再需要 onMounted) -3. 添加 `import { onShow } from "@dcloudio/uni-app"`(新 import 行) -4. 将 `onMounted(() => { fetchXxx() })` 替换为 `onShow(() => { fetchXxx() })` -5. 参考模式:项目中已有正确使用 `onShow` 的页面(如 profile/index.vue) - -**修复模板**: -```typescript -// 修复前: -import { ref, onMounted } from "vue"; -// ... -onMounted(() => { fetchEarnings(); }); - -// 修复后: -import { ref } from "vue"; -import { onShow } from "@dcloudio/uni-app"; -// ... -onShow(() => { fetchEarnings(); }); -``` - -### AUTO-FIX #23: uni-app 原生组件 H5 跨平台替换(max 1 次) - -**适用**: FAIL #23 — uni-app 原生组件在 H5 环境下不渲染或不可交互 - -**为什么这是 Bug(而非"环境限制")**: uni-app 项目需要同时运行在微信小程序和 H5(测试环境)。任何平台下组件不可用都意味着**跨平台兼容性缺陷**。正确的做法是在代码层面做跨平台适配,而非标记"环境限制"跳过。 - -**检测触发条件**(三条件任一满足): -- 条件 A:`browser_snapshot` 中目标交互区域空白/缺失,但 `Read` 源码确认该区域有 uni-app 原生组件(``/``/``/``/``) -- 条件 B:`browser_click` 在 ``/`` 等元素上失败,`browser_evaluate` 确认 DOM 中该元素存在但渲染尺寸异常(width/height=0 或 display=none) -- 条件 C:`grep` 源码扫描发现使用了已知 H5 不兼容组件,且该组件对用户流程必需 - -**⛔ 禁止行为**: -- ❌ 标记为"环境限制"/"已知问题"跳过修复 -- ❌ 仅在源码添加注释说明"H5 不可用" -- ❌ 用 `browser_evaluate` 绕过不可交互组件后声称"操作完成" -- ❌ 用 `browser_run_code` 直接操作 DOM 状态 - -**流程**: -1. `Read` 页面源码 → 定位原生组件的使用位置和绑定逻辑 -2. 根据 `../reference/platform-uni-app.md §8` 的替换方案,用纯 view + CSS 自定义组件替换 -3. 保持原有的 `v-model`/`@tap` 逻辑不变(仅替换模板和样式) -4. 重测失败步骤 → PASS 继续 - -**替换模板(checkbox)**: -```vue - - - - - - - -``` -```css -.custom-checkbox { - width: 18px; height: 18px; min-width: 18px; - border-radius: 4px; - border: 1.5px solid rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.05); - display: flex; align-items: center; justify-content: center; - transition: all 0.2s ease; -} -.custom-checkbox.checked { - background: linear-gradient(135deg, #ffb3b5, #ff5167); - border-color: #ff5167; -} -.check-icon { width: 12px; height: 12px; color: #000; } -``` - -**替换模板(radio)**: -```vue - - - - - - - -``` -```css -.custom-radio { - width: 18px; height: 18px; min-width: 18px; - border-radius: 50%; - border: 1.5px solid rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.05); - display: flex; align-items: center; justify-content: center; - transition: all 0.2s ease; -} -.custom-radio.checked { border-color: #ff5167; } -.radio-dot { - width: 10px; height: 10px; border-radius: 50%; - background: linear-gradient(135deg, #ffb3b5, #ff5167); -} -``` - -**替换模板(switch)**: -```vue - - - - - - - -``` -```css -.custom-switch { - width: 44px; height: 24px; border-radius: 12px; - background: rgba(255, 255, 255, 0.1); - position: relative; transition: background 0.2s; -} -.custom-switch.on { background: linear-gradient(135deg, #ffb3b5, #ff5167); } -.switch-thumb { - width: 20px; height: 20px; border-radius: 50%; - background: #fff; position: absolute; top: 2px; left: 2px; - transition: transform 0.2s; -} -.custom-switch.on .switch-thumb { transform: translateX(20px); } -``` - -### AUTO-FIX #16: 约束不一致修复(max 1 次) - -**适用**: FAIL #16 — 旅程描述/UI 文案说"可选"但代码实际必填,或 UI 标记"选填"但接口必填 - -**检测触发条件**(双条件同时满足): -- 条件 1:声明方说"可选"(旅程描述含"可跳过/可选/选填/skip/optional/使用占位图" 或 页面文本含"(选填)/(可选)") -- 条件 2:代码实际"必填"(Toast 含"请上传/请填写/请选择/不能为空" 或 源码有 `if (!field)` 验证) - -**为什么这是问题**: 用户被"可选"承诺误导,但操作时被强制校验拦截。真实用户体验受损。 - -**流程**: -1. `Read` 对应页面源码 → 定位必填验证逻辑(`if (!field)` / `required` / `validate`) -2. 判断正确设计意图: - - PRD/UX 规范说可选 → AUTO-FIX 代码(移除必填验证或改为可选提交) - - PRD/UX 规范说必填 → JOURNEY-FIX 旅程定义(更新描述为必填)+ AUTO-FIX UI 文案(移除"选填"标记) - - 无明确 PRD 依据 → JOURNEY-FIX(旅程定义改为反映代码行为)+ 记录 `constraint_mismatch` 到 pending_fixes -3. 修复后重测该步骤 - -**代码修复示例**(设计意图=可选时): -```javascript -// 修复前(强制必填): -if (!form.idCardFront || !form.idCardBack || !form.idCardHolding) { - uni.showToast({ title: "请上传所有证件照片", icon: "none" }); - return; -} - -// 修复后(改为可选): -// 移除强制校验,允许不上传照片直接提交 -``` - -**旅程修复示例**(设计意图=必填时): -```markdown -// 修复前: -9. 用户第二步上传身份证照片(可跳过或使用占位图)→ 点击"下一步" - -// 修复后: -9. 用户第二步上传身份证照片(必填:身份证正面、反面、手持照片)→ 点击"下一步" -``` - ### AUTO-DATA-FIX(数据状态,max 1 次) **适用**: 测试数据过期、冻结期收益不可用、关联数据缺失 @@ -435,3 +239,64 @@ if (!form.idCardFront || !form.idCardBack || !form.idCardHolding) { - 记录: `{journey, step, fail_reason, fix_type, fix_attempted, fix_result}` - 继续执行下一个步骤(不阻断整条旅程) - 旅程完成后统计: `steps_passed / steps_total` → 决定整体 PASS/FAIL + +### AUTO-FIX #29: 前端数据映射修复(max 2 次) + +**适用**: FAIL #29 — API 返回正确数据但前端显示值不匹配 + +**为什么这是 Bug**: 用户看到的数值与实际数据不一致,影响信任度和功能正确性(如积分余额显示 0 但实际有 3000)。 + +**检测触发条件**: +- `browser_evaluate` 抓取 API 响应值(如 `fetch('/api/v1/subscriptions').then(r=>r.json())`) +- `browser_snapshot` 读取页面显示的值 +- 两者不一致 → FAIL + +**流程**: +1. `browser_evaluate` 抓取 API 实际响应,确认 API 数据正确 +2. `browser_snapshot` 确认页面显示值与 API 不一致 +3. `grep` 搜索显示该数据的组件源码: + - 例: `grep -rn "credits" src/components/ src/app/` + - 例: `grep -rn "Current credits" src/` +4. `Read` 组件源码,定位数据提取逻辑: + - 检查是否从 API 响应的正确字段读取 + - 检查是否在 state 更新前渲染 + - 检查是否有类型转换/默认值覆盖问题 +5. `Edit` 修复数据映射逻辑 +6. 重测: `browser_navigate` → 验证页面显示值与 API 一致 + +**常见根因与修复**: +- API 返回 `{subscription: {credits: 3000}}` 但组件读 `data.credits`(少了一层嵌套)→ 修正字段路径 +- 组件用默认值 0 初始化 state,API 请求未完成时已渲染 → 添加 loading 状态或条件渲染 +- API 响应字段名与组件期望不一致 → 修正字段名映射 + +### AUTO-FIX #30: 静态资源 404 修复(max 2 次) + +**适用**: FAIL #30 — 静态资源(图片/图标/CSS/JS)返回 404 + +**为什么这是 Bug**: 缺失的资源会导致页面显示异常(破碎图标、样式缺失),影响用户体验和专业感。 + +**检测触发条件**(journey-execution.md 第 3 步资源加载状态检查): +- `browser_network_requests(static=true, filter="\\.(jpg|jpeg|png|gif|webp|svg|ico)$")` +- 检测到任何资源的 HTTP 状态码为 404/403 + +**流程**: +1. 从 network 请求中获取 404 资源的 URL 路径 +2. 判断资源类型: + - **应用内资源**(logo、图标等): `grep` 搜索引用该资源的源码文件 + - **第三方/CDN 资源**: 检查 URL 是否正确,是否需要更新 CDN 链接 +3. 修复路径: + - **路径错误**(如 `/sparklogo.png` → 实际在 `/images/sparklogo.png`): `Edit` 修正引用路径 + - **文件缺失**: 检查是否在构建时被排除 → 修复构建配置或补充文件 + - **动态导入路径错误**: 修正 `import` 或 `require` 路径 +4. `Edit` 修复引用路径或补充资源文件 +5. 重测: `browser_navigate` → `browser_network_requests` 确认资源 200 + +**常见修复模式**: +```tsx +// 路径错误修复: +// 修复前: +// 修复后: +// 或使用 Next.js Image: +import sparkLogo from '@/public/images/sparklogo.png'; +Spark +``` diff --git a/.claude/skills/d8d-dev/rules/iron-laws.md b/.claude/skills/d8d-dev/rules/iron-laws.md index 2328936..723b2d9 100644 --- a/.claude/skills/d8d-dev/rules/iron-laws.md +++ b/.claude/skills/d8d-dev/rules/iron-laws.md @@ -239,7 +239,7 @@ Epic N+1 完成 | analyze_image 视觉验证未执行 | pipeline-state 无 `analyze_image_called: true` 或 verification_detail.design_check 缺失 | 阻断 | 必须回退执行 analyze_image 后才能标记完成(见 ./visual-verification.md) | | 双重导航栏 | 页面在 pages.json 中有 `navigationBarTitleText`(非 custom)且页面源码含 `position:fixed` 的自定义 header 元素,导致系统导航栏与自定义头部同时显示(截图中可见两层标题) | Bug(#18) | AUTO-FIX: pages.json 该页面添加 `navigationStyle: "custom"` + 页面 header CSS 添加 safe-area padding(参见 ../reference/platform-uni-app.md §3) | | API route 无 try-catch | API 返回 500 + `Read` 源码发现 handler 函数无 try-catch 包裹(异常直接返回原始 500 且无日志) | Bug(#20) | AUTO-FIX: 添加 try-catch 包裹 + console.error + JSON 500 响应(参见 ./auto-fix/workflows.md #20) | -| 页面 navigateBack 后数据陈旧 | `uni.navigateBack()` 返回目标页后 `browser_snapshot` 数据未更新 + 源码用 `onMounted` 非 `onShow` | Bug(#21) | AUTO-FIX: 替换 `onMounted` 为 `onShow`(from `@dcloudio/uni-app`,参见 ./auto-fix/workflows.md #21) | +| 页面 navigateBack 后数据陈旧 | `uni.navigateBack()` 返回目标页后 `browser_snapshot` 数据未更新 + 源码用 `onMounted` 非 `onShow` | Bug(#21) | AUTO-FIX: 替换 `onMounted` 为 `onShow`(from `@dcloudio/uni-app`,参见 ./auto-fix/platforms/uni-app.md #21) | | 交互元素状态未变化 | browser_click 点击 checkbox/radio/toggle 后 browser_snapshot 确认 checked/selected 状态未改变 | Bug(#22) | AUTO-FIX: 修复事件绑定或状态更新逻辑 | | 按钮永久 disabled | 旅程定义中要求点击的按钮(如 Next/Submit/Publish)处于 disabled 状态且无法通过前置操作解除 | Bug(#23) | AUTO-FIX: 修复 disabled 条件或连接正确的状态源 | | API 成功但 UI 未更新 | curl/API 返回成功但 browser_snapshot + browser_take_screenshot 确认界面无变化(如封面预览未刷新、列表未新增) | Bug(#24) | AUTO-FIX: 添加 refetch/状态同步/乐观更新 | @@ -249,6 +249,19 @@ Epic N+1 完成 | 已修复标记盲信 | 旅程文件交互缺陷审计表标记"✅ 已修复",验证时不重新测试实际行为 | 漏检风险 | ⛔ 必须重新验证修复是否正确(不只是检查 handler 是否存在) | | 漏识别 uni-app 交互 | 交互元素识别只覆盖 React(onClick)不覆盖 uni-app(@tap) | 覆盖漏洞 | 交互元素识别必须同时支持 onClick + @tap/@click/bindtap | +### i18n 检测规则(⛔ 条件激活 — 仅 i18n 项目) + +> **激活条件**:项目存在 `src/i18n/` 目录,或 `next.config.*` 含 `createNextIntlPlugin` / `i18n` 配置,或旅程定义包含 locale 相关断言。单语言项目完全跳过以下所有规则。 + +| 检测项 | FAIL 条件 | 分类 | 修复动作 | +|--------|----------|------|---------| +| URL 缺少 locale 前缀 | 页面 URL 不包含 `/en/` 或 `/zh/`(或项目配置的 locale 列表)前缀 | Bug(#25) | AUTO-FIX: 修复导航链接/middleware 重定向添加 locale 前缀 | +| 导航跳转丢失 locale | 从 `/zh/xxx` 页面点击导航后 URL 变为 `/yyy`(无 locale)或 locale 切换 | Bug(#25) | AUTO-FIX: 修复 Link/router.push 添加 locale 前缀 | +| html lang 不一致 | `document.documentElement.lang` 与 URL 路径中的 locale 不匹配(如 `/zh/` 但 `lang="en"`) | Bug(#26) | AUTO-FIX: 修复 layout.tsx 的 html lang 属性绑定 | +| 内容语言不一致 | URL 含 `/zh/` 但页面可见文本包含英文整句(非专有名词),或 URL 含 `/en/` 但包含中文字符 | Bug(#27) | AUTO-FIX: 替换硬编码文本为翻译键 | +| locale cookie 缺失 | 页面加载后 `document.cookie` 不包含 `NEXT_LOCALE`(或项目配置的 cookie 名) | Bug(#28) | AUTO-FIX: 在 middleware/layout 中设置 locale cookie | +| locale cookie 值不一致 | `NEXT_LOCALE` cookie 值与 URL 路径 locale 不一致 | Bug(#28) | AUTO-FIX: 修复 cookie 设置时机和值来源 | + > 完整的 FAIL 分类标准(含兜底规则 #99)见 ./auto-fix/classification.md,修复流程见 ./auto-fix/workflows.md **违反此规则的行为**: diff --git a/.claude/skills/d8d-dev/rules/visual-verification.md b/.claude/skills/d8d-dev/rules/visual-verification.md index b61994b..fa76f5f 100644 --- a/.claude/skills/d8d-dev/rules/visual-verification.md +++ b/.claude/skills/d8d-dev/rules/visual-verification.md @@ -94,6 +94,17 @@ 3. **英文枚举检测**: - 页面可见文本中无英文大写枚举值(如 APPROVED/REJECTED/PENDING/PAID 等) +3.8. **i18n 语言一致性检测**(⛔ 条件激活 — 仅 i18n 项目): + - 前置检测:`glob src/i18n/**/*` 或 `next.config.*` 中 i18n 配置存在 → 启用 + - 项目无 i18n 配置 → 跳过以下所有检查 + - `browser_evaluate` → `window.location.pathname` → 提取 locale(如 `/zh/` → `zh`) + - `browser_evaluate` → `document.documentElement.lang` → 验证与 URL locale 一致 + - `browser_evaluate` → `document.body.innerText.substring(0, 2000)` → 语言检测: + - URL `/zh/` → 文本应包含中文(Unicode `\u4e00-\u9fff`),不应有英文整句 + - URL `/en/` → 文本应为英文,不应有中文字符 + - `browser_evaluate` → `document.cookie` → 验证包含 NEXT_LOCALE + - FAIL → 按检测即阻断表 i18n 规则处理(#25-#28) + 3.5. **SVG data URI 源码扫描**(⛔ 强制 — 每次视觉验证关卡必须执行): - `grep -rn "data:image/svg+xml" app/mini-program/src/`(或当前小程序源码目录) - 发现任何匹配 → 标记 svg_data_uri_check.found = FOUND → 记录文件列表 @@ -225,6 +236,19 @@ verification_detail: status: 403/404 type: "image/jpeg" details: "描述" + i18n_check: # ⛔ 条件激活 — 仅 i18n 项目 + enabled: true/false # 项目是否有 i18n 配置 + locale_cookie_name: "NEXT_LOCALE" # 项目配置的 locale cookie 名 + supported_locales: ["en", "zh"] # 项目支持的 locale 列表 + url_locale: "zh" # 从 URL 提取的 locale + html_lang: "zh" # document.documentElement.lang + lang_match: true/false # html lang 与 URL locale 是否一致 + content_language_match: true/false # 页面内容语言与 URL locale 是否一致 + content_mismatch_samples: [] # 不匹配的文本样本 + locale_cookie_present: true/false + locale_cookie_value: "zh" + cookie_match: true/false # cookie 值与 URL locale 是否一致 + details: "描述" ``` **⛔ 步骤级关联**:上述 verification_detail 中发现的每个问题,必须同时记录到对应步骤的 `step_results[].issues_detected` 中(含 `step_id` 关联),确保问题可追溯到具体操作步骤: -- 2.49.1