From 51a399c77562d7dfd638198117b186b158ef9f8d Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Tue, 2 Jun 2026 15:54:03 +0200 Subject: [PATCH] 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 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 --- .atl/.skill-registry.cache.json | 2 +- .atl/skill-registry.md | 3 +-- apps/web/src/components/app-shell.tsx | 18 ++++++++++++--- apps/web/src/components/session-card.tsx | 13 +++++++---- apps/web/src/components/terminal.tsx | 26 +++++++++++++++++++--- apps/web/src/hooks/use-instance-actions.ts | 4 ++-- 6 files changed, 51 insertions(+), 15 deletions(-) diff --git a/.atl/.skill-registry.cache.json b/.atl/.skill-registry.cache.json index f954908..ad4be15 100644 --- a/.atl/.skill-registry.cache.json +++ b/.atl/.skill-registry.cache.json @@ -1,3 +1,3 @@ { - "fingerprint": "fdea8a74bb4c7449c01c4bd61646c895b10ede78" + "fingerprint": "c36b11ec5edebc02aa51b1113a7a11dc2559e812" } \ No newline at end of file diff --git a/.atl/skill-registry.md b/.atl/skill-registry.md index 181a761..d833730 100644 --- a/.atl/skill-registry.md +++ b/.atl/skill-registry.md @@ -2,7 +2,7 @@ -Last updated: 2026-05-28 +Last updated: 2026-06-02 ## Sources scanned @@ -21,7 +21,6 @@ Last updated: 2026-05-28 | Skill | Trigger / description | Scope | Path | | --- | --- | --- | --- | | `auto-commit` | Use when you are making multiple edits or completing significant work in a git repository to automatically create commits | user | `/home/alex/.config/opencode/skills/auto-commit/SKILL.md` | -| `openspec` | Use OpenSpec as the source of truth for planning, implementation, verification, and archive discipline. | user | `/home/alex/.config/opencode/skills/openspec/SKILL.md` | | `openspec-apply-change` | Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. | project | `/home/alex/projects/headquarter/.opencode/skills/openspec-apply-change/SKILL.md` | | `openspec-archive-change` | Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete. | project | `/home/alex/projects/headquarter/.opencode/skills/openspec-archive-change/SKILL.md` | | `openspec-explore` | Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change. | project | `/home/alex/projects/headquarter/.opencode/skills/openspec-explore/SKILL.md` | diff --git a/apps/web/src/components/app-shell.tsx b/apps/web/src/components/app-shell.tsx index 54aee87..2fbd949 100644 --- a/apps/web/src/components/app-shell.tsx +++ b/apps/web/src/components/app-shell.tsx @@ -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 ( diff --git a/apps/web/src/components/session-card.tsx b/apps/web/src/components/session-card.tsx index a2f3598..d9b62fe 100644 --- a/apps/web/src/components/session-card.tsx +++ b/apps/web/src/components/session-card.tsx @@ -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({
{isActive && ( <> - {session.url ? ( + {openHref ? ( {isActive && ( <> - {session.url ? ( + {openHref ? ( ( 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( 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]); diff --git a/apps/web/src/hooks/use-instance-actions.ts b/apps/web/src/hooks/use-instance-actions.ts index 995eb98..08cd397 100644 --- a/apps/web/src/hooks/use-instance-actions.ts +++ b/apps/web/src/hooks/use-instance-actions.ts @@ -40,10 +40,10 @@ export function useInstanceActions( return; } if (session.tool_type_interfaces?.includes("terminal")) { - window.location.href = `/instances/${session.id}/terminal`; + window.open(`/instances/${session.id}/terminal`, "_blank", "noopener,noreferrer"); return; } - window.location.href = `/projects/${session.project_id}`; + window.open(`/projects/${session.project_id}`, "_blank", "noopener,noreferrer"); }, []); const handleStart = useCallback(