# 2026-10-08 ## 排查:模板字段报「can't evaluate field」= 二进制比源码旧 现象:`渲染 admin/index 失败: template: index.html:116:31: executing "content" at <.ImgRows>: can't evaluate field ImgRows in type web.AdminData` 结论:不是代码 bug,是跑的二进制比模板旧。取证链: - 字段在 `view.go:669` + `handlers_admin.go:177` 都有,引入于 `4c3032c`(2026-10-01 01:15) - `go version -m rond.exe` → `vcs.revision=d99c63f`,`2026-09-30T09:35:59Z` - `strings rond.exe | grep -c ImgRows` = 0(同批 `FogMasks` 有) - `git show d99c63f:internal/web/view.go | grep -c ImgRows` = 0 **机制**:`conf/app.ini` 的 `debug=true` 让 `server.go:488` 的 `pageSet()` 每次请求都 `ParseFiles` 重读磁盘 ⇒ **模板是磁盘新版、结构体是二进制旧版**,改 Go 不重启就错配。 (与「debug=false 下模板走 go:embed,改 html 必须重编」是同一根轴的两面。) 处置:`go build -o rond.exe .` 重启即可,无需改代码。 后续可加固:`pageSet` 命中字段解析失败时提示「模板比二进制新,请重新编译」。 ## 排查:时间框手动输入不跳段(flatpickr allowInput) 现象:想输 `2026/6/6`,实际要打到 `202666` 才进月份段。 实测取证(最小复现页 + 真派发按键,见下「无头测不到原生分段」): - flatpickr 把原 `datetime-local` **就地改成 `type="text"`**(实测 `el.type === 'text'`、 `altInput` 未生成、`sameNode=true`)⇒ 原生分段编辑器被彻底替换掉,谈不上自动跳段 - 项目三处配置一致:`admin/edit.html:807`、`admin/photos.html:512`、`admin/topics.html:99` - `inst.parseDate(s,'Y-m-dTH:i')` 实测: - `2026` → y=2026 mo=**1**;`20266` → 同上;**`202666` → mo=6**(用户看到的就是这个) - `2026/6/6` → 正常 y/mo/d=2026/6/6(**带分隔符能解析**);`20260606` → y=**2030** mo=**12**(错) - `setDate` 回写:`2026` → `2026-01-01T00:00`;`202666` → `2026-06-01T00:00`;`2026-6-6` → `2026-06-06T00:00` **根因**:`allowInput` 是「整串解析」语义,不是「逐段消费」语义。年份要满 4 位才被认, 之后月份位还要再吃 2 位才出结果 ⇒ 表现为「年份很长才跳下一位」。 **无头测不到原生分段**:`--headless=new` 下原生 `datetime-local` 的 `el.shadowRoot` 是 `null`(`shadow:false`),`sr.querySelectorAll('input.edit')` 直接抛 TypeError;且真派发按键后 `input.value` 恒为 `''`、`selectionStart` 为 `null` —— UA 内部 shadow 段落在无头下不暴露。**要对比原生行为必须用有头 Chrome, 本机 CDP 脚本测不了,别把「原生 value 恒空」误当成原生没有分段。** 修复方向(未落地,等批准):`parseDate` 换成自定义解析器,接受 `2026/6/6`、`2026-6-6`、`20260606`、`2026` 等写法并做**歧义拒绝** (`20260606` 必须判非法,不能悄悄解析成 2030-12-6)。服务端格式约束是 `2006-01-02T15:04`(`handlers_edit.go:1717`、`handlers_topics.go:26`),解析器须产出同一形状。 ## 落地:时间框跳段 + 无歧义解析(方案 B,已完成) 用户拍板:**只做严格标准格式**(`xxxx年xx月xx日` 的数字形态,月/日/时/分一律两位、 不自动补零、不做前滚)。理由:少一套猜测逻辑、行为完全可预测。 **新增** `internal/web/static/js/datetime-input.js`(+ 同目录 `.test.js`,34 断言纯 node 跑): - `parseDateStrict(dateStr)`:flatpickr 的 `parseDate` 钩子。接受 `2026`/`2026/6/6`/ `2026-6-6`/`20260606`/`2026-06-06T09:30`/`202606060930` 等; **纯数字只收 4/6/8/12/14 位**(5/7/9/10/11 位一律拒——`2026060630` 到底是 「日=30」还是「时06分30」说不清)。也拒 `2026/2/30`、`2026/13/6`、尾部残留。 - `layout(raw)`:把框里值按 [年4][月2][日2][时2][分2] 切分排版,**段满即补分隔符**, 于是敲完第 4 位年份立刻变 `2026-` 且光标跳到月份位(= 原版做不到的那件事)。 逐位取数时**必须跳过分隔符**(`2026-0` 的数字是 `20260` 不是 `2020`,否则整体错位一格)。 - `placeCaret(pos)`:**同步设一次 + 微任务再补一次**。flatpickr 自己监听 input/setDate 会在我们之后改 `selectionStart`,只设一次会被它抢走 ⇒ 下次按键插到分隔符**前面** (实测退化成 `2026-6`)。 - `attachPicker(el)`:三处页面共用的挂载入口,`_rondPickerReady` 守卫防重复 (照片页 `initPickers` 会被反复调)。配置与原先完全一致,只多 `parseDate` + 引导。 - 越界(月 66、日 45、时 25)**原样保留交解析器报错,绝不静默改成合法值**—— 把 13 月悄悄改成 1 月比报错坏得多。 **改动面**:`layout.html` 加一行 script(放 `.Flatpickr` 块内、flatpickr 之后); `admin/edit.html`、`admin/photos.html`、`admin/topics.html` 三处配置收敛成 `window.rondDatetime.attachPicker(el)`(模板净减 5 行)。 **验证**(真实站点 `localhost:8080`,CDP 派发真按键): 逐跳 `2→20→202→2026-→2026-06-→2026-06-06T` 正确; `20260606`/`202606060930`/`2026/6/6`/`2026-6-6` 四种写法解析全 OK, `2026666`/`20260230` 正确拒绝,`errs=0`;照片页动态框连调三次 attachPicker (二三次返 null)+ 跳段正常。Go 测试 14 包全绿,`gofmt` 干净。 变异验证两次都被抓到:① `sep=''` ⇒ 跳段退化成 `2026`;② 放行 7 位纯数字。 ## 环境坑(下次直接照抄) - ⚠️ **`site_cdp.py` 的会话签名算法已过期**,签出来的 cookie 服务一律 303。 `internal/auth/auth.go` 现在是 `hmac(secret, payload+"\x00"+**完整** password_hash)`, 且 **secret 用裸字符串不是 hex 解码**(`server.go:118` 是 `[]byte(cfg.SecretKey)`)。 过期脚本还在用「前 16 位摘要」⇒ 本地验后台页必须自己签。 - Git Bash 下传 `--base http://...` 给 python 会被吃掉 `//`(`Cannot navigate to invalid URL`), 须加 `MSYS_NO_PATHCONV=1`。 - flatpickr 挂载后 input **`type` 被改成 `text`** ⇒ CDP 探针里 `querySelectorAll('input[type=datetime-local]')` 一律数到 0,必须用 `input.flatpickr-input`。 - 自定义 `parseDate` 返回 `false` 时 flatpickr 会走 `errorHandler(new Error("Invalid date provided: ..."))` 并清空 `selectedDates` ——不是静默忽略,所以「拒绝」在页面上表现为框被清空。