美工改稿全站落地 · 跨稿重复才是规范
入档:2026-07-26 来源:PB Arena(pb.tiaozhuxiansheng.com)接收美工「简洁低疲劳 V2」改稿并全站落地的会话,5 个提交(
76a5b72/b88027c/6fdc155/e839394/4d5ab9a),已部署上线并线上验证 状态:已上线;五条 insight 均为本次首次发现 ⚠️,尚未跨项目二次验证
事实记录(不可修改区)
- 交付物:美工给的 31MB zip,内含 30 张 PNG(公共端桌面 10 + 移动 10 + 后台桌面 10)+ 1 份
UI视觉规范_简洁低疲劳V2.md - 规范要点(原文):暖白
#FAFAF8/ 炭黑#171717/ 珊瑚红#FF5A52占屏 5%–8%;普通控件圆角 6–8px、弹窗与主要面板 10–14px;取消渐变、光晕、厚阴影、玻璃拟态;列表/排行榜/人员管理/操作流水统一行式结构;多个统计卡片合并为一条细线分隔的指标栏 - 改动范围:全程只动
pb-arena-optimized/下 4 个文件(index.html / styles.css / app.js / api.js),后端零改动(git diff --stat f510fe5..HEAD -- pb-arena-server为空) - 落地顺序:先 token 层全局改(1 个提交),再逐页对稿(4 个提交)
- 客观事实(线上,不加解释):部署后
styles.css?v=20260726l生效、824 条规则全部解析、--bg为#fafaf8、正文字体 Noto Sans SC、控制台无报错;7 场真实赛事全部走通新增的派生字段(startsAt: 1784989920000→22:32/7月25日 周六) - 本机限制:借来的 Windows 机器没装 Node,后端测试跑不了、本地起不了服务,全程靠
file://静态快照 + 演示数据验证 - 未验证项(已如实交回作者):运营后台十个面板的新版式只量了计算值,没有登录后实际点过
一句话总结
设计稿是比规范文字更可靠的事实来源,但单张稿子只能证明”这一页长这样”——只有跨多张稿重复出现的处理,才够格当成全局规范去改代码。
五条可复用 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)写在通用类上时,等于宣告”这个属性归我管”;如果别的规则还要在这个属性上做文章,就别写它。
顺手教训
- 版本号要覆盖”会一起变的那一组文件”,不只是被改的那个 —— ⚠️ 赛事状态机到点不切换_写入方不等于推进方_v1 已记过「改 CSS 必须同步改
?v=」,本次是它的扩展面:这次连app.js里的 DOM 结构一起改了,而app.js压根没有版本号。只 bust CSS 的后果是老用户拿到新样式配旧结构——卡片样式已经删了,DOM 还在按旧结构渲染,比不更新更难看。给三个文件挂上同一个版本号才算完。二次验证并扩展。 - 登录态 UI 无法行为验证时的退路,以及必须如实标注 —— 后台十个面板要登录才能看到,本机又没 Node 起不了服务。退路是用 JS 往容器里注入样例 DOM,再用
getComputedStyle量栅格列数、边框宽度、计算色值,确认结构和配色对。但这只能证明规则算对了,不能证明真实数据下好看,交付时必须写成”这是量出来的,不是看到的”,并把需要人工点一遍的部分明确交回去。同族见 ⚠️ Claude预览环境不派发滚动事件_滚动类功能无法行为验证_v1。 - 无头 Chrome 截图是视口截图,不会自动截全页 —— 做进度图 PNG 时,
--screenshot只截--window-size那么大,内容更长就被切。解法是两趟:先在页面里塞一行document.title = "H" + document.documentElement.scrollHeight,用--dump-dom把高度读出来,再按精确高度截第二趟。比截一张超高图再裁干净。 - 别默认本机的图像库能用 —— 原计划用 Pillow 裁掉底部空白,
ImageChops.difference处理 2160×3462 直接 access violation 崩掉(而Image.open+load是好的)。与其排查这台机器的 Pillow,不如换成上面那条”让页面自报高度”的路子——绕开比修好快,尤其在借来的机器上。
下次改进
- 接到成套设计稿,先整体翻一遍数出”哪些处理跨多张重复”,再动第一行代码;别看一张改一张。
- 交付进度前,凡是要写”某项还没做”,先回代码里搜一遍确认它真的不存在。
- 改派生函数前先
grep调用方,把每条取数路径列出来再动手。 - 改前端结构时,把所有会一起变的静态资源版本号当成一个原子操作一起改。
关联文档
- ⚠️ 赛事状态机到点不切换_写入方不等于推进方_v1 —— 同项目、本次会话的时间起点(本篇的时间跨度就是从它修完开始);insight 3 与它的「写入方 ≠ 推进方」同族异形(那篇是字段没人推进,本篇是派生逻辑只挂一条取数路径);顺手教训里的版本号一条是对它的二次验证与扩展
- ⚠️ 零作品的比赛_界面演完不等于链路存在_v1 —— 同项目前一会话:那篇是”界面演完不等于链路存在”,本篇 insight 3 是”本地演示全绿不等于生产拿得到”,都是被”你正好在看的那条路径”骗了
- ⭐ 全站文字截断体检_检测工具本身要先被证伪_v1 —— 同项目、同一验证纪律:那篇是自制判据把正常元素判成缺陷,本篇 insight 2 是拿规范文字当待办清单把已有功能误报成缺失,共同点都是报缺陷前先回原地核实
- ⚠️ Claude预览环境不派发滚动事件_滚动类功能无法行为验证_v1 —— 同族「无法行为验证时退到什么」:那篇是预览环境不派发滚动事件,本篇是登录态 UI 够不着;两边的解法都是退到更低一层(量公式 / 量计算值)并如实标注验证等级
- 文档密集页两栏排版与对外截图脱敏_v1 —— 同族无头 Chrome 静态视觉自检;本篇补上「截图是视口截图,要先量内容高度再截第二趟」
- ⚠️ 导出运行时无系统字体回退律_CJK豆腐块_v1 —— 本次把正文字体从 Noto Serif SC 换成 Noto Sans SC 时,显式挂了
system-ui, -apple-system, "PingFang SC", "Microsoft YaHei"兜底栈,正是那篇「把兜底固化进产物」的应用 - 内测反馈渠道上线_匿名兜底与422探针验证_v1 —— 同项目更早会话:这轮改稿要解决的”视觉疲劳”问题,最初就是从那条反馈渠道收上来的
- 复盘事实先行原则 —— 本篇顶部的事实冻结区按它执行
- 09_平台工程索引 —— 平台工程区入口