Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions apps/web/src/appInit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { ClientSettings } from "@t3tools/contracts/settings";
import { createBrowserHistory, createHashHistory } from "@tanstack/react-router";

import { applyAppearanceContrast } from "./appearanceContrast";
import { applyAppearanceFontVariables } from "./appearanceFonts";
import { APP_BASE_NAME, APP_DISPLAY_NAME, APP_STAGE_LABEL } from "./branding";
import { resolveServerBackedAppDisplayName } from "./branding.logic";
import { isElectron } from "./env";
import { getClientSettings, subscribeClientSettings } from "./hooks/useSettings";
import {
syncDocumentElectronPlatformClasses,
syncDocumentWindowControlsOverlayClass,
} from "./lib/windowControlsOverlay";
import { prepareProviderAuthDelivery } from "./providerAuthDelivery";
import { getRouter, type AppRouter } from "./router";
import { appAtomRegistry } from "./rpc/atomRegistry";
import { primaryServerConfigAtom } from "./state/server";

/** Writes appearance settings onto the document, touching only the groups that changed. */
function startClientAppearance(): void {
const root = document.documentElement;
let applied: ClientSettings | null = null;
const apply = () => {
const settings = getClientSettings();
const previous = applied;
applied = settings;
if (previous?.diffColorScheme !== settings.diffColorScheme) {
root.dataset.diffColorScheme = settings.diffColorScheme;
}
if (previous?.chatWidth !== settings.chatWidth) {
root.dataset.chatWidth = settings.chatWidth;
}
if (previous?.redesignEnabled !== settings.redesignEnabled) {
root.toggleAttribute("data-redesign", settings.redesignEnabled);
}
if (previous?.appearanceContrast !== settings.appearanceContrast) {
applyAppearanceContrast(root, settings.appearanceContrast);
}
if (previous?.glassOpacity !== settings.glassOpacity) {
root.style.setProperty("--glass-opacity", `${settings.glassOpacity}%`);
if (settings.glassOpacity === 100) {
root.style.setProperty("--glass-blur", "0px");
} else {
root.style.removeProperty("--glass-blur");
}
}
if (
previous?.fontFamilySans !== settings.fontFamilySans ||
previous.fontFamilyCode !== settings.fontFamilyCode ||
previous.fontFamilyComposer !== settings.fontFamilyComposer ||
previous.fontSizeInterface !== settings.fontSizeInterface ||
previous.fontSizePrompt !== settings.fontSizePrompt ||
previous.fontSizeCode !== settings.fontSizeCode ||
previous.fontSmoothing !== settings.fontSmoothing
) {
applyAppearanceFontVariables(root, {
sans: settings.fontFamilySans,
code: settings.fontFamilyCode,
composer: settings.fontFamilyComposer,
sizeInterface: settings.fontSizeInterface,
sizePrompt: settings.fontSizePrompt,
sizeCode: settings.fontSizeCode,
smoothing: settings.fontSmoothing,
});
}
};
subscribeClientSettings(apply);
apply();
}

function startDocumentTitle(): void {
appAtomRegistry.subscribe(
primaryServerConfigAtom,
(config) => {
document.title = resolveServerBackedAppDisplayName({
baseName: APP_BASE_NAME,
fallbackDisplayName: APP_DISPLAY_NAME,
fallbackStageLabel: APP_STAGE_LABEL,
primaryServerVersion: config?.environment.serverVersion ?? null,
});
},
{ immediate: true },
);
}

/**
* Starts every app-lifetime service once, before the first render, and returns the router.
* They run for the life of the page, so their disposers are dropped here.
*/
export function startAppServices(): AppRouter {
// Ahead of the history: the provider auth code must leave the address before anything reads it.
prepareProviderAuthDelivery();

// Electron loads the app from a file-backed shell, so hash history avoids path resolution issues.
const router = getRouter(isElectron ? createHashHistory() : createBrowserHistory());

if (isElectron) {
syncDocumentElectronPlatformClasses(navigator.platform);
syncDocumentWindowControlsOverlayClass();
}

startClientAppearance();
startDocumentTitle();

return router;
}
2 changes: 1 addition & 1 deletion apps/web/src/hooks/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function setClientSettingsHydrationStatus(nextStatus: ClientSettingsHydrationSta
emitClientSettingsHydrationChange();
}

