This document details the fix implemented for the Messages tab in Crystal's session window. The Messages tab was intended to show JSON output from Claude Code for debugging purposes but was broken and not displaying any data.
The Messages tab in the session view was supposed to display JSON messages from Claude Code instances for debugging, but it was showing "No messages yet" even when sessions had active Claude Code conversations. The issue was that there was no mechanism to load and populate the JSON messages data.
- Missing Data Flow: The
JsonMessageViewcomponent expectedactiveSession.jsonMessagesto be populated, but this array was always empty - No Loading Mechanism: There was no IPC handler or frontend logic to fetch JSON messages separately from regular output
- Data Transformation Issue: The existing
sessions:get-outputhandler was transforming JSON messages to formatted stdout for the Output tab, losing the original JSON structure
The fix implements a complete end-to-end data flow:
Frontend (Messages Tab) → API Layer → IPC Handler → Session Manager → Database
- New IPC Handler:
sessions:get-json-messages - Frontend API Method:
API.sessions.getJsonMessages() - Session Store Method:
setSessionJsonMessages() - Auto-loading Logic: Effect hook that loads JSON messages when switching to Messages view
ipcMain.handle('sessions:get-json-messages', async (_event, sessionId: string) => {
try {
const outputs = await sessionManager.getSessionOutputs(sessionId);
// Filter to only JSON messages and include timestamp
const jsonMessages = outputs
.filter(output => output.type === 'json')
.map(output => ({
...output.data,
timestamp: output.timestamp.toISOString()
}));
return { success: true, data: jsonMessages };
} catch (error) {
return { success: false, error: 'Failed to get JSON messages' };
}
});Purpose: Extracts and returns only JSON-type outputs from session data, preserving the original JSON structure with timestamps.
getJsonMessages: (sessionId: string): Promise<IPCResponse> =>
ipcRenderer.invoke('sessions:get-json-messages', sessionId),Purpose: Exposes the new IPC handler to the frontend through the secure preload script.
async getJsonMessages(sessionId: string) {
if (!isElectron()) throw new Error('Electron API not available');
return window.electronAPI.sessions.getJsonMessages(sessionId);
}Purpose: Provides a consistent API interface with error handling for the frontend to use.
setSessionJsonMessages: (sessionId, jsonMessages) => set((state) => {
// Update both regular sessions and activeMainRepoSession
const updatedSessions = state.sessions.map(session => {
if (session.id === sessionId) {
return { ...session, jsonMessages };
}
return session;
});
let updatedActiveMainRepoSession = state.activeMainRepoSession;
if (state.activeMainRepoSession?.id === sessionId) {
updatedActiveMainRepoSession = { ...state.activeMainRepoSession, jsonMessages };
}
return { ...state, sessions: updatedSessions, activeMainRepoSession: updatedActiveMainRepoSession };
})Purpose: Updates session state with JSON messages while maintaining consistency between regular sessions and main repo sessions.
// Load JSON messages for the Messages tab
const loadJsonMessages = useCallback(async (sessionId: string) => {
try {
const response = await API.sessions.getJsonMessages(sessionId);
if (response.success) {
const jsonMessages = response.data || [];
useSessionStore.getState().setSessionJsonMessages(sessionId, jsonMessages);
}
} catch (error) {
console.error(`Error loading JSON messages:`, error);
}
}, []);
// Load JSON messages when switching to messages view
useEffect(() => {
if (!activeSession || viewMode !== 'messages') return;
loadJsonMessages(activeSession.id);
}, [activeSession?.id, viewMode, loadJsonMessages]);Purpose: Automatically loads JSON messages when the user switches to the Messages tab, ensuring data is available when needed.
interface ElectronAPI {
sessions: {
// ... other methods
getJsonMessages: (sessionId: string) => Promise<IPCResponse>;
// ... other methods
};
}Purpose: Provides type safety for the new IPC method in TypeScript.
- User Action: User clicks on "Messages" tab in session view
- View Mode Change:
viewModestate changes to 'messages' - Effect Trigger: useEffect hook detects the view mode change
- API Call:
loadJsonMessages()function callsAPI.sessions.getJsonMessages() - IPC Communication: Frontend calls the
sessions:get-json-messagesIPC handler - Data Retrieval: Backend fetches session outputs and filters for JSON messages
- Response: Filtered JSON messages returned to frontend
- State Update:
setSessionJsonMessages()updates the session store - UI Update:
JsonMessageViewcomponent re-renders with the new data
JSON messages are formatted with the following structure:
interface JsonMessage {
type: string; // e.g., 'system', 'user', 'assistant'
subtype?: string; // e.g., 'init', 'result'
content?: string; // Message content
data?: any; // Additional structured data
timestamp: string; // ISO timestamp
[key: string]: any; // Other fields from Claude Code
}- Debugging Capability: Developers can now inspect raw JSON messages from Claude Code
- Performance: JSON messages are only loaded when the Messages tab is viewed
- Real-time: Messages are loaded fresh each time the tab is accessed
- Type Safety: Full TypeScript support for the new functionality
- Consistency: Follows existing patterns in the codebase
The fix was tested by:
- Type Checking:
pnpm typecheckpasses without errors - Compilation:
pnpm build:mainsucceeds - Runtime Testing: Application starts successfully in development mode
- Integration: IPC handlers are properly registered and accessible
main/src/ipc/session.ts- Added new IPC handlermain/src/preload.ts- Exposed new method to frontendfrontend/src/utils/api.ts- Added API methodfrontend/src/stores/sessionStore.ts- Added state management methodfrontend/src/hooks/useSessionView.ts- Added loading logicfrontend/src/types/electron.d.ts- Added TypeScript definitions
Potential improvements could include:
- Caching: Cache JSON messages to avoid repeated API calls
- Real-time Updates: Listen for new JSON messages and update the tab live
- Filtering: Add filtering/search capabilities within the Messages tab
- Export: Allow exporting JSON messages for external debugging tools
The Messages tab fix provides a complete solution for debugging Claude Code sessions by making the raw JSON messages accessible through a clean, type-safe interface. The implementation follows Crystal's architectural patterns and maintains consistency with existing code.