Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { TerminalSearch } from "renderer/screens/main/components/WorkspaceView/C
import { useTheme } from "renderer/stores/theme";
import { resolveTerminalThemeType } from "renderer/stores/theme/utils";
import { LinkHoverTooltip } from "./components/LinkHoverTooltip";
import { useLinkClickHint } from "./hooks/useLinkClickHint";
import { useLinkHoverState } from "./hooks/useLinkHoverState";
import { useTerminalAppearance } from "./hooks/useTerminalAppearance";
import { shellEscapePaths } from "./utils";
Expand Down Expand Up @@ -58,6 +59,7 @@ export function TerminalPane({
onHover: onLinkHover,
onLeave: onLinkLeave,
} = useLinkHoverState();
const { hint, showHint } = useLinkClickHint();
const paneData = ctx.pane.data as TerminalPaneData;
const { terminalId } = paneData;
const containerRef = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -159,7 +161,10 @@ export function TerminalPane({
}
},
onFileLinkClick: (event, link) => {
if (!event.metaKey && !event.ctrlKey) return;
if (!event.metaKey && !event.ctrlKey) {
showHint(event.clientX, event.clientY);
return;
}
event.preventDefault();
if (event.shiftKey) {
openInExternalEditor(link.resolvedPath, {
Expand Down Expand Up @@ -200,6 +205,7 @@ export function TerminalPane({
openInExternalEditor,
onLinkHover,
onLinkLeave,
showHint,
]);

useHotkey(
Expand Down Expand Up @@ -312,7 +318,7 @@ export function TerminalPane({
<span>Disconnected</span>
</div>
)}
<LinkHoverTooltip hoveredLink={hoveredLink} />
<LinkHoverTooltip hoveredLink={hoveredLink} hint={hint} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,75 +1,68 @@
import type { ExternalApp } from "@superset/local-db";
import { useEffect, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { createPortal } from "react-dom";
import { getAppOption } from "renderer/components/OpenInExternalDropdown/constants";
import type { LinkHoverInfo } from "renderer/lib/terminal/terminal-runtime-registry";
import { electronTrpcClient } from "renderer/lib/trpc-client";
import type { LinkClickHint } from "../../hooks/useLinkClickHint";
import type { HoveredLink } from "../../hooks/useLinkHoverState";

const TOOLTIP_OFFSET_PX = 14;
const TOOLTIP_CLASSES =
"pointer-events-none fixed z-50 w-fit rounded-md bg-foreground px-3 py-1.5 text-xs text-background";

const isMac =
typeof navigator !== "undefined" &&
navigator.platform.toLowerCase().includes("mac");
Comment on lines +11 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 navigator.platform is deprecated

navigator.platform has been deprecated in modern browser/Electron versions. The recommended alternative is navigator.userAgentData?.platform (with a fallback for non-supporting environments) or parsing navigator.userAgent. While this still works fine in current Electron, it may produce warnings in future versions.

Suggested change
const isMac =
typeof navigator !== "undefined" &&
navigator.platform.toLowerCase().includes("mac");
const isMac =
typeof navigator !== "undefined" &&
(navigator.userAgentData
? navigator.userAgentData.platform.toLowerCase().includes("mac")
: navigator.userAgent.toLowerCase().includes("mac"));
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx
Line: 15-17

Comment:
**`navigator.platform` is deprecated**

`navigator.platform` has been deprecated in modern browser/Electron versions. The recommended alternative is `navigator.userAgentData?.platform` (with a fallback for non-supporting environments) or parsing `navigator.userAgent`. While this still works fine in current Electron, it may produce warnings in future versions.

```suggestion
const isMac =
	typeof navigator !== "undefined" &&
	(navigator.userAgentData
		? navigator.userAgentData.platform.toLowerCase().includes("mac")
		: navigator.userAgent.toLowerCase().includes("mac"));
```

How can I resolve this? If you propose a fix, please make it concise.

const MOD_LABEL = isMac ? "⌘" : "Ctrl";
const MOD_SHIFT_LABEL = isMac ? "⌘⇧" : "Ctrl+Shift";
const HINT_LABEL = `Hold ${MOD_LABEL} to open · ${MOD_SHIFT_LABEL} for external`;

interface LinkHoverTooltipProps {
hoveredLink: HoveredLink | null;
hint: LinkClickHint | null;
}

function getAppLabel(app: ExternalApp): string {
const option = getAppOption(app);
return option?.displayLabel ?? option?.label ?? "external editor";
}

function getLabel(
info: LinkHoverInfo,
shift: boolean,
defaultEditor: ExternalApp | null,
): string {
function getLabel(info: LinkHoverInfo, shift: boolean): string {
if (info.kind === "url") {
return shift ? "Open in external browser" : "Open in browser";
}
if (shift) {
return defaultEditor
? `Open in ${getAppLabel(defaultEditor)}`
: "Open externally";
return shift ? "Open in external browser" : "Open in pane";
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P2: Restore the non-shift URL tooltip label to match the browser action.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx, line 25:

<comment>Restore the non-shift URL tooltip label to match the browser action.</comment>

<file context>
@@ -22,7 +22,7 @@ interface LinkHoverTooltipProps {
 function getLabel(info: LinkHoverInfo, shift: boolean): string {
 	if (info.kind === "url") {
-		return shift ? "Open in external browser" : "Open in browser";
+		return shift ? "Open in external browser" : "Open in pane";
 	}
 	if (shift) return "Open in external editor";
</file context>
Suggested change
return shift ? "Open in external browser" : "Open in pane";
return shift ? "Open in external browser" : "Open in browser";
Fix with Cubic

}
return info.isDirectory ? "Reveal in sidebar" : "Open in editor";
if (shift) return "Open in external editor";
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P2: Keep the shift-hover label tied to the current default editor instead of hard-coding a generic string.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx, line 27:

<comment>Keep the shift-hover label tied to the current default editor instead of hard-coding a generic string.</comment>

<file context>
@@ -24,52 +20,15 @@ interface LinkHoverTooltipProps {
-			: "Open externally";
-	}
-	return info.isDirectory ? "Reveal in sidebar" : "Open in editor";
+	if (shift) return "Open in external editor";
+	return info.isDirectory ? "Reveal in sidebar" : "Open in pane";
 }
</file context>
Fix with Cubic

return info.isDirectory ? "Reveal in sidebar" : "Open in pane";
}

export function LinkHoverTooltip({ hoveredLink }: LinkHoverTooltipProps) {
const [defaultEditor, setDefaultEditor] = useState<ExternalApp | null>(null);

useEffect(() => {
let cancelled = false;
electronTrpcClient.settings.getDefaultEditor
.query()
.then((editor) => {
if (!cancelled) setDefaultEditor(editor);
})
.catch((error) => {
if (cancelled) return;
console.warn(
"[LinkHoverTooltip] Failed to fetch default editor:",
error,
);
setDefaultEditor(null);
});
return () => {
cancelled = true;
};
}, []);

if (!hoveredLink || !hoveredLink.modifier) return null;

const label = getLabel(hoveredLink.info, hoveredLink.shift, defaultEditor);
export function LinkHoverTooltip({ hoveredLink, hint }: LinkHoverTooltipProps) {
const showingHover = Boolean(hoveredLink?.modifier);

return createPortal(
<div
className="pointer-events-none fixed z-50 w-fit rounded-md bg-foreground px-3 py-1.5 text-xs text-background"
style={{
left: hoveredLink.clientX + TOOLTIP_OFFSET_PX,
top: hoveredLink.clientY + TOOLTIP_OFFSET_PX,
}}
>
{label}
</div>,
<>
{hoveredLink?.modifier && (
<div
className={TOOLTIP_CLASSES}
style={{
left: hoveredLink.clientX + TOOLTIP_OFFSET_PX,
top: hoveredLink.clientY + TOOLTIP_OFFSET_PX,
}}
>
{getLabel(hoveredLink.info, hoveredLink.shift)}
</div>
)}
<AnimatePresence>
{hint && !showingHover && (
<motion.div
key="hint"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className={TOOLTIP_CLASSES}
style={{
left: hint.clientX + TOOLTIP_OFFSET_PX,
top: hint.clientY + TOOLTIP_OFFSET_PX,
}}
>
{HINT_LABEL}
</motion.div>
)}
</AnimatePresence>
</>,
document.body,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { type LinkClickHint, useLinkClickHint } from "./useLinkClickHint";
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useCallback, useEffect, useRef, useState } from "react";

export interface LinkClickHint {
clientX: number;
clientY: number;
}

const HINT_DURATION_MS = 2000;
const MAX_HINTS_PER_SESSION = 2;

let hintsRemaining = MAX_HINTS_PER_SESSION;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Module-level counter survives HMR reloads in dev

hintsRemaining is initialized at module evaluation time and never resets as long as the module stays in memory. In development with hot module replacement, a file save that reloads only the component (not the module) won't reset the counter, so developers testing the nudge will see it fire fewer times than expected after the first two clicks. A comment noting this is intentional (or using sessionStorage to survive actual page reloads while still being resettable on dev reload) would help future contributors understand the choice.

This is non-critical for production users since HMR isn't active there.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/hooks/useLinkClickHint/useLinkClickHint.ts
Line: 11

Comment:
**Module-level counter survives HMR reloads in dev**

`hintsRemaining` is initialized at module evaluation time and never resets as long as the module stays in memory. In development with hot module replacement, a file save that reloads only the component (not the module) won't reset the counter, so developers testing the nudge will see it fire fewer times than expected after the first two clicks. A comment noting this is intentional (or using `sessionStorage` to survive actual page reloads while still being resettable on dev reload) would help future contributors understand the choice.

This is non-critical for production users since HMR isn't active there.

How can I resolve this? If you propose a fix, please make it concise.


export function useLinkClickHint() {
const [hint, setHint] = useState<LinkClickHint | null>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const showHint = useCallback((clientX: number, clientY: number) => {
if (hintsRemaining <= 0) return;
hintsRemaining -= 1;
if (timeoutRef.current) clearTimeout(timeoutRef.current);
setHint({ clientX, clientY });
timeoutRef.current = setTimeout(() => {
setHint(null);
timeoutRef.current = null;
}, HINT_DURATION_MS);
}, []);

useEffect(() => {
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}, []);

return { hint, showHint };
}
Loading