Skip to content
5 changes: 5 additions & 0 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2139,6 +2139,11 @@ const AppContent: React.FC = () => {
onToggleEditor={() => setShowEditorMode(!showEditorMode)}
showEditorButton={Boolean(activeTask)}
isEditorOpen={showEditorMode}
projects={projects}
selectedProject={selectedProject}
activeTask={activeTask}
onSelectProject={handleSelectProject}
onSelectTask={handleSelectTask}
/>
)}
<div
Expand Down
9 changes: 7 additions & 2 deletions src/renderer/components/titlebar/OpenInMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ const OpenInMenu: React.FC<OpenInMenuProps> = ({ path, align = 'right' }) => {
type="button"
variant="ghost"
size="sm"
className="h-8 gap-1 px-2 text-muted-foreground hover:bg-background/80"
className={[
'h-7 gap-1.5 px-2 text-[13px] font-medium leading-none text-muted-foreground hover:bg-background/70 hover:text-foreground',
open ? 'bg-background/80 text-foreground' : '',
].join(' ')}
onClick={async () => {
const newState = !open;
void import('../../lib/telemetryClient').then(({ captureTelemetry }) => {
Expand All @@ -141,7 +144,9 @@ const OpenInMenu: React.FC<OpenInMenuProps> = ({ path, align = 'right' }) => {
>
<span>Open in</span>
<ChevronDown
className={`h-4 w-4 transition-transform duration-200 ${open ? 'rotate-180' : ''}`}
className={`h-3 w-3 opacity-50 transition-transform duration-200 ${
open ? 'rotate-180' : ''
}`}
/>
</Button>
<AnimatePresence>
Expand Down
23 changes: 23 additions & 0 deletions src/renderer/components/titlebar/Titlebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/
import OpenInMenu from './OpenInMenu';
import FeedbackModal from '../FeedbackModal';
import BrowserToggleButton from './BrowserToggleButton';
import TitlebarContext from './TitlebarContext';
import type { Project, Task } from '../../types/app';

interface GithubUser {
login?: string;
Expand All @@ -38,6 +40,11 @@ interface TitlebarProps {
onToggleEditor?: () => void;
showEditorButton?: boolean;
isEditorOpen?: boolean;
projects: Project[];
selectedProject: Project | null;
activeTask: Task | null;
onSelectProject: (project: Project) => void;
onSelectTask: (task: Task) => void;
}

const Titlebar: React.FC<TitlebarProps> = ({
Expand All @@ -56,6 +63,11 @@ const Titlebar: React.FC<TitlebarProps> = ({
onToggleEditor,
showEditorButton = false,
isEditorOpen = false,
projects,
selectedProject,
activeTask,
onSelectProject,
onSelectTask,
}) => {
const [isFeedbackOpen, setIsFeedbackOpen] = useState(false);
const feedbackButtonRef = useRef<HTMLButtonElement | null>(null);
Expand Down Expand Up @@ -115,6 +127,17 @@ const Titlebar: React.FC<TitlebarProps> = ({
return (
<>
<header className="fixed inset-x-0 top-0 z-[80] flex h-[var(--tb,36px)] items-center justify-end bg-muted pr-2 shadow-[inset_0_-1px_0_hsl(var(--border))] [-webkit-app-region:drag] dark:bg-background">
<div className="pointer-events-none absolute inset-x-0 flex justify-center">
<div className="w-[min(60vw,720px)]">
<TitlebarContext
projects={projects}
selectedProject={selectedProject}
activeTask={activeTask}
onSelectProject={onSelectProject}
onSelectTask={onSelectTask}
/>
</div>
</div>
<div className="pointer-events-auto flex items-center gap-1 [-webkit-app-region:no-drag]">
{currentPath ? <OpenInMenu path={currentPath} align="right" /> : null}
{showEditorButton ? (
Expand Down
108 changes: 108 additions & 0 deletions src/renderer/components/titlebar/TitlebarContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import type { Project, Task } from '../../types/app';

interface TitlebarContextProps {
projects: Project[];
selectedProject: Project | null;
activeTask: Task | null;
onSelectProject: (project: Project) => void;
onSelectTask: (task: Task) => void;
}

const TitlebarContext: React.FC<TitlebarContextProps> = ({
projects,
selectedProject,
activeTask,
onSelectProject,
onSelectTask,
}) => {
if (!selectedProject) {
return <div />;
}

const tasks = selectedProject?.tasks ?? [];
const projectValue = selectedProject.id;
const taskValue = activeTask?.id;
const projectLabel = selectedProject.name;
const taskLabel = activeTask?.name ?? '';
const selectContentClassName = 'w-[min(280px,90vw)]';

const handleProjectChange = (value: string) => {
const nextProject = projects.find((project) => project.id === value);
if (nextProject) {
onSelectProject(nextProject);
}
};

const handleTaskChange = (value: string) => {
const nextTask = tasks.find((task) => task.id === value);
if (nextTask) {
onSelectTask(nextTask);
}
};

return (
<div className="grid w-full grid-cols-[1fr_auto_1fr] items-center">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing pointer-events-none blocks titlebar window dragging

Medium Severity

The grid container and its child elements in TitlebarContext lack pointer-events-none, causing them to capture click/drag events. Since CSS pointer-events is not inherited, these elements default to auto despite the parent wrapper having pointer-events-none. This blocks window dragging in the titlebar area around and between the dropdown triggers, as reported in the PR discussion.

Additional Locations (2)

Fix in Cursor Fix in Web

<div className="flex items-center justify-end">
<Select value={projectValue} onValueChange={handleProjectChange}>
<SelectTrigger
className="pointer-events-auto h-7 w-auto justify-start gap-1 border-none bg-transparent px-1 py-0.5 text-[13px] font-medium leading-none text-muted-foreground shadow-none [-webkit-app-region:no-drag] hover:bg-background/70 hover:text-foreground data-[state=open]:bg-background/80 data-[state=open]:text-foreground [&>span]:block [&>span]:max-w-[218px] [&>span]:truncate [&>svg]:hidden"
aria-label="Select project"
title={projectLabel}
>
<SelectValue placeholder="Project" />
</SelectTrigger>
<SelectContent side="bottom" align="start" className={selectContentClassName}>
{projects.length > 0 ? (
projects.map((project) => (
<SelectItem
key={project.id}
value={project.id}
className="min-w-0 [&>span:last-child]:block [&>span:last-child]:min-w-0 [&>span:last-child]:truncate"
>
{project.name}
</SelectItem>
))
) : (
<SelectItem value="__empty_projects__" disabled>
No projects yet
</SelectItem>
)}
</SelectContent>
</Select>
</div>
<span className="px-2 text-center text-[11px] text-muted-foreground/60">/</span>
<div className="flex items-center justify-start">
<Select value={taskValue} onValueChange={handleTaskChange} disabled={!selectedProject}>
<SelectTrigger
className="pointer-events-auto h-7 w-auto min-w-[60px] justify-start gap-1 border-none bg-transparent px-1 py-0.5 text-[13px] font-medium leading-none text-muted-foreground shadow-none [-webkit-app-region:no-drag] hover:bg-background/70 hover:text-foreground data-[state=open]:bg-background/80 data-[placeholder]:text-muted-foreground/70 data-[state=open]:text-foreground [&>span]:block [&>span]:max-w-[218px] [&>span]:truncate [&>svg]:hidden"
aria-label="Select task"
title={taskLabel}
>
<SelectValue placeholder="" />
</SelectTrigger>
<SelectContent side="bottom" align="start" className={selectContentClassName}>
{tasks.length > 0 ? (
tasks.map((task) => (
<SelectItem
key={task.id}
value={task.id}
className="min-w-0 [&>span:last-child]:block [&>span:last-child]:min-w-0 [&>span:last-child]:truncate"
>
{task.name}
</SelectItem>
))
) : (
<SelectItem value="__empty_tasks__" disabled>
No tasks yet
</SelectItem>
)}
</SelectContent>
</Select>
</div>
</div>
);
};

export default TitlebarContext;