From f1b968bb8810fa271f27b25459b8c7383567d65b Mon Sep 17 00:00:00 2001 From: Developer Date: Thu, 11 Jun 2026 08:11:04 +0000 Subject: [PATCH] feat: default session name to '{workspace} {tool_type}', improve terminal tab title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ToolStarter: - Auto-populate Session Name as '{workspace.name} {tool_type.display_name}' when a tool type is selected - Track whether user has manually edited the name (nameEdited flag) to avoid overwriting their custom input Terminal page: - Fetch instance display_name via getUserSessions for tab title - Tab title format: '{display_name} {terminal_session_name} — Terminal' instead of just '{session_name} — Terminal' Quality gates: tsc --noEmit pass, npm run build pass, 82/82 tests pass --- apps/web/src/api/sessions.ts | 11 +++++ .../components/features/tool/tool-starter.tsx | 40 ++++++++++++++----- apps/web/src/hooks/use-terminal-page.ts | 32 +++++++++++++-- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/apps/web/src/api/sessions.ts b/apps/web/src/api/sessions.ts index 2957168..ed80ce9 100644 --- a/apps/web/src/api/sessions.ts +++ b/apps/web/src/api/sessions.ts @@ -161,6 +161,17 @@ export async function deleteInstance( ); } +export async function getInstance( + projectId: string, + repoId: string, + instanceId: string, +): Promise { + const response = await apiClient.get( + `/projects/${projectId}/repositories/${repoId}/instances/${instanceId}`, + ); + return response.data; +} + export async function getUserSessions(): Promise { const response = await apiClient.get("/users/me/sessions"); return response.data.sessions; diff --git a/apps/web/src/components/features/tool/tool-starter.tsx b/apps/web/src/components/features/tool/tool-starter.tsx index bda85ba..e5680f9 100644 --- a/apps/web/src/components/features/tool/tool-starter.tsx +++ b/apps/web/src/components/features/tool/tool-starter.tsx @@ -3,7 +3,10 @@ import { useState, useEffect, useCallback } from "react"; import { Icon } from "../../icon"; import { listToolTypes, type ToolType } from "../../../api/tool-types"; -import { listConfigProfiles, type ConfigProfile } from "../../../api/config-profiles"; +import { + listConfigProfiles, + type ConfigProfile, +} from "../../../api/config-profiles"; import { listSSHKeys, type SSHKey } from "../../../api/ssh-keys"; import type { Workspace } from "../../../types/workspace"; import type { ToolInstance } from "../../../api/sessions"; @@ -34,6 +37,7 @@ export function ToolStarter({ const [selectedSshKeyIds, setSelectedSshKeyIds] = useState([]); const [displayName, setDisplayName] = useState(workspace.name); + const [nameEdited, setNameEdited] = useState(false); const [starting, setStarting] = useState(false); const [error, setError] = useState(null); @@ -115,7 +119,9 @@ export function ToolStarter({ setStarting(true); setError(null); try { - const { createInstance, startInstance } = await import("../../../api/sessions"); + const { createInstance, startInstance } = await import( + "../../../api/sessions" + ); const instance = await createInstance( workspace.project_id, workspace.repo_id, @@ -141,7 +147,13 @@ export function ToolStarter({ } finally { setStarting(false); } - }, [selectedToolTypeId, selectedProfileId, displayName, workspace, onStarted]); + }, [ + selectedToolTypeId, + selectedProfileId, + displayName, + workspace, + onStarted, + ]); return (
@@ -171,8 +183,13 @@ export function ToolStarter({ id="tool-type" value={selectedToolTypeId} onChange={(e) => { - setSelectedToolTypeId(e.target.value); + const toolId = e.target.value; + setSelectedToolTypeId(toolId); setError(null); + const tt = toolTypes.find((t) => t.id === toolId); + if (tt && !nameEdited) { + setDisplayName(`${workspace.name} ${tt.display_name}`); + } }} disabled={toolTypesLoading || starting} > @@ -192,12 +209,15 @@ export function ToolStarter({
setDisplayName(e.target.value)} - placeholder="My dev environment" - disabled={starting} + id="session-name" + type="text" + value={displayName} + onChange={(e) => { + setDisplayName(e.target.value); + setNameEdited(true); + }} + placeholder="My dev environment" + disabled={starting} />
diff --git a/apps/web/src/hooks/use-terminal-page.ts b/apps/web/src/hooks/use-terminal-page.ts index 9974e8c..dfeb820 100644 --- a/apps/web/src/hooks/use-terminal-page.ts +++ b/apps/web/src/hooks/use-terminal-page.ts @@ -9,6 +9,7 @@ import { useTerminalSessions } from "./use-terminal-sessions"; import type { TerminalSession } from "../api/terminal"; import type { ModifierKey } from "./use-special-keys"; + const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] => sessions.map((s) => ({ id: s.id, @@ -45,6 +46,10 @@ export const useTerminalPage = () => { const { isOpen: isKeyboardOpen, height: keyboardHeight } = useVirtualKeyboard(); + const [instanceDisplayName, setInstanceDisplayName] = useState( + null, + ); + const { sessions, activeSessionId, @@ -57,6 +62,26 @@ export const useTerminalPage = () => { error, } = useTerminalSessions(instanceId ?? ""); + // Fetch instance details for tab title + useEffect(() => { + if (!instanceId) return; + const load = async () => { + try { + // Try to find instance in user sessions first + const { getUserSessions } = await import("../api/sessions"); + const allSessions = await getUserSessions(); + const match = allSessions.find((s) => s.id === instanceId); + if (match) { + setInstanceDisplayName(match.display_name); + return; + } + } catch { + // ignore + } + }; + void load(); + }, [instanceId]); + // Auto-create default session useEffect(() => { if (!loading && sessions.length === 0 && !error && instanceId) { @@ -71,12 +96,13 @@ export const useTerminalPage = () => { return; } const active = sessions.find((s) => s.id === activeSessionId); - const name = active?.name ?? `Instance ${instanceId.slice(0, 8)}`; - document.title = `${name} — Terminal — Headquarter`; + const sessionName = active?.name ?? "Session"; + const baseName = instanceDisplayName ?? `Instance ${instanceId.slice(0, 8)}`; + document.title = `${baseName} ${sessionName} — Terminal — Headquarter`; return () => { document.title = "Headquarter"; }; - }, [instanceId, activeSessionId, sessions]); + }, [instanceId, activeSessionId, sessions, instanceDisplayName]); // Sync refs with sessions useEffect(() => {