| author | |
| committer | |
| log | 8d61172f8bbb675e7472bb949f33bb40a987d454 |
| tree | b3eb4dfe21f6c027ce0e85d3f5c469ff72fd4336 |
| parent | dc82f792c3fc43a06edca916a1da297af714600a |
| signature | Signed by PGP key B5690EEEBB952194 |
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 @@ |
| 1 | const NIGHTLY_SERVER_VERSION_PATTERN = /-nightly\.\d{8}\.\d+$/; | |
| 2 | ||
| 3 | export function formatAppDisplayName(input: { | |
| 4 | readonly baseName: string; | |
| 5 | readonly stageLabel: string; | |
| 6 | }): string { | |
| 7 | return `${input.baseName} (${input.stageLabel})`; | |
| 8 | } | |
| 9 | ||
| 10 | export 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 | ||
| 20 | export 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 | 1 | import { afterEach, describe, expect, it, vi } from "vite-plus/test"; |
| 2 | import { | |
| 3 | resolveServerBackedAppDisplayName, | |
| 4 | resolveServerBackedAppStageLabel, | |
| 5 | } from "./branding.logic"; | |
| 2 | 6 | |
| 3 | 7 | const originalWindow = globalThis.window; |
| 4 | 8 | |
| ... | ... | @@ -55,3 +59,47 @@ describe("branding", () => { |
| 55 | 59 | expect(branding.HOSTED_APP_CHANNEL_LABEL).toBeNull(); |
| 56 | 60 | }); |
| 57 | 61 | }); |
| 62 | ||
| 63 | describe("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 | 1 | import type { DesktopAppBranding } from "@t3tools/contracts"; |
| 2 | import { formatAppDisplayName } from "./branding.logic"; | |
| 2 | 3 | |
| 3 | 4 | function readInjectedDesktopAppBranding(): DesktopAppBranding | null { |
| 4 | 5 | if (typeof window === "undefined") { |
| ... | ... | @@ -21,5 +22,6 @@ export const APP_STAGE_LABEL = |
| 21 | 22 | HOSTED_APP_CHANNEL_LABEL ?? |
| 22 | 23 | (import.meta.env.DEV ? "Dev" : "Alpha"); |
| 23 | 24 | export 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 }); | |
| 25 | 27 | export 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 | 9 | import type { SidebarThreadSummary, Thread } from "../types"; |
| 10 | 10 | import { cn } from "../lib/utils"; |
| 11 | 11 | import { isLatestTurnSettled } from "../session-logic"; |
| 12 | import { resolveServerBackedAppStageLabel } from "../branding.logic"; | |
| 12 | 13 | |
| 13 | 14 | export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]"; |
| 14 | 15 | export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100; |
| 15 | const NIGHTLY_SERVER_VERSION_PATTERN = /-nightly\.\d{8}\.\d+$/; | |
| 16 | 16 | // Visible sidebar rows are prewarmed into the thread-detail cache so opening a |
| 17 | 17 | // nearby thread usually reuses an already-hot subscription. |
| 18 | 18 | export const SIDEBAR_THREAD_PREWARM_LIMIT = 10; |
| ... | ... | @@ -69,10 +69,7 @@ export function resolveSidebarStageBadgeLabel(input: { |
| 69 | 69 | primaryServerVersion: string | null | undefined; |
| 70 | 70 | fallbackStageLabel: string; |
| 71 | 71 | }): string { |
| 72 | return input.primaryServerVersion && | |
| 73 | NIGHTLY_SERVER_VERSION_PATTERN.test(input.primaryServerVersion) | |
| 74 | ? "Nightly" | |
| 75 | : input.fallbackStageLabel; | |
| 72 | return resolveServerBackedAppStageLabel(input); | |
| 76 | 73 | } |
| 77 | 74 | |
| 78 | 75 | export function createThreadJumpHintVisibilityController(input: { |
apps/web/src/main.tsx-3| ... | ... | @@ -15,7 +15,6 @@ import { isElectron } from "./env"; |
| 15 | 15 | import { ManagedRelayAuthProvider } from "./cloud/managedAuth"; |
| 16 | 16 | import { hasCloudPublicConfig } from "./cloud/publicConfig"; |
| 17 | 17 | import { getRouter } from "./router"; |
| 18 | import { APP_DISPLAY_NAME } from "./branding"; | |
| 19 | 18 | import { syncDocumentWindowControlsOverlayClass } from "./lib/windowControlsOverlay"; |
| 20 | 19 | import { AppRoot } from "./AppRoot"; |
| 21 | 20 | |
| ... | ... | @@ -28,8 +27,6 @@ if (isElectron) { |
| 28 | 27 | syncDocumentWindowControlsOverlayClass(); |
| 29 | 28 | } |
| 30 | 29 | |
| 31 | document.title = APP_DISPLAY_NAME; | |
| 32 | ||
| 33 | 30 | const clerkPublishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string | undefined; |
| 34 | 31 | |
| 35 | 32 | const app = <AppRoot router={router} />; |
apps/web/src/routes/__root.tsx+32-3| ... | ... | @@ -10,7 +10,8 @@ import { |
| 10 | 10 | } from "@tanstack/react-router"; |
| 11 | 11 | import { useEffect, useEffectEvent, useRef, useState } from "react"; |
| 12 | 12 | |
| 13 | import { APP_DISPLAY_NAME } from "../branding"; | |
| 13 | import { APP_BASE_NAME, APP_DISPLAY_NAME, APP_STAGE_LABEL } from "../branding"; | |
| 14 | import { resolveServerBackedAppDisplayName } from "../branding.logic"; | |
| 14 | 15 | import { AppSidebarLayout } from "../components/AppSidebarLayout"; |
| 15 | 16 | import { CommandPalette } from "../components/CommandPalette"; |
| 16 | 17 | import { RelayClientInstallDialog } from "../components/cloud/RelayClientInstallDialog"; |
| ... | ... | @@ -96,11 +97,21 @@ function RootRouteView() { |
| 96 | 97 | }, [pathname]); |
| 97 | 98 | |
| 98 | 99 | if (pathname === "/pair") { |
| 99 | return <Outlet />; | |
| 100 | return ( | |
| 101 | <> | |
| 102 | <DocumentTitleSync /> | |
| 103 | <Outlet /> | |
| 104 | </> | |
| 105 | ); | |
| 100 | 106 | } |
| 101 | 107 | |
| 102 | 108 | if (authGateState.status !== "authenticated" && authGateState.status !== "hosted-static") { |
| 103 | return <Outlet />; | |
| 109 | return ( | |
| 110 | <> | |
| 111 | <DocumentTitleSync /> | |
| 112 | <Outlet /> | |
| 113 | </> | |
| 114 | ); | |
| 104 | 115 | } |
| 105 | 116 | |
| 106 | 117 | const appShell = ( |
| ... | ... | @@ -114,6 +125,7 @@ function RootRouteView() { |
| 114 | 125 | return ( |
| 115 | 126 | <ToastProvider> |
| 116 | 127 | <AnchoredToastProvider> |
| 128 | <DocumentTitleSync /> | |
| 117 | 129 | {primaryEnvironmentAuthenticated ? <AuthenticatedTracingBootstrap /> : null} |
| 118 | 130 | <RelayClientInstallDialog /> |
| 119 | 131 | <SshPasswordPromptDialog /> |
| ... | ... | @@ -127,6 +139,23 @@ function RootRouteView() { |
| 127 | 139 | ); |
| 128 | 140 | } |
| 129 | 141 | |
| 142 | function 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 | ||
| 130 | 159 | function HostedStaticEnvironmentBootstrap() { |
| 131 | 160 | const { environments } = useEnvironments(); |
| 132 | 161 | const activeEnvironmentId = useActiveEnvironmentId(); |