Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1d6a676
feat: AI-generated workspace creation on first message
kylecarbs Nov 2, 2025
a3f7319
chore: format ipcMain.ts
kylecarbs Nov 2, 2025
f9e5f95
fix: Store branch name in 'name' field, display title in 'displayName'
kylecarbs Nov 2, 2025
0ecb461
fmt: Run prettier
kylecarbs Nov 2, 2025
2fd35e7
fmt: Prettier formatting for WorkspaceListItem
kylecarbs Nov 2, 2025
d4f9f4c
fix: Don't use context hooks in FirstMessageInput
kylecarbs Nov 2, 2025
6c93c76
fix: Remove unused import
kylecarbs Nov 2, 2025
f2f1866
refactor: Remove ProjectSelector, use single-project detection
kylecarbs Nov 2, 2025
72dfcf4
fix: Actually replace ProjectSelector usage in App.tsx
kylecarbs Nov 2, 2025
7b46c7b
fix: Tailwind class order
kylecarbs Nov 2, 2025
4e3e500
refactor: Address PR review comments
kylecarbs Nov 10, 2025
bd56390
chore: Trigger CI
kylecarbs Nov 10, 2025
407fc18
fmt: Run prettier on ipcMain.ts
kylecarbs Nov 10, 2025
9342bb9
fix: Don't use context hooks in FirstMessageInput
kylecarbs Nov 10, 2025
5e1bb21
fix: Await getAllWorkspaceMetadata (now async after rebase)
kylecarbs Nov 13, 2025
962175c
🤖 fix: Await editConfig promise in workspace creation
kylecarbs Nov 13, 2025
2676901
🤖 ci: retry integration tests (suspected flaky ollama test)
kylecarbs Nov 13, 2025
86aef37
🤖 feat: Show FirstMessageInput when clicking Add Workspace button
kylecarbs Nov 13, 2025
42fbfaf
🤖 style: prettier auto-format
kylecarbs Nov 13, 2025
0b58a32
🤖 feat: Refactor FirstMessageInput to match ChatInput styling
kylecarbs Nov 13, 2025
5181bda
🤖 style: prettier formatting
kylecarbs Nov 13, 2025
0229af1
🤖 feat: Improve FirstMessageInput UX
kylecarbs Nov 13, 2025
038c399
🤖 fix: Add flex-1 to FirstMessageInput container
kylecarbs Nov 13, 2025
b10dc46
feat: add mode selection (Exec/Plan) to FirstMessageInput
kylecarbs Nov 13, 2025
a78d92e
feat: wire up thinking level, 1M context, and mode to FirstMessageInp…
kylecarbs Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 54 additions & 42 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useResumeManager } from "./hooks/useResumeManager";
import { useUnreadTracking } from "./hooks/useUnreadTracking";
import { useAutoCompactContinue } from "./hooks/useAutoCompactContinue";
import { useWorkspaceStoreRaw, useWorkspaceRecency } from "./stores/WorkspaceStore";
import { FirstMessageInput } from "./components/FirstMessageInput";

