diff --git a/src/sidebar/components/ProjectPanel.context-menu.test.tsx b/src/sidebar/components/ProjectPanel.context-menu.test.tsx index f5f0b17b..4e98245e 100644 --- a/src/sidebar/components/ProjectPanel.context-menu.test.tsx +++ b/src/sidebar/components/ProjectPanel.context-menu.test.tsx @@ -370,7 +370,7 @@ function registerTaskPanelTransport({ fake, discoveryResult }: { fake: FakeTrans const wg = discoveryResult.workgroups.find(wg => wg.path === workgroupRoot); return sidebarSnapshot(String(workgroupRoot), { taskTitle: wg?.taskTitle ?? null, - description: wg?.task?.includes("stale body") ? "stale body text" : "Ready to start a new topic\n", + description: wg?.task?.includes("stale body") ? "stale body text" : "", }); }); fake.resolve("task_clean", { workgroupRoot: workgroupPath, task: null }); @@ -461,14 +461,19 @@ describe("ProjectPanel replica context menu — gray/red (#545)", () => { for (const active of [false, true]) { it.each([ - ["canonical LF", "Ready to start a new topic\n", true], + ["legacy LF", "Ready to start a new topic\n", true], ["CRLF", "Ready to start a new topic\r\n", true], ["no framing LF", "Ready to start a new topic", true], ["extra blank", "Ready to start a new topic\n\n", false], ["extra space", "Ready to start a new topic \n", false], ["case change", "ready to start a new topic\n", false], ["custom multiline", "human\nnotes", false], - ["empty body", "", false], + ["empty body", "", true], + ["space", " ", false], + ["tab", " ", false], + ["LF", "\n", false], + ["CRLF whitespace", "\r\n", false], + ["multiple LF", "\n\n", false], ])(`Clean description %s in ${active ? "active" : "inactive"} menu`, async (_label, description, disabled) => { const fake = await setupPanel(active ? [coordSession(), memberSession()] : [], projectDiscovery("Clean")); fake.resolve("task_get_snapshot_at", sidebarSnapshot(workgroupPath, { taskTitle: "Clean", description })); @@ -479,12 +484,13 @@ describe("ProjectPanel replica context menu — gray/red (#545)", () => { expect(broom.title).toBe(disabled ? "Nothing to clear" : "Clear task title"); }); - it(`allows status and unknown snapshots in ${active ? "active" : "inactive"} menu`, async () => { + it.each(["title", "status", "status record"])(`allows %s and unknown snapshots in ${active ? "active" : "inactive"} menu`, async (guard) => { const fake = await setupPanel(active ? [coordSession(), memberSession()] : [], projectDiscovery("Clean")); fake.resolve("task_get_snapshot_at", sidebarSnapshot(workgroupPath, { - taskTitle: "Clean", description: "Ready to start a new topic\n", status: "complete status", - statusRecord: { schemaVersion: 1, kind: "status", topicId: "topic", sequence: 1, - requestId: null, baseRevision: null, recordedAt: "now", author: null, status: "complete status" }, + taskTitle: guard === "title" ? "USER title" : "Clean", description: "", + status: guard === "status" ? "complete status" : null, + statusRecord: guard === "status record" ? { schemaVersion: 1, kind: "status", topicId: "topic", sequence: 1, + requestId: null, baseRevision: null, recordedAt: "now", author: null, status: "complete status" } : null, })); await projectStore.refreshTaskSnapshot(workgroupPath); contextMenu(findRow(rendered!.root, memberRowTestId)); diff --git a/src/sidebar/components/ProjectPanel.tsx b/src/sidebar/components/ProjectPanel.tsx index 253b1c52..e57e1679 100644 --- a/src/sidebar/components/ProjectPanel.tsx +++ b/src/sidebar/components/ProjectPanel.tsx @@ -496,6 +496,7 @@ function taskCleanDisabled(room: string): boolean { const snapshot = projectStore.taskSnapshot(room); if (!snapshot || snapshot.statusRecord?.kind === "status" || !isTaskClean(snapshot.taskTitle)) return false; if (snapshot.status !== null) return false; + if (snapshot.description === "") return true; const description = snapshot.description.replace(/\r\n/g, "\n").replace(/\n$/, ""); return description === "Ready to start a new topic"; } diff --git a/src/sidebar/stores/project.test.ts b/src/sidebar/stores/project.test.ts index 52eec46f..beb4ac9f 100644 --- a/src/sidebar/stores/project.test.ts +++ b/src/sidebar/stores/project.test.ts @@ -156,10 +156,11 @@ describe("projectStore", () => { const old = deferred(); m.getSnapshot.mockReturnValueOnce(old.promise); const pending = projectStore.refreshTaskSnapshot(wg.path); - m.getSnapshot.mockResolvedValueOnce(taskSnapshot(wg.path, { taskTitle: "Clean", description: "Ready to start a new topic\n", status: null })); + m.getSnapshot.mockResolvedValueOnce(taskSnapshot(wg.path, { taskTitle: "Clean", description: "", status: null })); await projectStore.refreshTaskSnapshot(wg.path); old.resolve(taskSnapshot(wg.path, { status: "before Clean" })); await pending; + expect(projectStore.taskSnapshot(wg.path)?.description).toBe(""); expect(projectStore.taskSnapshot(wg.path)?.taskTitle).toBe("Clean"); expect(projectStore.taskSnapshot(wg.path)?.status).toBeNull(); });