Skip to content

Commit 16b08e9

Browse files
dwertheimerclaude
andcommitted
Dashboard: catch untimed-today reminders in REM, and document the not-yet-due rule
Sweeping five perspectives against the reminder buckets showed every one of them losing items, and separated two causes. Real gap, now fixed: an untimed reminder due today had exactly one home, the Today section. With that off it went nowhere, even with the Reminders section on. It now falls back to REM alongside overdue and yesterday, under the same "Show Current Reminders" gate so an explicitly hidden item is not resurrected. Not a gap: a timed reminder due later today is shown nowhere until its time arrives. That is a deliberate decision, but nothing in the code said so, and from the outside it is indistinguishable from the bug above -- it cost a good while to tell the two apart. There is now a DESIGN DECISION note at the filter in getTimeBlockSectionData recording the intent, naming the two places that would have to change to show them early, and warning about the double-display that would result if only one were changed. The REM fallback comment says why this bucket alone is skipped, and the "no visible section" warning explicitly does not fire for it. Measured before and after on the same data (buckets timedToday=1 untimedToday=1 yesterday=0 tomorrow=0 overdue=4 undated=3, sum 9): perspective before after z_ALL_ON 8 of 9 8 shown + 1 not-yet-due = 9 Reminders Only - ALL 7 of 9 8 shown + 1 not-yet-due = 9 z_OVERDUE Only 7 of 9 8 shown + 1 not-yet-due = 9 Every reminder is now either displayed or intentionally withheld, with no silent losses left in those paths. Dashboard tests 220/220. Diagnostic logging is deliberately left in for continued testing: one line per reminder (list, title, raw EventKit date vs derived date/time), one per bucket assignment, the REM section size, and the names of reminder lists disabled in NotePlan -- that last one is how we found two "missing" reminders were simply in lists NotePlan has switched off. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 703dd71 commit 16b08e9

3 files changed

Lines changed: 58 additions & 7 deletions

File tree

