From 70bcc16ce1f58e81c01b34d647becc90e53d5833 Mon Sep 17 00:00:00 2001 From: soryu Date: Thu, 12 Feb 2026 23:38:26 +0000 Subject: feat: makima.jp: Fix log streaming on directive page --- .../src/components/directives/DirectiveDetail.tsx | 40 +++++++++++----------- .../components/directives/DirectiveLogStream.tsx | 26 +++++++++----- .../frontend/src/hooks/useMultiTaskSubscription.ts | 26 +++++++++++--- 3 files changed, 59 insertions(+), 33 deletions(-) diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx index ab6ddbb..69fadb6 100644 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx @@ -52,7 +52,9 @@ export function DirectiveDetail({ const [goalText, setGoalText] = useState(directive.goal); const [visibleTaskIds, setVisibleTaskIds] = useState | null>(null); const [searchQuery, setSearchQuery] = useState(""); - const [isLogCollapsed, setIsLogCollapsed] = useState(true); + const [isLogCollapsed, setIsLogCollapsed] = useState( + directive.status !== "active" + ); const prevHadRunningRef = useRef(false); const badge = STATUS_BADGE[directive.status] || STATUS_BADGE.draft; @@ -97,10 +99,10 @@ export function DirectiveDetail({ return map; }, [directive.orchestratorTaskId, directive.steps]); - // Subscribe to all task outputs + // Subscribe to all task outputs (always enabled so we're ready when tasks appear) const { connected, entries, clearEntries } = useMultiTaskSubscription({ taskMap, - enabled: taskMap.size > 0, + enabled: true, }); // Auto-expand log panel when tasks start running @@ -558,23 +560,21 @@ export function DirectiveDetail({ /> - {/* Log Stream */} - {taskMap.size > 0 && ( -
- setIsLogCollapsed((prev) => !prev)} - onSetVisibleTaskIds={setVisibleTaskIds} - onSetSearchQuery={setSearchQuery} - onClear={clearEntries} - /> -
- )} + {/* Log Stream — always visible so users can see the stream area */} +
+ setIsLogCollapsed((prev) => !prev)} + onSetVisibleTaskIds={setVisibleTaskIds} + onSetSearchQuery={setSearchQuery} + onClear={clearEntries} + /> +
); } diff --git a/makima/frontend/src/components/directives/DirectiveLogStream.tsx b/makima/frontend/src/components/directives/DirectiveLogStream.tsx index d457fe3..760a0ae 100644 --- a/makima/frontend/src/components/directives/DirectiveLogStream.tsx +++ b/makima/frontend/src/components/directives/DirectiveLogStream.tsx @@ -94,7 +94,7 @@ export function DirectiveLogStream({ }, [filteredEntries.length, autoScroll]); // Count active (running) tasks - const activeTaskCount = Array.from(taskMap.keys()).length; + const activeTaskCount = taskMap.size; if (isCollapsed) { return ( @@ -106,9 +106,15 @@ export function DirectiveLogStream({ Log Stream - - [{activeTaskCount} task{activeTaskCount !== 1 ? "s" : ""}] - + {activeTaskCount > 0 ? ( + + [{activeTaskCount} task{activeTaskCount !== 1 ? "s" : ""}] + + ) : ( + + [no active tasks] + + )} {connected && entries.length > 0 && ( @@ -258,11 +264,13 @@ export function DirectiveLogStream({ > {filteredEntries.length === 0 ? (
- {entries.length === 0 - ? connected - ? "Waiting for output..." - : "No tasks subscribed" - : "No entries match filter"} + {taskMap.size === 0 + ? "No active tasks — logs will appear here when tasks start running" + : entries.length === 0 + ? connected + ? "Waiting for output..." + : "Connecting..." + : "No entries match filter"}
) : (
diff --git a/makima/frontend/src/hooks/useMultiTaskSubscription.ts b/makima/frontend/src/hooks/useMultiTaskSubscription.ts index 19d6dea..1a43b7b 100644 --- a/makima/frontend/src/hooks/useMultiTaskSubscription.ts +++ b/makima/frontend/src/hooks/useMultiTaskSubscription.ts @@ -118,10 +118,12 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption setConnected(false); wsRef.current = null; - // Reconnect if we still have subscriptions + // Reconnect if enabled and we have active subscriptions if (subscribedTasksRef.current.size > 0 && enabledRef.current) { reconnectTimeoutRef.current = window.setTimeout(() => { - connect(); + if (enabledRef.current && subscribedTasksRef.current.size > 0) { + connect(); + } }, 3000); } }; @@ -132,8 +134,8 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption // Manage subscriptions when task IDs change useEffect(() => { - if (!enabled || taskIds.length === 0) { - // Close connection if no tasks + if (!enabled) { + // Disabled — tear down connection if (wsRef.current) { subscribedTasksRef.current.clear(); wsRef.current.close(); @@ -142,6 +144,19 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption return; } + if (taskIds.length === 0) { + // No tasks yet — keep the connection alive if it exists (ready for new tasks), + // but don't create a new one just for an empty set. + // Clear any stale subscriptions. + if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { + for (const existingId of subscribedTasksRef.current) { + unsubscribeFromTask(wsRef.current, existingId); + } + } + subscribedTasksRef.current = new Set(); + return; + } + const newTaskIds = new Set(taskIds); const ws = wsRef.current; @@ -165,6 +180,9 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption subscribeToTask(ws, newId); } } + + // Update tracked subscriptions + subscribedTasksRef.current = newTaskIds; }, [taskIds, enabled, connect, subscribeToTask, unsubscribeFromTask]); // Cleanup on unmount -- cgit v1.2.3