feat: default session name to '{workspace} {tool_type}', improve terminal tab title

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
This commit is contained in:
Developer
2026-06-11 08:11:04 +00:00
parent 3c222d4f0f
commit f1b968bb88
3 changed files with 70 additions and 13 deletions
+11
View File
@@ -161,6 +161,17 @@ export async function deleteInstance(
); );
} }
export async function getInstance(
projectId: string,
repoId: string,
instanceId: string,
): Promise<ToolInstance> {
const response = await apiClient.get(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}`,
);
return response.data;
}
export async function getUserSessions(): Promise<Session[]> { export async function getUserSessions(): Promise<Session[]> {
const response = await apiClient.get("/users/me/sessions"); const response = await apiClient.get("/users/me/sessions");
return response.data.sessions; return response.data.sessions;
@@ -3,7 +3,10 @@
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { Icon } from "../../icon"; import { Icon } from "../../icon";
import { listToolTypes, type ToolType } from "../../../api/tool-types"; 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 { listSSHKeys, type SSHKey } from "../../../api/ssh-keys";
import type { Workspace } from "../../../types/workspace"; import type { Workspace } from "../../../types/workspace";
import type { ToolInstance } from "../../../api/sessions"; import type { ToolInstance } from "../../../api/sessions";
@@ -34,6 +37,7 @@ export function ToolStarter({
const [selectedSshKeyIds, setSelectedSshKeyIds] = useState<string[]>([]); const [selectedSshKeyIds, setSelectedSshKeyIds] = useState<string[]>([]);
const [displayName, setDisplayName] = useState(workspace.name); const [displayName, setDisplayName] = useState(workspace.name);
const [nameEdited, setNameEdited] = useState(false);
const [starting, setStarting] = useState(false); const [starting, setStarting] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -115,7 +119,9 @@ export function ToolStarter({
setStarting(true); setStarting(true);
setError(null); setError(null);
try { try {
const { createInstance, startInstance } = await import("../../../api/sessions"); const { createInstance, startInstance } = await import(
"../../../api/sessions"
);
const instance = await createInstance( const instance = await createInstance(
workspace.project_id, workspace.project_id,
workspace.repo_id, workspace.repo_id,
@@ -141,7 +147,13 @@ export function ToolStarter({
} finally { } finally {
setStarting(false); setStarting(false);
} }
}, [selectedToolTypeId, selectedProfileId, displayName, workspace, onStarted]); }, [
selectedToolTypeId,
selectedProfileId,
displayName,
workspace,
onStarted,
]);
return ( return (
<div className="tool-starter"> <div className="tool-starter">
@@ -171,8 +183,13 @@ export function ToolStarter({
id="tool-type" id="tool-type"
value={selectedToolTypeId} value={selectedToolTypeId}
onChange={(e) => { onChange={(e) => {
setSelectedToolTypeId(e.target.value); const toolId = e.target.value;
setSelectedToolTypeId(toolId);
setError(null); setError(null);
const tt = toolTypes.find((t) => t.id === toolId);
if (tt && !nameEdited) {
setDisplayName(`${workspace.name} ${tt.display_name}`);
}
}} }}
disabled={toolTypesLoading || starting} disabled={toolTypesLoading || starting}
> >
@@ -192,12 +209,15 @@ export function ToolStarter({
<div className="form-group"> <div className="form-group">
<label htmlFor="session-name">Session Name</label> <label htmlFor="session-name">Session Name</label>
<input <input
id="session-name" id="session-name"
type="text" type="text"
value={displayName} value={displayName}
onChange={(e) => setDisplayName(e.target.value)} onChange={(e) => {
placeholder="My dev environment" setDisplayName(e.target.value);
disabled={starting} setNameEdited(true);
}}
placeholder="My dev environment"
disabled={starting}
/> />
</div> </div>
+29 -3
View File
@@ -9,6 +9,7 @@ import { useTerminalSessions } from "./use-terminal-sessions";
import type { TerminalSession } from "../api/terminal"; import type { TerminalSession } from "../api/terminal";
import type { ModifierKey } from "./use-special-keys"; import type { ModifierKey } from "./use-special-keys";
const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] => const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] =>
sessions.map((s) => ({ sessions.map((s) => ({
id: s.id, id: s.id,
@@ -45,6 +46,10 @@ export const useTerminalPage = () => {
const { isOpen: isKeyboardOpen, height: keyboardHeight } = const { isOpen: isKeyboardOpen, height: keyboardHeight } =
useVirtualKeyboard(); useVirtualKeyboard();
const [instanceDisplayName, setInstanceDisplayName] = useState<string | null>(
null,
);
const { const {
sessions, sessions,
activeSessionId, activeSessionId,
@@ -57,6 +62,26 @@ export const useTerminalPage = () => {
error, error,
} = useTerminalSessions(instanceId ?? ""); } = 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 // Auto-create default session
useEffect(() => { useEffect(() => {
if (!loading && sessions.length === 0 && !error && instanceId) { if (!loading && sessions.length === 0 && !error && instanceId) {
@@ -71,12 +96,13 @@ export const useTerminalPage = () => {
return; return;
} }
const active = sessions.find((s) => s.id === activeSessionId); const active = sessions.find((s) => s.id === activeSessionId);
const name = active?.name ?? `Instance ${instanceId.slice(0, 8)}`; const sessionName = active?.name ?? "Session";
document.title = `${name} — Terminal — Headquarter`; const baseName = instanceDisplayName ?? `Instance ${instanceId.slice(0, 8)}`;
document.title = `${baseName} ${sessionName} — Terminal — Headquarter`;
return () => { return () => {
document.title = "Headquarter"; document.title = "Headquarter";
}; };
}, [instanceId, activeSessionId, sessions]); }, [instanceId, activeSessionId, sessions, instanceDisplayName]);
// Sync refs with sessions // Sync refs with sessions
useEffect(() => { useEffect(() => {