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:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user