diff --git a/Makefile b/Makefile index 817d106fae..b2e1d7f315 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ # Add `## Description` after the target to make it appear in `make help` .PHONY: all build dev start clean help -.PHONY: build-renderer +.PHONY: build-renderer version .PHONY: lint lint-fix fmt fmt-check fmt-shell fmt-nix fmt-nix-check fmt-shell-check typecheck static-check .PHONY: test test-unit test-integration test-watch test-coverage test-e2e .PHONY: dist dist-mac dist-win dist-linux @@ -81,9 +81,12 @@ build-renderer: ensure-deps src/version.ts ## Build renderer process @echo "Building renderer..." @bun x vite build -src/version.ts: ## Generate version file +# Always regenerate version file (marked as .PHONY above) +version: ## Generate version file @./scripts/generate-version.sh +src/version.ts: version + ## Quality checks (can run in parallel) static-check: lint typecheck fmt-check ## Run all static checks diff --git a/scripts/generate-version.sh b/scripts/generate-version.sh index 954594b927..ee43894642 100755 --- a/scripts/generate-version.sh +++ b/scripts/generate-version.sh @@ -1,7 +1,8 @@ #!/bin/bash # Generate version.ts with git information -VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "unknown") +GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") +GIT_DESCRIBE=$(git describe --tags --always --dirty 2>/dev/null || echo "unknown") TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") cat >src/version.ts <src/version.ts < - ` + width: ${(props) => (props.collapsed ? "32px" : "280px")}; + height: 100vh; + background: #252526; + border-right: 1px solid #1e1e1e; + display: flex; + flex-direction: column; + flex-shrink: 0; + transition: width 0.2s ease; + overflow: hidden; +`; + +interface LeftSidebarProps { + projects: Map; + workspaceMetadata: Map; + selectedWorkspace: WorkspaceSelection | null; + onSelectWorkspace: (selection: WorkspaceSelection) => void; + onAddProject: () => void; + onAddWorkspace: (projectPath: string) => void; + onRemoveProject: (path: string) => void; + onRemoveWorkspace: (workspaceId: string) => Promise<{ success: boolean; error?: string }>; + onRenameWorkspace: ( + workspaceId: string, + newName: string + ) => Promise<{ success: boolean; error?: string }>; + getWorkspaceState: (workspaceId: string) => WorkspaceState; + collapsed: boolean; + onToggleCollapsed: () => void; + onGetSecrets: (projectPath: string) => Promise; + onUpdateSecrets: (projectPath: string, secrets: Secret[]) => Promise; +} + +export function LeftSidebar(props: LeftSidebarProps) { + const { collapsed, ...projectSidebarProps } = props; + + return ( + + {!collapsed && } + + + ); +} diff --git a/src/components/ProjectSidebar.tsx b/src/components/ProjectSidebar.tsx index 8a15e5bd09..e233746a18 100644 --- a/src/components/ProjectSidebar.tsx +++ b/src/components/ProjectSidebar.tsx @@ -17,17 +17,12 @@ import SecretsModal from "./SecretsModal"; import type { Secret } from "@/types/secrets"; // Styled Components -const SidebarContainer = styled.div<{ collapsed?: boolean }>` - width: ${(props) => (props.collapsed ? "32px" : "280px")}; - height: 100vh; - background: #252526; - border-right: 1px solid #1e1e1e; +const SidebarContent = styled.div` display: flex; flex-direction: column; - flex-shrink: 0; - font-family: var(--font-primary); - transition: width 0.2s ease; + flex: 1; overflow: hidden; + font-family: var(--font-primary); `; const SidebarHeader = styled.div` @@ -563,7 +558,7 @@ const ProjectSidebar: React.FC = ({ }, [selectedWorkspace, onAddWorkspace]); return ( - + {!collapsed && ( <> @@ -794,7 +789,7 @@ const ProjectSidebar: React.FC = ({ , document.body )} - + ); }; diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx new file mode 100644 index 0000000000..12c3927608 --- /dev/null +++ b/src/components/TitleBar.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { VERSION } from "@/version"; +import { TooltipWrapper, Tooltip } from "./Tooltip"; + +const TitleBarContainer = styled.div` + padding: 8px 16px; + background: #1e1e1e; + border-bottom: 1px solid #3c3c3c; + display: flex; + align-items: center; + justify-content: space-between; + font-family: var(--font-primary); + font-size: 11px; + color: #858585; + user-select: none; + flex-shrink: 0; +`; + +const TitleText = styled.div` + font-weight: normal; + letter-spacing: 0.5px; +`; + +const BuildInfo = styled.div` + font-size: 10px; + opacity: 0.7; + cursor: default; +`; + +function formatUSDate(isoDate: string): string { + const date = new Date(isoDate); + const month = String(date.getUTCMonth() + 1).padStart(2, "0"); + const day = String(date.getUTCDate()).padStart(2, "0"); + const year = date.getUTCFullYear(); + return `${month}/${day}/${year}`; +} + +function formatExtendedTimestamp(isoDate: string): string { + const date = new Date(isoDate); + return date.toLocaleString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + timeZoneName: "short", + }); +} + +export function TitleBar() { + const buildDate = formatUSDate(VERSION.buildTime); + const extendedTimestamp = formatExtendedTimestamp(VERSION.buildTime); + + return ( + + cmux {VERSION.git_describe} + + {buildDate} + Built at {extendedTimestamp} + + + ); +}