Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ef908e8

Browse files
fix(site/src/pages/AgentsPage): useClipboard, restore Cursor, fix fake assertions
- Replace raw navigator.clipboard.writeText in CopySSHMenuItem with useClipboard hook for HTTP-only deployment fallback (F35). - Restore built-in Cursor menu item with chatId injection via getVSCodeHref, fixing regression from meatball menu removal. - Add parameters.queries mock for API key so external app disabled assertions in stories actually test the !isRunning guard (F36). - Add Cursor coverage across all stories (presence + disabled state).
1 parent 6373d75 commit ef908e8

2 files changed

Lines changed: 46 additions & 17 deletions

File tree

site/src/pages/AgentsPage/components/WorkspacePill.stories.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ const meta: Meta<typeof WorkspacePill> = {
102102
decorators: [withProxyProvider()],
103103
parameters: {
104104
layout: "centered",
105+
queries: [{ key: ["me", "apiKey"], data: { key: "mock-api-key" } }],
105106
},
106107
};
107108
export default meta;
@@ -129,6 +130,7 @@ export const WithAllApps: Story = {
129130
expect(body.getByText("Open in VS Code Insiders")).toBeInTheDocument();
130131
expect(body.getByText("JetBrains Gateway")).toBeInTheDocument();
131132
expect(body.getByText("Cursor")).toBeInTheDocument();
133+
expect(body.getByText("Open in Cursor")).toBeInTheDocument();
132134
expect(body.getByText("Open Terminal")).toBeInTheDocument();
133135
expect(body.getByText("Copy SSH Command")).toBeInTheDocument();
134136
expect(body.getByText("View Workspace")).toBeInTheDocument();
@@ -138,6 +140,15 @@ export const WithAllApps: Story = {
138140
.getByText("Open in VS Code")
139141
.closest("[role=menuitem]");
140142
expect(vscodeItem).not.toHaveAttribute("aria-disabled", "true");
143+
144+
// External apps should be enabled with API key mock.
145+
const jetbrainsItem = body
146+
.getByText("JetBrains Gateway")
147+
.closest("[role=menuitem]");
148+
expect(jetbrainsItem).not.toHaveAttribute("aria-disabled", "true");
149+
150+
const cursorItem = body.getByText("Cursor").closest("[role=menuitem]");
151+
expect(cursorItem).not.toHaveAttribute("aria-disabled", "true");
141152
});
142153
},
143154
};
@@ -156,6 +167,7 @@ export const WithBuiltinAppsOnly: Story = {
156167
await waitFor(() => {
157168
const body = within(document.body);
158169
expect(body.getByText("Open in VS Code")).toBeInTheDocument();
170+
expect(body.getByText("Open in Cursor")).toBeInTheDocument();
159171
expect(body.getByText("Open Terminal")).toBeInTheDocument();
160172
expect(body.getByText("View Workspace")).toBeInTheDocument();
161173
// No external apps or VS Code Insiders.
@@ -181,8 +193,9 @@ export const WithExternalAppsOnly: Story = {
181193
const body = within(document.body);
182194
expect(body.getByText("JetBrains Gateway")).toBeInTheDocument();
183195
expect(body.getByText("Cursor")).toBeInTheDocument();
196+
expect(body.getByText("Open in Cursor")).toBeInTheDocument();
184197
expect(body.getByText("View Workspace")).toBeInTheDocument();
185-
// No built-in apps.
198+
// No built-in VS Code or terminal apps.
186199
expect(body.queryByText("Open in VS Code")).not.toBeInTheDocument();
187200
expect(body.queryByText("Open Terminal")).not.toBeInTheDocument();
188201
});
@@ -202,6 +215,8 @@ export const NoApps: Story = {
202215

203216
await waitFor(() => {
204217
const body = within(document.body);
218+
// Cursor is always shown even with no other apps.
219+
expect(body.getByText("Open in Cursor")).toBeInTheDocument();
205220
expect(body.getByText("View Workspace")).toBeInTheDocument();
206221
});
207222
},
@@ -260,6 +275,12 @@ export const WithStoppedWorkspace: Story = {
260275
.closest("[role=menuitem]");
261276
expect(terminalItem).toHaveAttribute("aria-disabled", "true");
262277

278+
// Built-in Cursor should be disabled.
279+
const cursorBuiltinItem = body
280+
.getByText("Open in Cursor")
281+
.closest("[role=menuitem]");
282+
expect(cursorBuiltinItem).toHaveAttribute("aria-disabled", "true");
283+
263284
// External app items should be disabled.
264285
const jetbrainsItem = body
265286
.getByText("JetBrains Gateway")

site/src/pages/AgentsPage/components/WorkspacePill.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
TooltipContent,
3131
TooltipTrigger,
3232
} from "#/components/Tooltip/Tooltip";
33+
import { useClipboard } from "#/hooks/useClipboard";
3334
import {
3435
getTerminalHref,
3536
getVSCodeHref,
@@ -74,12 +75,9 @@ export const WorkspacePill: FC<WorkspacePillProps> = ({
7475

7576
const userApps = agent.apps.filter((app) => !app.hidden);
7677

77-
const hasItemsAboveSeparator =
78-
hasVSCode ||
79-
hasVSCodeInsiders ||
80-
userApps.length > 0 ||
81-
hasTerminal ||
82-
!!sshCommand;
78+
// Cursor is always shown (unconditional), so there are always
79+
// items above the separator.
80+
const hasItemsAboveSeparator = true;
8381

8482
return (
8583
<DropdownMenu open={open} onOpenChange={setOpen}>
@@ -143,6 +141,17 @@ export const WorkspacePill: FC<WorkspacePillProps> = ({
143141
isGeneratingKey={isGeneratingKey}
144142
/>
145143
)}
144+
<VSCodeMenuItem
145+
variant="cursor"
146+
label="Cursor"
147+
workspace={workspace}
148+
agent={agent}
149+
chatId={chatId}
150+
folder={folder}
151+
isRunning={isRunning}
152+
generateKey={generateKey}
153+
isGeneratingKey={isGeneratingKey}
154+
/>
146155
{userApps.map((app) => (
147156
<AppMenuItem
148157
key={app.id}
@@ -173,7 +182,7 @@ export const WorkspacePill: FC<WorkspacePillProps> = ({
173182
};
174183

175184
const VSCodeMenuItem: FC<{
176-
variant: "vscode" | "vscode-insiders";
185+
variant: "vscode" | "vscode-insiders" | "cursor";
177186
label: string;
178187
workspace: Workspace;
179188
agent: WorkspaceAgent;
@@ -289,17 +298,16 @@ const TerminalMenuItem: FC<{
289298
const CopySSHMenuItem: FC<{
290299
sshCommand: string;
291300
}> = ({ sshCommand }) => {
292-
const handleCopySSH = async () => {
293-
try {
294-
await navigator.clipboard.writeText(sshCommand);
295-
toast.success("SSH command copied to clipboard");
296-
} catch {
297-
toast.error("Failed to copy SSH command");
298-
}
299-
};
301+
const { copyToClipboard } = useClipboard();
300302

301303
return (
302-
<DropdownMenuItem onSelect={() => void handleCopySSH()}>
304+
<DropdownMenuItem
305+
onSelect={() => {
306+
void copyToClipboard(sshCommand).then(() => {
307+
toast.success("SSH command copied to clipboard");
308+
});
309+
}}
310+
>
303311
<CopyIcon className="size-3.5" />
304312
Copy SSH Command
305313
</DropdownMenuItem>

0 commit comments

Comments
 (0)