平台工程

美工改稿全站落地 · 跨稿重复才是规范

入档:2026-07-26 来源:PB Arena(pb.tiaozhuxiansheng.com)接收美工「简洁低疲劳 V2」改稿并全站落地的会话,5 个提交(76a5b72 / b88027c / 6fdc155 / e839394 / 4d5ab9a),已部署上线并线上验证 状态:已上线;五条 insight 均为本次首次发现 ⚠️,尚未跨项目二次验证

事实记录(不可修改区)

一句话总结

设计稿是比规范文字更可靠的事实来源,但单张稿子只能证明”这一页长这样”——只有跨多张稿重复出现的处理,才够格当成全局规范去改代码。

五条可复用 insight

1. 跨稿重复出现的才是规范,单稿出现的是那一页的特例 ⚠️首次

规范文字说「减少卡片嵌套」,这句话落到代码上可以是任何幅度。真正让我敢把后台 .admin-card 的白底描边和 30px 内边距整个删掉的,是翻到第 6 张后台稿时发现十张里没有一张有卡片框——内容一律直接落在页面底色上、靠细线分组。这时它才从”某一页的画法”升格成”后台的规范”。

反面例子在同一次里:历史图库的桌面稿无边框、移动稿有边框。这个差异只在这一对稿子里出现,没有第二处佐证,于是按”响应式差异”照做(宽屏去框、窄屏收回成卡片),没有外推成”图库类一律无框”

判据:一个处理跨 ≥3 张不同页面的稿子重复出现 → 当规范,可以全局改;只在 1–2 张里出现 → 当该页特例,就地实现,不要提取。 提早提取的代价是全局改错、还得逐页打补丁改回来。

2. 「规范要求 X」不等于「实现缺 X」,下结论前必须回读实现 ⚠️首次

我看到规范写「移动端长列表优先两列图库」「比赛房间用三段式进度轨」,就在交付报告里写成了”这两项还没做”。实际去查:.history-grid 在窄屏本来就是 repeat(2, 1fr),.phase-track 连编号圆圈带连接线也早就在——两项都已存在,差的只是配色和字重细节

这个错误的机制很典型:读规范时脑子里默认”规范提到的都是待办”,而规范其实是对成品的完整描述,里面既有要改的也有已经对的。我用规范当了待办清单。

正确顺序是:先读实现 → 再对稿 → 最后才下”缺什么”的结论。 跳过第一步就会把”已有但不够好”误报成”没有”,报给作者的进度是虚的。这次是我在下一轮自己查代码时发现并当场纠正的,但如果没有下一轮,这个虚报就留在交付记录里了。

3. 给数据加派生字段前,先数清有几条取数路径 ⚠️首次

需求是把开赛时间拆成”大字时钟 + 日期说明”。我在 normalizeOfficialEvent() 里加了派生逻辑,本地四条演示数据全部正确,看起来收工了。

实际上 getOfficialEvents() 有两条分支:

const backendEvents = await tryBackend("/api/official-events?...");
if (backendEvents) {
  return backendEvents.map(...);   // ← 后端路径,故意不走 normalize
}
// ↓ 演示数据路径,走 normalize
return store.officialEvents.map(normalizeOfficialEvent)...

后端路径是有意绕开 normalize 的——因为 normalize 会用本地算法覆盖后端已经算好的 currentRoundTopic 等字段。于是只加在 normalize 里的结果是:本地演示全绿,生产环境根本拿不到新字段。而生产恰恰是这个需求唯一真正服务的场景。

定位方式:改一个派生函数前,先 grep 它被谁调用,然后逐个问”不调用它的分支,是不是也需要这个字段”。 修法不是把后端路径也塞进 normalize(会引入它刻意规避的副作用),而是抽一个只做派生的小函数,两条路径各贴各的。

这条与 ⚠️ 赛事状态机到点不切换_写入方不等于推进方_v1 的「写入方 ≠ 推进方」同族但形态不同:那篇是字段没人推进,这篇是派生逻辑只挂在一条取数路径上。共同点是”看起来齐了”的表象来自你正好在看的那条路径。

4. 宁可显示”没有”,也不要显示一个宽松解析出来的错值 ⚠️首次

要从开赛时间里摘出时钟。第一反应是 new Date(raw) 兜底——但演示数据里有 "06.21""05.18 - 05.25" 这种运营写的说明文本,V8 的宽松日期解析能把它们解析成一个日期,于是界面会渲染出一个煞有介事的 00:00。用户看到的是一个错的、但完全不像出错的值。

正解是显式白名单 + 逐级降级 + 兜底留空:

// 只认后端与建赛表单统一产出的 YYYY-MM-DD HH:mm
const ISO_MINUTE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/;

取值优先级定成:后端时间戳 → 统一格式字符串 → 说明文本结尾的时钟 → 都不成立时返回空,界面显示「开赛时间待公布」。

判据:凡是要把自由文本变成用户可见的结构化值,兜底分支必须是”什么都不显示”,不能是”尽力猜一个”。 猜错的值不会报错、不会告警,只会安静地骗人。

5. 要「没有 padding」就别写 padding: 0 ⚠️首次

去卡片化时,.admin-card 原本是 padding: 30px; border: 1px solid; background: white。我改成 .admin-card { padding: 0 },同时给分栏容器加了 .admin-grid > * + * { padding-left: 44px; border-left: 1px solid }

结果:竖分隔线出来了,44px 的内边距没有。两条选择器权重同为 (0,1,0),而 .admin-card 写在后面 —— 后置同权重胜出,padding: 0 精确地吃掉了 padding-left

解法不是提权重(.admin-grid > .admin-card + .admin-card 之类会越写越脆),而是干脆删掉那条声明:要”没有 padding”,让它保持默认的 0 就行,别显式写。显式写等于占位,会挡住后面所有想给它加内边距的规则。

一般化:重置类声明(padding: 0 / margin: 0 / border: 0)写在通用类上时,等于宣告”这个属性归我管”;如果别的规则还要在这个属性上做文章,就别写它。

顺手教训

下次改进

关联文档

类型/平台工程