Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions jgclark.Dashboard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ For more details see the [plugin's documentation](https://github.com/NotePlan/pl
- TODO: fix long-standing layout bug where some tooltips were getting clipped
- TODO: fix isNoteFromAllowedFolder() for teamspace or possibly 2025-W21.md
-->
## [2.4.0.b58] 2026-07-30
- Fix: reminders whose own section was switched off were discarded silently. Overdue, yesterday and **untimed today** reminders now fall back to the **Reminders** section (own section → Overdue → Reminders), so nothing disappears just because a section is hidden. Both fallbacks stay subject to **Show Current Reminders** / **Show Undated/Overdue Reminders**, so an item you deliberately hid is not resurrected.
- Fix: the **Overdue** section counted reminders it did not display. When `maxItemsToShowInSection` left fewer slots than there were reminders, the surplus was sliced off the list but still added to the total, so the header claimed more items than the section held. Now counts only what was added, and warns when some do not fit.
- Fix: **Spaces to Include** holding only unreachable teamspace IDs hid every task. Private notes are read only when `private` is in that list, so a list of stale IDs (e.g. after signing out of Spaces) filtered out every note before any task could be read - reminders still appeared, which made it look like tasks had vanished. Unreachable IDs are now discarded, falling back to private notes with a warning. Deliberate configurations are untouched.
- Doc: a timed reminder due later today is intentionally shown nowhere until its time arrives. This was undocumented and indistinguishable from the bug above; there is now a DESIGN DECISION note at the filter in `dataGenerationDays.js` naming what to change to show them early, and the pitfall of them then appearing twice.
- New: log when reminders or notes are dropped - reminder buckets with no visible host section, reminder lists disabled in NotePlan (a reminder there is invisible with no other clue), and overdue reminders that did not fit the section limit.
- dev: temporary diagnostic logging retained for ongoing testing - one line per reminder (list, title, raw EventKit date vs derived date/time), one per bucket assignment, and the REM section size.

## [2.4.0.b57] 2026-07-29
- New: sticky **Filter settings** search at the top of Dashboard Settings - matches label/description (from 3+ characters).
- Reduce opacity of 'chips' showing Reminder list name, and note names.
Expand Down
2 changes: 1 addition & 1 deletion jgclark.Dashboard/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"plugin.description": "A Dashboard for NotePlan, that in one place shows:\n- a compact list of open tasks and checklists from today's note\n- scheduled open tasks and checklists from other notes.\n- similarly for yesterday's note, tomorrow's note, and the weekly, monthly and quarterly notes too (if used)\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.",
"plugin.author": "@jgclark",
"plugin.comment": "TODO: On full release, change minAppVersion down to 3.7?",
"plugin.version": "2.4.0.b57",
"plugin.version": "2.4.0.b58",
"plugin.releaseStatus": "beta",
"plugin.hidden": false,
"plugin.lastUpdateInfo": "2.4.0: new 'Active Projects' Section. New '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)\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.\n2.1.10: More move-under-heading options. Bug fixes and performance improvements.\n2.1.9: performance improvements and better UI for iPhone users.\n2.1.8: various fixes and small improvements.\n2.1.7: various fixes and small improvements.\n2.1.6: allow all current timeblocks to be shown, not just the first. Add new @repeat()s if using the extended syntax from the Repeat Extensions plugin. Bug fixes.\n2.1.5: fixes to time blocks and scheduling items.\n2.1.4: fix to Interactive Processing, and Edit All Perspectives dialog now shows unsaved changes.",
Expand Down
47 changes: 44 additions & 3 deletions jgclark.Dashboard/src/dashboardHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ export function getOpenItemParasForTimePeriod(
const { matchingNotes, possTimePeriodNote } = getMatchingCalendarNotes(NPCalendarFilenameStr)

// Filter notes by allowed teamspaces
const allowedTeamspaceIDs = dashboardSettings.includedTeamspaces ?? ['private']
const allowedTeamspaceIDs = resolveAllowedTeamspaceIDs(dashboardSettings)
const filteredMatchingNotes = matchingNotes.filter((note) => isNoteFromAllowedTeamspace(note, allowedTeamspaceIDs))
logDebug('getOpenItemParasForTimePeriod', `- after teamspace filter: ${filteredMatchingNotes.length} of ${matchingNotes.length} notes`)

Expand Down Expand Up @@ -1119,6 +1119,47 @@ export function isLineDisallowedByIgnoreTerms(lineContent: string, ignoreItemsWi
return matchFound
}

/**
* Resolve which teamspaces this config should read notes from, discarding IDs that
* no longer exist.
*
* Why this exists: `includedTeamspaces` is a plain list of IDs, and 'private' has to
* appear in it for your own notes to be read at all. So a list holding only teamspace
* IDs silently hides every private note -- which for most users is every note they
* have. That is survivable while the IDs are real, because it is what you asked for.
* It is not survivable when the IDs are stale: signing out of Spaces (or leaving them)
* leaves a list that matches nothing, the Dashboard shows no tasks at all, and the
* settings UI reports "You are not a member of any Spaces" so there is nothing to
* click to undo it. Seen in the wild: 6 unreachable IDs, no 'private', zero tasks.
*
* When nothing in the list is reachable, the only sensible reading is private notes.
* @param {TDashboardSettings} dashboardSettings
* @returns {Array<string>} teamspace IDs to allow, possibly healed to ['private']
*/
export function resolveAllowedTeamspaceIDs(dashboardSettings: TDashboardSettings): Array<string> {
const configured = dashboardSettings.includedTeamspaces
// Absent means "private only"; an explicitly empty list means "don't filter".
// Both are long-standing behaviour, so leave them alone.
if (!configured) return ['private']
if (configured.length === 0) return configured

let existingIDs: Array<string> = []
try {
existingIDs = getAllTeamspaceIDsAndTitles().map((t) => t.id)
} catch (err) {
// No teamspace API / not signed in: treat every configured ID as unreachable
existingIDs = []
}
const reachable = configured.filter((id) => id === 'private' || existingIDs.includes(id))
if (reachable.length > 0) return reachable

logWarn(
'resolveAllowedTeamspaceIDs',
`includedTeamspaces lists ${String(configured.length)} teamspace(s) but none are reachable and 'private' is not among them, so no note could ever match. Falling back to private notes. Check the "Spaces to Include" setting; you may be signed out of Spaces.`,
)
return ['private']
}

/**
* Check if a note is from an allowed teamspace based on dashboard settings.
* If no teamspaces specified, allow all (backward compatibility).
Expand Down Expand Up @@ -1151,7 +1192,7 @@ export function filterNotesByAllowedTeamspaces(
notes: Array<TNote>,
dashboardSettings: TDashboardSettings
): Array<TNote> {
const allowedTeamspaceIDs = dashboardSettings.includedTeamspaces ?? ['private']
const allowedTeamspaceIDs = resolveAllowedTeamspaceIDs(dashboardSettings)
return notes.filter((note) => isNoteFromAllowedTeamspace(note, allowedTeamspaceIDs))
}

Expand Down Expand Up @@ -1191,7 +1232,7 @@ export function filterParasByAllowedTeamspaces(
startTime: Date,
functionName: string
): Array<TParagraph> {
const allowedTeamspaceIDs = dashboardSettings.includedTeamspaces ?? ['private']
const allowedTeamspaceIDs = resolveAllowedTeamspaceIDs(dashboardSettings)
const filteredParas = paras.filter((p) => {
const note = getNoteFromPara(p)
if (!note) {
Expand Down
23 changes: 22 additions & 1 deletion jgclark.Dashboard/src/dataGeneration.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { getTaggedSectionData } from './dataGenerationTags'
import { getLastWeekSectionData, getThisWeekSectionData } from './dataGenerationWeeks'
import { getTagSectionDetails } from './react/components/Section/sectionHelpers'
import { getNestedValue, setNestedValue } from '@helpers/dataManipulation'
import { logDebug, logError } from '@helpers/dev'
import { logDebug, logError, logWarn } from '@helpers/dev'
import { getLiveWindowRect, getStoredWindowRect, rectToString } from '@helpers/NPWindows'

//-----------------------------------------------------------------
Expand Down Expand Up @@ -119,6 +119,27 @@ export async function getSomeSectionsData(
const overdueReminderItems = undatedOverdueRemindersEnabled
? remindersData.overdueItems.concat(yesterdaySpillToOverdue)
: []
// A reminder only reaches the UI if some section hosts it. Yesterday and overdue
// reminders now fall back to the REM ("Undated/Overdue Reminders") section when
// their own section is off, so they are only truly lost if REM is off too.
// Tomorrow has no fallback by design, so it is lost whenever Tomorrow is off.
const remCanHost = undatedOverdueRemindersEnabled
const yesterdayHomeless =
remindersData.yesterdayItems.length > 0 && yesterdayForDaySection.length === 0 && !Boolean(config.showOverdueSection) && !remCanHost
const overdueHomeless = remindersData.overdueItems.length > 0 && !Boolean(config.showOverdueSection) && !remCanHost
const tomorrowHomeless = remindersData.tomorrowItems.length > 0 && !config.showTomorrowSection
// Untimed today falls back to REM, so it is only lost when REM cannot host either.
// Timed today reminders that are not yet due are intentionally shown nowhere
// (see the DESIGN DECISION note in dataGenerationDays.js), so they are not warned about.
const untimedTodayHomeless = remindersData.untimedTodayItems.length > 0 && !config.showTodaySection && !remCanHost
if (yesterdayHomeless || overdueHomeless || tomorrowHomeless || untimedTodayHomeless) {
const parts = []
if (yesterdayHomeless) parts.push(`${String(remindersData.yesterdayItems.length)} yesterday`)
if (overdueHomeless) parts.push(`${String(remindersData.overdueItems.length)} overdue`)
if (tomorrowHomeless) parts.push(`${String(remindersData.tomorrowItems.length)} tomorrow (no fallback: Tomorrow section off)`)
if (untimedTodayHomeless) parts.push(`${String(remindersData.untimedTodayItems.length)} untimed today`)
logWarn('getSomeSectionsData', `- ${parts.join('; ')} reminder(s) have no visible section and will not be shown anywhere`)
}

// DT and TB sections are now generated separately but share paragraph data fetching
if (sectionCodesToGet.includes('DT')) {
Expand Down
12 changes: 11 additions & 1 deletion jgclark.Dashboard/src/dataGenerationDays.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,21 @@ export function getTimeBlockSectionData(
let dueNowReminderCount = 0

// Append today's timed reminders whose due time has been reached (when Reminders section is enabled)
//
// DESIGN DECISION (@jgclark): a timed reminder due later today is shown NOWHERE
// until its time arrives. It is excluded here, and the Today section only ever
// receives *untimed* today reminders, so there is no other section that could
// pick it up -- this is deliberate, not an oversight, and it is why the REM
// fallback in dataGenerationReminders.js skips this bucket while catching the
// others. If you want "due later today" visible ahead of time, the change is to
// route the skipped items below into the Today section (or REM when Today is off)
// rather than dropping them, and to drop them from here once their time passes so
// they do not appear twice.
if (remindersSectionEnabled && timedTodayReminderItems.length > 0) {
const dueNowReminders = filterRemindersWhoseTimeHasBeenReached(timedTodayReminderItems)
const skippedFutureCount = timedTodayReminderItems.length - dueNowReminders.length
if (skippedFutureCount > 0) {
logDebug('getTimeBlockSectionData', `- skipped ${String(skippedFutureCount)} timed reminder(s) whose time has not been reached yet`)
logDebug('getTimeBlockSectionData', `- skipped ${String(skippedFutureCount)} timed reminder(s) whose time has not been reached yet (by design: not shown anywhere until due)`)
}
if (dueNowReminders.length > 0) {
dueNowReminderCount = dueNowReminders.length
Expand Down
10 changes: 9 additions & 1 deletion jgclark.Dashboard/src/dataGenerationOverdue.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,15 @@ export async function getOverdueSectionData(
const assigned = assignReminderItemsToSection(remindersToAdd, thisSectionCode, thisSectionCode, itemCount)
items.push(...assigned)
itemCount += assigned.length
totalOverdue += overdueReminderItems.length
// Count only what was actually added. Adding the full incoming length
// inflated the header count whenever maxItemsToShowInSection left fewer
// slots than there were reminders, so the section claimed more items
// than it showed and the extras were silently unreachable.
totalOverdue += assigned.length
const droppedForSlots = overdueReminderItems.length - remindersToAdd.length
if (droppedForSlots > 0) {
logWarn('getOverdueSectionData', `- ${String(droppedForSlots)} overdue reminder(s) did not fit in maxItemsToShowInSection=${String(maxInSection ?? 24)} and are not shown anywhere`)
}
logDebug('getOverdueSectionData', `- added ${String(assigned.length)} of ${String(overdueReminderItems.length)} overdue reminder(s)`)
}
}
Expand Down
Loading