feat(site): add under-construction navbar stripes for pre-release builds (#24157)

Dev and RC builds now show diagonal warning stripes in the navbar plus a
centered version badge, making it impossible to miss which build you're
running.

**Devel build:** amber "warning" from theme

**RC build:** sky "pending" from theme

> 🤖 Written by a Coder Agent. Will be reviewed by a human.
This commit is contained in:
Cian Johnston
2026-04-08 20:10:03 +00:00
committed by GitHub
parent e3a0dcd6fc
commit 461a31e5d8
5 changed files with 215 additions and 2 deletions
+44
View File
@@ -156,6 +156,50 @@
}
}
@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;
@@ -3,6 +3,7 @@ import { userEvent, within } from "storybook/test";
import type { TasksFilter } from "#/api/typesGenerated";
import { chromaticWithTablet } from "#/testHelpers/chromatic";
import {
MockBuildInfo,
MockTasks,
MockUserMember,
MockUserOwner,
@@ -170,3 +171,33 @@ export const DefaultSupportLinks: Story = {
],
},
};
export const DevelBuild: Story = {
args: {
buildInfo: {
...MockBuildInfo,
version: "v2.21.0-devel+abc123",
external_url: "https://github.com/coder/coder/commit/abc123",
},
},
};
export const RcBuild: Story = {
args: {
buildInfo: {
...MockBuildInfo,
version: "v2.21.0-rc.1+def456",
external_url: "https://github.com/coder/coder/releases/tag/v2.21.0-rc.1",
},
},
};
export const RcDevelBuild: Story = {
args: {
buildInfo: {
...MockBuildInfo,
version: "v2.33.0-rc.1-devel+727ec00f7",
external_url: "https://github.com/coder/coder/commit/727ec00f7",
},
},
};
@@ -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 } from "#/utils/buildInfo";
import { isDevBuild, isRcBuild } from "#/utils/buildInfo";
import { cn } from "#/utils/cn";
import { DeploymentDropdown } from "./DeploymentDropdown";
import { MobileMenu } from "./MobileMenu";
@@ -59,8 +59,17 @@ export const NavbarView: FC<NavbarViewProps> = ({
canViewAIBridge,
proxyContextValue,
}) => {
const isDev = buildInfo ? isDevBuild(buildInfo) : false;
const isRc = buildInfo ? isRcBuild(buildInfo) : false;
const isPreRelease = isDev || isRc;
return (
<div className="sticky top-0 bg-surface-primary z-40 border-0 border-b border-solid h-[72px] min-h-[72px] flex items-center leading-none px-6">
<div
className={cn(
"sticky top-0 bg-surface-primary z-40 border-0 border-b border-solid h-[72px] min-h-[72px] flex items-center leading-none px-6 relative",
isRc ? "navbar-stripe-rc" : isDev ? "navbar-stripe-devel" : undefined,
)}
>
<NavLink to="/workspaces">
{logo_url ? (
<ExternalImage className="h-7" src={logo_url} alt="Custom Logo" />
@@ -71,6 +80,23 @@ export const NavbarView: FC<NavbarViewProps> = ({
<NavItems className="ml-4" user={user} />
{isPreRelease && buildInfo?.version && (
<a
href={buildInfo.external_url}
target="_blank"
rel="noreferrer"
className="absolute top-1 left-1/2 -translate-x-1/2 no-underline z-10"
>
<Badge
variant={isRc ? "info" : "warning"}
size="sm"
className="font-mono"
>
{buildInfo.version}
</Badge>
</a>
)}
<div className="flex items-center gap-3 ml-auto">
{supportLinks.filter(isNavbarLink).map((link) => (
<div key={link.name} className="hidden md:block">
+100
View File
@@ -0,0 +1,100 @@
import type { BuildInfoResponse } from "#/api/typesGenerated";
import { isDevBuild, isRcBuild } from "./buildInfo";
const baseBuildInfo: BuildInfoResponse = {
agent_api_version: "1.0",
provisioner_api_version: "1.1",
external_url: "https://github.com/coder/coder",
version: "",
dashboard_url: "https://example.com",
workspace_proxy: false,
upgrade_message: "",
deployment_id: "test",
telemetry: false,
};
describe("isDevBuild", () => {
it("returns true for -devel versions", () => {
expect(
isDevBuild({ ...baseBuildInfo, version: "v2.16.0-devel+abc123" }),
).toBe(true);
});
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", () => {
expect(
isDevBuild({
...baseBuildInfo,
version: "v2.33.0-rc.1-devel+727ec00f7",
}),
).toBe(true);
});
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", () => {
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({
...baseBuildInfo,
version: "v2.33.0-rc.1-devel+727ec00f7",
}),
).toBe(true);
});
});
+12
View File
@@ -35,3 +35,15 @@ export const isDevBuild = (input: BuildInfoResponse): boolean => {
// 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;
}
return version.includes("-rc.");
};