fix: ensure exit message persists by setting it reactively (OpenCode pattern)
The exit message was appearing briefly then disappearing because: 1. The message wasn't pre-computed before exit 2. The timing of when the message was written wasn't synchronized Changes (matching OpenCode's pattern): - ExitProvider now has setExitMessage/getExitMessage functions - ExitProvider.exit() is now async and awaits onExit callback - Message is written AFTER renderer.destroy() and onExit, before process.exit() - AppContent uses createEffect to set exit message reactively - Message is pre-computed and ready when exit is called This ensures the message is written synchronously after cleanup, preventing it from being cleared by async operations or shell prompt.
This commit is contained in:
@@ -20,11 +20,13 @@ import { matchesAction } from "@services/keybind-resolver";
|
||||
import { copyToClipboard } from "@services/clipboard/text-clipboard";
|
||||
import versionData from "@/version.json";
|
||||
import { ExitProvider, useExit } from "@tui-solid/context/exit";
|
||||
import { generateSessionSummary } from "@utils/core/session-stats";
|
||||
import { RouteProvider, useRoute } from "@tui-solid/context/route";
|
||||
import {
|
||||
AppStoreProvider,
|
||||
useAppStore,
|
||||
setAppStoreRef,
|
||||
appStore,
|
||||
} from "@tui-solid/context/app";
|
||||
import { ThemeProvider, useTheme } from "@tui-solid/context/theme";
|
||||
import { KeybindProvider } from "@tui-solid/context/keybind";
|
||||
@@ -116,6 +118,20 @@ function AppContent(props: AppProps) {
|
||||
|
||||
setAppStoreRef(app);
|
||||
|
||||
// Set exit message reactively (like OpenCode does)
|
||||
// This ensures the message is pre-computed and ready when exit is called
|
||||
createEffect(() => {
|
||||
const state = appStore.getState();
|
||||
const summary = generateSessionSummary({
|
||||
sessionId: props.sessionId ?? "unknown",
|
||||
sessionStats: state.sessionStats,
|
||||
modifiedFiles: state.modifiedFiles,
|
||||
modelName: state.model,
|
||||
providerName: state.provider,
|
||||
});
|
||||
exit.setExitMessage(summary);
|
||||
});
|
||||
|
||||
/** Copy selected text to clipboard and clear selection */
|
||||
const copySelectionToClipboard = async (): Promise<void> => {
|
||||
const text = renderer.getSelection()?.getSelectedText();
|
||||
@@ -518,7 +534,6 @@ function App(props: AppProps) {
|
||||
return (
|
||||
<ErrorBoundary fallback={(err: Error) => <ErrorFallback error={err} />}>
|
||||
<ExitProvider
|
||||
sessionId={props.sessionId}
|
||||
onExit={() => props.onExit({ exitCode: 0, sessionId: props.sessionId })}
|
||||
>
|
||||
<RouteProvider>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { createSignal, onCleanup } from "solid-js";
|
||||
import { useRenderer } from "@opentui/solid";
|
||||
import { createSimpleContext } from "./helper";
|
||||
import { generateSessionSummary } from "@utils/core/session-stats";
|
||||
import { appStore } from "./app";
|
||||
|
||||
interface ExitContextInput extends Record<string, unknown> {
|
||||
sessionId?: string;
|
||||
onExit?: () => void;
|
||||
onExit?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ExitContextValue {
|
||||
@@ -16,6 +13,8 @@ interface ExitContextValue {
|
||||
requestExit: () => void;
|
||||
cancelExit: () => void;
|
||||
confirmExit: () => void;
|
||||
setExitMessage: (message: string | undefined) => void;
|
||||
getExitMessage: () => string | undefined;
|
||||
}
|
||||
|
||||
export const { provider: ExitProvider, use: useExit } = createSimpleContext<
|
||||
@@ -28,34 +27,43 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext<
|
||||
const [exitCode, setExitCode] = createSignal(0);
|
||||
const [isExiting, setIsExiting] = createSignal(false);
|
||||
const [exitRequested, setExitRequested] = createSignal(false);
|
||||
const [exitMessage, setExitMessageState] = createSignal<string | undefined>(undefined);
|
||||
|
||||
const exit = (code = 0): void => {
|
||||
const exit = async (code = 0): Promise<void> => {
|
||||
setExitCode(code);
|
||||
setIsExiting(true);
|
||||
|
||||
// Generate and store exit message before destroying renderer
|
||||
const state = appStore.getState();
|
||||
const summary = generateSessionSummary({
|
||||
sessionId: props.sessionId ?? "unknown",
|
||||
sessionStats: state.sessionStats,
|
||||
modifiedFiles: state.modifiedFiles,
|
||||
modelName: state.model,
|
||||
providerName: state.provider,
|
||||
});
|
||||
// Reset window title before destroying renderer
|
||||
try {
|
||||
renderer.setTerminalTitle("");
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Destroy renderer first to stop rendering
|
||||
// Destroy renderer to stop rendering and exit alternate screen
|
||||
renderer.destroy();
|
||||
|
||||
// Call the onExit callback
|
||||
props.onExit?.();
|
||||
// Call the onExit callback (may be async)
|
||||
await props.onExit?.();
|
||||
|
||||
// Write the summary after renderer is destroyed
|
||||
process.stdout.write(summary + "\n");
|
||||
// Write the stored exit message after renderer is destroyed
|
||||
const message = exitMessage();
|
||||
if (message) {
|
||||
process.stdout.write(message + "\n");
|
||||
}
|
||||
|
||||
// Exit the process
|
||||
process.exit(code);
|
||||
};
|
||||
|
||||
const setExitMessage = (message: string | undefined): void => {
|
||||
setExitMessageState(message);
|
||||
};
|
||||
|
||||
const getExitMessage = (): string | undefined => {
|
||||
return exitMessage();
|
||||
};
|
||||
|
||||
const requestExit = (): void => {
|
||||
setExitRequested(true);
|
||||
};
|
||||
@@ -73,6 +81,7 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext<
|
||||
onCleanup(() => {
|
||||
setIsExiting(false);
|
||||
setExitRequested(false);
|
||||
setExitMessageState(undefined);
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -82,6 +91,8 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext<
|
||||
requestExit,
|
||||
cancelExit,
|
||||
confirmExit,
|
||||
setExitMessage,
|
||||
getExitMessage,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user