Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
11 changes: 10 additions & 1 deletion apps/desktop/src/electron/ElectronMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ describe("ElectronMenu", () => {
const electronMenu = yield* ElectronMenu.ElectronMenu;
const selectedItemId = yield* electronMenu.showContextMenu({
window: makeWindow(2),
items: [{ id: "copy", label: "Copy" }],
items: [
{ id: "copy", label: "Copy" },
{ id: "delete", label: "Delete", destructive: true, separatorBefore: true },
],
position: Option.some({ x: 10.8, y: 20.2 }),
});

Expand All @@ -110,6 +113,12 @@ describe("ElectronMenu", () => {
enabled: true,
click: buildFromTemplateMock.mock.calls[0]?.[0][0].click,
});
assert.deepEqual(
buildFromTemplateMock.mock.calls[0]?.[0].map(
(item: Electron.MenuItemConstructorOptions) => item.type ?? item.label,
),
["Copy", "separator", "Delete"],
);
}).pipe(Effect.provide(TestLayer)),
);

Expand Down
10 changes: 9 additions & 1 deletion apps/desktop/src/electron/ElectronMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function normalizeContextMenuItems(source: readonly ContextMenuItem[]): ContextM
label: sourceItem.label,
destructive: sourceItem.destructive === true,
disabled: sourceItem.disabled === true,
...(sourceItem.separatorBefore === true ? { separatorBefore: true } : {}),
};

