首页近期任务:勾选完成后该行不消失(只改徽章,且概述卡计数会与列表矛盾) #20

Closed
opened 2026-10-28 10:05:38 +00:00 by laoyang · 4 comments
Owner

首页「近期任务」勾选完成后不移除该行(只改徽章文字)

现象

用户原话:「首页,近期任务,如果勾选了完成,应该刷新一下这个列表,把完成的删掉。」

在首页「近期任务」卡片里点某行前面的复选框把它标成完成,这一行仍然留在列表里,
只是右边的状态徽章从「进行中」变成「已完成」。用户期望它从列表里消失。

现状(读码定位)

templates/home.php 第 193-223 行那段首页复选框脚本,AJAX 成功后只做四件事:

b.setAttribute('data-tk-status', d.status);
b.classList.toggle('is-done', d.status === 'done');
b.setAttribute('aria-pressed', ...);
var row = b.closest('[data-task-row]');
var badge = row ? row.querySelector('.kb-st') : null;
if (badge) { badge.className = 'kb-st is-' + d.status + ' is-static'; badge.textContent = ...; }
say(d.msg || '已更新。', true);

没有任何"移除这一行"的逻辑。

而第 160-161 行还明确写着这是当年的设计决定:

⚠️ 改完不摘掉这一行:用户点完正想看自己刚完成什么,立刻消失会让人怀疑
"是不是点到删除了"。刷新时由服务端决定它还在不在。

用户现在明确要推翻这条:完成的就该从列表里删掉。

服务端本身是对的(不用改)

首页路由(index.php:839)只取 doing + todo:

$tasks = Task::byStatus($familyId, $me, 'doing');
foreach (Task::byStatus($familyId, $me, 'todo') as $t) { $tasks[] = $t; }

done 本来就不在列表里 —— 所以手动刷新页面这一行确实会消失,
问题纯粹在"AJAX 完成后前端没同步"。

🔴 只删这一行是不够的(同源数据有三处)

templates/home/_card_brief.php 第 53-62 行,「概述」卡片右下的统计数字
与近期任务卡片同源(注释里写明"同一个 $tasks 数组"):

<b><?= $nDoing ?></b> 件在做
<b><?= $nTodo ?></b> 件待办

只删行不同步这两个数字 ⇒ 列表里 2 条、概述卡却说 3 件在做(自相矛盾)。
另外删到一条不剩时,卡片里会变成一片空白(模板的 empty-hint 是服务端渲染的,
前端删完不会自动出现)。

验收标准

  1. 勾选「进行中」的任务 ⇒ 该行从列表移除
  2. 勾选「待办」的任务 ⇒ 该行从列表移除
  3. 概述卡的「N 件在做 / N 件待办」数字同步减 1(不能与列表矛盾)
  4. 删到最后一条时,卡片里出现「没有进行中的任务,也没有待办。」(不是一片空白)
  5. 判定依据是服务端返回的 status,不是"我点了复选框"(幂等:返回非 done 就不删)
  6. 不能影响任务看板 / 详情页那两处复选框(它们各有自己的脚本)
  7. 删除后要能让用户知道去哪找回来(不是静默消失)
首页「近期任务」勾选完成后不移除该行(只改徽章文字) ## 现象 用户原话:「首页,近期任务,如果勾选了完成,应该刷新一下这个列表,把完成的删掉。」 在首页「近期任务」卡片里点某行前面的复选框把它标成完成,**这一行仍然留在列表里**, 只是右边的状态徽章从「进行中」变成「已完成」。用户期望它从列表里消失。 ## 现状(读码定位) `templates/home.php` 第 193-223 行那段首页复选框脚本,AJAX 成功后只做四件事: ```js b.setAttribute('data-tk-status', d.status); b.classList.toggle('is-done', d.status === 'done'); b.setAttribute('aria-pressed', ...); var row = b.closest('[data-task-row]'); var badge = row ? row.querySelector('.kb-st') : null; if (badge) { badge.className = 'kb-st is-' + d.status + ' is-static'; badge.textContent = ...; } say(d.msg || '已更新。', true); ``` **没有任何"移除这一行"的逻辑。** 而第 160-161 行还明确写着这是**当年的设计决定**: > ⚠️ 改完**不摘掉这一行**:用户点完正想看自己刚完成什么,立刻消失会让人怀疑 > "是不是点到删除了"。刷新时由服务端决定它还在不在。 用户现在明确要推翻这条:完成的就该从列表里删掉。 ## 服务端本身是对的(不用改) 首页路由(`index.php:839`)只取 `doing` + `todo`: ```php $tasks = Task::byStatus($familyId, $me, 'doing'); foreach (Task::byStatus($familyId, $me, 'todo') as $t) { $tasks[] = $t; } ``` `done` 本来就不在列表里 —— 所以**手动刷新页面这一行确实会消失**, 问题纯粹在"AJAX 完成后前端没同步"。 ## 🔴 只删这一行是不够的(同源数据有三处) `templates/home/_card_brief.php` 第 53-62 行,「概述」卡片右下的统计数字 **与近期任务卡片同源**(注释里写明"同一个 `$tasks` 数组"): ```php <b><?= $nDoing ?></b> 件在做 <b><?= $nTodo ?></b> 件待办 ``` 只删行不同步这两个数字 ⇒ 列表里 2 条、概述卡却说 3 件在做(自相矛盾)。 另外删到一条不剩时,卡片里会变成**一片空白**(模板的 `empty-hint` 是服务端渲染的, 前端删完不会自动出现)。 ## 验收标准 1. 勾选「进行中」的任务 ⇒ 该行从列表移除 2. 勾选「待办」的任务 ⇒ 该行从列表移除 3. 概述卡的「N 件在做 / N 件待办」数字**同步减 1**(不能与列表矛盾) 4. 删到最后一条时,卡片里出现「没有进行中的任务,也没有待办。」(不是一片空白) 5. 判定依据是**服务端返回的 status**,不是"我点了复选框"(幂等:返回非 done 就不删) 6. 不能影响任务看板 / 详情页那两处复选框(它们各有自己的脚本) 7. 删除后要能让用户知道**去哪找回来**(不是静默消失)
Author
Owner

