Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,46 @@
* InputDialog styles
*/
.input-dialog {
padding: 4px;
display: flex;
flex-direction: column;

&__description {
margin: 0 0 20px 0;
font-size: 14px;
line-height: 1.6;
color: var(--text-secondary);
&__body {
padding: 20px 20px 16px;
}

&__input-wrapper {
margin-bottom: 24px;
&__description {
margin: 0 0 12px 0;
font-size: 12px;
line-height: 1.5;
color: var(--color-text-muted);
}

&__actions {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 4px;
align-items: center;
gap: 8px;
padding: 10px 20px 12px;
border-top: 1px solid var(--border-subtle);

// Give buttons explicit borders for visual consistency with the input field
.btn {
min-width: 68px;
border: 1px solid var(--border-base);

&-primary {
border-color: rgba(96, 165, 250, 0.4);
}
}
}
}

.modal--small .modal__content {
min-width: 320px;
.modal--small {
max-width: 360px;

.modal__content {
min-width: 0;
}
}

.modal-overlay:has(.input-dialog) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,10 @@ export const InputDialog: React.FC<InputDialogProps> = ({
showCloseButton={true}
>
<div className="input-dialog">
{description && (
<p className="input-dialog__description">{description}</p>
)}

<div className="input-dialog__input-wrapper">
<div className="input-dialog__body">
{description && (
<p className="input-dialog__description">{description}</p>
)}
<Input
ref={inputRef}
type={inputType}
Expand All @@ -136,14 +135,14 @@ export const InputDialog: React.FC<InputDialogProps> = ({
<div className="input-dialog__actions">
<Button
variant="secondary"
size="medium"
size="small"
onClick={handleCancel}
>
{resolvedCancelText}
</Button>
<Button
variant="primary"
size="medium"
size="small"
onClick={handleConfirm}
>
{resolvedConfirmText}
Expand Down
6 changes: 4 additions & 2 deletions src/web-ui/src/component-library/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import React, { useEffect, useState, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { useI18n } from '@/infrastructure/i18n';
import './Modal.scss';

Expand Down Expand Up @@ -241,7 +242,7 @@ export const Modal: React.FC<ModalProps> = ({
...(dimensions && resizable ? { width: dimensions.width, height: dimensions.height } : {})
} : {};

return (
return createPortal(
<div className={`modal-overlay ${placement !== 'center' ? `modal-overlay--${placement}` : ''}`} onClick={onClose}>
<div
ref={modalRef}
Expand Down Expand Up @@ -294,6 +295,7 @@ export const Modal: React.FC<ModalProps> = ({
</>
)}
</div>
</div>
</div>,
document.body
);
};
18 changes: 11 additions & 7 deletions src/web-ui/src/tools/terminal/components/ConnectedTerminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,28 @@ const ConnectedTerminal: React.FC<ConnectedTerminalProps> = memo(({
const [title, setTitle] = useState<string>(initialSession?.name || 'Terminal');
const [exitCode, setExitCode] = useState<number | null>(null);
const [isExited, setIsExited] = useState(false);
const [isTerminalReady, setIsTerminalReady] = useState(false);

const lastSentSizeRef = useRef<{ cols: number; rows: number } | null>(null);

// Buffer output until the terminal is ready.
// Use a ref (not state) to avoid stale closure issues with isTerminalReady.
const isTerminalReadyRef = useRef(false);
const outputQueueRef = useRef<string[]>([]);

const handleOutput = useCallback((data: string) => {
if (!isTerminalReady || !terminalRef.current) {
if (!isTerminalReadyRef.current || !terminalRef.current) {
outputQueueRef.current.push(data);
return;
}
terminalRef.current.write(data);
}, [isTerminalReady]);
}, []); // No state deps - reads from refs which are always current

const flushOutputQueue = useCallback(() => {
const queue = outputQueueRef.current;
if (queue.length === 0) return;
queue.forEach(data => terminalRef.current?.write(data));
// Clear first to prevent orphaned items if new data arrives during flush
outputQueueRef.current = [];
queue.forEach(data => terminalRef.current?.write(data));
}, []);

const handleReady = useCallback(() => {
Expand Down Expand Up @@ -129,9 +131,11 @@ const ConnectedTerminal: React.FC<ConnectedTerminalProps> = memo(({
}, [onTitleChange]);

const handleTerminalReady = useCallback(() => {
console.log('[ConnectedTerminal] handleTerminalReady called');
setIsTerminalReady(true);

// Set the ref synchronously first so handleOutput immediately writes directly
// instead of queuing. This eliminates the stale-closure window where new data
// would be queued after flushOutputQueue() cleared the queue but before React
// re-rendered and updated onOutputRef.current.
isTerminalReadyRef.current = true;
flushOutputQueue();
}, [flushOutputQueue]);

Expand Down
Loading