diff --git a/scripts/src/hwlab-node-web-observe-runner-control-source.ts b/scripts/src/hwlab-node-web-observe-runner-control-source.ts index 400a90b5..d7a72f07 100644 --- a/scripts/src/hwlab-node-web-observe-runner-control-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-control-source.ts @@ -1120,12 +1120,14 @@ async function waitForProjectManagementCommandReady(options = {}) { while (Date.now() <= deadline) { last = await projectManagementCommandSnapshot(); const path = String(last?.path || safeUrlPath(currentPageUrl()) || ""); + const needsTask = /\/tasks\//u.test(path); + const selectedTaskReady = Boolean(last?.selectedTaskId || last?.selectedTaskRef?.hash || last?.taskBodyVisible === true || last?.launchButtonVisible === true); + const taskCollectionReady = Number(last?.taskCount || 0) > 0; const baseReady = last?.pageKind === "project-management-mdtodo" && Number(last?.sourceCount || 0) > 0 && Number(last?.fileCount || 0) > 0 - && Number(last?.taskCount || 0) > 0; - const needsTask = /\/tasks\//u.test(path); - const taskReady = !needsTask || Boolean(last?.selectedTaskId || last?.selectedTaskRef?.hash || last?.taskBodyVisible === true || last?.launchButtonVisible === true); + && (needsTask ? selectedTaskReady : taskCollectionReady); + const taskReady = !needsTask || selectedTaskReady; const needsReport = /\/reports\//u.test(path); const reportReady = !needsReport || last?.reportPreviewVisible === true || last?.reportFullscreenVisible === true; if (baseReady && taskReady && reportReady) return { ok: true, reason: "project-management-command-ready", durationMs: Date.now() - started, snapshot: last, valuesRedacted: true }; diff --git a/scripts/src/hwlab-node-web-observe-runner-sampling-source.test.ts b/scripts/src/hwlab-node-web-observe-runner-sampling-source.test.ts index c72f3596..2462b356 100644 --- a/scripts/src/hwlab-node-web-observe-runner-sampling-source.test.ts +++ b/scripts/src/hwlab-node-web-observe-runner-sampling-source.test.ts @@ -6,6 +6,7 @@ import { nodeWebObserveRunnerSamplingSource, } from "./hwlab-node-web-observe-runner-sampling-source"; import { nodeWebObserveAnalyzerProjectSource } from "./hwlab-node-web-observe-analyzer-project-source"; +import { nodeWebObserveRunnerControlSource } from "./hwlab-node-web-observe-runner-control-source"; test("MDTODO task tree pane gap treats the pinned facts footer as content", async () => { assert.match( @@ -93,3 +94,40 @@ test("MDTODO pane gap ignores a naturally short task tree but keeps primary pane 1, ); }); + +test("MDTODO command readiness accepts a selected mobile task when the hidden tree has no visible rows", async () => { + const source = nodeWebObserveRunnerControlSource(); + const makeReady = (snapshot: Record) => new Function( + "projectManagementCommandSnapshot", + "safeUrlPath", + "currentPageUrl", + "page", + `${source}; return waitForProjectManagementCommandReady;`, + )( + async () => snapshot, + () => String(snapshot.path ?? ""), + () => `https://hwlab.pikapython.com${String(snapshot.path ?? "")}`, + { waitForTimeout: async () => undefined }, + ) as (options: { timeoutMs: number }) => Promise<{ ok: boolean; reason: string }>; + + const selectedMobileTask = { + path: "/projects/mdtodo/sources/source/files/file/tasks/R1", + pageKind: "project-management-mdtodo", + sourceCount: 3, + fileCount: 18, + taskCount: 0, + selectedTaskRef: { hash: "sha256:opaque" }, + taskBodyVisible: true, + launchButtonVisible: true, + }; + assert.equal((await makeReady(selectedMobileTask)({ timeoutMs: 1 })).ok, true); + + const unselectedRoot = { + path: "/projects/mdtodo", + pageKind: "project-management-mdtodo", + sourceCount: 3, + fileCount: 18, + taskCount: 0, + }; + assert.equal((await makeReady(unselectedRoot)({ timeoutMs: 1 })).ok, false); +});