| 1 | import "./lib/compat"; |
| 2 | import { StrictMode } from "react"; |
| 3 | import { createRoot } from "react-dom/client"; |
| 4 | import App from "./App"; |
| 5 | import { ErrorBoundary } from "./components/ErrorBoundary"; |
| 6 | import { installGlobalCrashHandlers, installPerformancePressureMonitor } from "./lib/crash"; |
| 7 | import { installWailsNonFileDragErrorSuppression } from "./lib/bridge"; |
| 8 | import { installBreadcrumbConsoleHook } from "./lib/breadcrumbs"; |
| 9 | import { installMessageSelectionCopy } from "./lib/messageSelectionCopy"; |
| 10 | import { LocaleProvider, preloadDetectedLocale } from "./lib/i18n"; |
| 11 | import { ToastProvider } from "./lib/toast"; |
| 12 | import { initFontFamily } from "./lib/fontFamily"; |
| 13 | import { initTextSize } from "./lib/textSize"; |
| 14 | import { initTypographyPreferences } from "./lib/typographyPreferences"; |
| 15 | import { initTheme } from "./lib/theme"; |
| 16 | import { initConversationWidth } from "./lib/conversationWidth"; |
| 17 | import "./styles.css"; |
| 18 | |
| 19 | // Install first so startup/runtime failures paint a useful error instead of a |
| 20 | // featureless webview background, with the recent console trail attached. |
| 21 | installWailsNonFileDragErrorSuppression(); |
| 22 | installGlobalCrashHandlers(); |
| 23 | installBreadcrumbConsoleHook(); |
| 24 | installPerformancePressureMonitor(); |
| 25 | |
| 26 | // Apply the saved appearance (auto/light/dark) before the first paint. |
| 27 | function initTypographyPlatform() { |
| 28 | if (typeof document === "undefined" || typeof navigator === "undefined") return; |
| 29 | const params = new URLSearchParams(window.location.search); |
| 30 | const override = params.get("platform"); |
| 31 | const marker = `${navigator.platform} ${navigator.userAgent}`; |
| 32 | const platform = |
| 33 | override === "darwin" || override === "windows" || override === "linux" |
| 34 | ? override |
| 35 | : /Win/i.test(marker) |
| 36 | ? "windows" |
| 37 | : /Mac/i.test(marker) |
| 38 | ? "darwin" |
| 39 | : "linux"; |
| 40 | document.documentElement.setAttribute("data-platform", platform); |
| 41 | } |
| 42 | |
| 43 | initTypographyPlatform(); |
| 44 | initTheme(); |
| 45 | initConversationWidth(); |
| 46 | initTextSize(); |
| 47 | initFontFamily(); |
| 48 | initTypographyPreferences(); |
| 49 | |
| 50 | // Pre-warm font fallback stacks so the first frame doesn't flicker between the |
| 51 | // browser default font and the app's configured typeface. Inserting a hidden span |
| 52 | // with CJK + emoji + math glyphs forces the OS font subsystem to resolve and |
| 53 | // cache the fallback chains before React mounts. |
| 54 | function prewarmFontFallbacks() { |
| 55 | const span = document.createElement("span"); |
| 56 | span.style.cssText = "position:absolute;visibility:hidden;font-size:1px;pointer-events:none"; |
| 57 | span.textContent = "中文日本語한국어 математика 😀🎉✓⚠∑∏∫"; |
| 58 | document.body.appendChild(span); |
| 59 | // Force layout so the browser resolves font fallback chains. |
| 60 | void span.offsetHeight; |
| 61 | requestAnimationFrame(() => { |
| 62 | requestAnimationFrame(() => { |
| 63 | span.remove(); |
| 64 | }); |
| 65 | }); |
| 66 | } |
| 67 | prewarmFontFallbacks(); |
| 68 | |
| 69 | installMessageSelectionCopy(document); |
| 70 | |
| 71 | // Inside the Wails shell, suppress the webview's default right-click menu — its |
| 72 | // Reload / Back / Inspect entries are easy to hit by accident and can reset or |
| 73 | // navigate away from the app. Text inputs keep their native Cut/Copy/Paste menu. |
| 74 | // Left alone in a plain browser (pnpm dev) so devtools stay reachable. |
| 75 | if (typeof window !== "undefined" && window.runtime) { |
| 76 | window.addEventListener("contextmenu", (e) => { |
| 77 | const target = e.target as HTMLElement | null; |
| 78 | if (!target?.closest("input, textarea")) e.preventDefault(); |
| 79 | }); |
| 80 | } |
| 81 | |
| 82 | const root = document.getElementById("root"); |
| 83 | if (!root) throw new Error("missing #root"); |
| 84 | const rootElement = root; |
| 85 | |
| 86 | async function mountApp() { |
| 87 | try { |
| 88 | await preloadDetectedLocale(); |
| 89 | } catch (error) { |
| 90 | console.error("failed to preload desktop locale", error); |
| 91 | } |
| 92 | createRoot(rootElement).render( |
| 93 | <StrictMode> |
| 94 | <ErrorBoundary> |
| 95 | <LocaleProvider> |
| 96 | <ToastProvider> |
| 97 | <App /> |
| 98 | </ToastProvider> |
| 99 | </LocaleProvider> |
| 100 | </ErrorBoundary> |
| 101 | </StrictMode>, |
| 102 | ); |
| 103 | } |
| 104 | |
| 105 | void mountApp(); |
| 106 |