import { useStableReference, compareMaps } from "./hooks/useStableReference";
import { CommandRegistryProvider, useCommandRegistry } from "./contexts/CommandRegistryContext";
Expand Down Expand Up @@ -57,6 +58,9 @@ function AppInner() {
const [workspaceModalLoadError, setWorkspaceModalLoadError] = useState<string | null>(null);
const workspaceModalProjectRef = useRef<string | null>(null);

// Track when we're in "new workspace creation" mode (show FirstMessageInput)
const [pendingNewWorkspaceProject, setPendingNewWorkspaceProject] = useState<string | null>(null);

// Auto-collapse sidebar on mobile by default
const isMobile = typeof window !== "undefined" && window.innerWidth <= 768;
const [sidebarCollapsed, setSidebarCollapsed] = usePersistedState("sidebarCollapsed", isMobile);
Expand Down Expand Up @@ -114,9 +118,10 @@ function AppInner() {
window.history.replaceState(null, "", newHash);
}

// Update window title with workspace name
// Update window title with workspace name (prefer displayName if available)
const metadata = workspaceMetadata.get(selectedWorkspace.workspaceId);
const workspaceName =
workspaceMetadata.get(selectedWorkspace.workspaceId)?.name ?? selectedWorkspace.workspaceId;
metadata?.displayName ?? metadata?.name ?? selectedWorkspace.workspaceId;
const title = `${workspaceName} - ${selectedWorkspace.projectName} - cmux`;
void window.api.window.setTitle(title);
} else {
Expand Down Expand Up @@ -169,46 +174,15 @@ function AppInner() {
[removeProject, selectedWorkspace, setSelectedWorkspace]
);

const handleAddWorkspace = useCallback(async (projectPath: string) => {
const projectName = projectPath.split("/").pop() ?? projectPath.split("\\").pop() ?? "project";

workspaceModalProjectRef.current = projectPath;
setWorkspaceModalProject(projectPath);
setWorkspaceModalProjectName(projectName);
setWorkspaceModalBranches([]);
setWorkspaceModalDefaultTrunk(undefined);
setWorkspaceModalLoadError(null);
setWorkspaceModalOpen(true);

try {
const branchResult = await window.api.projects.listBranches(projectPath);

// Guard against race condition: only update state if this is still the active project
if (workspaceModalProjectRef.current !== projectPath) {
return;
}

const sanitizedBranches = Array.isArray(branchResult?.branches)
? branchResult.branches.filter((branch): branch is string => typeof branch === "string")
: [];

const recommended =
typeof branchResult?.recommendedTrunk === "string" &&
sanitizedBranches.includes(branchResult.recommendedTrunk)
? branchResult.recommendedTrunk
: sanitizedBranches[0];

setWorkspaceModalBranches(sanitizedBranches);
setWorkspaceModalDefaultTrunk(recommended);
setWorkspaceModalLoadError(null);
} catch (err) {
console.error("Failed to load branches for modal:", err);
const message = err instanceof Error ? err.message : "Unknown error";
setWorkspaceModalLoadError(
`Unable to load branches automatically: ${message}. You can still enter the trunk branch manually.`
);
}
}, []);
const handleAddWorkspace = useCallback(
(projectPath: string) => {
// Show FirstMessageInput for this project
setPendingNewWorkspaceProject(projectPath);
// Clear any selected workspace so FirstMessageInput is shown
setSelectedWorkspace(null);
},
[setSelectedWorkspace]
);

// Memoize callbacks to prevent LeftSidebar/ProjectSidebar re-renders
const handleAddProjectCallback = useCallback(() => {
Expand Down Expand Up @@ -646,6 +620,44 @@ function AppInner() {
}
/>
</ErrorBoundary>
) : pendingNewWorkspaceProject || projects.size === 1 ? (
(() => {
const projectPath = pendingNewWorkspaceProject ?? Array.from(projects.keys())[0];
const projectName =
projectPath.split("/").pop() ?? projectPath.split("\\").pop() ?? "Project";
return (
<FirstMessageInput
projectPath={projectPath}
projectName={projectName}
onWorkspaceCreated={(metadata) => {
// Add to workspace metadata map
setWorkspaceMetadata((prev) => new Map(prev).set(metadata.id, metadata));

// Switch to new workspace
handleWorkspaceSwitch({
workspaceId: metadata.id,
projectPath: metadata.projectPath,
projectName: metadata.projectName,
namedWorkspacePath: metadata.namedWorkspacePath,
});

// Track telemetry
telemetry.workspaceCreated(metadata.id);

// Clear pending state
setPendingNewWorkspaceProject(null);
}}
onCancel={
pendingNewWorkspaceProject
? () => {
// User cancelled workspace creation - clear pending state
setPendingNewWorkspaceProject(null);
}
: undefined
}
/>
);
})()
) : (
<div
className="[&_p]:text-muted mx-auto w-full max-w-3xl text-center [&_h2]:mb-4 [&_h2]:font-bold [&_h2]:tracking-tight [&_h2]:text-white [&_p]:leading-[1.6]"
Expand Down
Loading
Loading