Skip to content

Commit 1bb8c0a

Browse files
authored
🤖 Add title bar to left sidebar (#159)
Adds a compact title bar above the Projects section showing version and build date. ## Changes - **TitleBar component**: Displays `cmux <git-version> <build-date>` with tight vertical padding - **LeftSidebar component**: Wraps TitleBar and ProjectSidebar together - **ProjectSidebar refactored**: Removed container styling (now handled by LeftSidebar) - Ctrl+P collapse/expand applies to entire LeftSidebar including title bar ## Design - Title bar only visible when sidebar is expanded - Subtle styling: dark background (#1e1e1e), muted text (#858585) - Version on left, build date on right - Font size: 11px for title, 10px for date _Generated with `cmux`_
1 parent e732a87 commit 1bb8c0a

File tree

6 files changed

+134
-17
lines changed

6 files changed

+134
-17
lines changed

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
# Add `## Description` after the target to make it appear in `make help`
2020

2121
.PHONY: all build dev start clean help
22-
.PHONY: build-renderer
22+
.PHONY: build-renderer version
2323
.PHONY: lint lint-fix fmt fmt-check fmt-shell fmt-nix fmt-nix-check fmt-shell-check typecheck static-check
2424
.PHONY: test test-unit test-integration test-watch test-coverage test-e2e
2525
.PHONY: dist dist-mac dist-win dist-linux
@@ -81,9 +81,12 @@ build-renderer: ensure-deps src/version.ts ## Build renderer process
8181
@echo "Building renderer..."
8282
@bun x vite build
8383

84-
src/version.ts: ## Generate version file
84+
# Always regenerate version file (marked as .PHONY above)
85+
version: ## Generate version file
8586
@./scripts/generate-version.sh
8687

88+
src/version.ts: version
89+
8790
## Quality checks (can run in parallel)
8891
static-check: lint typecheck fmt-check ## Run all static checks
8992

scripts/generate-version.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
#!/bin/bash
22
# Generate version.ts with git information
33

4-
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "unknown")
4+
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
5+
GIT_DESCRIBE=$(git describe --tags --always --dirty 2>/dev/null || echo "unknown")
56
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
67

78
cat >src/version.ts <<EOF
89
// This file is auto-generated by scripts/generate-version.sh
910
// Do not edit manually
1011
1112
export const VERSION = {
12-
git: "${VERSION}",
13+
git_commit: "${GIT_COMMIT}",
14+
git_describe: "${GIT_DESCRIBE}",
1315
buildTime: "${TIMESTAMP}",
1416
};
1517
EOF
1618

17-
echo "Generated version.ts: ${VERSION} at ${TIMESTAMP}"
19+
echo "Generated version.ts: ${GIT_DESCRIBE} (${GIT_COMMIT}) at ${TIMESTAMP}"

