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

Issue:
Completion date and recurrence not being applied to task when selecting any "DONE" indicator from drop down status menu in modal
### 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 selecting any status indicator in the drop down list that indicates a task is done a completion date should be added to the task and for recurring tasks a new recurrence should be triggered.

### Current behaviour

When using the status drop down to mark a task the task does not get a completion date added and a recurrence is not triggered.

### Steps to reproduce

Bring up the tasks modal and select a DONE status from the drop down list instead of checking the box.

### Which Operating Systems are you using?

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

### Obsidian Version

1.1.9

### Tasks Plugin Version

1.23

### Checks

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

### Possible solution

_No response_

Pull request:
fix: Make Completion and Done dates & Recurrence work in modal status edits
<!--- Provide a general summary of your changes in the Title above -->

# Description

- Completion date, Cancelled date and recurrence are all now correctly applied when changing the status in the Edit Task modal.
- Update user docs
- Update contributing guide

## Motivation and Context

Fixes #1590.

## How has this been tested?

New automated tests. Huge thanks to @ilandikov for two excellent hours of pairing on Friday, to make this happen!

## Screenshots (if appropriate)

<img width="1277" alt="image" src="https://github.com/obsidian-tasks-group/obsidian-tasks/assets/4840096/afa6e6fa-d032-4ab0-823f-a238207deced">




