feat: add session name input to tool starter

ToolStarter (used by FAB and workspace detail):
- Add Session Name text input, defaulting to workspace.name
- Pass user-provided name to createInstance display_name parameter
- If left empty or only whitespace, falls back to auto-generated name

Quality gates: tsc --noEmit pass, npm run build pass, 82/82 tests pass
This commit is contained in:
Developer
2026-06-10 20:26:34 +00:00
parent 1d10283fc9
commit 3c222d4f0f
6 changed files with 419 additions and 390 deletions
+2 -1
View File
@@ -41,7 +41,8 @@ const SessionItem = ({ session }: { session: Session }) => {
// - Everything else falls back to the project page // - Everything else falls back to the project page
const hasTerminal = session.tool_type_interfaces.includes("terminal"); const hasTerminal = session.tool_type_interfaces.includes("terminal");
const hasWeb = session.tool_type_interfaces.includes("web"); const hasWeb = session.tool_type_interfaces.includes("web");
const href = session.url && hasWeb const href =
session.url && hasWeb
? session.url ? session.url
: hasTerminal : hasTerminal
? `/instances/${session.id}/terminal` ? `/instances/${session.id}/terminal`
@@ -171,8 +171,7 @@ export function SessionCard({
{" "} {" "}
/ <span>{session.repository_name}</span> / <span>{session.repository_name}</span>
</> </>
)} )}{" "}
{" "}
· {session.tool_type_name} · {session.tool_type_name}
</p> </p>
{session.clone_mode && ( {session.clone_mode && (
@@ -19,7 +19,14 @@ export interface SessionListProps {
emptyMessage?: string; emptyMessage?: string;
} }
const activeStatuses = ["running", "building", "starting", "probing", "pending", "unhealthy"]; const activeStatuses = [
"running",
"building",
"starting",
"probing",
"pending",
"unhealthy",
];
const recentStatuses = ["stopped", "error"]; const recentStatuses = ["stopped", "error"];
export function SessionList({ export function SessionList({
@@ -38,7 +45,9 @@ export function SessionList({
maxRecent = 5, maxRecent = 5,
emptyMessage = "No sessions", emptyMessage = "No sessions",
}: SessionListProps) { }: SessionListProps) {
const activeSessions = sessions.filter((s) => activeStatuses.includes(s.status)); const activeSessions = sessions.filter((s) =>
activeStatuses.includes(s.status),
);
const recentSessions = sessions const recentSessions = sessions
.filter((s) => recentStatuses.includes(s.status)) .filter((s) => recentStatuses.includes(s.status))
.slice(0, maxRecent); .slice(0, maxRecent);
@@ -33,6 +33,7 @@ export function ToolStarter({
const [sshKeysLoading, setSshKeysLoading] = useState(true); const [sshKeysLoading, setSshKeysLoading] = useState(true);
const [selectedSshKeyIds, setSelectedSshKeyIds] = useState<string[]>([]); const [selectedSshKeyIds, setSelectedSshKeyIds] = useState<string[]>([]);
const [displayName, setDisplayName] = useState(workspace.name);
const [starting, setStarting] = useState(false); const [starting, setStarting] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -119,7 +120,7 @@ export function ToolStarter({
workspace.project_id, workspace.project_id,
workspace.repo_id, workspace.repo_id,
selectedToolTypeId, selectedToolTypeId,
workspace.name, displayName.trim() || undefined,
undefined, undefined,
undefined, undefined,
undefined, undefined,
@@ -140,7 +141,7 @@ export function ToolStarter({
} finally { } finally {
setStarting(false); setStarting(false);
} }
}, [selectedToolTypeId, selectedProfileId, workspace, onStarted]); }, [selectedToolTypeId, selectedProfileId, displayName, workspace, onStarted]);
return ( return (
<div className="tool-starter"> <div className="tool-starter">
@@ -187,6 +188,19 @@ export function ToolStarter({
{toolTypesError && <span className="error-text">{toolTypesError}</span>} {toolTypesError && <span className="error-text">{toolTypesError}</span>}
</div> </div>
{/* Session Name */}
<div className="form-group">
<label htmlFor="session-name">Session Name</label>
<input
id="session-name"
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="My dev environment"
disabled={starting}
/>
</div>
{/* Config Profile */} {/* Config Profile */}
{selectedToolTypeId && ( {selectedToolTypeId && (
<div className="form-group"> <div className="form-group">
+11 -4
View File
@@ -39,8 +39,11 @@ export const useTerminalPage = () => {
const focusInputRef = useRef<(() => void) | null>(null); const focusInputRef = useRef<(() => void) | null>(null);
const [showResetConfirm, setShowResetConfirm] = useState(false); const [showResetConfirm, setShowResetConfirm] = useState(false);
const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false); const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false);
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(null); const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(
const { isOpen: isKeyboardOpen, height: keyboardHeight } = useVirtualKeyboard(); null,
);
const { isOpen: isKeyboardOpen, height: keyboardHeight } =
useVirtualKeyboard();
const { const {
sessions, sessions,
@@ -124,7 +127,10 @@ export const useTerminalPage = () => {
break; break;
case "w": case "w":
e.preventDefault(); e.preventDefault();
if (activeSessionId && window.confirm("Close this terminal session?")) { if (
activeSessionId &&
window.confirm("Close this terminal session?")
) {
void closeSession(activeSessionId); void closeSession(activeSessionId);
} }
break; break;
@@ -139,7 +145,8 @@ export const useTerminalPage = () => {
e.preventDefault(); e.preventDefault();
if (activeSessionId) { if (activeSessionId) {
const idx = sessions.findIndex((s) => s.id === activeSessionId); const idx = sessions.findIndex((s) => s.id === activeSessionId);
if (idx < sessions.length - 1) setActiveSessionId(sessions[idx + 1].id); if (idx < sessions.length - 1)
setActiveSessionId(sessions[idx + 1].id);
} }
break; break;
case "r": case "r":
-1
View File
@@ -89,4 +89,3 @@
min-height: 44px; min-height: 44px;
} }
} }