diff --git a/scripts/audit/ui-audit.mjs b/scripts/audit/ui-audit.mjs index 32f434c..9b55a00 100644 --- a/scripts/audit/ui-audit.mjs +++ b/scripts/audit/ui-audit.mjs @@ -541,15 +541,20 @@ function scoreRoute(r) { if (perr.length) issues.push({ sev: 'P1', cat: 'UE/稳定性', msg: `未捕获异常 ${perr.length} 条`, detail: perr.slice(0, 3) }); const rfailAll = [...(d.failedRequests || []), ...(m.failedRequests || [])]; const rfailNoise = rfailAll.filter(f => THIRD_PARTY_NOISE_RE.test(String(f.url || ''))); - // net::ERR_ABORTED = 客户端主动取消。结合两类证据判定是否需要留痕: + // net::ERR_ABORTED = 客户端主动取消。结合三类证据判定是否留痕: // ① 同 URL 后来成功(requestfinished 关联)→ 重调度,完全无害,不留痕 // ② prefetch 资源被打断 → 本身无用户影响,不留痕 - // ③ 其余 abort → INFO 留痕(2026-09-05 实测 /methodology 三图 abort 后 complete=true、URL 全 200) + // ③ 同源 script 被取消且该路由无未捕获异常 → 运行时自行取代的请求(App Router 静态 chunk + // 预取不带 Sec-Purpose 头,无法按头识别;后果导向:必需 chunk 被取消必然伴随 + // hydration 失败 → pageErrors 非空,届时仍会以 P1 记录)。闭环成立,不留痕。 + // ④ 其余 abort → INFO 留痕(2026-09-05 实测 /methodology 三图 abort 后 complete=true、URL 全 200) const finishedSet = new Set([...(d.finishedUrls || []), ...(m.finishedUrls || [])]); + const noPageErr = perr.length === 0; const abortedAndGone = f => !THIRD_PARTY_NOISE_RE.test(String(f.url || '')) && f.failure === 'net::ERR_ABORTED' && - (f.resourceType === 'prefetch' || finishedSet.has(f.url)); + (f.resourceType === 'prefetch' || finishedSet.has(f.url) || + (f.resourceType === 'script' && noPageErr)); const rfailAbort = rfailAll.filter(f => !THIRD_PARTY_NOISE_RE.test(String(f.url || '')) && f.failure === 'net::ERR_ABORTED' && !abortedAndGone(f));