- docs/acceptance/qa-tracker.md:跨周期缺陷单一真源台账(§7=第五轮)。 - 周期 1/2 + iPhone SE/axe 验收证据目录、ACCEPTANCE_REVIEW 快照入库。 - 同步 README/CONTEXT/CLAUDE/DESIGN/testing/deployment/lessons-learned 口径; next.config/Dockerfile/nginx/Jenkinsfile/docker-compose/sentry/prisma 对齐 standalone 产物装配与部署形态。
49 KiB
QA 问题跟踪台账(系统性质量保障 · 单一真源)
- 分支:
refactor/optimize-uiHEAD:f543e47(本台账所引用的全部修复仍只存在于工作树,未提交) - 工作树状态(2026-09-25 实测):
git status --porcelain= 363 条(47 条未跟踪) - 上游报告:
- 周期 1:
docs/acceptance/2026-09-23-gates/final-verdict.md(发现 N-1…N-33;对 2026-09-21 阻断 A-/R-/B-* 的闭环) - 周期 2:
docs/acceptance/2026-09-23-cycle2/final-verdict.md(新增表现层/领域层轴:A-1…A-4、C-1、P-*、D-1(defect)、L-1…L-5、Sentry 接线;决策清单 D-1…D-11)
- 周期 1:
- 本台账的作用:把上述分散编号统一到一处,作为「问题跟踪机制」的执行面;不重复两份报告的证据叙述,只登记状态 + 归属阶段 + 闭合阻塞项。
- 编号消歧:周期 2 的缺陷
D-1(detail-trust-section)与决策清单D-1(入库)同名,本台账分别记为 DEF-D1(缺陷)与 AUTH-D1(决策/授权项)。
0. 验收判定(对齐 objective 五阶段)
| objective 阶段 | 交付物 | 状态 |
|---|---|---|
| 1 代码审查 | 两份周期报告 + 本台账 §1 | 已达成(多轴、subagent + 主线逐条复核) |
| 2 测试 | 门禁实测表(周期 1 §1 / 周期 2 §0)+ docs/testing.md |
已达成(可自主执行的门禁全部出数;写库/基线/线上/全量变异等需授权项见 §3) |
| 3 修复 + 跟踪机制 | 本台账(单一真源) | 机制已建立;修复的可复现性未达成(未入库) |
| 4 优化 | 性能(Lighthouse 9/9=100、a11y 节点计数、限流键、死资源清理)+ 安全(提权/XSS/越权/令牌/开放重定向) | 已达成,余留项见 §2/§3 |
| 5 验收确认 | 本报告 + 门禁凭据 | 未达「可判验收通过」——唯一阻断为交付形态(AUTH-D1 未入库),非代码质量 |
结论不变(承接周期 1「有条件通过」/周期 2「未达可判验收通过」):技术面已闭环并有实测凭据;验收不能判完成的唯一实质原因是 §3 AUTH-D1(约 363 条改动未提交,评审者/CI/下一位开发者无法复现本台账任何结论)。
2026-09-28 更新:第三/四/五轮(§4/§5/§7)在此结论上继续累积修复(第五轮再修 S-21/S-22/Q-10②/S-3 两个 P2 + 12 个 P3,全带判别测试与门禁凭据),未提交的工作树现 394 条——AUTH-D1 仍是唯一硬阻断。
1. 本台账建立时的独立复核(trust-but-verify,2026-09-25)
对最高后果的 12 项「已修」声明做了只读源码复核(读当前工作树,未采信报告自述),结论:
| ID | 声明 | 复核结果 | 证据(本次亲读) |
|---|---|---|---|
| N-18 | draft/disable 开放重定向防护 | CONFIRMED | src/lib/cms/internal-redirect.ts:8;disable/route.ts:13-21 先校验后副作用 |
| N-19 | admin/stats 越权读取 | CONFIRMED | src/app/api/admin/stats/route.ts:11 用 requirePermission('content-model','read') |
| N-20 | 停用账号令牌仍可用 | CONFIRMED | src/lib/permissions.ts:41-45 读 User.status,早于 :52 的 super_admin 短路 |
| N-25 | 限流 XFF 伪造绕过 | CONFIRMED | src/lib/client-ip.ts:14-23 优先 x-real-ip、XFF 取最右;contact/route.ts:4,51 消费 |
| N-22 | 安全头门禁恒不成立 | CONFIRMED | check-security-headers.ts:63-78,101,128-132 cookie 状态按 flag 推导并计入退出码;缺 CSP=fail;HSTS 分 http/https |
| N-1 | JSON-LD </script> 注入 |
CONFIRMED | src/components/seo/structured-data.tsx:8-9 jsonLd() 转义 </>,8 个 sink 全用 |
| DEF-A1..A4 | 后台编辑器定位/守卫 | CONFIRMED | admin/content/[modelCode]/[itemId]/page.tsx id 过滤 + itemNotFound(:691) + canEdit(:252,736,917) 收口 |
| C-1 | 案例页缺键整页崩溃 | CONFIRMED(边界归一) | 归一在唯一 importer (marketing)/cases/[slug]/client.tsx:22,30-35,恰在未受信任 CMS 强转处;报告所称组件文件为归属笔误,非漏洞(见 §2.2 REG-C1) |
| L-5 | 媒体派生命名冲突 | CONFIRMED | src/lib/media/image-processor.ts:75 追加 Math.random() 后缀 |
| L-1 | admin-api 吞掉 4xx/5xx | CONFIRMED | src/lib/admin-api.ts:85-102 解密后按 res.ok 抛错 |
| N-4/5/6 | 保存/发布状态通道 | CONFIRMED | 保存载荷无 status;发布走 runWorkflow;POST 拒非 draft |
| DEF-D1 | 指标前导符号错位 | CONFIRMED | detail-trust-section.tsx:26-48 用 [^0-9.] 否定类 + prefix/suffix 分列 |
Sentry 接线亦复核为真:
src/instrumentation.ts、src/instrumentation-client.ts存在且 import@sentry/nextjs;next.config.mjs:131export default withSentryConfig(nextConfig)。 覆盖扫描(非定点复核):src/+scripts/内无条件status:'pass' as const= 0;\|\| echo= 12(均在 CI/部署 shell 的信息性回退,非门禁判据);e2e/*.spec.ts内if (.count()>0)守卫 = 11 处(周期 1/2 已逐条定性为视口/角色合法分支,见 N-27)。
2. 缺陷登记(按闭合状态分组)
图例 · 状态:FIXED-UNCOMMITTED=已修且§1或原报告有 RED/GREEN 凭据,但未入库;DECISION=待产品/设计裁定;AUTH=待单独授权动作;RECORDED=已知、诚实披露、本轮不动。
2.1 已修复(代码在工作树内,含 §1 复核标记)
| ID | 级别 | 类别 | 摘要 | 状态 | 凭据 |
|---|---|---|---|---|---|
| N-18/19/20/25 | P1 | 安全 | 开放重定向 / 越权读取 / 停用账号令牌 / 限流绕过 | FIXED-UNCOMMITTED | §1 全部 CONFIRMED + 原报告 RED 实测 |
| N-22 | P1 | 门禁可信度 | 安全头门禁恒不成立断言 | FIXED-UNCOMMITTED | §1 CONFIRMED + 双向对照 |
| N-1 | P1 | 安全 | JSON-LD 存储型 XSS 面 | FIXED-UNCOMMITTED | §1 CONFIRMED |
| N-4/5/6/7 | P0/P1 | 功能 | 后台保存必 400 / 发布必失效 / 状态死控件 / 状态回写 | FIXED-UNCOMMITTED | page.test.tsx |
| N-9 | P0 | 门禁可信度 | standalone 产物缺 dist/static(CSS 404) |
FIXED-UNCOMMITTED | postbuild 落地 |
| N-11 | P2 | 性能/BP | /contact CSP 违规(Zod eval 探测) |
FIXED-UNCOMMITTED | z.config({jitless}) + Lighthouse A/B |
| N-12 | P1 | 门禁可信度 | 补测自身把 rAF 变只读致套件静默不跑 | FIXED-UNCOMMITTED | 隔离复跑 |
| N-17 | P2 | 测试诚实性 | security-headers 裸 return 伪装通过 |
FIXED-UNCOMMITTED | 68 passed + 4 skipped |
| N-24②③④ | P2 | 门禁可信度 | 404 即就绪 / 不受支持的 next start / pr-checklist 空转 | FIXED-UNCOMMITTED | 双向对照 |
| N-27(部分) | P2 | 测试装配 | 零断言用例 / expect()||expect() JS bug / 死选择器 |
FIXED-UNCOMMITTED | 四 project 实测 |
| N-30(删兜底) | P1 | 内容零编造 | 两处凭空 40% 兜底已删(含反转被锁死的测试) |
FIXED-UNCOMMITTED | 23→23 通过 |
| N-31 / N-33(部分) | P2 | 文档一致性 | 不可执行命令 / 假阈值 / 口径回填 | FIXED-UNCOMMITTED | 机械比对 |
| A-1..A-15, R-1..R-5, B-1/3/4/7 | P0-P2 | 安全/功能/规范/UI | 2026-09-21 阻断项 | FIXED-UNCOMMITTED | 周期 1 §2 |
| DEF-A1..A4, C-1, P-1/4/5, DEF-D1, L-1, L-5 | P1-P2 | 表现/领域层 | 后台编辑器数据损毁 / 案例页崩溃 / 滑动 / 进度条 / 前导符号 / 错误吞没 / 命名冲突 | FIXED-UNCOMMITTED | 周期 2 §1;C-1 见 §2.2 REG-C1 |
| Sentry 接线 | P1 | 可观测性 | 「声明未接线」→ 已接(含 Turbopack 约定文件) | FIXED-UNCOMMITTED | 周期 2 §1.3;§1 复核存在 |
| N-24① | P2 | 门禁可信度 | axe 逐页状态断言(期望集感知) | FIXED-UNCOMMITTED | 周期 2 §3.7 双向对照 |
2.2 待产品 / 设计决策(代码侧不该替业务作答)
| ID | 级别 | 事项 | 入口 |
|---|---|---|---|
| REG-C1 | P3(已复核,非活跃缺陷) | C-1 归一在 cases/[slug]/client.tsx:22,30-35(唯一 importer),位置正确:恰在未受信任的 CMS 强转 item.data as unknown as CaseStudyData 边界处 ?? ''/?? [] 一次归一,case-detail-page.tsx 消费已定型 props 可信任(无第二个真实调用方)。周期 2 报告把它记在组件文件属归属笔误,非漏洞;不在组件内加冗余守卫(违反"不为不可能发生的场景加校验") |
仅更正周期 2 §1.2 的 file:line 归属 |
| DEC-N21 | P2→潜伏 | 'content-item' 幻影 modelCode:周期 2 §5.3 以源码推翻「越权」定性,实为 fail-closed;真风险是将来被补进种子即成跨模型读取闸口。建议「缺 modelCode ⇒ 400」 |
admin/items/route.ts:79 |
| DEC-N21b | P2 | /admin 无服务端 gating(仅客户端跳转),HTML 外壳无会话也下发;数据 API 已守卫 |
无 middleware.ts |
| DEC-N30 | P1 | 四条互斥 SLA(工作日 2h / <4h / 48h / <2h·7×24)留哪一条 | 周期 1 §8.2 / 周期 2 §5.2 |
| DEC-D5 | P1 | /products/erp-upgrade 在线数字(99.2%/40%+/从5天到1天)在活组件 erp-upgrade-content-v2.tsx:63,69,77,无 basis 无 MetricsBasisNote,且该路由无视觉基线 |
周期 2 §5.1 |
| DEC-D7 | P2 | news 正文字段声明 richtext 而 renderField 无该分支(能力缺口) |
周期 2 §5.3 |
| DEC-P2-motion | P2 | 动效门禁 23 处判红(20 缓动曲线 + 2 裸时长 + --transition-gentle:1000ms):改代码 or 承认为设计例外 |
周期 2 §3.5 |
| DEC-L3 | P3 | client-ip.ts 头部信任无条件;直暴露(非 nginx)时限流可被 X-Real-IP 自报绕过(须与部署协同,勿单独改) |
周期 2 §5.3 |
| DEC-L4 | P3 | 对称加密密钥来自公开 bundle 的 NEXT_PUBLIC_ENCRYPTION_SECRET ⇒ 仅混淆无机密性,文档口径不得当安全控制引用 |
crypto.ts:36 |
| DEC-P-2 | P3 | 死码族删除(loading-state 是 brand-token 唯一白名单豁免 N-8;另有 hero-section-v2/insight-card/industry-grid/三份 product-card/breadcrumb/micro-interactions/PullToRefresh 等) |
周期 2 §5.3 |
| DEC-N4-N23 | P1 | 安全头门禁默认打线上 novalon.cn 而非本分支产物(声明与行为相反);改法涉 CI/test:all 语义 |
周期 1 N-23 |
2.3 待授权动作(阻塞验收;须逐项明文批准)
| ID | 动作 | 命令 | 为何阻塞 |
|---|---|---|---|
| AUTH-D1 | 入库 / PR(验收唯一硬阻断) | 先 git fetch origin dev 取真值 → 裁定双向分叉(周期 1 记 30↔31,两端同标题异 hash)重复提交对 → 按 5 组分笔提交 → Gitea PR(过 check-pr-checklist.sh) |
约 363 条改动只在工作树 ⇒ 本台账/两份报告结论均不可复现。⚠ 分叉非快进整理,须先 fetch 再判 |
| AUTH-D2 | 重跑 seed | npm run db:seed |
使 L-2(seed 权限过期)修复对数据生效;现网 dev.db 仍为旧权限行 |
| AUTH-D3 | 视觉基线写入 | npx playwright test visual-regression.spec.ts --update-snapshots |
20 例缺参照恒红 + erp-upgrade/about/brand/cases/[slug] 三路由从无基线;追认现状需人工核看 85 张差异 |
| AUTH-D4 | 写库 E2E | 完整 npm run test / test:e2e:prod(差 cms-workflow+user-journey 共 11 例) |
真写 prisma/dev.db;只读边界已实证,此 11 例是唯一未执行的功能层 |
| AUTH-D9 | Sentry 实投递 | 真实 DSN 起服务验证事件与 source map 上传 | 接线惰性有码路证据已足,但投递未线上验证 |
| AUTH-D10 | 全量变异测试 | 去 --inPlace(config 默认沙箱)跑 mutate 全集 |
成本/磁盘(峰值 1.7G 曾致 ENOSPC,周期 2 §3.9);须 ≥2G 余量与无并发门禁窗口 |
| AUTH-D11 | 门禁证据落盘 | check:axe 默认 OUT 按日期分目录(同步 Jenkinsfile);docs/acceptance/** 是否纳入版本控制 |
现固定目录互相覆盖证据且未跟踪(周期 2 §3.9) |
2.4 已知不稳定清单(禁止用 retry / 放宽阈值「修」)
mobile-user-journeysUJ-10(firefox,隔离复跑通过)、website-acceptance:111响应式(隔离通过)、@accessibilitywebkit 四例(周期 1 N-32,隔离不复现)。同属并发 / 墙钟族(周期 1 N-28:同一代码三轮失败数 8→1→5)。墙钟型用例(p4:31、p4:68)不该进共享并发门禁。
3. 提交前强制提醒(承接 memory 纪律)
- 提交只加自己的路径,勿
git add -A:prisma/dev.db-shm/-wal未被.gitignore覆盖(周期 1 §7),git add -A会把 SQLite 边车与陈旧dev.db/data.db(周期 2 §3.9)扫入。 - 破坏性 / 共享状态动作(fetch/rebase/commit/push/merge/seed/写库 E2E/写基线/force-push)须单独明文授权,不夹带在常规变更里。
- 会话结束前披露工作树状态(本台账 §0/§2.3 AUTH-D1 即披露)。
4. 第三轮审查轴(2026-09-25 本会话,subagent 并行 + 主线逐条复核)
两条并行 subagent:① 全部 src/app/api/** 路由处理器安全/正确性审计;② src/lib/**+src/components/** 逻辑缺陷狩猎。均要求 CONFIRMED 附 file:line + 触发条件,并给出已知项围栏以防重报。主线未照抄任何一条子代理结论:定点复核 → 对拟修复项做 RED→GREEN。
4.1 主线已复核确认 + 已修 + RED 测试(工作树内,未提交)
| ID | 级别 | 缺陷 | 触发/影响 | 修复 | RED→GREEN 凭据 |
|---|---|---|---|---|---|
| Q-1 | P1 | 登出未清除服务端 httpOnly cookie:auth-context.tsx logout() 只清 localStorage;但 login 经 setTokenCookie 写 novalon_token(24h)/novalon_refresh(7d),而 authenticateRequest(auth.ts:74) 回退读 cookie。/api/auth/logout 路由存在却从无调用者 ⇒ 点「登出」后全站受守卫 API 仍凭 cookie 放行至多 24h/7d |
后台「登出」是假登出;共享/被盗机器上会话延续 | logout() 先 POST /api/auth/logout(触发 clearTokenCookie),网络失败也清本地并跳转 |
新增 auth-context.test.tsx(2 例):断言 fetch 到 /api/auth/logout+POST 与网络失败仍清理;改前 1 failed(fetch 0 calls)→ 改后 2 passed |
| Q-2 | P2 | 缩略图格式伪标:image-processor.ts thumbnail 声明 ext 'webp'(buildDerivative(..., 'webp', ...))但缓冲只 .resize().toBuffer() 未调 .webp() ⇒ PNG/JPEG 上传的 -thumbnail.webp 实为原格式字节,Content-Type 与内容不符、零 WebP 压缩收益 |
落盘文件内容 ≠ 扩展名/MIME;source type 消费方受影响 |
缩略图管道加 .webp({quality:80}) |
既有测试只断路径/尺寸、从不验格式(覆盖空洞);新增例断 sharp(thumb.buffer).metadata().format==='webp'(PNG/JPEG 双输入);改前 Received:"png" → 改后 media 4 套件/60 例全通过 |
| Q-3 | P2 | 登录端点无限流:contact 有 IP 限流但 POST /api/auth/login 无任何节流 ⇒ 匿名者可无限驱动 cost-10 bcrypt(CPU DoS)+ 离线式撞库 |
凭据填充 / CPU 耗尽 | 抽出可复用 src/lib/rate-limit.ts(时钟可注入 + reset(),放进覆盖率棘轮内),登录按 IP max:10/15min 且判定在 bcrypt 之前;contact 重构复用同一实现(消除副本) |
新增 rate-limit.test.ts 4 例(边界/独立 key/窗口过期/reset)+ 扩展 login/route.test.ts 一例:前 10 次 200、第 11 次 429 且 verifyPassword 仍只 10 次(证限流省掉 bcrypt);4/4 + 全 login/contact/auth 73 例通过,type-check EXIT=0,改动文件 eslint --quiet 0 error |
| Q-4 | P2 | 停用账号令牌在 6 个内联 role handler 仍全权(N-20 同类,绕开已收口的 checkUserPermission):admin/users GET/POST/PUT/DELETE 与 admin/roles GET/PUT 用 authenticateRequest(仅验签名,不看 User.status)+ 内联 userRole 检查。⇒ 被停用但持 ≤24h 有效令牌的 super_admin 仍能列/建/改/删用户、重写权限矩阵 |
停用/离职管理员的残留令牌保留完整后台写权 | 抽出可复用 authenticateActiveRequest(request)(permissions.ts,prisma.user.count({id,status:1}) 判活),6 个 handler 全部改用它并在角色检查/任何写入前 fail-closed |
在 users/route.test.ts 与 roles/route.test.ts 各加 2 例(停用超管 → 401 且 user.create/user.update/$transaction/permission.* 均未触达);既有 30+ 例在默认存活下保持通过 ⇒ admin users/roles/permissions 3 套件/54 例全绿,type-check EXIT=0,5 改动文件 eslint --quiet 0 error |
| Q-5 | P2 | 响应式网格被 StaggerReveal 单子吞成单列:content/sections.tsx 的 TechStackShowcase/DataProofSection 把 grid md:grid-cols-N 挂在外层 div,而该 div 的唯一子元素是 StaggerReveal(渲染一个 motion.div,再为每卡片包 motion.div)⇒ 网格只有 1 个网格项,卡片在 md/lg 断点堆成单列、gap/space-y 失效;FAQSection 的 space-y-3 同理被吞。这些组件是活的(product-detail-content-v3.tsx:335,343,357 渲染产品详情页)。正确写法见 l3-signals-slot.tsx:109-119(网格类挂 StaggerReveal,其注释即记此坑) |
产品详情页技术栈/数据佐证/FAQ 桌面端单列,UX 退化 | 把网格/间距类移到 StaggerReveal className,删除多余外层 grid div(FAQ 仅移 space-y-3,保留 col-span 外层) |
在 sections.test.tsx 加 3 例,断言 [class*="grid-cols-3"]/grid-cols-4/space-y-3 元素的直接子元素数 == 条目数(旧结构下恒为 1 ⇒ RED);sections.test 28 例全通过,type-check EXIT=0,eslint --quiet 0 error |
| Q-7 | P3 | 列表路由分页参数未校验(NaN→500 + 无界 take):admin/items GET 用 parseInt(searchParams.get('page') || '1') ⇒ ?page=abc→NaN→skip:NaN→真库 Prisma 抛错→500(应安全回落);?pageSize=99999→无界 take(任何 read 权限者可整表拉取大 data JSON)。admin/users GET 有 Math.max/min 夹取但 Math.max(1,NaN) 仍是 NaN ⇒ 同类未愈 |
500 崩溃 + 无界读取 | 抽出可复用 src/lib/pagination.ts parsePagination(sp,{defaultPageSize,maxPageSize})(NaN/0/负数→默认,超上限→夹取,取整),items + users 两路都接入(max 100) |
pagination.test.ts 6 例(缺省/合法/非数字回退/夹取/取整,含"移除即崩"的核心 NaN 面)+ items/route.test.ts 1 例(?page=abc&pageSize=99999 ⇒ 200 且 findMany({skip:0,take:100}),旧码为 skip:NaN/take:NaN ⇒ RED)。pagination 6 + items 38 + users 18 全通过,type-check EXIT=0,改动文件 eslint --quiet 0 error |
| Q-8 | P3 | 内容 ID 存在性枚举 oracle:admin/items PUT/DELETE 与 admin/items/[id]/workflow POST 都先 contentItem.findUnique→notFound('内容不存在') 再 requirePermission ⇒ 匿名调用者可用「404=该 ID 存在 vs 401/403=权限屏障」枚举有效内容 ID(无写入先于鉴权,纯信息泄露面) |
未鉴权可枚举内容 ID | 三个 handler 均在存在性探针之前加 if (!authenticateRequest(request)) return unauthorized();(先认证→再 404→再 per-model 权限),关闭匿名枚举;记残留:已登录但无该模型权限者仍能分辨 404/403(把 notFound 与 forbidden 合流需改契约,未做) |
items 2 例 + workflow 1 例:匿名 PUT/DELETE/workflow ⇒ 401 且 findUnique 未被调用(证探针在认证门之后);两测试文件补 jest.mock('@/lib/auth') 默认有效会话使既有授权用例不受影响 ⇒ items+workflow+pagination 3 套件/47 例全通过,type-check EXIT=0,5 改动文件 eslint --quiet 0 error |
| Q-10a | P3 | ui/button.tsx 涟漪 id 用 Date.now():同毫秒两次点击 ⇒ 重复 React key + 清理 filter(r=>r.id!==id) 会一次删掉同 id 的多条涟漪;且 600ms setTimeout 卸载时从不清除(setState-after-unmount / 定时器泄漏)。Button 是全站在用组件 |
真实浏览器下重复 key 告警 + 卸载后定时器泄漏 | 涟漪 id 改自增 useRef 计数器(保证唯一);定时器收入 useRef 数组,useEffect 卸载时 clearTimeout 全部 |
诚实标注:真正判别性(重复 key)在本环境不可测——做过 RED 探针(还原 Date.now() 后断言仍绿),因 Jest 跑 production React 构建、dev key 告警被剥离,React 18 也不再就 setState-after-unmount 告警。故只补被改代码路径的行为用例(点击→涟漪出现→600ms→清除,能捕获"永不清除/不出现"类回归),不谎称它是缺陷锁。button 21 例通过,type-check EXIT=0,eslint --quiet 0 error |
| Q-10c | P3(隐私/同意 fail-open) | analytics.ts getStoredPreferences 把 JSON.parse(...) as CookiePreferences 原样透传,无字段校验:而 updateConsentDetailed 用 preferences.analytics ? 'granted':'denied' 判定 ⇒ 被篡改/写坏的 localStorage(如 {"analytics":"yes","marketing":1},或数组/数字等非对象)会以真值非布尔意外授予 analytics/ad 同意 |
同意管理可被污染存储绕过(GDPR/隐私面) | 新增 sanitizePreferences(raw):四个开关严格 === true 才算同意(否则 false),necessary 恒真,timestamp 仅数字保留;getStoredPreferences 走它 |
2 例真判别(未修即 RED:旧码原样返回 'yes'/1,断言 false 必红):truthy 非布尔→false、非对象载荷→全 false 且 necessary 恒真;既有有效对象用例({necessary:true,analytics:false,marketing:true,functionality:true})经 toEqual 仍通过 ⇒ analytics 31 例全通过,type-check EXIT=0,eslint --quiet 0 error |
| Q-10b | P3 | api-crypto.ts 用客户端 content-length 判定是否有请求体:hasBody = contentLength && parseInt(contentLength) > 0。加密的 POST/PUT/PATCH 若走分块传输(无 content-length 头,或该头被省略/伪造为 0)⇒ 被判「无 body」跳过解密,密文 {data:"..."} 原样交给 handler(isEncrypted:true 但体未解),handler .json() 拿到信封而非真实字段 |
加密通道在分块/缺头时静默不解密(功能正确性/健壮性) | 改按 HTTP 方法判体存在:method ∈ {POST,PUT,PATCH} 即尝试读体+解密(空体自然落回原路径),不再信 content-length |
新 1 例真判别(RED 探针实测:还原 content-length 版 ⇒ 该例判红;恢复 ⇒ 通过):加密 POST 无 content-length ⇒ 断言 mockDecrypt 被调用且 handler 收到解密体;既有 26 例(全部 method+content-length 配对)不受影响 ⇒ api-crypto 27 例通过,type-check EXIT=0,eslint --quiet 0 error |
| Q-9 | P3 | admin/media 多文件上传无原子性:上传循环内 ① 某文件超 10MB 直接 return validationError,但前面文件已 uploadMedia 落盘+入库;② 某文件在 uploadMedia 内被 magic-byte/类型策略拒(抛 UploadPolicyError,外层 catch 返 400)同样留下前面已写入的文件 ⇒ 请求返回错误却已产生孤儿媒体资产(磁盘 + MediaAsset 行),无返回、无清理路径 |
半途失败留下孤儿资产(磁盘/DB 泄漏 + 计费/存储面) | 把尺寸校验提到任何写入之前(整批预检);上传循环包 try,失败时对本请求已成功的 results 逐个 deleteMedia 回滚再返回错误 |
新 2 例真判别(旧码必 RED):① 第二个文件抛 UploadPolicyError ⇒ 断言 mockDeleteMedia('asset-1') 被调用(旧码无回滚 ⇒ 不调用);② 批次含一个超限文件 ⇒ 断言 mockUploadMedia 从未被调用(预检在任何写入前拒整批)。既有单/多文件成功用例仍通过 ⇒ media route 14 例通过,type-check EXIT=0,eslint --quiet 0 error |
4.2 子代理上报、主线尚未逐一复核(登记为待验证,不当作已确认,不据此宣称已修)
| ID | 级别 | 摘要 | 位置(子代理所引) | 主线状态 |
|---|---|---|---|---|
| Q-4 | — | 已复核确认为真缺陷并已修(见 §4.1 Q-4):status 维度缺位属实,role 授予逻辑本身仍正确 | ||
| Q-5 | content/sections.tsx |
已复核确认为真缺陷并已修(见 §4.1 Q-5):3 处组件为活代码(产品详情页),已把网格类挂回 StaggerReveal 并加 3 例结构断言 | ||
| Q-10 | P3 | 四条子代理上报:① api-crypto.ts hasBody 信任客户端 content-length;② workflow.ts:84 updateMany+auditLog.create 非 $transaction;③ analytics.ts:33 同意偏好无字段校验;④ button.tsx ripple Date.now() 键碰撞 + 卸载不清 timeout |
各处 | ④=Q-10a、③=Q-10c、①=Q-10b 均已修(见 §4.1,各带判别测试)。仅 ② 未做:修法需 interactive $transaction(async tx=>…)(update→读回→审计有数据依赖),会牵动 workflow 路由测试的 prisma mock 边界,风险高于其 P3 权重,暂缓并附精确配方 |
| Q-6 | P2 | detail-product-value.tsx:73-91 render 体内 setDisplayValue + requestAnimationFrame(StrictMode/重渲染派生多串未取消动画) |
组件 detail-product-value.tsx |
待复核:grep 显示该组件仅被 products/erp-upgrade-v3/page.tsx 引用,而周期 2 判定 erp-upgrade-v3 为死路由 ⇒ 若确死则非在线缺陷,仅登记;render 副作用面仍宜在组件复用时修 |
| Q-7 | admin/items/users GET 分页未校验 |
— | 已复核确认为真缺陷并已修(见 §4.1 Q-7):新增 parsePagination 复用件,items + users 两路接入,6+1 例覆盖 |
|
| Q-8 | — | 已复核确认为真缺陷并已修(见 §4.1 Q-8):三 handler 认证前置于存在性探针,3 例证 findUnique 不被匿名触达 |
||
| Q-9 | — | 已复核确认为真缺陷并已修(见 §4.1 Q-9):整批尺寸预检 + 失败回滚已成功文件,2 例真判别 |
4.3 主线证伪的子代理结论(不修,防把假发现写进代码/文档)
- Q-FP-1:子代理称
detail-product-value.tsx:69parseFloat(value.replace(/[^0-9.]/g,''))把"1,000+"变成1(千倍低估)。不成立——[^0-9.]会删掉逗号、保留数字与点,"1,000+"→"1000."→parseFloat=1000,正确。故该解析点不是缺陷(其 render-副作用面另见 Q-6,属不同问题,仍待复核)。
本轮验证层级诚实说明:
type-check为全程序tsc --noEmit且 EXIT=0(覆盖 Q-1/2/3 全部改动);受影响单元套件(auth/login/contact/media/rate-limit)73 例全通过;改动文件eslint --quiet0 error。但全量test:coverage(1725 例)本轮未重跑,且 Q-1(表现层登出)与 Q-3(登录 API 行为)不在任何已跑过的 E2E 子集里 ⇒ 其生产目标凭据待 AUTH-D1 入库 + AUTH-D4 后统一复跑。Q-2(media 派生)真sharp单测覆盖格式,最直接。Q-4 的凭据层级:6 个 handler 的存活判定由admin/users+admin/roles路由级 jest(mock prisma.user.count)端到端覆盖(默认存活 30+ 例仍绿 + 停用 4 例判 401/零写入),属单测层,非 E2E;其"移除 helper ⇒ 停用令牌被放行"的 RED 逻辑由"旧码走authenticateRequest不看 status、新 mock 的count遂成死参数、断言 401 必红"反证。验收判定不变:新增修复同样只在主工作树,AUTH-D1 仍是唯一硬阻断。
4.4 本会话全量回归证据(2026-09-25,覆盖 Q-1…Q-5/Q-7/Q-8 全部改动之后)
| 门禁 | 命令 | 结果 |
|---|---|---|
| 全量单元 | npm run test:unit |
142 套件 / 1760 例全通过,0 失败(较周期 2 的 137/1725 净增 5 套件 35 例,全为本会话新增 RED 测试;无一处回归) |
| 覆盖率棘轮 | npm run test:coverage |
EXIT=0,阈值全达标:global 85.96 stmts / 87.57 branch / 81.38 funcs / 85.96 lines(门限 75/82/75/75 未下调);本会话新增 src/lib/pagination.ts、src/lib/rate-limit.ts 各 100/100/100/100(在 collectCoverageFrom 的 src/lib/** 内,随棘轮受约束) |
| 真库集成层 | npm run test:integration:real |
EXIT=0,4 套件 / 23 例全通过;一次性 /tmp/novalon-itest.db,teardown 守卫实测 prisma/dev.db、dev.db、data.db 的 inode+size+mtime 与运行前完全一致 ⇒ 本会话对 permissions.ts(新增 authenticateActiveRequest)与 users/roles/items/workflow 鉴权路由的改动在真库鉴权层无回归,且未污染开发库 |
| 静态 a11y(对比度/令牌) | npm run check:contrast + check:brand-token |
两者 EXIT=0:contrast 44/44 令牌配对满足 WCAG 2.1 AA;brand-token 扫描 406 文件 0 违规(1 处已知白名单降级 = loading-state.tsx 死组件,见 N-8/D-8)⇒ 本会话改动无对比度/品牌令牌回归 |
| 生产构建 + 装配 | npm run build(含 postbuild 拷贝) |
EXIT=0,dist/standalone/{server.js,dist/static,public} 三项齐备(ASSEMBLY_OK)⇒ 本会话对 api-crypto/permissions/users·roles·items·workflow·media 路由的服务端改动可干净构建(N-9 前提成立);产物 heading-hierarchy-report.json 等落盘物均被 .gitignore 覆盖,不脏提交面 |
| 标题层级(产物上) | npm run check:headings(自起 standalone 扫 10 页) |
EXIT=0:10 页全通过、0 问题 ⇒ 与 contrast/brand-token 合起来 完整 check:a11y 三件套在本会话构建产物上全绿(不再仅沿用周期 1/2 结论) |
| Lighthouse(产物上) | npm run lighthouse(lhci autorun 自起 standalone) |
EXIT=0:对 9 URL × 3 次运行 = 27 次跑的全部断言通过(含本会话前几轮新加的 inspector-issues minScore 断言)⇒ 性能/a11y/BP/SEO 门禁在当前源码产物上仍达标 |
| 类型检查 | npm run type-check(全程序 tsc) | EXIT=0 |
| Lint | npm run lint | 0 error / 104 warning(与周期 2 口径 104 完全一致 ⇒ 本会话新文件 rate-limit.ts/pagination.ts 等零新增债) |
说明:本会话已在含全部 Q 修复 + S-1 修复的最终树上重跑并 EXIT=0:
test:unit(1758)、test:coverage(阈值棘轮)、test:integration:real(23)、check:contrast/check:brand-token/check:headings(完整 check:a11y)、npm run build+装配、npm run lighthouse;type-check/lint全程 EXIT=0。仍未在本会话重跑:全站check:axe(需自起 standalone + crawl-routes 清单,端口/残留进程需专管)、生产目标 E2E(注意npm run test:e2e:prod不排除写库 spec,跑它即触发 AUTH-D4)、视觉只读比对(Q-5/S 系列改了布局,需 AUTH-D3 重基线)。周期 1/2 的这三项结论仍登记于各自报告。
5. 第四轮审查轴(2026-09-25,2 路并行 subagent:src/app/** 页面 + 余下 src/components/**)
两条只读 subagent 分别扫前几轮只 grep 未逐行读过的面。主线未照抄任何一条:S-1 我逐行核实并修复;其余登记为「待主线复核」,其中标注与前序已知项同机制的(Q-5/C-1 新点位)优先,因为修法现成。
5.1 主线已核实 + 已修(工作树内,未提交)
| ID | 级别 | 缺陷 | 触发/影响 | 修复 | 凭据 |
|---|---|---|---|---|---|
| S-1 | P2 | 首页 CMS 数组未逐条守卫 ⇒ 整页 RSC 崩溃:home-content-v15.tsx 的 ServicesSection 只在 :510 校验 services[0].subtitle,随后 :546 service.highlights.map(无 ?./Array.isArray,而 :552 metrics 却有 && 守卫);CasesSection 的 spotlight :789 用 ?. 但 restCases :828 caseItem.metrics.slice 未守卫 |
发布任一缺 highlights/metrics 的服务或案例 ⇒ 流量最高的首页 500 到 error.tsx |
两处改 Array.isArray(x) ? x : [] 逐条守卫(与既有 ?. 惯例一致) |
type-check EXIT=0、eslint --quiet 0 error(改动文件);纯渲染守卫,行为对合法数据不变 |
5.2 子代理上报、待主线逐一复核(不当作已确认/已修)
| ID | 级别 | 位置 | 摘要 | 复核优先级/备注 |
|---|---|---|---|---|
| S-2 | P2 | products/product-detail-content-v3.tsx:178,231 + solutions/solution-detail-content-v3.tsx:121,168,223,278 |
grid/space-y 挂在外层 div、唯一子为裸 StaggerReveal ⇒ /products/[id]、/solutions/[id] 桌面端塌成单列(Q-5 同机制的新点位) |
已复核+已修(6 处):同文件 :132 已是正确写法可佐证。安全修法——把网格/间距类移到 StaggerReveal className、外层 <div> 留作无类包裹(不删闭合标签,规避 Q-5 那次改动风险)。content-v3 三套件 25 例通过 + type-check EXIT=0 + eslint --quiet 0 error。需 AUTH-D3 视觉重基线确认像素 |
| S-6 | cases/[slug]/page.tsx:27-34 |
generateMetadata 绕过 C-1 归一 |
主线核实为误报,未改:generateMetadata 已用 data.challenge?.substring / data.metrics?.map,对缺键的保护与 client.tsx 的 ?? [] 同级,并非"绕过 C-1"。唯一残余是类型错误值(如 metrics 存成字符串),但那对 client 路径同样会崩,属通用运行时类型校验缺失(非元数据专属崩溃),不在本轮改 |
|
| S-3 | P2 | about/page.tsx、contact/page.tsx、products/erp-upgrade/page.tsx |
无 export const revalidate,且 /api/cms/revalidate 未映射这些模型 ⇒ 构建后发布新内容永不刷新(并含「revalidateTag 因 data-server.ts:37 用 React cache() 而非 unstable_cache 实为 no-op」的重断言) |
高但涉渲染配置/缓存语义,须逐条核 next 行为再定改否(可能引 ISR/构建变化) |
| S-4 | P2 | home-content-v15.tsx:32-43(用于 :510-516) |
FALLBACK_SERVICES 兜底含 40%/6个月 等无 basis 的数字,N-30 类第三处残留,违背零编造 |
中:修法=删兜底 metrics(与 N-30 一致);属内容口径,宜确认后改 |
| S-5 | detail/detail-hero.tsx:171-184 + micro-interactions.tsx:148-153 |
<a href><PressableButton/></a>,而 PressableButton 渲 <motion.button> ⇒ button 嵌进 a(无效交互嵌套) |
已复核+已修:根因在 PressableButton 的 href 分支本就产 <a><button>。改为有 href 时渲单个 motion.a(承载按钮样式),detail-hero 去掉外层 <a> 直接传 href;onClick/样式保留,导航语义不变。新增 micro-interactions.test.tsx 2 例真判别(旧码 querySelector('button') 非 null ⇒ RED;且发现 detail.test.tsx mock 掉了 PressableButton,故测真组件须单列) |
|
| S-7 | P3 | services/[id]/solutions/[id]/products/[id]/news/[slug] 的 page |
dynamic='force-dynamic' 与 generateStaticParams/revalidate 冲突 ⇒ SSG 产物永不服务,注释与单测给假信心 |
中:涉渲染模式口径,须核 next16 行为再定 |
| S-8 | hero-particle-field.tsx:287-290 |
onVisibility 未判相交态即 start() ⇒ 离屏 canvas rAF 空转 |
已复核+已修:加 intersecting 跟踪(IO 回调置位),onVisibility 改为「可见且 intersecting」才启动,与 :271 契约对齐。凭现有 hero-particle-field 三套件/36 例(含 reduced-motion 降级、unmount 停环)无回归 + type-check/lint 绿;未加专测(jsdom 下 visibilitychange+IO 离屏组合难做判别性 RED,诚实标注)。首页活组件(home-content-v15) |
|
| S-9 | back-to-top.tsx:12-20;detail-trust-section.tsx:18-65 AnimatedCounter |
挂载未初始同步;裸 rAF 计数不查 useReducedMotion |
两处均已修:back-to-top effect 内补 handleScroll() 初始同步;AnimatedCounter 在 shouldReduceMotion 时直接落终值不跑 rAF(useReducedMotion 自绘,因 MotionConfig 不管裸 rAF)。back-to-top by-construction(jsdom scrollY 只读难判别,未加专测,诚实标注);AnimatedCounter 由 detail.test 27 例 + 全量 test:unit 142/1760 + type-check/lint 绿佐证无回归 |
|
| S-10 | P3 | admin/content/[modelCode]/page.tsx:210 状态误标(review→「已归档」);admin/roles/page.tsx:59-79 effect 依赖 selectedRole 覆未保存编辑;admin/users/page.tsx:85 搜索无防抖/序号守卫(content/[modelCode] 已修,此处未);admin/media/page.tsx:38,131 单 fileInputRef 挂两个 input ⇒ 顶部按钮重选同文件不触发 onChange;admin/page.tsx:108/media/page.tsx:40 吞错显示「空」非报错;admin/login/page.tsx label 无 htmlFor、密码切换钮无 aria-label |
后台多处 P3(错标/竞态/吞错/a11y 标签) | 中:逐条小修,部分与既有模式(防抖/seq)现成可搬 |
诚实边界:S-2…S-10 为 subagent 上报,我尚未逐行独立复核,不作「已确认/已修」主张;S-1 是我本轮亲自核实并修复的唯一项。子代理另排除了
cta-section(零 importer,其「30 分钟内响应」是 DEC-N30 的第 5 条 SLA 变体,仅附记)、Radix 包装件、slider(零消费者)等——同样待我复核后再采信。
6. 会话收尾与续工交接(2026-09-25,subagent 架构系统性 QA)
本文件(
qa-tracker.md)即 objective 阶段3 要求的「问题跟踪机制」单一真源;§4 为第三轮(API+lib/组件)、§5 为第四轮(src/app 页面 + 余下组件)审查轴。
6.1 本会话已完成(均在工作树,未提交)
- 已核实并修复的缺陷(含判别性测试):Q-1、Q-2、Q-3、Q-4、Q-5、Q-7、Q-8、Q-9、Q-10a、Q-10b、Q-10c、S-1、S-2(6 点位)、S-5;S-8、S-9 经检查修复(诚实标注:jsdom 无法做判别性 RED,仅 by-construction + 现有套件/类型门禁)。
- 主线证伪、未改的子代理断言:
1,000+→1解析(实为 1000,正确)、C-1 归属笔误、S-6(metadata 已?.守卫,非绕过 C-1)。 - 自纠:一次 Edit 误删既有测试体(已复原);一次写了不能失败的假测试(已换成诚实版);一次 §4.4 表格被打断+留陈旧说明(已合并修正)。
6.2 本会话门禁证据(最终树,全部 EXIT=0,见 §4.4 表)
test:unit 142 套件/1760 例 · test:coverage(阈值未下调,新 lib 100%)· test:integration:real(23,dev.db 指纹未变)· 完整 check:a11y(contrast 44/44 · headings 10/10 · brand-token 0 违规)· npm run build+standalone 装配 · npm run lighthouse(9 URL×3)· type-check · lint 0 error/104 warning。
6.3 工作树披露(最高优先,勿静默留给下一个人)
git status --porcelain = 378 条(54 未跟踪),HEAD 仍 f543e47。⇒ 两轮 + 本会话全部修复只在工作树,未提交/未 PR ⇒ 不可审、不可复现、不可 CI 验证。这是 objective 阶段5「可交付」未达成的唯一实质原因。
2026-09-28 第五轮后刷新:
git status --porcelain= 394 条(55 未跟踪),HEAD 仍f543e47。本轮净增 16 条改动/新文件(含新测试文件src/app/__tests__/cms-page-revalidate.test.ts)。结论不变。
6.4 续工:待授权动作(逐项明文批准后才动;见 §2.3 / 周期报告 §5)
- AUTH-D1 入库/PR(解锁验收):先
git fetch origin dev取真值 → 裁定origin/dev↔HEAD 双向分叉的重复提交对 → 按「表现层/后台编辑器/Sentry/测试装配/文档口径 + 本会话 Q/S」分笔提交 → 过scripts/check-pr-checklist.sh→ Gitea PR。git add按路径,禁-A:.gitignore未挡prisma/dev.db-shm/-wal,-A会带入 SQLite 边车。 - AUTH-D4 写库 E2E(11 例,真写
dev.db;⚠npm run test:e2e:prod不排除写库 spec,跑它即写库)。 - AUTH-D3 视觉基线:Q-5/S-2/S-5 改了布局,产品/方案/案例详情页需人工核看后
--update-snapshots。 - AUTH-D2 seed:使周期2 L-2 权限修复对数据生效。
6.5 续工:待复核/待决策的登记项(不阻断验收)
S-3(about/contact/erp-upgrade 无 revalidate + revalidateTag no-op,涉渲染配置)、S-7(force-dynamic vs generateStaticParams)、S-4(FALLBACK_SERVICES 无 basis 数字,宜按 N-30 删)、S-10(后台状态误标/搜索竞态/媒体双 input ref 冲突/登录表单 label 关联)、S-9 子项(detail-trust 裸 rAF 未查 reduced-motion)、以及周期1/2 遗留的 D-1…D-11 / DEC-* 决策项。
7. 第五轮审查轴(2026-09-27…28 会话,4 路并行 subagent + 主线逐条复核/修复)
承接 §6.5 的续工清单:S-3/S-4/S-7/S-10/Q-6/Q-10② + 未逐行覆盖的余下面(hooks、lib/cms 渲染器、seo/analytics 组件、marketing 残余页面)。主线未照抄任何子代理结论:逐条亲读源码复核后再修,修复均带判别性测试(RED→GREEN 实测)。
7.1 主线已复核并修复(工作树内,未提交)
| ID | 级别 | 缺陷(主线核实后口径) | 修复 | RED→GREEN 凭据 |
|---|---|---|---|---|
| S-21(=DEC-D7 落地) | P2 | renderField 无 richtext 分支 ⇒ news 正文/legal 正文落 default 单行 input[type=text],浏览器 value 消毒剥掉全部 \n,编辑一次即静默损毁段落(详情页 whitespace-pre-line 渲染) |
richtext 走 textarea(rows=12);顺带收敛出作用域读写机制 |
新增 3 例读写保真测试;改前 Expected TEXTAREA, Received INPUT 判红 → 改后编辑器套件 22/22 |
| S-22 | P3 | array/object 子字段编辑器读写顶层 formData[field.name]:编辑写不进数组,却把 label/value 垃圾顶层键混进保存载荷(假成功) |
array 改整体 JSON 编辑(复用 JsonEditor);object 子字段以 scope{parent,write} 读写父对象并合并回写 |
同上:改前 getByLabelText('指标') 无 TEXTAREA / 作者 值为空 判红;改后载荷断言 metrics 回写数组、无泄漏顶层键 |
| Q-10② | P2 | workflow.ts 状态更新→读回→审计三条独立写:审计失败时状态已落库但无审计,调用方收 500 误以为流转未发生 |
交互式 prisma.$transaction(async tx …) 包住 CAS→读回→审计;通知保持在事务外(不回滚不上抛的既有语义不变) |
测试 mock 增加带快照回滚的 $transaction 桩(数组形态直接判红防退化);升级既有「审计失败 fatal」例为回滚断言:改前 storedItem 停在 published 判红 → 改后 28/28;真库集成层 test:integration:real 23/23 EXIT=0(dev.db 指纹守卫通过) |
| S-3 | P2 | /about、/contact、/products/erp-upgrade 无 revalidate 导出且 revalidate 路由无映射 ⇒ 构建后发布的 CMS 内容永不刷新(子代理实证:三页在 prerender-manifest 为 static + initialRevalidateSeconds:false)。附带断言 revalidateTag 全程 no-op 成立(全站无 unstable_cache/'use cache'/fetch tags 消费点,React cache() 仅请求内去重) |
三页加 export const revalidate = 3600(与其余 14 页同档)+ 路由补 about-page/contact-page/product→/products/erp-upgrade 三条映射(推送即秒级失效,3600 为无推送兜底) |
新增 6 例(3 route 映射 + 3 页 revalidate 导出,新文件 src/app/__tests__/cms-page-revalidate.test.ts):改前 6 例全判红 → 改后 25/25;产物级实证:重建后 dist/prerender-manifest.json 三页 initialRevalidateSeconds=3600 |
| S-10 六条 | P3×6 | ①列表状态列把 review/archived 误标「已归档」②roles 的 fetchRoles 依赖 selectedRole,切角色 refetch 覆盖未保存编辑 ③users 搜索逐键打后端且无 seq 守卫(乱序响应覆盖新结果)④media 双 input 同挂一个 ref(重选同文件不触发 onChange)⑤admin 首页/media/zones 吞错显「空」(假绿)⑥login/zones/users 弹窗 label 无 htmlFor、密码显隐钮无可访问名 |
①四态 STATUS_LABELS 映射 ②fetchRoles 依赖清零 + `setSelectedRole(prev=>prev |
|
| S-11/S-12/S-13/S-15 | P3 | zones 吞错显空 / notifications 翻页无 seq 守卫 / roles 矩阵 super_admin 全显「未授权」与「拥有所有权限」文案矛盾 / zones·users label 关联(S-10 同族扩散) | 与 S-10 一并修(error 横幅、fetchSeqRef、super_admin 整行按已授权呈现、htmlFor) | 同上 |
| S-23 | P3 | ui/animated-counter.tsx displayValue = start ? count : value:未入视口直显终值,IO 异步首回调后跳回 0 再爬升(先给答案再数) |
displayValue = shouldReduceMotion ? value : count(useCountUp 未启用期本就停在起点 0) |
新 1 例(IO 桩驱动):改前判红(旧码未相交即显 500)→ 改后 6/6;既有用例改 startOnView={false} 语义不变 |
| S-24 | P3 | PerformanceTracker CLS 累加器在 observer 回调内:分批到达的 layout-shift 每批只报增量,GA 收到的永不是会话总 CLS |
累加器提到回调外,每批上报累计值 | 新 1 例双批次断言 [≈100, ≈150](旧码第二批报 50 ⇒ RED 由形状显然)→ 改后 17/17 |
| Q-6 | P3 | detail-product-value.tsx MetricCard render 体内 setState+rAF:探针实证每帧 setState 触发重渲染又 spawn 一条从 0 开始的动画链且从不 cancel,假时钟下大字永远停在 '0';卸载后帧链继续排队 |
动画收入 useEffect,cleanup cancelAnimationFrame;NaN 回退同 effect |
新 1 例(faithful rAF 桩:cancel 真撤销排队):改前判红(旧实现卸载后 sched 继续增长)→ 改后 28/28。注:第一版测试命中 benefits 同名文本属假绿,已被探针推翻重写(诚实记录) |
7.2 主线复核后的定性变更(不改代码,只改台账口径)
| ID | 原登记 | 复核结论 |
|---|---|---|
| Q-6 路由定性 | 「erp-upgrade-v3 死路由 ⇒ 若非活码仅登记」 |
定性修正:不是死路由,是零入口孤儿路由(不在 sitemap、src 内零内部链接,但静态可直达且有真实渲染)。缺陷本身属实已修(见 §7.1),影响面限直达 URL |
| S-4 | 「FALLBACK_SERVICES 无 basis 数字,N-30 类残留,宜删」 | 降为 DECISION(DEC-S4):子代理实证 fallback 与 CMS 数据走同一渲染 map,:562 对每张卡(含 fallback)渲染 <MetricsBasisNote basis={weakestBasis(…)}>,未声明 basis 一律保守落 target 角注 ⇒ 并非裸奔编造,与 N-30(虚构佐证)不同类。残余问题:硬编码数字本身无出处 + fallback 触发条件(CMS service 缺失/首条无 subtitle)。是否删数字属内容口径,留业务裁定 |
| S-7 | 「force-dynamic + generateStaticParams 冲突 ⇒ SSG 产物永不服务」 | CONFIRMED 但判为设计取舍(子代理引 bundled docs caching-without-cache-components.md:97 + prerender-manifest 实证四路由不在产物中;页内注释表明有意为之:硬 404 + 新内容首请求可见)。不自动改渲染模式,登记为 DECISION(承 DEC-N21b 同级) |
| S-14 | clipboard 无 catch | 随 S-10 批修(.catch + toast.error),SUSPECTED 定级不变 |
7.3 子代理上报、主线证伪或不修(防假发现入库)
- MetricCard
parseFloat('1,000+')→1类解析问题在本组件不成立([^0-9.]/保留数字与点),与 §4.3 Q-FP-1 同族。 - hooks 面(use-focus-trap/use-swipe-gesture/use-reduced-motion/useCountUp)、analytics 其余 tracker、structured-data、lib/cms 其余模块:子代理逐文件扫描后无新缺陷上报(含监听清理、capture 分工、XSS sink 检查均通过)。
- 死码新增:
CookieSettingsButton(零 importer,连带「重开偏好面板不与已存同意同步」的潜在缺陷一并挂账)、getInitialShowConsent三分支恒 false(死但无害)。
7.4 本轮收尾门禁(2026-09-28 最终树)
| 门禁 | 结果 |
|---|---|
| type-check | EXIT=0(一次 tsc 报错为 Q-6 测试 mock 类型断言丢失 .mock,已修后复跑绿) |
| lint | 0 error / 105 warning(与 AGENTS.md 口径一致 ⇒ 本轮零新增债) |
| test:coverage | EXIT=0,143 套件 / 1772 例全通过(较 §6.2 净增 1 套件 12 例,全为本轮判别测试) |
| test:integration:real | EXIT=0,4 套件 / 23 例,dev.db/data.db inode+size+mtime 守卫通过(workflow $transaction 改动在真 SQLite 无回归) |
| check:contrast / brand-token / headings | 44/44 · 0 违规 · 10/10 页,均 EXIT=0 |
| build + standalone 装配 | EXIT=0,ASSEMBLY_OK;prerender-manifest 实证三页 ISR=3600(S-3 产物级) |
| lighthouse | EXIT=0,9 URL × 3 次 = 27 run 全部断言通过(lhci autorun 自起 standalone :3200) |