已修复并部署(commit ea6e42c)

原来的行为

首页那段复选框脚本 AJAX 成功后只改徽章文字(kb-st → 「已完成」),
没有任何"移除这一行"的逻辑。而且 templates/home.php:160 那条注释还写着这是当年的刻意设计:

⚠️ 改完不摘掉这一行:用户点完正想看自己刚完成什么,立刻消失会让人怀疑
"是不是点到删除了"。刷新时由服务端决定它还在不在。

您这次明确推翻这条 —— 完成的就该从列表里删掉。

服务端本身是对的,一行没改

首页路由只取 doing + todo(index.php:839),done 本来就不在列表里
⇒ 手动刷新这一行确实会消失。问题纯粹在"AJAX 完成后前端没同步"。

改法

新增 dropTaskRow(row, wasStatus),按服务端返回的 d.status 判断(不是点击前算的 next),
成功后做三件事:

  1. 删掉这一行;
  2. 同步「概述」卡右下的「N 件在做 / N 件待办」;
  3. 一条不剩时把空态提示露出来。

为什么必须做第 2、3 件:

  • 那两个数字与这份列表同源(同一个 $tasks,_card_brief.php:53 的注释写明了)。
    只删行不减数字 ⇒ 列表 2 条、概述说 3 件在做,自相矛盾;
  • 空态提示是服务端渲染的,前端删完不会自己冒出来 ⇒ 删到最后一条时卡片一片空白,
    用户以为加载坏了。

配套小改动:

文件 改了什么 为什么
_card_tasks.php 空态改成一直渲染 + hidden 切换,加 data-tk-empty;外层加 data-tk-home-host 给脚本一个钩子,不靠匹配中文文案(文案改一个字就失效)
_card_brief.php 两个统计 <a> 加 data-hbs="doing|todo" 同上,这是 JS 要读的东西,不能把展示文案变成接口
app.css 补 .empty-hint[hidden]{display:none!important} 浏览器默认的 [hidden] 优先级最低,哪天有人给它加 display:flex 这句提示就会在"有任务时"也冒出来(与 app.css:601 同一约定)

两个自己踩到的坑(都写进代码注释了)

  1. wasStatus 必须在改 data-tk-status 之前存 ——
    删行时要靠它决定减「在做」还是「待办」。第一版我在删行时直接读 data-tk-status,
    读到的已经是 done 了。
  2. 提示文案要明说它去哪了 —— 删行的原罪就是"用户以为点到删除了"。
    所以不能沿用服务端那句「已标成「已完成」」(那会让人以为行还在,下一眼发现没了更慌),
    改成「…(已从近期任务移出,去「全部」里能看到)」。

验证

主判据 _tooling/_b37_home_task_drop.py —— 20 通过 / 0 失败

覆盖:勾「进行中」→ 行移除 + 「件在做」2→1 + 「件待办」不误减;
勾「待办」→ 行移除 + 「件待办」1→0 且链接藏起;
删到最后一条 → 0 行 + 空态露出;刷新后与服务端事实一致;库里那条状态真的是 done。

夹具说明:不能靠 deleted_at 软删历史任务来腾空卡片 ——
Task::byStatus() 的 SQL 里根本没有 deleted_at IS NULL(app/Task.php:511),
软删对首页无效,实测过。改成临时改状态,跑完逐个恢复(已确认 5 条历史任务状态全部还原)。

注入反证 _tooling/_b37_falsify.py —— 三次独立注入,各自红在对应判据上

注入 结果 命中的判据
① 不删行(原始 bug) 通过 12 / 失败 8 ③④⑤⑪⑭⑮
② 删行但不减概述卡计数 通过 17 / 失败 3 ⑤⑫⑯
③ 不限空态(删完一片空白) 通过 19 / 失败 1 ⑮

每次注入前断言锚点唯一,恢复后回到 20/0。

回归 _tooling/_b37_regress.py —— 11 通过 / 0 失败

看板勾选仍能改状态且不删行(与首页行为不同是有意的)、
详情页子任务复选框仍能改状态、[hidden] 的 display 真的是 none、
data-hbs 钩子在、卡片「全部」入口仍指向 tasks。

⚠️ 这里也踩了一个判据假设错:我一开始按"详情页也该有父任务复选框"写,直接 FAIL。
真实情况是详情页的复选框只给子任务(show.php:181 在子任务循环里),父任务没有 ——
是判据错了不是产品坏了。

