Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
21 changes: 6 additions & 15 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,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 @@ -258,6 +257,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 @@ -6211,20 +6211,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 @@ -6255,7 +6246,7 @@ function ChatViewContent(props: ChatViewProps) {
onUpdateProjectScript={updateProjectScript}
onDeleteProjectScript={deleteProjectScript}
/>
</header>
</WorkspacePageHeader>

<ThreadErrorBanner
error={visibleThreadError}
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
4 changes: 4 additions & 0 deletions apps/web/src/components/Sidebar.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,10 @@ describe("shouldCreateNewThreadInCurrentProject", () => {
expect(shouldCreateNewThreadInCurrentProject(false, 2)).toBe(false);
});

it("creates directly when the sidebar is scoped to a project", () => {
expect(shouldCreateNewThreadInCurrentProject(false, 2, true)).toBe(true);
});

it("creates directly on any click with a single project", () => {
expect(shouldCreateNewThreadInCurrentProject(false, 1)).toBe(true);
expect(shouldCreateNewThreadInCurrentProject(true, 1)).toBe(true);
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