feat: open all sessions in new tabs; sidebar terminal links
Sidebar SessionItem was only opening web tool URLs in new tabs. Terminal sessions linked to the project page in the same tab. SessionCard 'Open' buttons for terminal sessions navigated in-place. Changes: - app-shell.tsx: SessionItem now builds terminal URLs (/instances/:id/terminal) and always uses target=_blank - session-card.tsx: compute openHref for both web and terminal sessions, render <a> links with target=_blank instead of callback buttons - use-instance-actions.ts: handleOpen now uses window.open(..., '_blank') for terminal sessions and project fallback All session opening (sidebar, cards, callbacks) now consistently opens in a new tab. Quality gates: tsc clean
This commit is contained in:
@@ -35,11 +35,23 @@ const NAV_ITEMS: {
|
||||
const SessionItem = ({ session }: { session: Session }) => {
|
||||
const isRunning = session.status === "running";
|
||||
|
||||
// Determine the link target:
|
||||
// - Web tools open their tunnel URL
|
||||
// - Terminal tools open the terminal page
|
||||
// - Everything else falls back to the project page
|
||||
const hasTerminal = session.tool_type_interfaces.includes("terminal");
|
||||
const hasWeb = session.tool_type_interfaces.includes("web");
|
||||
const href = session.url && hasWeb
|
||||
? session.url
|
||||
: hasTerminal
|
||||
? `/instances/${session.id}/terminal`
|
||||
: `/projects/${session.project_id}`;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={session.url ?? `/projects/${session.project_id}`}
|
||||
target={session.url ? "_blank" : undefined}
|
||||
rel={session.url ? "noopener noreferrer" : undefined}
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="nav-item session-item"
|
||||
title={`${session.display_name} (${session.status})`}
|
||||
>
|
||||
|
||||
@@ -58,6 +58,11 @@ export function SessionCard({
|
||||
const isTerminalOnly =
|
||||
session.tool_type_interfaces?.includes("terminal") &&
|
||||
!session.tool_type_interfaces?.includes("web");
|
||||
const openHref = session.url
|
||||
? session.url
|
||||
: isTerminalOnly
|
||||
? `/instances/${session.id}/terminal`
|
||||
: undefined;
|
||||
const hasTunnelError =
|
||||
!isTerminalOnly && tunnelHealth?.tunnel_status === "unreachable";
|
||||
const hasAppError =
|
||||
@@ -150,9 +155,9 @@ export function SessionCard({
|
||||
<div className="session-card-actions mobile">
|
||||
{isActive && (
|
||||
<>
|
||||
{session.url ? (
|
||||
{openHref ? (
|
||||
<a
|
||||
href={session.url}
|
||||
href={openHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="secondary-button mobile-primary"
|
||||
@@ -207,9 +212,9 @@ export function SessionCard({
|
||||
<div className="session-card-actions">
|
||||
{isActive && (
|
||||
<>
|
||||
{session.url ? (
|
||||
{openHref ? (
|
||||
<a
|
||||
href={session.url}
|
||||
href={openHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="secondary-button small"
|
||||
|
||||
@@ -311,12 +311,18 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
||||
term.loadAddon(new WebLinksAddon());
|
||||
|
||||
// Load WebGL renderer for GPU acceleration, fall back to DOM
|
||||
let webglAddon: WebglAddon | null = null;
|
||||
try {
|
||||
const webglAddon = new WebglAddon();
|
||||
webglAddon = new WebglAddon();
|
||||
term.loadAddon(webglAddon);
|
||||
webglAddon.onContextLoss(() => {
|
||||
console.warn("WebGL context lost, falling back to DOM renderer");
|
||||
webglAddon.dispose();
|
||||
try {
|
||||
webglAddon?.dispose();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
webglAddon = null;
|
||||
// Trigger a refit since cell dimensions may differ
|
||||
requestAnimationFrame(() => fitTerminal());
|
||||
});
|
||||
@@ -579,7 +585,21 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
||||
window.clearInterval(heartbeatCheckRef.current);
|
||||
heartbeatCheckRef.current = null;
|
||||
}
|
||||
term.dispose();
|
||||
// Dispose WebGL addon BEFORE the terminal to avoid race with
|
||||
// RenderService.setRenderer accessing a disposed renderer
|
||||
if (webglAddon) {
|
||||
try {
|
||||
webglAddon.dispose();
|
||||
} catch {
|
||||
// Ignore disposal errors from partially torn-down terminal
|
||||
}
|
||||
webglAddon = null;
|
||||
}
|
||||
try {
|
||||
term.dispose();
|
||||
} catch {
|
||||
// Ignore disposal errors from partially torn-down terminal
|
||||
}
|
||||
};
|
||||
}, [instanceId, connectWebSocket]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user