Repository: obsidian-tasks-group/obsidian-tasks
Pull request #3060 (2024-09-02 20:57:05)

fix: load cache when workspace is ready
# Types of changes

Changes visible to users:

- [x] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)
    - Fixes https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2152

## Description

Load Vault and subscribe to changes when workspace is ready. 

### Original behaviour

After turning on logging and debugging the plugins startup, especially the Cache creation i noticed the following effects:
- Files are checked twice: Once by loadVault and once by subscribeToVault.
- Whereas loadVault is fast, subscribeToVault added significantly delays
- subscribeToVault (https://docs.obsidian.md/Reference/TypeScript+API/Vault/on('create')) registers for create events where for every file available the event is triggered when called in the plugins onload routine
- https://docs.obsidian.md/Reference/TypeScript+API/Vault/on('create') added significant delays in the Tasks plugin even if the event handler is an empty function doing nothing (to be validated with an blank plugin and a sample vault, which was not yet done)


### Changes made

Changes done in detail:
- Moved both functions to Workspace#onLayoutReady which omits the create events for every file, reducing the startup significantly #2152 
- Every file is stilled loaded to cache by loadVault
- For details see:  https://docs.obsidian.md/Reference/TypeScript+API/Vault/on('create')

## Motivation and Context

As discussed on Discord the loading time of Obsidian on Android has been significantly delayed by the Tasks plugin for my Vault. Due to the new feature of Obsidian, allowing to see the timing details on the App startup i was able to find the Task plugin delaying my startup between 3 to 5 seconds.

## How has this been tested?

- I used it the last days in my main Vault having better startup times
- Logs not showing any errors
- Tasks behave normally
- Since it changes the caches loading behavior I recommend additional users testing it, since a broken cache could result in wrong task states or views

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

## Screenshots (if appropriate)

## 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`.
- [ ] My change requires a change to the documentation.
- [ ] I have [updated the documentation](https://publish.obsidian.md/tasks-contributing/Documentation/About+Documentation) accordingly.
- [ ] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://publish.obsidian.md/tasks-contributing) and
[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
Put an x in the boxes to confirm you agree.
-->

- [x] My contribution follows this project's [contributing guide](https://publish.obsidian.md/tasks-contributing)
- [x] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)


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

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


claremacrae: Hi Kris

Thanks very much indeed for working on this.

## Speedup

> reducing the startup significantly

Do you have any measurements that you could record here?

## Testing

So I can know where I can focus my time on testing...

What platforms have you tested it in?

Also, please can you record what specific steps you used to test it.

For example, a good thing to test would be, if when you closed the vault with a Tasks search open in Reading mode, when you reopened the vault, are the correct tasks displayed?


claremacrae: > (to be validated with an blank plugin and a sample vault, which was not yet done)

Please could you let me know when you have had a chance to do that? Thanks!
rxkris: Platforms tested:
- Linux (Ubuntu 24.04), Obsidian 1.7.1
- Android 14, iodeOS, Obsidian 1.7.1

Example Vault: [TaskTest.zip](https://github.com/user-attachments/files/16841336/TaskTest.zip)

# Speedup (main difference in Total app startup -> Workspace)
- Linux: onLoad vs onLayoutReady: steady 0.2s to 0.3s.
- Android: onLoad 3 to 5 seconds and with onLayoutReady: around 0.4s
- Open Note at startup: No noticable difference which page was used as open note for the workspace loading time (onLoad, onLayoutReady, see testing below) 
- No noticable difference if Tasks is the only plugin or more plugins are installed

# Testing
Android: Force Quit Obsidian, Start Obsidian then View the startup time
- Done with a blank note at startup
- Done with a Task overview page with 5 queries

Both load ways show the correct results, timings the same as shown above or the attached screenshots.

# Screenshots

Small Vault, even bigger difference on the Vault attached

![Screenshot_Both](https://github.com/user-attachments/assets/483714dd-2705-470d-9eca-818ca11c9057)


claremacrae: @rxkris Thank you for confirming the testing you have done.

This is brilliant work! I will endeavour to test and merge it in the next day or two. 🎉 

Patch:
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index a7f27f8c..3ebe0475 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -42,6 +42,7 @@ jobs:
         uses: actions/upload-artifact@v4
         with:
           name: Tasks-Demo-VerifyCommit-Build${{ github.run_number }}-Run${{ github.run_attempt }}
+          include-hidden-files: true
           path: |
             resources/sample_vaults/Tasks-Demo
 
diff --git a/docs/Advanced/Styling.md b/docs/Advanced/Styling.md
index f50cadbd..b329edad 100644
--- a/docs/Advanced/Styling.md
+++ b/docs/Advanced/Styling.md
@@ -97,31 +97,22 @@ To help visualise the structure above, here is the HTML for a sample task shown
     <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
     <span class="task-recurring"><span>🔁 every day when done</span></span>
     <span class="task-onCompletion"><span>🏁 delete</span></span>
-    <span
-      class="task-created"
-      data-task-created="past-4d"
-      title="Click to edit created date, Right-click for more options">
+    <span class="task-created" data-task-created="past-4d" title="Right-click to edit created date">
       <span>➕ 2023-07-01</span>
     </span>
-    <span class="task-start" data-task-start="past-3d" title="Click to edit start date, Right-click for more options">
+    <span class="task-start" data-task-start="past-3d" title="Right-click to edit start date">
       <span>🛫 2023-07-02</span>
     </span>
-    <span
-      class="task-scheduled"
-      data-task-scheduled="past-2d"
-      title="Click to edit scheduled date, Right-click for more options">
+    <span class="task-scheduled" data-task-scheduled="past-2d" title="Right-click to edit scheduled date">
       <span>⏳ 2023-07-03</span>
     </span>
-    <span class="task-due" data-task-due="past-1d" title="Click to edit due date, Right-click for more options">
+    <span class="task-due" data-task-due="past-1d" title="Right-click to edit due date">
       <span>📅 2023-07-04</span>
     </span>
-    <span
-      class="task-cancelled"
-      data-task-cancelled="future-1d"
-      title="Click to edit cancelled date, Right-click for more options">
+    <span class="task-cancelled" data-task-cancelled="future-1d" title="Right-click to edit cancelled date">
       <span>❌ 2023-07-06</span>
     </span>
-    <span class="task-done" data-task-done="today" title="Click to edit done date, Right-click for more options">
+    <span class="task-done" data-task-done="today" title="Right-click to edit done date">
       <span>✅ 2023-07-05</span>
     </span>
     <span class="task-block-link"><span>^dcf64c</span></span>
@@ -164,33 +155,18 @@ Here is the same task in [[Layout#Short Mode|short mode]]. The only difference i
     <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
     <span class="task-recurring"><span>🔁</span></span>
     <span class="task-onCompletion"><span>🏁</span></span>
-    <span
-      class="task-created"
-      data-task-created="past-4d"
-      title="Click to edit created date, Right-click for more options">
+    <span class="task-created" data-task-created="past-4d" title="Right-click to edit created date">
       <span>➕</span>
     </span>
-    <span class="task-start" data-task-start="past-3d" title="Click to edit start date, Right-click for more options">
-      <span>🛫</span>
-    </span>
-    <span
-      class="task-scheduled"
-      data-task-scheduled="past-2d"
-      title="Click to edit scheduled date, Right-click for more options">
+    <span class="task-start" data-task-start="past-3d" title="Right-click to edit start date"><span>🛫</span></span>
+    <span class="task-scheduled" data-task-scheduled="past-2d" title="Right-click to edit scheduled date">
       <span>⏳</span>
     </span>
-    <span class="task-due" data-task-due="past-1d" title="Click to edit due date, Right-click for more options">
-      <span>📅</span>
-    </span>
-    <span
-      class="task-cancelled"
-      data-task-cancelled="future-1d"
-      title="Click to edit cancelled date, Right-click for more options">
+    <span class="task-due" data-task-due="past-1d" title="Right-click to edit due date"><span>📅</span></span>
+    <span class="task-cancelled" data-task-cancelled="future-1d" title="Right-click to edit cancelled date">
       <span>❌</span>
     </span>
-    <span class="task-done" data-task-done="today" title="Click to edit done date, Right-click for more options">
-      <span>✅</span>
-    </span>
+    <span class="task-done" data-task-done="today" title="Right-click to edit done date"><span>✅</span></span>
     <span class="task-block-link"><span>^dcf64c</span></span>
   </span>
 </li>
diff --git a/src/Obsidian/Cache.ts b/src/Obsidian/Cache.ts
index e04bb5e3..02a4fb8b 100644
--- a/src/Obsidian/Cache.ts
+++ b/src/Obsidian/Cache.ts
@@ -1,5 +1,5 @@
 import { MetadataCache, Notice, TAbstractFile, TFile, Vault } from 'obsidian';
-import type { CachedMetadata, EventRef } from 'obsidian';
+import type { CachedMetadata, EventRef, Workspace } from 'obsidian';
 import type { HeadingCache, ListItemCache, SectionCache } from 'obsidian';
 import { Mutex } from 'async-mutex';
 import { TasksFile } from '../Scripting/TasksFile';
@@ -134,6 +134,7 @@ export class Cache {
     private readonly metadataCache: MetadataCache;
     private readonly metadataCacheEventReferences: EventRef[];
     private readonly vault: Vault;
+    private readonly workspace: Workspace;
     private readonly vaultEventReferences: EventRef[];
     private readonly events: TasksEvents;
     private readonly eventsEventReferences: EventRef[];
@@ -152,12 +153,23 @@ export class Cache {
      */
     private loadedAfterFirstResolve: boolean;
 
-    constructor({ metadataCache, vault, events }: { metadataCache: MetadataCache; vault: Vault; events: TasksEvents }) {
+    constructor({
+        metadataCache,
+        vault,
+        workspace,
+        events,
+    }: {
+        metadataCache: MetadataCache;
+        vault: Vault;
+        workspace: Workspace;
+        events: TasksEvents;
+    }) {
         this.logger.debug('Creating Cache object');
 
         this.metadataCache = metadataCache;
         this.metadataCacheEventReferences = [];
         this.vault = vault;
+        this.workspace = workspace;
         this.vaultEventReferences = [];
         this.events = events;
         this.eventsEventReferences = [];
@@ -171,10 +183,16 @@ export class Cache {
         this.loadedAfterFirstResolve = false;
 
         this.subscribeToCache();
-        this.subscribeToVault();
-        this.subscribeToEvents();
 
-        this.loadVault();
+        // Subscribe to vault and load cache later when workspace is ready,
+        // prevents create events for every file, but loadVault cover all files anyway.
+        // For details see: https://docs.obsidian.md/Reference/TypeScript+API/Vault/on('create')
+        this.workspace.onLayoutReady(() => {
+            this.subscribeToVault();
+            this.loadVault();
+        });
+
+        this.subscribeToEvents();
     }
 
     public unload(): void {
diff --git a/src/Renderer/TaskLineRenderer.ts b/src/Renderer/TaskLineRenderer.ts
index 844df950..78b34f41 100644
--- a/src/Renderer/TaskLineRenderer.ts
+++ b/src/Renderer/TaskLineRenderer.ts
@@ -11,7 +11,6 @@ import { TaskRegularExpressions } from '../Task/TaskRegularExpressions';
 import { StatusMenu } from '../ui/Menus/StatusMenu';
 import type { AllTaskDateFields } from '../DateTime/DateFieldTypes';
 import { defaultTaskSaver } from '../ui/Menus/TaskEditingMenu';
-import { promptForDate } from '../ui/Menus/DatePicker';
 import { splitDateText } from '../DateTime/Postponer';
 import { DateMenu } from '../ui/Menus/DateMenu';
 import { TaskFieldRenderer } from './TaskFieldRenderer';
@@ -216,11 +215,11 @@ export class TaskLineRenderer {
                     const componentDateField = component as AllTaskDateFields;
 
                     // Note: The more convenient span.onClickEvent() doesn't work here, as it is not available when tests are run.
-                    span.addEventListener('click', (ev: MouseEvent) => {
-                        ev.preventDefault(); // suppress the default click behavior
-                        ev.stopPropagation(); // suppress further event propagation
-                        promptForDate(span, task, componentDateField, defaultTaskSaver);
-                    });
+                    // span.addEventListener('click', (ev: MouseEvent) => {
+                    //     ev.preventDefault(); // suppress the default click behavior
+                    //     ev.stopPropagation(); // suppress further event propagation
+                    //     promptForDate(span, task, componentDateField, defaultTaskSaver);
+                    // });
 
                     span.addEventListener('contextmenu', (ev: MouseEvent) => {
                         ev.preventDefault(); // suppress the default context menu
@@ -230,7 +229,8 @@ export class TaskLineRenderer {
                     });
                     span.setAttribute(
                         'title',
-                        `Click to edit ${splitDateText(componentDateField)}, Right-click for more options`,
+                        // `Click to edit ${splitDateText(componentDateField)}, Right-click for more options`,
+                        `Right-click to edit ${splitDateText(componentDateField)}`,
                     );
                 }
             }
diff --git a/src/main.ts b/src/main.ts
index 2a49b9a6..9b0b7c52 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -52,6 +52,7 @@ export default class TasksPlugin extends Plugin {
         this.cache = new Cache({
             metadataCache: this.app.metadataCache,
             vault: this.app.vault,
+            workspace: this.app.workspace,
             events,
         });
         this.inlineRenderer = new InlineRenderer({ plugin: this });
diff --git a/src/ui/DateEditor.svelte b/src/ui/DateEditor.svelte
index e573bb2d..048f0ea2 100644
--- a/src/ui/DateEditor.svelte
+++ b/src/ui/DateEditor.svelte
@@ -1,9 +1,4 @@
 <script lang="ts">
-    import flatpickr from 'flatpickr';
-    import { createEventDispatcher } from 'svelte';
-
-    import { setIcon } from 'obsidian';
-
     import { doAutocomplete } from '../DateTime/DateAbbreviations';
     import { parseTypedDateForDisplayUsingFutureDate } from '../DateTime/DateTools';
     import { labelContentWithAccessKey } from './EditTaskHelpers';
@@ -16,16 +11,16 @@
     export let accesskey: string | null;
 
     let parsedDate: string;
-    let inputElement: HTMLInputElement;
-    let flatpickrInstance: any;
-
-    const dispatch = createEventDispatcher();
-
-    const iconCalendarDays = (node: HTMLElement) => {
-        // For a more general implementation, see:
-        // https://github.com/joethei/obsidian-rss/blob/b600e2ead2505d58aa3e4c7898795bbf58fa3cdc/src/view/IconComponent.svelte
-        setIcon(node, 'calendar-days');
-    };
+    // let inputElement: HTMLInputElement;
+    // let flatpickrInstance: any;
+    //
+    // const dispatch = createEventDispatcher();
+    //
+    // const iconCalendarDays = (node: HTMLElement) => {
+    //     // For a more general implementation, see:
+    //     // https://github.com/joethei/obsidian-rss/blob/b600e2ead2505d58aa3e4c7898795bbf58fa3cdc/src/view/IconComponent.svelte
+    //     setIcon(node, 'calendar-days');
+    // };
 
     $: {
         date = doAutocomplete(date);
@@ -41,60 +36,59 @@
      *
      * See also {@link promptForDate}
      */
-    function openDatePicker() {
-        if (inputElement) {
-            if (flatpickrInstance) {
-                flatpickrInstance.destroy(); // Destroy any existing instance to avoid conflicts
-            }
-
-            const options: Record<string, any> = {
-                enableTime: false,
-                dateFormat: 'Y-m-d',
-                locale: {
-                    firstDayOfWeek: 1,
-                },
-                onClose: (selectedDates: Date[]) => {
-                    dispatch('close', { instance: flatpickrInstance }); // Notify parent about close
-
-                    if (selectedDates.length > 0) {
-                        const selectedDate = selectedDates[0];
-                        date = window.moment(selectedDate).format('YYYY-MM-DD');
-                    }
-
-                    flatpickrInstance.destroy(); // Destroy the instance after the date is selected
-                    flatpickrInstance = null;
-                },
-                allowInput: true, // Allow manual input to keep the field empty if no selection is made
-                defaultDate: undefined, // Explicitly define defaultDate with undefined
-            };
-
-            // We don't use parseDate() here as we want to strictly only match exact dates,
-            // and not words like 'today' or 'tomorrow', to make sure we really
-            // are using the already-parsed date in the Modal UI:
-            const dateMatcher = /^\d{4}-\d{2}-\d{2}$/;
-            if (parsedDate.match(dateMatcher)) {
-                // This is where the user had typed, for example, 'tomorrow' in the input
-                // field, and chrono has converted that to an exact date.
-                options.defaultDate = new Date(parsedDate);
-            } else if (date.match(dateMatcher)) {
-                // This is a precaution, for the unlikely event that the input field
-                // contains a parsed date, but the parsedDate does not.
-                // It's possible that it is unreachable, but we would need tests to confirm that.
-                options.defaultDate = new Date(date);
-            }
-
-            flatpickrInstance = flatpickr(inputElement, options);
-            dispatch('open', { instance: flatpickrInstance }); // Notify parent about open
-
-            flatpickrInstance.open(); // Directly open the date picker
-        }
-    }
+    // function openDatePicker() {
+    //     if (inputElement) {
+    //         if (flatpickrInstance) {
+    //             flatpickrInstance.destroy(); // Destroy any existing instance to avoid conflicts
+    //         }
+    //
+    //         const options: Record<string, any> = {
+    //             enableTime: false,
+    //             dateFormat: 'Y-m-d',
+    //             locale: {
+    //                 firstDayOfWeek: 1,
+    //             },
+    //             onClose: (selectedDates: Date[]) => {
+    //                 dispatch('close', { instance: flatpickrInstance }); // Notify parent about close
+    //
+    //                 if (selectedDates.length > 0) {
+    //                     const selectedDate = selectedDates[0];
+    //                     date = window.moment(selectedDate).format('YYYY-MM-DD');
+    //                 }
+    //
+    //                 flatpickrInstance.destroy(); // Destroy the instance after the date is selected
+    //                 flatpickrInstance = null;
+    //             },
+    //             allowInput: true, // Allow manual input to keep the field empty if no selection is made
+    //             defaultDate: undefined, // Explicitly define defaultDate with undefined
+    //         };
+    //
+    //         // We don't use parseDate() here as we want to strictly only match exact dates,
+    //         // and not words like 'today' or 'tomorrow', to make sure we really
+    //         // are using the already-parsed date in the Modal UI:
+    //         const dateMatcher = /^\d{4}-\d{2}-\d{2}$/;
+    //         if (parsedDate.match(dateMatcher)) {
+    //             // This is where the user had typed, for example, 'tomorrow' in the input
+    //             // field, and chrono has converted that to an exact date.
+    //             options.defaultDate = new Date(parsedDate);
+    //         } else if (date.match(dateMatcher)) {
+    //             // This is a precaution, for the unlikely event that the input field
+    //             // contains a parsed date, but the parsedDate does not.
+    //             // It's possible that it is unreachable, but we would need tests to confirm that.
+    //             options.defaultDate = new Date(date);
+    //         }
+    //
+    //         flatpickrInstance = flatpickr(inputElement, options);
+    //         dispatch('open', { instance: flatpickrInstance }); // Notify parent about open
+    //
+    //         flatpickrInstance.open(); // Directly open the date picker
+    //     }
+    // }
 </script>
 
 <label for={id}>{@html labelContentWithAccessKey(id, accesskey)}</label>
 <!-- svelte-ignore a11y-accesskey -->
 <input
-    bind:this={inputElement}
     bind:value={date}
     {id}
     type="text"
@@ -104,13 +98,13 @@
     {accesskey}
 />
 
-<button
-    class="tasks-modal-calendar-button"
-    use:iconCalendarDays
-    on:click={openDatePicker}
-    aria-label="Open date picker"
-    style="background: none; border: none; padding: 0; cursor: pointer;"
-/>
+<!--<button-->
+<!--    class="tasks-modal-calendar-button"-->
+<!--    use:iconCalendarDays-->
+<!--    on:click={openDatePicker}-->
+<!--    aria-label="Open date picker"-->
+<!--    style="background: none; border: none; padding: 0; cursor: pointer;"-->
+<!--/>-->
 
 <code class="tasks-modal-parsed-date">{dateSymbol} {@html parsedDate}</code>
 
diff --git a/src/ui/EditTask.scss b/src/ui/EditTask.scss
index 0c1d8f99..53b5da95 100644
--- a/src/ui/EditTask.scss
+++ b/src/ui/EditTask.scss
@@ -77,23 +77,23 @@
     }
 
     .tasks-modal-date-input {
-        grid-column: 2;
+        //grid-column: 2;
         min-width: 15em;
     }
 
-    .tasks-modal-calendar-button {
-        grid-column: 3;
-        color: var(--text-muted);
-    }
+    //.tasks-modal-calendar-button {
+    //    grid-column: 3;
+    //    color: var(--text-muted);
+    //}
 
     .tasks-modal-parsed-date {
-        grid-column: 4;
+        grid-column: 3;
         font-size: var(--font-ui-small);
     }
 
     .future-dates-only {
         grid-column-start: 1;
-        grid-column-end: 4;
+        grid-column-end: 3;
 
         input {
             margin-left: 0.67em;
@@ -149,7 +149,7 @@
     }
 }
 
-@media (max-width: 529px) {
+@media (max-width: 499px) {
     .tasks-modal-priority-section {
         grid-template-columns: 4em auto auto;
     }
@@ -162,12 +162,12 @@
             grid-column: 1;
         }
 
-        .tasks-modal-calendar-button {
-            grid-column: 2;
-        }
+        //.tasks-modal-calendar-button {
+        //    grid-column: 2;
+        //}
 
         .tasks-modal-parsed-date {
-            grid-column: 3;
+            grid-column: 2;
         }
 
         .status-editor-status-selector {
@@ -192,9 +192,9 @@
             grid-column: 1;
         }
 
-        .tasks-modal-calendar-button {
-            grid-column: 1;
-        }
+        //.tasks-modal-calendar-button {
+        //    grid-column: 1;
+        //}
 
         > .tasks-modal-parsed-date {
             grid-column: 1;


Test patch:
diff --git a/resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md b/resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md
index cf2a6f0f..f72cb2e3 100644
--- a/resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md
+++ b/resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md
@@ -1,5 +1,7 @@
 # Smoke Testing the Tasks Plugin
 
+*[[#Remaining tests|Jump to the tests...]]*
+
 ## Introduction
 
 - **Intended audience of this note**
@@ -117,6 +119,34 @@ heading includes Rendering of Task Blocks
 
 ---
 
+### Edit dates in Rendered Task Blocks
+
+> [!Example]
+>
+> - [ ] #task Sample task: I have all the supported date types ➕ 2024-09-01 🛫 2024-09-02 ⏳ 2024-09-03 📅 2024-09-04 ❌ 2024-09-05 ✅ 2024-09-06
+
+- View this file in **Reading mode**...
+- On the task line above:
+  - [ ] #task ~~**left**-click on a date value, and use the date picker to select and save a different date. Check that the date is updated.~~
+  - [ ] #task ~~**left**-click on a date value, and click outside the date picker, to confirm that the picker closes.~~
+  - [ ] #task **right**-click on a date value, and use the context menu to select and save a different date. Check that the date is updated.
+  - [ ] #task **right**-click on a date value, and click outside the context menu, to confirm that the menu closes.
+- In the tasks search block below:
+  - [ ] #task ~~**left**-click on a date value, and use the date picker to select and save a different date. Check that the date is updated.~~
+  - [ ] #task ~~**left**-click on a date value, and click outside the date picker, to confirm that the picker closes.~~
+  - [ ] #task **right**-click on a date value, and use the context menu to select and save a different date. Check that the date is updated.
+  - [ ] #task **right**-click on a date value, and click outside the context menu, to confirm that the menu closes.
+- [ ] #task **check**: Checked all above steps for **editing dates** worked
+
+```tasks
+path includes {{query.file.path}}
+description includes I have all the supported date types
+hide backlink
+hide postpone button
+```
+
+---
+
 ### Create or edit Task modal
 
 - This text should copied in to the task Description, after following steps below
@@ -125,10 +155,12 @@ heading includes Rendering of Task Blocks
     2. **Check** that the text in the list item is copied in to the Description field
     3. Type some values in to the fields
     4. In one of the date fields, type `tm` (including the space afterwards) and **Check** it is expanded in to `tomorrow`
-    5. Hit Return or click **Apply**
-    6. **Check** that the list item above is converted in to a task
-    7. **Check** that values you entered in the modal have been copied in to the list item above
-    8. **Check** that the `#task` tag has been added to the start of the task
+    5. ~~In one of the date fields, left-click the calendar button, and use the context menu to select and save a date. Check that the date is saved.~~
+    6. ~~In one of the date fields, left-click the calendar button, and click outside the date picker, to confirm that the picker closes and the modal is still usable.~~
+    7. Hit Return or click **Apply**
+    8. **Check** that the list item above is converted in to a task
+    9. **Check** that values you entered in the modal have been copied in to the list item above
+    10. **Check** that the `#task` tag has been added to the start of the task
 - [ ] #task **check**: Checked all above steps for **creating a task via the modal** worked
 
 ---
diff --git a/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_fully_populated_task.approved.html b/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_fully_populated_task.approved.html
index d9ad41d8..cf058098 100644
--- a/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_fully_populated_task.approved.html
+++ b/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_fully_populated_task.approved.html
@@ -21,34 +21,22 @@
         <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
         <span class="task-recurring"><span>🔁 every day when done</span></span>
         <span class="task-onCompletion"><span>🏁 delete</span></span>
-        <span
-          class="task-created"
-          data-task-created="past-far"
-          title="Click to edit created date, Right-click for more options">
+        <span class="task-created" data-task-created="past-far" title="Right-click to edit created date">
           <span>➕ 2023-07-01</span>
         </span>
-        <span
-          class="task-start"
-          data-task-start="past-far"
-          title="Click to edit start date, Right-click for more options">
+        <span class="task-start" data-task-start="past-far" title="Right-click to edit start date">
           <span>🛫 2023-07-02</span>
         </span>
-        <span
-          class="task-scheduled"
-          data-task-scheduled="past-far"
-          title="Click to edit scheduled date, Right-click for more options">
+        <span class="task-scheduled" data-task-scheduled="past-far" title="Right-click to edit scheduled date">
           <span>⏳ 2023-07-03</span>
         </span>
-        <span class="task-due" data-task-due="past-far" title="Click to edit due date, Right-click for more options">
+        <span class="task-due" data-task-due="past-far" title="Right-click to edit due date">
           <span>📅 2023-07-04</span>
         </span>
-        <span
-          class="task-cancelled"
-          data-task-cancelled="past-far"
-          title="Click to edit cancelled date, Right-click for more options">
+        <span class="task-cancelled" data-task-cancelled="past-far" title="Right-click to edit cancelled date">
           <span>❌ 2023-07-06</span>
         </span>
-        <span class="task-done" data-task-done="past-far" title="Click to edit done date, Right-click for more options">
+        <span class="task-done" data-task-done="past-far" title="Right-click to edit done date">
           <span>✅ 2023-07-05</span>
         </span>
         <span class="task-block-link"><span>^dcf64c</span></span>
diff --git a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html
index 5360594f..1d3200fe 100644
--- a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html
+++ b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_full_mode.approved.html
@@ -23,31 +23,22 @@
     <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
     <span class="task-recurring"><span>🔁 every day when done</span></span>
     <span class="task-onCompletion"><span>🏁 delete</span></span>
-    <span
-      class="task-created"
-      data-task-created="past-4d"
-      title="Click to edit created date, Right-click for more options">
+    <span class="task-created" data-task-created="past-4d" title="Right-click to edit created date">
       <span>➕ 2023-07-01</span>
     </span>
-    <span class="task-start" data-task-start="past-3d" title="Click to edit start date, Right-click for more options">
+    <span class="task-start" data-task-start="past-3d" title="Right-click to edit start date">
       <span>🛫 2023-07-02</span>
     </span>
-    <span
-      class="task-scheduled"
-      data-task-scheduled="past-2d"
-      title="Click to edit scheduled date, Right-click for more options">
+    <span class="task-scheduled" data-task-scheduled="past-2d" title="Right-click to edit scheduled date">
       <span>⏳ 2023-07-03</span>
     </span>
-    <span class="task-due" data-task-due="past-1d" title="Click to edit due date, Right-click for more options">
+    <span class="task-due" data-task-due="past-1d" title="Right-click to edit due date">
       <span>📅 2023-07-04</span>
     </span>
-    <span
-      class="task-cancelled"
-      data-task-cancelled="future-1d"
-      title="Click to edit cancelled date, Right-click for more options">
+    <span class="task-cancelled" data-task-cancelled="future-1d" title="Right-click to edit cancelled date">
       <span>❌ 2023-07-06</span>
     </span>
-    <span class="task-done" data-task-done="today" title="Click to edit done date, Right-click for more options">
+    <span class="task-done" data-task-done="today" title="Right-click to edit done date">
       <span>✅ 2023-07-05</span>
     </span>
     <span class="task-block-link"><span>^dcf64c</span></span>
diff --git a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html
index 378b57b8..cf2ac965 100644
--- a/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html
+++ b/tests/Renderer/TaskLineRenderer.test.Visualise_HTML_Full_task_-_short_mode.approved.html
@@ -23,33 +23,18 @@
     <span class="task-priority" data-task-priority="medium"><span>🔼</span></span>
     <span class="task-recurring"><span>🔁</span></span>
     <span class="task-onCompletion"><span>🏁</span></span>
-    <span
-      class="task-created"
-      data-task-created="past-4d"
-      title="Click to edit created date, Right-click for more options">
+    <span class="task-created" data-task-created="past-4d" title="Right-click to edit created date">
       <span>➕</span>
     </span>
-    <span class="task-start" data-task-start="past-3d" title="Click to edit start date, Right-click for more options">
-      <span>🛫</span>
-    </span>
-    <span
-      class="task-scheduled"
-      data-task-scheduled="past-2d"
-      title="Click to edit scheduled date, Right-click for more options">
+    <span class="task-start" data-task-start="past-3d" title="Right-click to edit start date"><span>🛫</span></span>
+    <span class="task-scheduled" data-task-scheduled="past-2d" title="Right-click to edit scheduled date">
       <span>⏳</span>
     </span>
-    <span class="task-due" data-task-due="past-1d" title="Click to edit due date, Right-click for more options">
-      <span>📅</span>
-    </span>
-    <span
-      class="task-cancelled"
-      data-task-cancelled="future-1d"
-      title="Click to edit cancelled date, Right-click for more options">
+    <span class="task-due" data-task-due="past-1d" title="Right-click to edit due date"><span>📅</span></span>
+    <span class="task-cancelled" data-task-cancelled="future-1d" title="Right-click to edit cancelled date">
       <span>❌</span>
     </span>
-    <span class="task-done" data-task-done="today" title="Click to edit done date, Right-click for more options">
-      <span>✅</span>
-    </span>
+    <span class="task-done" data-task-done="today" title="Right-click to edit done date"><span>✅</span></span>
     <span class="task-block-link"><span>^dcf64c</span></span>
   </span>
 </li>
diff --git a/tests/ui/EditTask.test.Edit_Modal_HTML_snapshot_tests_should_match_snapshot.approved.html b/tests/ui/EditTask.test.Edit_Modal_HTML_snapshot_tests_should_match_snapshot.approved.html
index d5e95626..7333f8b2 100644
--- a/tests/ui/EditTask.test.Edit_Modal_HTML_snapshot_tests_should_match_snapshot.approved.html
+++ b/tests/ui/EditTask.test.Edit_Modal_HTML_snapshot_tests_should_match_snapshot.approved.html
@@ -94,10 +94,6 @@
         class="tasks-modal-date-input"
         placeholder="Try 'Mon' or 'tm' then space"
         accesskey="d" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         📅
         <i>no due date</i>
@@ -112,10 +108,6 @@
         class="tasks-modal-date-input"
         placeholder="Try 'Mon' or 'tm' then space"
         accesskey="s" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         ⏳
         <i>no scheduled date</i>
@@ -131,10 +123,6 @@
         class="tasks-modal-date-input"
         placeholder="Try 'Mon' or 'tm' then space"
         accesskey="a" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         🛫
         <i>no start date</i>
@@ -233,10 +221,6 @@
         class="tasks-modal-date-input"
         placeholder="Try 'Mon' or 'tm' then space"
         accesskey="c" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         ➕
         <i>no created date</i>
@@ -252,10 +236,6 @@
         class="tasks-modal-date-input"
         placeholder="Try 'Mon' or 'tm' then space"
         accesskey="x" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         ✅
         <i>no done date</i>
@@ -271,10 +251,6 @@
         class="tasks-modal-date-input"
         placeholder="Try 'Mon' or 'tm' then space"
         accesskey="-" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         ❌
         <i>no cancelled date</i>
diff --git a/tests/ui/EditTask.test.Edit_Modal_HTML_snapshot_tests_should_match_snapshot_-_without_access_keys.approved.html b/tests/ui/EditTask.test.Edit_Modal_HTML_snapshot_tests_should_match_snapshot_-_without_access_keys.approved.html
index 8c67004b..e35d282d 100644
--- a/tests/ui/EditTask.test.Edit_Modal_HTML_snapshot_tests_should_match_snapshot_-_without_access_keys.approved.html
+++ b/tests/ui/EditTask.test.Edit_Modal_HTML_snapshot_tests_should_match_snapshot_-_without_access_keys.approved.html
@@ -56,30 +56,18 @@
       </code>
       <label for="due">Due</label>
       <input id="due" type="text" class="tasks-modal-date-input" placeholder="Try 'Mon' or 'tm' then space" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         📅
         <i>no due date</i>
       </code>
       <label for="scheduled">Scheduled</label>
       <input id="scheduled" type="text" class="tasks-modal-date-input" placeholder="Try 'Mon' or 'tm' then space" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         ⏳
         <i>no scheduled date</i>
       </code>
       <label for="start">Start</label>
       <input id="start" type="text" class="tasks-modal-date-input" placeholder="Try 'Mon' or 'tm' then space" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         🛫
         <i>no start date</i>
@@ -153,30 +141,18 @@
       </select>
       <label for="created">Created</label>
       <input id="created" type="text" class="tasks-modal-date-input" placeholder="Try 'Mon' or 'tm' then space" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         ➕
         <i>no created date</i>
       </code>
       <label for="done">Done</label>
       <input id="done" type="text" class="tasks-modal-date-input" placeholder="Try 'Mon' or 'tm' then space" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         ✅
         <i>no done date</i>
       </code>
       <label for="cancelled">Cancelled</label>
       <input id="cancelled" type="text" class="tasks-modal-date-input" placeholder="Try 'Mon' or 'tm' then space" />
-      <button
-        class="tasks-modal-calendar-button"
-        aria-label="Open date picker"
-        style="background: none; padding: 0px; cursor: pointer"></button>
       <code class="tasks-modal-parsed-date">
         ❌
         <i>no cancelled date</i>