style(13-03): apply Prettier formatting across repo

Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
Lucas Berger
2026-06-11 20:35:18 -04:00
parent 4bc0445173
commit 982438dc10
398 changed files with 19050 additions and 16382 deletions
@@ -17,26 +17,26 @@
* the pushManager.subscribe() call satisfies the iOS user-gesture requirement.
*/
import { useState, useEffect, useId } from 'react'
import { Bell, Loader2, X } from 'lucide-react'
import { usePushSubscription } from '../hooks/usePushSubscription.js'
import { useState, useEffect, useId } from 'react';
import { Bell, Loader2, X } from 'lucide-react';
import { usePushSubscription } from '../hooks/usePushSubscription.js';
// fetchVapidKey is the internal helper; we import it directly via the module
// rather than re-exporting it through usePushSubscription, since we need to
// store the resolved key in state (not just warm the cache).
async function fetchVapidKeyForPrompt(): Promise<string | null> {
try {
const cached = sessionStorage.getItem('vapidPublicKey')
if (cached) return cached
const res = await fetch('/api/push/vapid-public-key', { credentials: 'include' })
if (!res.ok) return null
const data = (await res.json()) as { publicKey: string }
const cached = sessionStorage.getItem('vapidPublicKey');
if (cached) return cached;
const res = await fetch('/api/push/vapid-public-key', { credentials: 'include' });
if (!res.ok) return null;
const data = (await res.json()) as { publicKey: string };
if (data.publicKey) {
sessionStorage.setItem('vapidPublicKey', data.publicKey)
sessionStorage.setItem('vapidPublicKey', data.publicKey);
}
return data.publicKey ?? null
return data.publicKey ?? null;
} catch {
return null
return null;
}
}
@@ -46,22 +46,22 @@ function isInstalled(): boolean {
return (
window.matchMedia('(display-mode: standalone)').matches ||
(navigator as unknown as { standalone?: boolean }).standalone === true
)
);
}
// ── localStorage guards ────────────────────────────────────────────────────
function readDismissed(): boolean {
try {
return localStorage.getItem('pushPermissionDismissed') === '1'
return localStorage.getItem('pushPermissionDismissed') === '1';
} catch {
return false
return false;
}
}
function persistDismissed(): void {
try {
localStorage.setItem('pushPermissionDismissed', '1')
localStorage.setItem('pushPermissionDismissed', '1');
} catch {
// Private mode / storage disabled — ignore
}
@@ -71,66 +71,66 @@ function persistDismissed(): void {
interface PushPermissionPromptProps {
/** Optional callback after the prompt is closed (granted or dismissed) */
onClose?: () => void
onClose?: () => void;
}
export function PushPermissionPrompt({ onClose }: PushPermissionPromptProps) {
const [dismissed, setDismissed] = useState<boolean>(readDismissed)
const [installed, setInstalled] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [dismissed, setDismissed] = useState<boolean>(readDismissed);
const [installed, setInstalled] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// CR-04: Pre-fetch the VAPID key into state so the tap handler can call
// subscribe(registration, vapidKey) without any await before pushManager.subscribe().
// Button is disabled until the key is ready (null = not yet loaded).
const [vapidKey, setVapidKey] = useState<string | null>(null)
const [vapidKey, setVapidKey] = useState<string | null>(null);
// NEW-CR-01: Pre-resolve the ServiceWorkerRegistration into state so the tap
// handler has ZERO awaits between the user gesture and pushManager.subscribe().
// Any await (including navigator.serviceWorker.ready) between the tap and
// pushManager.subscribe() breaks the iOS user-gesture requirement.
const [swRegistration, setSwRegistration] = useState<ServiceWorkerRegistration | null>(null)
const headingId = useId()
const [swRegistration, setSwRegistration] = useState<ServiceWorkerRegistration | null>(null);
const headingId = useId();
const { subscribe, permission } = usePushSubscription()
const { subscribe, permission } = usePushSubscription();
useEffect(() => {
setInstalled(isInstalled())
}, [])
setInstalled(isInstalled());
}, []);
// Pre-fetch the VAPID key into state while the prompt is visible.
// CR-04: We store the resolved key in state (not just sessionStorage) so the
// tap handler has synchronous access — no network await inside the tap path.
useEffect(() => {
if (!installed) return
if (permission !== 'default') return
if (dismissed) return
if (!installed) return;
if (permission !== 'default') return;
if (dismissed) return;
void fetchVapidKeyForPrompt().then((key) => {
if (key) setVapidKey(key)
})
}, [installed, permission, dismissed])
if (key) setVapidKey(key);
});
}, [installed, permission, dismissed]);
// NEW-CR-01: Pre-resolve the ServiceWorkerRegistration in a useEffect so the
// tap handler never has to await navigator.serviceWorker.ready.
// navigator.serviceWorker.ready resolves once the SW is active; doing this
// eagerly means the result is in state before the user can tap the button.
useEffect(() => {
if (!installed) return
if (permission !== 'default') return
if (dismissed) return
if (!navigator.serviceWorker) return
if (!installed) return;
if (permission !== 'default') return;
if (dismissed) return;
if (!navigator.serviceWorker) return;
void navigator.serviceWorker.ready.then((reg) => {
setSwRegistration(reg)
})
}, [installed, permission, dismissed])
setSwRegistration(reg);
});
}, [installed, permission, dismissed]);
// Don't render when: not installed, already granted/denied, or dismissed
if (!installed) return null
if (permission !== 'default') return null
if (dismissed) return null
if (!installed) return null;
if (permission !== 'default') return null;
if (dismissed) return null;
function handleDismiss() {
persistDismissed()
setDismissed(true)
onClose?.()
persistDismissed();
setDismissed(true);
onClose?.();
}
// onClick handler — subscribe() called synchronously (iOS user-gesture requirement).
@@ -138,34 +138,31 @@ export function PushPermissionPrompt({ onClose }: PushPermissionPromptProps) {
// There is ZERO await between the tap gesture and registration.pushManager.subscribe()
// inside subscribe() — the iOS gesture gate is fully satisfied.
function handleEnableClick() {
if (loading || !vapidKey || !swRegistration) return
setLoading(true)
setError(null)
if (loading || !vapidKey || !swRegistration) return;
setLoading(true);
setError(null);
// Capture both synchronously — no await in this scope before subscribe().
const resolvedVapidKey = vapidKey
const resolvedRegistration = swRegistration
const resolvedVapidKey = vapidKey;
const resolvedRegistration = swRegistration;
void (async () => {
try {
await subscribe(resolvedRegistration, resolvedVapidKey)
await subscribe(resolvedRegistration, resolvedVapidKey);
// On success: close the prompt (permission is now 'granted')
setLoading(false)
onClose?.()
setLoading(false);
onClose?.();
} catch (err) {
setLoading(false)
if (
err instanceof Error &&
err.name === 'NotAllowedError'
) {
setLoading(false);
if (err instanceof Error && err.name === 'NotAllowedError') {
// User denied in the native browser dialog — close the sheet
setDismissed(true)
onClose?.()
setDismissed(true);
onClose?.();
} else {
setError('Something went wrong. Please try again.')
setError('Something went wrong. Please try again.');
}
}
})()
})();
}
return (
@@ -250,11 +247,7 @@ export function PushPermissionPrompt({ onClose }: PushPermissionPromptProps) {
textAlign: 'center',
}}
>
<Bell
size={40}
aria-hidden="true"
style={{ color: 'var(--color-member-0, #4A90D9)' }}
/>
<Bell size={40} aria-hidden="true" style={{ color: 'var(--color-member-0, #4A90D9)' }} />
<p
style={{
margin: 0,
@@ -353,7 +346,6 @@ export function PushPermissionPrompt({ onClose }: PushPermissionPromptProps) {
</button>
</div>
</div>
</div>
)
);
}