fix(task): schedule overdue tasks for today via Shift+T and guard stale task focus (#8867)

* fix(task): schedule overdue tasks for today via Shift+T and guard stale task focus (#8851)

* fix(planner): only bind data-task-id on planner-task when focusable

The unconditional data-task-id host binding broke the e2e done-confirmation
strategy in e2e/pages/task.page.ts, which selects its wait branch based on
the attribute's presence. Boards render planner-task cards without an inner
<task> element, so the id-based wait could never resolve (#8851).

* refactor(tasks): share overdue predicate across selector and util

The rebase onto #8858 re-inlined the overdue comparison into
selectOverdueTaskIds and dropped the delegation to isTaskOverdue, so the
two overdue definitions (the overdue list vs. the Shift+T "Add to Today"
path) were duplicated and free to drift — the exact footgun #8851 set out
to avoid, and the util's JSDoc still claimed the selector delegated to it.

Extract isTaskOverdueByThreshold as the single source of truth for the
comparison and getLogicalTodayStartMs for the boundary. isTaskOverdue and
selectOverdueTaskIds both route through it, so they cannot drift. The
selector still computes the threshold once per recompute (no per-task date
parsing), preserving #8858's perf posture. No behavior change.

* test(e2e): base markTaskAsDone strategy on <task> host, not attr

planner-task now carries data-task-id in the Planner overdue list (#8851),
so keying the done-confirmation strategy off data-task-id presence would send
a wrapper down the <task> path — document.querySelectorAll('task') finds no
match and the wait hangs 10s. Key it off the element actually being a <task>
instead. No behavior change for real <task> rows; every wrapper keeps the
300ms wrapper path. Verified: worklog-basic (real task) and boards #7498
(planner-task wrapper) both pass.

---------

Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
This commit is contained in:
John Costa 2026-07-10 03:07:19 -07:00 committed by GitHub
parent 6bb0472549
commit d2015beb44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 555 additions and 58 deletions

View file

@ -53,10 +53,14 @@ export class TaskPage extends BasePage {
*/
async markTaskAsDone(task: Locator): Promise<void> {
await task.waitFor({ state: 'visible' });
// `data-task-id` is only bound on the <task> host. Wrapper components such
// as <planner-task> (Boards) render the same done-toggle but expose no id,
// so the done-confirmation strategy is chosen based on its presence.
const taskId = await task.getAttribute('data-task-id');
// The done-confirmation strategy differs for real <task> rows vs. wrapper
// components (<planner-task> etc.) that render the same done-toggle. Key it
// off the element actually being a <task>, not off `data-task-id` presence:
// <planner-task> also carries `data-task-id` in the Planner overdue list
// (#8851), but querying `document.querySelectorAll('task')` for it finds
// nothing, so the <task> strategy would hang for a wrapper.
const isTaskHost = (await task.evaluate((el) => el.tagName.toLowerCase())) === 'task';
const taskId = isTaskHost ? await task.getAttribute('data-task-id') : null;
await task.hover();
// Give hover effects time to settle