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
34 changes: 34 additions & 0 deletions apps/web/src/branding.logic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const NIGHTLY_SERVER_VERSION_PATTERN = /-nightly\.\d{8}\.\d+$/;

export function formatAppDisplayName(input: {
readonly baseName: string;
readonly stageLabel: string;
}): string {
return `${input.baseName} (${input.stageLabel})`;
}

export function resolveServerBackedAppStageLabel(input: {
readonly primaryServerVersion: string | null | undefined;
readonly fallbackStageLabel: string;
}): string {
return input.primaryServerVersion &&
NIGHTLY_SERVER_VERSION_PATTERN.test(input.primaryServerVersion)
? "Nightly"
: input.fallbackStageLabel;
}

export function resolveServerBackedAppDisplayName(input: {
readonly baseName: string;
readonly fallbackDisplayName: string;
readonly fallbackStageLabel: string;
readonly primaryServerVersion: string | null | undefined;
}): string {
const stageLabel = resolveServerBackedAppStageLabel({
primaryServerVersion: input.primaryServerVersion,
fallbackStageLabel: input.fallbackStageLabel,
});

return stageLabel === input.fallbackStageLabel
? input.fallbackDisplayName
: formatAppDisplayName({ baseName: input.baseName, stageLabel });
}
48 changes: 48 additions & 0 deletions apps/web/src/branding.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
import {
resolveServerBackedAppDisplayName,
resolveServerBackedAppStageLabel,
} from "./branding.logic";

const originalWindow = globalThis.window;

Expand Down Expand Up @@ -55,3 +59,47 @@ describe("branding", () => {
expect(branding.HOSTED_APP_CHANNEL_LABEL).toBeNull();
});
});

describe("branding logic", () => {
it("returns Nightly for nightly primary server versions", () => {
expect(
resolveServerBackedAppStageLabel({
primaryServerVersion: "0.0.28-nightly.20260616.12",
fallbackStageLabel: "Alpha",
}),
).toBe("Nightly");
});

it("updates the display name for nightly primary server versions", () => {
expect(
resolveServerBackedAppDisplayName({
baseName: "T3 Code",
fallbackDisplayName: "T3 Code (Alpha)",
fallbackStageLabel: "Alpha",
primaryServerVersion: "0.0.28-nightly.20260616.12",
}),
).toBe("T3 Code (Nightly)");
});

it("keeps the fallback display name for stable primary server versions", () => {
expect(
resolveServerBackedAppDisplayName({
baseName: "T3 Code",
fallbackDisplayName: "T3 Code (Alpha)",
fallbackStageLabel: "Alpha",
primaryServerVersion: "0.0.27",
}),
).toBe("T3 Code (Alpha)");
});

it("keeps the fallback display name for malformed nightly primary server versions", () => {
expect(
resolveServerBackedAppDisplayName({
baseName: "T3 Code",
fallbackDisplayName: "T3 Code (Alpha)",
fallbackStageLabel: "Alpha",
primaryServerVersion: "0.0.28-nightly.20260616",
}),
).toBe("T3 Code (Alpha)");
});
});
4 changes: 3 additions & 1 deletion apps/web/src/branding.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { DesktopAppBranding } from "@t3tools/contracts";
import { formatAppDisplayName } from "./branding.logic";

function readInjectedDesktopAppBranding(): DesktopAppBranding | null {
if (typeof window === "undefined") {
Expand All @@ -21,5 +22,6 @@ export const APP_STAGE_LABEL =
HOSTED_APP_CHANNEL_LABEL ??
(import.meta.env.DEV ? "Dev" : "Alpha");
export const APP_DISPLAY_NAME =
injectedDesktopAppBranding?.displayName ?? `${APP_BASE_NAME} (${APP_STAGE_LABEL})`;
injectedDesktopAppBranding?.displayName ??
formatAppDisplayName({ baseName: APP_BASE_NAME, stageLabel: APP_STAGE_LABEL });
export const APP_VERSION = import.meta.env.APP_VERSION || "0.0.0";
7 changes: 2 additions & 5 deletions apps/web/src/components/Sidebar.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import {
import type { SidebarThreadSummary, Thread } from "../types";
import { cn } from "../lib/utils";
import { isLatestTurnSettled } from "../session-logic";
import { resolveServerBackedAppStageLabel } from "../branding.logic";

export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";
export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100;
const NIGHTLY_SERVER_VERSION_PATTERN = /-nightly\.\d{8}\.\d+$/;
// Visible sidebar rows are prewarmed into the thread-detail cache so opening a
// nearby thread usually reuses an already-hot subscription.
export const SIDEBAR_THREAD_PREWARM_LIMIT = 10;
Expand Down Expand Up @@ -69,10 +69,7 @@ export function resolveSidebarStageBadgeLabel(input: {
primaryServerVersion: string | null | undefined;
fallbackStageLabel: string;
}): string {
return input.primaryServerVersion &&
NIGHTLY_SERVER_VERSION_PATTERN.test(input.primaryServerVersion)
? "Nightly"
: input.fallbackStageLabel;
return resolveServerBackedAppStageLabel(input);
}

export function createThreadJumpHintVisibilityController(input: {
Expand Down
3 changes: 0 additions & 3 deletions apps/web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { isElectron } from "./env";
import { ManagedRelayAuthProvider } from "./cloud/managedAuth";
import { hasCloudPublicConfig } from "./cloud/publicConfig";
import { getRouter } from "./router";
import { APP_DISPLAY_NAME } from "./branding";
import { syncDocumentWindowControlsOverlayClass } from "./lib/windowControlsOverlay";
import { AppRoot } from "./AppRoot";

Expand All @@ -28,8 +27,6 @@ if (isElectron) {
syncDocumentWindowControlsOverlayClass();
}

document.title = APP_DISPLAY_NAME;

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

const app = <AppRoot router={router} />;
Expand Down
35 changes: 32 additions & 3 deletions apps/web/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
} from "@tanstack/react-router";
import { useEffect, useEffectEvent, useRef, useState } from "react";

import { APP_DISPLAY_NAME } from "../branding";
import { APP_BASE_NAME, APP_DISPLAY_NAME, APP_STAGE_LABEL } from "../branding";
import { resolveServerBackedAppDisplayName } from "../branding.logic";
import { AppSidebarLayout } from "../components/AppSidebarLayout";
import { CommandPalette } from "../components/CommandPalette";
import { RelayClientInstallDialog } from "../components/cloud/RelayClientInstallDialog";
Expand Down Expand Up @@ -96,11 +97,21 @@ function RootRouteView() {
}, [pathname]);

if (pathname === "/pair") {
return <Outlet />;
return (
<>
<DocumentTitleSync />
<Outlet />
</>
);
}

if (authGateState.status !== "authenticated" && authGateState.status !== "hosted-static") {
return <Outlet />;
return (
<>
<DocumentTitleSync />
<Outlet />
</>
);
}

const appShell = (
Expand All @@ -114,6 +125,7 @@ function RootRouteView() {
return (
<ToastProvider>
<AnchoredToastProvider>
<DocumentTitleSync />
{primaryEnvironmentAuthenticated ? <AuthenticatedTracingBootstrap /> : null}
<RelayClientInstallDialog />
<SshPasswordPromptDialog />
Expand All @@ -127,6 +139,23 @@ function RootRouteView() {
);
}

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