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
+59 -59
View File
@@ -21,104 +21,104 @@
* Security: T-05-24 — all copy is plain-text JSX children, no dangerouslySetInnerHTML.
*/
import { useEffect, useRef, useState } from 'react'
import { X, Bell, AlertCircle, Loader2 } from 'lucide-react'
import { usePushSubscription } from '../hooks/usePushSubscription.js'
import { InstructionSheet } from './InstructionSheet.js'
import { useEffect, useRef, useState } from 'react';
import { X, Bell, AlertCircle, Loader2 } from 'lucide-react';
import { usePushSubscription } from '../hooks/usePushSubscription.js';
import { InstructionSheet } from './InstructionSheet.js';
// CR-04: fetch VAPID key (from sessionStorage cache if available) for the
// tap-gated subscribe() path. Same logic as PushPermissionPrompt.
async function fetchVapidKeyForSettings(): 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;
}
}
interface SettingsSheetProps {
isOpen: boolean
onClose: () => void
isOpen: boolean;
onClose: () => void;
}
export function SettingsSheet({ isOpen, onClose }: SettingsSheetProps) {
const { subscribe, permission, isSubscribed, setEnabled } = usePushSubscription()
const [isTogglingOn, setIsTogglingOn] = useState(false)
const [instructionsOpen, setInstructionsOpen] = useState(false)
const { subscribe, permission, isSubscribed, setEnabled } = usePushSubscription();
const [isTogglingOn, setIsTogglingOn] = useState(false);
const [instructionsOpen, setInstructionsOpen] = useState(false);
// CR-04: pre-fetch the VAPID key into state so the toggle tap handler can call
// subscribe(registration, vapidKey) without any network await before pushManager.subscribe().
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 toggle
// tap handler has ZERO awaits between the user gesture and pushManager.subscribe().
const [swRegistration, setSwRegistration] = useState<ServiceWorkerRegistration | null>(null)
const closeButtonRef = useRef<HTMLButtonElement>(null)
const [swRegistration, setSwRegistration] = useState<ServiceWorkerRegistration | null>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null);
// Compute initial toggle on/off state per UI-SPEC toggle initial state rule:
// on when notificationsEnabled !== '0' AND permission === 'granted' AND isSubscribed
const isOn = permission === 'granted' && isSubscribed
const isOn = permission === 'granted' && isSubscribed;
// Escape key listener (CreateListSheet pattern)
useEffect(() => {
if (!isOpen) return
if (!isOpen) return;
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', onKeyDown)
return () => document.removeEventListener('keydown', onKeyDown)
}, [isOpen, onClose])
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [isOpen, onClose]);
// Focus close button on open (a11y)
useEffect(() => {
if (isOpen && closeButtonRef.current) {
closeButtonRef.current.focus()
closeButtonRef.current.focus();
}
}, [isOpen])
}, [isOpen]);
// CR-04: Pre-fetch the VAPID key while the sheet is open so the OS-dialog path
// (permission === 'default') has the key ready before the user taps.
useEffect(() => {
if (!isOpen) return
if (permission === 'denied') return
if (!isOpen) return;
if (permission === 'denied') return;
void fetchVapidKeyForSettings().then((key) => {
if (key) setVapidKey(key)
})
}, [isOpen, permission])
if (key) setVapidKey(key);
});
}, [isOpen, permission]);
// NEW-CR-01: Pre-resolve the ServiceWorkerRegistration into state so the toggle
// tap handler never needs to await navigator.serviceWorker.ready. Any await
// between the tap gesture and pushManager.subscribe() breaks iOS.
useEffect(() => {
if (!isOpen) return
if (permission === 'denied') return
if (!navigator.serviceWorker) return
if (!isOpen) return;
if (permission === 'denied') return;
if (!navigator.serviceWorker) return;
void navigator.serviceWorker.ready.then((reg) => {
setSwRegistration(reg)
})
}, [isOpen, permission])
setSwRegistration(reg);
});
}, [isOpen, permission]);
if (!isOpen) return null
if (!isOpen) return null;
const handleToggle = async () => {
if (permission === 'denied') return // no-op — show hint below
if (permission === 'denied') return; // no-op — show hint below
if (isOn) {
// on → off: unsubscribe
await setEnabled(false)
await setEnabled(false);
} else if (permission === 'granted') {
// off → on, permission already granted: silent subscribe
setIsTogglingOn(true)
setIsTogglingOn(true);
try {
await setEnabled(true)
await setEnabled(true);
} finally {
setIsTogglingOn(false)
setIsTogglingOn(false);
}
} else {
// off → on, permission 'default': needs tap-gated subscribe with OS dialog
@@ -126,21 +126,21 @@ export function SettingsSheet({ isOpen, onClose }: SettingsSheetProps) {
// NEW-CR-01: Both vapidKey AND swRegistration are pre-resolved into state
// (useEffects above). There is ZERO await between the tap and
// registration.pushManager.subscribe() — iOS gesture gate satisfied.
if (!vapidKey || !swRegistration) return // not ready — useEffects still resolving
const resolvedVapidKey = vapidKey
const resolvedRegistration = swRegistration
setIsTogglingOn(true)
if (!vapidKey || !swRegistration) return; // not ready — useEffects still resolving
const resolvedVapidKey = vapidKey;
const resolvedRegistration = swRegistration;
setIsTogglingOn(true);
try {
await subscribe(resolvedRegistration, resolvedVapidKey)
await subscribe(resolvedRegistration, resolvedVapidKey);
} catch {
// Permission denied by OS or error — permission state will update reactively
} finally {
setIsTogglingOn(false)
setIsTogglingOn(false);
}
}
}
};
const isDisabled = permission === 'denied'
const isDisabled = permission === 'denied';
return (
<>
@@ -289,7 +289,9 @@ export function SettingsSheet({ isOpen, onClose }: SettingsSheetProps) {
role="switch"
aria-checked={isOn}
aria-label={isOn ? 'FamilySync Notifications, on' : 'FamilySync Notifications, off'}
onClick={() => { void handleToggle() }}
onClick={() => {
void handleToggle();
}}
disabled={isDisabled}
style={{
// 44px touch target
@@ -391,9 +393,7 @@ export function SettingsSheet({ isOpen, onClose }: SettingsSheetProps) {
)}
</div>
{instructionsOpen && (
<InstructionSheet onClose={() => setInstructionsOpen(false)} />
)}
{instructionsOpen && <InstructionSheet onClose={() => setInstructionsOpen(false)} />}
</>
)
);
}