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 {...@@ -14,6 +14,7 @@ import {
14 resolveProjectStatusIndicator,14 resolveProjectStatusIndicator,
15 resolveSidebarNewThreadSeedContext,15 resolveSidebarNewThreadSeedContext,
16 resolveSidebarNewThreadEnvMode,16 resolveSidebarNewThreadEnvMode,
17 resolveSidebarStageBadgeLabel,
17 resolveThreadRowClassName,18 resolveThreadRowClassName,
18 resolveThreadStatusPill,19 resolveThreadStatusPill,
19 shouldClearThreadSelectionOnMouseDown,20 shouldClearThreadSelectionOnMouseDown,
...@@ -36,6 +37,44 @@ import {...@@ -36,6 +37,44 @@ import {
3637
37const localEnvironmentId = EnvironmentId.make("environment-local");38const 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
39function makeLatestTurn(overrides?: {78function makeLatestTurn(overrides?: {
40 completedAt?: string | null;79 completedAt?: string | null;
41 startedAt?: string | null;80 startedAt?: string | null;
apps/web/src/components/Sidebar.logic.ts+11
...@@ -12,6 +12,7 @@ import { isLatestTurnSettled } from "../session-logic";...@@ -12,6 +12,7 @@ import { isLatestTurnSettled } from "../session-logic";
1212
13export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";13export const THREAD_SELECTION_SAFE_SELECTOR = "[data-thread-item], [data-thread-selection-safe]";
14export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100;14export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100;
15const NIGHTLY_SERVER_VERSION_PATTERN = /-nightly\.\d{8}\.\d+$/;
15// 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
16// nearby thread usually reuses an already-hot subscription.17// nearby thread usually reuses an already-hot subscription.
17export const SIDEBAR_THREAD_PREWARM_LIMIT = 10;18export const SIDEBAR_THREAD_PREWARM_LIMIT = 10;
...@@ -64,6 +65,16 @@ export interface ThreadJumpHintVisibilityController {...@@ -64,6 +65,16 @@ export interface ThreadJumpHintVisibilityController {
64 dispose: () => void;65 dispose: () => void;
65}66}
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
67export function createThreadJumpHintVisibilityController(input: {78export function createThreadJumpHintVisibilityController(input: {
68 delayMs: number;79 delayMs: number;
69 onVisibilityChange: (visible: boolean) => void;80 onVisibilityChange: (visible: boolean) => void;
apps/web/src/components/Sidebar.tsx+9-2
...@@ -188,6 +188,7 @@ import {...@@ -188,6 +188,7 @@ import {
188 orderItemsByPreferredIds,188 orderItemsByPreferredIds,
189 shouldClearThreadSelectionOnMouseDown,189 shouldClearThreadSelectionOnMouseDown,
190 sortProjectsForSidebar,190 sortProjectsForSidebar,
191 resolveSidebarStageBadgeLabel,
191 useThreadJumpHintVisibility,192 useThreadJumpHintVisibility,
192 ThreadStatusPill,193 ThreadStatusPill,
193} from "./Sidebar.logic";194} from "./Sidebar.logic";
...@@ -197,7 +198,7 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";...@@ -197,7 +198,7 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
197import { useIsMobile } from "~/hooks/useMediaQuery";198import { useIsMobile } from "~/hooks/useMediaQuery";
198import { CommandDialogTrigger } from "./ui/command";199import { CommandDialogTrigger } from "./ui/command";
199import { useSettings, useUpdateSettings } from "~/hooks/useSettings";200import { useSettings, useUpdateSettings } from "~/hooks/useSettings";
200import { primaryServerKeybindingsAtom } from "../state/server";201import { primaryServerConfigAtom, primaryServerKeybindingsAtom } from "../state/server";
201import {202import {
202 derivePhysicalProjectKey,203 derivePhysicalProjectKey,
203 deriveProjectGroupingOverrideKey,204 deriveProjectGroupingOverrideKey,
...@@ -2680,6 +2681,12 @@ const SidebarChromeHeader = memo(function SidebarChromeHeader({...@@ -2680,6 +2681,12 @@ const SidebarChromeHeader = memo(function SidebarChromeHeader({
2680}: {2681}: {
2681 isElectron: boolean;2682 isElectron: boolean;
2682}) {2683}) {
2684 const primaryServerVersion =
2685 useAtomValue(primaryServerConfigAtom)?.environment.serverVersion ?? null;
2686 const stageBadgeLabel = resolveSidebarStageBadgeLabel({
2687 primaryServerVersion,
2688 fallbackStageLabel: APP_STAGE_LABEL,
2689 });
2683 const wordmark = (2690 const wordmark = (
2684 <div className="flex items-center gap-2">2691 <div className="flex items-center gap-2">
2685 <SidebarTrigger className="shrink-0 md:hidden" />2692 <SidebarTrigger className="shrink-0 md:hidden" />
...@@ -2696,7 +2703,7 @@ const SidebarChromeHeader = memo(function SidebarChromeHeader({...@@ -2696,7 +2703,7 @@ const SidebarChromeHeader = memo(function SidebarChromeHeader({
2696 Code2703 Code
2697 </span>2704 </span>
2698 <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">2705 <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}
2700 </span>2707 </span>
2701 </Link>2708 </Link>
2702 }2709 }