mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
fix(reminders): stop dismissed reminders from re-opening every 10s
An overdue scheduled reminder kept re-opening its modal every ~10s (the reminder worker's check interval) and on every app resume. Dismissing the dialog via backdrop / Escape / Android back runs none of the clear logic (ngOnDestroy only clears deadline reminders), so the worker re-emitted the still-active reminder and the module re-opened the modal indefinitely. On mobile this reads as a fully frozen app where no controls respond. Add a short in-memory UI cooldown: on a passive dismiss, scheduled reminders are suppressed from re-opening for 5 minutes. The reminder itself stays active — it re-nudges after the cooldown and on cold start — and explicit actions (snooze / done / add-to-today / reschedule) are unaffected. The cooldown is presentation-only; no synced state is touched. Related to #8551 (a dropped done-from-notification leaves a task overdue and active), the common way to reach this state on Android. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e5a6b1982b
commit
d1d6949f43
7 changed files with 213 additions and 15 deletions
56
e2e/tests/reminders/reminders-dismiss-loop.spec.ts
Normal file
56
e2e/tests/reminders/reminders-dismiss-loop.spec.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { expect, test } from '../../fixtures/test.fixture';
|
||||
import { addTaskWithReminder } from '../../utils/schedule-task-helper';
|
||||
|
||||
// Repro for: "had some problems where no controls were working" (Android, SuperSync).
|
||||
// Session log showed two OVERDUE scheduled reminders re-opening the reminder modal
|
||||
// every ~10s and on every app resume. Dismissing the modal via backdrop / Android
|
||||
// back button (== Escape) runs none of the dialog's clear logic — ngOnDestroy only
|
||||
// clears DEADLINE reminders, never scheduled ones — so the worker
|
||||
// (reminder.worker.ts CHECK_INTERVAL_DURATION = 10000) keeps re-emitting the overdue
|
||||
// reminder and reminder.module.ts re-opens the modal (its only guard is
|
||||
// openDialogs.length === 0). The modal repeatedly seizing the screen reads, on
|
||||
// device, as a fully frozen app where no controls respond.
|
||||
|
||||
const DIALOG = 'dialog-view-task-reminder';
|
||||
const DIALOG_TASK1 = `${DIALOG} .task:first-of-type`;
|
||||
const SCHEDULE_MAX_WAIT_TIME = 60000;
|
||||
// The reminder worker re-checks every 10s; wait out more than one full cycle.
|
||||
const WORKER_RECHECK_WINDOW = 13000;
|
||||
|
||||
test.describe('Reminders dismiss loop', () => {
|
||||
test('should not re-open a scheduled reminder after it is dismissed without acting', async ({
|
||||
page,
|
||||
workViewPage,
|
||||
testPrefix,
|
||||
}) => {
|
||||
test.setTimeout(SCHEDULE_MAX_WAIT_TIME + 60000);
|
||||
|
||||
await workViewPage.waitForTaskList();
|
||||
|
||||
// A scheduled (non-deadline) reminder due ~now: minute-granular time input
|
||||
// rounds it to the current minute, so it is overdue the instant it fires —
|
||||
// the exact state from the bug log.
|
||||
const taskTitle = `${testPrefix}-0 dismiss-loop task`;
|
||||
await addTaskWithReminder(page, workViewPage, taskTitle, Date.now() + 8000);
|
||||
|
||||
// The reminder fires and the modal appears.
|
||||
const dialog = page.locator(DIALOG);
|
||||
await dialog.waitFor({ state: 'visible', timeout: SCHEDULE_MAX_WAIT_TIME });
|
||||
await expect(page.locator(DIALOG_TASK1)).toContainText(taskTitle);
|
||||
|
||||
// The user dismisses it without acting (backdrop tap / Android back == Escape).
|
||||
await page.keyboard.press('Escape');
|
||||
await dialog.waitFor({ state: 'hidden', timeout: 10000 });
|
||||
|
||||
// EXPECTED (correct behaviour, currently fails): a reminder the user has
|
||||
// actively dismissed must not re-seize the screen on its own. With the bug the
|
||||
// worker re-emits within ~10s and the modal reopens, so this assertion fails
|
||||
// because the dialog reappears inside the re-check window.
|
||||
const reappeared = await dialog
|
||||
.waitFor({ state: 'visible', timeout: WORKER_RECHECK_WINDOW })
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
expect(reappeared).toBe(false);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue