跳转至

UI 改动前置自查清单

触发场景:要改某个字段的 UI 渲染(文案 / 颜色 / chip / icon / tooltip)。 起因:v0.10.90 修了 log-view.tsx 的 mstage 显示,但漏了 data-view.tsx 的 HTTP 列 — 用户截图反馈"matsudental.com 显示失败",实际是 UI 漏改。dogfood 一次踩坑 = 一条 rule。

核心问题

同一个字段往往被多个组件渲染。React 项目里常见模式: - field: 'phone' 在 5 个 DataGrid columns 定义里都被引用 - e.error 在 log-view / data-view / kpi 卡片里都有 tooltip - scrape_status 在 chip / banner / 商家详情 dialog 都有显示

改一处不改其他 = 视觉不一致 = 用户投诉

改 UI 前的 4 步自查

Step 1: grep 所有渲染该字段的位置

# 例:要改 e.error 的渲染
grep -rn "\.error\|e\.error\|log\.error\|row\.error" src/sections/ src/components/ | grep -v "\.test\."

输出列表 → 每个文件都要看是否需要同款修改。

关键字段速查(高频踩坑):

字段 涉及文件(grep -rn ...)
e.error / log.error (page-log) src/sections/page/log-view.tsxsrc/sections/data/data-view.tsx、KPI cards
row.phone src/sections/page/cell-renderers.tsx(多个 Render 函数)、src/sections/data/data-view.tsx columns、src/sections/page/local-data-table.tsx columns、src/sections/page-results/index.tsx columns
row.emails 同上 + 各 export/邮箱列表 view
row.scrape_status cell-renderers.tsx (StatusChip)、tasks view chip、商家详情 dialog
task.id 显示 task-view.tsx (列表 chip)、task-detail-dialog.tsx (标题 chip)、TaskFilterPicker
HTTP status data-view.tsx httpText/httpColor、log-view.tsx statusText/statusSx、KPI

Step 2: 抽公用 helper(如果还没有)

信号:发现 ≥ 2 个文件用相似但分散的判断逻辑(如 v0.10.90 的 classifyMstage 一开始只在 log-view.tsx 内联)。

动作:抽到 src/utils/xxx-classify.tssrc/utils/xxx-format.ts,导出单个函数返回结构化结果(color / chipColor / tooltipPrefix / shortLabel / kind)。所有渲染点 import 这一个函数。

模板:

// src/utils/xxx-classify.ts
export interface XxxClassResult {
  kind: 'a' | 'b' | 'c';
  color: string;          // sx.color 友好值
  chipColor: 'success' | 'default' | ...;  // MUI Chip color prop
  tooltipPrefix: string;
  shortLabel: string;     // chip 上显示
}

export function classifyXxx(input: string): XxxClassResult { ... }

参考实现:src/utils/mstage-classify.ts

Step 3: 改每个渲染点 import + 使用

import { classifyXxx } from '@/utils/xxx-classify';

// renderCell
const x = classifyXxx(row.field);
return (
  <Tooltip title={`${x.tooltipPrefix}${row.field}`}>
    <Chip color={x.chipColor} label={x.shortLabel} />
  </Tooltip>
);

Step 4: 在 spec / wiki / rule 沉淀规则

写一份完整渲染规则表(参考 scrape-pipeline-决策表 第 2 节):

输入 kind color chipColor tooltipPrefix shortLabel
值 1 ... ... ... ... ...
值 N ... ... ... ... ...

作用: - 下次加新输入类型时,先在表里加一行 - 改文案 / 颜色时,搜表 + 改 helper(一处)+ rebuild - code review 时对照表检查

反面教材 / 历史踩坑

案例 1(v0.10.90):mstage 显示漏改

  • v0.10.87 实现 multi-stage pipeline,把 mstage:* 编码到 e.error
  • v0.10.90 修了 log-view.tsx 区分 mstage vs 真错误
  • 漏改data-view.tsx HTTP 列也用了 log.error 一律渲染"抓取失败"红 chip
  • 用户截图 matsudental.com hover 显示 "抓取失败:mstage:success — 3 contacts"
  • v0.10.92 才补 + 抽公用 classifyMstage

