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
+6 -5
View File
@@ -41,11 +41,12 @@ 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 session.url && hasWeb
: hasTerminal ? session.url
? `/instances/${session.id}/terminal` : hasTerminal
: `/projects/${session.project_id}`; ? `/instances/${session.id}/terminal`
: `/projects/${session.project_id}`;
const tooltipParts = [session.display_name, session.project_name]; const tooltipParts = [session.display_name, session.project_name];
if (session.workspace_name) tooltipParts.push(session.workspace_name); if (session.workspace_name) tooltipParts.push(session.workspace_name);
@@ -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 && (
@@ -3,128 +3,137 @@ import { SessionCard } from "./session-card";
import type { InstanceHealth } from "../../../api/sessions"; import type { InstanceHealth } from "../../../api/sessions";
export interface SessionListProps { export interface SessionListProps {
sessions: Session[]; sessions: Session[];
onOpen?: (session: Session) => void; onOpen?: (session: Session) => void;
onStart?: (session: Session) => void; onStart?: (session: Session) => void;
onStop?: (session: Session) => void; onStop?: (session: Session) => void;
onDelete?: (session: Session) => void; onDelete?: (session: Session) => void;
onRecreateTunnel?: (session: Session) => void; onRecreateTunnel?: (session: Session) => void;
onRename?: (session: Session, newName: string) => void; onRename?: (session: Session, newName: string) => void;
actionBusyId?: string | null; actionBusyId?: string | null;
tunnelHealth?: Record<string, InstanceHealth>; tunnelHealth?: Record<string, InstanceHealth>;
showGrouping?: boolean; showGrouping?: boolean;
activeTitle?: string; activeTitle?: string;
recentTitle?: string; recentTitle?: string;
maxRecent?: number; maxRecent?: number;
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({
sessions, sessions,
onOpen, onOpen,
onStart, onStart,
onStop, onStop,
onDelete, onDelete,
onRecreateTunnel, onRecreateTunnel,
onRename, onRename,
actionBusyId = null, actionBusyId = null,
tunnelHealth = {}, tunnelHealth = {},
showGrouping = true, showGrouping = true,
activeTitle = "Active Sessions", activeTitle = "Active Sessions",
recentTitle = "Recent Sessions", recentTitle = "Recent Sessions",
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) =>
const recentSessions = sessions activeStatuses.includes(s.status),
.filter((s) => recentStatuses.includes(s.status)) );
.slice(0, maxRecent); const recentSessions = sessions
.filter((s) => recentStatuses.includes(s.status))
.slice(0, maxRecent);
if (!showGrouping) { if (!showGrouping) {
return ( return (
<div className="sessions-grid"> <div className="sessions-grid">
{sessions.length === 0 ? ( {sessions.length === 0 ? (
<p className="muted">{emptyMessage}</p> <p className="muted">{emptyMessage}</p>
) : ( ) : (
sessions.map((session) => ( sessions.map((session) => (
<SessionCard <SessionCard
key={session.id} key={session.id}
session={session} session={session}
onOpen={onOpen} onOpen={onOpen}
onStart={onStart} onStart={onStart}
onStop={onStop} onStop={onStop}
onDelete={onDelete} onDelete={onDelete}
onRecreateTunnel={onRecreateTunnel} onRecreateTunnel={onRecreateTunnel}
onRename={onRename} onRename={onRename}
isBusy={actionBusyId === session.id} isBusy={actionBusyId === session.id}
tunnelHealth={tunnelHealth[session.id] || null} tunnelHealth={tunnelHealth[session.id] || null}
/> />
)) ))
)} )}
</div> </div>
); );
} }
return ( return (
<div className="session-list"> <div className="session-list">
{/* Active Sessions */} {/* Active Sessions */}
<div className="session-group"> <div className="session-group">
<div className="session-group-header"> <div className="session-group-header">
<h3>{activeTitle}</h3> <h3>{activeTitle}</h3>
{activeSessions.length > 0 && ( {activeSessions.length > 0 && (
<span className="badge">{activeSessions.length}</span> <span className="badge">{activeSessions.length}</span>
)} )}
</div> </div>
{activeSessions.length === 0 ? ( {activeSessions.length === 0 ? (
<p className="muted">No active sessions</p> <p className="muted">No active sessions</p>
) : ( ) : (
<div className="sessions-grid"> <div className="sessions-grid">
{activeSessions.map((session) => ( {activeSessions.map((session) => (
<SessionCard <SessionCard
key={session.id} key={session.id}
session={session} session={session}
onOpen={onOpen} onOpen={onOpen}
onStart={onStart} onStart={onStart}
onStop={onStop} onStop={onStop}
onDelete={onDelete} onDelete={onDelete}
onRecreateTunnel={onRecreateTunnel} onRecreateTunnel={onRecreateTunnel}
onRename={onRename} onRename={onRename}
isBusy={actionBusyId === session.id} isBusy={actionBusyId === session.id}
tunnelHealth={tunnelHealth[session.id] || null} tunnelHealth={tunnelHealth[session.id] || null}
/> />
))} ))}
</div> </div>
)} )}
</div> </div>
{/* Recent Sessions */} {/* Recent Sessions */}
{recentSessions.length > 0 && ( {recentSessions.length > 0 && (
<div className="session-group"> <div className="session-group">
<div className="session-group-header"> <div className="session-group-header">
<h3>{recentTitle}</h3> <h3>{recentTitle}</h3>
<span className="badge">{recentSessions.length}</span> <span className="badge">{recentSessions.length}</span>
</div> </div>
<div className="sessions-grid"> <div className="sessions-grid">
{recentSessions.map((session) => ( {recentSessions.map((session) => (
<SessionCard <SessionCard
key={session.id} key={session.id}
session={session} session={session}
onOpen={onOpen} onOpen={onOpen}
onStart={onStart} onStart={onStart}
onStop={onStop} onStop={onStop}
onDelete={onDelete} onDelete={onDelete}
onRecreateTunnel={onRecreateTunnel} onRecreateTunnel={onRecreateTunnel}
onRename={onRename} onRename={onRename}
isBusy={actionBusyId === session.id} isBusy={actionBusyId === session.id}
tunnelHealth={tunnelHealth[session.id] || null} tunnelHealth={tunnelHealth[session.id] || null}
/> />
))} ))}
</div> </div>
</div> </div>
)} )}
</div> </div>
); );
} }
@@ -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">
+272 -265
View File
@@ -10,294 +10,301 @@ 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,
name: s.name, name: s.name,
status: s.status as TerminalSessionInfo["status"], status: s.status as TerminalSessionInfo["status"],
})); }));
type TerminalStatus = type TerminalStatus =
| "connecting" | "connecting"
| "connected" | "connected"
| "disconnected" | "disconnected"
| "error" | "error"
| "resetting"; | "resetting";
export const useTerminalPage = () => { export const useTerminalPage = () => {
const { instanceId } = useParams<{ instanceId: string }>(); const { instanceId } = useParams<{ instanceId: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const isMobile = useMobileViewport(); const isMobile = useMobileViewport();
const [isFullscreen, setIsFullscreen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false);
const terminalRefs = useRef<Record<string, React.RefObject<TerminalRef>>>({}); const terminalRefs = useRef<Record<string, React.RefObject<TerminalRef>>>({});
const headerAutoHide = useAutoHide({ timeout: 3000, enabled: isMobile }); const headerAutoHide = useAutoHide({ timeout: 3000, enabled: isMobile });
const [terminalStatuses, setTerminalStatuses] = useState< const [terminalStatuses, setTerminalStatuses] = useState<
Record<string, TerminalStatus> Record<string, TerminalStatus>
>({}); >({});
const changeFontSizeRef = useRef<((delta: number) => void) | null>(null); const changeFontSizeRef = useRef<((delta: number) => void) | null>(null);
const sendDataRef = useRef<((data: string) => void) | null>(null); const sendDataRef = useRef<((data: string) => void) | null>(null);
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,
activeSessionId, activeSessionId,
setActiveSessionId, setActiveSessionId,
createSession, createSession,
closeSession, closeSession,
renameSession, renameSession,
resetSession, resetSession,
loading, loading,
error, error,
} = useTerminalSessions(instanceId ?? ""); } = useTerminalSessions(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) {
void createSession("Session 1"); void createSession("Session 1");
} }
}, [loading, sessions.length, error, instanceId, createSession]); }, [loading, sessions.length, error, instanceId, createSession]);
// Update document title based on active terminal session // Update document title based on active terminal session
useEffect(() => { useEffect(() => {
if (!instanceId) { if (!instanceId) {
document.title = "Terminal — Headquarter"; document.title = "Terminal — Headquarter";
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 name = active?.name ?? `Instance ${instanceId.slice(0, 8)}`;
document.title = `${name} — Terminal — Headquarter`; document.title = `${name} — Terminal — Headquarter`;
return () => { return () => {
document.title = "Headquarter"; document.title = "Headquarter";
}; };
}, [instanceId, activeSessionId, sessions]); }, [instanceId, activeSessionId, sessions]);
// Sync refs with sessions // Sync refs with sessions
useEffect(() => { useEffect(() => {
for (const session of sessions) { for (const session of sessions) {
if (!terminalRefs.current[session.id]) { if (!terminalRefs.current[session.id]) {
terminalRefs.current[session.id] = React.createRef<TerminalRef>(); terminalRefs.current[session.id] = React.createRef<TerminalRef>();
} }
} }
const currentIds = new Set(sessions.map((s) => s.id)); const currentIds = new Set(sessions.map((s) => s.id));
for (const id of Object.keys(terminalRefs.current)) { for (const id of Object.keys(terminalRefs.current)) {
if (!currentIds.has(id)) { if (!currentIds.has(id)) {
delete terminalRefs.current[id]; delete terminalRefs.current[id];
} }
} }
}, [sessions]); }, [sessions]);
// Fit and focus active terminal // Fit and focus active terminal
useEffect(() => { useEffect(() => {
if (activeSessionId && terminalRefs.current[activeSessionId]) { if (activeSessionId && terminalRefs.current[activeSessionId]) {
const ref = terminalRefs.current[activeSessionId]; const ref = terminalRefs.current[activeSessionId];
let raf1 = 0; let raf1 = 0;
let raf2 = 0; let raf2 = 0;
raf1 = requestAnimationFrame(() => { raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(() => { raf2 = requestAnimationFrame(() => {
ref.current?.fit(); ref.current?.fit();
ref.current?.focus(); ref.current?.focus();
}); });
}); });
return () => { return () => {
cancelAnimationFrame(raf1); cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2); cancelAnimationFrame(raf2);
}; };
} }
}, [activeSessionId]); }, [activeSessionId]);
// Keyboard shortcuts // Keyboard shortcuts
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
const isAltShift = e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey; const isAltShift = e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey;
if (!isAltShift) return; if (!isAltShift) return;
switch (e.key.toLowerCase()) { switch (e.key.toLowerCase()) {
case "n": case "n":
e.preventDefault(); e.preventDefault();
if (sessions.length < 5) { if (sessions.length < 5) {
void createSession(`Session ${sessions.length + 1}`); void createSession(`Session ${sessions.length + 1}`);
} }
break; break;
case "w": case "w":
e.preventDefault(); e.preventDefault();
if (activeSessionId && window.confirm("Close this terminal session?")) { if (
void closeSession(activeSessionId); activeSessionId &&
} window.confirm("Close this terminal session?")
break; ) {
case "arrowleft": void closeSession(activeSessionId);
e.preventDefault(); }
if (activeSessionId) { break;
const idx = sessions.findIndex((s) => s.id === activeSessionId); case "arrowleft":
if (idx > 0) setActiveSessionId(sessions[idx - 1].id); e.preventDefault();
} if (activeSessionId) {
break; const idx = sessions.findIndex((s) => s.id === activeSessionId);
case "arrowright": if (idx > 0) setActiveSessionId(sessions[idx - 1].id);
e.preventDefault(); }
if (activeSessionId) { break;
const idx = sessions.findIndex((s) => s.id === activeSessionId); case "arrowright":
if (idx < sessions.length - 1) setActiveSessionId(sessions[idx + 1].id); e.preventDefault();
} if (activeSessionId) {
break; const idx = sessions.findIndex((s) => s.id === activeSessionId);
case "r": if (idx < sessions.length - 1)
e.preventDefault(); setActiveSessionId(sessions[idx + 1].id);
if (activeSessionId) void resetSession(activeSessionId); }
break; break;
case "f": case "r":
e.preventDefault(); e.preventDefault();
setIsFullscreen((prev) => !prev); if (activeSessionId) void resetSession(activeSessionId);
break; break;
} case "f":
}; e.preventDefault();
setIsFullscreen((prev) => !prev);
break;
}
};
window.addEventListener("keydown", handleKeyDown); window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown);
}, [ }, [
sessions, sessions,
activeSessionId, activeSessionId,
createSession, createSession,
closeSession, closeSession,
resetSession, resetSession,
setActiveSessionId, setActiveSessionId,
]); ]);
// Keep screen awake // Keep screen awake
useEffect(() => { useEffect(() => {
let wakeLock: WakeLockSentinel | null = null; let wakeLock: WakeLockSentinel | null = null;
const requestWakeLock = async () => { const requestWakeLock = async () => {
try { try {
if ("wakeLock" in navigator) { if ("wakeLock" in navigator) {
wakeLock = await navigator.wakeLock.request("screen"); wakeLock = await navigator.wakeLock.request("screen");
} }
} catch { } catch {
// ignore // ignore
} }
}; };
void requestWakeLock(); void requestWakeLock();
const handleVisibilityChange = () => { const handleVisibilityChange = () => {
if (document.visibilityState === "visible") void requestWakeLock(); if (document.visibilityState === "visible") void requestWakeLock();
}; };
document.addEventListener("visibilitychange", handleVisibilityChange); document.addEventListener("visibilitychange", handleVisibilityChange);
return () => { return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange); document.removeEventListener("visibilitychange", handleVisibilityChange);
wakeLock?.release().catch(() => {}); wakeLock?.release().catch(() => {});
}; };
}, []); }, []);
// Lock page scroll on mobile // Lock page scroll on mobile
useEffect(() => { useEffect(() => {
if (!isMobile) return; if (!isMobile) return;
document.documentElement.classList.add("terminal-page-open"); document.documentElement.classList.add("terminal-page-open");
document.body.classList.add("terminal-page-open"); document.body.classList.add("terminal-page-open");
return () => { return () => {
document.documentElement.classList.remove("terminal-page-open"); document.documentElement.classList.remove("terminal-page-open");
document.body.classList.remove("terminal-page-open"); document.body.classList.remove("terminal-page-open");
}; };
}, [isMobile]); }, [isMobile]);
const handleFullscreenClick = useCallback( const handleFullscreenClick = useCallback(
(e: React.MouseEvent<HTMLElement>) => { (e: React.MouseEvent<HTMLElement>) => {
if (!isFullscreen) return; if (!isFullscreen) return;
const target = e.target as Node; const target = e.target as Node;
const current = e.currentTarget as HTMLElement; const current = e.currentTarget as HTMLElement;
const content = current.querySelector(".terminal-page-content"); const content = current.querySelector(".terminal-page-content");
const header = current.querySelector(".terminal-fullscreen-header"); const header = current.querySelector(".terminal-fullscreen-header");
if (content?.contains(target) || header?.contains(target)) return; if (content?.contains(target) || header?.contains(target)) return;
setIsFullscreen(false); setIsFullscreen(false);
}, },
[isFullscreen], [isFullscreen],
); );
const handleSelect = useCallback( const handleSelect = useCallback(
(sessionId: string) => setActiveSessionId(sessionId), (sessionId: string) => setActiveSessionId(sessionId),
[setActiveSessionId], [setActiveSessionId],
); );
const handleClose = useCallback( const handleClose = useCallback(
async (sessionId: string) => closeSession(sessionId), async (sessionId: string) => closeSession(sessionId),
[closeSession], [closeSession],
); );
const handleCreate = useCallback(() => { const handleCreate = useCallback(() => {
void createSession(`Session ${sessions.length + 1}`); void createSession(`Session ${sessions.length + 1}`);
}, [createSession, sessions.length]); }, [createSession, sessions.length]);
const handleRename = useCallback( const handleRename = useCallback(
(sessionId: string, newName: string) => { (sessionId: string, newName: string) => {
void renameSession(sessionId, newName); void renameSession(sessionId, newName);
}, },
[renameSession], [renameSession],
); );
const handleTerminalReady = useCallback( const handleTerminalReady = useCallback(
( (
sendData: (data: string) => void, sendData: (data: string) => void,
status: TerminalStatus, status: TerminalStatus,
focusInput: () => void, focusInput: () => void,
changeFontSize: (delta: number) => void, changeFontSize: (delta: number) => void,
) => { ) => {
setTerminalStatuses((prev) => ({ setTerminalStatuses((prev) => ({
...prev, ...prev,
[activeSessionId ?? "default"]: status, [activeSessionId ?? "default"]: status,
})); }));
sendDataRef.current = sendData; sendDataRef.current = sendData;
focusInputRef.current = focusInput; focusInputRef.current = focusInput;
changeFontSizeRef.current = changeFontSize; changeFontSizeRef.current = changeFontSize;
}, },
[activeSessionId], [activeSessionId],
); );
const handleFontSizeChange = useCallback((delta: number) => { const handleFontSizeChange = useCallback((delta: number) => {
changeFontSizeRef.current?.(delta); changeFontSizeRef.current?.(delta);
}, []); }, []);
const handleSendKey = useCallback((data: string) => { const handleSendKey = useCallback((data: string) => {
sendDataRef.current?.(data); sendDataRef.current?.(data);
}, []); }, []);
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
if (activeSessionId && terminalRefs.current[activeSessionId]) { if (activeSessionId && terminalRefs.current[activeSessionId]) {
terminalRefs.current[activeSessionId].current?.reset(); terminalRefs.current[activeSessionId].current?.reset();
} }
}, [activeSessionId]); }, [activeSessionId]);
return { return {
instanceId, instanceId,
navigate, navigate,
isMobile, isMobile,
isFullscreen, isFullscreen,
setIsFullscreen, setIsFullscreen,
terminalRefs, terminalRefs,
headerAutoHide, headerAutoHide,
terminalStatuses, terminalStatuses,
sendDataRef, sendDataRef,
focusInputRef, focusInputRef,
changeFontSizeRef, changeFontSizeRef,
showResetConfirm, showResetConfirm,
setShowResetConfirm, setShowResetConfirm,
showSpecialKeysPanel, showSpecialKeysPanel,
setShowSpecialKeysPanel, setShowSpecialKeysPanel,
activeModifier, activeModifier,
setActiveModifier, setActiveModifier,
isKeyboardOpen, isKeyboardOpen,
keyboardHeight, keyboardHeight,
sessions, sessions,
activeSessionId, activeSessionId,
setActiveSessionId, setActiveSessionId,
loading, loading,
error, error,
handleFullscreenClick, handleFullscreenClick,
handleSelect, handleSelect,
handleClose, handleClose,
handleCreate, handleCreate,
handleRename, handleRename,
handleTerminalReady, handleTerminalReady,
handleFontSizeChange, handleFontSizeChange,
handleSendKey, handleSendKey,
handleReset, handleReset,
sessionInfos: SESSIONS_TO_INFO(sessions), sessionInfos: SESSIONS_TO_INFO(sessions),
}; };
}; };
-1
View File
@@ -89,4 +89,3 @@
min-height: 44px; min-height: 44px;
} }
} }