内联 JS 校验(新写的 _tooling/_b37_inline_js_check.py):
php -l 不会看 HTML 里的 JS,所以把渲染后的 <script> 抽出来逐个 node --check。
并做了反证:喂一段坏 JS,它确实报 1 块坏,不是恒绿。

部署

  • git push → e733c4f..ea6e42c
  • 4 个文件线上 md5 与本地全部一致:bc973363… / db9c246a… / 3acdd5f1… / e53da6eb…
  • 属主仍 family、mtime 为部署时刻、3 个 PHP 文件 php -l 全过
  • 线上 CSS 实际能取到且含新规则;asset() 缓存戳为 0cad8609(由新 mtime+size 算出,
    用户不会吃到旧缓存)

一处行为说明

首页删行、看板不删行 —— 这是有意的不一致:看板是"全量视图",
完成了也该留在原地(用户还要在上面拖来拖去);首页「近期任务」是"手头在做"的窄视图,
完成了就不该再占地方。回归判据 ③ 专门守着这件事。

线上登录态的冒烟按分工留给您。

## 已修复并部署(commit `ea6e42c`) ### 原来的行为 首页那段复选框脚本 AJAX 成功后**只改徽章文字**(`kb-st` → 「已完成」), 没有任何"移除这一行"的逻辑。而且 `templates/home.php:160` 那条注释还写着这是**当年的刻意设计**: > ⚠️ 改完**不摘掉这一行**:用户点完正想看自己刚完成什么,立刻消失会让人怀疑 > "是不是点到删除了"。刷新时由服务端决定它还在不在。 您这次明确推翻这条 —— 完成的就该从列表里删掉。 ### 服务端本身是对的,一行没改 首页路由只取 `doing` + `todo`(`index.php:839`),`done` 本来就不在列表里 ⇒ 手动刷新这一行确实会消失。问题纯粹在"AJAX 完成后前端没同步"。 ### 改法 新增 `dropTaskRow(row, wasStatus)`,按**服务端返回的 `d.status`** 判断(不是点击前算的 `next`), 成功后做三件事: 1. **删掉这一行**; 2. **同步「概述」卡右下的「N 件在做 / N 件待办」**; 3. **一条不剩时把空态提示露出来**。 为什么必须做第 2、3 件: - 那两个数字与这份列表**同源**(同一个 `$tasks`,`_card_brief.php:53` 的注释写明了)。 只删行不减数字 ⇒ 列表 2 条、概述说 3 件在做,**自相矛盾**; - 空态提示是服务端渲染的,前端删完不会自己冒出来 ⇒ 删到最后一条时卡片**一片空白**, 用户以为加载坏了。 配套小改动: | 文件 | 改了什么 | 为什么 | |---|---|---| | `_card_tasks.php` | 空态改成一直渲染 + `hidden` 切换,加 `data-tk-empty`;外层加 `data-tk-home-host` | 给脚本一个钩子,**不靠匹配中文文案**(文案改一个字就失效) | | `_card_brief.php` | 两个统计 `<a>` 加 `data-hbs="doing\|todo"` | 同上,这是 JS 要读的东西,不能把展示文案变成接口 | | `app.css` | 补 `.empty-hint[hidden]{display:none!important}` | 浏览器默认的 `[hidden]` 优先级最低,哪天有人给它加 `display:flex` 这句提示就会在"有任务时"也冒出来(与 app.css:601 同一约定) | ### 两个自己踩到的坑(都写进代码注释了) 1. **`wasStatus` 必须在改 `data-tk-status` 之前存** —— 删行时要靠它决定减「在做」还是「待办」。第一版我在删行时直接读 `data-tk-status`, 读到的已经是 `done` 了。 2. **提示文案要明说它去哪了** —— 删行的原罪就是"用户以为点到删除了"。 所以不能沿用服务端那句「已标成「已完成」」(那会让人以为行还在,下一眼发现没了更慌), 改成「…(已从近期任务移出,去「全部」里能看到)」。 ### 验证 **主判据 `_tooling/_b37_home_task_drop.py` —— 20 通过 / 0 失败** 覆盖:勾「进行中」→ 行移除 + 「件在做」2→1 + 「件待办」不误减; 勾「待办」→ 行移除 + 「件待办」1→0 且链接藏起; 删到最后一条 → 0 行 + 空态露出;**刷新后与服务端事实一致**;库里那条状态真的是 `done`。 > 夹具说明:不能靠 `deleted_at` 软删历史任务来腾空卡片 —— > `Task::byStatus()` 的 SQL 里**根本没有 `deleted_at IS NULL`**(`app/Task.php:511`), > 软删对首页无效,实测过。改成临时改状态,跑完逐个恢复(已确认 5 条历史任务状态全部还原)。 **注入反证 `_tooling/_b37_falsify.py` —— 三次独立注入,各自红在对应判据上** | 注入 | 结果 | 命中的判据 | |---|---|---| | ① 不删行(原始 bug) | 通过 12 / **失败 8** | ③④⑤⑪⑭⑮ | | ② 删行但不减概述卡计数 | 通过 17 / **失败 3** | ⑤⑫⑯ | | ③ 不限空态(删完一片空白) | 通过 19 / **失败 1** | ⑮ | 每次注入前断言锚点唯一,恢复后回到 20/0。 **回归 `_tooling/_b37_regress.py` —— 11 通过 / 0 失败** 看板勾选仍能改状态且**不删行**(与首页行为不同是有意的)、 详情页子任务复选框仍能改状态、`[hidden]` 的 `display` 真的是 `none`、 `data-hbs` 钩子在、卡片「全部」入口仍指向 tasks。 ⚠️ 这里也踩了一个**判据假设错**:我一开始按"详情页也该有父任务复选框"写,直接 FAIL。 真实情况是详情页的复选框**只给子任务**(`show.php:181` 在子任务循环里),父任务没有 —— 是判据错了不是产品坏了。 **内联 JS 校验**(新写的 `_tooling/_b37_inline_js_check.py`): `php -l` 不会看 HTML 里的 JS,所以把渲染后的 `<script>` 抽出来逐个 `node --check`。 并做了反证:喂一段坏 JS,它确实报 1 块坏,不是恒绿。 ### 部署 - `git push` → `e733c4f..ea6e42c` - 4 个文件线上 md5 与本地全部一致:`bc973363…` / `db9c246a…` / `3acdd5f1…` / `e53da6eb…` - 属主仍 `family`、mtime 为部署时刻、3 个 PHP 文件 `php -l` 全过 - 线上 CSS 实际能取到且含新规则;`asset()` 缓存戳为 `0cad8609`(由新 mtime+size 算出, 用户不会吃到旧缓存) ### 一处行为说明 首页删行、看板不删行 —— 这是**有意的不一致**:看板是"全量视图", 完成了也该留在原地(用户还要在上面拖来拖去);首页「近期任务」是"手头在做"的窄视图, 完成了就不该再占地方。回归判据 ③ 专门守着这件事。 线上登录态的冒烟按分工留给您。
Author
Owner

