Files
rond-with-you/.workbuddy/memory/2026-10-09.md
T
tamakyi b69b827d6e 日期框换成自控输入(原生 date 会吞位)+ 气温图桌面端铺满 + 同行卡片不再被拉伸
三件事都由用户实测反馈,前两件先复现再动手。

一、日期框:敲 20260606 变成 202606/06(用户要「永远解决」)
- **实测复现**:写了个真实按键工具(CDP Input.dispatchKeyEvent 每字符带 text;
  技能里的 `--press` 只给 Enter 带 text,数字敲不进去)。在 /stats 的「起始日期」上敲 20260606:
  敲之前 `INPUT|date|`、attached=false;敲完显示 `202606/06`、`el.value` 是**空**(非法)—— 截图为证。
- **根因**:筛选区的起止日期是原生 `<input type="date">`,而挂载代码只写了
  `querySelectorAll('input[type=datetime-local]')` ⇒ **type=date 从没人管**。
  Chrome 的年段允许超过 4 位数字(年份上限 275760),8 位被切成 年=202606 + 月=06。
  原生分段状态读不到、`setSelectionRange` 对 date 类输入直接抛 ⇒ 修不了,只能换控件
  (用户也说了「如果当前控件无法实现就换别的」)。
- **做法**:`datetime-input.js` 加「只日期」模式(`SPEC.date`:年/月/日三段,输出 `Y-m-d`),
  并提供**统一入口 `attachAll(root)`** 按 `el.type` 分派;`attach` 里先把控件换成 `text`
  (引导会写 `2026-06-` 这种半成品,而原生控件对非法值一律置空)。
  `datetime-input.js` 改为**无条件引入**(公共页筛选区也要用;flatpickr 仍按需加载 ——
  没有它时只挂输入引导、不给日历,省 60 多 KB),挂载在 body 末尾**立刻**跑一次
  (别只等 DOMContentLoaded,否则会先露出原生框再变文本框)。三个后台页改用 `attachAll`,
  顺带覆盖行程筛选的 `mfrom/mto`(date 类走 `enableTime:false`,实测无时间选择器)。
- **验证**:真实按键敲 20260606 → `value="2026-06-06"`;端到端 `from=2025-08-01&to=2025-08-31`
  筛出 **186 次**,与月度图峰值月(186)完全吻合(排除「解析成空导致恒 0」)。
  门禁 `TestDateInputsGoThroughAttachAll`:模板里再出现按 type 挑的选择器即红(做过变异验证);
  JS 单测补 11 条逐键模拟(45/45)。

二、逐月平均气温图在桌面端很小
- 量出来的:10 个月固定 36px 宽,只占 1042px 里的 **396px**,右边 600 多像素全空。
- 窄屏保持「固定 36px + 横滚」(挤在一起月份标签会糊),宽屏改 `flex: 1 1 36px; max-width: 96px`
  并把轨道从 84px 加到 130px、字号提到 `--fs-sm`。

三、类型分布与一天中的分布同行高度不齐
- grid 默认 `align-items: stretch`,把 256px 的「一天中的分布」拉到与 1411px 的「类型分布」一样高,
  卡片内部留一大片空白。改成 `.grid { align-items: start }`。

回归:./scripts/verify.sh **PASS=349 FAIL=0 SKIP=0**;移动端 390 / 桌面 1440 溢出与改前一致。

⚠️ 两个验证纪律(都写进 details 三十二):
- 验收跑着的时候别并发跑测试/变异实验(两次踩到,其中一次变异正好被验收抓到,那轮结果作废)。
- **变异验证「内嵌文件」(templates/static)之后必须 `go clean -cache`**:`//go:embed` 的内容
  在「改坏→还原」后,构建缓存会继续给出变异期间编译的那份 —— 实测同一个测试
  `go test ./internal/web/` 绿、`go test ./...` 红,`go clean -testcache` 不管用。
2026-10-09 18:29:55 +08:00

