From 4e392bb1dc6df26f8f0b3e6610ca0d9384676c33 Mon Sep 17 00:00:00 2001 From: Code UX Date: Thu, 9 Jul 2026 04:07:43 +0000 Subject: [PATCH 1/2] feat(task T02): implement via codex --- dashboard/src/v2/components/TitleBar.tsx | 26 ++++--- .../v2/components/__tests__/TitleBar.test.tsx | 72 +++++++++++++++---- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/dashboard/src/v2/components/TitleBar.tsx b/dashboard/src/v2/components/TitleBar.tsx index 47f2492edf..fb588cafa1 100644 --- a/dashboard/src/v2/components/TitleBar.tsx +++ b/dashboard/src/v2/components/TitleBar.tsx @@ -2,6 +2,7 @@ import type { FunctionComponent, JSX } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Copy, Download, Minus, Square, X } from "lucide-preact"; import { RobotLogo } from "./brand/RobotLogo.js"; +import { useUpdateStatus } from "../hooks/use-update-status.js"; declare const __APP_VERSION__: string; @@ -21,6 +22,7 @@ export const TitleBar: FunctionComponent = ({ appearanceVariant = const windowApi = desktop?.window; const [platform, setPlatform] = useState(() => resolvePlatform(desktop?.platform)); const [isMaximized, setIsMaximized] = useState(false); + const { updateAvailable, latestVersion } = useUpdateStatus(); useEffect(() => { if (!windowApi) return; @@ -55,6 +57,7 @@ export const TitleBar: FunctionComponent = ({ appearanceVariant = event.stopPropagation(); void desktop?.openUpdates?.(); }; + const updateLabel = latestVersion ? `Update available: v${latestVersion}` : "Open updates"; const controls = isMac ? null : (
@@ -129,16 +132,19 @@ export const TitleBar: FunctionComponent = ({ appearanceVariant = v{__APP_VERSION__} - + {updateAvailable ? ( + + ) : null}
{controls} diff --git a/dashboard/src/v2/components/__tests__/TitleBar.test.tsx b/dashboard/src/v2/components/__tests__/TitleBar.test.tsx index 2b2078cb3a..470d101cf5 100644 --- a/dashboard/src/v2/components/__tests__/TitleBar.test.tsx +++ b/dashboard/src/v2/components/__tests__/TitleBar.test.tsx @@ -6,11 +6,41 @@ import { h } from "preact"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen } from "@testing-library/preact"; import "@testing-library/jest-dom/vitest"; +import { useUpdateStatus } from "../../hooks/use-update-status.js"; import { TitleBar } from "../TitleBar.js"; +vi.mock("../../hooks/use-update-status.js", () => ({ + useUpdateStatus: vi.fn(), +})); + +const mockUseUpdateStatus = vi.mocked(useUpdateStatus); + +const installDesktopApi = () => { + const desktopApi = { + platform: "linux", + renderProfile: "standard" as const, + pickDirectory: vi.fn(), + openUpdates: vi.fn().mockResolvedValue(true), + window: { + getState: vi.fn().mockResolvedValue({ isMaximized: false, isFullScreen: false, platform: "linux" }), + onStateChange: vi.fn(() => () => {}), + minimize: vi.fn(), + toggleMaximize: vi.fn().mockResolvedValue(true), + close: vi.fn(), + }, + }; + window.codeUxDesktop = desktopApi; + return desktopApi; +}; + describe("TitleBar", () => { beforeEach(() => { vi.stubGlobal("__APP_VERSION__", "0.8.9"); + mockUseUpdateStatus.mockReturnValue({ + status: null, + updateAvailable: false, + latestVersion: null, + }); }); afterEach(() => { @@ -24,27 +54,39 @@ describe("TitleBar", () => { expect(container).toBeEmptyDOMElement(); - window.codeUxDesktop = { - platform: "linux", - renderProfile: "standard", - pickDirectory: vi.fn(), - openUpdates: vi.fn().mockResolvedValue(true), - window: { - getState: vi.fn().mockResolvedValue({ isMaximized: false, isFullScreen: false, platform: "linux" }), - onStateChange: vi.fn(() => () => {}), - minimize: vi.fn(), - toggleMaximize: vi.fn().mockResolvedValue(true), - close: vi.fn(), - }, - }; + installDesktopApi(); rerender(); expect(screen.getByText("v0.8.9")).toBeInTheDocument(); - const updateButton = screen.getByRole("button", { name: "Open updates" }); + }); + + it("renders the update button with the available version and opens updates when clicked", () => { + mockUseUpdateStatus.mockReturnValue({ + status: null, + updateAvailable: true, + latestVersion: "0.9.0", + }); + const desktopApi = installDesktopApi(); + + render(); + + const updateButton = screen.getByRole("button", { name: "Update available: v0.9.0" }); fireEvent.click(updateButton); - expect(window.codeUxDesktop.openUpdates).toHaveBeenCalledTimes(1); + expect(desktopApi.openUpdates).toHaveBeenCalledTimes(1); + }); + + it("hides the update button when no update is available while keeping title and window controls", () => { + installDesktopApi(); + + render(); + + expect(screen.queryByRole("button", { name: /update/i })).not.toBeInTheDocument(); + expect(screen.getByText("v0.8.9")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Minimize window" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Maximize window" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Close window" })).toBeInTheDocument(); }); }); From 44f86f4716727eacfd190bd34f6cafc615cd053e Mon Sep 17 00:00:00 2001 From: Code UX Date: Thu, 9 Jul 2026 04:13:35 +0000 Subject: [PATCH 2/2] fix(ci): resolve failing checks on task/feature-codux-199-fix-update-button-t02-codex-mrczfplp --- tests/dashboard/v2/title-bar.test.tsx | 47 +++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/tests/dashboard/v2/title-bar.test.tsx b/tests/dashboard/v2/title-bar.test.tsx index 160842e510..0d5243d11c 100644 --- a/tests/dashboard/v2/title-bar.test.tsx +++ b/tests/dashboard/v2/title-bar.test.tsx @@ -5,9 +5,15 @@ import { act } from "preact/test-utils"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/preact"; import "@testing-library/jest-dom/vitest"; +import { useUpdateStatus } from "../../../dashboard/src/v2/hooks/use-update-status.js"; import { TitleBar } from "../../../dashboard/src/v2/components/TitleBar.js"; +vi.mock("../../../dashboard/src/v2/hooks/use-update-status.js", () => ({ + useUpdateStatus: vi.fn(), +})); + type WindowStateListener = (state: CodeUxWindowState) => void; +const mockUseUpdateStatus = vi.mocked(useUpdateStatus); const createDesktopBridge = () => { let stateListener: WindowStateListener | null = null; @@ -41,6 +47,11 @@ const createDesktopBridge = () => { describe("TitleBar", () => { beforeEach(() => { vi.stubGlobal("__APP_VERSION__", "2.3.4"); + mockUseUpdateStatus.mockReturnValue({ + status: null, + updateAvailable: false, + latestVersion: null, + }); }); afterEach(() => { @@ -57,7 +68,7 @@ describe("TitleBar", () => { expect(container).toBeEmptyDOMElement(); }); - it("renders Code UX, the version, and an update action when the desktop window API exists", () => { + it("renders Code UX, the version, and the window controls when the desktop window API exists", () => { const { bridge } = createDesktopBridge(); window.codeUxDesktop = bridge; @@ -65,14 +76,10 @@ describe("TitleBar", () => { expect(screen.getByRole("img", { name: "Code UX" })).toBeInTheDocument(); expect(screen.getByText("v2.3.4")).toBeInTheDocument(); - - const updateButton = screen.getByRole("button", { name: "Open updates" }); - expect(updateButton).toHaveTextContent("Update"); - expect(updateButton).toHaveClass("titlebar-no-drag"); - - fireEvent.click(updateButton); - - expect(bridge.openUpdates).toHaveBeenCalledTimes(1); + expect(screen.queryByRole("button", { name: /update/i })).not.toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Minimize window" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Maximize window" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Close window" })).toBeInTheDocument(); }); it("double-clicks the non-interactive title-bar area to toggle maximize", async () => { @@ -102,7 +109,6 @@ describe("TitleBar", () => { render(); const controls = [ - screen.getByRole("button", { name: "Open updates" }), screen.getByRole("button", { name: "Minimize window" }), screen.getByRole("button", { name: "Maximize window" }), screen.getByRole("button", { name: "Close window" }), @@ -116,6 +122,27 @@ describe("TitleBar", () => { expect(bridge.window.toggleMaximize).not.toHaveBeenCalled(); }); + it("renders the update affordance with the available version and opens updates when clicked", () => { + mockUseUpdateStatus.mockReturnValue({ + status: null, + updateAvailable: true, + latestVersion: "2.4.0", + }); + const { bridge } = createDesktopBridge(); + window.codeUxDesktop = bridge; + + render(); + + const updateButton = screen.getByRole("button", { name: "Update available: v2.4.0" }); + + expect(updateButton).toHaveTextContent("Update"); + expect(updateButton).toHaveClass("titlebar-no-drag"); + + fireEvent.click(updateButton); + + expect(bridge.openUpdates).toHaveBeenCalledTimes(1); + }); + it("updates the maximize button label and icon from state-change events", async () => { const { bridge, emitWindowState } = createDesktopBridge(); window.codeUxDesktop = bridge;