## 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**)
- [x] **Contributing Guidelines** (prefix: `contrib` - any improvements to documentation content **for contributors** - see [Contributing to Tasks](https://publish.obsidian.md/tasks-contributing/))

Internal changes:

- [x] **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)

## 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).

**Note**: I do need to update the screenshot of the Tasks modal after the merge of another PR, which makes further changes to it.

## 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/contributing/Code/How does Tasks handle status changes.md b/contributing/Code/How does Tasks handle status changes.md
index 88c19aeb..2b9b256c 100644
--- a/contributing/Code/How does Tasks handle status changes.md
+++ b/contributing/Code/How does Tasks handle status changes.md
@@ -2,12 +2,12 @@
 
 You can toggle a task‘s status by:
 
-| #   | User mechanism                                                                                | Source-code location                                                                                                                                                                                                                                | Toggle behaviour                                                                                                                                                                                                                                                            |
-| --- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| 1   | using the **command** (may be bound to a hotkey).                                             | [src/Commands/ToggleDone.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Commands/ToggleDone.ts)                                                                                                                           | toggles the line directly where the cursor is in the file inside Obsidian's vault.                                                                                                                                                                                          |
-| 2   | clicking on a checkbox of an **inline task** in **Live Preview**.                             | [src/Obsidian/LivePreviewExtension.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Obsidian/LivePreviewExtension.ts)                                                                                                                         | toggles the line directly where the checkbox is on the "document" of CodeMirror (the library that Obsidian uses to show text on screen).<br>That, in turn, updates the file in Obsidian's Vault.                                                                            |
-| 3   | clicking on a checkbox of an **inline task** in **Reading mode**.                             | uses a checkbox created by `renderTaskLine()` in [src/Renderer/TaskLineRenderer](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Renderer/TaskLineRenderer.ts).<br>There, the checkbox gets a click event handler.                               | The click event listener of 3. and 4. uses `replaceTaskWithTasks()` in [src/Obsidian/File.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Obsidian/File.ts).<br>That, in turn, updates the file in Obsidian‘s Vault (like 1, but it needs to find the correct line). |
-| 4   | clicking on a checkbox in **query results** (same for **Reading mode** and **Live Preview**). | As 3                                                                                                                                                                                                                                                | As 3                                                                                                                                                                                                                                                                        |
-| 5   | via 'Create or edit task' **modal** Status dropdown                                           | [src/ui/EditTask.svelte](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/ui/EditTask.svelte) and [src/Commands/CreateOrEdit.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Commands/CreateOrEdit.ts) | Not yet implemented: see [#1590](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1590)                                                                                                                                                                        |
+| #   | User mechanism                                                                                | Source-code location                                                                                                                                                                                                                                                                                                                                                         | Toggle behaviour                                                                                                                                                                                                                                                                              |
+| --- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 1   | using the **command** (may be bound to a hotkey).                                             | [src/Commands/ToggleDone.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Commands/ToggleDone.ts)                                                                                                                                                                                                                                                    | toggles the line directly where the cursor is in the file inside Obsidian's vault.                                                                                                                                                                                                            |
+| 2   | clicking on a checkbox of an **inline task** in **Live Preview**.                             | [src/Obsidian/LivePreviewExtension.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Obsidian/LivePreviewExtension.ts)                                                                                                                                                                                                                                | toggles the line directly where the checkbox is on the "document" of CodeMirror (the library that Obsidian uses to show text on screen).<br>That, in turn, updates the file in Obsidian's Vault.                                                                                              |
+| 3   | clicking on a checkbox of an **inline task** in **Reading mode**.                             | uses a checkbox created by `renderTaskLine()` in [src/Renderer/TaskLineRenderer](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Renderer/TaskLineRenderer.ts).<br>There, the checkbox gets a click event handler.                                                                                                                                      | The click event listener of 3. and 4. uses `replaceTaskWithTasks()` in [src/Obsidian/File.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Obsidian/File.ts).<br>That, in turn, updates the file in Obsidian‘s Vault (like 1, but it needs to find the correct line). |
+| 4   | clicking on a checkbox in **query results** (same for **Reading mode** and **Live Preview**). | As 3                                                                                                                                                                                                                                                                                                                                                                         | As 3                                                                                                                                                                                                                                                                                          |
+| 5   | via 'Create or edit task' **modal** Status dropdown                                           | [src/ui/EditTask.svelte](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/ui/EditTask.svelte), [src/Obsidian/TaskModal.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Obsidian/TaskModal.ts) and [src/Commands/CreateOrEdit.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/src/Commands/CreateOrEdit.ts) | Callers of `new TaskModal(...)` pass in an `onSubmit()` callback that take care of saving the edited task.                                                                                                                                                                                    |
 
 Obsidian writes the changes to disk at its own pace.
diff --git a/docs/Editing/Create or edit Task.md b/docs/Editing/Create or edit Task.md
index 1915ae61..dce0c51d 100644
--- a/docs/Editing/Create or edit Task.md
+++ b/docs/Editing/Create or edit Task.md
@@ -128,14 +128,33 @@ Use the Status dropdown to change the Status Symbol for the task.
 
 For more information, including adding your own customised statuses, see [[Statuses]].
 
-> [!warning]
-> Editing the Status in the modal does not yet add, remove or update the Done date.
->
-> Also, completing a recurring task via the Status in the modal does not yet add the new recurrence.
->
-> We are tracking this in [issue #1590](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1590).
->
-> For now, you should still complete tasks via [[Toggling and Editing Statuses#'Tasks Toggle task done' command|command]], by [[Toggling and Editing Statuses#Toggling Tasks with mouse|clicking on task checkboxes]] or by [[Toggling and Editing Statuses#'Change task status' context menu|right-clicking on task checkboxes]].
+#### Automatic setting of dates upon status change
+
+When you change the Status Symbol for a Task, the [[Dates#Date-tracking settings|Date-tracking settings]] are used to determine whether to update any date fields:
+
+- **If** the status is changed **to** a [[Status Types#DONE|DONE status type]] **from** any other status type,
+  - **and** the "Set done date on every completed task" option is enabled,
+  - **then** the **Done date** is changed to today's date.
+- **If** the status is changed **to** a [[Status Types#CANCELLED|CANCELLED status type]] **from** any other status type,
+  - **and** the "Set cancelled date on every completed task" option is enabled,
+  - **then** the **Cancelled date** is changed to today's date.
+
+#### Overriding dates on status change
+
+When Tasks has automatically set a Created, Done or Cancelled date to today, you are free to edit the value.
+
+For example, perhaps you actually completed a task yesterday and forget to mark it as complete. Now you can complete it in the modal, and set the Done date to yesterday, before clicking Apply.
+
+#### Rewriting history: completing recurring tasks in the modal
+
+> [!Tip]
+> When marking `when done` recurring tasks as Done in the Edit Task modal, the date of the *next* occurrence is now calculated from the value in the Done date field.
+
+The Done date value defaults to the current date, but can then be edited before clicking Apply.
+
+This might be useful if you realise that you had forgotten to mark a recurring task as Done on the actual day that you completed it, and so you would like the new instance to be created based on the day you really completed the task, rather than today when you are marking it as done.
+
+(Today's date is still used for an Created day on a new recurrence, though.)
 
 ## Display values
 
@@ -161,17 +180,6 @@ To close the modal and cancel your edits, do one of:
 
 ## Known limitations
 
-### Editing status on Edit task modal
-
-> [!warning]
-> Editing the Status in the modal does not yet add, remove or update the Done date.
->
-> Also, completing a recurring task via the Status in the modal does not yet add the new recurrence.
->
-> We are tracking this in [issue #1590](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1590).
->
-> For now, you should still complete tasks via [[Toggling and Editing Statuses#'Tasks Toggle task done' command|command]], by [[Toggling and Editing Statuses#Toggling Tasks with mouse|clicking on task checkboxes]] or by [[Toggling and Editing Statuses#'Change task status' context menu|right-clicking on task checkboxes]].
-
 ### Need to scroll on phone screens
 
 On phone screens the 'Create or edit Task' Modal may be too tall to fit on the screen.
diff --git a/docs/Editing/Toggling and Editing Statuses.md b/docs/Editing/Toggling and Editing Statuses.md
index 823bf939..a895b2ad 100644
--- a/docs/Editing/Toggling and Editing Statuses.md
+++ b/docs/Editing/Toggling and Editing Statuses.md
@@ -82,12 +82,3 @@ Obsidian allows you to assign a [hotkey](https://help.obsidian.md/Customization/
 ## Edit task modal
 
 The [[Create or edit Task]] modal also allows editing of task statuses.
-
-> [!warning]
-> Unlike the other options listed in this page, editing the Status in the modal does not yet add, remove or update the Done date.
->
-> Also, completing a recurring task via the Status in the modal does not yet add the new recurrence.
->
-> We are tracking this in [issue #1590](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1590).
->
-> For now, you should still complete tasks via command or by clicking or [[Toggling and Editing Statuses#'Change task status' context menu|right-clicking]] on task checkboxes.
diff --git a/docs/Introduction.md b/docs/Introduction.md
index 75669936..b457b653 100644
--- a/docs/Introduction.md
+++ b/docs/Introduction.md
@@ -15,6 +15,7 @@ _In recent [releases](https://github.com/obsidian-tasks-group/obsidian-tasks/rel
 
 - X.Y.Z:
   - `query.allTasks` is now available in custom searches: see [[Query Properties#Values for Query Search Properties|query search properties]].
+  - The [[Create or edit Task]] modal now fully supports editing of statuses, updating done and cancelled dates, and creating new recurrences. See the [[Create or edit Task#Status|section on editing statuses]] for details and tips.
   - Add HTML samples in [[Styling#Sample HTML Full mode|full]] and [[Styling#Sample HTML Short mode|short]] modes to demonstrate custom styling.
 - 6.0.0:
   - Add [[Custom Sorting|custom sorting]].
diff --git a/src/Task/OnCompletion.ts b/src/Task/OnCompletion.ts
deleted file mode 100644
index 3946e67f..00000000
--- a/src/Task/OnCompletion.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { StatusType } from '../Statuses/StatusConfiguration';
-import type { Task } from './Task';
-
-export function handleOnCompletion(task: Task, tasks: Task[]): Task[] {
-    const tasksArrayLength = tasks.length;
-    if (tasksArrayLength === 0) {
-        return tasks;
-    }
-    const startStatus = task.status;
-
-    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) {
-        return tasks;
-    }
-
-    if (taskString.includes('🏁 Delete')) {
-        return tasks.filter((task) => task !== changedStatusTask);
-    }
-    // const errorMessage = 'Unknown "On Completion" action: ' + ocAction;
-    const errorMessage = 'Unknown "On Completion" action';
-    console.log(errorMessage);
-    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/Recurrence.ts b/src/Task/Recurrence.ts
index b8d10cee..20a1ff79 100644
--- a/src/Task/Recurrence.ts
+++ b/src/Task/Recurrence.ts
@@ -122,13 +122,15 @@ export class Recurrence {
 
     /**
      * Returns the dates of the next occurrence or null if there is no next occurrence.
+     *
+     * @param today - Optional date representing the completion date. Defaults to today.
      */
-    public next(): {
+    public next(today = window.moment()): {
         startDate: Moment | null;
         scheduledDate: Moment | null;
         dueDate: Moment | null;
     } | null {
-        const next = this.nextReferenceDate();
+        const next = this.nextReferenceDate(today);
 
         if (next !== null) {
             // Keep the relative difference between the reference date and
@@ -195,11 +197,10 @@ export class Recurrence {
         return this.toText() === other.toText(); // this also checks baseOnToday
     }
 
-    private nextReferenceDate(): Date {
+    private nextReferenceDate(today: Moment): Date {
         if (this.baseOnToday) {
             // The next occurrence should happen based off the current date.
             // begin-snippet: use-moment-in-src
-            const today = window.moment();
             // end-snippet
             return this.nextReferenceDateFromToday(today).toDate();
         } else {
diff --git a/src/Task/Task.ts b/src/Task/Task.ts
index 57e5c152..8d1769c3 100644
--- a/src/Task/Task.ts
+++ b/src/Task/Task.ts
@@ -317,17 +317,46 @@ export class Task {
         return newTasks;
     }
 
-    public handleNewStatus(newStatus: Status): Task[] {
+    /**
+     * Edits the {@link status} of this task and returns the resulting task(s).
+     *
+     * Use this method if you need to know which is the original (edited)
+     * task and which is the new recurrence, if any.
+     *
+     * If the task is not recurring, it will return `[edited]`,
+     * or `[this]` if the status is unchanged.
+     *
+     * Editing the status can result in more than one returned task in the case of
+     * recurrence. In this case, the edited task will be returned
+     * together with the next occurrence in the order `[next, edited]`.
+     *
+     * There is a possibility to use user set order `[next, edited]`
+     * or `[toggled, next]` - {@link handleNewStatusWithRecurrenceInUsersOrder}.
+     *
+     * @param newStatus
+     * @param today - Optional date representing the completion date. This defaults to today.
+     *                It is used for any new done date, and for the calculation of new
+     *                dates on recurring tasks that are marked as 'when done'.
+     *                However, any created date on a new recurrence is, for now, calculated from the
+     *                actual current date, rather than this parameter.
+     */
+    public handleNewStatus(newStatus: Status, today = window.moment()): Task[] {
         if (newStatus.identicalTo(this.status)) {
             // There is no need to create a new Task object if the new status behaviour is identical to the current one.
             return [this];
         }
 
         const { setDoneDate } = getSettings();
-        const newDoneDate = this.newDate(newStatus, StatusType.DONE, this.doneDate, setDoneDate);
+        const newDoneDate = this.newDate(newStatus, StatusType.DONE, this.doneDate, setDoneDate, today);
 
         const { setCancelledDate } = getSettings();
-        const newCancelledDate = this.newDate(newStatus, StatusType.CANCELLED, this.cancelledDate, setCancelledDate);
+        const newCancelledDate = this.newDate(
+            newStatus,
+            StatusType.CANCELLED,
+            this.cancelledDate,
+            setCancelledDate,
+            today,
+        );
 
         let nextOccurrence: {
             startDate: Moment | null;
@@ -336,7 +365,7 @@ export class Task {
         } | null = null;
         if (newStatus.isCompleted()) {
             if (!this.status.isCompleted() && this.recurrence !== null) {
-                nextOccurrence = this.recurrence.next();
+                nextOccurrence = this.recurrence.next(today);
             }
         }
 
@@ -371,13 +400,14 @@ export class Task {
         statusType: StatusType,
         oldDate: moment.Moment | null,
         dateEnabledInSettings: boolean,
+        today: moment.Moment,
     ) {
         let newDate = null;
         if (newStatus.type === statusType) {
             if (this.status.type !== statusType) {
                 // Set date only if setting value is true.
                 if (dateEnabledInSettings) {
-                    newDate = window.moment();
+                    newDate = today;
                 }
             } else {
                 // This task was already in statusType, so preserve its existing date.
@@ -402,6 +432,11 @@ export class Task {
         }
         // In case the task being toggled was previously cancelled, ensure the new task has no cancelled date:
         const cancelledDate = null;
+
+        // Also set the new done date to zero, to simplify the
+        // saving of edited tasks in the Edit Task modal:
+        const doneDate = null;
+
         const statusRegistry = StatusRegistry.getInstance();
         const nextStatus = statusRegistry.getNextRecurrenceStatusOrCreate(newStatus);
         return new Task({
@@ -419,6 +454,7 @@ export class Task {
             // add new createdDate on recurring tasks
             createdDate,
             cancelledDate,
+            doneDate,
         });
     }
 
@@ -444,13 +480,13 @@ export class Task {
         return this.putRecurrenceInUsersOrder(newTasks);
     }
 
-    public handleNewStatusWithRecurrenceInUsersOrder(newStatus: Status): Task[] {
+    public handleNewStatusWithRecurrenceInUsersOrder(newStatus: Status, today = window.moment()): Task[] {
         const logger = logging.getLogger('tasks.Task');
         logger.debug(
             `changed task ${this.taskLocation.path} ${this.taskLocation.lineNumber} ${this.originalMarkdown} status to '${newStatus.symbol}'`,
         );
 
-        const newTasks = this.handleNewStatus(newStatus);
+        const newTasks = this.handleNewStatus(newStatus, today);
         return this.putRecurrenceInUsersOrder(newTasks);
     }
 
diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte
index a1ff98aa..95a03d16 100644
--- a/src/ui/EditTask.svelte
+++ b/src/ui/EditTask.svelte
@@ -20,6 +20,8 @@
     export let statusOptions: Status[];
     export let allTasks: Task[];
 
+    let statusSymbol = task.status.symbol;
+
     const {
         // NEW_TASK_FIELD_EDIT_REQUIRED
         prioritySymbols,
@@ -211,6 +213,27 @@
         return updatedTask;
     }
 
+    const _onStatusChange = () => {
+        // Use statusSymbol to find the status to save to editableTask.status
+        const selectedStatus: Status | undefined = statusOptions.find((s)=> s.symbol === statusSymbol);
+        if (selectedStatus) {
+            editableTask.status = selectedStatus;
+        } else {
+            console.log(`Error in EditTask: cannot find status with symbol ${statusSymbol}`);
+            return;
+        }
+
+        // Obtain a temporary task with the new status applied, to see what would
+        // happen to the done date:
+        const taskWithEditedStatusApplied = task.handleNewStatus(selectedStatus).pop();
+
+        if (taskWithEditedStatusApplied) {
+            // Update the doneDate field, in case changing the status changed the value:
+            editableTask.doneDate = taskWithEditedStatusApplied.done.formatAsDate();
+            editableTask.cancelledDate = taskWithEditedStatusApplied.cancelled.formatAsDate();
+        }
+    }
+
     $: accesskey = (key: string) => withAccessKeys ? key : null;
     $: formIsValid = isDueDateValid && isRecurrenceValid && isScheduledDateValid && isStartDateValid && isDescriptionValid && isCancelledDateValid && isCreatedDateValid && isDoneDateValid;
     $: isDescriptionValid = editableTask.description.trim() !== '';
@@ -430,11 +453,12 @@
             addedBlocking = editableTask.blocking.filter(task => !originalBlocking.includes(task))
         }
 
+        // First create an updated task, with all edits except Status:
         const updatedTask = new Task({
             // NEW_TASK_FIELD_EDIT_REQUIRED
             ...task,
             description,
-            status: editableTask.status,
+            status: task.status,
             priority: parsedPriority,
             recurrence,
             startDate,
@@ -457,7 +481,13 @@
             await replaceTaskWithTasks({originalTask: blocking, newTasks: newParent});
         }
 
-        onSubmit([updatedTask]);
+        // Then apply the new status to the updated task, in case a new recurrence
+        // needs to be created.
+        // If there is a 'done' date, use that for today's date for recurrence calculations.
+        // Otherwise, use the current date.
+        const today = doneDate ? doneDate : window.moment();
+        const newTasks = updatedTask.handleNewStatusWithRecurrenceInUsersOrder(editableTask.status, today);
+        onSubmit(newTasks);
     };
 </script>
 
@@ -661,33 +691,15 @@ Availability of access keys:
         <div class="tasks-modal-section">
             <label for="status">Stat<span class="accesskey">u</span>s</label>
             <!-- svelte-ignore a11y-accesskey -->
-            <select bind:value={editableTask.status}
+            <select bind:value={statusSymbol}
+                    on:change={_onStatusChange}
                     id="status-type"
                     class="dropdown"
                     accesskey={accesskey('u')}>
                 {#each statusOptions as status}
-                    <option value={status}>{status.name} [{status.symbol}]</option>
+                    <option value={status.symbol}>{status.name} [{status.symbol}]</option>
                 {/each}
             </select>
-            <!-- svelte-ignore a11y-label-has-associated-control -->
-            <label class="tasks-modal-warning">⚠️ Changing the status does not yet auto-update Done or Cancelled Dates, nor create a new recurrence.
-                Complete tasks via command, by clicking on task checkboxes or by right-clicking on task checkboxes.</label>
-        </div>
-
-        <div class="tasks-modal-section tasks-modal-status">
-            <!-- --------------------------------------------------------------------------- -->
-            <!--  Completed  -->
-            <!-- --------------------------------------------------------------------------- -->
-            <div>
-                <label for="status">Completed:</label>
-                <input
-                    id="status"
-                    type="checkbox"
-                    class="task-list-item-checkbox tasks-modal-checkbox"
-                    checked={editableTask.status.isCompleted()}
-                    disabled
-                />
-            </div>
         </div>
 
         <div class="tasks-modal-section tasks-modal-dates">


Test patch:
diff --git a/tests/Task/OnCompletion.test.ts b/tests/Task/OnCompletion.test.ts
deleted file mode 100644
index 703db479..00000000
--- a/tests/Task/OnCompletion.test.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import moment from 'moment';
-import { RecurrenceBuilder } from '../TestingTools/RecurrenceBuilder';
-import { Status } from '../../src/Statuses/Status';
-import { StatusConfiguration, StatusType } from '../../src/Statuses/StatusConfiguration';
-import { TaskBuilder } from '../TestingTools/TaskBuilder';
-import { toLines } from '../TestingTools/TestHelpers';
-import type { Task } from '../../src/Task/Task';
-import { handleOnCompletion } from '../../src/Task/OnCompletion';
-
-window.moment = moment;
-
-beforeEach(() => {
-    jest.useFakeTimers();
-    jest.setSystemTime(new Date('2024-02-11'));
-});
-
-afterEach(() => {
-    jest.useRealTimers();
-    // resetSettings();
-});
-
-export function applyStatusAndOnCompletionAction(task: Task, newStatus: Status) {
-    const tasks = task.handleNewStatus(newStatus);
-    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"',
-        );
-    });
-
-    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')
-            .dueDate(dueDate)
-            .doneDate(dueDate)
-            .status(Status.DONE)
-            .build();
-        expect(task.status.type).toEqual(StatusType.DONE);
-
-        // Act
-        const returnedTasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
-
-        // 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"',
-        );
-    });
-
-    it('should just return trigger-less, non-recurring task', () => {
-        // Arrange
-        const dueDate = '2024-02-10';
-        const task = new TaskBuilder().description('A non-recurring task with no trigger').dueDate(dueDate).build();
-        expect(task.status.type).toEqual(StatusType.TODO);
-
-        // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
-
-        // Assert
-        expect(tasks.length).toEqual(1);
-        expect(toLines(tasks).join('\n')).toMatchInlineSnapshot(
-            '"- [x] A non-recurring task with no trigger 📅 2024-02-10 ✅ 2024-02-11"',
-        );
-    });
-
-    it('should just return trigger-less recurring task', () => {
-        // 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 no trigger')
-            .recurrence(recurrence)
-            .dueDate(dueDate)
-            .build();
-        expect(task.status.type).toEqual(StatusType.TODO);
-
-        // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
-
-        // Assert
-        expect(tasks.length).toEqual(2);
-        expect(toLines(tasks).join('\n')).toMatchInlineSnapshot(`
-            "- [ ] A recurring task with no trigger 🔁 every day 📅 2024-02-11
-            - [x] A recurring task with no trigger 🔁 every day 📅 2024-02-10 ✅ 2024-02-11"
-        `);
-    });
-
-    // 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', () => {
-        // 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);
-
-        // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
-
-        // Assert
-        expect(tasks).toEqual([]);
-    });
-
-    it('should return only the next instance of a 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)
-            .dueDate(dueDate)
-            .build();
-        expect(task.status.type).toEqual(StatusType.TODO);
-
-        // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
-
-        // Assert
-        expect(tasks.length).toEqual(1);
-        expect(toLines(tasks).join('\n')).toMatchInlineSnapshot(
-            '"- [ ] A recurring task with 🏁 Delete 🔁 every day 📅 2024-02-11"',
-        );
-    });
-    it('should delete a simple task with flag on completion', () => {
-        // Arrange
-        const task = new TaskBuilder().description('A non-recurring task with 🏁 Delete').build();
-
-        // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
-
-        // Assert
-        expect(tasks.length).toEqual(0);
-    });
-
-    it('should return the task when going from TODO to IN_PROGRESS', () => {
-        // 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)
-            .dueDate(dueDate)
-            .build();
-
-        // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeInProgress());
-
-        // Assert
-        expect(tasks.length).toEqual(1);
-        expect(tasks[0].status.type).toEqual(StatusType.IN_PROGRESS);
-    });
-
-    it('should return the task when going from one DONE status to another DONE status', () => {
-        // 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();
-
-        // Act
-        const tasks = applyStatusAndOnCompletionAction(task, done2);
-
-        // Assert
-        expect(tasks.length).toEqual(1);
-        expect(tasks[0].status.symbol).toEqual('X');
-        expect(tasks[0].status.type).toEqual(StatusType.DONE);
-    });
-
-    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 🏁').build();
-
-        // Act
-        const tasks = applyStatusAndOnCompletionAction(task, Status.makeDone());
-
-        // Assert
-        expect(tasks.length).toEqual(1);
-    });
-});
diff --git a/tests/Task/Task.test.ts b/tests/Task/Task.test.ts
index 8f246ba3..fb4b129e 100644
--- a/tests/Task/Task.test.ts
+++ b/tests/Task/Task.test.ts
@@ -1243,6 +1243,11 @@ describe('handle new status', () => {
     beforeEach(() => {
         jest.useFakeTimers();
         jest.setSystemTime(new Date('2023-06-26'));
+        resetSettings();
+    });
+
+    afterEach(() => {
+        resetSettings();
     });
 
     // Note: We only need to test transitions which are not covered by the standard 'toggle done' tests above.
@@ -1305,6 +1310,26 @@ describe('handle new status', () => {
         expect(newTasks[0].status.symbol).toEqual('X');
     });
 
+    it('should allow "today" to be overridden, and to determine created, done and next due dates', () => {
+        // Arrange
+        const originalTask = fromLine({
+            line: '- [ ] Annual task 🔁 every year when done 📅 1989-12-23',
+        });
+        const newStatus = StatusRegistry.getInstance().bySymbol('x');
+        updateSettings({ setCreatedDate: true });
+
+        // Act
+        const completionDate = moment('2023-01-23');
+        const newTasks = originalTask.handleNewStatus(newStatus, completionDate);
+
+        // Assert
+        // 'Created' date of new task is based on today, ignoring the manually set completion date.
+        expect(toMarkdown(newTasks)).toMatchInlineSnapshot(`
+            "- [ ] Annual task 🔁 every year when done ➕ 2023-06-26 📅 2024-01-23
+            - [x] Annual task 🔁 every year when done 📅 1989-12-23 ✅ 2023-01-23"
+        `);
+    });
+
     describe('cancelled dates and new status', () => {
         it('should add cancelled date and remove done date, if changing from DONE to CANCELLED', () => {
             // Arrange
diff --git a/tests/ui/EditTask.test.ts b/tests/ui/EditTask.test.ts
index 0c2970bc..7baa6183 100644
--- a/tests/ui/EditTask.test.ts
+++ b/tests/ui/EditTask.test.ts
@@ -7,16 +7,14 @@ import moment from 'moment';
 import { taskFromLine } from '../../src/Commands/CreateOrEditTaskParser';
 import type { Task } from '../../src/Task/Task';
 import EditTask from '../../src/ui/EditTask.svelte';
-import { Status } from '../../src/Statuses/Status';
 import { DateFallback } from '../../src/Task/DateFallback';
 import { GlobalFilter } from '../../src/Config/GlobalFilter';
 import { resetSettings, updateSettings } from '../../src/Config/Settings';
 import { verifyAllCombinations3Async } from '../TestingTools/CombinationApprovalsAsync';
 import { TaskBuilder } from '../TestingTools/TaskBuilder';
+import { StatusRegistry } from '../../src/Statuses/StatusRegistry';
 
 window.moment = moment;
-const statusOptions: Status[] = [Status.DONE, Status.TODO];
-
 /**
  * Construct an onSubmit function for editing the given task, and when Apply is clicked,
  * returning the edit task(s) converted to a string.
@@ -39,20 +37,31 @@ function constructSerialisingOnSubmit(task: Task) {
 }
 
 function renderAndCheckModal(task: Task, onSubmit: (updatedTasks: Task[]) => void, allTasks = [task]) {
-    const result: RenderResult<EditTask> = render(EditTask, { task, statusOptions, onSubmit, allTasks });
+    const result: RenderResult<EditTask> = render(EditTask, {
+        task,
+        statusOptions: StatusRegistry.getInstance().registeredStatuses,
+        onSubmit,
+        allTasks,
+    });
     const { container } = result;
     expect(() => container).toBeTruthy();
     return { result, container };
 }
 
-function getAndCheckRenderedElement(container: HTMLElement, elementId: string) {
-    const renderedDescription = container.ownerDocument.getElementById(elementId) as HTMLInputElement;
-    expect(() => renderedDescription).toBeTruthy();
-    return renderedDescription;
+/**
+ * Find the element with the given id.
+ * Template type T might be, for example, HTMLInputElement or HTMLSelectElement
+ * @param container
+ * @param elementId
+ */
+function getAndCheckRenderedElement<T>(container: HTMLElement, elementId: string) {
+    const element = container.ownerDocument.getElementById(elementId) as T;
+    expect(() => element).toBeTruthy();
+    return element;
 }
 
 function getAndCheckRenderedDescriptionElement(container: HTMLElement): HTMLInputElement {
-    return getAndCheckRenderedElement(container, 'description');
+    return getAndCheckRenderedElement<HTMLInputElement>(container, 'description');
 }
 
 function getAndCheckApplyButton(result: RenderResult<EditTask>): HTMLButtonElement {
@@ -61,13 +70,17 @@ function getAndCheckApplyButton(result: RenderResult<EditTask>): HTMLButtonEleme
     return submit;
 }
 
+async function editInputElement(inputElement: HTMLInputElement, newValue: string) {
+    await fireEvent.input(inputElement, { target: { value: newValue } });
+}
+
 async function editInputElementAndSubmit(
     inputElement: HTMLInputElement,
     newValue: string,
     submit: HTMLButtonElement,
     waitForClose: Promise<string>,
 ): Promise<string> {
-    await fireEvent.input(inputElement, { target: { value: newValue } });
+    await editInputElement(inputElement, newValue);
     submit.click();
     return await waitForClose;
 }
@@ -123,12 +136,31 @@ async function editFieldAndSave(line: string, elementId: string, newValue: strin
     const { waitForClose, onSubmit } = constructSerialisingOnSubmit(task);
     const { result, container } = renderAndCheckModal(task, onSubmit);
 
-    const description = getAndCheckRenderedElement(container, elementId);
+    const description = getAndCheckRenderedElement<HTMLInputElement>(container, elementId);
     const submit = getAndCheckApplyButton(result);
 
     return await editInputElementAndSubmit(description, newValue, submit, waitForClose);
 }
 
+async function renderTaskModalAndChangeStatus(line: string, newStatusSymbol: string) {
+    const task = taskFromLine({ line: line, path: '' });
+    const { waitForClose, onSubmit } = constructSerialisingOnSubmit(task);
+    const { result, container } = renderAndCheckModal(task, onSubmit);
+
+    const statusSelector = getAndCheckRenderedElement<HTMLSelectElement>(container, 'status-type');
+    const submit = getAndCheckApplyButton(result);
+
+    await fireEvent.change(statusSelector, {
+        target: { value: newStatusSymbol },
+    });
+    return { waitForClose, container, submit };
+}
+
+function getElementValue(container: HTMLElement, elementId: string) {
+    const element = getAndCheckRenderedElement<HTMLInputElement>(container, elementId);
+    return element.value;
+}
+
 describe('Task rendering', () => {
     afterEach(() => {
         GlobalFilter.getInstance().reset();
@@ -140,7 +172,7 @@ describe('Task rendering', () => {
         const onSubmit = (_: Task[]): void => {};
         const { container } = renderAndCheckModal(task, onSubmit);
 
-        const inputElement = getAndCheckRenderedElement(container, elementId);
+        const inputElement = getAndCheckRenderedElement<HTMLInputElement>(container, elementId);
         expect(inputElement!.value).toEqual(expectedElementValue);
     }
 
@@ -285,6 +317,111 @@ describe('Task editing', () => {
         );
     });
 
+    describe('Status editing', () => {
+        const today = '2024-02-29';
+        beforeAll(() => {
+            jest.useFakeTimers();
+            jest.setSystemTime(new Date(today));
+        });
+
+        afterAll(() => {
+            jest.useRealTimers();
+        });
+
+        afterEach(() => {
+            resetSettings();
+        });
+
+        const line = '- [ ] simple';
+        it('should change status to Done and add doneDate', async () => {
+            const { waitForClose, container, submit } = await renderTaskModalAndChangeStatus(line, 'x');
+            expect(getElementValue(container, 'done')).toEqual(today);
+
+            submit.click();
+            expect(await waitForClose).toMatchInlineSnapshot('"- [x] simple ✅ 2024-02-29"');
+        });
+
+        it('should change status to Todo and remove doneDate', async () => {
+            const { waitForClose, container, submit } = await renderTaskModalAndChangeStatus(
+                '- [x] simple ✅ 2024-02-29',
+                ' ',
+            );
+            expect(getElementValue(container, 'done')).toEqual('');
+
+            submit.click();
+            expect(await waitForClose).toMatchInlineSnapshot('"- [ ] simple"');
+        });
+
+        it('should change status to Cancelled and add cancelledDate', async () => {
+            const { waitForClose, container, submit } = await renderTaskModalAndChangeStatus(line, '-');
+            expect(getElementValue(container, 'cancelled')).toEqual(today);
+
+            submit.click();
+            expect(await waitForClose).toMatchInlineSnapshot('"- [-] simple ❌ 2024-02-29"');
+        });
+
+        it('should create new instance of recurring task, with doneDate set to today', async () => {
+            updateSettings({ recurrenceOnNextLine: false });
+            const { waitForClose, submit } = await renderTaskModalAndChangeStatus(
+                '- [ ] Recurring 🔁 every day 📅 2024-02-17',
+                'x',
+            );
+
+            submit.click();
+            expect(await waitForClose).toMatchInlineSnapshot(`
+                "- [ ] Recurring 🔁 every day 📅 2024-02-18
+                - [x] Recurring 🔁 every day 📅 2024-02-17 ✅ 2024-02-29"
+            `);
+        });
+
+        it('should respect user setting for order of new recurring tasks', async () => {
+            updateSettings({ recurrenceOnNextLine: true });
+            const { waitForClose, submit } = await renderTaskModalAndChangeStatus(
+                '- [ ] Recurring 🔁 every day 📅 2024-02-17',
+                'x',
+            );
+
+            submit.click();
+            expect(await waitForClose).toMatchInlineSnapshot(`
+                "- [x] Recurring 🔁 every day 📅 2024-02-17 ✅ 2024-02-29
+                - [ ] Recurring 🔁 every day 📅 2024-02-18"
+            `);
+        });
+
+        it('should create new instance of "when done" recurring task, with doneDate set to today', async () => {
+            updateSettings({ setCreatedDate: true });
+
+            const { waitForClose, submit } = await renderTaskModalAndChangeStatus(
+                '- [ ] Recurring 🔁 every day when done 📅 2024-02-17',
+                'x',
+            );
+
+            submit.click();
+            expect(await waitForClose).toMatchInlineSnapshot(`
+                "- [ ] Recurring 🔁 every day when done ➕ 2024-02-29 📅 2024-03-01
+                - [x] Recurring 🔁 every day when done 📅 2024-02-17 ✅ 2024-02-29"
+            `);
+        });
+
+        it('should calculate the next recurrence date based on the actual done date in the field', async () => {
+            updateSettings({ setCreatedDate: true });
+
+            const { waitForClose, container, submit } = await renderTaskModalAndChangeStatus(
+                '- [ ] Recurring 🔁 every day when done 📅 2024-02-17',
+                'x',
+            );
+
+            const doneField = getAndCheckRenderedElement<HTMLInputElement>(container, 'done');
+            await editInputElement(doneField, '2024-02-23');
+
+            submit.click();
+            expect(await waitForClose).toMatchInlineSnapshot(`
+                "- [ ] Recurring 🔁 every day when done ➕ 2024-02-29 📅 2024-02-24
+                - [x] Recurring 🔁 every day when done 📅 2024-02-17 ✅ 2024-02-23"
+            `);
+        });
+    });
+
     describe('Date editing', () => {
         const line = '- [ ] simple';