if (sourceItem.children) {
Expand Down Expand Up @@ -141,10 +142,17 @@ export const make = Effect.gen(function* () {
): Electron.MenuItemConstructorOptions[] => {
const template: Electron.MenuItemConstructorOptions[] = [];
let hasInsertedDestructiveSeparator = false;
const appendSeparator = () => {
if (template.length === 0 || template.at(-1)?.type === "separator") return;
template.push({ type: "separator" });
};

for (const item of entries) {
if (item.separatorBefore) {
appendSeparator();
}
if (item.destructive && !hasInsertedDestructiveSeparator && template.length > 0) {
template.push({ type: "separator" });
appendSeparator();
hasInsertedDestructiveSeparator = true;
}

Expand Down
58 changes: 32 additions & 26 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ import {
WifiOffIcon,
} from "lucide-react";
import { cn, randomHex } from "~/lib/utils";
import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";
import { stackedThreadToast, toastManager } from "./ui/toast";
import { decodeProjectScriptKeybindingRule } from "~/lib/projectScriptKeybindings";
import { type NewProjectScriptInput } from "./ProjectScriptsControl";
Expand Down Expand Up @@ -220,7 +219,11 @@ import {
import { appendPreviewAnnotationPrompt } from "../lib/previewAnnotation";
import { appendReviewCommentsToPrompt, type ReviewCommentContext } from "../reviewCommentContext";
import { environmentCatalog } from "../connection/catalog";
import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore";
import {
selectThreadTerminalCustomLabels,
selectThreadTerminalUiState,
useTerminalUiStateStore,
} from "../terminalUiStateStore";
import { useKnownTerminalSessions, useThreadRunningTerminalIds } from "../state/terminalSessions";
import { projectEnvironment } from "../state/projects";
import { useEnvironmentQuery } from "../state/query";
Expand Down Expand Up @@ -256,6 +259,7 @@ import { ChatHeader } from "./chat/ChatHeader";
import { PanelLayoutControls, RightPanelMaximizeControl } from "./chat/PanelLayoutControls";
import { type ExpandedImagePreview } from "./chat/ExpandedImagePreview";
import { NoActiveThreadState } from "./NoActiveThreadState";
import { WorkspacePageHeader } from "./WorkspacePageContainer";
import {
resolveEffectiveEnvMode,
resolveLocalCheckoutBranchMismatch,
Expand Down Expand Up @@ -653,6 +657,7 @@ interface PersistentThreadTerminalDrawerProps {
newShortcutLabel: string | undefined;
closeShortcutLabel: string | undefined;
keybindings: ResolvedKeybindingsConfig;
onHide: () => void;
onAddTerminalContext: (selection: TerminalContextSelection) => void;
}

Expand All @@ -667,6 +672,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra
newShortcutLabel,
closeShortcutLabel,
keybindings,
onHide,
onAddTerminalContext,
}: PersistentThreadTerminalDrawerProps) {
const openTerminal = useAtomCommand(terminalEnvironment.open, "terminal open");
Expand Down Expand Up @@ -990,6 +996,7 @@ const PersistentThreadTerminalDrawer = memo(function PersistentThreadTerminalDra
onSplitTerminal={splitTerminal}
onSplitTerminalVertical={splitTerminalVertical}
onNewTerminal={createNewTerminal}
onHide={onHide}
splitShortcutLabel={visible ? splitShortcutLabel : undefined}
splitVerticalShortcutLabel={visible ? splitVerticalShortcutLabel : undefined}
newShortcutLabel={visible ? newShortcutLabel : undefined}
Expand Down Expand Up @@ -1540,6 +1547,16 @@ function ChatViewContent(props: ChatViewProps) {
const canCheckoutPullRequestIntoThread = isLocalDraftThread;
const activeThreadId = activeThread?.id ?? null;
const activeThreadEnvironmentId = activeThread?.environmentId ?? null;
const activeThreadRef = useMemo(
() =>
activeThreadEnvironmentId && activeThreadId
? scopeThreadRef(activeThreadEnvironmentId, activeThreadId)
: null,
[activeThreadEnvironmentId, activeThreadId],
);
const activeTerminalCustomLabels = useTerminalUiStateStore((state) =>
selectThreadTerminalCustomLabels(state.terminalCustomLabelsByThreadKey, activeThreadRef),
);
const runningTerminalIds = useThreadRunningTerminalIds({
environmentId: activeThread?.environmentId ?? null,
threadId: activeThreadId,
Expand Down Expand Up @@ -1569,18 +1586,15 @@ function ChatViewContent(props: ChatViewProps) {
for (const session of activeThreadKnownSessions) {
labels.set(
session.target.terminalId,
resolveTerminalSessionLabel(session.target.terminalId, session.state.summary),
activeTerminalCustomLabels[session.target.terminalId] ??
resolveTerminalSessionLabel(session.target.terminalId, session.state.summary),
);
}
for (const [terminalId, label] of Object.entries(activeTerminalCustomLabels)) {
if (!labels.has(terminalId)) labels.set(terminalId, label);
}
return labels;
}, [activeThreadKnownSessions]);
const activeThreadRef = useMemo(
() =>
activeThreadEnvironmentId && activeThreadId
? scopeThreadRef(activeThreadEnvironmentId, activeThreadId)
: null,
[activeThreadEnvironmentId, activeThreadId],
);
}, [activeTerminalCustomLabels, activeThreadKnownSessions]);
const activeThreadKey = activeThreadRef ? scopedThreadKey(activeThreadRef) : null;
const [timelineAnchor, setTimelineAnchor] = useState<{
readonly threadKey: string | null;
Expand Down Expand Up @@ -2808,6 +2822,7 @@ function ChatViewContent(props: ChatViewProps) {
},
[activeThreadRef, storeSetTerminalOpen],
);
const hideTerminal = useCallback(() => setTerminalOpen(false), [setTerminalOpen]);
const toggleTerminalVisibility = useCallback(() => {
if (!activeThreadRef) return;
const nextOpen = !terminalUiState.terminalOpen;
Expand Down Expand Up @@ -6114,7 +6129,6 @@ function ChatViewContent(props: ChatViewProps) {
? "thread"
: "page"
}
chromeVariant="collapse"
composerDraftTarget={composerDraftTarget}
onStateChange={handlePullRequestTabStatusChange}
/>
Expand Down Expand Up @@ -6160,20 +6174,11 @@ function ChatViewContent(props: ChatViewProps) {
data-chat-column-maximized-away={rightPanelMaximized ? "true" : "false"}
>
{/* Top bar */}
<header
<WorkspacePageHeader
data-chat-header
className={cn(
"bg-background transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none",
isElectron
? cn(
"drag-region relative flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 sm:px-5",
reserveTitleBarControlInset &&
!inlineRightPanelOwnsTitleBar &&
"wco:pr-[var(--workspace-native-controls-inset)]",
)
: "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)]",
COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
)}
electron={isElectron}
reserveNativeControls={reserveTitleBarControlInset && !inlineRightPanelOwnsTitleBar}
className="relative bg-background"
>
{!rightPanelOpen ? panelLayoutControls : null}
<ChatHeader
Expand Down Expand Up @@ -6204,7 +6209,7 @@ function ChatViewContent(props: ChatViewProps) {
onUpdateProjectScript={updateProjectScript}
onDeleteProjectScript={deleteProjectScript}
/>
</header>
</WorkspacePageHeader>

<ThreadErrorBanner
error={visibleThreadError}
Expand Down Expand Up @@ -6542,6 +6547,7 @@ function ChatViewContent(props: ChatViewProps) {
newShortcutLabel={newTerminalShortcutLabel ?? undefined}
closeShortcutLabel={closeTerminalShortcutLabel ?? undefined}
keybindings={keybindings}
onHide={hideTerminal}
onAddTerminalContext={addTerminalContextToDraft}
/>
))}
Expand Down
19 changes: 4 additions & 15 deletions apps/web/src/components/NoActiveThreadState.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "./ui/empty";
import { SidebarInset } from "./ui/sidebar";
import { isElectron } from "../env";
import { cn } from "~/lib/utils";
import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";
import { WorkspacePageHeader } from "./WorkspacePageContainer";

export function NoActiveThreadState() {
return (
<SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground">
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background">
<header
className={cn(
"border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5",
isElectron
? "drag-region flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center"
: "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center",
COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
)}
>
<WorkspacePageHeader electron={isElectron} className="border-b border-border">
{isElectron ? (
<span className="text-xs text-muted-foreground/50 wco:pr-[var(--workspace-native-controls-inset)]">
No active thread
</span>
<span className="text-xs text-muted-foreground/50">No active thread</span>
) : (
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-foreground md:text-muted-foreground/60">
No active thread
</span>
</div>
)}
</header>
</WorkspacePageHeader>

<Empty className="flex-1">
<div className="w-full max-w-lg px-8 py-12">
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/Sidebar.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,9 @@ export function isSidebarNestedLinkClick(target: EventTarget | null): boolean {
export function shouldCreateNewThreadInCurrentProject(
shiftKey: boolean,
projectGroupCount: number,
hasProjectScope = false,
): boolean {
return shiftKey || projectGroupCount <= 1;
return hasProjectScope || shiftKey || projectGroupCount <= 1;
}

export function orderItemsByPreferredIds<TItem, TId>(input: {
Expand Down
Loading
Loading