收尾:已修复、已验证、已部署

改动

commit ea6e42c — fix(home): 近期任务勾选完成后该行不消失(连概述卡计数也不减)(#20)

4 个文件:

  • templates/home.php(+75 行):新增 dropTaskRow(row, wasStatus) 与 syncBriefCount(wasStatus);点击前先存 var was = now; 再改 data-tk-status;AJAX 成功后若 status === 'done' 就删行 + 减计数 + 必要时显空态。
  • templates/home/_card_tasks.php:外层加 data-tk-home-host,空态提示加 data-tk-empty(按 $tasks 决定是否 hidden)。
  • templates/home/_card_brief.php:两个统计链接加 data-hbs="doing" / data-hbs="todo" 钩子——JS 不再靠中文文案「件在做」定位。
  • assets/css/app.css:补 .empty-hint[hidden] { display: none !important; }(作者样式里一旦给 .empty-hint 声明了 display,浏览器默认的 [hidden] 就会失效)。

主判据(_tooling/_b37_home_task_drop.py,20 条)

通过 20 / 失败 0

覆盖:勾选后该行从 DOM 移除、列表 ul 空了就整体移除、空态提示显形、概述卡「件在做」/「件待办」各自减 1 且减到 0 时隐藏、刷新页面后确实只剩未完成任务(服务端真落库,不只是前端假删)。

三重注入反证(_tooling/_b37_falsify.py)

  • ① 不删行 → 通过 12 / 失败 8(红在 ③④⑤⑪⑭⑮ 等)
  • ② 不减计数 → 通过 17 / 失败 3(⑤⑫⑯)
  • ③ 不限空态 → 通过 19 / 失败 1(⑮)

每次注入前断言锚点唯一(count(anchor) == 1),恢复后回到 20 / 0。

回归(_tooling/_b37_regress.py,11 条)

通过 11 / 失败 0

关键几条:

  • 看板(r=tasks)勾选仍然只改状态、不删行——删行是首页特有的行为,不能外溢。
  • 任务详情页的子任务复选框照旧可用(注意:父任务本来就没有复选框,这是原有设计,不是缺陷)。
  • [hidden] 的实际 display 确实是 none。
  • 卡片「全部」入口还在。

内联 JS 校验(_tooling/_b37_inline_js_check.py)

php -l 管不到 HTML 里的内联脚本,单独抽出来逐个 node --check:0 块坏。并反证过——喂一段坏 JS 能报出 1 块坏,判据有牙齿。

部署验证

4 个文件线上 md5 与本地逐一相等:

bc973363b634fba367a264241df5b016  assets/css/app.css
db9c246a6cb0ba5e397095549adf3a71  templates/home.php
3acdd5f12391a8fb3a81409bee13e982  templates/home/_card_brief.php
e53da6eb6ed6457788faf68d088ea727  templates/home/_card_tasks.php
  • php -l ×3 全过;mtime/属主 2026-10-28 18:14 family family 全部对。
  • 关键字抽查:dropTaskRow|syncBriefCount = 4、data-tk-* = 3、data-hbs = 3、empty-hint[hidden] = 1。
  • 线上入口 /、?r=home、?r=tasks 均 302 跳登录(正常,未登录态)。
  • asset() 缓存戳变为 0cad8609(由新 mtime+size 算出),旧 CSS 缓存会自动失效,不需要用户手工清缓存。

夹具清理

测试期间为腾空卡片曾临时改动 5 条历史任务的状态(注意:软删 deleted_at 对首页任务列表无效,Task::byStatus() 的 SQL 里没有这个条件,所以只能临时改 status)。已逐个还原:

4  周末大扫除      todo
7  乙板进行中的事  doing
10 面板验收任务    doing
11 面板加的子任务  todo
12 首页新建的任务  todo

本次自建的任务已全部删除。

线上登录态冒烟

按分工留给你点。建议顺手看一眼这两处:

  1. 首页勾一个「在做」的任务 → 该行消失、N 件在做 减 1;再勾到 0 → 那个统计块整体隐藏。
  2. 去 r=tasks 看板上勾同一个任务 → 应该只改状态、行还在(首页才删行)。

确认手感没问题再关单也可以;我这边先按验证结果关闭,有异常随时重开。

## 收尾:已修复、已验证、已部署 ### 改动 commit `ea6e42c` — fix(home): 近期任务勾选完成后该行不消失(连概述卡计数也不减)(#20) 4 个文件: - `templates/home.php`(+75 行):新增 `dropTaskRow(row, wasStatus)` 与 `syncBriefCount(wasStatus)`;点击前先存 `var was = now;` 再改 `data-tk-status`;AJAX 成功后若 `status === 'done'` 就删行 + 减计数 + 必要时显空态。 - `templates/home/_card_tasks.php`:外层加 `data-tk-home-host`,空态提示加 `data-tk-empty`(按 `$tasks` 决定是否 `hidden`)。 - `templates/home/_card_brief.php`:两个统计链接加 `data-hbs="doing"` / `data-hbs="todo"` 钩子——JS 不再靠中文文案「件在做」定位。 - `assets/css/app.css`:补 `.empty-hint[hidden] { display: none !important; }`(作者样式里一旦给 `.empty-hint` 声明了 `display`,浏览器默认的 `[hidden]` 就会失效)。 ### 主判据(`_tooling/_b37_home_task_drop.py`,20 条) `通过 20 / 失败 0` 覆盖:勾选后该行从 DOM 移除、列表 ul 空了就整体移除、空态提示显形、概述卡「件在做」/「件待办」各自减 1 且减到 0 时隐藏、刷新页面后确实只剩未完成任务(服务端真落库,不只是前端假删)。 ### 三重注入反证(`_tooling/_b37_falsify.py`) - ① 不删行 → `通过 12 / 失败 8`(红在 ③④⑤⑪⑭⑮ 等) - ② 不减计数 → `通过 17 / 失败 3`(⑤⑫⑯) - ③ 不限空态 → `通过 19 / 失败 1`(⑮) 每次注入前断言锚点唯一(`count(anchor) == 1`),恢复后回到 `20 / 0`。 ### 回归(`_tooling/_b37_regress.py`,11 条) `通过 11 / 失败 0` 关键几条: - 看板(`r=tasks`)勾选仍然只改状态、**不删行**——删行是首页特有的行为,不能外溢。 - 任务详情页的**子任务**复选框照旧可用(注意:父任务本来就没有复选框,这是原有设计,不是缺陷)。 - `[hidden]` 的实际 `display` 确实是 `none`。 - 卡片「全部」入口还在。 ### 内联 JS 校验(`_tooling/_b37_inline_js_check.py`) php -l 管不到 HTML 里的内联脚本,单独抽出来逐个 `node --check`:**0 块坏**。并反证过——喂一段坏 JS 能报出 1 块坏,判据有牙齿。 ### 部署验证 4 个文件线上 md5 与本地**逐一相等**: ``` bc973363b634fba367a264241df5b016 assets/css/app.css db9c246a6cb0ba5e397095549adf3a71 templates/home.php 3acdd5f12391a8fb3a81409bee13e982 templates/home/_card_brief.php e53da6eb6ed6457788faf68d088ea727 templates/home/_card_tasks.php ``` - `php -l` ×3 全过;mtime/属主 `2026-10-28 18:14 family family` 全部对。 - 关键字抽查:`dropTaskRow|syncBriefCount` = 4、`data-tk-*` = 3、`data-hbs` = 3、`empty-hint[hidden]` = 1。 - 线上入口 `/`、`?r=home`、`?r=tasks` 均 302 跳登录(正常,未登录态)。 - `asset()` 缓存戳变为 `0cad8609`(由新 mtime+size 算出),旧 CSS 缓存会自动失效,不需要用户手工清缓存。 ### 夹具清理 测试期间为腾空卡片曾临时改动 5 条历史任务的状态(注意:**软删 `deleted_at` 对首页任务列表无效**,`Task::byStatus()` 的 SQL 里没有这个条件,所以只能临时改 status)。已逐个还原: ``` 4 周末大扫除 todo 7 乙板进行中的事 doing 10 面板验收任务 doing 11 面板加的子任务 todo 12 首页新建的任务 todo ``` 本次自建的任务已全部删除。 ### 线上登录态冒烟 按分工留给你点。建议顺手看一眼这两处: 1. 首页勾一个「在做」的任务 → 该行消失、`N 件在做` 减 1;再勾到 0 → 那个统计块整体隐藏。 2. 去 `r=tasks` 看板上勾同一个任务 → 应该**只改状态、行还在**(首页才删行)。 确认手感没问题再关单也可以;我这边先按验证结果关闭,有异常随时重开。
Author
Owner

补充:这条没修干净,找到了漏掉的第二条路径

用户又提了一次同一条需求,我去核了线上代码——4 个文件 md5 与本地完全一致、mtime 18:14,
部署没问题。复跑主判据 _b37_home_task_drop.py 仍然 20 / 0 全绿。
判据绿、用户还能复现 ⇒ 判据有盲区,不是部署问题。

盲区:我漏了「浮层面板」这条路径

首页每一行除了行首复选框,整行还能点开浮层面板(v78 加的,_card_tasks.php:47 的 data-fb-entry 里带了 panel 地址)。
面板里有一排「状态」三段按钮(_panel.php:87-91,data-tk-act="status"):

<?php foreach (\App\Task::STATUS as $tpSk => $tpSv): ?>
  <button data-tk-act="status" data-status="<?= e($tpSk) ?>"><?= e($tpSv) ?></button>

点「已完成」走的是 assets/js/task_panel.js:

function syncRow(d) {
  var row = document.querySelector('[data-task-row="' + taskId() + '"]');
  if (!row) { return; }
  var badge = row.querySelector('.kb-st');
  if (badge) { badge.className = 'kb-st is-' + d.status + ' is-static'; ... }
  var chk = row.querySelector('.tk-check[data-tk-done]');
  if (chk) { chk.setAttribute('data-tk-status', d.status); ... }   // ← 只改状态,不删行
}

它只改徽章和复选框的 aria 状态,从不删行。 于是:

  • 从行首复选框勾 → 行消失(#20 修的那条)
  • 从浮层面板点「已完成」→ 行留在原地,徽章变成「已完成」

同一条需求、同一个用户动作(都是"把这个任务标成完成"),两条路径两种结果。
用户很可能就是点的浮层。

判据为什么没抓到

grep panel|浮层|fbEntry|data-fb-entry 在 _b37_home_task_drop.py 和 _b37_regress.py 里一条都没有——
我当初只照着"复选框"写判据,完全没意识到"改状态"在首页有两个入口。
这是 #456 的同族问题(需求横跨几层就每层都要判据),但这次不是"层",是入口。

还发现一处遗留问题

home.php:263-266 那段注释还在说「撤回按钮……成功后整页刷新把行放回去」,
但实际代码里没有这个撤回按钮(say() 只是一句 3.2 秒自动消失的提示)。
注释描述了一个不存在的功能——留着会误导下一个人。

修法

  1. task_panel.js 的 syncRow() 在 d.status === 'done' 时也走删行逻辑,
    和 home.php 的 dropTaskRow 共用一份实现(抽到 app.js 或两处各写一份但行为一致,
    抽公共函数更稳——不然又是"改一处忘一处")。
  2. 判据补上浮层路径:点行 → 面板出现 → 点「已完成」→ 断言行消失、计数减 1、空态正确。
  3. 反证:把 syncRow 的删行注掉,断言新判据变红。
  4. 顺手修掉那条说谎的注释。

关闭后重新打开处理。

## 补充:这条没修干净,找到了漏掉的第二条路径 用户又提了一次同一条需求,我去核了线上代码——4 个文件 md5 与本地完全一致、mtime `18:14`, **部署没问题**。复跑主判据 `_b37_home_task_drop.py` 仍然 **20 / 0 全绿**。 判据绿、用户还能复现 ⇒ 判据有盲区,不是部署问题。 ### 盲区:我漏了「浮层面板」这条路径 首页每一行除了行首复选框,**整行还能点开浮层面板**(v78 加的,`_card_tasks.php:47` 的 `data-fb-entry` 里带了 `panel` 地址)。 面板里有一排「状态」三段按钮(`_panel.php:87-91`,`data-tk-act="status"`): ```php <?php foreach (\App\Task::STATUS as $tpSk => $tpSv): ?> <button data-tk-act="status" data-status="<?= e($tpSk) ?>"><?= e($tpSv) ?></button> ``` 点「已完成」走的是 `assets/js/task_panel.js`: ```js function syncRow(d) { var row = document.querySelector('[data-task-row="' + taskId() + '"]'); if (!row) { return; } var badge = row.querySelector('.kb-st'); if (badge) { badge.className = 'kb-st is-' + d.status + ' is-static'; ... } var chk = row.querySelector('.tk-check[data-tk-done]'); if (chk) { chk.setAttribute('data-tk-status', d.status); ... } // ← 只改状态,不删行 } ``` **它只改徽章和复选框的 aria 状态,从不删行。** 于是: - 从**行首复选框**勾 → 行消失(#20 修的那条) - 从**浮层面板**点「已完成」→ 行留在原地,徽章变成「已完成」 同一条需求、同一个用户动作(都是"把这个任务标成完成"),两条路径两种结果。 用户很可能就是点的浮层。 ### 判据为什么没抓到 `grep panel|浮层|fbEntry|data-fb-entry` 在 `_b37_home_task_drop.py` 和 `_b37_regress.py` 里**一条都没有**—— 我当初只照着"复选框"写判据,完全没意识到"改状态"在首页有两个入口。 这是 #456 的同族问题(需求横跨几层就每层都要判据),但这次不是"层",是**入口**。 ### 还发现一处遗留问题 `home.php:263-266` 那段注释还在说「撤回按钮……成功后整页刷新把行放回去」, 但实际代码里**没有这个撤回按钮**(`say()` 只是一句 3.2 秒自动消失的提示)。 注释描述了一个不存在的功能——留着会误导下一个人。 ### 修法 1. `task_panel.js` 的 `syncRow()` 在 `d.status === 'done'` 时也走删行逻辑, 和 `home.php` 的 `dropTaskRow` 共用一份实现(抽到 app.js 或两处各写一份但行为一致, 抽公共函数更稳——不然又是"改一处忘一处")。 2. 判据补上浮层路径:点行 → 面板出现 → 点「已完成」→ 断言行消失、计数减 1、空态正确。 3. 反证:把 `syncRow` 的删行注掉,断言新判据变红。 4. 顺手修掉那条说谎的注释。 关闭后重新打开处理。
laoyang reopened this issue 2026-10-28 10:23:00 +00:00
Author
Owner

补齐:浮层那条路径已修,两条路都验过了

根因(上一版漏掉的那条)

首页把任务标成完成有两个入口:

  • ① 每行前面那颗复选框 → home.php 末尾的脚本(上一版已修)
  • ② 点整行弹浮层 → 面板里「状态」三段按钮 → assets/js/task_panel.js 的 syncRow()

上一版只改了 ①。syncRow() 只改徽章和复选框的 aria-pressed,从不删行——
所以从面板点「已完成」后,行留在原地只把徽标换成「已完成」。你点的就是 ②。

原判据里 grep panel|浮层|data-fb-entry 一条都没有,完全没测这条路,所以 20/0 全绿也照样漏。

改法

把删行抽成一份共用实现 window.fbHomeTaskDrop(row, wasStatus)(assets/js/app.js),两个入口都调它:

  • task_panel.js 的 syncRow():d.status === 'done' 时调它
  • home.php:原来那份本地实现改成薄转发,不留第二份逻辑

两个关键约束写进代码注释了:

  1. 作用域必须限定 [data-tk-home-host]。任务看板页的行也带 data-task-row,
    那里勾完成只改状态、必须保留该行(看板按状态分栏,删了就没处看)。
    共用函数一开始就判"行不在首页卡片里就完全不碰",这是它能被两个入口共用的前提。
  2. wasStatus 必须在改写 data-tk-status 之前读。改完再读只能读到 done,
    概述卡就不知道该减「件在做」还是「件待办」。

子任务安全:task_panel_json() 用 panel_of 找的是父任务,回的 status 是父任务的状态。
所以在面板里勾子任务不会把父任务那一行删掉(判据 ⑱ 专门守这条)。

验证

新判据 _b38_panel_done_drop.py(21 条,走真实浮层点击,不碰复选框)

修复前先跑了一次,确认判据有牙齿:

通过 16 / 失败 5
  - ⑥ 该行已从列表移除
  - ⑦ 剩下的还在(没连带删错)
  - ⑧ 概述卡「件在做」2 → 1
  - ⑬ 待办那条也移除了
  - ⑭ 「件待办」1 → 0 且链接藏起

修复后:21 / 0

注入反证 _b38_falsify.py:把删行那两行用 if (false) 挡掉,锚点先断言 count == 1
(防打错靶子)。注入后立刻红回同样的 5 条,关键四条 ⑥⑧⑬⑭ 命中 4/4,副本自动还原。

原有判据没被重构搅坏

  • _b37_home_task_drop.py(复选框路径):20 / 0
  • _b37_regress.py(回归):11 / 0——看板仍改状态且不删行(③)、
    详情页子任务复选框正常、空态 display 真的是 none

语法:php -l ×2 通过;app.js / task_panel.js node --check 通过;home.php 内联 JS 0 块坏。

顺手修掉的

home.php 原注释写着"补偿了一个可撤回的提示",但代码里从来没有撤回按钮,
只有一句 3.2 秒自动消失的 say()。注释描述不存在的功能比没注释更糟——
下一个人会去找它、找不到、然后再写一个。已改成如实描述,并写明撤回的正路
是卡片右上角「全部」→ 任务看板(那里 done 的还在,能改回来)。

部署

commit 4941e9d。4 个文件线上 md5 与本地逐一相等:

c27a5d2180db72364ba8853401626dcd  assets/js/app.js
8a238ef77666e1cc575c13299b15f01b  assets/js/task_panel.js
6acfdd71f80f16b2972e7c5a1cab7c81  templates/home.php
232c69a6dab1951b00055d179994767c  templates/home/_card_tasks.php

属主 family family。asset() 缓存戳已自动变:
app.js → 752c193f、task_panel.js → 1a56f271(都与上一版不同,浏览器会拉新文件,
不用你手工清缓存)。

线上入口 /、?r=home、?r=tasks 均 302 跳登录(未登录态正常)。
https://family.yangyq.net/assets/js/app.js?v=752c193f 能取到且含 fbHomeTaskDrop。

线上冒烟(留给你)

这一版请重点走浮层那条路:

  1. 首页点开一条「进行中」任务的标题(不是复选框)→ 面板弹出来
  2. 在面板「状态」里点**「已完成」**
  3. 应该看到:浮层背后的那一行立刻消失,概述卡 N 件在做 减 1
  4. 关掉浮层,在 r=tasks 看板里找它——应该还在(状态 done),点「进行中」能改回来

如果这次还是不消失,麻烦看一眼浏览器控制台有没有报错,那说明是另一个入口的问题。

## 补齐:浮层那条路径已修,两条路都验过了 ### 根因(上一版漏掉的那条) 首页把任务标成完成有**两个入口**: - ① 每行前面那颗复选框 → `home.php` 末尾的脚本(上一版已修) - ② **点整行弹浮层 → 面板里「状态」三段按钮** → `assets/js/task_panel.js` 的 `syncRow()` 上一版只改了 ①。`syncRow()` 只改徽章和复选框的 `aria-pressed`,**从不删行**—— 所以从面板点「已完成」后,行留在原地只把徽标换成「已完成」。你点的就是 ②。 原判据里 `grep panel|浮层|data-fb-entry` **一条都没有**,完全没测这条路,所以 20/0 全绿也照样漏。 ### 改法 把删行抽成**一份**共用实现 `window.fbHomeTaskDrop(row, wasStatus)`(`assets/js/app.js`),两个入口都调它: - `task_panel.js` 的 `syncRow()`:`d.status === 'done'` 时调它 - `home.php`:原来那份本地实现改成薄转发,不留第二份逻辑 两个关键约束写进代码注释了: 1. **作用域必须限定 `[data-tk-home-host]`**。任务**看板**页的行也带 `data-task-row`, 那里勾完成只改状态、**必须保留该行**(看板按状态分栏,删了就没处看)。 共用函数一开始就判"行不在首页卡片里就完全不碰",这是它能被两个入口共用的前提。 2. **`wasStatus` 必须在改写 `data-tk-status` 之前读**。改完再读只能读到 `done`, 概述卡就不知道该减「件在做」还是「件待办」。 **子任务安全**:`task_panel_json()` 用 `panel_of` 找的是**父任务**,回的 `status` 是父任务的状态。 所以在面板里勾子任务不会把父任务那一行删掉(判据 ⑱ 专门守这条)。 ### 验证 **新判据 `_b38_panel_done_drop.py`(21 条,走真实浮层点击,不碰复选框)** 修复前先跑了一次,确认判据有牙齿: ``` 通过 16 / 失败 5 - ⑥ 该行已从列表移除 - ⑦ 剩下的还在(没连带删错) - ⑧ 概述卡「件在做」2 → 1 - ⑬ 待办那条也移除了 - ⑭ 「件待办」1 → 0 且链接藏起 ``` 修复后:**21 / 0** **注入反证 `_b38_falsify.py`**:把删行那两行用 `if (false)` 挡掉,锚点先断言 `count == 1` (防打错靶子)。注入后立刻红回同样的 5 条,关键四条 ⑥⑧⑬⑭ 命中 4/4,副本自动还原。 **原有判据没被重构搅坏** - `_b37_home_task_drop.py`(复选框路径):**20 / 0** - `_b37_regress.py`(回归):**11 / 0**——看板仍改状态且**不删行**(③)、 详情页子任务复选框正常、空态 `display` 真的是 `none` **语法**:`php -l` ×2 通过;`app.js` / `task_panel.js` `node --check` 通过;`home.php` 内联 JS 0 块坏。 ### 顺手修掉的 `home.php` 原注释写着"补偿了一个可撤回的提示",但代码里**从来没有撤回按钮**, 只有一句 3.2 秒自动消失的 `say()`。注释描述不存在的功能比没注释更糟—— 下一个人会去找它、找不到、然后再写一个。已改成如实描述,并写明撤回的正路 是卡片右上角「全部」→ 任务看板(那里 done 的还在,能改回来)。 ### 部署 commit `4941e9d`。4 个文件线上 md5 与本地逐一相等: ``` c27a5d2180db72364ba8853401626dcd assets/js/app.js 8a238ef77666e1cc575c13299b15f01b assets/js/task_panel.js 6acfdd71f80f16b2972e7c5a1cab7c81 templates/home.php 232c69a6dab1951b00055d179994767c templates/home/_card_tasks.php ``` 属主 `family family`。`asset()` 缓存戳已自动变: `app.js` → `752c193f`、`task_panel.js` → `1a56f271`(都与上一版不同,浏览器会拉新文件, 不用你手工清缓存)。 线上入口 `/`、`?r=home`、`?r=tasks` 均 302 跳登录(未登录态正常)。 `https://family.yangyq.net/assets/js/app.js?v=752c193f` 能取到且含 `fbHomeTaskDrop`。 ### 线上冒烟(留给你) 这一版请**重点走浮层那条路**: 1. 首页点开一条「进行中」任务的标题(不是复选框)→ 面板弹出来 2. 在面板「状态」里点**「已完成」** 3. 应该看到:浮层背后的那一行**立刻消失**,概述卡 `N 件在做` 减 1 4. 关掉浮层,在 `r=tasks` 看板里找它——应该还在(状态 done),点「进行中」能改回来 如果这次还是不消失,麻烦看一眼浏览器控制台有没有报错,那说明是另一个入口的问题。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: laoyang/FamilyBook#20