症状
実端末で tecode を Ctrl+C で終了すると、wireProcessExit の shutdown() が一度も実行されない。開いていたタブ/レイアウトは保存されず、全サービスの dispose も走らない。
根本原因
shutdown() は SIGINT / SIGTERM にのみ紐付いている(packages/cli/src/main.ts)。
for(constsignalof["SIGINT","SIGTERM"]asconst){process.once(signal,()=>{voidshutdown().finally(()=>process.exit(0));});ところが通常の対話終了では SIGINT が発生しない。
createCliRenderer() が stdin.setRawMode(true) を呼ぶ。Node の raw mode はシグナル生成を無効化するため、Ctrl+C は \x03 という通常の入力バイトとして届く。- ピン留めされている
@opentui/core@0.1.107 は、これを自前の exitOnCtrlC キー処理経路(既定 true、matchesKeyBinding で判定)で受け、renderer.destroy() を直接呼んでプロセスを終える。 - その結果
process.once("SIGINT", ...) は発火せず、shutdown() は通常終了パスにおいて実質デッドコードになっている。
失われている処理
awaitroot.layoutState.flush();// ← レイアウト状態の永続化root.config.dispose();root.chordPendingIndicator.dispose();root.chordMachine.dispose();root.findService.dispose();root.editorSession.dispose();// ... 以下 15 個以上の disposeawaitroot.hostRef.current?.disposeAll();// ← 拡張機能の deactivate
とくに layoutState.flush() が走らないため、セッションのレイアウトが毎回静かに失われる。拡張機能の deactivate も呼ばれない。
補足
destroy() の cleanupBeforeDestroy() は setRawMode(false) と stdin リスナ解除を正しく行っているため、端末の復元自体には問題は見つかっていない。あくまで tecode 側のクリーンアップが呼ばれない点が本件。
なお Issue #82 の調査中に判明したもので、#82 の修正(テキスト面への初期フォーカス)とは独立している。
修正方針の候補
createCliRenderer に exitOnCtrlC: false を渡し、Ctrl+C を tecode 自身のキーバインドとして扱って shutdown() を通す。終了コマンドが明示的になる利点もある(現状 ctrl+q は未バインド)。- あるいは OpenTUI の終了フックに
shutdown() を接続する。
前者なら「終了前に未保存の変更を確認する」といった将来の要求にも素直に繋がる。ただし Ctrl+C が効かなくなる実装ミスは致命的なので、実端末での確認が必要。
検証上の注意
この不具合は実 TTY がないと再現できない。開発サンドボックスでは main.ts が stdout 非 TTY 時に no-op の renderShellHeadless を強制するため、そもそも実レンダラ経路が走らない。テストで固定するなら、exitOnCtrlC 経路ではなく「shutdown() が終了時に呼ばれること」を注入可能なシームとして検証する形になる。
症状
実端末で tecode を Ctrl+C で終了すると、
wireProcessExitのshutdown()が一度も実行されない。開いていたタブ/レイアウトは保存されず、全サービスの dispose も走らない。根本原因
shutdown()は SIGINT / SIGTERM にのみ紐付いている(packages/cli/src/main.ts)。ところが通常の対話終了では SIGINT が発生しない。
createCliRenderer()がstdin.setRawMode(true)を呼ぶ。Node の raw mode はシグナル生成を無効化するため、Ctrl+C は\x03という通常の入力バイトとして届く。@opentui/core@0.1.107は、これを自前のexitOnCtrlCキー処理経路(既定true、matchesKeyBindingで判定)で受け、renderer.destroy()を直接呼んでプロセスを終える。process.once("SIGINT", ...)は発火せず、shutdown()は通常終了パスにおいて実質デッドコードになっている。失われている処理
とくに
layoutState.flush()が走らないため、セッションのレイアウトが毎回静かに失われる。拡張機能のdeactivateも呼ばれない。補足
destroy()のcleanupBeforeDestroy()はsetRawMode(false)と stdin リスナ解除を正しく行っているため、端末の復元自体には問題は見つかっていない。あくまで tecode 側のクリーンアップが呼ばれない点が本件。なお Issue #82 の調査中に判明したもので、#82 の修正(テキスト面への初期フォーカス)とは独立している。
修正方針の候補
createCliRendererにexitOnCtrlC: falseを渡し、Ctrl+C を tecode 自身のキーバインドとして扱ってshutdown()を通す。終了コマンドが明示的になる利点もある(現状ctrl+qは未バインド)。shutdown()を接続する。前者なら「終了前に未保存の変更を確認する」といった将来の要求にも素直に繋がる。ただし Ctrl+C が効かなくなる実装ミスは致命的なので、実端末での確認が必要。
検証上の注意
この不具合は実 TTY がないと再現できない。開発サンドボックスでは
main.tsが stdout 非 TTY 時に no-op のrenderShellHeadlessを強制するため、そもそも実レンダラ経路が走らない。テストで固定するなら、exitOnCtrlC経路ではなく「shutdown()が終了時に呼ばれること」を注入可能なシームとして検証する形になる。