Merge branch 'dev' of ssh://git.commumedia.org:2222/alex/headquarter into dev

This commit is contained in:
Alex Blank
2026-05-28 20:36:39 +02:00
5 changed files with 84 additions and 47 deletions
+11
View File
@@ -37,6 +37,7 @@ export interface TerminalProps {
export interface TerminalRef {
fit: () => void;
focus: () => void;
}
const FONT_SIZE_KEY = "terminal-font-size";
@@ -106,6 +107,7 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
wsRef.current = ws;
ws.onopen = () => {
console.log(`[Terminal ${sessionId ?? "default"}] WebSocket opened`);
setStatus("connected");
setError(null);
reconnectAttemptsRef.current = 0;
@@ -137,6 +139,8 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
if (!termRef.current) return;
if (event.data instanceof Blob) {
// eslint-disable-next-line no-console
console.log(`[Terminal ${sessionId ?? "default"}] received ${(event.data as Blob).size} bytes`);
event.data.arrayBuffer().then((buffer) => {
const data = new Uint8Array(buffer);
termRef.current?.write(data);
@@ -306,6 +310,8 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
// Open xterm first (must happen before fit)
term.open(container);
term.focus();
console.log(`[Terminal ${sessionId ?? "default"}] xterm opened and focused`);
const ws = connectWebSocket();
// Initial fit after layout settles (terminal must be opened first)
@@ -330,6 +336,8 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
// Handle terminal input
term.onData((data) => {
// eslint-disable-next-line no-console
console.log(`[Terminal ${sessionId ?? "default"}] sending:`, JSON.stringify(data));
const currentWs = wsRef.current;
if (currentWs?.readyState !== WebSocket.OPEN) return;
@@ -466,6 +474,9 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
}
}
},
focus: () => {
termRef.current?.focus();
},
}));
// Update parent about status changes
+1 -1
View File
@@ -25,7 +25,7 @@ export function useTerminalSessions(
): UseTerminalSessionsResult {
const [sessions, setSessions] = useState<TerminalSession[]>([]);
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadSessions = useCallback(async () => {
+29 -36
View File
@@ -39,7 +39,7 @@ export const TerminalPage: React.FC = () => {
error,
} = useTerminalSessions(instanceId ?? "");
// Auto-create default session if none exist
// Auto-create default session if none exist after loading completes
useEffect(() => {
if (!loading && sessions.length === 0 && !error && instanceId) {
void createSession("Session 1");
@@ -62,13 +62,14 @@ export const TerminalPage: React.FC = () => {
}
}, [sessions]);
// Fit active terminal when switching tabs
// Fit and focus active terminal when switching tabs
useEffect(() => {
if (activeSessionId && terminalRefs.current[activeSessionId]) {
const ref = terminalRefs.current[activeSessionId];
// Small delay to allow display:block to apply
const timer = setTimeout(() => {
ref.current?.fit();
ref.current?.focus();
}, 50);
return () => clearTimeout(timer);
}
@@ -229,23 +230,19 @@ export const TerminalPage: React.FC = () => {
</div>
<div className="terminal-page-content">
{error && <div className="terminal-error-banner">{error}</div>}
{sessions.map((session) => (
<div
key={session.id}
className={`terminal-instance ${session.id === activeSessionId ? "active" : ""}`}
style={{
display: session.id === activeSessionId ? "flex" : "none",
}}
>
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => handleClose(session.id)}
isMobile={true}
/>
</div>
))}
{sessions
.filter((session) => session.id === activeSessionId)
.map((session) => (
<div key={session.id} className="terminal-instance active">
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => handleClose(session.id)}
isMobile={true}
/>
</div>
))}
{sessions.length === 0 && !loading && (
<div className="terminal-empty-state">
<p>No terminal sessions. Press Alt+Shift+N to create one.</p>
@@ -289,23 +286,19 @@ export const TerminalPage: React.FC = () => {
/>
<div className="terminal-page-content">
{error && <div className="terminal-error-banner">{error}</div>}
{sessions.map((session) => (
<div
key={session.id}
className={`terminal-instance ${session.id === activeSessionId ? "active" : ""}`}
style={{
display: session.id === activeSessionId ? "flex" : "none",
}}
>
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => handleClose(session.id)}
isMobile={false}
/>
</div>
))}
{sessions
.filter((session) => session.id === activeSessionId)
.map((session) => (
<div key={session.id} className="terminal-instance active">
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => handleClose(session.id)}
isMobile={false}
/>
</div>
))}
{sessions.length === 0 && !loading && (
<div className="terminal-empty-state">
<p>No terminal sessions. Press Alt+Shift+N to create one.</p>