‎jgclark.Dashboard/src/dataGeneration.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,16 @@ export async function getSomeSectionsData(
128128
remindersData.yesterdayItems.length > 0 && yesterdayForDaySection.length === 0 && !Boolean(config.showOverdueSection) && !remCanHost
129129
const overdueHomeless = remindersData.overdueItems.length > 0 && !Boolean(config.showOverdueSection) && !remCanHost
130130
const tomorrowHomeless = remindersData.tomorrowItems.length > 0 && !config.showTomorrowSection
131-
if (yesterdayHomeless || overdueHomeless || tomorrowHomeless) {
131+
// Untimed today falls back to REM, so it is only lost when REM cannot host either.
132+
// Timed today reminders that are not yet due are intentionally shown nowhere
133+
// (see the DESIGN DECISION note in dataGenerationDays.js), so they are not warned about.
134+
const untimedTodayHomeless = remindersData.untimedTodayItems.length > 0 && !config.showTodaySection && !remCanHost
135+
if (yesterdayHomeless || overdueHomeless || tomorrowHomeless || untimedTodayHomeless) {
132136
const parts = []
133137
if (yesterdayHomeless) parts.push(`${String(remindersData.yesterdayItems.length)} yesterday`)
134138
if (overdueHomeless) parts.push(`${String(remindersData.overdueItems.length)} overdue`)
135139
if (tomorrowHomeless) parts.push(`${String(remindersData.tomorrowItems.length)} tomorrow (no fallback: Tomorrow section off)`)
140+
if (untimedTodayHomeless) parts.push(`${String(remindersData.untimedTodayItems.length)} untimed today`)
136141
logWarn('getSomeSectionsData', `- ${parts.join('; ')} reminder(s) have no visible section and will not be shown anywhere`)
137142
}
138143

‎jgclark.Dashboard/src/dataGenerationDays.js‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,11 +321,21 @@ export function getTimeBlockSectionData(
321321
let dueNowReminderCount = 0
322322

323323
// Append today's timed reminders whose due time has been reached (when Reminders section is enabled)
324+
//
325+
// DESIGN DECISION (@jgclark): a timed reminder due later today is shown NOWHERE
326+
// until its time arrives. It is excluded here, and the Today section only ever
327+
// receives *untimed* today reminders, so there is no other section that could
328+
// pick it up -- this is deliberate, not an oversight, and it is why the REM
329+
// fallback in dataGenerationReminders.js skips this bucket while catching the
330+
// others. If you want "due later today" visible ahead of time, the change is to
331+
// route the skipped items below into the Today section (or REM when Today is off)
332+
// rather than dropping them, and to drop them from here once their time passes so
333+
// they do not appear twice.
324334
if (remindersSectionEnabled && timedTodayReminderItems.length > 0) {
325335
const dueNowReminders = filterRemindersWhoseTimeHasBeenReached(timedTodayReminderItems)
326336
const skippedFutureCount = timedTodayReminderItems.length - dueNowReminders.length
327337
if (skippedFutureCount > 0) {
328-
logDebug('getTimeBlockSectionData', `- skipped ${String(skippedFutureCount)} timed reminder(s) whose time has not been reached yet`)
338+
logDebug('getTimeBlockSectionData', `- skipped ${String(skippedFutureCount)} timed reminder(s) whose time has not been reached yet (by design: not shown anywhere until due)`)
329339
}
330340
if (dueNowReminders.length > 0) {
331341
dueNowReminderCount = dueNowReminders.length

‎jgclark.Dashboard/src/dataGenerationReminders.js‎

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@ export function getEnabledReminderLists(): TReminderListsResult {
122122
const enabledLists = Calendar.availableReminderLists({ enabledOnly: true })
123123
const result = titlesAndColorsFromReminderListObjects(enabledLists)
124124
logDebug('getEnabledReminderLists', `- ${String(result.titles.length)} enabled reminder list(s): ${result.titles.join(', ') || '(none)'}`)
125+
// Name the lists that exist but are switched off in NotePlan. A reminder living
126+
// in one of them is simply absent from the Dashboard, with nothing anywhere to
127+
// say why -- which reads as "the Dashboard lost my reminder".
128+
try {
129+
const allTitles = titlesAndColorsFromReminderListObjects(Calendar.availableReminderLists()).titles
130+
const ignored = allTitles.filter((t) => !result.titles.includes(t))
131+
if (ignored.length > 0) {
132+
logDebug('getEnabledReminderLists', `- ignoring ${String(ignored.length)} list(s) disabled in NotePlan: ${ignored.join(', ')}`)
133+
}
134+
} catch (err) {
135+
logDebug('getEnabledReminderLists', `- could not enumerate all lists to report ignored ones: ${err.message}`)
136+
}
125137
return result
126138
}
127139
// Older NotePlan: cannot read enabled/disabled or colors; use all accessible list titles
@@ -287,6 +299,10 @@ export function mapCalendarItemToReminderForDashboard(
287299
clof(calendarItem, "CalendarItem (for Reminder): ", ['title', 'date', 'occurences', 'isAllDay', 'isCompleted', 'priority'])
288300
clo(reminder, " => Reminder: ")
289301
}
302+
// One compact line per reminder: the raw EventKit fields next to what we derived.
303+
// Strictly better than the older `if (title.match(/test/i))` dump, which only
304+
// covered reminders that happened to have "test" in the name.
305+
logDebug('reminderFromCalendarItem', `- list="${String(listname ?? "?")}" "${String(calendarItem.title).slice(0, 34)}" rawDate=${String(calendarItem.date)} isAllDay=${String(calendarItem.isAllDay)} occurences=${String(calendarItem.occurences ? calendarItem.occurences.length : 'none')} -> date=${String(reminder.date ?? 'UNDATED')} time=${String(reminder.time ?? '-')}`)
290306
return reminder
291307
}
292308

@@ -468,6 +484,14 @@ export function bucketReminderItems(allItems: Array<TSectionItem>): {
468484
if (skippedFutureCount > 0) {
469485
logDebug('bucketReminderItems', `- skipped ${String(skippedFutureCount)} reminder(s) dated after ${tomorrowISO}`)
470486
}
487+
// Which bucket each reminder landed in, so a mis-bucketed one is visible.
488+
const bucketOf = (arr, name) => arr.forEach((it) => logDebug('bucketReminderItems', `- bucket=${name} "${String(it.reminder?.title ?? '?').slice(0, 34)}" date=${String(it.reminder?.date ?? 'UNDATED')} time=${String(it.reminder?.time ?? '-')}`))
489+
bucketOf(timedTodayItems, 'timedToday')
490+
bucketOf(untimedTodayItems, 'untimedToday')
491+
bucketOf(yesterdayItems, 'yesterday')
492+
bucketOf(tomorrowItems, 'tomorrow')
493+
bucketOf(overdueItems, 'overdue')
494+
bucketOf(undatedItems, 'undated')
471495

472496
return {
473497
timedTodayItems: sortReminderSectionItems(timedTodayItems),
@@ -552,11 +576,16 @@ export async function getRemindersGeneratedData(
552576
// Fallback chain, so a dated reminder is never silently discarded just because
553577
// the section that would have hosted it is switched off. This section's setting
554578
// is "Show Undated/Overdue Reminders", so an overdue reminder belongs here by
555-
// name, and a yesterday one is overdue in every sense that matters once there is
556-
// no Yesterday section to put it in. Order: own section -> Overdue -> here.
557-
// Tomorrow is deliberately excluded: a future reminder is neither undated nor
558-
// overdue, and hiding the Tomorrow section is a reasonable way to say you don't
559-
// want to see it yet.
579+
// name; a yesterday one is overdue in every sense that matters once there is no
580+
// Yesterday section to put it in; and an untimed reminder due today would
581+
// otherwise vanish entirely when the Today section is off. Order in each case is
582+
// own section -> Overdue (where applicable) -> here.
583+
//
584+
// Two buckets deliberately have no fallback:
585+
// - tomorrow: a future reminder is neither undated nor overdue, and hiding the
586+
// Tomorrow section is a reasonable way to say you don't want to see it yet.
587+
// - today's TIMED reminders whose time has not been reached: see the note in
588+
// getTimeBlockSectionData -- those are meant to stay hidden until they are due.
560589
if (undatedOverdueRemindersEnabled) {
561590
const overdueSectionVisible = Boolean(config.showOverdueSection)
562591
const fallbackItems: Array<TSectionItem> = []
@@ -569,6 +598,12 @@ export async function getRemindersGeneratedData(
569598
if (currentRemindersEnabled && !config.showYesterdaySection && !overdueSectionVisible && buckets.yesterdayItems.length > 0) {
570599
fallbackItems.push(...buckets.yesterdayItems)
571600
}
601+
// Untimed reminders due today have only ever had one home, the Today section,
602+
// so with that off they had nowhere to go at all. Same "Show Current Reminders"
603+
// gate as yesterday, for the same reason.
604+
if (currentRemindersEnabled && !config.showTodaySection && buckets.untimedTodayItems.length > 0) {
605+
fallbackItems.push(...buckets.untimedTodayItems)
606+
}
572607
if (fallbackItems.length > 0) {
573608
logDebug('getRemindersGeneratedData', `- REM fallback: adopting ${String(fallbackItems.length)} reminder(s) whose own section is off (overdueVisible=${String(overdueSectionVisible)} yesterdayVisible=${String(Boolean(config.showYesterdaySection))})`)
574609
undatedItems = sortReminderSectionItems(undatedItems.concat(fallbackItems))
@@ -577,6 +612,7 @@ export async function getRemindersGeneratedData(
577612

578613
const maxInSection = config.maxItemsToShowInSection ?? 24
579614
const totalUndatedCount = undatedItems.length
615+
logDebug('getRemindersGeneratedData', `- REM section will hold ${String(undatedItems.length)} reminder(s)`)
580616
if (totalUndatedCount > maxInSection) {
581617
undatedItems = undatedItems.slice(0, maxInSection)
582618
}

0 commit comments

Comments
 (0)