一个复选框,三处静默
它本该默认藏着,却从第一天起就露在外面;而即使藏住了,藏的判据也是错的;而它有时什么都不做,标签却说自己在做事。
导出卡片上有个小复选框,「将豆瓣上无法检测私密状态的日记导出成公开状态」。 它的设计是默认藏着,只有真碰上才露面。 写完,测试绿了,发了两个版本。
然后项目主人在 issue 上贴了一张截图,问我另一件事——而那张截图里, 那个框好端端地露在外面。
它从第一天起就没藏住过。一个小复选框,三处静默,每一处都是这个项目已经取过名字的类别。
一、它从来没藏住过
HTML 是对的:
<label class="opt" id="export-neodb-unknown-row" hidden>
CSS 也没写错任何一条:
.opt { display: flex; align-items: flex-start; gap: var(--s1); }
问题在两者之间。浏览器自带的 [hidden] { display: none }
特指度是 0,1,0——跟一条 .opt 一模一样。
同分的时候,作者样式表赢。于是那个 hidden 属性被一条
display: flex 静静盖掉了。
这一路上没有任何东西会响:el.hidden = false 照常执行,
「HTML 里有 hidden」的断言照常通过,元素照常看得见。
而测试断言的是「HTML 里写了 hidden」,要守的性质是 「用户看不见它」。两者只在「没人给那个类写 display」的时候等价—— 又一条代理断言,这个文件里已经记到第六条了。
更值得记的是,样式表里本来就有一条 section[hidden] { display: none; }。
这个坑当年为标签页踩过一次,当时只按住了一种元素。
现在改成兜住全部,!important 在这里是对的:这不是版式偏好,
是「藏起来」的定义。
二、它本来就不该藏
修好隐藏之后,项目主人给了一个更狠的理由——那个露面条件本身就是错的。
原来的判据是「上一次导出发现了这种日记就露面」。可
unsure(读不出公私状态)是抽取器在解析那一刻的判断,
不是档案的属性。
| 档案 | 抽取器 | 结果 | |
|---|---|---|---|
| v1.1 抓的,v1.1 解析 | 冻住不变 | 旧 | unsure |
| 同一批档案,扩展升级后解析 | 一模一样 | 新 | 不再 unsure |
| 豆瓣改版后新抓的页 | 新 | 没跟上 | 变成 unsure |
所以那个条件描述的是「档案 × 抽取器版本」这个组合,而不是用户的数据。 升级一次抽取器,控件就会在用户可能正需要它的时候消失。
常驻反而是诚实的。顺带它也终于跟上面那组单选并进了同一个 fieldset——
原来放在组外,它的上外边距和整组的下外边距折叠成一个更大的值,看起来离老远,像是另一件事。
三、它有时什么都不做,却不吭声
改完之后项目主人又问了一句:「私密 + 勾上『读不出来的也公开』」呢?
跑一遍:
面板上的选择 你公开的 豆瓣锁的 读不出的 你设私密的
公开 公开 公开 私密 私密
公开 + 勾 公开 公开 公开 私密
私密 私密 私密 私密 私密
私密 + 勾 私密 私密 私密 私密 ← 那个勾毫无作用
行为是对的。 判据链是「每一级只收紧、不放松」—— 读不出公私状态的日记,绝不该比读得出来的日记还公开。
错的是它不吭声。那个勾的标签写着「导出成公开状态」,
而它此刻什么也不做,还没有任何提示。这正是这个项目栽过三次的形状:
封面回退那次、remote 数封面那次、「有 5 张图没取到」那次——
消息说的后果和实际发生的事对不上。
修法是让「不起作用」看得见:选「私密」时禁用它,整行连文字一起变淡。 只禁不清——勾选状态留着,拨回「公开」就还在; 替他把勾清掉是又一次替他拿主意,而他并没有改变那个意愿。
三次都问了同一个错问题
判据比要守的性质宽,就是漏。 而三次都漏在同一个控件上,是因为三次问的都是「代码里写了吗」, 没有一次问「用户看到的是什么」。
- 「HTML 里有
hidden吗」——有。用户看得见它。 - 「露面逻辑写对了吗」——写对了。它描述的东西不属于用户的档案。
- 「勾上会传 0 吗」——会。传了也不影响任何东西。
每一条都能在源码里查证,每一条都查证通过,每一条都没碰到真正要守的那件事。 这三处最后是被一张截图破的——一张为了别的事顺手贴上来的截图。
这个项目已经写过一次:「一个不可能失败的检查证明不了什么。」 今天补上它的近亲——一个查得动的检查,也可能一直在查一件不要紧的事。