src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { GlobalFonts } from "./styles/fonts";
66
import { GlobalScrollbars } from "./styles/scrollbars";
77
import type { ProjectConfig } from "./config";
88
import type { WorkspaceSelection } from "./components/ProjectSidebar";
9-
import ProjectSidebar from "./components/ProjectSidebar";
9+
import { LeftSidebar } from "./components/LeftSidebar";
1010
import NewWorkspaceModal from "./components/NewWorkspaceModal";
1111
import { AIView } from "./components/AIView";
1212
import { ErrorBoundary } from "./components/ErrorBoundary";
@@ -485,7 +485,7 @@ function AppInner() {
485485
<Global styles={globalStyles} />
486486
<GitStatusProvider workspaceMetadata={workspaceMetadata}>
487487
<AppContainer>
488-
<ProjectSidebar
488+
<LeftSidebar
489489
projects={projects}
490490
workspaceMetadata={workspaceMetadata}
491491
selectedWorkspace={selectedWorkspace}

src/components/LeftSidebar.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from "react";
2+
import styled from "@emotion/styled";
3+
import type { ProjectConfig } from "@/config";
4+
import type { WorkspaceMetadata } from "@/types/workspace";
5+
import type { WorkspaceSelection } from "./ProjectSidebar";
6+
import type { WorkspaceState } from "@/hooks/useWorkspaceAggregators";
7+
import type { Secret } from "@/types/secrets";
8+
import ProjectSidebar from "./ProjectSidebar";
9+
import { TitleBar } from "./TitleBar";
10+
11+
const LeftSidebarContainer = styled.div<{ collapsed?: boolean }>`
12+
width: ${(props) => (props.collapsed ? "32px" : "280px")};
13+
height: 100vh;
14+
background: #252526;
15+
border-right: 1px solid #1e1e1e;
16+
display: flex;
17+
flex-direction: column;
18+
flex-shrink: 0;
19+
transition: width 0.2s ease;
20+
overflow: hidden;
21+
`;
22+
23+
interface LeftSidebarProps {
24+
projects: Map<string, ProjectConfig>;
25+
workspaceMetadata: Map<string, WorkspaceMetadata>;
26+
selectedWorkspace: WorkspaceSelection | null;
27+
onSelectWorkspace: (selection: WorkspaceSelection) => void;
28+
onAddProject: () => void;
29+
onAddWorkspace: (projectPath: string) => void;
30+
onRemoveProject: (path: string) => void;
31+
onRemoveWorkspace: (workspaceId: string) => Promise<{ success: boolean; error?: string }>;
32+
onRenameWorkspace: (
33+
workspaceId: string,
34+
newName: string
35+
) => Promise<{ success: boolean; error?: string }>;
36+
getWorkspaceState: (workspaceId: string) => WorkspaceState;
37+
collapsed: boolean;
38+
onToggleCollapsed: () => void;
39+
onGetSecrets: (projectPath: string) => Promise<Secret[]>;
40+
onUpdateSecrets: (projectPath: string, secrets: Secret[]) => Promise<void>;
41+
}
42+
43+
export function LeftSidebar(props: LeftSidebarProps) {
44+
const { collapsed, ...projectSidebarProps } = props;
45+
46+
return (
47+
<LeftSidebarContainer collapsed={collapsed}>
48+
{!collapsed && <TitleBar />}
49+
<ProjectSidebar {...projectSidebarProps} collapsed={collapsed} />
50+
</LeftSidebarContainer>
51+
);
52+
}

src/components/ProjectSidebar.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,12 @@ import SecretsModal from "./SecretsModal";
1717
import type { Secret } from "@/types/secrets";
1818

1919
// Styled Components
20-
const SidebarContainer = styled.div<{ collapsed?: boolean }>`
21-
width: ${(props) => (props.collapsed ? "32px" : "280px")};
22-
height: 100vh;
23-
background: #252526;
24-
border-right: 1px solid #1e1e1e;
20+
const SidebarContent = styled.div`
2521
display: flex;
2622
flex-direction: column;
27-
flex-shrink: 0;
28-
font-family: var(--font-primary);
29-
transition: width 0.2s ease;
23+
flex: 1;
3024
overflow: hidden;
25+
font-family: var(--font-primary);
3126
`;
3227

3328
const SidebarHeader = styled.div`
@@ -563,7 +558,7 @@ const ProjectSidebar: React.FC<ProjectSidebarProps> = ({
563558
}, [selectedWorkspace, onAddWorkspace]);
564559

565560
return (
566-
<SidebarContainer collapsed={collapsed} role="navigation" aria-label="Projects">
561+
<SidebarContent role="navigation" aria-label="Projects">
567562
{!collapsed && (
568563
<>
569564
<SidebarHeader>
@@ -794,7 +789,7 @@ const ProjectSidebar: React.FC<ProjectSidebarProps> = ({
794789
</RemoveErrorToast>,
795790
document.body
796791
)}
797-
</SidebarContainer>
792+
</SidebarContent>
798793
);
799794
};
800795

src/components/TitleBar.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from "react";
2+
import styled from "@emotion/styled";
3+
import { VERSION } from "@/version";
4+
import { TooltipWrapper, Tooltip } from "./Tooltip";
5+
6+
const TitleBarContainer = styled.div`
7+
padding: 8px 16px;
8+
background: #1e1e1e;
9+
border-bottom: 1px solid #3c3c3c;
10+
display: flex;
11+
align-items: center;
12+
justify-content: space-between;
13+
font-family: var(--font-primary);
14+
font-size: 11px;
15+
color: #858585;
16+
user-select: none;
17+
flex-shrink: 0;
18+
`;
19+
20+
const TitleText = styled.div`
21+
font-weight: normal;
22+
letter-spacing: 0.5px;
23+
`;
24+
25+
const BuildInfo = styled.div`
26+
font-size: 10px;
27+
opacity: 0.7;
28+
cursor: default;
29+
`;
30+
31+
function formatUSDate(isoDate: string): string {
32+
const date = new Date(isoDate);
33+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
34+
const day = String(date.getUTCDate()).padStart(2, "0");
35+
const year = date.getUTCFullYear();
36+
return `${month}/${day}/${year}`;
37+
}
38+
39+
function formatExtendedTimestamp(isoDate: string): string {
40+
const date = new Date(isoDate);
41+
return date.toLocaleString("en-US", {
42+
month: "long",
43+
day: "numeric",
44+
year: "numeric",
45+
hour: "2-digit",
46+
minute: "2-digit",
47+
second: "2-digit",
48+
timeZoneName: "short",
49+
});
50+
}
51+
52+
export function TitleBar() {
53+
const buildDate = formatUSDate(VERSION.buildTime);
54+
const extendedTimestamp = formatExtendedTimestamp(VERSION.buildTime);
55+
56+
return (
57+
<TitleBarContainer>
58+
<TitleText>cmux {VERSION.git_describe}</TitleText>
59+
<TooltipWrapper>
60+
<BuildInfo>{buildDate}</BuildInfo>
61+
<Tooltip align="right">Built at {extendedTimestamp}</Tooltip>
62+
</TooltipWrapper>
63+
</TitleBarContainer>
64+
);
65+
}

0 commit comments

Comments
 (0)