Files
rond-with-you/.workbuddy/memory/2026-10-08.md
T
tamakyi 4015ddf373 时间框跳段 + 弹窗不再脏地址栏 + 统计脚本做成后台开关
时间框(flatpickr allowInput 的手输体验)
- flatpickr 把 <input type=datetime-local> 就地改成 type=text,原生分段编辑器被替换掉;
  allowInput 又是「整串解析」语义:实测 parseDate('2026') 只出年份、'202666' 才出月份,
  于是「年份很长才跳下一位」;'20260606' 还会被静默错解成 2030-12-06 且不报错。
- 新增 static/js/datetime-input.js:自定义 parseDate(纯数字只收 4/6/8/12/14 位,
  歧义与非法日期一律拒)+ 输入引导(段满即补分隔符,敲完 4 位年份立刻跳月份位)。
  按标准格式不做自动补零、不前滚;越界值原样保留交报错,不静默改成合法值。
- 三处页面(编辑/照片/专题)共用 attachPicker,配置与原先一致,模板净减 5 行。

弹窗
- window.prompt 会把提示文本与默认值写进地址栏并留进历史(confirm/alert 无此问题)。
- 新增 static/js/prompt-box.js 替代:原生 <dialog>.showModal(),遮罩/Esc/焦点齐全,
  旧浏览器降级成页面顶部一条。已换 report.html 复制链接兜底与 edit.html 活动改名。
- 门禁 no_prompt_test.go:静态扫模板禁止 prompt(,并钉住 JS 与 CSS 都在。

统计脚本
- 原来只能改 conf/app.ini(要重启),现在后台「站点设置」可开关 + 填写,默认关闭。
- cleanTrackScript 挡住注入面:开闭 <script> 标签数不配平(提前闭合会让页面散架)、
  src 的 javascript:/data: 伪协议、超长截断。注意不能用「出现 </script 就拒」当规则——
  百度统计、Umami 贴的就是完整 HTML,正常用法会被挡死。

门禁
- datetime-input.test.js:34 条断言(node 直跑,无依赖)。
- TestCleanTrackScript:逐条边界 + 定义性不变量(任何输入都不得产出含 </script 的非空结果)。
- 三项均做过变异验证,改坏实现都会被对应测试抓到。

实测:真实站点逐键 2→20→202→2026-→2026-06-→2026-06-06T;
统计脚本六个场景(默认关/只填不开/开+URL/后台与登录页不挂/危险输入/关掉恢复)全部符合预期。
全量 internal/web 204 例 0 skip,其余包全绿,gofmt 干净。
2026-10-08 18:27:59 +08:00

6.4 KiB
Raw Blame History

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 ——不是静默忽略,所以「拒绝」在页面上表现为框被清空。