18 KiB
Raw Blame History

2026-10-09

排查:/admin/settings 报错(旧二进制 + 新模板)

现象:「统计脚本做成后台开关」那次提交(4015ddf)之后,访问 /admin/settings 报错。

根因(已实测复现):跑的是旧二进制(根目录 rond.exe,10-08 18:06 编译,早于 18:27 的提交), 磁盘上的模板却是新的。debug=true 下 pageSet() 每次渲染都从磁盘重解析模板, 于是新 settings.html:148 引用了旧 setting.Settings 结构体里没有的 TrackEnabled → 执行期报错:

渲染 admin/settings 失败: template: settings.html:148:85: executing "content"
at <.Settings.TrackEnabled>: can't evaluate field TrackEnabled in type setting.Settings

render 是流式直写 ⇒ header 已发(HTTP 200)但表单在「统计脚本」卡片处截断, 没保存按钮、没 </form></html>。这就是用户看到的「报错」。

判据:strings <二进制> | grep -c cleanTrackScript —— 旧根二进制 0,新编译的 2。 (注意别用 strings | grep TrackScript 判定,config 里本来就有这个名字,旧二进制也命中。)

修复:go build -o rond.exe .(+ bin/rond.exe),重启。旧二进制备份到 bin/rond.exe.old-20261008-stale。修后 /admin/settings 完整(19642B,含 </html>/保存按钮)。

教训:Go 结构与模板字段是成对改的,debug=true 的热加载只覆盖模板 —— 改 Go 必须重编译重启, 否则就是「新模板 + 旧结构 = 执行期报错」。想根治可在启动时把所有页面各渲染一遍做冒烟(现在没有)。

其他发现(未改代码)

  1. P1 待确认:settings 行 pages=null(写入时间 2026-10-08 18:14:05)⇒ 访客侧 /map /places /timeline /stats /report 全部 404、导航只剩 1 个链接。代码语义是 「全不选 = 访客只能看主页」,属合法状态,像是测 404 分档时留下的。没动数据,等确认。
  2. P2:cleanTrackScript 的 src= 检查是全文 strings.Index,会误伤脚本体内的 src=。实测:<script>var i=new Image();i.src=u;</script> → 返回 ""(整段被静默清空), 而页面仍提示「设置已保存」⇒ 静默失效。建议只在标签属性里查 src,或把「被清空」回传前端显式提示。
  3. P2:开闭标签只拒 shut > open;末尾未闭合 <script>(open > shut)会吞掉后面内容。 实测 <script>x</script><script>y 与 <script>y 都被放行。建议改成要求 open == shut。
  4. 运维:根 rond.exe 与 bin/rond.exe 两份二进制容易走偏(这次根目录那份就是旧的)。 考虑只保留 bin/,或在启动日志打印构建时间便于比对。

回归:go vet 干净、gofmt -l 空、JS 单测 34/34、go test ./... 13 包全 ok 0 FAIL。 (注:internal/web 里 110 例是 DB 门控的,不开 ROND_TEST_CONF/ROND_SRC 会 skip,不算真跑过。)


用户确认后的收尾(四件)

用户反馈:「管理员访问也只剩主页,这好像不对」+ P2 按建议修 + 二进制只留根目录 + 防复发一并做。

1. P1 更正:不是配置问题,是导航缺 IsAdmin 分支

上面第 1 条判断得不完整。实测:站长访问 /map /places /timeline /stats /report 全是 200 (guardPage 只拦访客),但导航里只有「主页」⇒ layout.html 的页面显隐写的是 {{if .Settings.HasPage "x"}},没有 IsAdmin 分支,与 guardPage 口径不一致。 改成 {{if or .IsAdmin (.Settings.HasPage "x")}} 五处。门禁 nav_visibility_test.go (三向不变量 + 变异验证)。访客侧的 pages=null 没动(仍是「只对访客关」的配置状态)。

