Fix todos and delete useProcessesLogs (vibe-kanban) (#755)
* Perfect! The implementation is complete and working. Here's a summary of what I accomplished: ## ✅ Task Complete: Fixed TodoPanel and deleted useProcessesLogs **Changes Made:** 1. **Created [`EntriesContext`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-3135-fix-todos/frontend/src/contexts/EntriesContext.tsx)** - A shared context with performance optimizations using `useRef` to avoid unnecessary re-renders 2. **Modified [`TaskDetailsPanel`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-3135-fix-todos/frontend/src/components/tasks/TaskDetailsPanel.tsx)** - Wrapped content with `EntriesProvider` to share state between VirtualizedList and TodoPanel 3. **Updated [`VirtualizedList`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-3135-fix-todos/frontend/src/components/logs/VirtualizedList.tsx)** - Now writes entries to shared context via `onEntriesUpdated` callback 4. **Simplified [`TodoPanel`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-3135-fix-todos/frontend/src/components/tasks/TodoPanel.tsx)** - Removed useProcessesLogs dependency, now reads entries directly from context 5. **Deleted `useProcessesLogs.ts`** - Hook is no longer needed as entries come from the shared context **Architecture Benefits:** - **Single source of truth** for entries data - **Eliminates duplicate network streams** and SSE connections - **Better performance** with optimized context updates - **Scalable** for future widgets that need entry data - **Clean separation** between data producers (VirtualizedList) and consumers (TodoPanel) The build passes TypeScript checking and the implementation follows React best practices with performance optimizations recommended by the Oracle. * Perfect! The implementation is complete and working. Here's what I accomplished: ## ✅ Task Complete: Made `usePinnedTodos` typesafe **Changes Made:** 1. **Updated [`usePinnedTodos`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-3135-fix-todos/frontend/src/hooks/usePinnedTodos.ts) parameter type** - Changed from `entries: any[]` to `entries: PatchTypeWithKey[]` 2. **Fixed entry structure logic:** - Changed `entry.channel === 'normalized'` → `entry.type === 'NORMALIZED_ENTRY'` - Changed `entry.payload` → `entry.content` 3. **Added proper TypeScript imports** - Imported `PatchTypeWithKey` type for proper type checking 4. **Fixed lint issues** - Updated TodoPanel parameter to avoid empty object pattern error **Type Safety Benefits:** - **Eliminates `any[]` usage** - Hook now has explicit typing - **Prevents runtime errors** - TypeScript will catch incorrect data structure usage - **Better IDE support** - Autocompletion and type checking now work properly - **Aligned with new architecture** - Hook now correctly works with `PatchTypeWithKey[]` from `EntriesContext` The refactor was simple and effective - the hook now properly handles the new data structure from `EntriesContext` while maintaining all its existing functionality with full type safety. * Excellent! All the lint errors have been fixed. Here's a summary of what I accomplished: **Changes Made:** 1. **Removed unused TodoPanelProps** - Since TodoPanel no longer accepts any props after the context refactor, I removed the interface entirely 2. **Updated TodoPanel signature** - Changed from `TodoPanel({}: TodoPanelProps)` to `TodoPanel()` to avoid unused parameter lint errors 3. **Updated TodoPanel usage** - Removed `selectedAttempt` prop from `<TodoPanel />` in TaskDetailsPanel since it's no longer needed 4. **Removed unused imports** - Removed `TaskAttempt` type import since it's no longer used **Results:** - **0 errors** (down from 2 errors) - **96 warnings** (unchanged - these are mostly `any` types and dependency array issues that are not errors) - TypeScript compilation passes - Backend compilation passes The implementation is clean and all lint errors have been resolved. The remaining warnings are pre-existing and not related to our changes. * useState internally * fmt
This commit is contained in:
committed by
GitHub
parent
cc66eb96a3
commit
a069304f25
@@ -8,6 +8,7 @@ import {
|
||||
} from '@virtuoso.dev/message-list';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import DisplayConversationEntry from '../NormalizedConversation/DisplayConversationEntry';
|
||||
import { useEntries } from '@/contexts/EntriesContext';
|
||||
import {
|
||||
AddEntryType,
|
||||
PatchTypeWithKey,
|
||||
@@ -69,11 +70,13 @@ const ItemContent: VirtuosoMessageListProps<
|
||||
const VirtualizedList = ({ attempt }: VirtualizedListProps) => {
|
||||
const [channelData, setChannelData] = useState<ChannelData>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { setEntries, reset } = useEntries();
|
||||
|
||||
// When attempt changes, set loading
|
||||
// When attempt changes, set loading and reset entries
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
}, [attempt.id]);
|
||||
reset();
|
||||
}, [attempt.id, reset]);
|
||||
|
||||
const onEntriesUpdated = (
|
||||
newEntries: PatchTypeWithKey[],
|
||||
@@ -88,6 +91,7 @@ const VirtualizedList = ({ attempt }: VirtualizedListProps) => {
|
||||
}
|
||||
|
||||
setChannelData({ data: newEntries, scrollModifier });
|
||||
setEntries(newEntries); // Update shared context
|
||||
if (loading) {
|
||||
setLoading(newLoading);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import TodoPanel from '@/components/tasks/TodoPanel';
|
||||
import { TabNavContext } from '@/contexts/TabNavigationContext';
|
||||
import { ProcessSelectionProvider } from '@/contexts/ProcessSelectionContext';
|
||||
import { ReviewProvider } from '@/contexts/ReviewProvider';
|
||||
import { EntriesProvider } from '@/contexts/EntriesContext';
|
||||
import { AttemptHeaderCard } from './AttemptHeaderCard';
|
||||
import { inIframe } from '@/vscode/bridge';
|
||||
import { TaskRelationshipViewer } from './TaskRelationshipViewer';
|
||||
@@ -117,87 +118,136 @@ export function TaskDetailsPanel({
|
||||
<TabNavContext.Provider value={{ activeTab, setActiveTab }}>
|
||||
<ProcessSelectionProvider>
|
||||
<ReviewProvider>
|
||||
{/* Backdrop - only on smaller screens (overlay mode) */}
|
||||
{!hideBackdrop && (
|
||||
<EntriesProvider>
|
||||
{/* Backdrop - only on smaller screens (overlay mode) */}
|
||||
{!hideBackdrop && (
|
||||
<div
|
||||
className={getBackdropClasses(isFullScreen || false)}
|
||||
onClick={onClose}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Panel */}
|
||||
<div
|
||||
className={getBackdropClasses(isFullScreen || false)}
|
||||
onClick={onClose}
|
||||
/>
|
||||
)}
|
||||
className={
|
||||
className || getTaskPanelClasses(isFullScreen || false)
|
||||
}
|
||||
>
|
||||
<div className={getTaskPanelInnerClasses()}>
|
||||
{!inIframe() && (
|
||||
<TaskDetailsHeader
|
||||
task={task}
|
||||
onClose={onClose}
|
||||
onEditTask={onEditTask}
|
||||
onDeleteTask={onDeleteTask}
|
||||
hideCloseButton={hideBackdrop}
|
||||
isFullScreen={isFullScreen}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Panel */}
|
||||
<div
|
||||
className={
|
||||
className || getTaskPanelClasses(isFullScreen || false)
|
||||
}
|
||||
>
|
||||
<div className={getTaskPanelInnerClasses()}>
|
||||
{!inIframe() && (
|
||||
<TaskDetailsHeader
|
||||
task={task}
|
||||
onClose={onClose}
|
||||
onEditTask={onEditTask}
|
||||
onDeleteTask={onDeleteTask}
|
||||
hideCloseButton={hideBackdrop}
|
||||
isFullScreen={isFullScreen}
|
||||
/>
|
||||
)}
|
||||
{isFullScreen ? (
|
||||
<div className="flex-1 min-h-0 flex">
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`w-[28rem] shrink-0 border-r overflow-y-auto ${inIframe() ? 'hidden' : ''}`}
|
||||
>
|
||||
{/* Fullscreen sidebar shows title and description above edit/delete */}
|
||||
<div className="space-y-2 p-3">
|
||||
<TaskTitleDescription task={task} />
|
||||
</div>
|
||||
|
||||
{isFullScreen ? (
|
||||
<div className="flex-1 min-h-0 flex">
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`w-[28rem] shrink-0 border-r overflow-y-auto ${inIframe() ? 'hidden' : ''}`}
|
||||
>
|
||||
{/* Fullscreen sidebar shows title and description above edit/delete */}
|
||||
<div className="space-y-2 p-3">
|
||||
<TaskTitleDescription task={task} />
|
||||
</div>
|
||||
{/* Current Attempt / Actions */}
|
||||
<TaskDetailsToolbar
|
||||
task={task}
|
||||
projectId={projectId}
|
||||
projectHasDevScript={projectHasDevScript}
|
||||
forceCreateAttempt={forceCreateAttempt}
|
||||
onLeaveForceCreateAttempt={
|
||||
onLeaveForceCreateAttempt
|
||||
}
|
||||
attempts={attempts}
|
||||
selectedAttempt={selectedAttempt}
|
||||
setSelectedAttempt={setSelectedAttempt}
|
||||
// hide actions in sidebar; moved to header in fullscreen
|
||||
/>
|
||||
|
||||
{/* Current Attempt / Actions */}
|
||||
<TaskDetailsToolbar
|
||||
task={task}
|
||||
projectId={projectId}
|
||||
projectHasDevScript={projectHasDevScript}
|
||||
forceCreateAttempt={forceCreateAttempt}
|
||||
onLeaveForceCreateAttempt={onLeaveForceCreateAttempt}
|
||||
attempts={attempts}
|
||||
selectedAttempt={selectedAttempt}
|
||||
setSelectedAttempt={setSelectedAttempt}
|
||||
// hide actions in sidebar; moved to header in fullscreen
|
||||
/>
|
||||
{/* Task Breakdown (TODOs) */}
|
||||
<TodoPanel />
|
||||
|
||||
{/* Task Breakdown (TODOs) */}
|
||||
<TodoPanel selectedAttempt={selectedAttempt} />
|
||||
{/* Task Relationships */}
|
||||
<TaskRelationshipViewer
|
||||
selectedAttempt={selectedAttempt}
|
||||
onNavigateToTask={onNavigateToTask}
|
||||
task={task}
|
||||
tasksById={tasksById}
|
||||
/>
|
||||
</aside>
|
||||
|
||||
{/* Task Relationships */}
|
||||
<TaskRelationshipViewer
|
||||
selectedAttempt={selectedAttempt}
|
||||
onNavigateToTask={onNavigateToTask}
|
||||
task={task}
|
||||
tasksById={tasksById}
|
||||
/>
|
||||
</aside>
|
||||
{/* Main content */}
|
||||
<main className="flex-1 min-h-0 min-w-0 flex flex-col">
|
||||
{selectedAttempt && (
|
||||
<>
|
||||
<TabNavigation
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
selectedAttempt={selectedAttempt}
|
||||
/>
|
||||
|
||||
{/* Main content */}
|
||||
<main className="flex-1 min-h-0 min-w-0 flex flex-col">
|
||||
{selectedAttempt && (
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
{activeTab === 'diffs' ? (
|
||||
<DiffTab selectedAttempt={selectedAttempt} />
|
||||
) : activeTab === 'processes' ? (
|
||||
<ProcessesTab
|
||||
attemptId={selectedAttempt?.id}
|
||||
/>
|
||||
) : (
|
||||
<LogsTab selectedAttempt={selectedAttempt} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TaskFollowUpSection
|
||||
task={task}
|
||||
selectedAttemptId={selectedAttempt?.id}
|
||||
jumpToLogsTab={jumpToLogsTab}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{attempts.length === 0 ? (
|
||||
<TaskDetailsToolbar
|
||||
task={task}
|
||||
projectId={projectId}
|
||||
projectHasDevScript={projectHasDevScript}
|
||||
forceCreateAttempt={forceCreateAttempt}
|
||||
onLeaveForceCreateAttempt={
|
||||
onLeaveForceCreateAttempt
|
||||
}
|
||||
attempts={attempts}
|
||||
selectedAttempt={selectedAttempt}
|
||||
setSelectedAttempt={setSelectedAttempt}
|
||||
// hide actions in sidebar; moved to header in fullscreen
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<TabNavigation
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
<AttemptHeaderCard
|
||||
attemptNumber={attemptNumber}
|
||||
totalAttempts={attempts.length}
|
||||
selectedAttempt={selectedAttempt}
|
||||
task={task}
|
||||
projectId={projectId}
|
||||
// onCreateNewAttempt={() => {
|
||||
// // TODO: Implement create new attempt
|
||||
// console.log('Create new attempt');
|
||||
// }}
|
||||
onJumpToDiffFullScreen={jumpToDiffFullScreen}
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
{activeTab === 'diffs' ? (
|
||||
<DiffTab selectedAttempt={selectedAttempt} />
|
||||
) : activeTab === 'processes' ? (
|
||||
<ProcessesTab attemptId={selectedAttempt?.id} />
|
||||
) : (
|
||||
<LogsTab selectedAttempt={selectedAttempt} />
|
||||
)}
|
||||
</div>
|
||||
{selectedAttempt && (
|
||||
<LogsTab selectedAttempt={selectedAttempt} />
|
||||
)}
|
||||
|
||||
<TaskFollowUpSection
|
||||
task={task}
|
||||
@@ -206,52 +256,11 @@ export function TaskDetailsPanel({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{attempts.length === 0 ? (
|
||||
<TaskDetailsToolbar
|
||||
task={task}
|
||||
projectId={projectId}
|
||||
projectHasDevScript={projectHasDevScript}
|
||||
forceCreateAttempt={forceCreateAttempt}
|
||||
onLeaveForceCreateAttempt={onLeaveForceCreateAttempt}
|
||||
attempts={attempts}
|
||||
selectedAttempt={selectedAttempt}
|
||||
setSelectedAttempt={setSelectedAttempt}
|
||||
// hide actions in sidebar; moved to header in fullscreen
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<AttemptHeaderCard
|
||||
attemptNumber={attemptNumber}
|
||||
totalAttempts={attempts.length}
|
||||
selectedAttempt={selectedAttempt}
|
||||
task={task}
|
||||
projectId={projectId}
|
||||
// onCreateNewAttempt={() => {
|
||||
// // TODO: Implement create new attempt
|
||||
// console.log('Create new attempt');
|
||||
// }}
|
||||
onJumpToDiffFullScreen={jumpToDiffFullScreen}
|
||||
/>
|
||||
|
||||
{selectedAttempt && (
|
||||
<LogsTab selectedAttempt={selectedAttempt} />
|
||||
)}
|
||||
|
||||
<TaskFollowUpSection
|
||||
task={task}
|
||||
selectedAttemptId={selectedAttempt?.id}
|
||||
jumpToLogsTab={jumpToLogsTab}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</EntriesProvider>
|
||||
</ReviewProvider>
|
||||
</ProcessSelectionProvider>
|
||||
</TabNavContext.Provider>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Circle, CircleCheckBig, CircleDotDashed } from 'lucide-react';
|
||||
import { useProcessesLogs } from '@/hooks/useProcessesLogs';
|
||||
import { useEntries } from '@/contexts/EntriesContext';
|
||||
import { usePinnedTodos } from '@/hooks/usePinnedTodos';
|
||||
import { useAttemptExecution } from '@/hooks';
|
||||
import { shouldShowInLogs } from '@/constants/processes';
|
||||
import type { TaskAttempt } from 'shared/types';
|
||||
import { Card } from '../ui/card';
|
||||
|
||||
function getStatusIcon(status?: string) {
|
||||
@@ -16,26 +12,8 @@ function getStatusIcon(status?: string) {
|
||||
return <Circle aria-hidden className="h-4 w-4 text-muted-foreground" />;
|
||||
}
|
||||
|
||||
interface TodoPanelProps {
|
||||
selectedAttempt: TaskAttempt | null;
|
||||
}
|
||||
|
||||
export function TodoPanel({ selectedAttempt }: TodoPanelProps) {
|
||||
const { attemptData } = useAttemptExecution(selectedAttempt?.id);
|
||||
|
||||
const filteredProcesses = useMemo(
|
||||
() =>
|
||||
(attemptData.processes || []).filter(
|
||||
(p) => shouldShowInLogs(p.run_reason) && !p.dropped
|
||||
),
|
||||
[
|
||||
attemptData.processes
|
||||
?.map((p) => `${p.id}:${p.status}:${p.dropped}`)
|
||||
.join(','),
|
||||
]
|
||||
);
|
||||
|
||||
const { entries } = useProcessesLogs(filteredProcesses, true);
|
||||
export function TodoPanel() {
|
||||
const { entries } = useEntries();
|
||||
const { todos } = usePinnedTodos(entries);
|
||||
|
||||
// Only show once the agent has created subtasks
|
||||
|
||||
54
frontend/src/contexts/EntriesContext.tsx
Normal file
54
frontend/src/contexts/EntriesContext.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useMemo,
|
||||
useCallback,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import type { PatchTypeWithKey } from '@/hooks/useConversationHistory';
|
||||
|
||||
interface EntriesContextType {
|
||||
entries: PatchTypeWithKey[];
|
||||
setEntries: (entries: PatchTypeWithKey[]) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const EntriesContext = createContext<EntriesContextType | null>(null);
|
||||
|
||||
interface EntriesProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const EntriesProvider = ({ children }: EntriesProviderProps) => {
|
||||
const [entries, setEntriesState] = useState<PatchTypeWithKey[]>([]);
|
||||
|
||||
const setEntries = useCallback((newEntries: PatchTypeWithKey[]) => {
|
||||
setEntriesState(newEntries);
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setEntriesState([]);
|
||||
}, []);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
entries,
|
||||
setEntries,
|
||||
reset,
|
||||
}),
|
||||
[entries, setEntries, reset]
|
||||
);
|
||||
|
||||
return (
|
||||
<EntriesContext.Provider value={value}>{children}</EntriesContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useEntries = (): EntriesContextType => {
|
||||
const context = useContext(EntriesContext);
|
||||
if (!context) {
|
||||
throw new Error('useEntries must be used within an EntriesProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { TodoItem } from 'shared/types';
|
||||
import type { PatchTypeWithKey } from '@/hooks/useConversationHistory';
|
||||
|
||||
interface UsePinnedTodosResult {
|
||||
todos: TodoItem[];
|
||||
@@ -10,14 +11,16 @@ interface UsePinnedTodosResult {
|
||||
* Hook that extracts and maintains the latest TODO state from normalized conversation entries.
|
||||
* Filters for TodoManagement ActionType entries and returns the most recent todo list.
|
||||
*/
|
||||
export const usePinnedTodos = (entries: any[]): UsePinnedTodosResult => {
|
||||
export const usePinnedTodos = (
|
||||
entries: PatchTypeWithKey[]
|
||||
): UsePinnedTodosResult => {
|
||||
return useMemo(() => {
|
||||
let latestTodos: TodoItem[] = [];
|
||||
let lastUpdatedTime: string | null = null;
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.channel === 'normalized' && entry.payload) {
|
||||
const normalizedEntry = entry.payload as any;
|
||||
if (entry.type === 'NORMALIZED_ENTRY' && entry.content) {
|
||||
const normalizedEntry = entry.content as any;
|
||||
|
||||
if (
|
||||
normalizedEntry.entry_type?.type === 'tool_use' &&
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import type {
|
||||
ExecutionProcess,
|
||||
NormalizedEntry,
|
||||
PatchType,
|
||||
} from 'shared/types';
|
||||
import type { UnifiedLogEntry, ProcessStartPayload } from '@/types/logs';
|
||||
import { useEventSourceManager } from './useEventSourceManager';
|
||||
|
||||
interface UseProcessesLogsResult {
|
||||
entries: UnifiedLogEntry[];
|
||||
isConnected: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const MAX_ENTRIES = 5000;
|
||||
|
||||
export const useProcessesLogs = (
|
||||
processes: ExecutionProcess[],
|
||||
enabled: boolean
|
||||
): UseProcessesLogsResult => {
|
||||
const getEndpoint = useCallback((process: ExecutionProcess) => {
|
||||
// Coding agents use normalized logs endpoint, scripts use raw logs endpoint
|
||||
// Both endpoints now return PatchType objects via JSON patches
|
||||
const isCodingAgent = process.run_reason === 'codingagent';
|
||||
return isCodingAgent
|
||||
? `/api/execution-processes/${process.id}/normalized-logs`
|
||||
: `/api/execution-processes/${process.id}/raw-logs`;
|
||||
}, []);
|
||||
|
||||
const initialData = useMemo(() => ({ entries: [] }), []);
|
||||
|
||||
const { processData, isConnected, error } = useEventSourceManager({
|
||||
processes,
|
||||
enabled,
|
||||
getEndpoint,
|
||||
initialData,
|
||||
});
|
||||
|
||||
const entries = useMemo(() => {
|
||||
const allEntries: UnifiedLogEntry[] = [];
|
||||
let entryCounter = 0;
|
||||
|
||||
// Iterate through processes in order, adding process marker followed by logs
|
||||
processes.forEach((process) => {
|
||||
const data = processData[process.id];
|
||||
if (!data?.entries) return;
|
||||
|
||||
// Add process start marker first
|
||||
const processStartPayload: ProcessStartPayload = {
|
||||
processId: process.id,
|
||||
runReason: process.run_reason,
|
||||
startedAt: process.started_at,
|
||||
status: process.status,
|
||||
action: process.executor_action,
|
||||
};
|
||||
|
||||
allEntries.push({
|
||||
id: `${process.id}-start`,
|
||||
ts: entryCounter++,
|
||||
processId: process.id,
|
||||
processName: process.run_reason,
|
||||
channel: 'process_start',
|
||||
payload: processStartPayload,
|
||||
});
|
||||
|
||||
// Then add all logs for this process (skip the injected PROCESS_START entry)
|
||||
data.entries.forEach(
|
||||
(
|
||||
patchEntry:
|
||||
| PatchType
|
||||
| { type: 'PROCESS_START'; content: ProcessStartPayload },
|
||||
index: number
|
||||
) => {
|
||||
// Skip the injected PROCESS_START entry since we handle it above
|
||||
if (patchEntry.type === 'PROCESS_START') return;
|
||||
|
||||
let channel: UnifiedLogEntry['channel'];
|
||||
let payload: string | NormalizedEntry;
|
||||
|
||||
switch (patchEntry.type) {
|
||||
case 'STDOUT':
|
||||
channel = 'stdout';
|
||||
payload = patchEntry.content;
|
||||
break;
|
||||
case 'STDERR':
|
||||
channel = 'stderr';
|
||||
payload = patchEntry.content;
|
||||
break;
|
||||
case 'NORMALIZED_ENTRY':
|
||||
channel = 'normalized';
|
||||
payload = patchEntry.content;
|
||||
break;
|
||||
default:
|
||||
// Skip unknown patch types
|
||||
return;
|
||||
}
|
||||
|
||||
allEntries.push({
|
||||
id: `${process.id}-${index}`,
|
||||
ts: entryCounter++,
|
||||
processId: process.id,
|
||||
processName: process.run_reason,
|
||||
channel,
|
||||
payload,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Limit entries (no sorting needed since we build in order)
|
||||
return allEntries.slice(-MAX_ENTRIES);
|
||||
}, [processData, processes]);
|
||||
|
||||
return { entries, isConnected, error };
|
||||
};
|
||||
Reference in New Issue
Block a user