fix(reminders): replace a journal or note reminder instead of adding a second - #1943
fix(reminders): replace a journal or note reminder instead of adding a second#1943h4yfans wants to merge 7 commits into
Conversation
|
React Doctor found 1 new issue in 1 file · 1 warning · score 84 / 100 (Needs work) · 1 fixed · vs 1 warning
Reviewed by React Doctor for commit |
The calendar widget refresh suite faked the clock onto 2026-08-31, the day it was written. use-today snapshots the local date into module scope at import and re-reads the wall clock for its first subscriber, so on every later day that fake clock arrives as a midnight rollover. todayCalendarRange moves, the useCalendarRange query key moves with it, and the widget fetches a second day during mount, which is the second getRange call the first test counted. Derive the fixture clock and its event hours from the real local date instead, so the mount no longer straddles a day boundary. Local date fields rather than a UTC instant, because far enough from UTC the two name different days.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Dismissing the one new warning as a diff-attribution artifact, with a disproof.
Same rule, same file, same line, without any of this branch's changes. Splitting |
Summary
A reader set a reminder on a journal entry with the "in one week" preset, then wanted a different hour. There was no way to change or remove it, so he opened the picker again and chose a custom time, expecting it to override. It did not. He ended up with two reminders on one entry, both firing.
Two defects sat behind that.
use-journal-reminders.tscalleduseCreateReminderunconditionally, so every pick appended a row.journal-reminder-button.tsxwired onlyonSelectintoReminderPicker, even though the picker has takenreminders,onEditandonDeletesince the task surface shipped, and even though the journal hook already exposeddeleteReminder. The tooltip could already count the extras (reminder.tooltipMore) while offering no way to remove one.note-reminder-button.tsxand the note page's own toolbar picker had the identical gap, so both are fixed here.A journal entry or a note now carries one reminder.
useSetOrReplaceReminderinhooks/use-set-or-replace-reminder.tsholds that rule once for both surfaces: given the id of the active reminder it updates that row, and withnullit creates. It reuses the existingreminders.updatechannel, so nothing inpackages/contractsormain/ipc/reminder-handlers.tschanged. Updating in place rather than deleting and recreating keeps the row id, which means sync sees an update and the calendar projection follows the same row;remindersService.updateReminderalready resetsstatusto pending and clearstriggeredAt/snoozedUntilwhenremindAtmoves, so replacing a snoozed reminder re-arms it. The note carried by the new pick is written through asnullwhen the picker comes back without one, so replacing clears a stale note instead of stranding it.The note surface merges the note's own reminders with its highlight reminders. Only the note's own next active reminder is replaceable there: moving a passage reminder from the note toolbar would silently reschedule something the user never opened. Highlight reminders stay in the list with their own remove button.
There is no migration. Entries that already collected duplicates keep every row, they still fire, and the picker now lists each one with its own remove button, which is how a user gets rid of them. Task reminders are deliberately untouched: that surface already had edit and remove, and its
+Naffordance treats several reminders on one task as intentional.Blast radius is the two reminder buttons, the note page's toolbar picker, the two hooks and two English locale files.
ReminderPickeritself is unchanged; it already rendered the management list when handed the props. Four test files that partially mock these hooks were extended with the newactiveRemindersandeditRemindermembers.Carries the test-only main fix from the calendar-widget PR until it lands; rebasing after that drops it.
Closes #1939
Release note
Changing the time on a journal or note reminder now moves the reminder you already had instead of adding a second one, and the picker lets you remove it.
Test plan
vitest --project rendereron the 4 touched specs, against origin/main sourcepnpm --filter @memry/desktop test:rendererhome/widgets/calendar-widget-refresh.test.tsx, failed identically with every source file of this branch reverted to origin/main, so it predates this work. The carried commit fixes it: that spec is now 6 passedpnpm --filter @memry/desktop test:mainpnpm --filter @memry/desktop typecheck:webpnpm --filter @memry/desktop typecheck:nodepnpm --filter @memry/desktop typecheck:testpnpm lintpnpm --filter @memry/desktop i18n:checkpnpm check:architecturepnpm check:contractspnpm docs:impact --base origin/main --strictpnpm docs:buildgit diff --checkpnpm ipc:generate && pnpm ipc:checkwas not run: no contract, preload, main IPC handler or generated binding changed. The replace path reuses the existingreminder:updatechannel.E2E,
tests/e2e/journal-reminder-edit.e2e.ts. It navigates to the journal, sets the "In 1 Week" preset through the real picker, reopens it, picks a custom day and 18:45 from the date and time pane, and reads the rows back overwindow.api.reminders.list. It asserts exactly one row remains, that its id is the preset's id, that its time moved, and that the local parts are the picked day at 18:45. Then it removes the reminder from the picker and asserts the row count is zero and the bell's accessible name is back to "Set reminder to revisit".Green with the fix,
1 passed (25.3s). Red with the seven changed source files checked out from origin/main:That is the dead end from the report: an entry with a reminder, and the picker offering no way to remove it. The duplicate half of the bug is pinned by the renderer specs, where reverting the fix turns 10 of them red.
Five mutations were run against the staged fix and each was killed: always create rather than replace (10 tests), dropping the note write-through on replace (1), removing
onDeletefrom the journal picker (2), replacing from the merged note and highlight list rather than the note's own (2), and replacing the raw first row instead of the next active one (3). The baseline returned 23 passed.I grepped
apps/desktop/tests/e2e/for specs asserting the picker layout. Onlyintegration.e2e.tsmentions reminders, and its selectors (add-reminder,reminder-picker,reminder-1hour) exist nowhere in the renderer; every assertion is behind anisVisible()guard and the tests end inexpect(true).toBe(true). It cannot be affected by this change.property-status-options.e2e.tsmatches[data-slot="picker-content"]but drives the status picker, and noPickerprimitive changed.