2. P2 修复:cleanTrackScript 重写

  • src= 只在 <script …> 开标签内部找(原来对整段 strings.Index,i.src=u 被误伤清空)。
  • 开闭标签从「只拒 shut > open」改成严格配平(末尾未闭合的开标签原来会被放行)。
  • 签名改 (script, warn string),说明码经 ?warn= 回显成人话;判危险时整段不采纳、 库里原脚本与开关不动。
  • 实测(测试实例 8081 / rond_fog_test,用完已还原):i.src=u 现在原样落库; 不配平 → ?warn=unbalanced 且库值不变;javascript: → ?warn=badsrc 且库值不变; 5000 字符 → ?warn=truncated 且落库 4096。注入范围精确复核:只在公开页(/、/map), 后台与登录页 0。

3. bin/ 删除

15 个 exe、412MB 全删;只保留根 rond.exe。技能 rond-web-verify 与 details 十八的 bin/rond.exe 写法已同步改掉。

4. 防复发:启动自检 smokeRender

template_smoke.go:pageProbe(24 个页面各一份最小可用数据,键与 pageFiles 双向校验)

  • New() 里全量渲染一遍,失败即 log.Fatalf 拒启。实测:往 settings.html 插一个不存在的 字段 → 启动报 模板自检未通过…admin/settings: can't evaluate field …,退出码 1。 门禁 template_smoke_test.go(含变异验证:把探针换成字段更少的类型必须报错)。

5. 用户裁决 + 文案订正

  • 访客侧 pages=null 不动:用户明确说「访客那一侧是根据我在后台配置的显示范围公开的, 倒不如说原来默认就是这样才对」⇒ 那是预期配置,不是故障。已把这个结论写进 details 四十一,免得后续会话又去「修」数据。
  • settings.html 的统计脚本文案订正(P3):原文两处不准 —— ①「多一个 </script> 会被判为粘贴错误」漏了「少一个也会拒」(判据已改成严格配平); ②「conf/app.ini 的 track_script 会作为这里的默认值」有歧义:它不会回填文本框, 而是「这个框留空时,注入时回落到 ini 的值」。已改成准确说法。 实测热加载生效(debug=true,无需重编),页面 19996B、含 </html>,旧文案 0 命中。
  • 验收:官方 ./scripts/verify.sh → PASS=346 FAIL=0 SKIP=0(改动前那次全量; 之后只动了这一个模板文案,重跑了全部模板门禁与 TestSmokeRender*,全绿)。

提交 + 前端现代化方案

提交:d4e4257「启动模板自检 + 站长导航放行 + 统计脚本关口收紧」(12 文件,+564/−75)。

⚠️ shots/ 里的截图是旧状态,做视觉判断前必须现场重截:mobile-home.png 还显示顶栏 折成 3 行、右按钮竖排,实测现在 390 下品牌名只留 🐾、按钮「后台」、navH=51 且不溢出。 差点拿旧图当现状写进方案。

现状实测(CDP,真实视口):

  • 卡片:17 个盒子(.card/.kpi/.filters-card)只有 2 种样式指纹 ⇒ 层级扁平。
  • 字阶 8 档(10/12/13/14/15/18/21/23),10–15px 挤了 5 档;字重 400/500/600/650。
  • 正文色只有 4 种;类型色 10 种只用在圆点与柱子,没进界面语义层。
  • 站长导航 scrollWidth=277:430→254(溢 23)、390→214(溢 63)、320→144(溢 133); 390 下「统计」被切成半个字紧贴「后台」,.overflow-right 的渐隐看不见。
  • 已有优点别改坏:KPI 已是 tabular-nums、--text-3 按 AA 调过、暗色模式已有 3 段 media。

