authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-06-19 20:16:37+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-19 11:16:37-07:00
log7dc182337cfddf3de1a3b2c2e1ccae810f60901a
tree9314a4fe0f31ab5e4780008753357a2474360563
parent13917df1fe48890f794daad8c2d94f124b18b9fe
signature Signed by PGP key B5690EEEBB952194

[codex] fix: show nightly badge from primary web server version (#3103)

Co-authored-by: codex <codex@users.noreply.github.com>

3 files changed, 59 insertions(+), 2 deletions(-)

apps/web/src/components/Sidebar.logic.test.ts+39
......@@ -14,6 +14,7 @@ import {
1414 resolveProjectStatusIndicator,
1515 resolveSidebarNewThreadSeedContext,
1616 resolveSidebarNewThreadEnvMode,
17 resolveSidebarStageBadgeLabel,
1718 resolveThreadRowClassName,
1819 resolveThreadStatusPill,
1920 shouldClearThreadSelectionOnMouseDown,
......@@ -36,6 +37,44 @@ import {
3637
3738const localEnvironmentId = EnvironmentId.make("environment-local");
3839
40describe("resolveSidebarStageBadgeLabel", () => {
41 it("returns Nightly for nightly primary server versions", () => {
42 expect(
43 resolveSidebarStageBadgeLabel({
44 primaryServerVersion: "0.0.28-nightly.20260616.12",
45 fallbackStageLabel: "Alpha",
46 }),
47 ).toBe("Nightly");
48 });
49
50 it("returns the fallback label for stable primary server versions", () => {
51 expect(
52 resolveSidebarStageBadgeLabel({
53 primaryServerVersion: "0.0.27",
54 fallbackStageLabel: "Alpha",
55 }),
56 ).toBe("Alpha");
57 });
58
59 it("returns the fallback label when the primary server version is missing", () => {
60 expect(
61 resolveSidebarStageBadgeLabel({
62 primaryServerVersion: null,
63 fallbackStageLabel: "Dev",
64 }),
65 ).toBe("Dev");
66 });
67
68 it("returns the fallback label for malformed nightly prerelease versions", () => {
69 expect(
70 resolveSidebarStageBadgeLabel({
71 primaryServerVersion: "0.0.28-nightly.20260616",
72 fallbackStageLabel: "Alpha",
73 }),
74 ).toBe("Alpha");
75 });
76});
77
3978function makeLatestTurn(overrides?: {
4079 completedAt?: string | null;
4180 startedAt?: string | null;
apps/web/src/components/Sidebar.logic.ts+11
......@@ -12,6 +12,7 @@ import { isLatestTurnSettled } from "../session-logic";
1212
1313export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";
1414export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100;
15const NIGHTLY_SERVER_VERSION_PATTERN = /-nightly\.\d{8}\.\d+$/;
1516// Visible sidebar rows are prewarmed into the thread-detail cache so opening a
1617// nearby thread usually reuses an already-hot subscription.
1718export const SIDEBAR_THREAD_PREWARM_LIMIT = 10;
......@@ -64,6 +65,16 @@ export interface ThreadJumpHintVisibilityController {
6465 dispose: () => void;
6566}
6667
68export function resolveSidebarStageBadgeLabel(input: {
69 primaryServerVersion: string | null | undefined;
70 fallbackStageLabel: string;
71}): string {
72 return input.primaryServerVersion &&
73 NIGHTLY_SERVER_VERSION_PATTERN.test(input.primaryServerVersion)
74 ? "Nightly"
75 : input.fallbackStageLabel;
76}
77
6778export function createThreadJumpHintVisibilityController(input: {
6879 delayMs: number;
6980 onVisibilityChange: (visible: boolean) => void;
apps/web/src/components/Sidebar.tsx+9-2
......@@ -188,6 +188,7 @@ import {
188188 orderItemsByPreferredIds,
189189 shouldClearThreadSelectionOnMouseDown,
190190 sortProjectsForSidebar,
191 resolveSidebarStageBadgeLabel,
191192 useThreadJumpHintVisibility,
192193 ThreadStatusPill,
193194} from "./Sidebar.logic";
......@@ -197,7 +198,7 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
197198import { useIsMobile } from "~/hooks/useMediaQuery";
198199import { CommandDialogTrigger } from "./ui/command";
199200import { useSettings, useUpdateSettings } from "~/hooks/useSettings";
200import { primaryServerKeybindingsAtom } from "../state/server";
201import { primaryServerConfigAtom, primaryServerKeybindingsAtom } from "../state/server";
201202import {
202203 derivePhysicalProjectKey,
203204 deriveProjectGroupingOverrideKey,
......@@ -2680,6 +2681,12 @@ const SidebarChromeHeader = memo(function SidebarChromeHeader({
26802681}: {
26812682 isElectron: boolean;
26822683}) {
2684 const primaryServerVersion =
2685 useAtomValue(primaryServerConfigAtom)?.environment.serverVersion ?? null;
2686 const stageBadgeLabel = resolveSidebarStageBadgeLabel({
2687 primaryServerVersion,
2688 fallbackStageLabel: APP_STAGE_LABEL,
2689 });
26832690 const wordmark = (
26842691 <div className="flex items-center gap-2">
26852692 <SidebarTrigger className="shrink-0 md:hidden" />
......@@ -2696,7 +2703,7 @@ const SidebarChromeHeader = memo(function SidebarChromeHeader({
26962703 Code
26972704 </span>
26982705 <span className="rounded-full bg-muted/50 px-1.5 py-0.5 text-[8px] font-medium uppercase tracking-[0.18em] text-muted-foreground/60">
2699 {APP_STAGE_LABEL}
2706 {stageBadgeLabel}
27002707 </span>
27012708 </Link>
27022709 }