diff options
| author | soryu <soryu@soryu.co> | 2026-02-12 23:38:26 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-12 23:38:26 +0000 |
| commit | 70bcc16ce1f58e81c01b34d647becc90e53d5833 (patch) | |
| tree | 01c8c850766fb43959fa066abb03201b1184899f | |
| parent | 355f10964c4dbec24a244a00caba5c17ed23fc65 (diff) | |
| download | soryu-makima/makima-jp--fix-log-streaming-on-directive-page-d4c53b88.tar.gz soryu-makima/makima-jp--fix-log-streaming-on-directive-page-d4c53b88.zip | |
feat: makima.jp: Fix log streaming on directive pagemakima/makima-jp--fix-log-streaming-on-directive-page-d4c53b88
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<Set<string> | 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({ /> </div> - {/* Log Stream */} - {taskMap.size > 0 && ( - <div className="px-4 py-3 border-t border-[rgba(117,170,252,0.1)]"> - <DirectiveLogStream - entries={entries} - taskMap={taskMap} - connected={connected} - visibleTaskIds={visibleTaskIds} - searchQuery={searchQuery} - isCollapsed={isLogCollapsed} - onToggleCollapse={() => setIsLogCollapsed((prev) => !prev)} - onSetVisibleTaskIds={setVisibleTaskIds} - onSetSearchQuery={setSearchQuery} - onClear={clearEntries} - /> - </div> - )} + {/* Log Stream — always visible so users can see the stream area */} + <div className="px-4 py-3 border-t border-[rgba(117,170,252,0.1)]"> + <DirectiveLogStream + entries={entries} + taskMap={taskMap} + connected={connected} + visibleTaskIds={visibleTaskIds} + searchQuery={searchQuery} + isCollapsed={isLogCollapsed} + onToggleCollapse={() => setIsLogCollapsed((prev) => !prev)} + onSetVisibleTaskIds={setVisibleTaskIds} + onSetSearchQuery={setSearchQuery} + onClear={clearEntries} + /> + </div> </div> ); } 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({ <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide"> Log Stream </span> - <span className="text-[10px] font-mono text-[#556677]"> - [{activeTaskCount} task{activeTaskCount !== 1 ? "s" : ""}] - </span> + {activeTaskCount > 0 ? ( + <span className="text-[10px] font-mono text-[#556677]"> + [{activeTaskCount} task{activeTaskCount !== 1 ? "s" : ""}] + </span> + ) : ( + <span className="text-[10px] font-mono text-[#556677]"> + [no active tasks] + </span> + )} {connected && entries.length > 0 && ( <span className="flex items-center gap-1 px-1.5 py-0.5 bg-green-400/10 border border-green-400/20 rounded"> <span className="w-1.5 h-1.5 bg-green-400 rounded-full animate-pulse" /> @@ -258,11 +264,13 @@ export function DirectiveLogStream({ > {filteredEntries.length === 0 ? ( <div className="text-[#555] italic text-[10px]"> - {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"} </div> ) : ( <div className="space-y-1"> 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 |
