Skip to content

Commit 788ac79

Browse files
committed
2.5.0.b2 Added cache for all open Priority>0 tasks.
1 parent bac92ed commit 788ac79

8 files changed

Lines changed: 68 additions & 43 deletions

‎jgclark.Dashboard/CHANGELOG.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# What's changed in 🎛 Dashboard plugin?
22
For more details see the [plugin's documentation](https://github.com/NotePlan/plugins/tree/main/jgclark.Dashboard/).
33

4+
## [2.5.0.b2] 2026-08-27
5+
- New: Priority note-index cache so the Priority section can avoid a full vault scan on warm refreshes. On by default (`FFlag_UsePriorityCache`); falls back to full scan and schedules a background rebuild when the cache is missing. Commands: `generatePriorityNoteIndexCache` / `updatePriorityNoteIndexCache`.
6+
47
## [2.5.0.b1] 2026-08-27
5-
- New: Priority note-index cache (`priorityNoteIndexCache.json`) so the Priority section can avoid a full vault scan on warm refreshes. On by default (`FFlag_UsePriorityCache`); falls back to full scan and schedules a background rebuild when the cache is missing. Commands: `generatePriorityNoteIndexCache` / `updatePriorityNoteIndexCache`.
68
- New: Interactive Processing on Active Projects and Projects to Review (`>> N` walks project rows; PROJACT allows back-navigate, PROJREVIEW forward-only; Start Reviews unchanged).
79
- Dev: Consolidate shared section-row CSS (`.sectionItemRow`, `.sectionItemContent`, `.itemIcon`) into `ItemRow.css`; remove duplicate `TaskItem.css` / `TasksFiltered.css`.
810
- Dev: Bring Reminder edit / Convert to Task / `ITEM_ORIG_SECTION` refresh and related 2.4.2–2.4.3 work from main onto the 2.5.0 branch.

‎jgclark.Dashboard/docs/ARCHITECTURE-How_Stuff_Works.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ Additionally, synthetic sections (e.g. `WINS`) are stripped from pluginData in t
243243
244244
## Priority note-index cache (`priorityNoteIndexCache.js`)
245245
246-
The Priority section has no NotePlan API equivalent of `DataStore.listOverdueTasks()`, so discovery historically walked every non-special project note plus past calendar notes (~11s on a large vault for a few dozen hits). From **2.5.0.b1**, Dashboard keeps a **plugin-local filename index** of notes that contain at least one **open, unscheduled, priority>0** paragraph (same membership rule as `getOpenPriorityItems` in `dataGenerationPriority.js`).
246+
The Priority section has no NotePlan API equivalent of `DataStore.listOverdueTasks()`, so discovery historically walked every non-special project note plus past calendar notes (~11s on a large vault for a few dozen hits). From 2.5.0.b2, Dashboard keeps a **plugin-local filename index** of notes that contain at least one **open, unscheduled, priority>0** paragraph (same membership rule as `getOpenPriorityItems` in `dataGenerationPriority.js`).
247247
248248
Implementation: `src/priorityNoteIndexCache.js`. Consumed by `getRelevantPriorityTasks()` when Priority cache is enabled (default -- **on** unless `FFlag_UsePriorityCache: false` in top-level `dashboardSettings`).
249249
@@ -262,11 +262,11 @@ Paragraphs are **not** stored. On every Priority refresh the plugin re-opens can
262262
| All non-`@` regular notes | Existing folder filters (`includedFolders` / `excludedFolders`) via `filterParasByRelevantFolders` |
263263
| **All** calendar notes that match the membership rule | Restrict calendar candidates to `pastCalendarNotes()` (date start before today) |
264264
265-
Indexing calendar notes broadly avoids a midnight-only invalidation path when yesterday's note becomes "past." Folder filters are **not** baked into the index so one cache serves all perspectives.
265+
Indexing calendar notes broadly avoids a midnight-only invalidation path when yesterday's note becomes "past." _Folder filters are **not** baked into the index so one cache serves all perspectives._
266266
267267
### Cold / missing cache
268268
269-
If the cache file is missing or invalid, Priority falls back to the full vault scan (correct first paint) and **schedules** `generatePriorityNoteIndexCache` via preference. Scheduled rebuilds run fire-and-forget after `incrementallyRefreshSomeSections` / `batchReplaceSections` (same pattern as the tag cache), on the **main thread** with loading/banner feedback.
269+
If the cache file is missing or invalid, Priority falls back to the full vault scan (correct first paint) and **schedules** `generatePriorityNoteIndexCache` via preference. Scheduled rebuilds run fire-and-forget after `incrementallyRefreshSomeSections` / `batchReplaceSections` (same pattern as the tag cache), on the **main thread** with WebView banner feedback (`sendBannerMessage`).
270270
271271
### Refresh cadence
272272

‎jgclark.Dashboard/plugin.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
"plugin.iconColor": "red-600",
88
"plugin.description": "A Dashboard for NotePlan, that in one place can show:\n- a compact list of open tasks and checklists from today's note, and scheduled ones from other notes.\n- similarly for yesterday's note, tomorrow's note, and the weekly, monthly and quarterly notes too (if used)\n- open Apple Reminders\n- any timeblocks for today\n- all overdue tasks\n- all open tasks and checklists that contain particular @tags or #mentions of your choosing\n- the next notes ready to review (if you use the 'Projects and Reviews' plugin).\nIt includes many other ways of speeding up managing your tasks: see the website for more details.",
99
"plugin.author": "@jgclark",
10-
"plugin.version": "2.5.0.b1",
10+
"plugin.version": "2.5.0.b2",
1111
"plugin.releaseStatus": "beta",
1212
"plugin.hidden": false,
13-
"plugin.lastUpdateInfo": "2.5.0.b1: Priority note-index cache speeds up the Priority section (avoids full vault scan on warm refreshes). Interactive Processing for Active/Review Projects. Shared section-row CSS.\n2.4.2: Further extending edit dialog and interactive processing for Reminders.\n2.4.1: New Edit dialog and Interactive Processing for Apple Reminders. Quicker response when changing Perspective. Fix IP/task dialog error banner when Update/Enter with unchanged content (#778). Demo timing and Timed Items display fixes.\n2.4.0: New 'Reminders' Section, and addition of Apple Reminders into other Sections.\nNew 'Active Projects' Section.\nNew 'Wins' Section.\nNew 'Spaces to Include' setting which controls which (Team)Spaces you wish to include, plus whether or not to include the Private 'Space' (all notes not in a Space)\nSignificant performance improvements on refresh and for Tag/Mention sections.\nDozens of other improvements and fixes.\n2.3.3: new 'Year' section available.\n2.3.2: fix display when there are no priority items shown.\n2.3.1: fix for possible loss of settings error when upgrading.\n2.3.0: Support for NotePlan (Team)Spaces. Can re-order display of Sections.New '/backupSettings' command. Added 'noteTags' feature. Speeded up Tag/Mention sections. Layout improvements. Lots of other small fixes and improvements.\n2.2.1: Add new sorting option for Tag and Overdue sections.\n2.2.0: Add 'Search' section. New keyboard shortcuts. Plus many small improvements, bug fixes and performance improvements. See documentation for details.",
13+
"plugin.lastUpdateInfo": "2.5.0.b2: Priority note-index cache speeds up the Priority section (avoids full vault scan on warm refreshes).\n2.5.0.b1: Interactive Processing for Active/Review Projects. Shared section-row CSS.\n2.4.2: Further extending edit dialog and interactive processing for Reminders.\n2.4.1: New Edit dialog and Interactive Processing for Apple Reminders. Quicker response when changing Perspective. Fix IP/task dialog error banner when Update/Enter with unchanged content (#778). Demo timing and Timed Items display fixes.\n2.4.0: New 'Reminders' Section, and addition of Apple Reminders into other Sections.\nNew 'Active Projects' Section.\nNew 'Wins' Section.\nNew 'Spaces to Include' setting which controls which (Team)Spaces you wish to include, plus whether or not to include the Private 'Space' (all notes not in a Space)\nSignificant performance improvements on refresh and for Tag/Mention sections.\nDozens of other improvements and fixes.\n2.3.3: new 'Year' section available.\n2.3.2: fix display when there are no priority items shown.\n2.3.1: fix for possible loss of settings error when upgrading.\n2.3.0: Support for NotePlan (Team)Spaces. Can re-order display of Sections.New '/backupSettings' command. Added 'noteTags' feature. Speeded up Tag/Mention sections. Layout improvements. Lots of other small fixes and improvements.\n2.2.1: Add new sorting option for Tag and Overdue sections.\n2.2.0: Add 'Search' section. New keyboard shortcuts. Plus many small improvements, bug fixes and performance improvements. See documentation for details.",
1414
"plugin.dependencies": [],
1515
"plugin.requiredFiles": [
1616
"react.c.WebView.bundle.dev.js"
@@ -63,7 +63,7 @@
6363
"description": "Show Demo Dashboard",
6464
"jsFunction": "showDemoDashboard",
6565
"hidden": false,
66-
"comment": "TODO: hide me in time",
66+
"comment": "TODO(later): hide me in time",
6767
"alias": [
6868
"sdd"
6969
],

‎jgclark.Dashboard/src/dataGenerationPriority.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22
//-----------------------------------------------------------------------------
33
// Dashboard plugin main function to generate data
4-
// Last updated 2026-08-12 for v2.4.0.b63 by @jgclark + @CursorAI
4+
// Last updated 2026-08-27 for v2.5.0.b2 by @jgclark + @CursorAI
55
//-----------------------------------------------------------------------------
66

77
import moment from 'moment/min/moment-with-locales'
@@ -172,7 +172,7 @@ async function getRelevantPriorityTasks(config: TDashboardSettings): Promise<Arr
172172
const cachedNotes = await getNotesFromPriorityNoteIndexCache()
173173
if (cachedNotes != null) {
174174
notesToCheck = cachedNotes
175-
logTimer('getRelevantPriorityTasks', thisStartTime, `- from PRIORITY CACHE: ${String(notesToCheck.length)} candidate notes`)
175+
logInfo('getRelevantPriorityTasks', `- from PRIORITY CACHE: ${String(notesToCheck.length)} candidate notes`)
176176
} else {
177177
logInfo('getRelevantPriorityTasks', `- Priority cache unavailable; falling back to full vault scan (generation scheduled for later)`)
178178
schedulePriorityNoteIndexCacheGeneration()

‎jgclark.Dashboard/src/priorityNoteIndexCache.js‎

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22
//-----------------------------------------------------------------------------
33
// Priority note-index cache for Dashboard PRIORITY section
4-
// Last updated 2026-08-27 for v2.5.0.b1 by @jgclark + @CursorAI
4+
// Last updated 2026-08-27 for v2.5.0.b2 by @CursorAI
55
//-----------------------------------------------------------------------------
66
// Cache structure (JSON file):
77
// {
@@ -14,6 +14,7 @@
1414
// Indexes notes that contain at least one open, unscheduled, priority>0 item.
1515
// Folder filters are applied at query time so one cache serves all perspectives.
1616
// Calendar notes are indexed broadly; Priority query restricts to past calendar notes.
17+
// Rebuild every PRIORITY_CACHE_GENERATE_INTERVAL_DAYS (default 5) days, or on demand if the user has requested it.
1718
//-----------------------------------------------------------------------------
1819

1920
import moment from 'moment/min/moment-with-locales'
@@ -188,23 +189,16 @@ function addNoteToPriorityCache(cache: TPriorityNoteIndexCache, filename: string
188189
/**
189190
* Scan notes and collect filenames that match the Priority membership rule.
190191
* @param {$ReadOnlyArray<TNote>} notes
191-
* @param {string} progressLabel
192192
* @returns {{ filenames: Array<string>, matchingNoteCount: number, notesSkippedByPrefilter: number }}
193193
*/
194194
function processNotesForPriorityIndex(
195195
notes: $ReadOnlyArray<TNote>,
196-
progressLabel: string,
197196
): { filenames: Array<string>, matchingNoteCount: number, notesSkippedByPrefilter: number } {
198197
const filenames: Array<string> = []
199198
let matchingNoteCount = 0
200199
let notesSkippedByPrefilter = 0
201-
let noteCount = 0
202200

203201
for (const note of notes) {
204-
noteCount++
205-
if (noteCount % 100 === 0) {
206-
CommandBar.showLoading(true, progressLabel, noteCount / notes.length)
207-
}
208202
if (!isNonBlankMarkdownNote(note)) {
209203
notesSkippedByPrefilter++
210204
continue
@@ -247,6 +241,28 @@ export function schedulePriorityNoteIndexCacheGeneration(): void {
247241
DataStore.setPreference(regeneratePriorityNoteIndexCachePref, true)
248242
}
249243

244+
/**
245+
* True when a scheduled full regenerate should actually run.
246+
* Skips leftover schedule flags when a fresh-enough cache file already exists
247+
* (e.g. after a prior generate that saved the file but never cleared the pref).
248+
* @returns {boolean}
249+
*/
250+
export function shouldRunScheduledPriorityNoteIndexCacheGeneration(): boolean {
251+
if (!isPriorityNoteIndexCacheGenerationScheduled()) return false
252+
const cache = loadPriorityNoteIndexCache()
253+
if (!cache || !cache.generatedAt) return true
254+
const diffHours = moment().diff(moment(cache.generatedAt), 'hours', true)
255+
if (diffHours < PRIORITY_CACHE_GENERATE_INTERVAL_DAYS * 24) {
256+
logInfo(
257+
'shouldRunScheduledPriorityNoteIndexCacheGeneration',
258+
`Schedule flag set, but cache is only ${diffHours.toFixed(3)} hours old (< ${String(PRIORITY_CACHE_GENERATE_INTERVAL_DAYS)} days) - clearing flag and skipping full regenerate`,
259+
)
260+
clearPriorityNoteIndexCacheGenerationPref()
261+
return false
262+
}
263+
return true
264+
}
265+
250266
/**
251267
* Schedule regeneration if the cache is older than PRIORITY_CACHE_GENERATE_INTERVAL_DAYS.
252268
* @param {string} generatedAtStr
@@ -290,13 +306,13 @@ export function loadPriorityNoteIndexCache(): ?TPriorityNoteIndexCache {
290306
* Generate the priority note-index cache from scratch.
291307
* Scans all non-@ regular notes and all calendar notes on the **main thread**
292308
* (same lesson as tagMentionCache: async-thread regenerations have stalled without saving).
309+
* Progress uses the Dashboard WebView banner (`sendBannerMessage`), same as tag/mention cache generation.
293310
* @param {string} generationReason
294311
* @returns {Promise<void>}
295312
*/
296313
export async function generatePriorityNoteIndexCache(generationReason: string = 'Triggered by external call'): Promise<void> {
297314
const startTime = new Date()
298315
let progressBannerShown = false
299-
let loadingIndicatorShown = false
300316
try {
301317
logInfo('generatePriorityNoteIndexCache', `Starting for ${generationReason}`)
302318

@@ -309,16 +325,14 @@ export async function generatePriorityNoteIndexCache(generationReason: string =
309325
'INFO',
310326
)
311327
progressBannerShown = true
312-
313-
CommandBar.showLoading(true, `Generating Priority note-index cache (${String(allCalNotes.length + allRegularNotes.length)} notes) ...`)
314-
loadingIndicatorShown = true
328+
logInfo('generatePriorityNoteIndexCache', `- processing ${String(allCalNotes.length)} calendar + ${String(allRegularNotes.length)} regular notes ...`)
315329

316330
logInfo('generatePriorityNoteIndexCache', `- scanning ${String(allCalNotes.length)} calendar notes ...`)
317-
const calResult = processNotesForPriorityIndex(allCalNotes, 'Generating Priority note-index cache (calendar)')
331+
const calResult = processNotesForPriorityIndex(allCalNotes)
318332
logDebug('generatePriorityNoteIndexCache', ` - pre-filter skipped ${String(calResult.notesSkippedByPrefilter)} calendar notes`)
319333

320334
logInfo('generatePriorityNoteIndexCache', `- scanning ${String(allRegularNotes.length)} regular notes ...`)
321-
const regResult = processNotesForPriorityIndex(allRegularNotes, 'Generating Priority note-index cache (regular)')
335+
const regResult = processNotesForPriorityIndex(allRegularNotes)
322336
logDebug('generatePriorityNoteIndexCache', ` - pre-filter skipped ${String(regResult.notesSkippedByPrefilter)} regular notes`)
323337

324338
const elapsedSecs = Math.max(((new Date(): any) - startTime) / 1000, 0.001)
@@ -344,16 +358,17 @@ export async function generatePriorityNoteIndexCache(generationReason: string =
344358
logTimer('generatePriorityNoteIndexCache', startTime, `- after saving ${String(totalMatching)} note filenames to ${priorityNoteIndexCacheFile}`)
345359

346360
recordPriorityCacheLastRunTime(startTime)
361+
// Clear the schedule flag before any WebView banner await, so a hung banner cannot leave regen permanently scheduled
362+
clearPriorityNoteIndexCacheGenerationPref()
347363

364+
// Replace progress banner with a timed completion message (progress banners have no timeout and would persist otherwise)
348365
await sendBannerMessage(
349366
WEBVIEW_WINDOW_ID,
350367
`Priority note-index cache re-generated; it contains ${String(totalMatching)} notes with raised-priority items`,
351368
'INFO',
352369
5000,
353370
)
354371
progressBannerShown = false
355-
356-
clearPriorityNoteIndexCacheGenerationPref()
357372
} catch (err) {
358373
logError('generatePriorityNoteIndexCache', JSP(err))
359374
if (progressBannerShown) {
@@ -363,9 +378,6 @@ export async function generatePriorityNoteIndexCache(generationReason: string =
363378
progressBannerShown = false
364379
}
365380
} finally {
366-
if (loadingIndicatorShown) {
367-
CommandBar.showLoading(false)
368-
}
369381
if (progressBannerShown) {
370382
logWarn('generatePriorityNoteIndexCache', `- removing progress banner`)
371383
await sendBannerMessage(WEBVIEW_WINDOW_ID, '', 'REMOVE')
@@ -489,6 +501,13 @@ export async function getNotesFromPriorityNoteIndexCache(): Promise<?Array<TNote
489501
}
490502

491503
schedulePriorityNoteIndexCacheGenerationIfTooOld(cache.generatedAt)
504+
// If we have a usable cache, drop any leftover regenerate flag from a prior interrupted generate
505+
if (isPriorityNoteIndexCacheGenerationScheduled()) {
506+
const diffHours = moment().diff(moment(cache.generatedAt), 'hours', true)
507+
if (diffHours < PRIORITY_CACHE_GENERATE_INTERVAL_DAYS * 24) {
508+
clearPriorityNoteIndexCacheGenerationPref()
509+
}
510+
}
492511

493512
const pastCalFilenames = new Set(pastCalendarNotes().map((n) => n.filename))
494513
const candidateFilenames: Array<string> = cache.regularNotes.concat(cache.calendarNotes.filter((fn) => pastCalFilenames.has(fn)))

‎jgclark.Dashboard/src/react/components/DialogForProjectItems.jsx‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
//--------------------------------------------------------------------------
33
// Dashboard React component to show the Dialog for Projects
44
// Called by Dialog component. Supports Interactive Processing for PROJACT / PROJREVIEW.
5-
// Last updated 2026-08-21 for v2.4.1 by @jgclark + @CursorAI
5+
// Last updated 2026-08-28 for v2.5.0.b by @jgclark + @CursorAI
66
//--------------------------------------------------------------------------
77

88
import React, { useRef, useLayoutEffect, useState, useCallback } from 'react'
99
import { validateAndFlattenMessageObject } from '../../shared'
10-
import { type MessageDataObject } from "../../types"
10+
import { type MessageDataObject, type TSection, type TSectionCode } from '../../types'
1111
import { useAppContext } from './AppContext.jsx'
1212
import CalendarPicker from './CalendarPicker.jsx'
1313
import ItemNoteLink from './ItemNoteLink.jsx'
@@ -65,7 +65,9 @@ const DialogForProjectItems = ({ details: detailsMessageObject, onClose, positio
6565
const monthsToShow = (pluginData.platform === 'iOS') ? 1 : 2
6666

6767
const thisItem = detailsMessageObject?.item
68-
const sectionCode = thisItem?.sectionCode || detailsMessageObject?.sectionCodes?.[0] || ''
68+
const sectionCode: TSectionCode | '' = thisItem?.sectionCode || detailsMessageObject?.sectionCodes?.[0] || ''
69+
const thisSection: ?TSection =
70+
sectionCode !== '' ? pluginData.sections?.find((section) => section.sectionCode === sectionCode) : undefined
6971
// PROJACT: true back-navigate; PROJREVIEW: forward skip only
7072
const showBackNavigate = sectionCode === 'PROJACT' && canNavigateBackInIP(currentIPIndex)
7173

@@ -286,13 +288,15 @@ const DialogForProjectItems = ({ details: detailsMessageObject, onClose, positio
286288
label={`Project Item Dialog for ${title}`}
287289
>
288290
<span className="dialogItemNote">
289-
<ItemNoteLink
290-
item={thisItem}
291-
thisSection={sectionCode}
292-
alwaysShowNoteTitle={true}
293-
suppressTeamspaceName={false}
294-
normalSize={true}
295-
/>
291+
{thisSection != null && (
292+
<ItemNoteLink
293+
item={thisItem}
294+
thisSection={thisSection}
295+
alwaysShowNoteTitle={true}
296+
suppressTeamspaceName={false}
297+
normalSize={true}
298+
/>
299+
)}
296300
</span>
297301
<span className="reviewDetailsText">{reviewDetails}</span>
298302
</TooltipOnKeyPress>

‎jgclark.Dashboard/src/react/components/interactiveProcessingHelpers.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// Last updated 2026-08-21 for v2.4.1 by @CursorAI
55
//--------------------------------------------------------------------------
66

7-
import type { TItemToProcess } from '../../types'
7+
import type { TItemToProcess, TSectionItem } from '../../types'
88

99
// Alias of TItemToProcess so IP helpers stay aligned with TInteractiveProcessing.visibleItems.
1010
export type TIPVisibleItem = TItemToProcess

0 commit comments

Comments
 (0)