Push could only reach one mobile device per user (a token file), quiet hours did not exist, and the web app had no service worker, so a closed tab never heard about finished work. This lands the shared interfaces first so onboarding can build on them: - push_subscriptions (web push endpoints and Expo tokens, one row per device) and user_notification_settings (quiet hours as minutes in the profile time zone, nudges, a per-day nudge counter), with CHECK constraints. - NotificationSettingsSchema and web push contracts, notifications.settings.get/update and notifications.webPush.config/subscribe/unsubscribe (owner-scoped, https endpoints only). - getNotificationSettings/updateNotificationSettings plus the policy and nudge-cap helpers. - VAPID keys from VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY or generated once and stored encrypted, and an SSRF-guarded web push sender built on the web-push package. - The web app links its manifest, registers a push-only service worker where supported (secure context, not the desktop app) and exposes webPushSupported/webPushEnabled/enableWebPush/ disableWebPush. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { StrictMode, useEffect, useLayoutEffect } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { BrowserRouter } from "react-router-dom";
|
|
import { App } from "./App";
|
|
import { DesktopUpdatesProvider } from "./components/DesktopUpdates";
|
|
import { I18nBootstrap } from "./components/I18nBootstrap";
|
|
import { applyUiDirection } from "./lib/apply-ui-direction";
|
|
import { markAfterPaint, markOnce } from "./lib/performance";
|
|
import { installPreloadRecovery } from "./lib/preload-recovery";
|
|
import { applyUiAppearance, watchSystemAppearance } from "./lib/ui-appearance";
|
|
import { resolveUiLocale } from "./lib/ui-locale";
|
|
import { registerPushServiceWorker } from "./lib/web-push";
|
|
import "./styles.css";
|
|
|
|
markOnce("rk:renderer:module-evaluated");
|
|
try {
|
|
// Set by the inline boot script in index.html before it reloads once.
|
|
sessionStorage.removeItem("rakazo:boot-reload");
|
|
} catch {}
|
|
installPreloadRecovery();
|
|
applyUiDirection(resolveUiLocale());
|
|
applyUiAppearance();
|
|
void registerPushServiceWorker();
|
|
|
|
function PerformanceProbe() {
|
|
useLayoutEffect(() => {
|
|
markOnce("rk:renderer:first-react-commit");
|
|
markAfterPaint("rk:renderer:first-react-painted");
|
|
}, []);
|
|
return null;
|
|
}
|
|
|
|
function AppearanceSync() {
|
|
useEffect(() => watchSystemAppearance(), []);
|
|
return null;
|
|
}
|
|
|
|
createRoot(document.getElementById("root")!).render(
|
|
<StrictMode>
|
|
<PerformanceProbe />
|
|
<AppearanceSync />
|
|
<I18nBootstrap>
|
|
<BrowserRouter>
|
|
<DesktopUpdatesProvider>
|
|
<App />
|
|
</DesktopUpdatesProvider>
|
|
</BrowserRouter>
|
|
</I18nBootstrap>
|
|
</StrictMode>,
|
|
);
|