import { createCliRenderer } from '@opentui/core'; import { createRoot } from '@opentui/react'; import { closeDb, initDb } from '../db'; import { App } from './App'; import { createTuiLifecycle, registerTuiTerminationSignals } from './lifecycle'; /** Launch a fully initialized TUI, preserving the programmatic custom-database contract. */ export async function launchTui(dbPath?: string): Promise { initDb(dbPath); let renderer: Awaited> | undefined; let removeTerminationListeners = () => {}; let exitHandlerRegistered = false; let handleExit: (() => void) | undefined; try { renderer = await createCliRenderer({ exitOnCtrlC: false, exitSignals: [], }); const root = createRoot(renderer); handleExit = () => closeDb(); const lifecycle = createTuiLifecycle({ unmount: () => root.unmount(), destroyRenderer: () => { removeTerminationListeners(); if (handleExit) process.off('exit', handleExit); renderer?.destroy(); }, closeDatabase: closeDb, }); // Keep a process-exit fallback for consumers that launch the TUI directly, // but remove this per-session listener during normal lifecycle shutdown. process.once('exit', handleExit); exitHandlerRegistered = true; removeTerminationListeners = registerTuiTerminationSignals(lifecycle); root.render(); } catch (error) { removeTerminationListeners(); // A renderer/root construction failure occurs before the lifecycle can own // cleanup. Do not leave the database connection or signal listeners behind. if (exitHandlerRegistered && handleExit) process.off('exit', handleExit); try { renderer?.destroy(); } catch { // Preserve the renderer/root construction error. } closeDb(); throw error; } }