authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-06-20 08:49:33+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-19 23:49:33-07:00
log8d61172f8bbb675e7472bb949f33bb40a987d454
treeb3eb4dfe21f6c027ce0e85d3f5c469ff72fd4336
parentdc82f792c3fc43a06edca916a1da297af714600a
signature Signed by PGP key B5690EEEBB952194

Cosmetic fix: Sync web title with nightly server branding (#3219)

Co-authored-by: Codex <codex@openai.com>

6 files changed, 119 insertions(+), 12 deletions(-)

apps/web/src/branding.logic.ts created+34
...@@ -0,0 +1,34 @@
1const NIGHTLY_SERVER_VERSION_PATTERN = /-nightly\.\d{8}\.\d+$/;
2
3export function formatAppDisplayName(input: {
4 readonly baseName: string;
5 readonly stageLabel: string;
6}): string {
7 return `${input.baseName} (${input.stageLabel})`;
8}
9
10export function resolveServerBackedAppStageLabel(input: {
11 readonly primaryServerVersion: string | null | undefined;
12 readonly fallbackStageLabel: string;
13}): string {
14 return input.primaryServerVersion &&
15 NIGHTLY_SERVER_VERSION_PATTERN.test(input.primaryServerVersion)
16 ? "Nightly"
17 : input.fallbackStageLabel;
18}
19
20export function resolveServerBackedAppDisplayName(input: {
21 readonly baseName: string;
22 readonly fallbackDisplayName: string;
23 readonly fallbackStageLabel: string;
24 readonly primaryServerVersion: string | null | undefined;
25}): string {
26 const stageLabel = resolveServerBackedAppStageLabel({
27 primaryServerVersion: input.primaryServerVersion,
28 fallbackStageLabel: input.fallbackStageLabel,
29 });
30
31 return stageLabel === input.fallbackStageLabel
32 ? input.fallbackDisplayName
33 : formatAppDisplayName({ baseName: input.baseName, stageLabel });
34}
apps/web/src/branding.test.ts+48
...@@ -1,4 +1,8 @@...@@ -1,4 +1,8 @@
1import { afterEach, describe, expect, it, vi } from "vite-plus/test";1import { afterEach, describe, expect, it, vi } from "vite-plus/test";
2import {
3 resolveServerBackedAppDisplayName,
4 resolveServerBackedAppStageLabel,
5} from "./branding.logic";
26
3const originalWindow = globalThis.window;7const originalWindow = globalThis.window;
48
...@@ -55,3 +59,47 @@ describe("branding", () => {...@@ -55,3 +59,47 @@ describe("branding", () => {
55 expect(branding.HOSTED_APP_CHANNEL_LABEL).toBeNull();59 expect(branding.HOSTED_APP_CHANNEL_LABEL).toBeNull();
56 });60 });
57});61});
62
63describe("branding logic", () => {
64 it("returns Nightly for nightly primary server versions", () => {
65 expect(
66 resolveServerBackedAppStageLabel({
67 primaryServerVersion: "0.0.28-nightly.20260616.12",
68 fallbackStageLabel: "Alpha",
69 }),
70 ).toBe("Nightly");
71 });
72
73 it("updates the display name for nightly primary server versions", () => {
74 expect(
75 resolveServerBackedAppDisplayName({
76 baseName: "T3 Code",
77 fallbackDisplayName: "T3 Code (Alpha)",
78 fallbackStageLabel: "Alpha",
79 primaryServerVersion: "0.0.28-nightly.20260616.12",
80 }),
81 ).toBe("T3 Code (Nightly)");
82 });
83
84 it("keeps the fallback display name for stable primary server versions", () => {
85 expect(
86 resolveServerBackedAppDisplayName({
87 baseName: "T3 Code",
88 fallbackDisplayName: "T3 Code (Alpha)",
89 fallbackStageLabel: "Alpha",
90 primaryServerVersion: "0.0.27",
91 }),
92 ).toBe("T3 Code (Alpha)");
93 });
94
95 it("keeps the fallback display name for malformed nightly primary server versions", () => {
96 expect(
97 resolveServerBackedAppDisplayName({
98 baseName: "T3 Code",
99 fallbackDisplayName: "T3 Code (Alpha)",
100 fallbackStageLabel: "Alpha",
101 primaryServerVersion: "0.0.28-nightly.20260616",
102 }),
103 ).toBe("T3 Code (Alpha)");
104 });
105});
apps/web/src/branding.ts+3-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import type { DesktopAppBranding } from "@t3tools/contracts";1import type { DesktopAppBranding } from "@t3tools/contracts";
2import { formatAppDisplayName } from "./branding.logic";
23
3function readInjectedDesktopAppBranding(): DesktopAppBranding | null {4function readInjectedDesktopAppBranding(): DesktopAppBranding | null {
4 if (typeof window === "undefined") {5 if (typeof window === "undefined") {
...@@ -21,5 +22,6 @@ export const APP_STAGE_LABEL =...@@ -21,5 +22,6 @@ export const APP_STAGE_LABEL =
21 HOSTED_APP_CHANNEL_LABEL ??22 HOSTED_APP_CHANNEL_LABEL ??
22 (import.meta.env.DEV ? "Dev" : "Alpha");23 (import.meta.env.DEV ? "Dev" : "Alpha");
23export const APP_DISPLAY_NAME =24export const APP_DISPLAY_NAME =
24 injectedDesktopAppBranding?.displayName ?? `${APP_BASE_NAME} (${APP_STAGE_LABEL})`;25 injectedDesktopAppBranding?.displayName ??
26 formatAppDisplayName({ baseName: APP_BASE_NAME, stageLabel: APP_STAGE_LABEL });
25export const APP_VERSION = import.meta.env.APP_VERSION || "0.0.0";27export const APP_VERSION = import.meta.env.APP_VERSION || "0.0.0";
apps/web/src/components/Sidebar.logic.ts+2-5
...@@ -9,10 +9,10 @@ import {...@@ -9,10 +9,10 @@ import {
9import type { SidebarThreadSummary, Thread } from "../types";9import type { SidebarThreadSummary, Thread } from "../types";
10import { cn } from "../lib/utils";10import { cn } from "../lib/utils";
11import { isLatestTurnSettled } from "../session-logic";11import { isLatestTurnSettled } from "../session-logic";
12import { resolveServerBackedAppStageLabel } from "../branding.logic";
1213
13export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";14export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";
14export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100;15export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100;
15const NIGHTLY_SERVER_VERSION_PATTERN = /-nightly\.\d{8}\.\d+$/;
16// Visible sidebar rows are prewarmed into the thread-detail cache so opening a16// Visible sidebar rows are prewarmed into the thread-detail cache so opening a
17// nearby thread usually reuses an already-hot subscription.17// nearby thread usually reuses an already-hot subscription.
18export const SIDEBAR_THREAD_PREWARM_LIMIT = 10;18export const SIDEBAR_THREAD_PREWARM_LIMIT = 10;
...@@ -69,10 +69,7 @@ export function resolveSidebarStageBadgeLabel(input: {...@@ -69,10 +69,7 @@ export function resolveSidebarStageBadgeLabel(input: {
69 primaryServerVersion: string | null | undefined;69 primaryServerVersion: string | null | undefined;
70 fallbackStageLabel: string;70 fallbackStageLabel: string;
71}): string {71}): string {
72 return input.primaryServerVersion &&72 return resolveServerBackedAppStageLabel(input);
73 NIGHTLY_SERVER_VERSION_PATTERN.test(input.primaryServerVersion)
74 ? "Nightly"
75 : input.fallbackStageLabel;
76}73}
7774
78export function createThreadJumpHintVisibilityController(input: {75export function createThreadJumpHintVisibilityController(input: {
apps/web/src/main.tsx-3
...@@ -15,7 +15,6 @@ import { isElectron } from "./env";...@@ -15,7 +15,6 @@ import { isElectron } from "./env";
15import { ManagedRelayAuthProvider } from "./cloud/managedAuth";15import { ManagedRelayAuthProvider } from "./cloud/managedAuth";
16import { hasCloudPublicConfig } from "./cloud/publicConfig";16import { hasCloudPublicConfig } from "./cloud/publicConfig";
17import { getRouter } from "./router";17import { getRouter } from "./router";
18import { APP_DISPLAY_NAME } from "./branding";
19import { syncDocumentWindowControlsOverlayClass } from "./lib/windowControlsOverlay";18import { syncDocumentWindowControlsOverlayClass } from "./lib/windowControlsOverlay";
20import { AppRoot } from "./AppRoot";19import { AppRoot } from "./AppRoot";
2120
...@@ -28,8 +27,6 @@ if (isElectron) {...@@ -28,8 +27,6 @@ if (isElectron) {
28 syncDocumentWindowControlsOverlayClass();27 syncDocumentWindowControlsOverlayClass();
29}28}
3029
31document.title = APP_DISPLAY_NAME;
32
33const clerkPublishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string | undefined;30const clerkPublishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string | undefined;
3431
35const app = <AppRoot router={router} />;32const app = <AppRoot router={router} />;
apps/web/src/routes/__root.tsx+32-3
...@@ -10,7 +10,8 @@ import {...@@ -10,7 +10,8 @@ import {
10} from "@tanstack/react-router";10} from "@tanstack/react-router";
11import { useEffect, useEffectEvent, useRef, useState } from "react";11import { useEffect, useEffectEvent, useRef, useState } from "react";
1212
13import { APP_DISPLAY_NAME } from "../branding";13import { APP_BASE_NAME, APP_DISPLAY_NAME, APP_STAGE_LABEL } from "../branding";
14import { resolveServerBackedAppDisplayName } from "../branding.logic";
14import { AppSidebarLayout } from "../components/AppSidebarLayout";15import { AppSidebarLayout } from "../components/AppSidebarLayout";
15import { CommandPalette } from "../components/CommandPalette";16import { CommandPalette } from "../components/CommandPalette";
16import { RelayClientInstallDialog } from "../components/cloud/RelayClientInstallDialog";17import { RelayClientInstallDialog } from "../components/cloud/RelayClientInstallDialog";
...@@ -96,11 +97,21 @@ function RootRouteView() {...@@ -96,11 +97,21 @@ function RootRouteView() {
96 }, [pathname]);97 }, [pathname]);
9798
98 if (pathname === "/pair") {99 if (pathname === "/pair") {
99 return <Outlet />;100 return (
101 <>
102 <DocumentTitleSync />
103 <Outlet />
104 </>
105 );
100 }106 }
101107
102 if (authGateState.status !== "authenticated" && authGateState.status !== "hosted-static") {108 if (authGateState.status !== "authenticated" && authGateState.status !== "hosted-static") {
103 return <Outlet />;109 return (
110 <>
111 <DocumentTitleSync />
112 <Outlet />
113 </>
114 );
104 }115 }
105116
106 const appShell = (117 const appShell = (
...@@ -114,6 +125,7 @@ function RootRouteView() {...@@ -114,6 +125,7 @@ function RootRouteView() {
114 return (125 return (
115 <ToastProvider>126 <ToastProvider>
116 <AnchoredToastProvider>127 <AnchoredToastProvider>
128 <DocumentTitleSync />
117 {primaryEnvironmentAuthenticated ? <AuthenticatedTracingBootstrap /> : null}129 {primaryEnvironmentAuthenticated ? <AuthenticatedTracingBootstrap /> : null}
118 <RelayClientInstallDialog />130 <RelayClientInstallDialog />
119 <SshPasswordPromptDialog />131 <SshPasswordPromptDialog />
...@@ -127,6 +139,23 @@ function RootRouteView() {...@@ -127,6 +139,23 @@ function RootRouteView() {
127 );139 );
128}140}
129141
142function DocumentTitleSync() {
143 const primaryServerVersion =
144 useAtomValue(primaryServerConfigAtom)?.environment.serverVersion ?? null;
145 const title = resolveServerBackedAppDisplayName({
146 baseName: APP_BASE_NAME,
147 fallbackDisplayName: APP_DISPLAY_NAME,
148 fallbackStageLabel: APP_STAGE_LABEL,
149 primaryServerVersion,
150 });
151
152 useEffect(() => {
153 document.title = title;
154 }, [title]);
155
156 return null;
157}
158
130function HostedStaticEnvironmentBootstrap() {159function HostedStaticEnvironmentBootstrap() {
131 const { environments } = useEnvironments();160 const { environments } = useEnvironments();
132 const activeEnvironmentId = useActiveEnvironmentId();161 const activeEnvironmentId = useActiveEnvironmentId();