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(