mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 06:19:49 +00:00
fix(web): toasts with an action or link stay ~10s (TASK-3973) (#2245)
A toast that offers an action button or a link now defaults to ACTIONABLE_DURATION (10s, the same as an error) when the caller passes no duration, so quick add's "Open" is still there when a person reaches for it. An explicit duration still wins. Hover and focus still pause the clock. Claude-Session: https://claude.ai/code/session_012tVNjc1RAdLdRN1ae77u4U Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,13 @@ const DEFAULT_DURATION = 3000;
|
||||
* duration; an explicit duration still wins.
|
||||
*/
|
||||
export const ERROR_DURATION = 10000;
|
||||
/**
|
||||
* A toast that offers something to do (an action button or a link) lingers as
|
||||
* long as an error (TASK-3973): at 3s the "Open" on a quick-add confirmation
|
||||
* was gone before a person could reach it. Applied when the caller passes no
|
||||
* duration; an explicit duration still wins.
|
||||
*/
|
||||
export const ACTIONABLE_DURATION = ERROR_DURATION;
|
||||
|
||||
/**
|
||||
* Test-surface kill switch for CROSS-ACTOR notification toasts (BUG-2334).
|
||||
@@ -110,7 +117,8 @@ function show(
|
||||
action?: ToastAction,
|
||||
opts: ShowOptions = {},
|
||||
): string {
|
||||
duration ??= type === 'error' ? ERROR_DURATION : DEFAULT_DURATION;
|
||||
duration ??=
|
||||
type === 'error' ? ERROR_DURATION : action || link ? ACTIONABLE_DURATION : DEFAULT_DURATION;
|
||||
const id = generateId();
|
||||
const toast: Toast = { id, message, type, duration, link, action };
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// TASK-2202 — errors linger, and a toast someone is reading or reaching for
|
||||
// does not vanish under them. Fake timers, so durations are exact.
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { toastStore, ERROR_DURATION } from './toast.svelte';
|
||||
import { toastStore, ERROR_DURATION, ACTIONABLE_DURATION } from './toast.svelte';
|
||||
import { announceCaptured } from '$lib/items/quickCapture';
|
||||
|
||||
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
|
||||
|
||||
const present = (id: string) => toastStore.toasts.some((t) => t.id === id);
|
||||
|
||||
@@ -36,6 +39,41 @@ describe('toast durations (TASK-2202)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('toasts that offer something to do linger (TASK-3973)', () => {
|
||||
const act = { label: 'Undo', onAction: () => {} };
|
||||
|
||||
it('a toast with an action and no duration stays for ACTIONABLE_DURATION', () => {
|
||||
expect(ACTIONABLE_DURATION).toBeGreaterThanOrEqual(8000);
|
||||
const id = toastStore.show('Archived 3 items', 'success', undefined, undefined, act);
|
||||
vi.advanceTimersByTime(ACTIONABLE_DURATION - 1);
|
||||
expect(present(id)).toBe(true);
|
||||
vi.advanceTimersByTime(1);
|
||||
expect(present(id)).toBe(false);
|
||||
});
|
||||
|
||||
it('a toast with only a link stays for ACTIONABLE_DURATION', () => {
|
||||
const id = toastStore.show('Imported DOC-4', 'info', undefined, '/w/docs/doc-4');
|
||||
vi.advanceTimersByTime(ACTIONABLE_DURATION - 1);
|
||||
expect(present(id)).toBe(true);
|
||||
vi.advanceTimersByTime(1);
|
||||
expect(present(id)).toBe(false);
|
||||
});
|
||||
|
||||
it('an explicit duration still wins over the actionable default', () => {
|
||||
const id = toastStore.show('Archived', 'success', 2000, '/w/x', act);
|
||||
vi.advanceTimersByTime(2000);
|
||||
expect(present(id)).toBe(false);
|
||||
});
|
||||
|
||||
it("quick add's Open toast survives 9s", () => {
|
||||
announceCaptured({ title: 'Buy milk', collection_prefix: 'TASK', item_number: 12 }, '/w/tasks/task-12');
|
||||
const toast = toastStore.toasts.find((t) => t.message === 'Added TASK-12');
|
||||
expect(toast?.action?.label).toBe('Open');
|
||||
vi.advanceTimersByTime(9000);
|
||||
expect(present(toast!.id), 'still up at 9s').toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toast hold (TASK-2202)', () => {
|
||||
it('a paused toast stays, and resumes with only what was left', () => {
|
||||
const id = toastStore.show('Saved', 'success', 3000);
|
||||
|
||||
Reference in New Issue
Block a user