From 57b8c6290994a9e1bd4c035e5cd8b34db5e5347e Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Wed, 4 Feb 2026 10:54:48 -0600
Subject: fix(app): terminal hyperlink clicks

---
 packages/app/src/components/terminal.tsx | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

(limited to 'packages/app/src/components/terminal.tsx')

diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx
index 11bcd4cc8..4ad79ee82 100644
--- a/packages/app/src/components/terminal.tsx
+++ b/packages/app/src/components/terminal.tsx
@@ -1,5 +1,6 @@
 import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
 import { ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
+import { usePlatform } from "@/context/platform"
 import { useSDK } from "@/context/sdk"
 import { monoFontFamily, useSettings } from "@/context/settings"
 import { SerializeAddon } from "@/addons/serialize"
@@ -52,6 +53,7 @@ const DEFAULT_TERMINAL_COLORS: Record<"light" | "dark", TerminalColors> = {
 }
 
 export const Terminal = (props: TerminalProps) => {
+  const platform = usePlatform()
   const sdk = useSDK()
   const settings = useSettings()
   const theme = useTheme()
@@ -135,6 +137,22 @@ export const Terminal = (props: TerminalProps) => {
     focusTerminal()
   }
 
+  const handleLinkClick = (event: MouseEvent) => {
+    if (!event.shiftKey && !event.ctrlKey && !event.metaKey) return
+    if (event.altKey) return
+    if (event.button !== 0) return
+
+    const t = term
+    if (!t) return
+
+    const link = (t as unknown as { currentHoveredLink?: { text: string } }).currentHoveredLink
+    if (!link?.text) return
+
+    event.preventDefault()
+    event.stopImmediatePropagation()
+    platform.openLink(link.text)
+  }
+
   onMount(() => {
     const run = async () => {
       const loaded = await loadGhostty()
@@ -240,6 +258,9 @@ export const Terminal = (props: TerminalProps) => {
       container.addEventListener("pointerdown", handlePointerDown)
       cleanups.push(() => container.removeEventListener("pointerdown", handlePointerDown))
 
+      container.addEventListener("click", handleLinkClick, { capture: true })
+      cleanups.push(() => container.removeEventListener("click", handleLinkClick, { capture: true }))
+
       handleTextareaFocus = () => {
         t.options.cursorBlink = true
       }
-- 
cgit v1.2.3