产出:frontend-plan-2026-10-09.md(仓库根,与 audit-2026-09-21.md 并列)。 四批:A 设计令牌收敛(字阶砍到 6 档、字重 3 档、间距/圆角/阴影令牌)→ B 卡片分 hero/常规/次级

  • 首屏 1 个大数 → C 手写图表加基线/峰值/轴标(不上图表库)→ D 类型色语义化 + 窄屏导航。 明确不做:前端框架/构建链、图表库、信息架构重构。建议先做 A+B(约 1.5 天,纯 CSS + 少量 class)。 方案未改任何代码,等选批次。

落地:A + B 批次(用户选了「先按 A-B 去做」)

做法:先跑基线存档(ab.js 探针 + audit/run_all.py --mobile 390 / --overflow 1440), 再改,再用同一份探针跑一遍逐值 diff。CSS 改动量 187+/150−,另有 19 个模板共 54 处内联字号并档。

关键结果(同一探针,改动前 → 后):

  • / 的 (字号,字重) 组合 390:13 → 10、1440:15 → 10;/places 10 → 5; /timeline 10 → 6;/stats 15 → 9;/admin/settings 10 → 7。
  • 最小字号到处都 ≥11px(原来有 10 / 10.5px)。
  • 卡片样式档:/ 2 → 3,/admin/settings 1 → 2。
  • 溢出:移动端审计逐值一致;桌面 1440 唯一差异是 /map 露外元素 9 → 8。
  • 验收 ./scripts/verify.sh:PASS=347 FAIL=0 SKIP=0。

踩到的四处(都只有「量 DOM 的 (字号,字重) 组合」才看得见,截图上看都正常):

  1. Leaflet 字号跟着根字号漂:vendor .leaflet-container { font-size: .75rem }, 根 14 → 13px 后地图署名/比例尺掉到 9.8px、弹窗正文也跟着缩。 已在 app.css 钉死(容器 --fs-sm、署名/比例尺 --fs-xs)。
  2. 项目自带的静态守卫抓到我:TestNavLinksNarrowRuleHasNoDeadPadding 写死了 font-size: 13px, 并档后它红了。守卫是对的,改判据为「钉规则还在、且设了字号」;另补 TestNoBareFontSizePx (静态扫 app.css + 模板禁裸 px 字号,做过变异验证)。
  3. h1..h4 从没设过字号(原来只有字重与 margin),靠浏览器默认 2em/1.5em/1.17em ⇒ 也跟着根字号漂: 根 14 → 13px 后,/stats 里没被 .card > h2 命中的两个 h2 从 21px 变成 19.5px,比同页卡片标题(15px)大一圈。 已显式给出 h1/h2/h3 字号,实测 12 个 h2 统一 15px/600。
  4. <b>/<strong> 浏览器默认 700,比全站最高层级(标题 600)还重 ⇒ 已钉成 600。

