diff --git a/AppImage/components/script-terminal-modal.tsx b/AppImage/components/script-terminal-modal.tsx index 76f90669..db33d093 100644 --- a/AppImage/components/script-terminal-modal.tsx +++ b/AppImage/components/script-terminal-modal.tsx @@ -31,7 +31,7 @@ import { import "xterm/css/xterm.css" import { API_PORT } from "@/lib/api-config" import { getTicketedWsUrl } from "@/lib/terminal-ws" -import { scriptCompletion } from "@/lib/script-completion" +import { scriptCompletion, scriptCompletionOutput } from "@/lib/script-completion" import { useT } from "../lib/i18n/provider" interface WebInteraction { @@ -203,7 +203,7 @@ const initMessage = { if (scriptCompletion(exitCode, cancelledExitCode).kind !== "cancelled") termRef.current?.write(event.data) completionReceivedRef.current = true setIsComplete(true) - termRef.current?.writeln(`\x1b[${scriptCompletion(exitCode, cancelledExitCode).color}m${getCompletionMessage(exitCode)}\x1b[0m`) + termRef.current?.writeln(scriptCompletionOutput(exitCode, getCompletionMessage(exitCode), cancelledExitCode)) onCompleteRef.current?.(exitCode) if (ws.readyState === WebSocket.OPEN) ws.close(1000, "script complete") return @@ -234,7 +234,7 @@ const initMessage = { const exitCode = Number(msg.exit_code ?? 1) completionReceivedRef.current = true setIsComplete(true) - termRef.current?.writeln(`\x1b[${scriptCompletion(exitCode, cancelledExitCode).color}m${getCompletionMessage(exitCode)}\x1b[0m`) + termRef.current?.writeln(scriptCompletionOutput(exitCode, getCompletionMessage(exitCode), cancelledExitCode)) onCompleteRef.current?.(exitCode) if (ws.readyState === WebSocket.OPEN) ws.close(1000, "script complete") return @@ -420,7 +420,7 @@ const initMessage = { if (scriptCompletion(exitCode, cancelledExitCode).kind !== "cancelled") term.write(event.data) completionReceivedRef.current = true setIsComplete(true) - term.writeln(`\x1b[${scriptCompletion(exitCode, cancelledExitCode).color}m${getCompletionMessage(exitCode)}\x1b[0m`) + term.writeln(scriptCompletionOutput(exitCode, getCompletionMessage(exitCode), cancelledExitCode)) onCompleteRef.current?.(exitCode) if (ws.readyState === WebSocket.OPEN) ws.close(1000, "script complete") return @@ -453,7 +453,7 @@ const initMessage = { const exitCode = Number(msg.exit_code ?? 1) completionReceivedRef.current = true setIsComplete(true) - term.writeln(`\x1b[${scriptCompletion(exitCode, cancelledExitCode).color}m${getCompletionMessage(exitCode)}\x1b[0m`) + term.writeln(scriptCompletionOutput(exitCode, getCompletionMessage(exitCode), cancelledExitCode)) onCompleteRef.current?.(exitCode) if (ws.readyState === WebSocket.OPEN) ws.close(1000, "script complete") return diff --git a/AppImage/lib/script-completion.ts b/AppImage/lib/script-completion.ts index 7ee1d2c5..01f8cc4b 100644 --- a/AppImage/lib/script-completion.ts +++ b/AppImage/lib/script-completion.ts @@ -12,3 +12,11 @@ export function scriptCompletion(exitCode: number, cancelledExitCode?: number) { export function shouldRefreshOciAfterCompletion(exitCode?: number) { return exitCode !== OCI_CANCELLED_EXIT_CODE } + +export function scriptCompletionOutput(exitCode: number, message: string, cancelledExitCode?: number) { + const completion = scriptCompletion(exitCode, cancelledExitCode) + // Queue RIS with the result so earlier ncurses writes finish before reset. + // A synchronous terminal.reset() could be overwritten by that pending output. + const reset = completion.kind === "cancelled" ? "\x1bc" : "" + return `${reset}\x1b[${completion.color}m${message}\x1b[0m` +} diff --git a/tests/test_oci_update_cancellation.cjs b/tests/test_oci_update_cancellation.cjs index 0f364bc8..e9c74ed7 100644 --- a/tests/test_oci_update_cancellation.cjs +++ b/tests/test_oci_update_cancellation.cjs @@ -44,10 +44,12 @@ for (const handler of handlers) for (const locale of ['en','sk']) for (const fra if(kind==='cancelled') { assert.ok(rendered.includes(f.t('scriptTerminal.cancelled'))); assert.ok(rendered.includes('\x1b[36m')); + assert.ok(rendered.startsWith('\x1bc'),'cancel resets the obsolete dialog in the queued output'); assert.ok(!rendered.includes('Script exited with code')); assert.ok(!rendered.includes('\x1b[31m')); assert.ok(!rendered.includes(f.t('scriptTerminal.completedSuccessfully'))); } else { + assert.ok(!rendered.includes('\x1bc'),'success/errors and non-opted-in scripts keep their logs'); assert.ok(rendered.includes(kind==='success'?f.t('scriptTerminal.completedSuccessfully'):f.t('scriptTerminal.completedWithError',{code}))); } // Worker sends both forms; neither a second frame nor a heartbeat may complete twice.