fix: remove rel=noopener from all session/instance links to enable tab reuse

rel=noopener forces a fresh browsing context, breaking target name matching
and window reference tracking across browsers.

Changes:
- use-instance-actions.ts: track opened tabs in Map, call .focus() on existing
- session-card.tsx: all Open buttons now go through handleOpen callback
- app-shell.tsx: sidebar session links use target='session-{id}' + noreferrer
- workspace-instance-chips.tsx: remove noopener from chip links
- instance-list.tsx: remove noopener from Open links
- WorkspaceDetailPage.tsx: use named target + noreferrer

Quality gates: tsc --noEmit pass, npm run build pass, 80/80 tests pass
This commit is contained in:
Developer
2026-06-09 12:39:32 +00:00
parent e5e29aca49
commit 680417a0a2
6 changed files with 47 additions and 67 deletions
+18 -9
View File
@@ -1,4 +1,4 @@
import { useState, useCallback } from "react";
import { useState, useCallback, useRef } from "react";
import {
stopInstance,
deleteInstance,
@@ -33,18 +33,27 @@ export function useInstanceActions(
null,
);
const [dirtyDeleteFiles, setDirtyDeleteFiles] = useState<string[]>([]);
const tabRefs = useRef<Map<string, Window | null>>(new Map());
const handleOpen = useCallback((session: Session) => {
const tabName = `session-${session.id}`;
const key = session.id;
const existing = tabRefs.current.get(key);
if (existing && !existing.closed) {
existing.focus();
return;
}
let url: string;
if (session.url) {
window.open(session.url, tabName);
return;
url = session.url;
} else if (session.tool_type_interfaces?.includes("terminal")) {
url = `/instances/${session.id}/terminal`;
} else {
url = `/projects/${session.project_id}`;
}
if (session.tool_type_interfaces?.includes("terminal")) {
window.open(`/instances/${session.id}/terminal`, tabName);
return;
}
window.open(`/projects/${session.project_id}`, tabName);
const w = window.open(url, `session-${session.id}`);
tabRefs.current.set(key, w);
}, []);
const handleStart = useCallback(