598 lines · 20.2 KB · TypeScript
RawDownload
import { type ServerLifecycleWelcomePayload } from "@t3tools/contracts";import { scopedProjectKey, scopeProjectRef } from "@t3tools/client-runtime/environment";import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";import {  Outlet,  redirect,  createRootRoute,  type ErrorComponentProps,  useLocation,  useNavigate,} from "@tanstack/react-router";import { CheckIcon, CopyIcon } from "lucide-react";import { useEffect, useEffectEvent, useMemo, useRef, useState } from "react"; import { APP_BASE_NAME, APP_DISPLAY_NAME, APP_STAGE_LABEL, APP_VERSION } from "../branding";import { resolveServerBackedAppDisplayName } from "../branding.logic";import { AppSidebarLayout } from "../components/AppSidebarLayout";import { CommandPalette } from "../components/CommandPalette";import { ConfirmDialogHost } from "../components/ConfirmDialogHost";import { FirstRunGate } from "../components/onboarding/FirstRunGate";import { ConnectOnboardingDialog } from "../components/cloud/ConnectOnboardingDialog";import { RelayClientInstallDialog } from "../components/cloud/RelayClientInstallDialog";import { SshPasswordPromptDialog } from "../components/desktop/SshPasswordPromptDialog";import { SnapShotCoordinator } from "../components/desktop/SnapShotCoordinator";import { DesktopAppActivationCoordinator } from "../components/desktop/DesktopAppActivationCoordinator";import { ProviderUpdateLaunchNotification } from "../components/ProviderUpdateLaunchNotification";import { SlowRpcRequestToastCoordinator } from "../components/SlowRpcRequestToastCoordinator";import { ThemeEditorHost } from "../components/settings/ThemeEditorHost";import { useCopyToClipboard } from "../hooks/useCopyToClipboard";import { useDefaultThemeAdoption } from "../hooks/useDefaultTheme";import { useEnvironmentThemeSync } from "../hooks/useEnvironmentTheme";import { Button } from "../components/ui/button";import {  AnchoredToastProvider,  stackedThreadToast,  ToastProvider,  toastManager,} from "../components/ui/toast";import { resolveAndPersistPreferredEditor } from "../editorPreferences";import { applyAppearanceFontVariables } from "~/appearanceFonts";import { applyAppearanceContrast } from "~/appearanceContrast";import { useClientSettings } from "../hooks/useSettings";import { PlanAgentSelectionHeal } from "../planAgentSelectionHeal";import {  deriveLogicalProjectKeyFromSettings,  derivePhysicalProjectKeyFromPath,  selectProjectGroupingSettings,} from "../logicalProject";import { useUiStateStore } from "../uiStateStore";import { syncBrowserChromeTheme } from "../hooks/useTheme";import { configureClientTracing } from "../observability/clientTracing";import { resolveInitialServerAuthGateState } from "../environments/primary";import { hasHostedPairingRequest, isHostedStaticApp } from "../hostedPairing";import { shellEnvironment } from "../state/shell";import { useAtomValue } from "@effect/atom-react";import { useAtomCommand } from "../state/use-atom-command";import { useEnvironments, usePrimaryEnvironment } from "../state/environments";import {  primaryServerConfigAtom,  primaryServerConfigEventAtom,  primaryServerWelcomeAtom,} from "../state/server";import { readProject, setActiveEnvironmentId, useActiveEnvironmentId } from "../state/entities";import {  createKeybindingsUpdateToastController,  type KeybindingsUpdateToastController,} from "../components/KeybindingsUpdateToast.logic"; import { getDesktopSnapShotBridge } from "../lib/desktopSnapShot";import { shouldResumeSnapShotSetupOnStartup } from "../lib/snapShotSetupResume"; export const Route = createRootRoute({  beforeLoad: async ({ location }) => {    if (location.pathname === "/pair" && hasHostedPairingRequest(new URL(window.location.href))) {      return {        authGateState: {          status: "hosted-pairing",        } as const,      };    }     if (isHostedStaticApp(new URL(window.location.href))) {      return {        authGateState: {          status: "hosted-static",        } as const,      };    }     const authGateState = await resolveInitialServerAuthGateState();    if (      authGateState.status === "authenticated" &&      getDesktopSnapShotBridge() &&      shouldResumeSnapShotSetupOnStartup() &&      location.pathname !== "/settings/snap-shot"    ) {      throw redirect({ to: "/settings/snap-shot", replace: true });    }    return {      authGateState,    };  },  component: RootRouteView,  errorComponent: RootRouteErrorView,  head: () => ({    meta: [{ name: "title", content: APP_DISPLAY_NAME }],  }),}); function RootRouteView() {  const pathname = useLocation({ select: (location) => location.pathname });  const { authGateState } = Route.useRouteContext();  const primaryEnvironmentAuthenticated = authGateState.status === "authenticated";  const returningFromWelcomeRef = useRef(pathname === "/welcome");   useEffect(() => {    if (pathname === "/welcome") {      returningFromWelcomeRef.current = true;    }  }, [pathname]);   useEffect(() => {    const frame = window.requestAnimationFrame(() => {      syncBrowserChromeTheme();    });    return () => {      window.cancelAnimationFrame(frame);    };  }, [pathname]);   if (pathname === "/pair" || pathname === "/connect" || pathname.startsWith("/connect/")) {    return (      <>        <DocumentTitleSync />        <Outlet />      </>    );  }   // Show onboarding over the workspace, keeping automatic thread navigation  // and other startup dialogs suspended until setup finishes.  if (pathname === "/welcome") {    return (      <ToastProvider>        <AnchoredToastProvider>          <DocumentTitleSync />          <ContrastAppearanceSync />          <EnvironmentThemeSync />          <GlassAppearanceSync />          <FontAppearanceSync />          <CommandPalette>            <AppSidebarLayout>              <Outlet />            </AppSidebarLayout>          </CommandPalette>        </AnchoredToastProvider>      </ToastProvider>    );  }   if (authGateState.status !== "authenticated" && authGateState.status !== "hosted-static") {    return (      <>        <DocumentTitleSync />        <Outlet />      </>    );  }   const appShell = (    <CommandPalette>      <AppSidebarLayout>        <Outlet />      </AppSidebarLayout>    </CommandPalette>  );   // FirstRunGate holds back everything below it — including EventRouter,  // whose welcome payload navigates into a thread — until the first-run  // decision is known, so a fresh install renders nothing (not the shell,  // not a flash of threads) before landing on the welcome wizard.  return (    <ToastProvider>      <AnchoredToastProvider>        <DocumentTitleSync />        <ContrastAppearanceSync />        <EnvironmentThemeSync />        <GlassAppearanceSync />        <FontAppearanceSync />        <FirstRunGate          enabled={primaryEnvironmentAuthenticated}          hostedStatic={authGateState.status === "hosted-static"}        >          {primaryEnvironmentAuthenticated ? <AuthenticatedTracingBootstrap /> : null}          {primaryEnvironmentAuthenticated ? <DesktopAppActivationCoordinator /> : null}          <RelayClientInstallDialog />          <ConnectOnboardingDialog />          <SshPasswordPromptDialog />          <SnapShotCoordinator />          <ConfirmDialogHost />          <SlowRpcRequestToastCoordinator />          <HostedStaticEnvironmentBootstrap />          {primaryEnvironmentAuthenticated ? (            <EventRouter skipInitialBootstrapNavigation={returningFromWelcomeRef.current} />          ) : null}          {primaryEnvironmentAuthenticated ? <PlanAgentSelectionHeal /> : null}          {primaryEnvironmentAuthenticated ? <ProviderUpdateLaunchNotification /> : null}          {appShell}          {/* Above the router: a theme draft is judged by walking the app, so the              editor has to survive navigation away from settings. */}          <ThemeEditorHost />        </FirstRunGate>      </AnchoredToastProvider>    </ToastProvider>  );} /** Follows the palette the primary environment's machine publishes, if any. */function EnvironmentThemeSync() {  useEnvironmentThemeSync();  // Ordered after the palette sync so a first-run client adopting the  // environment's own theme finds it already in the library.  useDefaultThemeAdoption();  return null;} function ContrastAppearanceSync() {  const appearanceContrast = useClientSettings((settings) => settings.appearanceContrast);  const diffColorScheme = useClientSettings((settings) => settings.diffColorScheme);   useEffect(() => {    document.documentElement.dataset.diffColorScheme = diffColorScheme;  }, [diffColorScheme]);   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;} 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();   useEffect(() => {    if (      environments.some(        (environment) => environment.entry.target._tag === "PrimaryConnectionTarget",      )    ) {      return;    }     if (activeEnvironmentId) {      return;    }     const firstSavedEnvironment = environments[0];    if (!firstSavedEnvironment) {      return;    }     setActiveEnvironmentId(firstSavedEnvironment.environmentId);  }, [activeEnvironmentId, environments]);   return null;} function RootRouteErrorView({ error, reset }: ErrorComponentProps) {  const message = errorMessage(error);  // Router pathname rather than window.location: desktop uses hash history, where the window path is always "/".  const pathname = useLocation({ select: (location) => location.pathname });  const report = useMemo(() => errorReport(error, pathname), [error, pathname]);   return (    <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-background px-4 py-10 text-foreground sm:px-6">      <div className="pointer-events-none absolute inset-0 opacity-80">        <div className="absolute inset-x-0 top-0 h-44 bg-[radial-gradient(44rem_16rem_at_top,color-mix(in_srgb,var(--color-red-500)_16%,transparent),transparent)]" />        <div className="absolute inset-0 bg-[linear-gradient(145deg,color-mix(in_srgb,var(--background)_90%,var(--color-black))_0%,var(--background)_55%)]" />      </div>       <section className="relative w-full max-w-xl rounded-2xl border border-border/80 bg-card/90 p-6 shadow-2xl shadow-black/20 backdrop-blur-md sm:p-8">        <p className="text-[11px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">          {APP_DISPLAY_NAME}        </p>        <h1 className="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">          Something went wrong.        </h1>        <p className="mt-2 text-sm leading-relaxed text-muted-foreground">{message}</p>         <div className="mt-5 flex flex-wrap gap-2">          <Button size="sm" onClick={() => reset()}>            Try again          </Button>          <Button size="sm" variant="outline" onClick={() => window.location.reload()}>            Reload app          </Button>          <CopyErrorButton report={report} />        </div>         <div className="mt-5 overflow-hidden rounded-lg border border-border/70 bg-background/55">          <p className="px-3 py-1.5 text-xs font-medium text-muted-foreground">Error report</p>          <pre className="max-h-64 overflow-auto border-t border-border/70 bg-background/80 px-3 py-2 text-xs whitespace-pre-wrap text-foreground/85">            {report}          </pre>        </div>      </section>    </div>  );} /** Copies the full error report and swaps to a check mark for a moment as confirmation. */function CopyErrorButton({ report }: { report: string }) {  const { copyToClipboard, isCopied } = useCopyToClipboard({ target: "error-report" });   return (    <Button size="sm" variant="outline" onClick={() => copyToClipboard(report)}>      {isCopied ? <CheckIcon className="text-success" /> : <CopyIcon />}      {isCopied ? "Copied" : "Copy error"}    </Button>  );} function errorMessage(error: unknown): string {  if (error instanceof Error && error.message.trim().length > 0) {    return error.message;  }   if (typeof error === "string" && error.trim().length > 0) {    return error;  }   return "An unexpected router error occurred.";} function errorDetails(error: unknown): string {  if (error instanceof Error) {    return error.stack ?? error.message;  }   if (typeof error === "string") {    return error;  }   try {    return JSON.stringify(error, null, 2);  } catch {    return "No additional error details are available.";  }} const MAX_ERROR_CAUSE_DEPTH = 5; /** * Full error text for bug reports: app build, page path, time, then the stack * and any cause chain. Takes the pathname only so tokens in the query never * land on the clipboard. */function errorReport(error: unknown, pathname: string): string {  const lines = [    `${APP_DISPLAY_NAME} ${APP_VERSION}`,    `Path: ${pathname}`,    `Time: ${new Date().toISOString()}`,    "",    errorDetails(error),  ];  let cause = error instanceof Error ? error.cause : undefined;  for (let depth = 0; cause !== undefined && depth < MAX_ERROR_CAUSE_DEPTH; depth += 1) {    lines.push("", "Caused by:", errorDetails(cause));    cause = cause instanceof Error ? cause.cause : undefined;  }  return lines.join("\n");} function AuthenticatedTracingBootstrap() {  useEffect(() => {    void configureClientTracing();  }, []);   return null;} function EventRouter({  skipInitialBootstrapNavigation,}: {  readonly skipInitialBootstrapNavigation: boolean;}) {  const navigate = useNavigate();  const pathname = useLocation({ select: (loc) => loc.pathname });  const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);  const primaryEnvironment = usePrimaryEnvironment();  const openInEditor = useAtomCommand(shellEnvironment.openInEditor, {    reportFailure: false,  });  const serverConfig = useAtomValue(primaryServerConfigAtom);  const serverConfigEvent = useAtomValue(primaryServerConfigEventAtom);  const serverWelcome = useAtomValue(primaryServerWelcomeAtom);  const readPathname = useEffectEvent(() => pathname);  const handledBootstrapThreadIdRef = useRef<string | null>(null);  const skipInitialBootstrapNavigationRef = useRef(skipInitialBootstrapNavigation);  const handledConfigEventRef = useRef(serverConfigEvent);  const [keybindingsToastController] = useState<KeybindingsUpdateToastController>(() =>    createKeybindingsUpdateToastController({}),  );   const handleWelcome = useEffectEvent((payload: ServerLifecycleWelcomePayload | null) => {    if (!payload) return;     setActiveEnvironmentId(payload.environment.environmentId);    void (async () => {      if (!payload.bootstrapProjectId || !payload.bootstrapThreadId) {        return;      }      const bootstrapProject = readProject(        scopeProjectRef(payload.environment.environmentId, payload.bootstrapProjectId),      );      const bootstrapProjectKey =        (bootstrapProject          ? deriveLogicalProjectKeyFromSettings(bootstrapProject, projectGroupingSettings)          : null) ??        (serverConfig?.cwd          ? derivePhysicalProjectKeyFromPath(payload.environment.environmentId, serverConfig.cwd)          : null) ??        scopedProjectKey(          scopeProjectRef(payload.environment.environmentId, payload.bootstrapProjectId),        );      useUiStateStore.getState().setProjectExpanded(bootstrapProjectKey, true);       if (readPathname() !== "/") {        return;      }      if (skipInitialBootstrapNavigationRef.current) {        skipInitialBootstrapNavigationRef.current = false;        handledBootstrapThreadIdRef.current = payload.bootstrapThreadId;

Lines 1–500 of 598.