From 9c995d204cdbf80dbb6b1fd5de6aa08533285802 Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 21 Jan 2026 06:33:59 +0100 Subject: [PATCH] feat(tui): integrate RalphPanel with live state polling - Poll inner loop state from state-inner.json every 500ms - Display RalphPanel when loop is enabled and has data - Auto-adjust terminal height when panel is shown - Add innerLoopState and innerTodos to session manager hook Co-Authored-By: Claude Opus 4.5 --- src/tui/App.tsx | 20 +++++++++-- src/tui/hooks/useSessionManager.ts | 55 +++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/tui/App.tsx b/src/tui/App.tsx index 48b7a0e3..16983451 100644 --- a/src/tui/App.tsx +++ b/src/tui/App.tsx @@ -14,6 +14,7 @@ import { TabBar } from './components/TabBar.js'; import { TerminalView } from './components/TerminalView.js'; import { StatusBar } from './components/StatusBar.js'; import { HelpOverlay } from './components/HelpOverlay.js'; +import { RalphPanel } from './components/RalphPanel.js'; import { useSessionManager } from './hooks/useSessionManager.js'; import type { ScreenSession } from '../types.js'; @@ -42,6 +43,8 @@ export function App(): React.ReactElement { prevSession, sendInput, terminalOutput, + innerLoopState, + innerTodos, } = useSessionManager(); // Calculate terminal height based on stdout dimensions @@ -224,7 +227,13 @@ export function App(): React.ReactElement { ); } - // Render main view with tabs, terminal, and status bar + // Check if Ralph panel should be visible (enabled and has data) + const showRalphPanel = innerLoopState?.enabled && (innerLoopState.active || innerTodos.length > 0); + + // Adjust terminal height if Ralph panel is shown + const adjustedTerminalHeight = showRalphPanel ? terminalHeight - 6 : terminalHeight; + + // Render main view with tabs, terminal, ralph panel, and status bar return ( + {showRalphPanel && ( + + )} + ); diff --git a/src/tui/hooks/useSessionManager.ts b/src/tui/hooks/useSessionManager.ts index b5b104fe..12600048 100644 --- a/src/tui/hooks/useSessionManager.ts +++ b/src/tui/hooks/useSessionManager.ts @@ -12,9 +12,10 @@ import { existsSync, readFileSync, writeFileSync, watchFile, unwatchFile, unlink import { homedir } from 'os'; import { join } from 'path'; import { execSync } from 'child_process'; -import type { ScreenSession } from '../../types.js'; +import type { ScreenSession, InnerLoopState, InnerTodoItem, InnerSessionState } from '../../types.js'; const SCREENS_FILE = join(homedir(), '.claudeman', 'screens.json'); +const INNER_STATE_FILE = join(homedir(), '.claudeman', 'state-inner.json'); const OUTPUT_POLL_INTERVAL = 500; // Poll terminal output every 500ms interface SessionManagerState { @@ -22,6 +23,8 @@ interface SessionManagerState { activeSessionId: string | null; activeSession: ScreenSession | null; terminalOutput: string; + innerLoopState: InnerLoopState | null; + innerTodos: InnerTodoItem[]; refreshSessions: () => void; selectSession: (sessionId: string) => void; createSession: () => Promise; @@ -65,6 +68,22 @@ function loadSessions(): ScreenSession[] { } } +/** + * Load inner state for a session + */ +function loadInnerState(sessionId: string): InnerSessionState | null { + try { + if (!existsSync(INNER_STATE_FILE)) { + return null; + } + const data = readFileSync(INNER_STATE_FILE, 'utf-8'); + const allStates = JSON.parse(data) as Record; + return allStates[sessionId] || null; + } catch { + return null; + } +} + /** * Hook for managing sessions in the TUI */ @@ -72,6 +91,8 @@ export function useSessionManager(): SessionManagerState { const [sessions, setSessions] = useState([]); const [activeSessionId, setActiveSessionId] = useState(null); const [terminalOutput, setTerminalOutput] = useState(''); + const [innerLoopState, setInnerLoopState] = useState(null); + const [innerTodos, setInnerTodos] = useState([]); const outputBufferRef = useRef(''); // Load sessions on mount and watch for changes @@ -102,6 +123,36 @@ export function useSessionManager(): SessionManagerState { // Get active session const activeSession = sessions.find((s) => s.sessionId === activeSessionId) || null; + // Poll inner state for active session + useEffect(() => { + if (!activeSessionId) { + setInnerLoopState(null); + setInnerTodos([]); + return; + } + + const pollInnerState = () => { + const state = loadInnerState(activeSessionId); + if (state) { + setInnerLoopState(state.loop); + setInnerTodos(state.todos); + } else { + setInnerLoopState(null); + setInnerTodos([]); + } + }; + + // Initial poll + pollInnerState(); + + // Set up polling interval (same as output polling) + const intervalId = setInterval(pollInnerState, OUTPUT_POLL_INTERVAL); + + return () => { + clearInterval(intervalId); + }; + }, [activeSessionId]); + // Poll terminal output for active session useEffect(() => { if (!activeSessionId || !activeSession) return; @@ -282,6 +333,8 @@ export function useSessionManager(): SessionManagerState { activeSessionId, activeSession, terminalOutput, + innerLoopState, + innerTodos, refreshSessions, selectSession, createSession,