Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2840 (2024-05-20 18:32:37)

feat: New "On Completion" field with support for Delete automatic action
The "**On Completion**" feature adds the ability for the Tasks plugin to perform certain actions on tasks automatically at the time they are completed.

# Description

The feature adds a new, optional field to the Tasks task object:  `onCompletion`, represented by the checkered flag emoji 🏁, followed by a string denoting the desired _**action**_.)  

This first release supports only a single _**action**_ ('delete').  Additional actions (to move the completed task either to a "completed tasks" list within its host note or to a dedicated "completed tasks" note file) are planned.

[Note:  the _Create or Edit Task.md_ file also will need to be updated once the `onCompletion` field is added to the **Create/Edit Task** modal.]

## Motivation and Context
Several users have submitted Issues relating to actions that they want the Tasks plugin to take _automatically_ as soon as they complete a given task.

<!--- Why is this change required? What problem does it solve? -->

One such action is to make completed instances of certain tasks disappear (especially common in connection with frequently-recurring tasks.)  For reference, see the following issues and discussions:

<!--- If it fixes an open issue, please link to the issue here. -->    

- https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1647
- https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/2318
- https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/2478
- https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2547

## How has this been tested?

Automated tests have been added 
- in connection with the new Tasks field;
- in connection with the new OnCompletion feature generally; and
- in connection with the "delete" action specifically

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

