feat: improve session naming, project display, rename support, tab titles

Backend:
- sessions.py: include workspace_name in session response
- instance_service.py: auto-generate display names as
  'Project / Workspace / Tool #N' instead of 'Workspace / Tool #N'
- instance_service.py: add rename_tool_instance() service function
- tool_instances.py: add PATCH /instances/{id} endpoint for renaming
  display_name

Frontend:
- api/sessions.ts: add workspace_name to Session type, add renameInstance()
- use-instance-actions.ts: add handleRename, set document.title when opening
- session-card.tsx: click-to-edit display_name inline; always show project
  context line (Project / Workspace or Repo / Tool)
- session-list.tsx: pass through onRename prop
- SessionsPage.tsx: wire handleRename to SessionCard and SessionList
- app-shell.tsx: sidebar tooltip includes workspace or repo name
- use-terminal-page.ts: set document.title based on active terminal session

Quality gates: py_compile all backend files pass, tsc --noEmit pass,
npm run build pass, 82/82 tests pass
This commit is contained in:
Developer
2026-06-10 17:04:03 +00:00
parent 886be83af5
commit 1d10283fc9
13 changed files with 243 additions and 11 deletions
@@ -4,6 +4,7 @@ import {
deleteInstance,
startInstance,
recreateInstanceTunnel,
renameInstance,
} from "../api/sessions";
import type { Session } from "../api/sessions";
@@ -21,6 +22,7 @@ interface UseInstanceActionsReturn {
handleDelete: (session: Session) => Promise<void>;
handleForceDelete: (session: Session) => Promise<void>;
handleRecreateTunnel: (session: Session) => Promise<void>;
handleRename: (session: Session, newName: string) => Promise<void>;
clearDirtyDelete: () => void;
}
@@ -52,6 +54,7 @@ export function useInstanceActions(
url = `/projects/${session.project_id}`;
}
document.title = `${session.display_name} — Headquarter`;
const w = window.open(url, `session-${session.id}`);
tabRefs.current.set(key, w);
}, []);
@@ -177,6 +180,27 @@ export function useInstanceActions(
[loadingSessionId, onRefresh],
);
const handleRename = useCallback(
async (session: Session, newName: string) => {
if (!newName.trim()) return;
setLoadingSessionId(session.id);
try {
await renameInstance(
session.project_id,
session.repository_id,
session.id,
newName.trim(),
);
await onRefresh();
} catch {
// ignore
} finally {
setLoadingSessionId(null);
}
},
[onRefresh],
);
const clearDirtyDelete = useCallback(() => {
setDirtyDeleteSession(null);
setDirtyDeleteFiles([]);
@@ -192,6 +216,7 @@ export function useInstanceActions(
handleDelete,
handleForceDelete,
handleRecreateTunnel,
handleRename,
clearDirtyDelete,
};
}
+14
View File
@@ -61,6 +61,20 @@ export const useTerminalPage = () => {
}
}, [loading, sessions.length, error, instanceId, createSession]);
// Update document title based on active terminal session
useEffect(() => {
if (!instanceId) {
document.title = "Terminal — Headquarter";
return;
}
const active = sessions.find((s) => s.id === activeSessionId);
const name = active?.name ?? `Instance ${instanceId.slice(0, 8)}`;
document.title = `${name} — Terminal — Headquarter`;
return () => {
document.title = "Headquarter";
};
}, [instanceId, activeSessionId, sessions]);
// Sync refs with sessions
useEffect(() => {
for (const session of sessions) {
+11 -1
View File
@@ -72,5 +72,15 @@ export function useWorkspaceFiles(
refresh();
}, [refresh]);
return { entries, content, currentPath, loading, error, refresh, loadFile, saveFile, navigateTo };
return {
entries,
content,
currentPath,
loading,
error,
refresh,
loadFile,
saveFile,
navigateTo,
};
}