Improve operational UX, recovery flows, and action safety
Implement the approved UX review across Fleet, Betas, Library, deployments, and Settings. Add reviewed promotion and reconciliation plans, atomic queued-removal cancellation, recoverable beta drafts and launch inputs, accessible controls, and mobile improvements. Validated with 473 tests, a clean typecheck, production build, and isolated desktop/mobile browser walkthroughs. Include the review and implementation coverage notes.
This commit is contained in:
@@ -18,6 +18,27 @@ export interface Toast {
|
||||
}
|
||||
|
||||
export const toasts = writable<Toast[]>([]);
|
||||
export interface Notice extends Toast { createdAt: string; read: boolean; }
|
||||
export const notificationHistory = writable<Notice[]>([]);
|
||||
const historyKey = 'electric-cherry:notifications';
|
||||
|
||||
export function startNotificationHistory(): () => void {
|
||||
try {
|
||||
const saved: unknown = JSON.parse(sessionStorage.getItem(historyKey) ?? '[]');
|
||||
if (Array.isArray(saved)) {
|
||||
const valid = saved.filter((item): item is Notice => item && typeof item.id === 'number' && Number.isSafeInteger(item.id)
|
||||
&& typeof item.message === 'string' && ['info', 'success', 'warning', 'danger'].includes(item.variant)
|
||||
&& typeof item.createdAt === 'string' && Number.isFinite(Date.parse(item.createdAt))
|
||||
&& typeof item.read === 'boolean' && (!item.href || (typeof item.href === 'string' && item.href.startsWith('/') && !item.href.startsWith('//')))).slice(0, 50);
|
||||
notificationHistory.set(valid);
|
||||
nextId = Math.max(nextId, ...valid.map(item => item.id + 1));
|
||||
}
|
||||
} catch { /* Notifications still work when browser storage is unavailable. */ }
|
||||
return notificationHistory.subscribe(items => { try { sessionStorage.setItem(historyKey, JSON.stringify(items)); } catch { /* Optional persistence. */ } });
|
||||
}
|
||||
|
||||
export function markNotificationsRead() { notificationHistory.update(items => items.map(item => ({ ...item, read: true }))); }
|
||||
export function clearNotifications() { notificationHistory.set([]); }
|
||||
|
||||
let nextId = 1;
|
||||
|
||||
@@ -33,6 +54,9 @@ export function toast(
|
||||
href: opts.href
|
||||
};
|
||||
toasts.update((arr) => [...arr, t]);
|
||||
if (t.href || t.ttl === 0 || t.variant !== 'info') {
|
||||
notificationHistory.update(items => [{ ...t, createdAt: new Date().toISOString(), read: false }, ...items].slice(0, 50));
|
||||
}
|
||||
if (t.ttl > 0) {
|
||||
setTimeout(() => dismiss(t.id), t.ttl);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user