diff --git a/site/src/index.css b/site/src/index.css index a6b02cb83b..d6a172bb11 100644 --- a/site/src/index.css +++ b/site/src/index.css @@ -156,50 +156,6 @@ } } -@layer components { - /* Map each stripe variant to a color token so the - pseudo-element rules can stay DRY. */ - .navbar-stripe-devel { - --stripe-color: var(--content-warning); - } - - .navbar-stripe-rc { - --stripe-color: var(--border-sky); - } - - /* Thin stripe bars at the top and bottom edges of the - navbar. Using pseudo-elements keeps the stripes out of - the content area so nav links stay readable. */ - .navbar-stripe-devel::before, - .navbar-stripe-devel::after, - .navbar-stripe-rc::before, - .navbar-stripe-rc::after { - content: ""; - position: absolute; - left: 0; - right: 0; - height: 4px; - background: repeating-linear-gradient( - -45deg, - transparent, - transparent 4px, - hsl(var(--stripe-color) / 0.5) 4px, - hsl(var(--stripe-color) / 0.5) 8px - ); - pointer-events: none; - } - - .navbar-stripe-devel::before, - .navbar-stripe-rc::before { - top: 0; - } - - .navbar-stripe-devel::after, - .navbar-stripe-rc::after { - bottom: 0; - } -} - @layer base { * { @apply border-border; diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index 19f6e15b49..3d09b5d37b 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -16,7 +16,7 @@ import type { ProxyContextValue } from "#/contexts/ProxyContext"; import { useEmbeddedMetadata } from "#/hooks/useEmbeddedMetadata"; import { useDashboard } from "#/modules/dashboard/useDashboard"; import { NotificationsInbox } from "#/modules/notifications/NotificationsInbox/NotificationsInbox"; -import { isDevBuild, isRcBuild } from "#/utils/buildInfo"; +import { getPrereleaseFlag } from "#/utils/buildInfo"; import { cn } from "#/utils/cn"; import { DeploymentDropdown } from "./DeploymentDropdown"; import { MobileMenu } from "./MobileMenu"; @@ -61,16 +61,27 @@ export const NavbarView: FC = ({ canCreateChat, proxyContextValue, }) => { - const isDev = buildInfo ? isDevBuild(buildInfo) : false; - const isRc = buildInfo ? isRcBuild(buildInfo) : false; - const isPreRelease = isDev || isRc; + const prerelease = getPrereleaseFlag(buildInfo); return (
{logo_url ? ( @@ -82,7 +93,7 @@ export const NavbarView: FC = ({ - {isPreRelease && buildInfo?.version && ( + {prerelease && buildInfo?.version && ( = ({ className="absolute top-0 left-1/2 -translate-x-1/2 no-underline z-10" > @@ -262,8 +273,9 @@ function idleTasksLabel(count: number) { const AgentsNavItem: FC<{ canCreateChat: boolean }> = ({ canCreateChat }) => { const { experiments, buildInfo } = useDashboard(); + const prerelease = getPrereleaseFlag(buildInfo); const experimentEnabled = - experiments.includes("agents") || isDevBuild(buildInfo); + experiments.includes("agents") || prerelease === "devel"; if (!experimentEnabled || !canCreateChat) { return null; diff --git a/site/src/modules/management/DeploymentSidebarView.tsx b/site/src/modules/management/DeploymentSidebarView.tsx index 2577402296..23b6e453c5 100644 --- a/site/src/modules/management/DeploymentSidebarView.tsx +++ b/site/src/modules/management/DeploymentSidebarView.tsx @@ -7,7 +7,7 @@ import { } from "#/components/Sidebar/Sidebar"; import { Stack } from "#/components/Stack/Stack"; import type { Permissions } from "#/modules/permissions"; -import { isDevBuild } from "#/utils/buildInfo"; +import { getPrereleaseFlag } from "#/utils/buildInfo"; interface DeploymentSidebarViewProps { /** Site-wide permissions. */ @@ -54,7 +54,8 @@ export const DeploymentSidebarView: FC = ({ )} {permissions.viewDeploymentConfig && - (experiments.includes("oauth2") || isDevBuild(buildInfo)) && ( + (experiments.includes("oauth2") || + getPrereleaseFlag(buildInfo) === "devel") && ( OAuth2 Applications diff --git a/site/src/pages/UserSettingsPage/Sidebar.tsx b/site/src/pages/UserSettingsPage/Sidebar.tsx index 5b1a23a944..223ae79e58 100644 --- a/site/src/pages/UserSettingsPage/Sidebar.tsx +++ b/site/src/pages/UserSettingsPage/Sidebar.tsx @@ -18,7 +18,7 @@ import { SidebarNavItem, } from "#/components/Sidebar/Sidebar"; import { useDashboard } from "#/modules/dashboard/useDashboard"; -import { isDevBuild } from "#/utils/buildInfo"; +import { getPrereleaseFlag } from "#/utils/buildInfo"; interface SidebarProps { user: User; @@ -45,7 +45,8 @@ export const Sidebar: FC = ({ user }) => { External Authentication - {(experiments.includes("oauth2") || isDevBuild(buildInfo)) && ( + {(experiments.includes("oauth2") || + getPrereleaseFlag(buildInfo) === "devel") && ( OAuth2 Applications diff --git a/site/src/utils/buildInfo.test.ts b/site/src/utils/buildInfo.test.ts index 7f99daa48c..8428239620 100644 --- a/site/src/utils/buildInfo.test.ts +++ b/site/src/utils/buildInfo.test.ts @@ -1,5 +1,5 @@ import type { BuildInfoResponse } from "#/api/typesGenerated"; -import { isDevBuild, isRcBuild } from "./buildInfo"; +import { getPrereleaseFlag } from "./buildInfo"; const baseBuildInfo: BuildInfoResponse = { agent_api_version: "1.0", @@ -13,88 +13,97 @@ const baseBuildInfo: BuildInfoResponse = { telemetry: false, }; -describe("isDevBuild", () => { - it("returns true for -devel versions", () => { +describe("getPrereleaseFlag", () => { + it("returns devel for -devel versions", () => { expect( - isDevBuild({ ...baseBuildInfo, version: "v2.16.0-devel+abc123" }), - ).toBe(true); + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v2.16.0-devel+abc123", + }), + ).toBe("devel"); }); - it("returns true for bare -devel versions", () => { - expect(isDevBuild({ ...baseBuildInfo, version: "v2.32.0-devel" })).toBe( - true, - ); - }); - - it("returns true for v0.0.0", () => { - expect(isDevBuild({ ...baseBuildInfo, version: "v0.0.0" })).toBe(true); - }); - - it("returns false for release versions", () => { - expect(isDevBuild({ ...baseBuildInfo, version: "v2.16.0" })).toBe(false); - }); - - it("returns false for RC versions", () => { - expect(isDevBuild({ ...baseBuildInfo, version: "v2.32.0-rc.1" })).toBe( - false, - ); - }); - - it("returns true for combined rc+devel versions", () => { + it("returns devel for bare -devel versions", () => { expect( - isDevBuild({ + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v2.32.0-devel", + }), + ).toBe("devel"); + }); + + it("returns devel for v0.0.0", () => { + expect( + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v0.0.0", + }), + ).toBe("devel"); + }); + + it("returns undefined for release versions", () => { + expect( + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v2.16.0", + }), + ).toBeUndefined(); + }); + + it("returns rc for RC versions", () => { + expect( + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v2.32.0-rc.1", + }), + ).toBe("rc"); + }); + + it("returns devel when version contains both rc and -devel (devel wins)", () => { + expect( + getPrereleaseFlag({ ...baseBuildInfo, version: "v2.33.0-rc.1-devel+727ec00f7", }), - ).toBe(true); + ).toBe("devel"); }); - it("returns false for empty version", () => { - expect(isDevBuild({ ...baseBuildInfo, version: "" })).toBe(false); - }); -}); - -describe("isRcBuild", () => { - it("returns true for -rc.0 versions", () => { - expect(isRcBuild({ ...baseBuildInfo, version: "v2.32.0-rc.0" })).toBe(true); - }); - - it("returns true for -rc.1 with build metadata", () => { + it("returns undefined for empty version", () => { expect( - isRcBuild({ ...baseBuildInfo, version: "v2.32.0-rc.1+abc123" }), - ).toBe(true); - }); - - it("returns true for higher RC numbers", () => { - expect(isRcBuild({ ...baseBuildInfo, version: "v2.32.0-rc.12" })).toBe( - true, - ); - }); - - it("returns false for release versions", () => { - expect(isRcBuild({ ...baseBuildInfo, version: "v2.16.0" })).toBe(false); - }); - - it("returns false for devel versions", () => { - expect( - isRcBuild({ ...baseBuildInfo, version: "v2.16.0-devel+abc123" }), - ).toBe(false); - }); - - it("returns false for empty version", () => { - expect(isRcBuild({ ...baseBuildInfo, version: "" })).toBe(false); - }); - - it("returns false for versions with rc but no dot", () => { - expect(isRcBuild({ ...baseBuildInfo, version: "v2.32.0-rc" })).toBe(false); - }); - - it("returns true for combined rc+devel versions", () => { - expect( - isRcBuild({ + getPrereleaseFlag({ ...baseBuildInfo, - version: "v2.33.0-rc.1-devel+727ec00f7", + version: "", }), - ).toBe(true); + ).toBeUndefined(); + }); + + it("returns rc for -rc.0 and build metadata", () => { + expect( + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v2.32.0-rc.0", + }), + ).toBe("rc"); + expect( + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v2.32.0-rc.1+abc123", + }), + ).toBe("rc"); + expect( + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v2.32.0-rc.12", + }), + ).toBe("rc"); + }); + + it("returns undefined when rc segment lacks a dot", () => { + expect( + getPrereleaseFlag({ + ...baseBuildInfo, + version: "v2.32.0-rc", + }), + ).toBeUndefined(); }); }); diff --git a/site/src/utils/buildInfo.ts b/site/src/utils/buildInfo.ts index 0306dc0a1d..5a578e2ff6 100644 --- a/site/src/utils/buildInfo.ts +++ b/site/src/utils/buildInfo.ts @@ -23,27 +23,28 @@ export const getStaticBuildInfo = () => { return CACHED_BUILD_INFO; }; -// Check if the current build is a development build. -// Development builds have versions containing "-devel" or "v0.0.0". -// This matches the backend's buildinfo.IsDev() logic. -export const isDevBuild = (input: BuildInfoResponse): boolean => { - const version = input.version; - if (!version) { - return false; +type PrereleaseFlag = "devel" | "rc" | undefined; + +/** Classifies the dashboard build version for dev vs RC styling and experiments. */ +export const getPrereleaseFlag = ( + input?: BuildInfoResponse, +): PrereleaseFlag => { + // If no input is provided, return undefined. + if (!input) { + return undefined; } + const version = input.version; + // Check for dev version pattern (contains "-devel") or no version (v0.0.0) - return version.includes("-devel") || version === "v0.0.0"; -}; - -// Check if the current build is a release candidate. Release -// candidates have versions containing "-rc." (e.g. v2.32.0-rc.0, -// v2.32.0-rc.1+abc123, v2.33.0-rc.1-devel+727ec00f7). -export const isRcBuild = (input: BuildInfoResponse): boolean => { - const version = input.version; - if (!version) { - return false; + if (version.includes("-devel") || version === "v0.0.0") { + return "devel"; } - return version.includes("-rc."); + // Check if the current build is a release candidate. Release + // candidates have versions containing "-rc." (e.g. v2.32.0-rc.0, + // v2.32.0-rc.1+abc123, v2.33.0-rc.1-devel+727ec00f7). + if (version.includes("-rc.")) { + return "rc"; + } };