fix(home): 从浮层把任务标成完成时该行不消失(只修了复选框那条路)(#20)

用户第二次提同一条需求。核了线上代码:4 文件 md5 与本地一致、mtime 18:14,
部署没问题;复跑原判据仍 20/0 全绿。判据绿 + 用户能复现 ⇒ 判据有盲区。

盲区:首页把任务标成完成有**两个入口**
  ① 每行前面那颗复选框 —— home.php 末尾的脚本(上一版已修)
  ② 点整行弹浮层 → 面板里「状态」三段按钮 → assets/js/task_panel.js 的 syncRow()
上一版只改了 ①。syncRow() 只改徽章与复选框的 aria 状态,**从不删行**,
所以从面板点「已完成」后行留在原地,只把徽标换成「已完成」。
用户点的就是 ②,而原判据 grep 全文没有 panel / 浮层 / data-fb-entry —— 完全没测这条路。

改法:
- 把删行逻辑抽成 window.fbHomeTaskDrop(row, wasStatus) 放 assets/js/app.js,
  两个入口共用一份。作用域限定 [data-tk-home-host]:任务**看板**页的行也带
  data-task-row,那里勾完成只改状态、**必须保留该行**(看板按状态分栏,删了就没处看)。
- task_panel.js 的 syncRow() 在 d.status === 'done' 时调它;
  wasStatus 必须在改写 data-tk-status **之前**读,否则概述卡不知道该减「在做」还是「待办」。
- home.php 原来那份本地实现改成薄转发,避免两份实现改一处忘一处。
- 顺带修掉一条说谎的注释:原注释写"补偿了一个可撤回的提示",但代码里从来没有
  撤回按钮,只有一句 3.2 秒自动消失的 say()。
- _card_tasks.php 补记"两个入口"这件事。

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

判据:
- 新增 _b38_panel_done_drop.py(21 条,走真实浮层点击):
  修复前 **16 通过 / 5 失败**(⑥⑦⑧⑬⑭)→ 修复后 **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
- home.php 内联 JS 0 块坏;app.js / task_panel.js node --check 全过
This commit is contained in:
2026-10-28 18:29:59 +08:00
parent ea6e42c27f
commit 4941e9db9c
4 changed files with 123 additions and 48 deletions
+16 -42
View File
@@ -231,8 +231,12 @@
⇒ 手动刷新时它确实会消失,所以"删行"与刷新后的结果**一致**,
不是前端自己造了个假象。
⚠️ 当年这里是刻意**不删**的(原注释:怕用户以为"点到删除了")——
用户现在明确要它消失,所以改掉;但**补偿**了一个可撤回的提示,
见下面 say() 那段(既能满足"看着干净",又不至于找不到刚完成的东西)。 */
用户现在明确要它消失,所以改掉。
🔴 补偿方式是**一句说明去哪了**(下面 say() 那段),**不是"撤回按钮"**:
先前这里的注释写着"补偿了一个可撤回的提示",但代码里从来没有撤回按钮,
只有一句 3.2 秒自动消失的 `say()`。
注释描述不存在的功能,比没有注释更糟 —— 下一个人会去找它、找不到、然后再写一个。
(撤回的正路:卡片右上角「全部」→ 任务看板,那里 done 的还在,能改回来。) */
if (d.status === 'done' && row) {
dropTaskRow(row, was);
/* 🔴 工单 #20:行被摘掉了,必须**明说**它去哪了 ——
@@ -252,47 +256,17 @@
/* ============================================================
把一条刚完成的任务行从首页列表里摘掉(工单 #20)
============================================================
三件事一起做,少一件就露馅:
① 删掉这一行;
② 同步「概述」卡片右下的「N 件在做 / N 件待办」
—— 那两个数字**与这份列表同源**(同一个 $tasks,见 _card_brief.php:53),
只删行不减去数字 ⇒ 列表 2 条、概述说 3 件在做,自相矛盾;
③ 一条不剩时把空态提示露出来(服务端渲染的 `[data-tk-empty]`,
原来一直 hidden)—— 否则卡片变成一片空白,用户以为加载坏了。
⚠️ 撤回按钮:完成的任务没地方看了,得留一条回去的路。
`data-tk-status` 已经是 done,再点一次这颗按钮就是"改回 todo",
但行已经不在 DOM 里了 —— 所以撤回走的是**直接发一次状态请求**,
成功后整页刷新把行放回去(列表的真相在服务端,前端不自己"插回一行")。 */
🔴🔴 实现在 `assets/js/app.js` 的 `window.fbHomeTaskDrop()` —— 这里只是转发。
**不要**把那段逻辑抄回来:同一个动作在首页有两个入口
(① 这颗复选框 ② 点整行弹浮层、在面板里点「已完成」,
脚本是 task_panel.js),抄两份必然只改一份。
第一版就是抄在本地,于是"从复选框勾会删行、从面板点不删",
用户复现了后者,而我的判据只测前者 ⇒ 20/0 全绿也照样是假绿。
⚠️ 保留这个薄封装是因为下面两处调用都读 `was`,就地传更清楚;
同时也让 home.php 里"删行"这件事还有一处可 grep 的落点。 */
function dropTaskRow(row, wasStatus) {
var host = row.closest('[data-tk-home-host]');
var ul = row.parentNode;
// ① 删行
if (row.parentNode) { row.parentNode.removeChild(row); }
// ② 概述卡计数:wasStatus 是"改之前"的状态 — doing 的完成就减「在做」,todo 的减「待办」
syncBriefCount(wasStatus);
// ③ 空态
if (ul && ul.querySelectorAll('[data-task-row]').length === 0) {
if (ul.parentNode) { ul.parentNode.removeChild(ul); }
var empty = host ? host.querySelector('[data-tk-empty]') : null;
if (empty) { empty.hidden = false; }
}
}
/* 概述卡的「N 件在做 / N 件待办」减 1(工单 #20)
⚠️ 用 `[data-hbs]` 定位(模板里刚加的钩子),**不靠中文文案** ——
"件在做"改一个字就失效,而这是 JS 要读的东西。
⚠️ 减到 0 时把那个 <a> 藏起来:服务端是 `if ($nTodo > 0)` 才渲染待办那一条,
前端不藏的话会出现「0 件待办」——服务端永远不会画出来的状态。 */
function syncBriefCount(wasStatus) {
if (wasStatus !== 'doing' && wasStatus !== 'todo') { return; }
var a = document.querySelector('.hb-stats a.hbs[data-hbs="' + wasStatus + '"]');
if (!a) { return; }
var b = a.querySelector('b');
if (!b) { return; }
var n = Math.max(0, (parseInt(b.textContent, 10) || 0) - 1);
b.textContent = String(n);
if (n === 0) { a.hidden = true; }
if (typeof window.fbHomeTaskDrop !== 'function') { return; }
window.fbHomeTaskDrop(row, wasStatus);
}
})();
</script>
+9 -1
View File
@@ -26,7 +26,15 @@
⇒ 勾完要把这一行**从列表里摘掉**(原来只改徽章文字,见 home.php 那段脚本)。
⚠️ 但"删到一条不剩"时卡里不能是一片空白 —— 下面这句 `empty-hint` 由服务端渲染,
前端删完不会自己冒出来,所以脚本要能在空的时候把它补上。
`data-tk-empty` 就是给脚本找它的钩子(不靠文案匹配,文案改一个字就失效)。 */ ?>
`data-tk-empty` 就是给脚本找它的钩子(不靠文案匹配,文案改一个字就失效)。
🔴🔴 把任务标成完成在首页有**两个入口**,两个都要删行:
① 每行前面那颗复选框 → 脚本在 `home.php` 末尾
② **点整行弹浮层** → 面板里的「状态」三段按钮 → `assets/js/task_panel.js`
真正干活的是 `assets/js/app.js` 的 `window.fbHomeTaskDrop()`,两个入口共用。
⚠️ 第一版只改了 ①,用户点的是 ②,于是"改完还是不消失"——
而我的判据只测了 ①,所以 20/0 全绿也是假绿。
**需求有几个入口,就要有几份判据。** */ ?>
<p class="empty-hint" data-tk-empty<?= $tasks ? ' hidden' : '' ?>>没有进行中的任务,也没有待办。</p>
<?php if ($tasks): ?>
<ul class="dash-list tk-list">