feat(reminders): view and cancel a message's existing reminders

There was no way to see or cancel a reminder once set (removeReminder was only
called by the fire-and-forget monitor), and addReminder didn't dedupe, so a
message could silently accumulate duplicate reminders. The Remind Me dialog now
lists the reminders already set on that message (soonest first) each with a
cancel button.

- New shared, tested formatFriendlyDateTime(ts, now?) in utils/datetimeInput.ts
  (Today/Tomorrow/date + time).
- Per-row cancel busy-guard; inline "Could not cancel" on failure.

Also applies two nits from the custom-time review: focus the date input when the
custom picker is revealed, and clear the error when editing date/time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 15:51:11 -04:00
co-authored by Claude Opus 4.8
parent b6725a6ee3
commit 7d67cdeef0
4 changed files with 123 additions and 5 deletions
+26 -1
View File
@@ -1,6 +1,11 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { toLocalDate, toLocalTime, parseLocalDateTime } from './datetimeInput';
import {
toLocalDate,
toLocalTime,
parseLocalDateTime,
formatFriendlyDateTime,
} from './datetimeInput';
// All helpers work in the LOCAL timezone. We construct Dates with the local
// component constructor so the assertions hold regardless of where tests run.
@@ -43,3 +48,23 @@ test('parseLocalDateTime: valid input yields the local wall-clock time', () => {
assert.equal(parsed.getHours(), 9);
assert.equal(parsed.getMinutes(), 3);
});
test('formatFriendlyDateTime: uses Today/Tomorrow/date prefixes', () => {
const now = new Date(2026, 0, 5, 10, 0).getTime();
const laterToday = new Date(2026, 0, 5, 15, 30).getTime();
const tomorrow = new Date(2026, 0, 6, 9, 0).getTime();
const nextWeek = new Date(2026, 0, 12, 9, 0).getTime();
assert.ok(formatFriendlyDateTime(laterToday, now).startsWith('Today at '));
assert.ok(formatFriendlyDateTime(tomorrow, now).startsWith('Tomorrow at '));
const other = formatFriendlyDateTime(nextWeek, now);
assert.ok(!other.startsWith('Today'));
assert.ok(!other.startsWith('Tomorrow'));
assert.ok(other.includes(' at '));
});
test('formatFriendlyDateTime: Tomorrow rolls over month/year boundaries', () => {
const nye = new Date(2026, 11, 31, 23, 0).getTime();
const jan1 = new Date(2027, 0, 1, 9, 0).getTime();
assert.ok(formatFriendlyDateTime(jan1, nye).startsWith('Tomorrow at '));
});