## Screenshots (if appropriate)
Screenshot(s) pending update to the **_Create/Edit Task_** modal dialog.)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [ ] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)
- [x] **New feature** (prefix: `feat` - non-breaking change which adds functionality)
- [ ] **Breaking change** (prefix: `feat!!` or `fix!!` - fix or feature that would cause existing functionality to not work as expected)
- [x] **Documentation** (prefix: `docs` - improvements to any documentation content **for users**)
- [ ] **Sample vault** (prefix: `vault` - improvements to the [Tasks-Demo sample vault](https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/resources/sample_vaults/Tasks-Demo))
- [ ] **Contributing Guidelines** (prefix: `contrib` - any improvements to documentation content **for contributors** - see [Contributing to Tasks](https://publish.obsidian.md/tasks-contributing/))

Internal changes:

- [ ] **Refactor** (prefix: `refactor` - non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)
- [ ] **Infrastructure** (prefix: `chore` - examples include GitHub Actions, issue templates)

## 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 follows 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)


claremacrae: Thanks Tom!
therden: You're welcome Clare.

Is it best for _me_ to resolve the conflicts in the Dataview and Default task serializer files, or...?
claremacrae: > Is it best for _me_ to resolve the conflicts in the Dataview and Default task serializer files, or...?

I'll have a go...

claremacrae: Yes, that worked - the resolved conflicts after the merge can be seen in: 7657ae3af7c2498ad8fb4a43855147e69f100e52
therden: Terrific -- thank you Clare!

I've just edited my original comment in this pull request's conversation, adding links to the open Issues and Conversations that concern (at least in part) automatic deletion of completed tasks.
claremacrae: @therden Unfortunately deleting your branch has closed this PR...

claremacrae: The commits are on my machine, from when I did the merge yesterday... 
therden: I learned something too... maybe a couple of things 😅

On Tue, May 21, 2024 at 12:49 PM Clare Macrae ***@***.***>
wrote:

> Ah right, I'm glad you figured it out, and thanks for fixing it.
>
> GitHub tries to set up a sensible initial title for the PR, based on the
> branch name - but after that, the branch name and PR title are totally
> independent things...
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2840#issuecomment-2123041240>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAYG2KMEK52HITERX52PP2LZDN3JJAVCNFSM6AAAAABIAF36GSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMRTGA2DCMRUGA>
> .
> You are receiving this because you modified the open/close state.Message
> ID: ***@***.***>
>

therden: Hi @claremacrae,

Upon further thought, I'd like to choose an alternative identifier for the default, no-op action for this feature.  

Because (except for "delete") all the other actions we've contemplated also preserve the task -- just not in its original location -- I think that "keep" could be confusing.  In its place, I'd suggest something like "none", "nothing", or "ignore".

I welcome your thoughts...
claremacrae: Tom, thanks for the thoughts... I see what you mean - sorry but I don't have the capacity right now to say anything useful.

I agree with your point about "keep" - but am not sure which of "none", "nothing", or "ignore" - or anything else to pick.

So, please feel free to pick one and go ahead and use it for now, in the enum move...

The nice thing about the enum approach is WebStorm will do most of the work of updating the code if we refine ideas later...
bastislack: Can't wait to see this feature!
Thank you so much, both of you :)
claremacrae: Hi Tom,

I'm reviewing the changes now - so please, no more rebases or merges to the branch.

If I end up requesting any changes, they can be added as new commits without needing to merge in any newer changes - that will get taken care of in the merge process....

Cheers!
therden: Hi Clare,

Understood -- thanks!

On June 30, 2024, at 9:26 AM, Clare Macrae ***@***.***> wrote:

Hi Tom,

I'm reviewing the changes now - so please, no more rebases or merges to the branch.

If I end up requesting any changes, they can be added as new commits without needing to merge in any newer changes - that will get taken care of in the merge process....

Cheers!

—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you modified the open/close state.￼Message ID: ***@***.***>

[ { ***@***.***": "<a href="http://schema.org">http://schema.org</a>", ***@***.***": "EmailMessage", "potentialAction": { ***@***.***": "ViewAction", "target": "<a href="https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2840#issuecomment-2198563428">https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2840#issuecomment-2198563428</a>", "url": "<a href="https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2840#issuecomment-2198563428">https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2840#issuecomment-2198563428</a>", "name": "View Pull Request" }, "description": "View this Pull Request on GitHub", "publisher": { ***@***.***": "Organization", "name": "GitHub", "url": "<a href="https://github.com">https://github.com</a>" } } ]
claremacrae: I am mystified by the test failure. I've got the code checked out on my Mac and it runs fine.

I've made GitHub Actions re-run the build and fails.

And there's no way that the editorconfig change could have changed the test behaviour...

therden: That *does* seem weird.

Meanwhile:  without my having touched them, immediately after I pushed the
commit featuring the .editorconfig change yesterday, WebStorm started
reporting that EditTask.svelte and RecurrenceEditor.svelte have changed...

On Tue, Jul 2, 2024 at 5:19 AM Clare Macrae ***@***.***>
wrote:

> I am mystified by the test failure. I've got the code checked out on my
> Mac and it runs fine.
>
> I've made GitHub Actions re-run the build and fails.
>
> And there's no way that the editorconfig change could have changed the
> test behaviour...
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2840#issuecomment-2202490809>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAYG2KMR6EFFIXFTXP6QR6LZKJWBNAVCNFSM6AAAAABIAF36GSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMBSGQ4TAOBQHE>
> .
> You are receiving this because you modified the open/close state.Message
> ID: ***@***.***>
>

claremacrae: > That *does* seem weird. Meanwhile: without my having touched them, immediately after I pushed the commit featuring the .editorconfig change yesterday, WebStorm started reporting that EditTask.svelte and RecurrenceEditor.svelte have changed...

Oh dear....

OK, please could you try adding .svelte to the `.gitattributes` file on your branch and see if it fixes the diffs?

I suspect it will... You might need to do something - like maybe delete them and then revert the deletion - to get it to set their EOLs to LF???


therden: Your suggestion worked a treat --  thank you Clare.
I've pushed the commit with the edited .gitattributes file to my fork.

On Tue, Jul 2, 2024 at 1:35 PM Clare Macrae ***@***.***>
wrote:

> That *does* seem weird. Meanwhile: without my having touched them,
> immediately after I pushed the commit featuring the .editorconfig change
> yesterday, WebStorm started reporting that EditTask.svelte and
> RecurrenceEditor.svelte have changed...
>
> Oh dear....
>
> OK, please could you try adding .svelte to the .gitattributes file on
> your branch and see if it fixes the diffs?
>
> I suspect it will... You might need to do something - like maybe delete
> them and then revert the deletion - to get it to set their EOLs to LF???
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2840#issuecomment-2203922315>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAYG2KMHY5EFKXJWSXVYLOTZKLQEVAVCNFSM6AAAAABIAF36GSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMBTHEZDEMZRGU>
> .
> You are receiving this because you modified the open/close state.Message
> ID: ***@***.***>
>

claremacrae: Now that Tasks 7.7.0 is out I will work on merging this. 🎉 
sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [C Reliability Rating on New Code](https://sonarcloud.io/dashboard?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840) (required ≥ A)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [5 Accepted issues](https://sonarcloud.io/project/issues?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [5 Accepted issues](https://sonarcloud.io/project/issues?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=obsidian-tasks-group_obsidian-tasks&pullRequest=2840)



Patch:
diff --git a/.gitattributes b/.gitattributes
index ec019ea6..71853a2d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -9,6 +9,7 @@
 *.ps1 text eol=lf
 *.py text eol=lf
 *.sh text eol=lf
+*.svelte text eol=lf
 *.text text eol=lf
 *.ts text eol=lf
 *.txt text eol=lf
diff --git a/docs/Advanced/Styling.md b/docs/Advanced/Styling.md
index 6d16eab3..159ce5ff 100644
--- a/docs/Advanced/Styling.md
+++ b/docs/Advanced/Styling.md
@@ -73,7 +73,7 @@ To help visualise the structure above, here is the HTML for a sample task shown
 <!-- snippet: TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html -->
 ```html
 <!--
-  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c
+  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done 🏁 delete ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c
 -->
 
 <li
@@ -96,6 +96,7 @@ To help visualise the structure above, here is the HTML for a sample task shown
     <span class="task-dependsOn"><span>⛔ 123456,abc123</span></span>
     <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
     <span class="task-recurring"><span>🔁 every day when done</span></span>
+    <span class="task-onCompletion"><span>🏁 delete</span></span>
     <span class="task-created" data-task-created="past-4d"><span>➕ 2023-07-01</span></span>
     <span class="task-start" data-task-start="past-3d"><span>🛫 2023-07-02</span></span>
     <span class="task-scheduled" data-task-scheduled="past-2d"><span>⏳ 2023-07-03</span></span>
@@ -118,7 +119,7 @@ Here is the same task in [[Layout#Short Mode|short mode]]. The only difference i
 <!-- snippet: TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html -->
 ```html
 <!--
-  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c
+  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done 🏁 delete ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c
 -->
 
 <li
@@ -141,6 +142,7 @@ Here is the same task in [[Layout#Short Mode|short mode]]. The only difference i
     <span class="task-dependsOn"><span>⛔</span></span>
     <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
     <span class="task-recurring"><span>🔁</span></span>
+    <span class="task-onCompletion"><span>🏁</span></span>
     <span class="task-created" data-task-created="past-4d"><span>➕</span></span>
     <span class="task-start" data-task-start="past-3d"><span>🛫</span></span>
     <span class="task-scheduled" data-task-scheduled="past-2d"><span>⏳</span></span>
diff --git a/docs/Getting Started/About Getting Started.md b/docs/Getting Started/About Getting Started.md
index c9ce1872..f324857e 100644
--- a/docs/Getting Started/About Getting Started.md
+++ b/docs/Getting Started/About Getting Started.md
@@ -27,6 +27,8 @@ publish: true
   - Indicate the importance of your tasks.
 - [[Recurring Tasks]]
   - Use recurring tasks to easily track actions you must do regularly.
+- [[On Completion]]
+  - Specify an automatic action to perform on a task when it is completed.
 - [[Statuses]]
   - Also known as Custom Checkboxes.
   - In vanilla Obsidian tasks are either 'TODO' or 'DONE'.
diff --git a/docs/Getting Started/On Completion.md b/docs/Getting Started/On Completion.md
new file mode 100644
index 00000000..35e2a66f
--- /dev/null
+++ b/docs/Getting Started/On Completion.md
@@ -0,0 +1,75 @@
+---
+publish: true
+---
+# On Completion
+> [!tip]
+> If you have ever wished that Tasks would automatically *do something* with the tasks that you complete (*especially* likely if you use [[Recurring Tasks|recurring tasks]], which tend to accumulate within the note that holds them), then the **"On Completion"** feature could be the answer!
+
+> [!released]
+> Introduced in Tasks X.Y.Z.
+## Introduction
+Obsidian Tasks can automatically perform an action upon a task when it is marked 'done'.
+
+This feature is enabled by adding (*after* the description within a task) a field consisting of:
+
+- the *checkered flag* 🏁 emoji ***signifier***, followed by
+- a string identifying the desired ***action*** to take when the item is completed.
+
+> [!info]
+> If you are using [[Dataview Format]] for Tasks,  instead of the 🏁 emoji the desired ***action*** would follow the double colons in `[onCompletion:: ]`.)
+
+## Supported actions
+
+At present, two "On Completion" ***actions*** are supported:
+
+1. **Ignore** &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Nothing will be done with the just-completed task.  (This is the default action.)
+2. **Delete** &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Removes the completed instance of the task.
+
+> [!tip]
+> Two additional ***actions*** are being considered for future implementation, namely moving a just-completed task to:
+>
+> - either a separate "completed tasks archive" note,
+> - or to a "completed tasks list" within the original host note.
+>
+> We are tracking these ideas in [issue #2855](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2855) and [issue #2856](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2856), respectively.
+
+## Worked examples
+
+> [!info]
+> In order to keep it simple, the following assumes that **Tasks** is configured so as not to use a [[Global Filter]] nor  add a [[Dates#Done date]] when you complete a task.
+
+> [!example] Imagine that your vault consists of a single note file `My Project.md`, with these contents:
+>
+> ```text
+> # My Project Tasks
+> - [ ] Leave me alone
+> - [ ] Leave me alone too! 🏁 ignore
+> - [ ] Delete me upon completion 🏁 delete
+> - [ ] Delete my completed instance, leave my next instance 📅 2021-05-20 🔁 every day when done 🏁 delete
+> ```
+
+> [!success] Using the plugin's default settings, after *using Tasks* to mark each task as "done", your `My Project` note file will contain:
+>
+> ```text
+> # My Project Tasks
+> - [x] Leave me alone
+> - [x] Leave me alone too! 🏁 ignore
+> - [ ] Delete my completed instance, leave my next instance 📅 2021-05-21 🔁 every day when done 🏁 delete
+> ```
+
+> [!note] Note that
+>
+> - The task assigned the `ignore` action is treated the same as one that has no onCompletion field at all, and
+> - The next instance of the recurring task has replaced the original, completed instance.
+
+> [!warning]
+> Currently, `🏁 ignore`  has some slightly confusing behaviour:
+>
+> - When viewed in Reading mode, `🏁 ignore` is not shown.
+> - When tasks are completed, any `🏁 ignore` text is deleted.
+
+## Assigning and changing a given task's "On Completion" action
+
+At present, the "On Completion" signifier and desired **Action** identifier have to be added to a task manually.
+
+In recognition that this could be both tedious and error-prone, we will eventually enable the [[Create or edit Task|'Create or Edit Task' modal dialog]] to edit the "On Completion" **Action**.
diff --git a/docs/How To/Find tasks with invalid data.md b/docs/How To/Find tasks with invalid data.md
index c83d389d..c832fb57 100644
--- a/docs/How To/Find tasks with invalid data.md
+++ b/docs/How To/Find tasks with invalid data.md
@@ -32,7 +32,7 @@ The following tasks block lists any tasks that have emoji in the description, wh
 ````text
 ```tasks
 # These instructions need to be all on one line:
-(description includes 🔺) OR (description includes ⏫) OR (description includes 🔼) OR (description includes 🔽) OR (description includes ⏬) OR (description includes 🛫) OR (description includes ➕) OR (description includes ⏳) OR (description includes 📅) OR (description includes ✅) OR (description includes ❌) OR (description includes 🔁) OR (description includes ⛔) OR (description includes 🆔)
+(description includes 🔺) OR (description includes ⏫) OR (description includes 🔼) OR (description includes 🔽) OR (description includes ⏬) OR (description includes 🛫) OR (description includes ➕) OR (description includes ⏳) OR (description includes 📅) OR (description includes ✅) OR (description includes ❌) OR (description includes 🔁) OR (description includes 🏁) OR (description includes ⛔) OR (description includes 🆔)
 
 # Optionally, uncomment this line and exclude your templates location
 # path does not include _templates
diff --git a/docs/Reference/Task Formats/Dataview Format.md b/docs/Reference/Task Formats/Dataview Format.md
index 39d570e2..5374641d 100644
--- a/docs/Reference/Task Formats/Dataview Format.md
+++ b/docs/Reference/Task Formats/Dataview Format.md
@@ -131,6 +131,17 @@ For more information, see [[Priority]].
 
 For more information, see [[Recurring Tasks]].
 
+### Dataview Format for OnCompletion
+
+<!-- snippet: DocsSamplesForTaskFormats.test.Serializer_OnCompletion_dataview-snippet.approved.md -->
+```md
+- [ ] #task Remove this task when done  [onCompletion:: delete]
+- [ ] #task Remove completed instance of this recurring task when done  [repeat:: every day]  [onCompletion:: delete]
+```
+<!-- endSnippet -->
+
+For more information, see [[On Completion]].
+
 ### Dataview Format for Dependencies
 
 <!-- snippet: DocsSamplesForTaskFormats.test.Serializer_Dependencies_dataview-snippet.approved.md -->
diff --git a/docs/Reference/Task Formats/Tasks Emoji Format.md b/docs/Reference/Task Formats/Tasks Emoji Format.md
index a0881edf..7ac20426 100644
--- a/docs/Reference/Task Formats/Tasks Emoji Format.md
+++ b/docs/Reference/Task Formats/Tasks Emoji Format.md
@@ -48,6 +48,17 @@ For more information, see [[Priority]].
 
 For more information, see [[Recurring Tasks]].
 
+## Tasks Emoji Format for OnCompletion
+
+<!-- snippet: DocsSamplesForTaskFormats.test.Serializer_OnCompletion_tasksPluginEmoji-snippet.approved.md -->
+```md
+- [ ] #task Remove this task when done 🏁 delete
+- [ ] #task Remove completed instance of this recurring task when done 🔁 every day 🏁 delete
+```
+<!-- endSnippet -->
+
+For more information, see [[On Completion]].
+
 ## Tasks Emoji  Format for Dependencies
 
 <!-- snippet: DocsSamplesForTaskFormats.test.Serializer_Dependencies_tasksPluginEmoji-snippet.approved.md -->
diff --git a/docs/Scripting/Task Properties.md b/docs/Scripting/Task Properties.md
index 7101129f..47cb9361 100644
--- a/docs/Scripting/Task Properties.md
+++ b/docs/Scripting/Task Properties.md
@@ -145,8 +145,9 @@ For more information, including adding your own customised statuses, see [[Statu
 | `task.urgency` | `number` | `3.3000000000000007` | `number` | `1.9500000000000002` |
 | `task.isRecurring` | `boolean` | `true` | `boolean` | `false` |
 | `task.recurrenceRule` | `string` | `'every day when done'` | `string` | `''` |
+| `task.onCompletion` | `string` | `'delete'` | `string` | `''` |
 | `task.tags` | `string[]` | `['#todo', '#health']` | `any[]` | `[]` |
-| `task.originalMarkdown` | `string` | `'  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c'` | `string` | `'- [/] minimal task'` |
+| `task.originalMarkdown` | `string` | `'  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done 🏁 delete ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c'` | `string` | `'- [/] minimal task'` |
 
 <!-- placeholder to force blank line after included text --><!-- endInclude -->
 
diff --git a/src/Commands/CreateOrEditTaskParser.ts b/src/Commands/CreateOrEditTaskParser.ts
index a7490be6..37a678ad 100644
--- a/src/Commands/CreateOrEditTaskParser.ts
+++ b/src/Commands/CreateOrEditTaskParser.ts
@@ -1,5 +1,6 @@
 import { TasksFile } from '../Scripting/TasksFile';
 import { Status } from '../Statuses/Status';
+import { OnCompletion } from '../Task/OnCompletion';
 import { Task } from '../Task/Task';
 import { DateFallback } from '../Task/DateFallback';
 import { StatusRegistry } from '../Statuses/StatusRegistry';
@@ -92,6 +93,7 @@ export const taskFromLine = ({ line, path }: { line: string; path: string }): Ta
             doneDate: null,
             cancelledDate: null,
             recurrence: null,
+            onCompletion: OnCompletion.Ignore,
             dependsOn: [],
             id: '',
             blockLink: '',
@@ -132,6 +134,7 @@ export const taskFromLine = ({ line, path }: { line: string; path: string }): Ta
         doneDate: null,
         cancelledDate: null,
         recurrence: null,
+        onCompletion: OnCompletion.Ignore,
         tags: [],
         originalMarkdown: '',
         // Not needed since the inferred status is always re-computed after submitting.
diff --git a/src/Layout/TaskLayoutOptions.ts b/src/Layout/TaskLayoutOptions.ts
index fb491eb9..793ac643 100644
--- a/src/Layout/TaskLayoutOptions.ts
+++ b/src/Layout/TaskLayoutOptions.ts
@@ -11,6 +11,7 @@ export enum TaskLayoutComponent {
     DependsOn = 'dependsOn',
     Priority = 'priority',
     RecurrenceRule = 'recurrenceRule',
+    OnCompletion = 'onCompletion',
     CreatedDate = 'createdDate',
     StartDate = 'startDate',
     ScheduledDate = 'scheduledDate',
diff --git a/src/Renderer/TaskFieldRenderer.ts b/src/Renderer/TaskFieldRenderer.ts
index 761b7a3a..99cd73ed 100644
--- a/src/Renderer/TaskFieldRenderer.ts
+++ b/src/Renderer/TaskFieldRenderer.ts
@@ -177,6 +177,7 @@ const taskFieldHTMLData: { [c in TaskLayoutComponent]: TaskFieldHTMLData } = {
 
     description: createFieldWithoutDataAttributes('task-description'),
     recurrenceRule: createFieldWithoutDataAttributes('task-recurring'),
+    onCompletion: createFieldWithoutDataAttributes('task-onCompletion'),
     dependsOn: createFieldWithoutDataAttributes('task-dependsOn'),
     id: createFieldWithoutDataAttributes('task-id'),
     blockLink: createFieldWithoutDataAttributes('task-block-link'),
diff --git a/src/Task/OnCompletion.ts b/src/Task/OnCompletion.ts
index 3946e67f..b06a3276 100644
--- a/src/Task/OnCompletion.ts
+++ b/src/Task/OnCompletion.ts
@@ -1,6 +1,16 @@
+import { Notice } from 'obsidian';
 import { StatusType } from '../Statuses/StatusConfiguration';
 import type { Task } from './Task';
 
+export enum OnCompletion {
+    Ignore = '',
+    Delete = 'delete',
+}
+
+function returnWithoutCompletedInstance(tasks: Task[], changedStatusTask: Task) {
+    return tasks.filter((task) => task !== changedStatusTask);
+}
+
 export function handleOnCompletion(task: Task, tasks: Task[]): Task[] {
     const tasksArrayLength = tasks.length;
     if (tasksArrayLength === 0) {
@@ -11,23 +21,19 @@ export function handleOnCompletion(task: Task, tasks: Task[]): Task[] {
     const changedStatusTask = tasks[tasksArrayLength - 1];
     const endStatus = changedStatusTask.status;
 
-    const ocTrigger = ' 🏁 ';
-    const taskString = changedStatusTask.description;
-
-    if (!taskString.includes(ocTrigger) || endStatus.type !== StatusType.DONE || endStatus.type === startStatus.type) {
+    if (!task.onCompletion || endStatus.type !== StatusType.DONE || endStatus.type === startStatus.type) {
         return tasks;
     }
 
-    if (taskString.includes('🏁 Delete')) {
-        return tasks.filter((task) => task !== changedStatusTask);
+    const ocAction = task.onCompletion.toLowerCase();
+
+    if ('delete' === ocAction) {
+        return returnWithoutCompletedInstance(tasks, changedStatusTask);
     }
-    // const errorMessage = 'Unknown "On Completion" action: ' + ocAction;
-    const errorMessage = 'Unknown "On Completion" action';
-    console.log(errorMessage);
+
+    const errorText = 'Unknown "On Completion" action: ' + task.onCompletion;
+    const hintText = '\nClick here to clear';
+    const noticeText = errorText + hintText;
+    new Notice(noticeText, 0);
     return tasks;
-    // const hint = '\nClick here to clear';
-    // const noticeMessage = errorMessage + hint;
-    // new Notice(noticeMessage, 0);
-    // console.log('Uh-oh -- we should never actually get here...  :( ');
-    // throw new Error('Something went wrong');
 }
diff --git a/src/Task/Task.ts b/src/Task/Task.ts
index 0355263e..bb77727b 100644
--- a/src/Task/Task.ts
+++ b/src/Task/Task.ts
@@ -18,6 +18,7 @@ import type { Recurrence } from './Recurrence';
 import type { TaskLocation } from './TaskLocation';
 import type { Priority } from './Priority';
 import { TaskRegularExpressions } from './TaskRegularExpressions';
+import { OnCompletion, handleOnCompletion } from './OnCompletion';
 
 /**
  * Storage for the task line, broken down in to sections.
@@ -60,6 +61,7 @@ export class Task extends ListItem {
     public readonly cancelledDate: Moment | null;
 
     public readonly recurrence: Recurrence | null;
+    public readonly onCompletion: OnCompletion;
 
     public readonly dependsOn: string[];
     public readonly id: string;
@@ -87,6 +89,7 @@ export class Task extends ListItem {
         doneDate,
         cancelledDate,
         recurrence,
+        onCompletion,
         dependsOn,
         id,
         blockLink,
@@ -109,6 +112,7 @@ export class Task extends ListItem {
         doneDate: moment.Moment | null;
         cancelledDate: moment.Moment | null;
         recurrence: Recurrence | null;
+        onCompletion: OnCompletion;
         dependsOn: string[] | [];
         id: string;
         blockLink: string;
@@ -137,6 +141,7 @@ export class Task extends ListItem {
         this.cancelledDate = cancelledDate;
 
         this.recurrence = recurrence;
+        this.onCompletion = onCompletion;
 
         this.dependsOn = dependsOn;
         this.id = id;
@@ -482,8 +487,9 @@ export class Task extends ListItem {
     }
 
     private putRecurrenceInUsersOrder(newTasks: Task[]) {
+        const potentiallyPrunedTasks = handleOnCompletion(this, newTasks);
         const { recurrenceOnNextLine } = getSettings();
-        return recurrenceOnNextLine ? newTasks.reverse() : newTasks;
+        return recurrenceOnNextLine ? potentiallyPrunedTasks.reverse() : potentiallyPrunedTasks;
     }
 
     /**
@@ -833,6 +839,7 @@ export class Task extends ListItem {
             'scheduledDateIsInferred',
             'id',
             'dependsOn',
+            'onCompletion',
         ];
         for (const el of args) {
             if (this[el]?.toString() !== other[el]?.toString()) return false;
diff --git a/src/TaskSerializer/DataviewTaskSerializer.ts b/src/TaskSerializer/DataviewTaskSerializer.ts
index ed60a386..d67cc3e1 100644
--- a/src/TaskSerializer/DataviewTaskSerializer.ts
+++ b/src/TaskSerializer/DataviewTaskSerializer.ts
@@ -75,6 +75,7 @@ export const DATAVIEW_SYMBOLS = {
     doneDateSymbol: 'completion::',
     cancelledDateSymbol: 'cancelled::',
     recurrenceSymbol: 'repeat::',
+    onCompletionSymbol: 'onCompletion::',
     idSymbol: 'id::',
     dependsOnSymbol: 'dependsOn::',
     TaskFormatRegularExpressions: {
@@ -86,6 +87,7 @@ export const DATAVIEW_SYMBOLS = {
         doneDateRegex: toInlineFieldRegex(/completion:: *(\d{4}-\d{2}-\d{2})/),
         cancelledDateRegex: toInlineFieldRegex(/cancelled:: *(\d{4}-\d{2}-\d{2})/),
         recurrenceRegex: toInlineFieldRegex(/repeat:: *([a-zA-Z0-9, !]+)/),
+        onCompletionRegex: toInlineFieldRegex(/onCompletion:: *([a-zA-Z]+)/),
         dependsOnRegex: toInlineFieldRegex(new RegExp('dependsOn:: *(' + taskIdSequenceRegex.source + ')')),
         idRegex: toInlineFieldRegex(new RegExp('id:: *(' + taskIdRegex.source + ')')),
     },
diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts
index 700da15b..5c75010f 100644
--- a/src/TaskSerializer/DefaultTaskSerializer.ts
+++ b/src/TaskSerializer/DefaultTaskSerializer.ts
@@ -1,5 +1,6 @@
 import type { Moment } from 'moment';
 import { TaskLayoutComponent, TaskLayoutOptions } from '../Layout/TaskLayoutOptions';
+import { OnCompletion } from '../Task/OnCompletion';
 import { Occurrence } from '../Task/Occurrence';
 import { Recurrence } from '../Task/Recurrence';
 import { Task } from '../Task/Task';
@@ -30,6 +31,7 @@ export interface DefaultTaskSerializerSymbols {
     readonly doneDateSymbol: string;
     readonly cancelledDateSymbol: string;
     readonly recurrenceSymbol: string;
+    readonly onCompletionSymbol: string;
     readonly idSymbol: string;
     readonly dependsOnSymbol: string;
     readonly TaskFormatRegularExpressions: {
@@ -41,6 +43,7 @@ export interface DefaultTaskSerializerSymbols {
         doneDateRegex: RegExp;
         cancelledDateRegex: RegExp;
         recurrenceRegex: RegExp;
+        onCompletionRegex: RegExp;
         idRegex: RegExp;
         dependsOnRegex: RegExp;
     };
@@ -73,6 +76,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = {
     doneDateSymbol: '✅',
     cancelledDateSymbol: '❌',
     recurrenceSymbol: '🔁',
+    onCompletionSymbol: '🏁',
     dependsOnSymbol: '⛔',
     idSymbol: '🆔',
     TaskFormatRegularExpressions: {
@@ -87,6 +91,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = {
         doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u,
         cancelledDateRegex: /❌ *(\d{4}-\d{2}-\d{2})$/u,
         recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu,
+        onCompletionRegex: /🏁 ?([a-zA-Z]+)$/iu,
         dependsOnRegex: new RegExp('⛔\uFE0F? *(' + taskIdSequenceRegex.source + ')$', 'iu'),
         idRegex: new RegExp('🆔 *(' + taskIdRegex.source + ')$', 'iu'),
     },
@@ -156,6 +161,7 @@ export class DefaultTaskSerializer implements TaskSerializer {
             doneDateSymbol,
             cancelledDateSymbol,
             recurrenceSymbol,
+            onCompletionSymbol,
             dueDateSymbol,
             dependsOnSymbol,
             idSymbol,
@@ -197,6 +203,9 @@ export class DefaultTaskSerializer implements TaskSerializer {
             case TaskLayoutComponent.RecurrenceRule:
                 if (!task.recurrence) return '';
                 return symbolAndStringValue(shortMode, recurrenceSymbol, task.recurrence.toText());
+            case TaskLayoutComponent.OnCompletion:
+                if (!task.onCompletion) return '';
+                return symbolAndStringValue(shortMode, onCompletionSymbol, task.onCompletion);
             case TaskLayoutComponent.DependsOn: {
                 if (task.dependsOn.length === 0) return '';
                 return symbolAndStringValue(shortMode, dependsOnSymbol, task.dependsOn.join(','));
@@ -259,6 +268,7 @@ export class DefaultTaskSerializer implements TaskSerializer {
         let createdDate: Moment | null = null;
         let recurrenceRule: string = '';
         let recurrence: Recurrence | null = null;
+        let onCompletion: OnCompletion = OnCompletion.Ignore;
         let id: string = '';
         let dependsOn: string[] | [] = [];
         // Tags that are removed from the end while parsing, but we want to add them back for being part of the description.
@@ -331,6 +341,18 @@ export class DefaultTaskSerializer implements TaskSerializer {
                 matched = true;
             }
 
+            const onCompletionMatch = line.match(TaskFormatRegularExpressions.onCompletionRegex);
+            if (onCompletionMatch != null) {
+                line = line.replace(TaskFormatRegularExpressions.onCompletionRegex, '').trim();
+                const onCompletionString = onCompletionMatch[1].trim().toLowerCase();
+                if (onCompletionString === 'delete') {
+                    onCompletion = OnCompletion.Delete;
+                } else {
+                    onCompletion = OnCompletion.Ignore;
+                }
+                matched = true;
+            }
+
             // Match tags from the end to allow users to mix the various task components with
             // tags. These tags will be added back to the description below
             const tagsMatch = line.match(TaskRegularExpressions.hashTagsFromEnd);
@@ -388,6 +410,7 @@ export class DefaultTaskSerializer implements TaskSerializer {
             doneDate,
             cancelledDate,
             recurrence,
+            onCompletion,
             id,
             dependsOn,
             tags: Task.extractHashtags(line),
diff --git a/src/TaskSerializer/index.ts b/src/TaskSerializer/index.ts
index b7cb622f..9647fedc 100644
--- a/src/TaskSerializer/index.ts
+++ b/src/TaskSerializer/index.ts
@@ -21,6 +21,7 @@ export type TaskDetails = Writeable<
         | 'doneDate'
         | 'cancelledDate'
         | 'recurrence'
+        | 'onCompletion'
         | 'dependsOn'
         | 'id'
         | 'tags'
diff --git a/src/ui/EditableTask.ts b/src/ui/EditableTask.ts
index 54da4f91..66dacd0a 100644
--- a/src/ui/EditableTask.ts
+++ b/src/ui/EditableTask.ts
@@ -2,6 +2,7 @@ import { GlobalFilter } from '../Config/GlobalFilter';
 import { parseTypedDateForSaving } from '../lib/DateTools';
 import { replaceTaskWithTasks } from '../Obsidian/File';
 import type { Status } from '../Statuses/Status';
+import { OnCompletion } from '../Task/OnCompletion';
 import { Occurrence } from '../Task/Occurrence';
 import { Priority } from '../Task/Priority';
 import { Recurrence } from '../Task/Recurrence';
@@ -22,8 +23,10 @@ export class EditableTask {
     // NEW_TASK_FIELD_EDIT_REQUIRED
     description: string;
     status: Status;
-    priority: 'none' | 'lowest' | 'low' | 'medium' | 'high' | 'highest';
+    priority: EditableTaskPriority;
     recurrenceRule: string;
+    // onCompletion: 'ignore' | 'delete';
+    onCompletion: OnCompletion;
     createdDate: string;
     startDate: string;
     scheduledDate: string;
@@ -42,6 +45,8 @@ export class EditableTask {
         description: string;
         status: Status;
         priority: EditableTaskPriority;
+        // onCompletion: 'ignore' | 'delete';
+        onCompletion: OnCompletion;
         recurrenceRule: string;
         createdDate: string;
         startDate: string;
@@ -59,6 +64,7 @@ export class EditableTask {
         this.description = editableTask.description;
         this.status = editableTask.status;
         this.priority = editableTask.priority;
+        this.onCompletion = editableTask.onCompletion;
         this.recurrenceRule = editableTask.recurrenceRule;
         this.createdDate = editableTask.createdDate;
         this.startDate = editableTask.startDate;
@@ -119,6 +125,7 @@ export class EditableTask {
             status: task.status,
             priority,
             recurrenceRule: task.recurrence ? task.recurrence.toText() : '',
+            onCompletion: task.onCompletion,
             createdDate: task.created.formatAsDate(),
             startDate: task.start.formatAsDate(),
             scheduledDate: task.scheduled.formatAsDate(),
@@ -183,6 +190,15 @@ export class EditableTask {
                 parsedPriority = Priority.None;
         }
 
+        let parsedOnCompletion: OnCompletion;
+        switch (this.onCompletion) {
+            case 'delete':
+                parsedOnCompletion = OnCompletion.Delete;
+                break;
+            default:
+                parsedOnCompletion = OnCompletion.Ignore;
+        }
+
         const blockedByWithIds = [];
 
         for (const depTask of this.blockedBy) {
@@ -211,6 +227,7 @@ export class EditableTask {
             description,
             status: task.status,
             priority: parsedPriority,
+            onCompletion: parsedOnCompletion,
             recurrence,
             startDate,
             scheduledDate,


Test patch:
diff --git a/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts b/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts
index 1f58be93..66c0ce96 100644
--- a/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts
+++ b/tests/CustomMatchers/CustomMatchersForTaskSerializer.ts
@@ -1,6 +1,7 @@
 import { diff } from 'jest-diff';
 import type { MatcherFunction } from 'expect';
 import moment from 'moment';
+import { OnCompletion } from '../../src/Task/OnCompletion';
 import type { TaskDetails } from '../../src/TaskSerializer';
 import { Recurrence } from '../../src/Task/Recurrence';
 import { Priority } from '../../src/Task/Priority';
@@ -61,6 +62,10 @@ function isTaskDetails(val: any): val is TaskDetails {
         return false;
     }
 
+    if (!Object.values(OnCompletion).includes(val.onCompletion)) {
+        return false;
+    }
+
     return true;
 }
 
@@ -110,6 +115,7 @@ function tryBuildTaskDetails(t: object): TaskDetails | null {
         doneDate: null,
         cancelledDate: null,
         recurrence: null,
+        onCompletion: '',
         dependsOn: [],
         id: '',
         tags: [],
diff --git a/tests/DocumentationSamples/ValidateTasks.test.validate-tasks_all-emojis-emojis.approved.text b/tests/DocumentationSamples/ValidateTasks.test.validate-tasks_all-emojis-emojis.approved.text
index 0d1326e0..446ebaa3 100644
--- a/tests/DocumentationSamples/ValidateTasks.test.validate-tasks_all-emojis-emojis.approved.text
+++ b/tests/DocumentationSamples/ValidateTasks.test.validate-tasks_all-emojis-emojis.approved.text
@@ -1,1 +1,1 @@
-🔺⏫🔼🔽⏬🛫➕⏳📅✅❌🔁⛔🆔
+🔺⏫🔼🔽⏬🛫➕⏳📅✅❌🔁🏁⛔🆔
diff --git a/tests/DocumentationSamples/ValidateTasks.test.validate-tasks_find-unread-emojis.approved.text b/tests/DocumentationSamples/ValidateTasks.test.validate-tasks_find-unread-emojis.approved.text
index daf8d09d..d275634e 100644
--- a/tests/DocumentationSamples/ValidateTasks.test.validate-tasks_find-unread-emojis.approved.text
+++ b/tests/DocumentationSamples/ValidateTasks.test.validate-tasks_find-unread-emojis.approved.text
@@ -2,7 +2,7 @@
 ````text
 ```tasks
 # These instructions need to be all on one line:
-(description includes 🔺) OR (description includes ⏫) OR (description includes 🔼) OR (description includes 🔽) OR (description includes ⏬) OR (description includes 🛫) OR (description includes ➕) OR (description includes ⏳) OR (description includes 📅) OR (description includes ✅) OR (description includes ❌) OR (description includes 🔁) OR (description includes ⛔) OR (description includes 🆔)
+(description includes 🔺) OR (description includes ⏫) OR (description includes 🔼) OR (description includes 🔽) OR (description includes ⏬) OR (description includes 🛫) OR (description includes ➕) OR (description includes ⏳) OR (description includes 📅) OR (description includes ✅) OR (description includes ❌) OR (description includes 🔁) OR (description includes 🏁) OR (description includes ⛔) OR (description includes 🆔)
 
 # Optionally, uncomment this line and exclude your templates location
 # path does not include _templates
diff --git a/tests/Layout/TaskLayout.test.ts b/tests/Layout/TaskLayout.test.ts
index 2981f6a1..ab6cc204 100644
--- a/tests/Layout/TaskLayout.test.ts
+++ b/tests/Layout/TaskLayout.test.ts
@@ -36,6 +36,7 @@ describe('TaskLayout tests', () => {
             tasks-layout-hide-dependsOn
             tasks-layout-hide-priority
             tasks-layout-hide-recurrenceRule
+            tasks-layout-hide-onCompletion
             tasks-layout-hide-createdDate
             tasks-layout-hide-startDate
             tasks-layout-hide-scheduledDate
diff --git a/tests/Layout/TaskLayoutOptions.test.ts b/tests/Layout/TaskLayoutOptions.test.ts
index 985e870c..c7c13cca 100644
--- a/tests/Layout/TaskLayoutOptions.test.ts
+++ b/tests/Layout/TaskLayoutOptions.test.ts
@@ -11,6 +11,7 @@ describe('TaskLayoutOptions', () => {
             dependsOn
             priority
             recurrenceRule
+            onCompletion
             createdDate
             startDate
             scheduledDate
@@ -66,6 +67,7 @@ describe('TaskLayoutOptions', () => {
             dependsOn
             priority
             recurrenceRule
+            onCompletion
             createdDate
             startDate
             scheduledDate
@@ -84,6 +86,7 @@ describe('TaskLayoutOptions', () => {
             dependsOn
             priority
             recurrenceRule
+            onCompletion
             createdDate
             startDate
             scheduledDate
@@ -138,6 +141,7 @@ describe('TaskLayoutOptions', () => {
             dependsOn
             priority
             recurrenceRule
+            onCompletion
             createdDate
             startDate
             scheduledDate
diff --git a/tests/Query/Query.test.ts b/tests/Query/Query.test.ts
index a8eaabb3..a0610b96 100644
--- a/tests/Query/Query.test.ts
+++ b/tests/Query/Query.test.ts
@@ -6,6 +6,7 @@ import { Query } from '../../src/Query/Query';
 import { TasksFile } from '../../src/Scripting/TasksFile';
 import { Status } from '../../src/Statuses/Status';
 import { Task } from '../../src/Task/Task';
+import { OnCompletion } from '../../src/Task/OnCompletion';
 import { TaskLocation } from '../../src/Task/TaskLocation';
 import { fieldCreators } from '../../src/Query/FilterParser';
 import type { Field } from '../../src/Query/Filter/Field';
@@ -752,6 +753,7 @@ describe('Query', () => {
                     doneDate: null,
                     cancelledDate: null,
                     recurrence: null,
+                    onCompletion: OnCompletion.Ignore,
                     dependsOn: [],
                     id: '',
                     blockLink: '',
@@ -773,6 +775,7 @@ describe('Query', () => {
                     doneDate: null,
                     cancelledDate: null,
                     recurrence: null,
+                    onCompletion: OnCompletion.Ignore,
                     dependsOn: [],
                     id: '',
                     blockLink: '',
diff --git a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html
index c5ca9b5e..d4232b51 100644
--- a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html
+++ b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html
@@ -1,5 +1,5 @@
 <!--
-  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c
+  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done 🏁 delete ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c
 -->
 
 <li
@@ -22,6 +22,7 @@
     <span class="task-dependsOn"><span>⛔ 123456,abc123</span></span>
     <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
     <span class="task-recurring"><span>🔁 every day when done</span></span>
+    <span class="task-onCompletion"><span>🏁 delete</span></span>
     <span class="task-created" data-task-created="past-4d"><span>➕ 2023-07-01</span></span>
     <span class="task-start" data-task-start="past-3d"><span>🛫 2023-07-02</span></span>
     <span class="task-scheduled" data-task-scheduled="past-2d"><span>⏳ 2023-07-03</span></span>
diff --git a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html
index 52df5a96..7295507e 100644
--- a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html
+++ b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html
@@ -1,5 +1,5 @@
 <!--
-  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c
+  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done 🏁 delete ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c
 -->
 
 <li
@@ -22,6 +22,7 @@
     <span class="task-dependsOn"><span>⛔</span></span>
     <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
     <span class="task-recurring"><span>🔁</span></span>
+    <span class="task-onCompletion"><span>🏁</span></span>
     <span class="task-created" data-task-created="past-4d"><span>➕</span></span>
     <span class="task-start" data-task-start="past-3d"><span>🛫</span></span>
     <span class="task-scheduled" data-task-scheduled="past-2d"><span>⏳</span></span>
diff --git a/tests/Renderer/TaskLineRenderer.test.ts b/tests/Renderer/TaskLineRenderer.test.ts
index 394f3352..2ba78e30 100644
--- a/tests/Renderer/TaskLineRenderer.test.ts
+++ b/tests/Renderer/TaskLineRenderer.test.ts
@@ -207,6 +207,7 @@ describe('task line rendering - layout options', () => {
                 ' ⛔ 123456,abc123',
                 ' 🔼',
                 ' 🔁 every day when done',
+                ' 🏁 delete',
                 ' ➕ 2023-07-01',
                 ' 🛫 2023-07-02',
                 ' ⏳ 2023-07-03',
@@ -227,6 +228,7 @@ describe('task line rendering - layout options', () => {
                 ' ⛔ 123456,abc123',
                 ' 🔼',
                 ' 🔁 every day when done',
+                ' 🏁 delete',
                 ' ➕ 2023-07-01',
                 ' 🛫 2023-07-02',
                 ' ⏳ 2023-07-03',
@@ -283,6 +285,10 @@ describe('task line rendering - layout options', () => {
     it('renders with depends on', async () => {
         await testLayoutOptions(['Do exercises #todo #health', ' ⛔ 123456,abc123'], [TaskLayoutComponent.DependsOn]);
     });
+
+    it('renders with onCompletion', async () => {
+        await testLayoutOptions(['Do exercises #todo #health', ' 🏁 delete'], [TaskLayoutComponent.OnCompletion]);
+    });
 });
 
 describe('task line rendering - errors in task fields', () => {
diff --git a/tests/Scripting/TaskProperties.test.task_other_fields.approved.md b/tests/Scripting/TaskProperties.test.task_other_fields.approved.md
index 28bf2684..8dd7ee5c 100644
--- a/tests/Scripting/TaskProperties.test.task_other_fields.approved.md
+++ b/tests/Scripting/TaskProperties.test.task_other_fields.approved.md
@@ -10,8 +10,9 @@
 | `task.urgency` | `number` | `3.3000000000000007` | `number` | `1.9500000000000002` |
 | `task.isRecurring` | `boolean` | `true` | `boolean` | `false` |
 | `task.recurrenceRule` | `string` | `'every day when done'` | `string` | `''` |
+| `task.onCompletion` | `string` | `'delete'` | `string` | `''` |
 | `task.tags` | `string[]` | `['#todo', '#health']` | `any[]` | `[]` |
-| `task.originalMarkdown` | `string` | `'  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c'` | `string` | `'- [/] minimal task'` |
+| `task.originalMarkdown` | `string` | `'  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done 🏁 delete ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c'` | `string` | `'- [/] minimal task'` |
 
 
 <!-- placeholder to force blank line after included text -->
diff --git a/tests/Scripting/TaskProperties.test.ts b/tests/Scripting/TaskProperties.test.ts
index ae29324b..f4a9dc12 100644
--- a/tests/Scripting/TaskProperties.test.ts
+++ b/tests/Scripting/TaskProperties.test.ts
@@ -124,6 +124,7 @@ describe('task', () => {
             'task.urgency',
             'task.isRecurring',
             'task.recurrenceRule',
+            'task.onCompletion',
             'task.tags',
             // 'task.indentation', // Cannot just use length to determine if sub-task, as it many be '> ' due to being in a sub-task
             // 'task.listMarker', // Not a priority to release
diff --git a/tests/Task/OnCompletion.test.ts b/tests/Task/OnCompletion.test.ts
index e94c50e9..e4a372ea 100644
--- a/tests/Task/OnCompletion.test.ts
+++ b/tests/Task/OnCompletion.test.ts
@@ -28,31 +28,13 @@ export function applyStatusAndOnCompletionAction(task: Task, newStatus: Status)
     return handleOnCompletion(task, tasks);
 }
 
-describe('OnCompletion', () => {
-    it('should just return task if Action is not recognized', () => {
-        // Arrange
-        const dueDate = '2024-02-10';
-        const task = new TaskBuilder()
-            .description('A non-recurring task with invalid OC trigger 🏁 INVALID_ACTION')
-            .dueDate(dueDate)
-            .build();
-        expect(task.status.type).toEqual(StatusType.TODO);
-
-        // Act
-        const returnedTasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
-
-        // Assert
-        expect(returnedTasks.length).toEqual(1);
-        expect(toLines(returnedTasks).join('\n')).toMatchInlineSnapshot(
-            '"- [x] A non-recurring task with invalid OC trigger 🏁 INVALID_ACTION 📅 2024-02-10 ✅ 2024-02-11"',
-        );
-    });
-
+describe('OnCompletion feature', () => {
     it('should just return task if StatusType has not changed', () => {
         // Arrange
         const dueDate = '2024-02-10';
         const task = new TaskBuilder()
-            .description('An already-DONE, non-recurring task 🏁 Delete')
+            .description('An already-DONE, non-recurring task')
+            .onCompletion('delete')
             .dueDate(dueDate)
             .doneDate(dueDate)
             .status(Status.DONE)
@@ -65,7 +47,7 @@ describe('OnCompletion', () => {
         // Assert
         expect(returnedTasks.length).toEqual(1);
         expect(toLines(returnedTasks).join('\n')).toMatchInlineSnapshot(
-            '"- [x] An already-DONE, non-recurring task 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10"',
+            '"- [x] An already-DONE, non-recurring task 🏁 delete 📅 2024-02-10 ✅ 2024-02-10"',
         );
     });
 
@@ -107,93 +89,107 @@ describe('OnCompletion', () => {
         `);
     });
 
-    // TODO is there a better way to handle the following?  does an 'empty' Task exist?
-    it('should return an empty Array for a non-recurring task with Delete Action', () => {
+    it('should return the task when going from TODO to IN_PROGRESS', () => {
         // Arrange
         const dueDate = '2024-02-10';
-        const task = new TaskBuilder().description('A non-recurring task with 🏁 Delete').dueDate(dueDate).build();
-        expect(task.status.type).toEqual(StatusType.TODO);
+        const recurrence = new RecurrenceBuilder().rule('every day').dueDate(dueDate).build();
+        const task = new TaskBuilder()
+            .description('A recurring task with OC_DELETE trigger')
+            .recurrence(recurrence)
+            .onCompletion('delete')
+            .dueDate(dueDate)
+            .build();
 
         // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
+        const tasks = applyStatusAndOnCompletionAction(task, Status.makeInProgress());
 
         // Assert
-        expect(tasks).toEqual([]);
+        expect(tasks.length).toEqual(1);
+        expect(tasks[0].status.type).toEqual(StatusType.IN_PROGRESS);
     });
 
-    it('should return only the next instance of a recurring task with Delete action', () => {
+    it('should return the task when going from one DONE status to another DONE status', () => {
         // Arrange
-        const dueDate = '2024-02-10';
-        const recurrence = new RecurrenceBuilder().rule('every day').dueDate(dueDate).build();
+        const done1 = new Status(new StatusConfiguration('x', 'done', ' ', true, StatusType.DONE));
+        const done2 = new Status(new StatusConfiguration('X', 'DONE', ' ', true, StatusType.DONE));
         const task = new TaskBuilder()
-            .description('A recurring task with 🏁 Delete')
-            .recurrence(recurrence)
-            .dueDate(dueDate)
+            .description('A simple done task with')
+            .onCompletion('delete')
+            .status(done1)
             .build();
-        expect(task.status.type).toEqual(StatusType.TODO);
 
         // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
+        const tasks = applyStatusAndOnCompletionAction(task, done2);
 
         // Assert
         expect(tasks.length).toEqual(1);
-        expect(toLines(tasks).join('\n')).toMatchInlineSnapshot(
-            '"- [ ] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-11"',
-        );
+        expect(tasks[0].status.symbol).toEqual('X');
+        expect(tasks[0].status.type).toEqual(StatusType.DONE);
     });
-    it('should delete a simple task with flag on completion', () => {
+
+    it('should return a task featuring the On Completion flag trigger but an empty string Action', () => {
         // Arrange
-        const task = new TaskBuilder().description('A non-recurring task with 🏁 Delete').build();
+        const task = new TaskBuilder().description('A non-recurring task with').onCompletion('').build();
 
         // Act
         const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
 
         // Assert
-        expect(tasks.length).toEqual(0);
+        expect(tasks.length).toEqual(1);
     });
+});
 
-    it('should return the task when going from TODO to IN_PROGRESS', () => {
+describe('OnCompletion - Delete action', () => {
+    it('should return an empty Array for a non-recurring task with "delete" Action', () => {
         // Arrange
         const dueDate = '2024-02-10';
-        const recurrence = new RecurrenceBuilder().rule('every day').dueDate(dueDate).build();
         const task = new TaskBuilder()
-            .description('A recurring task with 🏁 Delete')
-            .recurrence(recurrence)
+            .description('A non-recurring task with OC_DELETE trigger')
             .dueDate(dueDate)
+            .onCompletion('delete')
             .build();
+        expect(task.status.type).toEqual(StatusType.TODO);
 
         // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeInProgress());
+        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
 
         // Assert
-        expect(tasks.length).toEqual(1);
-        expect(tasks[0].status.type).toEqual(StatusType.IN_PROGRESS);
+        expect(tasks).toEqual([]);
     });
 
-    it('should return the task when going from one DONE status to another DONE status', () => {
+    it('should return only the next instance of a recurring task with "delete" Action', () => {
         // Arrange
-        const done1 = new Status(new StatusConfiguration('x', 'done', ' ', true, StatusType.DONE));
-        const done2 = new Status(new StatusConfiguration('X', 'DONE', ' ', true, StatusType.DONE));
-        const task = new TaskBuilder().description('A simple done task with 🏁 Delete').status(done1).build();
+        const dueDate = '2024-02-10';
+        const recurrence = new RecurrenceBuilder().rule('every day').dueDate(dueDate).build();
+        const task = new TaskBuilder()
+            .description('A recurring task with OC_DELETE trigger')
+            .recurrence(recurrence)
+            .dueDate(dueDate)
+            .onCompletion('delete')
+            .build();
+        expect(task.status.type).toEqual(StatusType.TODO);
 
         // Act
-        const tasks = applyStatusAndOnCompletionAction(task, done2);
+        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
 
         // Assert
         expect(tasks.length).toEqual(1);
-        expect(tasks[0].status.symbol).toEqual('X');
-        expect(tasks[0].status.type).toEqual(StatusType.DONE);
+        expect(toLines(tasks).join('\n')).toMatchInlineSnapshot(
+            '"- [ ] A recurring task with OC_DELETE trigger 🔁 every day 🏁 delete 📅 2024-02-11"',
+        );
     });
-
-    it('should return a task featuring the On Completion flag trigger but an empty string Action', () => {
+    it('should delete a simple task with flag on completion', () => {
         // Arrange
-        const task = new TaskBuilder().description('A non-recurring task with 🏁').build();
+        const task = new TaskBuilder()
+            .description('A non-recurring task with OC_DELETE trigger')
+            .onCompletion('delete')
+            .build();
 
         // Act
         const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
 
         // Assert
-        expect(tasks.length).toEqual(1);
+        expect(tasks.length).toEqual(0);
     });
 });
 
@@ -213,12 +209,12 @@ function getCases(): ToggleCase[] {
 
         {
             nextStatus: Status.makeDone(),
-            line: '- [ ] A non-recurring task with 🏁 Delete',
+            line: '- [ ] A non-recurring task with 🏁 delete',
         },
 
         {
             nextStatus: Status.makeDone(),
-            line: '- [ ] A non-recurring task with 🏁 Delete 📅 2024-02-10',
+            line: '- [ ] A non-recurring task with 🏁 delete 📅 2024-02-10',
         },
 
         {
@@ -240,24 +236,36 @@ function getCases(): ToggleCase[] {
 
         {
             nextStatus: Status.makeDone(),
-            line: '- [ ] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-10',
+            line: '- [ ] A recurring task with 🏁 delete 🔁 every day 📅 2024-02-10',
         },
 
         {
             nextStatus: Status.makeInProgress(),
-            line: '- [ ] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-10',
+            line: '- [ ] A recurring task with 🏁 delete 🔁 every day 📅 2024-02-10',
         },
 
         // Other
 
         {
             nextStatus: Status.makeDone(),
-            line: '- [x] An already-DONE task, changing to Same      DONE status 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10',
+            line: '- [x] An already-DONE task, changing to Same      DONE status 🏁 delete 📅 2024-02-10 ✅ 2024-02-10',
         },
 
         {
             nextStatus: new Status(new StatusConfiguration('X', 'new status', ' ', false, StatusType.DONE)),
-            line: '- [x] An already-DONE task, changing to Different DONE status 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10',
+            line: '- [x] An already-DONE task, changing to Different DONE status 🏁 delete 📅 2024-02-10 ✅ 2024-02-10',
+        },
+
+        // Indented, within callout/code block
+
+        {
+            nextStatus: Status.makeDone(),
+            line: '    - [ ] An indented task with 🏁 delete',
+        },
+
+        {
+            nextStatus: Status.makeDone(),
+            line: '> - [ ] A task within a block quote or callout and 🏁 delete',
         },
     ];
 }
diff --git a/tests/Task/OnCompletion.test.visualise_completion-behaviour_visualise.approved.txt b/tests/Task/OnCompletion.test.visualise_completion-behaviour_visualise.approved.txt
index 7e584bab..64048405 100644
--- a/tests/Task/OnCompletion.test.visualise_completion-behaviour_visualise.approved.txt
+++ b/tests/Task/OnCompletion.test.visualise_completion-behaviour_visualise.approved.txt
@@ -14,10 +14,10 @@ initial task:
 
 
 initial task:
-- [ ] A non-recurring task with 🏁 Delete
+- [ ] A non-recurring task with 🏁 delete
 
 => advances to status [x] and type DONE:
-- [x] A non-recurring task with 🏁 Delete ✅ 2024-02-11
+- [x] A non-recurring task with 🏁 delete ✅ 2024-02-11
 
 => which, after any on-completion action, results in:
 
@@ -25,10 +25,10 @@ initial task:
 
 
 initial task:
-- [ ] A non-recurring task with 🏁 Delete 📅 2024-02-10
+- [ ] A non-recurring task with 🏁 delete 📅 2024-02-10
 
 => advances to status [x] and type DONE:
-- [x] A non-recurring task with 🏁 Delete 📅 2024-02-10 ✅ 2024-02-11
+- [x] A non-recurring task with 🏁 delete 📅 2024-02-10 ✅ 2024-02-11
 
 => which, after any on-completion action, results in:
 
@@ -71,46 +71,68 @@ initial task:
 
 
 initial task:
-- [ ] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-10
+- [ ] A recurring task with 🔁 every day 🏁 delete 📅 2024-02-10
 
 => advances to status [x] and type DONE:
-- [ ] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-11
-- [x] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-10 ✅ 2024-02-11
+- [ ] A recurring task with 🔁 every day 🏁 delete 📅 2024-02-11
+- [x] A recurring task with 🔁 every day 🏁 delete 📅 2024-02-10 ✅ 2024-02-11
 
 => which, after any on-completion action, results in:
-- [ ] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-11
+- [ ] A recurring task with 🔁 every day 🏁 delete 📅 2024-02-11
 ----------------------------------------------
 
 
 initial task:
-- [ ] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-10
+- [ ] A recurring task with 🔁 every day 🏁 delete 📅 2024-02-10
 
 => advances to status [/] and type IN_PROGRESS:
-- [/] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-10
+- [/] A recurring task with 🔁 every day 🏁 delete 📅 2024-02-10
 
 => which, after any on-completion action, results in:
-- [/] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-10
+- [/] A recurring task with 🔁 every day 🏁 delete 📅 2024-02-10
 ----------------------------------------------
 
 
 initial task:
-- [x] An already-DONE task, changing to Same      DONE status 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10
+- [x] An already-DONE task, changing to Same      DONE status 🏁 delete 📅 2024-02-10 ✅ 2024-02-10
 
 => advances to status [x] and type DONE:
-- [x] An already-DONE task, changing to Same      DONE status 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10
+- [x] An already-DONE task, changing to Same      DONE status 🏁 delete 📅 2024-02-10 ✅ 2024-02-10
 
 => which, after any on-completion action, results in:
-- [x] An already-DONE task, changing to Same      DONE status 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10
+- [x] An already-DONE task, changing to Same      DONE status 🏁 delete 📅 2024-02-10 ✅ 2024-02-10
 ----------------------------------------------
 
 
 initial task:
-- [x] An already-DONE task, changing to Different DONE status 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10
+- [x] An already-DONE task, changing to Different DONE status 🏁 delete 📅 2024-02-10 ✅ 2024-02-10
 
 => advances to status [X] and type DONE:
-- [X] An already-DONE task, changing to Different DONE status 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10
+- [X] An already-DONE task, changing to Different DONE status 🏁 delete 📅 2024-02-10 ✅ 2024-02-10
 
 => which, after any on-completion action, results in:
-- [X] An already-DONE task, changing to Different DONE status 🏁 Delete 📅 2024-02-10 ✅ 2024-02-10
+- [X] An already-DONE task, changing to Different DONE status 🏁 delete 📅 2024-02-10 ✅ 2024-02-10
+----------------------------------------------
+
+
+initial task:
+    - [ ] An indented task with 🏁 delete
+
+=> advances to status [x] and type DONE:
+    - [x] An indented task with 🏁 delete ✅ 2024-02-11
+
+=> which, after any on-completion action, results in:
+
+----------------------------------------------
+
+
+initial task:
+> - [ ] A task within a block quote or callout and 🏁 delete
+
+=> advances to status [x] and type DONE:
+> - [x] A task within a block quote or callout and 🏁 delete ✅ 2024-02-11
+
+=> which, after any on-completion action, results in:
+
 ----------------------------------------------
 
diff --git a/tests/Task/Task.test.ts b/tests/Task/Task.test.ts
index e687e801..7b0c3871 100644
--- a/tests/Task/Task.test.ts
+++ b/tests/Task/Task.test.ts
@@ -1704,6 +1704,12 @@ describe('identicalTo', () => {
         expect(lhs).not.toBeIdenticalTo(new TaskBuilder().dependsOn(['12345']));
     });
 
+    it('should check onCompletion', () => {
+        const lhs = new TaskBuilder().onCompletion('');
+        expect(lhs).toBeIdenticalTo(new TaskBuilder().onCompletion(''));
+        expect(lhs).not.toBeIdenticalTo(new TaskBuilder().onCompletion('delete'));
+    });
+
     it('should correctly compare a task with status read from user settings', () => {
         // This was added when fixing:
         // https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2044
diff --git a/tests/TaskSerializer/DataviewTaskSerializer.test.ts b/tests/TaskSerializer/DataviewTaskSerializer.test.ts
index d0c8636a..902d5acd 100644
--- a/tests/TaskSerializer/DataviewTaskSerializer.test.ts
+++ b/tests/TaskSerializer/DataviewTaskSerializer.test.ts
@@ -7,6 +7,7 @@ import { TaskBuilder } from '../TestingTools/TaskBuilder';
 import { DATAVIEW_SYMBOLS, DataviewTaskSerializer } from '../../src/TaskSerializer/DataviewTaskSerializer';
 import type { Task } from '../../src/Task/Task';
 import type { TaskDetails } from '../../src/TaskSerializer';
+import { OnCompletion } from '../../src/Task/OnCompletion';
 import { Priority } from '../../src/Task/Priority';
 
 jest.mock('obsidian');
@@ -52,6 +53,14 @@ describe('DataviewTaskSerializer', () => {
             });
         });
 
+        describe('should parse onCompletion', () => {
+            it('should parse delete action', () => {
+                const onCompletion = '[onCompletion:: delete]';
+                const taskDetails = deserialize(onCompletion);
+                expect(taskDetails).toMatchTaskDetails({ onCompletion: OnCompletion.Delete });
+            });
+        });
+
         describe('should parse depends on', () => {
             it('should parse depends on one task', () => {
                 const id = '[dependsOn:: F12345]';
@@ -317,6 +326,12 @@ describe('DataviewTaskSerializer', () => {
             expect(serialized).toEqual('  [repeat:: every day]');
         });
 
+        it('should serialize onCompletion', () => {
+            const task = new TaskBuilder().onCompletion('delete').description('').build();
+            const serialized = serialize(task);
+            expect(serialized).toEqual('  [onCompletion:: delete]');
+        });
+
         it('should serialize tags', () => {
             const task = new TaskBuilder().description('').tags(['#hello', '#world', '#task']).build();
             const serialized = serialize(task);
@@ -339,7 +354,7 @@ describe('DataviewTaskSerializer', () => {
             const task = TaskBuilder.createFullyPopulatedTask();
             const serialized = serialize(task);
             expect(serialized).toMatchInlineSnapshot(
-                '"Do exercises #todo #health  [id:: abcdef]  [dependsOn:: 123456,abc123]  [priority:: medium]  [repeat:: every day when done]  [created:: 2023-07-01]  [start:: 2023-07-02]  [scheduled:: 2023-07-03]  [due:: 2023-07-04]  [cancelled:: 2023-07-06]  [completion:: 2023-07-05] ^dcf64c"',
+                '"Do exercises #todo #health  [id:: abcdef]  [dependsOn:: 123456,abc123]  [priority:: medium]  [repeat:: every day when done]  [onCompletion:: delete]  [created:: 2023-07-01]  [start:: 2023-07-02]  [scheduled:: 2023-07-03]  [due:: 2023-07-04]  [cancelled:: 2023-07-06]  [completion:: 2023-07-05] ^dcf64c"',
             );
         });
     });
diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts
index a69d31bc..1d52bcff 100644
--- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts
+++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts
@@ -11,6 +11,7 @@ import {
     allTaskPluginEmojis,
 } from '../../src/TaskSerializer/DefaultTaskSerializer';
 import { TaskBuilder } from '../TestingTools/TaskBuilder';
+import { OnCompletion } from '../../src/Task/OnCompletion';
 import { Priority } from '../../src/Task/Priority';
 
 jest.mock('obsidian');
@@ -51,6 +52,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({
         startDateSymbol,
         createdDateSymbol,
         recurrenceSymbol,
+        onCompletionSymbol,
         scheduledDateSymbol,
         dueDateSymbol,
         doneDateSymbol,
@@ -130,6 +132,14 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({
             });
         });
 
+        describe('should parse onCompletion', () => {
+            it('should parse delete action', () => {
+                const onCompletion = `${onCompletionSymbol} delete`;
+                const taskDetails = deserialize(onCompletion);
+                expect(taskDetails).toMatchTaskDetails({ onCompletion: OnCompletion.Delete });
+            });
+        });
+
         describe('should parse depends on', () => {
             it('should parse depends on one task', () => {
                 const id = `${dependsOnSymbol} F12345`;
@@ -247,6 +257,12 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({
             expect(serialized).toEqual(` ${recurrenceSymbol} every day`);
         });
 
+        it('should serialize onCompletion', () => {
+            const task = new TaskBuilder().onCompletion('delete').description('').build();
+            const serialized = serialize(task);
+            expect(serialized).toEqual(` ${onCompletionSymbol} delete`);
+        });
+
         it('should serialize depends on', () => {
             const task = new TaskBuilder().description('').dependsOn(['123456', 'abc123']).build();
             const serialized = serialize(task);
diff --git a/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_Emojis_tabulate-emojis.approved.md b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_Emojis_tabulate-emojis.approved.md
index 5768744b..c93e88df 100644
--- a/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_Emojis_tabulate-emojis.approved.md
+++ b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_Emojis_tabulate-emojis.approved.md
@@ -3,6 +3,7 @@
 | Emoji | Codepoint |
 | ----- | ----- |
 | 🆔 | U+1F194 |
+| 🏁 | U+1F3C1 |
 | 📅 | U+1F4C5 |
 | 🔁 | U+1F501 |
 | 🔺 | U+1F53A |
diff --git a/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_dataview-include.approved.md b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_dataview-include.approved.md
new file mode 100644
index 00000000..cf9f28e9
--- /dev/null
+++ b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_dataview-include.approved.md
@@ -0,0 +1,6 @@
+<!-- placeholder to force blank line before included text -->
+
+- [ ] #task Remove this task when done  [onCompletion:: delete]
+- [ ] #task Remove completed instance of this recurring task when done  [repeat:: every day]  [onCompletion:: delete]
+
+<!-- placeholder to force blank line after included text -->
diff --git a/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_dataview-snippet.approved.md b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_dataview-snippet.approved.md
new file mode 100644
index 00000000..d3332c42
--- /dev/null
+++ b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_dataview-snippet.approved.md
@@ -0,0 +1,2 @@
+- [ ] #task Remove this task when done  [onCompletion:: delete]
+- [ ] #task Remove completed instance of this recurring task when done  [repeat:: every day]  [onCompletion:: delete]
diff --git a/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_tasksPluginEmoji-include.approved.md b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_tasksPluginEmoji-include.approved.md
new file mode 100644
index 00000000..341cd134
--- /dev/null
+++ b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_tasksPluginEmoji-include.approved.md
@@ -0,0 +1,6 @@
+<!-- placeholder to force blank line before included text -->
+
+- [ ] #task Remove this task when done 🏁 delete
+- [ ] #task Remove completed instance of this recurring task when done 🔁 every day 🏁 delete
+
+<!-- placeholder to force blank line after included text -->
diff --git a/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_tasksPluginEmoji-snippet.approved.md b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_tasksPluginEmoji-snippet.approved.md
new file mode 100644
index 00000000..64e77907
--- /dev/null
+++ b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.Serializer_OnCompletion_tasksPluginEmoji-snippet.approved.md
@@ -0,0 +1,2 @@
+- [ ] #task Remove this task when done 🏁 delete
+- [ ] #task Remove completed instance of this recurring task when done 🔁 every day 🏁 delete
diff --git a/tests/TaskSerializer/DocsSamplesForTaskFormats.test.ts b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.ts
index 53afb284..5e45afa3 100644
--- a/tests/TaskSerializer/DocsSamplesForTaskFormats.test.ts
+++ b/tests/TaskSerializer/DocsSamplesForTaskFormats.test.ts
@@ -73,6 +73,23 @@ describe('Serializer', () => {
         });
     });
 
+    describe('OnCompletion', () => {
+        function allOnCompletionLines() {
+            const tasks = SampleTasks.withSampleOnCompletionValues();
+            return tasks.map((t) => t.toFileLineString()).join('\n');
+        }
+
+        it.each(Object.keys(TASK_FORMATS))('%s-snippet', (key: string) => {
+            updateSettings({ taskFormat: key as keyof TASK_FORMATS });
+            verifyMarkdown(allOnCompletionLines());
+        });
+
+        it.each(Object.keys(TASK_FORMATS))('%s-include', (key: string) => {
+            updateSettings({ taskFormat: key as keyof TASK_FORMATS });
+            verifyMarkdownForDocs(allOnCompletionLines());
+        });
+    });
+
     describe('Dependencies', () => {
         function allDependencyLines() {
             const tasks = SampleTasks.withAllRepresentativeDependencyFields();
diff --git a/tests/TaskSerializer/TaskSerializer.test.ts b/tests/TaskSerializer/TaskSerializer.test.ts
index ec0f1d70..1541ce9f 100644
--- a/tests/TaskSerializer/TaskSerializer.test.ts
+++ b/tests/TaskSerializer/TaskSerializer.test.ts
@@ -6,6 +6,7 @@ import moment from 'moment';
 import { Task } from '../../src/Task/Task';
 import type { TaskDetails, TaskSerializer } from '../../src/TaskSerializer';
 import { TaskBuilder } from '../TestingTools/TaskBuilder';
+import { OnCompletion } from '../../src/Task/OnCompletion';
 import { Priority } from '../../src/Task/Priority';
 import { TaskRegularExpressions } from '../../src/Task/TaskRegularExpressions';
 
@@ -72,6 +73,7 @@ describe('TaskSerializer Example', () => {
                 doneDate: null,
                 cancelledDate: null,
                 recurrence: null,
+                onCompletion: OnCompletion.Ignore,
                 dependsOn: [],
                 id: '',
             };
diff --git a/tests/TestingTools/SampleTasks.ts b/tests/TestingTools/SampleTasks.ts
index 3b46cb94..425264de 100644
--- a/tests/TestingTools/SampleTasks.ts
+++ b/tests/TestingTools/SampleTasks.ts
@@ -279,4 +279,22 @@ export class SampleTasks {
             return new TaskBuilder().blockLink(blockLink).build();
         });
     }
+
+    public static withSampleOnCompletionValues() {
+        const everyDay = Recurrence.fromText({
+            recurrenceRuleText: 'every day',
+            occurrence: new Occurrence({
+                startDate: null,
+                scheduledDate: null,
+                dueDate: null,
+            }),
+        });
+        const task1 = new TaskBuilder().description('#task Remove this task when done').onCompletion('delete').build();
+        const task2 = new TaskBuilder()
+            .description('#task Remove completed instance of this recurring task when done')
+            .onCompletion('delete')
+            .recurrence(everyDay)
+            .build();
+        return [task1, task2];
+    }
 }
diff --git a/tests/TestingTools/TaskBuilder.test.ts b/tests/TestingTools/TaskBuilder.test.ts
index 5ace43a1..2cefebc6 100644
--- a/tests/TestingTools/TaskBuilder.test.ts
+++ b/tests/TestingTools/TaskBuilder.test.ts
@@ -75,7 +75,7 @@ describe('TaskBuilder', () => {
         expect(getNullOrUnsetFields(task.taskLocation)).toEqual([]);
 
         expect(task.originalMarkdown).toEqual(
-            '  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c',
+            '  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done 🏁 delete ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c',
         );
     });
 });
diff --git a/tests/TestingTools/TaskBuilder.ts b/tests/TestingTools/TaskBuilder.ts
index 18ad2ea7..0f84b8f1 100644
--- a/tests/TestingTools/TaskBuilder.ts
+++ b/tests/TestingTools/TaskBuilder.ts
@@ -2,6 +2,7 @@
 import type { Moment } from 'moment';
 import { TasksFile } from '../../src/Scripting/TasksFile';
 import { Status } from '../../src/Statuses/Status';
+import { OnCompletion } from '../../src/Task/OnCompletion';
 import { Occurrence } from '../../src/Task/Occurrence';
 import { Task } from '../../src/Task/Task';
 import { Recurrence } from '../../src/Task/Recurrence';
@@ -47,6 +48,7 @@ export class TaskBuilder {
     private _cancelledDate: Moment | null = null;
 
     private _recurrence: Recurrence | null = null;
+    private _onCompletion: OnCompletion = OnCompletion.Ignore;
     private _blockLink: string = '';
 
     private _scheduledDateIsInferred: boolean = false;
@@ -96,6 +98,7 @@ export class TaskBuilder {
             doneDate: this._doneDate,
             cancelledDate: this._cancelledDate,
             recurrence: this._recurrence,
+            onCompletion: this._onCompletion,
             dependsOn: this._dependsOn,
             id: this._id,
             blockLink: this._blockLink,
@@ -126,6 +129,7 @@ export class TaskBuilder {
             .dueDate('2023-07-04')
             .doneDate('2023-07-05')
             .cancelledDate('2023-07-06')
+            .onCompletion('delete')
             .dependsOn(['123456', 'abc123'])
             .id('abcdef')
             .blockLink(' ^dcf64c')
@@ -288,6 +292,15 @@ export class TaskBuilder {
         return this;
     }
 
+    public onCompletion(onCompletion: string): TaskBuilder {
+        if (onCompletion === 'delete') {
+            this._onCompletion = OnCompletion.Delete;
+        } else {
+            this._onCompletion = OnCompletion.Ignore;
+        }
+        return this;
+    }
+
     public blockLink(blockLink: string): TaskBuilder {
         this._blockLink = blockLink;
         return this;
diff --git a/tests/ui/EditableTask.test.ts b/tests/ui/EditableTask.test.ts
index 04e95357..bff9e5c2 100644
--- a/tests/ui/EditableTask.test.ts
+++ b/tests/ui/EditableTask.test.ts
@@ -5,6 +5,7 @@ import moment from 'moment';
 import type { Task } from 'Task/Task';
 import { GlobalFilter } from '../../src/Config/GlobalFilter';
 import { Status } from '../../src/Statuses/Status';
+import { OnCompletion } from '../../src/Task/OnCompletion';
 import { EditableTask } from '../../src/ui/EditableTask';
 import { TaskBuilder } from '../TestingTools/TaskBuilder';
 
@@ -54,6 +55,7 @@ describe('EditableTask tests', () => {
               "doneDate": "2023-07-05",
               "dueDate": "2023-07-04",
               "forwardOnly": true,
+              "onCompletion": "delete",
               "originalBlocking": [],
               "priority": "medium",
               "recurrenceRule": "every day when done",
@@ -140,6 +142,7 @@ describe('EditableTask tests', () => {
         editableTask.description = '';
         editableTask.status = Status.TODO;
         editableTask.priority = 'none';
+        editableTask.onCompletion = OnCompletion.Ignore;
         editableTask.recurrenceRule = '';
         editableTask.createdDate = '';
         editableTask.startDate = '';
@@ -168,7 +171,8 @@ describe('EditableTask tests', () => {
               "id": "abcdef",
               "indentation": "  ",
               "listMarker": "-",
-              "originalMarkdown": "  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c",
+              "onCompletion": "",
+              "originalMarkdown": "  - [ ] Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 🔁 every day when done 🏁 delete ➕ 2023-07-01 🛫 2023-07-02 ⏳ 2023-07-03 📅 2023-07-04 ❌ 2023-07-06 ✅ 2023-07-05 ^dcf64c",
               "parent": null,
               "priority": "3",
               "recurrence": null,