教训:在 log-view.tsx 内 inline classifyMstage 当时偷懒了。抽公用是 Step 2 的核心

案例 2(v0.10.89):phone 列 UI 跟邮箱列不对齐

  • v0.10.88 把 phone 字段合并存多号码
  • v0.10.89 改了 cell-renderers.tsx 的 RenderPhone + RenderContact
  • 没漏改因为本来就在同一文件,但如果 data-view.tsx columns 内还有 field: 'phone' 用默认渲染,会漏

教训:grep field: 'phone' 提前发现所有 columns 定义点。

案例 3(旧的,多次踩):scrape_status chip 字段映射

  • 多处定义 SCRAPE_STATUS = { 1: ..., 2: ..., 3: ... } 常量
  • 改状态映射时容易漏一个文件
  • v0.10.0 后抽 StatusChip 组件后才统一

案例 4(v0.10.114 ISSUE-0075):列维度不一致

  • 同一行 "状态" 列看 row.scrape_status、"HTTP" 列看 websiteLogMap.get(row.website) URL 聚合
  • 连锁店共享 URL:一行采过、其他行排队 → 视觉显示「状态=待采集 + HTTP=✓已采」矛盾
  • 修:v0.10.114 加 batchDedupeByUrl 写回 row 字段 + 官网去重代表按 score 选

教训加 UI 列前必问"这一列数据来自 row 还是其他维度"。 跨维度不是不行,但要在列名 / tooltip 明示。详见 ui-column-dimension-consistency

案例 5(v0.10.122 ISSUE-0085):兜底文案撒谎 — fix-regression

  • v0.10.120 修 HTTP 列空兜底 tooltip:"日志被环形 buffer 淘汰"
  • 实际 3 种原因(淘汰 / batchDedupeByUrl 复用 / sourceRow 复用),只列了 1 种
  • 用户 43 商家场景下 tooltip 撒谎被发现

教训兜底分支 / fallback 文案绝不假定单一原因。 修前 grep 所有触发路径,文案用"可能是 / 或"措辞。详见 fix-regression-defense

加新 UI 列时的额外检查(v0.10.115 起)

除了上面 4 步自查,加新列时还要走维度检查

  • 列 renderCell 数据来自 row 字段还是其他维度(URL Map / 全局 Service / 聚合 useMemo)?
  • 如果跨维度,列名 / tooltip 是否明示?(如 "该网址采集情况" 暗示 URL 维度)
  • 跟同一行其他列对比:会不会出现视觉矛盾?
  • 如果是去重视图:分组取代表是否按 score 模式

完整规则ui-column-dimension-consistency + group-by-best-representative

写兜底 / fallback 文案时(v0.10.123 起强制)

任何「else / default / 空状态 / 未命中」分支的显示文案:

  • grep 出该字段的所有写入点(如 grep "scrape_status: 2"
  • 列举每个写入点是否走"标准路径"(如写 page-log)还是"快速路径"(如 batchDedupeByUrl 跳过 log)
  • 如果有 ≥ 2 种可能原因,文案绝不假定单一原因
  • 措辞用 "可能是 X / 或 Y" 而非 "是 X"
  • 启发式区分时用 row 上其他字段(如 hasContact)+ 写注释说明

反例(v0.10.120 撒谎)

title="日志已被环形 buffer 淘汰"   // ❌ 假定单一原因,用户 43 商家场景下撒谎

正例(v0.10.122 诚实)

const hint = hasContact
  ? '可能是同 URL 其他商家先采过,本行通过批量复用获得'
  : '可能是:批量复用、缓存命中、或较早采集已被日志淘汰';
title={hint}

详见 fix-regression-defense

速查 checklist

改任何 UI 渲染前:

  • grep 列出所有用该字段的渲染点
  • 是否需要抽公用 helper?(≥ 2 个文件用相似逻辑就是)
  • 每个渲染点都 import + 用
  • 在 rule 表里更新(或新建)
  • 跑 build + dogfood 看实际效果(视觉不一致只在真渲染才暴露)
  • 写到 commit message 里"涉及 X 个渲染点"

相关