Repository: obsidian-tasks-group/obsidian-tasks (ts)

Issue:
Tasks prevents checking off tasks on Canvas cards
### Please check that this issue hasn't been reported before.

- [X] I searched previous [Bug Reports](https://github.com/obsidian-tasks-group/obsidian-tasks/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+bug%22+sort%3Acomments-desc) didn't find any similar reports.

### Expected Behavior

When checking off a task that's on a Canvas card, task should become checked off

### Current behaviour

- A series of notifications pop up "Tasks: No file found for task engine. Retrying …"
- The task never gets checked off

![Screenshot 2023-07-14 at 3 55 26 PM](https://github.com/obsidian-tasks-group/obsidian-tasks/assets/1118503/89034a15-00c9-4c5e-9d76-d40bfd626d58)


### Steps to reproduce

1. Launch Obsidian Sandbox via the Open Sandbox Vault command
2. Install the Tasks plugin & Enable
3. Make a new Canvas
4. Click to make a new Card
5. Type out a task, e.g. - [ ] test task
6. Click the canvas background to deselect the card (Tasks words fine in the card when it is new and has not been deselected)
7. Click the card background to select the card
8. Attempt to tick the task checkbox

### Which Operating Systems are you using?

- [ ] Android
- [ ] iPhone/iPad
- [ ] Linux
- [X] macOS
- [ ] Windows

### Obsidian Version

1.3.5

### Tasks Plugin Version

4.2.0

### Checks

- [X] I have tried it with all other plugins disabled and the error still occurs

### Possible solution

_No response_

Pull request description:
fix: Tasks no longer prevents checking off tasks on Canvas cards
<!--- Provide a general summary of your changes in the Title above -->

# Description

Stop Tasks registering callbacks when rendering tasks that have an empty path, meaning they are likely from canvas notes.

## Motivation and Context

Fix #2130.

## How has this been tested?

- Unit tests
- Manual testing
- Running the smoke tests

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [x] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)
- [x] **Documentation** (prefix: `docs` - improvements to any documentation content **for users**)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My code follows the code style of this project and passes `yarn run lint`.
- [x] My change requires a change to the documentation.
- [x] I have [updated the documentation](https://publish.obsidian.md/tasks-contributing/Documentation/About+Documentation) accordingly.
- [x] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://publish.obsidian.md/tasks-contributing) and
[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
Put an x in the boxes to confirm you agree.
-->

- [x] My contribution follow this project's [contributing guide](https://publish.obsidian.md/tasks-contributing)
- [x] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)


Patch:
diff --git a/docs/Getting Started/Getting Started.md b/docs/Getting Started/Getting Started.md
index 6e1f0654..c3ae73e0 100644
--- a/docs/Getting Started/Getting Started.md
+++ b/docs/Getting Started/Getting Started.md
@@ -167,11 +167,6 @@ We are tracking this in [issue #1989](https://github.com/obsidian-tasks-group/ob
 
 We are tracking this in [issue #2100](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2100).
 
-> [!warning]
-> Tasks sometimes prevents checking off/completing tasks in **Obsidian Canvas cards**.
-
-We are tracking this in [issue #2130](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2130).
-
 ### Tasks in Code Blocks
 
 > [!warning]
diff --git a/src/Renderer/TaskLineRenderer.ts b/src/Renderer/TaskLineRenderer.ts
index 59819f6f..ea0a563b 100644
--- a/src/Renderer/TaskLineRenderer.ts
+++ b/src/Renderer/TaskLineRenderer.ts
@@ -133,26 +133,32 @@ export class TaskLineRenderer {
             li.classList.add('is-checked');
         }
 
-        checkbox.addEventListener('click', (event: MouseEvent) => {
-            event.preventDefault();
-            // It is required to stop propagation so that obsidian won't write the file with the
-            // checkbox (un)checked. Obsidian would write after us and overwrite our change.
-            event.stopPropagation();
-
-            // Should be re-rendered as enabled after update in file.
-            checkbox.disabled = true;
-            const toggledTasks = task.toggleWithRecurrenceInUsersOrder();
-            replaceTaskWithTasks({
-                originalTask: task,
-                newTasks: toggledTasks,
+        // If we don't have a path, the task is likely to be in a card on a canvas file,
+        // and we cannot save any edits, so there is no point listening for any events on the task.
+        // See https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2130
+        const addEventListeners = task.taskLocation.hasKnownPath;
+        if (addEventListeners) {
+            checkbox.addEventListener('click', (event: MouseEvent) => {
+                event.preventDefault();
+                // It is required to stop propagation so that obsidian won't write the file with the
+                // checkbox (un)checked. Obsidian would write after us and overwrite our change.
+                event.stopPropagation();
+
+                // Should be re-rendered as enabled after update in file.
+                checkbox.disabled = true;
+                const toggledTasks = task.toggleWithRecurrenceInUsersOrder();
+                replaceTaskWithTasks({
+                    originalTask: task,
+                    newTasks: toggledTasks,
+                });
             });
-        });
 
-        checkbox.addEventListener('contextmenu', (ev: MouseEvent) => {
-            const menu = new StatusMenu(StatusRegistry.getInstance(), task);
-            menu.showAtPosition({ x: ev.clientX, y: ev.clientY });
-        });
-        checkbox.setAttribute('title', 'Right-click for options');
+            checkbox.addEventListener('contextmenu', (ev: MouseEvent) => {
+                const menu = new StatusMenu(StatusRegistry.getInstance(), task);
+                menu.showAtPosition({ x: ev.clientX, y: ev.clientY });
+            });
+            checkbox.setAttribute('title', 'Right-click for options');
+        }
 
         li.prepend(checkbox);
 
diff --git a/src/Task/TaskLocation.ts b/src/Task/TaskLocation.ts
index 822cc504..d1f66470 100644
--- a/src/Task/TaskLocation.ts
+++ b/src/Task/TaskLocation.ts
@@ -60,4 +60,17 @@ export class TaskLocation {
     public get precedingHeader(): string | null {
         return this._precedingHeader;
     }
+
+    /**
+     * Whether the path is known, that-is, non-empty.
+     *
+     * This doesn't check whether the path points to an existing file.
+     *
+     * It was written to allow detection of tasks in Canvas cards, but note
+     * that some editing code in this plugin does not bother to set the location
+     * of the task, if not needed.
+     */
+    public get hasKnownPath(): boolean {
+        return this.path !== '';
+    }
 }


Test patch:
diff --git a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Minimal_task_-_full_mode.approved.html b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Minimal_task_-_full_mode.approved.html
index bd6dcb5e..a3e5200a 100644
--- a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Minimal_task_-_full_mode.approved.html
+++ b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Minimal_task_-_full_mode.approved.html
@@ -9,7 +9,7 @@
   data-line="0"
   data-task-status-name="Cancelled"
   data-task-status-type="CANCELLED">
-  <input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="0" />
+  <input class="task-list-item-checkbox" type="checkbox" data-line="0" />
   <span class="tasks-list-text">
     <span class="task-description"><span>empty</span></span>
   </span>
diff --git a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Minimal_task_-_short_mode.approved.html b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Minimal_task_-_short_mode.approved.html
index bd6dcb5e..a3e5200a 100644
--- a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Minimal_task_-_short_mode.approved.html
+++ b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Minimal_task_-_short_mode.approved.html
@@ -9,7 +9,7 @@
   data-line="0"
   data-task-status-name="Cancelled"
   data-task-status-type="CANCELLED">
-  <input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="0" />
+  <input class="task-list-item-checkbox" type="checkbox" data-line="0" />
   <span class="tasks-list-text">
     <span class="task-description"><span>empty</span></span>
   </span>
diff --git a/tests/Task/TaskLocation.test.ts b/tests/Task/TaskLocation.test.ts
index 901f2dad..d1c747b9 100644
--- a/tests/Task/TaskLocation.test.ts
+++ b/tests/Task/TaskLocation.test.ts
@@ -55,4 +55,9 @@ describe('TaskLocation', () => {
         expect(newLocation.sectionIndex).toStrictEqual(sectionIndex);
         expect(newLocation.precedingHeader).toStrictEqual(precedingHeader);
     });
+
+    it('should recognise unknown paths', () => {
+        expect(TaskLocation.fromUnknownPosition('x.md').hasKnownPath).toBe(true);
+        expect(TaskLocation.fromUnknownPosition('').hasKnownPath).toBe(false);
+    });
 });