教训(已写进 details 四十二):quiet 卡片第一版用 --bg-2(#fafbfb), 与页面底 #f6f7f8 只差 4/255 ⇒ 实测看不出边界、像「破了个洞」。配色差异必须量,不能凭「应该能看出来」。

运维提醒:测试库跑到 180MB / 21 万死元组时 verify.sh 会从 8 分钟拖到 12 分钟以上 (脚本 200MB 才自动 VACUUM);手动 VACUUM (ANALYZE) 后回到 ~10 分钟、137MB / 0 死元组。

发现未修:7 个模板工作区里是 CRLF,而 .gitattributes 要求 eol=lf (admin/index.html、admin/login.html、admin/twofa-login.html、admin/twofa.html、 map.html、places.html、timeline.html)。本批按字节替换保住了各自行尾、diff 无行尾噪声, 要不要统一另开一次清理。


落地:C + D 批次(用户选了「C和D继续做」)

C 图表:月度分布加基线 + 峰值柱标次数(.pv,副标题不再重复写「最高 N 次」); 一天中的分布把「0/6/12/18 四个数字」换成 6 小时一段的标尺(深夜/上午/下午/晚上), 用同一套 24 列 grid 保证段边界与柱子严格对齐;首页月度图同样加峰值(给 HomeData 补了 MaxMonthly)。 空态那条划掉了 —— 查下来已经统一(8 页共用 emptystate、局部走 .empty)。

D1 类型色:先算对比度,结论推翻了原计划 ——

  • 白底上 6 个色低于图形 3:1(yellow 1.87 / mint 2.12 / orange 2.20 / green 2.22 / cyan 2.54 / teal 2.57)
  • 暗底只有 indigo 2.90 不够

所以没有把类型色做成 chips / 表格类型列 / KPI 强调(那几处要的是文字色,会更难读)—— 这恰好解释了全站为什么一直只用 8px 小圆点做标记。浅色 6 个色一个没动(是手机端/地图的同一套身份色)。 实际做了:色值收口成 --cat-* + --cat-*-soft(26 个变量)、模板 colorof → csscolor/csssoft(19 处)、 暗色只提亮 indigo。colorOf 已成死代码,删掉。

⚠️ 踩到 ZgotmplZ:html/template 的 CSS 过滤器不认识 var(),函数返回普通 string 会被替换成 ZgotmplZ,页面上所有圆点当场变透明、且不报错、模板测试照过。 必须返回 template.CSS;而挂了它等于关掉过滤,所以数据侧色值先过严格校验(只有 #+6 位 hex 放行)。 已加守卫 TestCSSColorSurvivesTemplateSanitizer(两个方向都钉,做过变异验证)。

D2 窄屏导航:≤620px 整行收进 .nav-menu(原生 <details>,JS 只补点外面/Esc/点链接后收起)。 ⚠️ 顶栏 z-index 抬到 --z-nav: 1200(必须高于地图浮层 1100,否则展开的菜单被筛选面板盖住), 已在 /map 用 elementFromPoint 实测。改前 390 溢 63px、320 溢 133px(「统计」被切半个字)。

回归:verify.sh PASS=348 FAIL=0 SKIP=0;320/390 溢出全 0(改前 /admin 在 320 还溢 34px); 桌面 1440 与改前一致;7 个页面都没有低于 11px 的文字;暗色下菜单/类型色/阴影均按暗色变量生效。


提交 + 完整回归(含「原有代码是否受影响」专项)

提交:ab7ccd2「前端改造:设计令牌 + 卡片层级 + 图表可读性 + 类型色收口 + 窄屏导航」 (32 文件,+997/−283)。A/B 与 C/D 本想拆两笔,但 app.css/layout.html/view.go 里两批改动交织, 按文件拆不开、按 hunk 拆容易做出「中间那笔编译不过」的历史,合并成一笔、消息里分段,并在提交消息里写明原因。

完整验证链(一次跑齐,全部串行):

  • ./scripts/verify.sh:PASS=348 FAIL=0 SKIP=0
  • ./scripts/verify.sh --pkg ./internal/fog/ --race:PASS=22 FAIL=0 SKIP=0(mingw 在 PATH 里,-race 可用)
  • JS 单测 34/34
  • 移动端 320 / 390 + 桌面 1440 审计:320/390 溢出全 0;1440 只有 −15(滚动条宽,既有口径)

「原有代码受影响」专项检查(静态扫描 + 逐文件回读 diff):

  • 自定义属性:定义 90 / 使用 61,「用了但没定义」为 0(先前一轮报的 12 个全是我正则的假阳性: CSS 一行写多个属性、--cat-* 是 Go 侧动态拼的、模板 :root 里也有定义)。
  • 定义了没人用的:只剩 28 个 --cat-*(Go 动态拼接,运行时实测 14 种颜色都渲染出来了)—— 另外揪出 --sp-5 真没人用,已用到「停一下」页的留白上(正好是大间距场景)。
  • 模板函数:没有真孤儿(先前列表是正则把 appleColors 的键当成了函数名)。
  • 已删项残留:colorof/--radius/--shadow/旧测试名 全部 0 残留(app.css 里那句注释也顺手改对了)。
  • 逐文件回读:19 个模板的 diff 只有字号替换与 colorof→csscolor,没有误伤; Go 侧 diff 只有 MaxMonthly、cssColor/cssColorSoft、注册表与 regexp 导入。
  • 顺带发现(既有、与本批无关):.g3/.g4 是 HEAD 里就有的死类(模板/JS 都没引用); .notice-card 等不是死类 —— 它在 notice.go 里由 Go 拼 HTML,我的模板侧扫描覆盖不到。

验证中抓到的一例失败(已实证是环境抖动): TestOpenCreatesMissingDatabase 报「建库后首次连接超时」10.02s。根因是 db.Open 的 ping 写死 10s, 当时库正被验收 + autovacuum + CDP 审计一起压着;停掉审计单独跑 4.1s 通过。 ⚠️ 这条测试失败还会把临时库 rond_test_autocreate_* 留在库里(清库的 defer 在 Open 成功之后才注册)——已清掉残留。 两条结论都写进了 details 三十二的验收小节。


第三批:日期框(用户报的硬 bug)+ 两处观感

用户报:① 逐月平均气温图在桌面端很小;② 日期框敲 20260606 变成 202606/06(要「永远解决」); ③ 类型分布与一天中的分布同行高度不齐、下面一大片空白。

② 日期框:实测复现后定位(写了真实按键工具 storage/_type.py)

技能里的 --press 只给 Enter 带 text,数字敲不进去(Chrome 不执行默认动作),所以单独写了一个 CDP 打字脚本(每字符 keyDown(text) + keyUp)。用它复现:

  • 敲之前 INPUT|date|、attached=false;敲完 202606/06、value="" —— 截图为证。
  • 根因:筛选区的「起始/结束日期」是原生 <input type="date">,而挂载代码只写了 querySelectorAll('input[type=datetime-local]') ⇒ type=date 从没人管。 Chrome 的年段允许 >4 位数字(年份上限 275760),8 位被切成 年=202606 + 月=06。 原生分段状态读不到、setSelectionRange 对 date 类输入直接抛 ⇒ 修不了,只能换控件。

做法:datetime-input.js 加只日期模式(SPEC.date:年/月/日,输出 Y-m-d)+ 统一入口 attachAll(root) 按 el.type 分派;attach 里先把控件换成 text(引导会写 2026-06- 这种半成品, 原生控件对非法值一律置空)。datetime-input.js 改为无条件引入(公共页筛选区也要用), 挂载在 body 末尾立刻跑一次(别只等 DOMContentLoaded,否则先露原生框再变文本框)。 三个后台页改用 attachAll(顺带覆盖 mfrom/mto,且 date 类走 enableTime:false)。

验证:真实按键敲 20260606 → value="2026-06-06" ✓;端到端 from=2025-08-01&to=2025-08-31 筛出 186 次,与月度图峰值月(186)完全吻合 ✓(排除「解析成空导致恒 0」)。 守卫 TestDateInputsGoThroughAttachAll(模板出现按 type 挑的选择器即红;变异验证过)。

① 气温图:10 个月只用了 1042px 里的 396px

.temp-col 固定 36×84。改成宽屏 flex: 1 1 36px; max-width: 96px + 轨道 130px + 字号提到 --fs-sm; 窄屏保持固定宽 + 横滚(否则月份标签糊成一团)。

③ 同行卡片高度不齐:grid 默认 stretch

「类型分布」1411px 把「一天中的分布」也拉成 1411px,后者内部一大片空白。 .grid { align-items: start } 后两项各按自然高(256px)。

回归:verify.sh 348 例 0 skip、JS 单测 45/45(新增 11 条逐键模拟)、移动端 390 / 桌面 1440 审计溢出与改前一致。 ⚠️ 纪律(又踩一次):验收跑着的时候别并发跑别的(我并发跑了变异测试,verify 当场把注入的旧写法抓成 FAIL —— 守卫是对的,但那轮结果得作废重跑)。这条上次已写进 details 三十二,这次是实锤。