From 3932835fa4ed243bc3e287e77c384f077bcdb906 Mon Sep 17 00:00:00 2001 From: Jan Burzinski <156842394+janburzinski@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:44:42 +0200 Subject: [PATCH 1/3] fix(renderer): allow shift-clicks on markdown links --- .../src/renderer/lib/ui/markdown-renderer.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx b/apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx index 2c4a925d3..13d779e08 100644 --- a/apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx +++ b/apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx @@ -115,6 +115,11 @@ const ResolvedImage: React.FC<{ type WithChildren = { children?: React.ReactNode }; type WithChildrenAndClass = { children?: React.ReactNode; className?: string }; type AnchorProps = { href?: string; children?: React.ReactNode }; + +function shouldConfirmExternalLinkClick(event: React.MouseEvent): boolean { + return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey; +} + type ImgProps = React.ComponentPropsWithoutRef<'img'> & ExtraProps; function getCodeBlock(children: React.ReactNode, className?: string) { @@ -207,7 +212,7 @@ function useFullComponents( a: ({ href, children }: AnchorProps) => { const isHttp = typeof href === 'string' && /^https?:\/\//i.test(href); const handleClick = (e: React.MouseEvent) => { - if (isHttp) { + if (isHttp && shouldConfirmExternalLinkClick(e)) { e.preventDefault(); confirmOpenExternalLink(href); } @@ -354,7 +359,7 @@ function useCompactComponents(isDark: boolean) { a: ({ href, children }: AnchorProps) => { const isHttp = typeof href === 'string' && /^https?:\/\//i.test(href); const handleClick = (e: React.MouseEvent) => { - if (isHttp) { + if (isHttp && shouldConfirmExternalLinkClick(e)) { e.preventDefault(); confirmOpenExternalLink(href); } From 7a22865fe90f13d53cc9dfbfd7b790e266c0e4d7 Mon Sep 17 00:00:00 2001 From: Jan Burzinski <156842394+janburzinski@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:23:34 +0200 Subject: [PATCH 2/3] fix(markdown): confirm alt-click links --- apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx b/apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx index 13d779e08..c3777b6fd 100644 --- a/apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx +++ b/apps/emdash-desktop/src/renderer/lib/ui/markdown-renderer.tsx @@ -117,7 +117,7 @@ type WithChildrenAndClass = { children?: React.ReactNode; className?: string }; type AnchorProps = { href?: string; children?: React.ReactNode }; function shouldConfirmExternalLinkClick(event: React.MouseEvent): boolean { - return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey; + return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey; } type ImgProps = React.ComponentPropsWithoutRef<'img'> & ExtraProps; From cd01505aa11b643fa4d3bcfcabf97ac2ed5120a9 Mon Sep 17 00:00:00 2001 From: Jan Burzinski <156842394+janburzinski@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:26:28 +0200 Subject: [PATCH 3/3] fix(renderer): preserve modified link clicks --- .gitignore | 1 + .../skills/components/skills-info-box.tsx | 19 +++++++----------- .../issue-selector/issue-selector.tsx | 16 ++++++++------- .../components/pr-entry/checks-list.tsx | 19 +++++++++++------- .../components/pr-entry/comments-list.tsx | 12 +++++------ .../components/pr-entry/pr-entry.tsx | 13 +++++------- .../renderer/features/tasks/task-titlebar.tsx | 13 ++++++------ .../renderer/lib/components/external-link.tsx | 20 +++++++++++++++++++ .../src/renderer/lib/components/pr-badge.tsx | 8 ++++---- 9 files changed, 70 insertions(+), 51 deletions(-) create mode 100644 apps/emdash-desktop/src/renderer/lib/components/external-link.tsx diff --git a/.gitignore b/.gitignore index d5725f473..3325ce84f 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ apps/*/tooling/db/dev.db-shm apps/*/tooling/node-deps/node_modules/ .pi/extensions/emdash-hook.ts .opencode/plugins/emdash-notifications.js +.amp/plugins/emdash-hook.ts # Vitest / Browser testing .vitest-attachments/ diff --git a/apps/emdash-desktop/src/renderer/features/skills/components/skills-info-box.tsx b/apps/emdash-desktop/src/renderer/features/skills/components/skills-info-box.tsx index 5587b8050..802eb0499 100644 --- a/apps/emdash-desktop/src/renderer/features/skills/components/skills-info-box.tsx +++ b/apps/emdash-desktop/src/renderer/features/skills/components/skills-info-box.tsx @@ -1,3 +1,4 @@ +import { ExternalLink } from '@renderer/lib/components/external-link'; import { Alert, AlertDescription } from '@renderer/lib/ui/alert'; export function SkillsInfoBox() { @@ -5,33 +6,27 @@ export function SkillsInfoBox() { Skills from the{' '} - OpenAI - {' '} + {' '} and{' '} - Anthropic - {' '} + {' '} catalogs. Install a skill to make it available across all your coding agents. Skills follow the open{' '} - Agent Skills - {' '} + {' '} standard. If you want to use skills from another library, feel free to let us know through the feedback modal. diff --git a/apps/emdash-desktop/src/renderer/features/tasks/components/issue-selector/issue-selector.tsx b/apps/emdash-desktop/src/renderer/features/tasks/components/issue-selector/issue-selector.tsx index 719d1a046..8309e849f 100644 --- a/apps/emdash-desktop/src/renderer/features/tasks/components/issue-selector/issue-selector.tsx +++ b/apps/emdash-desktop/src/renderer/features/tasks/components/issue-selector/issue-selector.tsx @@ -1,4 +1,4 @@ -import { ExternalLink, Link, Loader2 } from 'lucide-react'; +import { ExternalLink as ExternalLinkIcon, Link, Loader2 } from 'lucide-react'; import { observer } from 'mobx-react-lite'; import { type ReactNode, useCallback, useRef, useState } from 'react'; import { @@ -6,12 +6,12 @@ import { ISSUE_PROVIDER_ORDER, } from '@renderer/features/integrations/issue-provider-meta'; import { PROVIDER_ICON_COMPONENTS } from '@renderer/features/integrations/provider-icons'; +import { ExternalLink } from '@renderer/lib/components/external-link'; import { InlineMarkdown } from '@renderer/lib/components/inline-markdown'; import { IssueStatusIndicator, toIssueStatus, } from '@renderer/lib/components/issue-status-indicator'; -import { rpc } from '@renderer/lib/ipc'; import { useNavigate } from '@renderer/lib/layout/navigation-provider'; import { Button } from '@renderer/lib/ui/button'; import { @@ -324,13 +324,15 @@ export function SelectedIssueValue({ issue }: { issue: LinkedIssue }) {
{issue.title}
- + +
diff --git a/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/checks-list.tsx b/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/checks-list.tsx index 16de26d21..35348f8cd 100644 --- a/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/checks-list.tsx +++ b/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/checks-list.tsx @@ -1,8 +1,14 @@ -import { CheckCircle2, ExternalLink, Loader2, MinusCircle, XCircle } from 'lucide-react'; +import { + CheckCircle2, + ExternalLink as ExternalLinkIcon, + Loader2, + MinusCircle, + XCircle, +} from 'lucide-react'; import { observer } from 'mobx-react-lite'; import { useMemo } from 'react'; import { useSyncCheckRuns } from '@renderer/features/tasks/diff-view/state/use-check-runs'; -import { rpc } from '@renderer/lib/ipc'; +import { ExternalLink } from '@renderer/lib/components/external-link'; import { EmptyState } from '@renderer/lib/ui/empty-state'; import { computeCheckBucket, @@ -70,14 +76,13 @@ export function CheckRunItem({ check }: { check: CheckRun }) {
{duration && {duration}} {detailsUrl && ( - + + )}
diff --git a/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/comments-list.tsx b/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/comments-list.tsx index f8802f35d..76e889fb4 100644 --- a/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/comments-list.tsx +++ b/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/comments-list.tsx @@ -1,6 +1,6 @@ -import { ExternalLink, MessageSquare } from 'lucide-react'; +import { ExternalLink as ExternalLinkIcon, MessageSquare } from 'lucide-react'; import { useMemo } from 'react'; -import { rpc } from '@renderer/lib/ipc'; +import { ExternalLink } from '@renderer/lib/components/external-link'; import { MarkdownRenderer } from '@renderer/lib/ui/markdown-renderer'; import { RelativeTime } from '@renderer/lib/ui/relative-time'; import { cn } from '@renderer/utils/utils'; @@ -71,12 +71,12 @@ function CommentItem({ comment }: { comment: PullRequestConversationItem }) { - + + ); } diff --git a/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/pr-entry.tsx b/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/pr-entry.tsx index 7f278e6a9..53f579e1b 100644 --- a/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/pr-entry.tsx +++ b/apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/components/pr-entry/pr-entry.tsx @@ -1,15 +1,15 @@ -import { ExternalLink } from 'lucide-react'; +import { ExternalLink as ExternalLinkIcon } from 'lucide-react'; import { observer } from 'mobx-react-lite'; import { useState } from 'react'; import { useTaskViewContext, useWorkspaceViewModel, } from '@renderer/features/tasks/task-view-context'; +import { ExternalLink } from '@renderer/lib/components/external-link'; import { PrMergeLine } from '@renderer/lib/components/pr-merge-line'; import { PrNumberBadge } from '@renderer/lib/components/pr-number-badge'; import { StatusIcon } from '@renderer/lib/components/pr-status-icon'; import { toast } from '@renderer/lib/hooks/use-toast'; -import { rpc } from '@renderer/lib/ipc'; import { type SplitButtonAction } from '@renderer/lib/ui/split-button'; import { ToggleGroup, ToggleGroupItem } from '@renderer/lib/ui/toggle-group'; import { cn } from '@renderer/utils/utils'; @@ -106,17 +106,14 @@ export const PullRequestEntry = observer(function PullRequestEntry({ pr }: { pr:
- +
diff --git a/apps/emdash-desktop/src/renderer/features/tasks/task-titlebar.tsx b/apps/emdash-desktop/src/renderer/features/tasks/task-titlebar.tsx index 76f860c65..991e0a04a 100644 --- a/apps/emdash-desktop/src/renderer/features/tasks/task-titlebar.tsx +++ b/apps/emdash-desktop/src/renderer/features/tasks/task-titlebar.tsx @@ -28,9 +28,9 @@ import { useWorkspaceViewModel, } from '@renderer/features/tasks/task-view-context'; import { ConnectionStatusDot } from '@renderer/lib/components/connection-status-dot'; +import { ExternalLink } from '@renderer/lib/components/external-link'; import { OpenInMenu } from '@renderer/lib/components/titlebar/open-in-menu'; import { Titlebar } from '@renderer/lib/components/titlebar/Titlebar'; -import { rpc } from '@renderer/lib/ipc'; import { useNavigate } from '@renderer/lib/layout/navigation-provider'; import { Badge } from '@renderer/lib/ui/badge'; import { Button } from '@renderer/lib/ui/button'; @@ -420,11 +420,10 @@ function LinkedIssueBadge({ issue }: { issue: LinkedIssue }) { { - if (issue.url) void rpc.app.openExternal(issue.url); + { + if (!issue.url) event.preventDefault(); }} className="hover:bg-muted/30 flex items-center gap-1 rounded-md border border-border px-1.5 py-0.5 text-xs text-foreground-muted disabled:cursor-default disabled:opacity-60" > @@ -434,7 +433,7 @@ function LinkedIssueBadge({ issue }: { issue: LinkedIssue }) { ) : ( {issue.identifier} )} - + } /> {issue.title || issue.identifier} diff --git a/apps/emdash-desktop/src/renderer/lib/components/external-link.tsx b/apps/emdash-desktop/src/renderer/lib/components/external-link.tsx new file mode 100644 index 000000000..9a01f953c --- /dev/null +++ b/apps/emdash-desktop/src/renderer/lib/components/external-link.tsx @@ -0,0 +1,20 @@ +import { type ComponentPropsWithoutRef, type MouseEvent } from 'react'; +import { confirmOpenExternalLink } from '@renderer/lib/open-external-link'; + +type ExternalLinkProps = Omit, 'href'> & { + href: string; + onOpenError?: (error: unknown) => void; +}; + +export function ExternalLink({ href, onClick, onOpenError, ...props }: ExternalLinkProps) { + const handleClick = (event: MouseEvent) => { + onClick?.(event); + if (event.defaultPrevented) return; + if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey) return; + + event.preventDefault(); + confirmOpenExternalLink(href, onOpenError); + }; + + return ; +} diff --git a/apps/emdash-desktop/src/renderer/lib/components/pr-badge.tsx b/apps/emdash-desktop/src/renderer/lib/components/pr-badge.tsx index 5bd70b8ee..144bddb9a 100644 --- a/apps/emdash-desktop/src/renderer/lib/components/pr-badge.tsx +++ b/apps/emdash-desktop/src/renderer/lib/components/pr-badge.tsx @@ -1,9 +1,9 @@ -import { ExternalLink } from 'lucide-react'; +import { ExternalLink as ExternalLinkIcon } from 'lucide-react'; +import { ExternalLink } from '@renderer/lib/components/external-link'; import { PrMergeLine } from '@renderer/lib/components/pr-merge-line'; import { Popover, PopoverContent, PopoverTrigger } from '@renderer/lib/ui/popover'; import { cn } from '@renderer/utils/utils'; import { getPrNumber, type PullRequest } from '@shared/core/pull-requests/pull-requests'; -import { rpc } from '../ipc'; import { Button } from '../ui/button'; import { RelativeTime } from '../ui/relative-time'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; @@ -59,12 +59,12 @@ export function PrBadge({ variant = 'default', pr, className, hoverDelay }: PrBa Open PR on GitHub