[feat](client): surface expiry, endings, and device health in the app

The alert detail screen now creates with a lifetime (1/3/7/14 days) and shows expiresAt, lists subscriber rows by their non-identifying fields, and renders each ending (delivered, expired, disarmed, deleted) as a terminal state instead of a stale alert. Settings configures the single instance origin and reports the real readiness/listening checklist; the trigger pad keeps its 1200ms hold, and time helpers format the deadline and last-seen values.
This commit is contained in:
2026-10-19 02:34:20 +01:00
parent dcfc9a5df8
commit 7c4788afba
6 changed files with 421 additions and 141 deletions
+28
View File
@@ -40,6 +40,34 @@ export function relativeSince(value: string | null | undefined, now: number): st
return date ? relativeTime(date, now) : null;
}
/**
* Time left before an alert self-destructs, e.g. `Ends in 6d 4h` / `Ends in
* 12m`. Null when there is no deadline or it cannot be parsed; a deadline in
* the past renders `Ended` (the service will 404 from then on).
*/
export function remainingLifetime(expiresAt: string | null | undefined, now: number): string | null {
const date = parseServerTime(expiresAt);
if (!date) return null;
const left = date.getTime() - now;
if (left <= 0) return 'Ended';
if (left < MINUTE) return 'Ends in under a minute';
if (left < HOUR) return `Ends in ${Math.floor(left / MINUTE)}m`;
if (left < DAY) {
const hours = Math.floor(left / HOUR);
const minutes = Math.floor((left % HOUR) / MINUTE);
return minutes ? `Ends in ${hours}h ${minutes}m` : `Ends in ${hours}h`;
}
const days = Math.floor(left / DAY);
const hours = Math.floor((left % DAY) / HOUR);
return hours ? `Ends in ${days}d ${hours}h` : `Ends in ${days}d`;
}
/** True when the deadline is present and already past. */
export function isPastDeadline(expiresAt: string | null | undefined, now: number): boolean {
const date = parseServerTime(expiresAt);
return !!date && date.getTime() <= now;
}
/** Local wall-clock time for the accessibility label of a relative stamp. */
export function absoluteLocal(date: Date): string {
const hh = String(date.getHours()).padStart(2, '0');