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

Issue:
Scheduled date breaks repeat of start date [if removeScheduledDateOnRecurrence enabled & there is no due date]
### ⚠️ Check issue is valid.

- [x] My request is about the [Tasks plugin for Obsidian](https://publish.obsidian.md/tasks/Introduction).

### ⚠️ Check that this issue hasn't been reported before.

- [x] I searched previous [Tasks plugin 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.

### Steps to reproduce

1. Sandbox, tasks plugin
2. Settings, enable the "Remove scheduled date on recurrence"
3. Create a task from the following markdown:

```
- [ ] Example 1 🛫 2025-08-03 🔁 every 2 days when done ⏳ 2025-08-05

- [ ] Example 2 🛫 2025-08-03 🔁 every 2 days when done
```

4. Check both tasks

### Expected Behavior

You expect to get the following tasks:

```
- [ ] Example 1 🔁 every 2 days when done 🛫 2025-08-08
- [x] Example 1 🔁 every 2 days when done 🛫 2025-08-03 ⏳ 2025-08-05 ✅ 2025-08-06

- [ ] Example 2 🔁 every 2 days when done 🛫 2025-08-08
- [x] Example 2 🔁 every 2 days when done 🛫 2025-08-03 ✅ 2025-08-06
```

### Current behaviour

But you actually get this:

```
- [ ] Example 1 🔁 every 2 days when done 🛫 2025-08-06
- [x] Example 1 🔁 every 2 days when done 🛫 2025-08-03 ⏳ 2025-08-05 ✅ 2025-08-06

- [ ] Example 2 🔁 every 2 days when done 🛫 2025-08-08
- [x] Example 2 🔁 every 2 days when done 🛫 2025-08-03 ✅ 2025-08-06
```

The difference is the start date of the first task.  When there is also a scheduled date, on that task, the repetition uses the wrong date. I think this is because the recurrence is calculated from the scheduled date. I guess there is a bug in the PR I submitted for the remove the scheduled date feature.

### Which Operating Systems are you using?

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

### Obsidian Version

1.8.10

### Tasks Plugin Version

7.20.0

### Checks

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

### Possible solution

I will take a look at the code and see if I can figure out where the problem lies.


Discussion:
> the repetition uses the wrong date

I have read the above multiple times, and it's not clear to me why you feel that is the wrong date.

Also, what is the minimal reproduction? Why does it need two tasks to demonstrate the problem?
The relevant section of the docs is here:
https://publish.obsidian.md/tasks/Getting+Started/Recurring+Tasks#Remove+scheduled+date+on+recurrence

Ah, I see you are the author of the original PR! I guess I don't need to explain how it works. 😄 

- https://github.com/obsidian-tasks-group/obsidian-tasks/pull/3423

The minimal reproduction is Example 1. The two tasks should, as I understand it, be the same when repeated. The "drop scheduled date on recurrenct" flag should make the task repeat while ignoring the scheduled date. While in this case, they repeat based on the scheduled date, but then delete the scheduled date afterwards.

The "repeat when done" should use the done date as the repetition start date. But in this case, the next start date of the task is in the past. That's being caused by the "drop scheduled date on recurrence" flag.

Not sure if that's all making the situation any clearer. I'll try it from a different perspective. My assumption is that when I say "repeat every 2 days when done", and then I mark a task as done, the newly created task will have a date that is 2 days from today. The example above shows that it does not, the newly created task has today's date. That doesn't make sense to me.
> Ah, I see you are the author of the original PR! I guess I don't need to explain how it works. 😄
> 
>     * [feat: Add option to remove Scheduled date on new recurring tasks #3423](https://github.com/obsidian-tasks-group/obsidian-tasks/pull/3423)

Yes, in my PR I didn't consider the "when done" case. And I think it's just no handled properly. I'm looking at the code again now to see if I can write a test that fails as a starting point.
@claremacrae I'm looking at the tests here:

https://github.com/obsidian-tasks-group/obsidian-tasks/blob/4e55a6408f40ab073c2ad8bf706dcf385ebae0e4/tests/Task/Recurrence.test.ts#L253-L267

Do you know how I can add the current date to test a `when done` repetition? I can't figure out how the current date is being set in these tests...
I feel that the name "Remove scheduled date on recurrence" implies that it is doing two things:

1. Create the new instance, as per the standard algorithm
2. Then remove the Scheduled date from the new task


> Do you know how I can add the current date to test a `when done` repetition? I can't figure out how the current date is being set in these tests...

Look for uses of `jest.useFakeTimers` in the existing tests...

The standard pattern is as follows, although you can make it finer-grained than that if need be. The important thing is to ensure you turn off the fake times after tests.

```ts
beforeEach(() => {
    jest.useFakeTimers();
    jest.setSystemTime(new Date('2023-06-11'));
});

afterEach(() => {
    jest.useRealTimers();
});
```
Awesome, thanks. Okay, I created a test which fails, and in my view, should not fail.
https://github.com/chmac/obsidian-tasks/tree/3553-failing-test

I think the behaviour "create task based on standard logic and then delete the scheduled date" creates unexpected behaviour. I think when the option says "remove scheduled date on recurrence" it's also reasonable to assume that the date is not considered when calculating the recurrence, rather than used to calculate recurrence and then deleted.

I still don't understand why this bug occurs though. As far as I can tell from reading the code, when we enable "remove scheduled date on recurrence", we remove the date *before* we calculate the next recurrence of the task. Therefore it should not be considered when choosing future dates. But somehow it is being.
Apologies, my branch was empty. I didn't notice the push failed because of an environment issue on my local machine.

I find that with this change, all tests pass, including the test that I introduced to reproduce this bug.

https://github.com/chmac/obsidian-tasks/commit/5ae681bf813724c11fca40173e6f86fbe9823d5a

@claremacrae Do you think it makes sense to deploy this change? There might be consequences that I'm not seeing beyond just the tests passing.
> Apologies, my branch was empty. I didn't notice the push failed because of an environment issue on my local machine.
> 
> I find that with this change, all tests pass, including the test that I introduced to reproduce this bug.
> 
> [chmac@5ae681b](https://github.com/chmac/obsidian-tasks/commit/5ae681bf813724c11fca40173e6f86fbe9823d5a)
> 
> [@claremacrae](https://github.com/claremacrae) Do you think it makes sense to deploy this change? There might be consequences that I'm not seeing beyond just the tests passing.

Hi, many thanks for taking a look at this.

No, this fix definitely cannot be deployed. 😄 
It would be a breaking change, as Tasks would no longer honour this bit of the docs: [Priority of Dates](https://publish.obsidian.md/tasks/Getting+Started/Recurring+Tasks#Priority%20of%20Dates).

Today I learned that `Occurrence.getReferenceDate()` was not actually covered by tests, so I've just push some tests that will correctly fail on that above change: https://github.com/obsidian-tasks-group/obsidian-tasks/commit/3a85ddd663a1584d32f5940d810d2835aa106ff1

> I still don't understand why this bug occurs though. As far as I can tell from reading the code, when we enable "remove scheduled date on recurrence", we remove the date before we calculate the next recurrence of the task. Therefore it should not be considered when choosing future dates. But somehow it is being.

Also, all my experience tells me never to merge code - by me or anyone else - that fixes something, but the original behaviour is not understood. Doing this has always, without fail, come back to bite me at some point in the future!
I've tried to understand your failing test, but I'm not seeing how I can run it, on your branch, before you added the change in behaviour.

When creating a failing test to demonstrate a bug, it's a great idea to make a first commit with that failing test in, and marked as `it.failing(...)`.

That way, it:

1. reliably demonstrates the original behaviour
2. allows checking out of that revision, to confirm that the test really did fail with the original code
3. allows debugging of the original code, with the new test
Yeah, I meant to commit the test first but it failed because of the commit hooks and I didn't notice. You could cherry pick that commit, or I'll try to swap the commit order when I'm on my laptop (currently on my phone).
I think you will want to drop the commit that changed the behaviour anyway, and once there is a failing test committed, and can see if I understand the test, and can maybe spend a few minutes debugging it. 

I’m also interested in seeing how clear the “should” statement in the failing test will be, describing in words what exactly the test is proving. 
Another tip is not to commit with `.only()` - as, as far as I understand it, it prevents other tests from running, meaning that absence of test failures doesn't guarantee correct behaviour:

https://github.com/obsidian-tasks-group/obsidian-tasks/blob/89102cd5c900d9487e006276375fc7825c0b1d66/tests/Task/Recurrence.test.ts#L269-L295
>      it('calculates the correct start date when dropScheduledDate is true and repeat is when done', () => { 


I'm not seeing where `dropScheduledDate` is used anywhere in that test....
What I am missing from this issue is a real-world example scenario and description of the desired behaviour, using the documented meanings of the 3 date types.

Currently, there is a statement that the current behaviour is wrong - and I am not disagreeing with that - but I do not use this combination of dates, so I would really appreciate something more concrete and meaningful than:

> When there is also a scheduled date, on that task, the repetition uses the wrong date

Here is a useful example, justifying the feature you originally kindly implemented:

https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2751#issuecomment-2047911330
> Another tip is not to commit with `.only()` - as, as far as I understand it, it prevents other tests from running, meaning that absence of test failures doesn't guarantee correct behaviour:

Hahaha, yes, you're absolutely right. That was a mistake. Nice catch.

> What I am missing from this issue is a real-world example scenario and description of the desired behaviour, using the documented meanings of the 3 date types.

Makes sense. I'll tell you how this came up for me. I have a recurring task to pump my bicycle tyres. It repeats 3 weeks after it was last scheduled. It has only a start date, because it's not deadline driven. Then when I want to "snooze" it from my currently focused tasks, I set a scheduled date of tomorrow, or next week, etc. Then it's not shown on my "home" dashboard, but will reappear after the scheduled date. Once I check it off, it should be scheduled 3 weeks from now, because my tyres need inflated 3 weeks after the last time, even if it was 6 weeks since they were last inflated.

I hit this problem, because I checked the task, and it was still on my list. Then I realised I'd been ignoring it for a year (🙈) and it was repeated for some date last year. I expected with the `when done` flag, it would be scheduled for 3 weeks from today. But because I have a scheduled date, and I have the "remove scheduled dates from recurring tasks" enabled, then it ended up with a start date far in the past.

In terms of potential solutions, I thought it might not be as simple as changing the selection priority! What about trying to pass the `dropScheduledDate` flag down into the `Occurrence`, so that if that setting is enabled, we change the preference order in `Occurrence.getReferenceDate()`? If that doesn't make sense, or isn't clear, I can whip up a quick and dirty commit to illustrate what I mean.
> > ```
> >  it('calculates the correct start date when dropScheduledDate is true and repeat is when done', () => { 
> > ```
> 
> I'm not seeing where `dropScheduledDate` is used anywhere in that test....

Yeah, this caught me out as well. I looked and looked and looked and couldn't figure out where it was being set. It's the second parameter to this call, that's the `dropScheduledDate` flag.

```
            const next = recurrence!.next(undefined, true);
```

I even thought of rewriting the function to look like this just for clarity:

```
            const dropScheduledDate = true;
            const next = recurrence!.next(undefined, dropScheduledDate);
```

Would probably make the tests easier to read in the future.
Okay, fixed up the branches, removed the `.only()`. The failing test is now here:
https://github.com/chmac/obsidian-tasks/tree/3553-failing-test-take-2
> I even thought of rewriting the function to look like this just for clarity:

Yes, that's a great idea. This is always an argument again `boolean` parameters - they can lead to rather cryptic calling code.
>  If that doesn't make sense, or isn't clear, I can whip up a quick and dirty commit to illustrate what I mean.

I really appreciate your time on this, but I think it's premature to write code of any sort.

First I feel we should establish test examples (which could become documentation examples) for the new behaviours.

For example, it would really help if you could edit your bike pump example to add demonstration Markdown lines, to ensure clarity.

What other `when done` examples can you think if, which different combinations of due, scheduled and start, and how should they behave with `when done` and without `when done`?

Once we have nailed down a useful set of examples, failing tests have been added for them - then its time to think about the implementation.
> Okay, fixed up the branches, removed the `.only()`. The failing test is now here: https://github.com/chmac/obsidian-tasks/tree/3553-failing-test-take-2

Thanks, but I can't see it.

When you have time, could you paste in a permalink to the line with `.failing` in please?
> > Okay, fixed up the branches, removed the `.only()`. The failing test is now here: https://github.com/chmac/obsidian-tasks/tree/3553-failing-test-take-2
> 
> Thanks, but I can't see it.
> 
> When you have time, could you paste in a permalink to the line with `.failing` in please?

https://github.com/chmac/obsidian-tasks/blob/0716fff70285ef991c7fe531be6a932b67596c3e/tests/Task/Recurrence.test.ts#L269-L295

I just added `.failing`, but that's the code block with the test in question.
Great. I wanted to test my understanding of the problem, so had a go at the name of the test, and also at trying to add a comment about how the 'expected' values were probably chosen.

Have I understood the reasoning OK?

```ts
    describe('dropScheduledDate and when done', () => {
        beforeEach(() => {
            jest.useFakeTimers();
            jest.setSystemTime(new Date('2022-01-10'));
        });
        afterEach(() => {
            jest.useRealTimers();
        });

        it.failing('calculates correct start date with "dropScheduledDate" and "when done", with no due date', () => {
            // Arrange

            // The task is being completed on the 10th of January.
            // And the user has turned on the setting to remove the scheduled date in the new instance.
            // This means that the user will apply a scheduled date at some point in the future,
            // when they are ready to work on the task.
            // Therefore, in the new task, as there is no Due date, the only date the user will see
            // is the Start date.
            // And so it makes sense to calculate the new 'happens' date using the Start date,
            // not the old scheduled date.
            const recurrence = Recurrence.fromText({
                recurrenceRuleText: 'every 3 days when done',
                occurrence: new Occurrence({
                    startDate: moment('2022-01-01').startOf('day'),
                    scheduledDate: moment('2022-01-04').startOf('day'),
                }),
            });

            // Act
            const removeScheduledDate = true;
            const next = recurrence!.next(undefined, removeScheduledDate);

            // Assert
            expect(next!.startDate).toEqualMoment(moment('2022-01-13'));
            expect(next!.scheduledDate).toBeNull();
        });
    });
```

Here are the diffs:

<img width="2023" height="828" alt="Image" src="https://github.com/user-attachments/assets/f1ec08aa-780b-4dda-b78e-1f42c971ebd9" />
> What other when done examples can you think if, which different combinations of due, scheduled and start, and how should they behave with when done and without when done?

My original 2 task example was to show the differences between having a `due`, `start`, and `scheduled` dates. Whenever there is a `due` date, the behaviour is as I would expect. It's only because `scheduled` date is favoured over `start` that this behaviour occurs. When there is a `scheduled` and a `start` date, and the task repeats with `when done`, the repetition is based on the `scheduled` date, then the `start` date gets set according to the difference in time between `scheduled` and `start` on the previous occurrence of the task. In the case where we're dropping the scheduled date, that doesn't make sense.

In this example from the original issue:

```
- [ ] Example 1 🔁 every 2 days when done 🛫 2025-08-06
- [x] Example 1 🔁 every 2 days when done 🛫 2025-08-03 ⏳ 2025-08-05 ✅ 2025-08-06
```

The newly created task doesn't make any sense. The new start date (2025-08-06) is not 2 days from the old start or scheduled date.

I think this example captures the essence of the bug. I spent a bit of time experimenting with different options to figure out what was going on before I wrote the bug report. It requires a start date, a scheduled date and no due date. Then the repetition is based on the scheduled date and weird behaviour comes in.

Here's another example to show it with a scheduled date that is in the future:

```
- [ ] Example 1 🔁 every 2 days when done 🛫 2025-08-02
- [x] Example 1 🔁 every 2 days when done 🛫 2025-08-03 ⏳ 2025-08-09 ✅ 2025-08-06
```

Here the start date ends up before the previous start date. It's repeating backwards. :-)

Although, to be fair, without the drop scheduled on recurrence flag, the same thing happens:

```
- [ ] Example 1 🔁 every 2 days when done 🛫 2025-08-02 ⏳ 2025-08-08
- [x] Example 1 🔁 every 2 days when done 🛫 2025-08-03 ⏳ 2025-08-09 ✅ 2025-08-06
```

I would also argue that this case doesn't make so much sense. Both my scheduled and start dates went backwards.
> Great. I wanted to test my understanding of the problem, so had a go at the name of the test, and also at trying to add a comment about how the 'expected' values were probably chosen.
> 
> Have I understood the reasoning OK?

Yes, I think your description of the reasoning is spot on. But it's getting late here, so I will sign off for tonight and look again tomorrow. I just realised the case with the dates moving backwards, so will think about that overnight.
I've finally realised one reason (other than tiredness) why I am struggling a bit to read the quite a few of the examples in the issue - the fields are not aligning as I am used to, because often:

1. The original task has a created date `➕ 2025-08-01`
2. The new task does not have a created date

So the columns do not line up, like here:

```
- [ ] Example 1 🔁 every 2 days when done 🛫 2025-08-02
- [x] Example 1 🔁 every 2 days when done ➕ 2025-08-01 🛫 2025-08-03 ⏳ 2025-08-09 ✅ 2025-08-06
```

Here I have deleted the Created date, and now the Start values do align, and it is at least clear how `🛫` has changed:

```
- [ ] Example 1 🔁 every 2 days when done 🛫 2025-08-02
- [x] Example 1 🔁 every 2 days when done 🛫 2025-08-03 ⏳ 2025-08-09 ✅ 2025-08-06
```


Ahh, right. I have the "always add a created date" setting in my own vault but not in the sandbox vault, so my examples only have the created date on the original event and not the new recurrence.

```
- [ ] Example 1 🔁 every 2 days when done 🛫 2025-08-02 ⏳ 2025-08-08
- [x] Example 1 🔁 every 2 days when done 🛫 2025-08-03 ⏳ 2025-08-09 ✅ 2025-08-06
```

That's the example where all the dates go backwards again without the created date confusion. That's without the drop scheduled dates setting.
I've given this some further thought. My sense is that when the "drop scheduled date from recurrence" option is set, the reference date for a task should not choose the scheduled date over the start date, but the reverse. That would solve the issue, and in my view, also makes more logical sense.

However, the `Occurence` class sets the reference date on instantiation, which means it's not possible to pass the `dropScheduledDate` flag into it. I believe the issue arises here on line 133:

https://github.com/obsidian-tasks-group/obsidian-tasks/blob/826528923596f6698ee9e133a3640eaea42747bc/src/Task/Occurrence.ts#L121-L140

The referenceDate is already set, and in this case, is set to the scheduled date. So the logic says, count how many days difference there is from the scheduled date to the start date (which in this case is a negative number), and then add that negative number to the newly calculated date (which is today's date plus the 2 days from the `repeat every 2 days`).

Fundamentally, when the scheduled date is chosen before the start date as a reference date, and then later a decision is made to remove the scheduled date, the logic doesn't work as one would expect.
I have written a potential fix, although I don't find it so elegant. You can find it here:

https://github.com/chmac/obsidian-tasks/tree/3553-potential-fix

This is the commit with the fix:

https://github.com/chmac/obsidian-tasks/commit/287f1c963df8779575cb6517a0353edfef9c59d3

What I don't like about it, is that I use `this.getReferenceDate()` instead of `this.referenceDate` because the latter is not updated correctly. I find this quite ugly as a solution. But I'm not sure what a better approach would be. Some ideas that come to mind:

- Pass the `removeScheduledDateOnRecurrence` flag to the `Occurrence` class on instantiation, so that `this.referenceDate` will always take it into account
- Call `getSettings()` inside `Occurrence.getReferenceDate()`
- Change the logic so that start dates are always chosen over scheduled dates, a breaking change
I tried the `getSettings()` approach, but the tests don't work, because importing `getSettings()` throws all kinds of issues. So maybe that's a terrible idea. But I pushed it (with `--no-verify`), so you can have a look if you like:

https://github.com/chmac/obsidian-tasks/tree/3553-alternative-potential-fix

The specific commit is:

https://github.com/chmac/obsidian-tasks/commit/6e677aaa8c7dd386f78fc97bf191bef35afd0c33

I'm not sure what seems reasonable here. I don't really like the fix that works, but I don't have such a good grasp of the architecture of the whole plugin to make any better suggestions. 🤷 
> I've finally realised one reason (other than tiredness) why I am struggling a bit to read the quite a few of the examples in the issue - the fields are not aligning as I am used to, because often:


Transparency note: I have edited these to posts, to remove the Created date fields, and where appropriate to add blank lines dividing up different tasks, to aid readability:

- https://github.com/obsidian-tasks-group/obsidian-tasks/issues/3553#issue-3296117031
- https://github.com/obsidian-tasks-group/obsidian-tasks/issues/3553#issuecomment-3161625466
> I've given this some further thought. My sense is that when the "drop scheduled date from recurrence" option is set, the reference date for a task should not choose the scheduled date over the start date, but the reverse. That would solve the issue, and in my view, also makes more logical sense.

I think that you are saying that the problem you've identified in this issue, when `removeScheduledDateOnRecurrence` is enabled, also occurs _without_ `when done`.

If so, yes, I agree.

If so, then yes, I had formed the same conclusion independently.



To help understand the consequences of changing `Occurrence.referenceDate` I tried making it private and running `yarn lint`.

It gave these errors:

```
src/Task/Recurrence.ts:36:50 - error TS2341: Property 'referenceDate' is private and only accessible within class 'Occurrence'.
36                 const referenceDate = occurrence.referenceDate;
                                                    ~~~~~~~~~~~~~

src/Task/Recurrence.ts:124:37 - error TS2341: Property 'referenceDate' is private and only accessible within class 'Occurrence'.
124             .moment(this.occurrence.referenceDate ?? undefined)
                                        ~~~~~~~~~~~~~

Found 2 errors in the same file, starting at: src/Task/Recurrence.ts:36

error Command failed with exit code 2.
```

The affected lines are:

https://github.com/obsidian-tasks-group/obsidian-tasks/blob/a6ef18b689d31e6b4e121b0ca1aa68f5d9466aa9/src/Task/Recurrence.ts#L36

https://github.com/obsidian-tasks-group/obsidian-tasks/blob/a6ef18b689d31e6b4e121b0ca1aa68f5d9466aa9/src/Task/Recurrence.ts#L124

In these methods:

https://github.com/obsidian-tasks-group/obsidian-tasks/blob/a6ef18b689d31e6b4e121b0ca1aa68f5d9466aa9/src/Task/Recurrence.ts#L18-L60

https://github.com/obsidian-tasks-group/obsidian-tasks/blob/a6ef18b689d31e6b4e121b0ca1aa68f5d9466aa9/src/Task/Recurrence.ts#L117-L128

It's very likely that there are not existing tests fully covering the behaviour:

- Of toggling recurring tasks that have Scheduled and Starts but not Due
- With `removeScheduledDateOnRecurrence` enabled (since it is a very recent addition)
- On combinations of boundary dates that reveal the problem you have currently reported here.


I also tried removing the default parameter values where `removeScheduledDateOnRecurrence` is passed in, to see what code would be affected if the parameter was added everywhere.

(Also, missing values for default parameters can be a good source of unidentified bugs in code bases).

The diff:

```diff
Index: src/Task/Recurrence.ts
===================================================================
diff --git a/src/Task/Recurrence.ts b/src/Task/Recurrence.ts
--- a/src/Task/Recurrence.ts	(revision a6ef18b689d31e6b4e121b0ca1aa68f5d9466aa9)
+++ b/src/Task/Recurrence.ts	(date 1754670893484)
@@ -74,7 +74,7 @@
      * @param today - Optional date representing the completion date. Defaults to today.
      * @param removeScheduledDate - Optional boolean to remove the scheduled date from the next occurrence so long as a start or due date exists.
      */
-    public next(today = window.moment(), removeScheduledDate: boolean = false): Occurrence | null {
+    public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
         const nextReferenceDate = this.nextReferenceDate(today);
 
         if (nextReferenceDate === null) {
Index: src/Task/Occurrence.ts
===================================================================
diff --git a/src/Task/Occurrence.ts b/src/Task/Occurrence.ts
--- a/src/Task/Occurrence.ts	(revision a6ef18b689d31e6b4e121b0ca1aa68f5d9466aa9)
+++ b/src/Task/Occurrence.ts	(date 1754670893482)
@@ -89,7 +89,7 @@
      * @param nextReferenceDate
      * @param removeScheduledDate - Optional boolean to remove the scheduled date from the next occurrence so long as a start or due date exists.
      */
-    public next(nextReferenceDate: Date, removeScheduledDate: boolean = false): Occurrence {
+    public next(nextReferenceDate: Date, removeScheduledDate: boolean): Occurrence {
         // Only if a reference date is given. A reference date will exist if at
         // least one of the other dates is set.
         if (this.referenceDate === null) {
```


The `yarn lint` errors:


```
tests/Task/Recurrence.test.ts:20:34 - error TS2554: Expected 2 arguments, but got 0.

20         const next = recurrence!.next();
                                    ~~~~~~

  src/Task/Recurrence.ts:77:17
    77     public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
                       ~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'today' was not provided.

tests/Task/Recurrence.test.ts:37:34 - error TS2554: Expected 2 arguments, but got 0.

37         const next = recurrence!.next();
                                    ~~~~~~

  src/Task/Recurrence.ts:77:17
    77     public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
                       ~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'today' was not provided.

tests/Task/Recurrence.test.ts:55:34 - error TS2554: Expected 2 arguments, but got 0.

55         const next = recurrence!.next();
                                    ~~~~~~

  src/Task/Recurrence.ts:77:17
    77     public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
                       ~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'today' was not provided.

tests/Task/Recurrence.test.ts:73:34 - error TS2554: Expected 2 arguments, but got 0.

73         const next = recurrence!.next();
                                    ~~~~~~

  src/Task/Recurrence.ts:77:17
    77     public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
                       ~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'today' was not provided.

tests/Task/Recurrence.test.ts:91:34 - error TS2554: Expected 2 arguments, but got 0.

91         const next = recurrence!.next();
                                    ~~~~~~

  src/Task/Recurrence.ts:77:17
    77     public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
                       ~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'today' was not provided.

tests/Task/Recurrence.test.ts:109:34 - error TS2554: Expected 2 arguments, but got 0.

109         const next = recurrence!.next();
                                     ~~~~~~

  src/Task/Recurrence.ts:77:17
    77     public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
                       ~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'today' was not provided.

tests/Task/Recurrence.test.ts:127:34 - error TS2554: Expected 2 arguments, but got 0.

127         const next = recurrence!.next();
                                     ~~~~~~

  src/Task/Recurrence.ts:77:17
    77     public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
                       ~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'today' was not provided.

tests/Task/Recurrence.test.ts:170:34 - error TS2554: Expected 2 arguments, but got 0.

170         const next = recurrence!.next();
                                     ~~~~~~

  src/Task/Recurrence.ts:77:17
    77     public next(today = window.moment(), removeScheduledDate: boolean): Occurrence | null {
                       ~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'today' was not provided.


Found 8 errors in the same file, starting at: tests/Task/Recurrence.test.ts:20

error Command failed with exit code 2.
```
So a summary of all that is:

1. Yes, I agree that any fix should apply to completing all recurring tasks, regardless of `when done`, where:
    1. `Remove scheduled date on recurrence` is enabled
    2. The task has `scheduled` and `start` dates, but not `due`
1. I also agree that coming up with a clean and safe fix is non-obvious
2. Likely some more test cases will be needed, as a separate PR, before actually fixing the issue.
> I have written a potential fix, although I don't find it so elegant. You can find it here:
> 
> https://github.com/chmac/obsidian-tasks/tree/3553-potential-fix
> 
> This is the commit with the fix:
> 
> [chmac@287f1c9](https://github.com/chmac/obsidian-tasks/commit/287f1c963df8779575cb6517a0353edfef9c59d3)
> 
> What I don't like about it, is that I use `this.getReferenceDate()` instead of `this.referenceDate` because the latter is not updated correctly. I find this quite ugly as a solution. 

I agree - and it's actually more serious than that.

Because `this.referenceDate` is public, there is other code elsewhere in the plugin that uses that value - and there will be latent bugs caused by there being two different interpretations of the `referenceDate`, depending on who does the calling....

Yeah, so far I don't see an elegant solution at all. I'm also not sure how to think about the drop scheduled date flag. It can presumably change while the plugin is running. So it can't really be set on instantiation. Changing `referenceDate` to always be fetched also seems like a big, complicated, error prone change.

How much of a breaking change would it be to prefer start date over scheduled date? But that would fail if you add the option to drop other dates in the future...
## How to make the fix

> Yeah, so far I don't see an elegant solution at all. I'm also not sure how to think about the drop scheduled date flag. It can presumably change while the plugin is running. So it can't really be set on instantiation.

That's a good point.

> How much of a breaking change would it be to prefer start date over scheduled date? But that would fail if you add the option to drop other dates in the future...

I'm not sure there are degrees of breaking change, but it would be 100% of a breaking change, because the current behaviour is so clearly documented.


## Refactoring to make the change easy

> Changing `referenceDate` to always be fetched also seems like a big, complicated, error prone change.

Not if it is refactored safely, incrementally.

## Preparatory Pull Request

### tl;dr

- I've created a PR that does not change the visible behaviour of the code, but it does make the eventual change very easy indeed.
- I won't merge this until after the next Tasks release, planned in the next few days

### What you need to know...

1. See PR #3558.
2. Start with the [first commit](https://github.com/obsidian-tasks-group/obsidian-tasks/pull/3558/commits/86adddd0137e891e3c305076ff539d3696131986). 
3. Review that, then click on the `Next >` button, to go to the next change, and so on
    <img width="500"  alt="Image" src="https://github.com/user-attachments/assets/013026af-9a4f-47ef-aa8f-a3e99cfb2979" />

## About the commit messages

FYI We have a convention in the project for conveying levels of risk in commits:

- Commits beginning `refactor: .`: the 'dot' means "WebStorm made the change, not me - so it is **provably** safe"
- Commits beginning `refactor: -`: the 'hyphen' means "I made the change, but it is covered by tests"

_Correction made to the above: "probably safe" => "provably safe"..._
Once #3558 is merged, I think that the fix for this will involve:

1. Coming up with a relatively readable way of making `Occurrence.getReferenceDate()` modify the order of its results, depending on whether `removeScheduledDateOnRecurrence` is enabled
2. Adding the new behaviour to the user docs here: https://publish.obsidian.md/tasks/Getting+Started/Recurring+Tasks#Priority%20of%20Dates
3. Exploring whether the wording needs to be changed in the settings here: https://publish.obsidian.md/tasks/Getting+Started/Recurring+Tasks#Remove%20scheduled%20date%20on%20recurrence

Then, as a separate step:

1. Investigate whether there is an observable  performance impact (worsening) of the refactoring in this PR, in which case #2734  needs to be fixed before release.
2. (It might be easier to fix that issue anyway, than spend time trying to measure...)
I saw all the PR action. Looks great. If pulling settings inside the getter, that seems like a clean solution. I also think it logically makes sense. If the drop scheduled date setting is enabled, choosing it last in the reference date makes sense. Sounds like this provides a smooth path forward. Just let me know if I can do anything to help out.
Hi Callum,

> Just let me know if I can do anything to

Thank you, yes - when I've merged the PR, I would really appreciate your assistance with the first 3 steps [listed above](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/3553#issuecomment-3169238652), if it was convenient for you at the time....
Sure thing, if you ping me in the PR or here I'll be happy to help out.
> Once [#3558](https://github.com/obsidian-tasks-group/obsidian-tasks/pull/3558) is merged, I think that the fix for this will involve:
> 
> 1. Coming up with a relatively readable way of making `Occurrence.getReferenceDate()` modify the order of its results, depending on whether `removeScheduledDateOnRecurrence` is enabled
> 2. Adding the new behaviour to the user docs here: https://publish.obsidian.md/tasks/Getting+Started/Recurring+Tasks#Priority%20of%20Dates
> 3. Exploring whether the wording needs to be changed in the settings here: https://publish.obsidian.md/tasks/Getting+Started/Recurring+Tasks#Remove%20scheduled%20date%20on%20recurrence

Hi @chmac, Tasks 7.21.0 is out now, so I have gone ahead and merged #3558...

So if you fancy experimenting with the above, I would be very grateful! 😄 

This issue was closed because the text of the PR included "potential fix for #3353" and so GitHub misunderstood that PR as a fix for this issue. I can't seem to reopen it on my phone, not sure if it's a permissions issue or not.

I'll take a look at the points above on my laptop. It might be the week after next before I can get back to it due to travels and other commitments, but I've noted it on my list to follow up.
Oops. GitHub being helpful. Thanks for letting me know. 

I’ve reopened it. 

No hurry. Happy travels. 
@claremacrae I've had a look at this now, and created a branch which I think addresses the issues. You can see it here:
https://github.com/obsidian-tasks-group/obsidian-tasks/compare/main...chmac:obsidian-tasks:3553-round-two

For point 3, I reviewed the docs for the "Remove scheduled date on recurrence" feature, and I don't think that any changes are required there. I think it's reasonable to expect that if the date is being removed, it will be deprioritised when choosing the reference date. I think the behaviour is what people would expect.

I did add a note to the docs on date priority. Not sure what you think of the wording, I know you've used ChatGPT to great success in the past, perhaps that would fit here.
Hi Callum,

> [@claremacrae](https://github.com/claremacrae) I've had a look at this now, and created a branch which I think addresses the issues. You can see it here: [main...chmac:obsidian-tasks:3553-round-two](https://github.com/obsidian-tasks-group/obsidian-tasks/compare/main...chmac:obsidian-tasks:3553-round-two)

Thanks very much. Would you be OK with going ahead and creating a PR, so I can review?

(It's easier and more reliable than leaving comments here...)


> For point 3, I reviewed the docs for the "Remove scheduled date on recurrence" feature, and I don't think that any changes are required there. I think it's reasonable to expect that if the date is being removed, it will be deprioritised when choosing the reference date. I think the behaviour is what people would expect.

I'll have a think about the wording when I review the PR...




@claremacrae Sure, submitted as a PR now. I didn't go through all the checkboxes in great detail, just gave them a quick glance.



Patch:
diff --git a/docs/Getting Started/Recurring Tasks.md b/docs/Getting Started/Recurring Tasks.md
index 3ff5a64f87..bbb8ff88bf 100644
--- a/docs/Getting Started/Recurring Tasks.md
+++ b/docs/Getting Started/Recurring Tasks.md
@@ -198,6 +198,8 @@ When a task has multiple dates, one of them is selected as reference date based
 2. Scheduled date
 3. Start date
 
+> If the "Remove scheduled date on recurrence" setting is true, the Start Date will be chosen before the Scheduled date.
+
 If more dates than the reference date exist on the original recurring task, the next occurrence will have the same dates.
 All dates of the next occurring task will have the relative distance to the reference date that they had on the original task.
 
diff --git a/src/Task/Occurrence.ts b/src/Task/Occurrence.ts
index 4c441117be..467c549509 100644
--- a/src/Task/Occurrence.ts
+++ b/src/Task/Occurrence.ts
@@ -57,6 +57,16 @@ export class Occurrence {
             return window.moment(this.dueDate);
         }
 
+        // If the `removeScheduledDateOnRecurrence` setting is enabled, it does
+        // not make sense to pick the scheduled date over the start date because
+        // the scheduled date will be deleted in the newly created task. So if
+        // this setting is enabled, and there is a start date, we pick that date
+        // now before falling back on the standard logic below.
+        const { removeScheduledDateOnRecurrence } = getSettings();
+        if (removeScheduledDateOnRecurrence && this.startDate) {
+            return window.moment(this.startDate);
+        }
+
         if (this.scheduledDate) {
             return window.moment(this.scheduledDate);
         }


Test patch:
diff --git a/tests/Task/Recurrence.test.ts b/tests/Task/Recurrence.test.ts
index 816163d454..264e7c8f85 100644
--- a/tests/Task/Recurrence.test.ts
+++ b/tests/Task/Recurrence.test.ts
@@ -284,7 +284,7 @@ describe('Recurrence - with removeScheduledDateOnRecurrence', () => {
             jest.useRealTimers();
         });
 
-        it.failing('calculates correct start date with "dropScheduledDate" and "when done", with no due date', () => {
+        it('calculates correct start date with "dropScheduledDate" and "when done", with no due date', () => {
             // Arrange
 
             // The task is being completed on the 10th of January.