function subscribeClientSettings(listener: () => void): () => void {
export function subscribeClientSettings(listener: () => void): () => void {
clientSettingsListeners.add(listener);
void hydrateClientSettings().catch(() => undefined);
return () => {
Expand Down
22 changes: 3 additions & 19 deletions apps/web/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { createHashHistory, createBrowserHistory } from "@tanstack/react-router";

import "@fontsource-variable/inter";
import "./index.css";

import { prepareProviderAuthDelivery } from "./providerAuthDelivery";
import { isElectron } from "./env";
import { hasCloudPublicConfig } from "./cloud/publicConfig";
import { getRouter } from "./router";
import {
syncDocumentElectronPlatformClasses,
syncDocumentWindowControlsOverlayClass,
} from "./lib/windowControlsOverlay";
import { isElectron } from "./env";
import { startAppServices } from "./appInit";
import { AppRoot } from "./AppRoot";
import { clearChunkReloadGuard, reloadOnceForChunkLoadError } from "./lib/chunkReloadGuard";

prepareProviderAuthDelivery();

// Electron loads the app from a file-backed shell, so hash history avoids path resolution issues.
const history = isElectron ? createHashHistory() : createBrowserHistory();

const router = getRouter(history);

if (isElectron) {
syncDocumentElectronPlatformClasses(navigator.platform);
syncDocumentWindowControlsOverlayClass();
}
const router = startAppServices();

const clerkPublishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string | undefined;

Expand Down
105 changes: 1 addition & 104 deletions apps/web/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ import {
import { Check, Copy } from "lucide";
import { useEffect, useEffectEvent, useRef, useState } from "react";

import { APP_BASE_NAME, APP_DISPLAY_NAME, APP_STAGE_LABEL, APP_VERSION } from "../branding";
import { resolveServerBackedAppDisplayName } from "../branding.logic";
import { APP_DISPLAY_NAME, APP_VERSION } from "../branding";
import { AppSidebarLayout } from "../components/AppSidebarLayout";
import { CommandPalette } from "../components/CommandPalette";
import { CustomSnoozeDialogHost } from "../components/CustomSnoozeDialog";
Expand Down Expand Up @@ -56,8 +55,6 @@ import {
} from "../components/ui/toast";
import { isElectron } from "../env";
import { cn } from "../lib/utils";
import { applyAppearanceFontVariables } from "~/appearanceFonts";
import { applyAppearanceContrast } from "~/appearanceContrast";
import { useClientSettings } from "../hooks/useSettings";
import {
deriveLogicalProjectKeyFromSettings,
Expand Down Expand Up @@ -170,7 +167,6 @@ function RootRouteView() {
if (pathname === "/pair" || pathname === "/connect" || pathname === "/connect-agent") {
return (
<>
<DocumentTitleSync />
<BrowserChromeThemeSync key={pathname} />
<Outlet />
</>
Expand All @@ -181,7 +177,6 @@ function RootRouteView() {
if (hostedPromptLinkLanding !== null) {
return (
<>
<DocumentTitleSync />
<BrowserChromeThemeSync key={pathname} />
{hostedPromptLinkLanding}
</>
Expand All @@ -192,12 +187,8 @@ function RootRouteView() {
return (
<ToastProvider>
<AnchoredToastProvider>
<DocumentTitleSync />
<BrowserChromeThemeSync key={pathname} />
<ContrastAppearanceSync />
<EnvironmentThemeSync />
<GlassAppearanceSync />
<FontAppearanceSync />
<ProviderAuthCallbackCoordinator />
<CustomSnoozeDialogHost />
<GuestShareDialogHost />
Expand All @@ -214,7 +205,6 @@ function RootRouteView() {
if (authGateState.status !== "authenticated" && authGateState.status !== "hosted-static") {
return (
<>
<DocumentTitleSync />
<BrowserChromeThemeSync key={pathname} />
<Outlet />
</>
Expand All @@ -232,12 +222,8 @@ function RootRouteView() {
return (
<ToastProvider>
<AnchoredToastProvider>
<DocumentTitleSync />
<BrowserChromeThemeSync key={pathname} />
<ContrastAppearanceSync />
<EnvironmentThemeSync />
<GlassAppearanceSync />
<FontAppearanceSync />
<ProviderAuthCallbackCoordinator />
<ChatGptWelcomeCoordinator />
<FirstRunGate
Expand Down Expand Up @@ -285,78 +271,6 @@ function EnvironmentThemeSync() {
return null;
}

function ContrastAppearanceSync() {
const appearanceContrast = useClientSettings((settings) => settings.appearanceContrast);
const diffColorScheme = useClientSettings((settings) => settings.diffColorScheme);

useEffect(() => {
document.documentElement.dataset.diffColorScheme = diffColorScheme;
}, [diffColorScheme]);

const chatWidth = useClientSettings((settings) => settings.chatWidth);
const redesignEnabled = useClientSettings((settings) => settings.redesignEnabled);
useEffect(() => {
document.documentElement.toggleAttribute("data-redesign", redesignEnabled);
}, [redesignEnabled]);
useEffect(() => {
document.documentElement.dataset.chatWidth = chatWidth;
}, [chatWidth]);

useEffect(() => {
applyAppearanceContrast(document.documentElement, appearanceContrast);
}, [appearanceContrast]);

return null;
}

function GlassAppearanceSync() {
const glassOpacity = useClientSettings((settings) => settings.glassOpacity);

useEffect(() => {
const style = document.documentElement.style;
style.setProperty("--glass-opacity", `${glassOpacity}%`);
if (glassOpacity === 100) {
style.setProperty("--glass-blur", "0px");
} else {
style.removeProperty("--glass-blur");
}
}, [glassOpacity]);

return null;
}

function FontAppearanceSync() {
const fontFamilySans = useClientSettings((settings) => settings.fontFamilySans);
const fontFamilyCode = useClientSettings((settings) => settings.fontFamilyCode);
const fontFamilyComposer = useClientSettings((settings) => settings.fontFamilyComposer);
const fontSizeInterface = useClientSettings((settings) => settings.fontSizeInterface);
const fontSizePrompt = useClientSettings((settings) => settings.fontSizePrompt);
const fontSizeCode = useClientSettings((settings) => settings.fontSizeCode);
const fontSmoothing = useClientSettings((settings) => settings.fontSmoothing);

useEffect(() => {
applyAppearanceFontVariables(document.documentElement, {
sans: fontFamilySans,
code: fontFamilyCode,
composer: fontFamilyComposer,
sizeInterface: fontSizeInterface,
sizePrompt: fontSizePrompt,
sizeCode: fontSizeCode,
smoothing: fontSmoothing,
});
}, [
fontFamilyCode,
fontFamilyComposer,
fontFamilySans,
fontSizeCode,
fontSizeInterface,
fontSizePrompt,
fontSmoothing,
]);

return null;
}

// Each route paints its own surface for browser chrome to sample.
function BrowserChromeThemeSync() {
useEffect(() => {
Expand All @@ -370,23 +284,6 @@ function BrowserChromeThemeSync() {
return null;
}

function DocumentTitleSync() {
const primaryServerVersion =
useAtomValue(primaryServerConfigAtom)?.environment.serverVersion ?? null;
const title = resolveServerBackedAppDisplayName({
baseName: APP_BASE_NAME,
fallbackDisplayName: APP_DISPLAY_NAME,
fallbackStageLabel: APP_STAGE_LABEL,
primaryServerVersion,
});

useEffect(() => {
document.title = title;
}, [title]);

return null;
}

function HostedStaticEnvironmentBootstrap() {
const { environments } = useEnvironments();
const activeEnvironmentId = useActiveEnvironmentId();
Expand Down
Loading